WPF MVVM hierarchy selected item

2020-03-04 07:56发布

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?

3条回答
【Aperson】
2楼-- · 2020-03-04 08:23

Your ViewModel should not also be a collection, it should contain collections and properties which are bound to the view. SelectedRun should be a property of this ViewModel (MountainViewModel) not Mountains. MountainViewModel should expose the Mountains collection and SelectedRun and should be bound to the listboxes' ItemsSource and SelectedItem.

查看更多
淡お忘
3楼-- · 2020-03-04 08:33

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; }
}
查看更多
Rolldiameter
4楼-- · 2020-03-04 08:38

Here's how I would do it. You want to make sure that you fire the INotifyPropertyChanged event when setting the properties. To get the Selected Run you'll have to get 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}">
查看更多
登录 后发表回答