Выбранный элемент иерархии WPF MVVM

В настоящее время я реализую приложение, которое отображает иерархию, используя ListBoxes (пожалуйста, не предлагайте использовать TreeView, ListBoxes необходимы).

Похоже на в статье: CollectionViewSource WPF (с исходным кодом).

введите здесь описание изображения

Классы:

public class Mountains : ObservableCollection<Mountain>
{
    public ObservableCollection<Lift> Lifts { get; }

    public string Name { get; }
}

public class Lift
{
    public ObservableCollection<string> Runs { get; }
}

В примере используется CollectionViewSource экземпляров (см. XAML) для упрощения дизайна. Экземпляр класса Mountains является DataContext для окна.


Проблема в том, что я хотел бы, чтобы класс Mountains имел свойство SelectedRun, и он должен быть установлен для текущего выбранного запуска.

public class Mountains : ObservableCollection<Mountain>
{
    public ObservableCollection<Lift> Lifts { get; }

    public string Name { get; }

    public string SelectedRun { get; set; }
}

Может быть, я упустил что-то из основного принципа, но как я могу этого добиться?


person Sergey Vyacheslavovich Brunov    schedule 22.03.2012    source источник


Ответы (3)


Вы можете прочитать об использовании '/' в привязках. См. раздел "указатели текущих элементов" в этой статье MSDN.

Вот мое решение:

Ксамл

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <TextBlock Margin="5" Grid.Row="0" Grid.Column="0" Text="Mountains"/>
    <TextBlock Margin="5" Grid.Row="0" Grid.Column="1" Text="Lifts"/>
    <TextBlock Margin="5" Grid.Row="0" Grid.Column="2" Text="Runs"/>

    <ListBox Grid.Row="1" Grid.Column="0" Margin="5" 
             ItemsSource="{Binding Mountains}" DisplayMemberPath="Name" 
             IsSynchronizedWithCurrentItem="True" />

    <ListBox Grid.Row="1" Grid.Column="1" Margin="5" 
             ItemsSource="{Binding Mountains/Lifts}" DisplayMemberPath="Name" 
             IsSynchronizedWithCurrentItem="True"/>

    <ListBox Grid.Row="1" Grid.Column="2" Margin="5" 
             ItemsSource="{Binding Mountains/Lifts/Runs}" 
             IsSynchronizedWithCurrentItem="True" 
             SelectedItem="{Binding SelectedRun}"/>
</Grid>

C# (обратите внимание, вам не нужно реализовывать INotifyPropertyChanged, если только свойства не будут изменены, а не просто выбраны)

public class MountainsViewModel
{
    public MountainsViewModel()
    {
        Mountains = new ObservableCollection<Mountain>
                        {
                            new Mountain
                                {
                                    Name = "Whistler",
                                    Lifts = new ObservableCollection<Lift>
                                                {
                                                    new Lift
                                                        {
                                                            Name = "Big Red",
                                                            Runs = new ObservableCollection<string>
                                                                       {
                                                                           "Headwall",
                                                                           "Fisheye",
                                                                           "Jimmy's"
                                                                       }
                                                        },
                                                    new Lift
                                                        {
                                                            Name = "Garbanzo",
                                                            Runs = new ObservableCollection<string>
                                                                       {
                                                                           "Headwall1",
                                                                           "Fisheye1",
                                                                           "Jimmy's1"
                                                                       }
                                                        },
                                                    new Lift {Name = "Orange"},
                                                }

                                },
                            new Mountain
                                {
                                    Name = "Stevens",
                                    Lifts = new ObservableCollection<Lift>
                                                {
                                                    new Lift {Name = "One"},
                                                    new Lift {Name = "Two"},
                                                    new Lift {Name = "Three"},
                                                }

                                },
                            new Mountain {Name = "Crystal"},
                        };
    }

    public string Name { get; set; }
    private string _selectedRun;
    public string SelectedRun
    {
        get { return _selectedRun; }
        set
        {
            Debug.WriteLine(value);
            _selectedRun = value;
        }
    }

    public ObservableCollection<Mountain> Mountains { get; set; }
}

public class Mountain
{
    public string Name { get; set; }

    public ObservableCollection<Lift> Lifts { get; set; }
}

public class Lift
{
    public string Name { get; set; }

    public ObservableCollection<string> Runs { get; set; }
}
person Phil    schedule 22.03.2012
comment
Большое спасибо! Это именно то, что я хочу! Кстати, а если у меня ICollectionView вместо ObservableCollection? Я пробовал ICollectionView, и решение также отлично работает. Я из любопытства, есть ли способ получить SelectedRun на MountainsViewModel даже без привязки ListBox SelectedItem? Может быть, как-то использовать ICollectionView свойства/события? - person Sergey Vyacheslavovich Brunov; 23.03.2012
comment
WPF автоматически оборачивает любую коллекцию в соответствующую внутреннюю реализацию ICollectionView, так что в итоге особой разницы нет. Если SelectedRun был свойством зависимости для элемента управления (а не модели представления), вы могли бы связать его с Mountains/Lifts/Runs/, обратите внимание на завершающий символ «/», ... возможно, я не пробовал. - person Phil; 23.03.2012
comment
Принятый! Еще раз спасибо! знак равно - person Sergey Vyacheslavovich Brunov; 23.03.2012

Вот как бы я это сделал. Вы хотите убедиться, что запускаете событие INotifyPropertyChanged при установке свойств. Чтобы получить Selected Run, вам нужно получить MainViewModel.SelectedMountain.SelectedLift.SelectedRun.

public class MainViewModel: ViewModelBae
{
    ObservableCollection<MountainViewModel> mountains
    public ObservableCollection<MountainViewModel> Mountains
    {
        get { return mountains; }
        set
        {
            if (mountains != value)
            {
                mountains = value;
                RaisePropertyChanged("Mountains");
            }
        }
    }
    MountainViewModel selectedMountain
    public MountainViewModel SelectedMountain
    {
        get { return selectedMountain; }
        set
        {
            if (selectedMountain != value)
            {
                selectedMountain = value;
                RaisePropertyChanged("SelectedMountain");
            }
        }
    }
}

public class MountainViewModel: ViewModelBae
{
    ObservableCollection<LiftViewModel> lifts
    public ObservableCollection<LiftViewModel> Lifts
    {
        get { return lifts; }
        set
        {
            if (lifts != value)
            {
                lifts = value;
                RaisePropertyChanged("Lifts");
            }
        }
    }
    LiftViewModel selectedLift
    public LiftViewModel SelectedLift
    {
        get { return selectedLift; }
        set
        {
            if (selectedLift != value)
            {
                selectedLift = value;
                RaisePropertyChanged("SelectedLift");
            }
        }
    }
}

public class LiftViewModel: ViewModelBae
{
    ObservableCollection<string> runs
    public ObservableCollection<string> Runs
    {
        get { return runs; }
        set
        {
            if (runs != value)
            {
                runs = value;
                RaisePropertyChanged("Runs");
            }
        }
    }
    string selectedRun
    public string SelectedRun
    {
        get { return selectedLift; }
        set
        {
            if (selectedLift != value)
            {
                selectedLift = value;
                RaisePropertyChanged("SelectedLift");
            }
        }
    }
}

<ListBox ItemsSource="{Binding Mountains}" SelectedItem="{Binding SelectedMountain, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.Lifts}" SelectedItem="{Binding SelectedMountain.SelectedLift, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.SelectedLift.Runs}" SelectedItem="{Binding SelectedMountain.SelectedLift.SelectedRun, Mode=TwoWay}">
person evanb    schedule 22.03.2012

Ваша ViewModel также не должна быть коллекцией, она должна содержать коллекции и свойства, привязанные к представлению. SelectedRun должен быть свойством этой ViewModel (MountainViewModel), а не Mountains. MountainViewModel должен предоставлять коллекцию Mountains и SelectedRun и должен быть привязан к ItemsSource и SelectedItem списков.

person Slugart    schedule 22.03.2012
comment
Спасибо! Но это очень простой пример. Классы, которые я описал, являются очень упрощенными версиями ViewModels. Я пробовал решение с привязкой к SelectedRun с использованием свойства SelectedItem: оно не запоминает последний выбранный пробег внутри категории лифта. - person Sergey Vyacheslavovich Brunov; 22.03.2012
comment
Вы имеете в виду, что когда пользователь выбирает другую гору или подъем, свойство SelectedRun очищается? - person Slugart; 22.03.2012
comment
@Серж, не могли бы вы показать нам, как вы привязываетесь к SelectedRun в xaml? - person Slugart; 22.03.2012
comment
подробности см. в ответе Фила. - person Sergey Vyacheslavovich Brunov; 23.03.2012