且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用WPF MVVM使selecteditemchanged事件在树视图中工作

更新时间:2022-05-20 02:57:33

Below is an update with a working example for getting the selected item for a TreeView.



I’ve separated the hierarchical collection from the data collection by using CollectionViewSource. Any changes to the collection data will be reflected in the hierarchical collection used by the view.



I have also included an example of how to sort the branches using a Converter class. It could also be done in the CollectionViewSource. If you want to use the latter, I’ll leave that exercise up to you. ;)



Here is a base class to wrap INotifyPropertyChanged:

Below is an update with a working example for getting the selected item for a TreeView.

I've separated the hierarchical collection from the data collection by using CollectionViewSource. Any changes to the collection data will be reflected in the hierarchical collection used by the view.

I have also included an example of how to sort the branches using a Converter class. It could also be done in the CollectionViewSource. If you want to use the latter, I'll leave that exercise up to you. ;)

Here is a base class to wrap INotifyPropertyChanged:
public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<TValue>.Default.Equals(field, default(TValue)) || !field.Equals(newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}



The data model:


The data model:

public class EmployeeModel : ObservableBase
{
    private int id;
    public int Id
    {
        get => id;
        set => Set(ref id, value);
    }

    private string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }

    private string role;
    public string Role
    {
        get => role;
        set => Set(ref role, value);
    }

    private int managerId;
    public int ManagerId
    {
        get => managerId;
        set => Set(ref managerId, value);
    }
}



A wrapper ViewModel for each data model. This handles the hierarchical collection and TreeViewItem selection :


A wrapper ViewModel for each data model. This handles the hierarchical collection and TreeViewItem selection :

public class EmployeeViewModel : ObservableBase
{
    public EmployeeModel Employee { get; set; }
    public CollectionViewSource Subordinates { get; set; }

    private bool isSelected;
    public bool IsSelected
    {
        get => isSelected;
        set => Set(ref isSelected, value);
    }
}



Now for the main view model for the view/window - binds the collection to the UI and handles the SelectedItem:


Now for the main view model for the view/window - binds the collection to the UI and handles the SelectedItem:

public class MainViewModel : ObservableBase
{
    public MainViewModel()
    {
        MockData();
    }

    private EmployeeViewModel selectedEmployee;
    public EmployeeViewModel SelectedEmployee
    {
        get => selectedEmployee;
        set => Set(ref selectedEmployee, value);
    }

    // employee list
    private ObservableCollection<EmployeeViewModel> employeesData
        = new ObservableCollection<EmployeeViewModel>();

    // employee list hierarchical view for UI
    public CollectionViewSource Employees { get; set; }

    private void MockData()
    {
        // Listen for changes to the collection
        employeesData.CollectionChanged += EmployyeeDataCollectionChanged;

        // Now add employees
        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 1,
                Name = "Bob"
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 2,
                Name = "Paul",
                ManagerId = 3
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 3,
                Name = "Mary",
                ManagerId = 1
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 4,
                Name = "Joe",
                ManagerId = 1
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 5,
                Name = "Jane",
                ManagerId = 2
            }
        });

        // Build hierarchical View for UI
        Employees = new CollectionViewSource { Source = employeesData };
        Employees.View.Filter =  new Predicate<object>((o)
                => (o as EmployeeViewModel)?.Employee.ManagerId == 0);

        foreach (var employee in employeesData)
        {
            employee.Subordinates = new CollectionViewSource
            { Source = employeesData };
            employee.Subordinates.View.Filter = new Predicate<object>((o)
                => (o as EmployeeViewModel)?.Employee.ManagerId
                    == employee.Employee.Id);
        }
    }

    // Listen or unlisten to employees as they're added or removed
    private void EmployyeeDataCollectionChanged(object sender,
                                                NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    var employee = e.NewItems[i] as EmployeeViewModel;
                    employee.PropertyChanged += EmployeePropertyChanged;

                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (int i = 0; i < e.OldItems.Count; i++)
                {
                    var employee = e.NewItems[i] as EmployeeViewModel;
                    employee.PropertyChanged -= EmployeePropertyChanged;
                }
                break;
        }
    }

    // Only listen for the employee being selected
    private void EmployeePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(EmployeeViewModel.IsSelected))
        {
            SelectedEmployee = sender as EmployeeViewModel;
        }
    }
}



Note: We are listening to all the data ViewModels (EmployeeViewModel) to identify which item is being selected.



Now that the Data is ready, we can build & bind the UI:


Note: We are listening to all the data ViewModels (EmployeeViewModel) to identify which item is being selected.

Now that the Data is ready, we can build & bind the UI:

<Window

    x:Class="TreeViewSelectedItem.MainWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"



    mc:Ignorable="d"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"



    xmlns:local="clr-namespace:TreeViewSelectedItem"



    Title="CodeProject  -  TREEVIEW SELECTED ITEM"

    WindowStartupLocation="CenterScreen" Height="500" Width="300">

    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

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

        <TreeView ItemsSource="{Binding Employees.View}">
            <TreeView.Resources>

                <local:TvBranchSortPropertyConverter x:Key="SortConverter"/>

                <HierarchicalDataTemplate DataType="{x:Type local:EmployeeViewModel}"

                                          ItemsSource="{Binding Subordinates.View, 
                                          Converter={StaticResource SortConverter},
                                          ConverterParameter=Employee.Name}">
                    <TextBlock Text="{Binding Employee.Name}"

                               VerticalAlignment="Center"/>
                </HierarchicalDataTemplate>

            </TreeView.Resources>

            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                </Style>
            </TreeView.ItemContainerStyle>

        </TreeView>

        <TextBlock Text="{Binding SelectedEmployee.Employee.Name, FallbackValue=None}"

                   Grid.Row="1" Margin="10"/>

    </Grid>

</Window>



NOTE: As we are binding w ith a CollectionViewSource, we need to bind to it’s View property.



Lastly, here is the converter for custom sorting of the TreeView nodes:


NOTE: As we are binding with a CollectionViewSource, we need to bind to it's View property.

Lastly, here is the converter for custom sorting of the TreeView nodes:

public class TvBranchSortPropertyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var view = value as ListCollectionView;
        view.SortDescriptions.Add(new SortDescription(parameter.ToString(), ListSortDirection.Ascending));
        return view;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}