Search code examples
c#wpfmvvmcollectionviewsource

WPF MVVM hierarchy selected item


I am currently implementing the application that displays hierarchy using ListBoxes (please do not suggest using TreeView, ListBoxes are needed).

It looks like that in the article: WPF’s CollectionViewSource (with source code).

enter image description here

Classes:

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

    public string Name { get; }
}

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

The example uses CollectionViewSource instances (see XAML) to simplify the design. An instance of Mountains class is the DataContext for the window.


The problem is: I would like that the Mountains class to have SelectedRun property and it should be set to currently selected run.

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

    public string Name { get; }

    public string SelectedRun { get; set; }
}

Maybe I've missed something basic principle, but how can I achieve this?


Solution

  • You may want to read about the use of '/' in bindings. See the section 'current item pointers' on this MSDN article.

    Here's my solution:

    Xaml

        <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# (note, you don't need to implement INotifyPropertyChanged unless the properties will be changed and not just selected)

    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; }
    }