Treeview, ICollectionView和绑定到SelectedValue或SelectedItemChanged

huangapple go评论58阅读模式
英文:

Treeview, ICollectionView and binding to SelectedValue or SelectedItemChanged

问题

I will only provide translations for the code and not the explanations or additional content. Here is the translated code:

<Window x:Class="WPFUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:viewmodels="clr-namespace:WPFUI.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MainWindowViewModel}"
        mc:Ignorable="d"
        FontSize="16"
        Title="Title" Height="450" Width="800">
    <Grid Background="Black">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="4*" />
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" Grid.ColumnSpan="3">
            <DockPanel>
                <Menu DockPanel.Dock="Top">
                    <MenuItem Header="_File">
                        <MenuItem Header="_Open" />
                    </MenuItem>
                </Menu>
            </DockPanel>
        </Grid>
        <Grid Grid.Row="1">
            <TextBox Text="{Binding FilterValue, UpdateSourceTrigger=PropertyChanged}" />
        </Grid>
        <Grid Grid.Row="2" Grid.Column="1" Background="DarkGreen">
            <DataGrid x:Name="Grid_Upper" Grid.Row="2"
                ItemsSource="{Binding TreeViewCollection}"
                AlternatingRowBackground="GreenYellow"
                HeadersVisibility="Column"  AutoGenerateColumns="False"
                CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="True"
                CanUserResizeColumns="True" CanUserResizeRows="True" CanUserSortColumns="True"
                ScrollViewer.VerticalScrollBarVisibility="Auto"
                VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.IsVirtualizingWhenGrouping="True"
                VirtualizingStackPanel.VirtualizationMode="Recycling">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                    <DataGridTemplateColumn Header="Text">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Text}" TextWrapping="Wrap" Padding="10,10,10,10" MinWidth="100" Width="300" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
                <DataGrid.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <Label Content="{Binding Name}" FontWeight="Bold"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </DataGrid.GroupStyle>
            </DataGrid>
        </Grid>
        <Grid Grid.Row="2" Grid.RowSpan="2">
            <TreeView x:Name="TreeViewMyModelList" ItemsSource="{Binding TreeViewCollection.Groups}"                      
                VirtualizingStackPanel.IsVirtualizing="True"
                VirtualizingStackPanel.VirtualizationMode="Recycling">
                <Behaviors:Interaction.Triggers>
                    <Behaviors:EventTrigger EventName="SelectedItemChanged">
                    </Behaviors:EventTrigger>
                </Behaviors:Interaction.Triggers>
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=Items}">
                        <TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}">
                        </TextBlock>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
                <TreeView.ItemContainerStyle>
                    <Style TargetType="TreeViewItem">
                        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                    </Style>
                </TreeView.ItemContainerStyle>
            </TreeView>
        </Grid>
        <Grid Grid.Row="3" Grid.Column="1" Background="LightBlue">
            <TextBlock>Second Grid shows group of selected item in TreeViewMyModelList</TextBlock>
        </Grid>
        <Grid Grid.Row="2" Grid.Column="2" Background="green" Grid.RowSpan="2">
            <TextBlock>More information about MyModel</TextBlock>
        </Grid>
        <Grid Grid.Row="4" Height="300">
        </Grid>
        <Grid Grid.Row="5" Grid.ColumnSpan="3" Background="Gray">
        </Grid>
    </Grid>
</Window>
英文:

I am totally new to whole WPF world. I want to create a simple (for now) app with only one window but in MVVM way.

I'm build it in .NET 7 with nuget packages: CommunityToolkit.Mvvm 8.1.0 and Microsoft.Xaml.Behaviors.Wpf 1.1.39.

Layout of my app

I'm looking for a way to bind SelectedItem property in TreeViewMyModelList.

I want to retrieve MyModel object to show more detailed information on right panel (green).

The problem about SelectedItem is that it has only getter, no setter.

Also I wanted to handle SelectedItemChanged event, but since I want to do it in MVVM way I don't want to mess with my MainWindow.xaml.cs, the only things are there InitializeComponent(); and DataContext = new MainWindowViewModel();. Decided to add Microsoft.Xaml.Behaviors.Wpf but I don't even know how to use it.

Another thing that I want to achieve is to show on second DataGrid (lower, blue) a list of parent node selected item.

For example: if I click on Some_Name2 item then show list of MyModel objects grouped by GroupName Some_Group_01.

If I click on Some_Group6 item then show list of MyModel objects grouped by ShortName short_03 then by every group in it.

Upper DataGrid will be used to show data depends on FilterValue.

Why am I using ICollectionView? To share same collection between TreeView and DataGrid. Also for purpose of sorting, grouping and filtering.

Why am I virtualizing data? There is about 155,000 MyModel objects in my collection so it's quite laggy.

I would appreciate every tip and trick Treeview, ICollectionView和绑定到SelectedValue或SelectedItemChanged

MyModel.cs

public sealed class MyModel
{
    public string Name { get; set; }
    public string Text { get; set; }
    public string GroupName { get; set; }
    public string ShortName { get; set; }
    public int Number { get; set; }
    public string Description { get; set; }
    public string EvenMoreInfo { get; set; }
}

MainWindowViewModel.cs

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private string _filterValue = string.Empty;

    private readonly List&lt;LoadedDataFromFile&gt; _loadedDataFromFile;

    private List&lt;MyModel&gt; myModelList;

    public ICollectionView TreeViewCollection { get; }

    public MainWindowViewModel()
    {
        // loading data from file and transforming it into `MyModel` list
        // for this example i populate it here
        myModelList = new List&lt;MyModel&gt;()
        {
            new MyModel {Name=&quot;Some_Name1&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=1},
            new MyModel {Name=&quot;Some_Name2&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=2},
            new MyModel {Name=&quot;Some_Name3&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=3},
            new MyModel {Name=&quot;Some_Name4&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=4},

            new MyModel {Name=&quot;Some_Name5&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=5},
            new MyModel {Name=&quot;Some_Name6&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=6},
            new MyModel {Name=&quot;Some_Name7&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=7},
            new MyModel {Name=&quot;Some_Name8&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=8},

            new MyModel {Name=&quot;Some_Name9&quot;, GroupName=&quot;Some_Group_05&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=9},
            new MyModel {Name=&quot;Some_Name10&quot;, GroupName=&quot;Some_Group_05&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=10},
            new MyModel {Name=&quot;Some_Name11&quot;, GroupName=&quot;Some_Group_06&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=11},
            new MyModel {Name=&quot;Some_Name12&quot;, GroupName=&quot;Some_Group_07&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=12},
        };


        TreeViewCollection = CollectionViewSource.GetDefaultView(myModelList);

        TreeViewCollection.Filter = FilterCollection;
        var pgd = new PropertyGroupDescription(nameof(MyModel.ShortName));
        pgd.SortDescriptions.Add(new SortDescription(&quot;Name&quot;, ListSortDirection.Ascending));
        TreeViewCollection.GroupDescriptions.Add(pgd);

        pgd = new PropertyGroupDescription(nameof(MyModel.GroupName));
        pgd.SortDescriptions.Add(new SortDescription(&quot;Name&quot;, ListSortDirection.Ascending));
        TreeViewCollection.GroupDescriptions.Add(pgd);

        TreeViewCollection.SortDescriptions.Add(new SortDescription(nameof(MyModel.Number), ListSortDirection.Ascending));
    }

    private bool FilterCollection(object obj)
    {
        if (obj is not MyModel model)
        {
            return false;
        }

        return model.Name!.ToLower().Contains(FilterValue.ToLower());
    }

    partial void OnFilterValueChanged(string value)
    {
        TreeViewCollection.Refresh();
    }
}

MainWindow.xaml

&lt;Window x:Class=&quot;WPFUI.MainWindow&quot;
xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
xmlns:Behaviors=&quot;http://schemas.microsoft.com/xaml/behaviors&quot;
xmlns:viewmodels=&quot;clr-namespace:WPFUI.ViewModels&quot; d:DataContext=&quot;{d:DesignInstance Type=viewmodels:MainWindowViewModel}&quot;
mc:Ignorable=&quot;d&quot;
FontSize=&quot;16&quot;
Title=&quot;Title&quot; Height=&quot;450&quot; Width=&quot;800&quot;
&gt;
&lt;Grid Background=&quot;Black&quot;&gt;
&lt;Grid.ColumnDefinitions&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;4*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;2*&quot;/&gt;
&lt;/Grid.ColumnDefinitions&gt;
&lt;Grid.RowDefinitions&gt;
&lt;RowDefinition Height=&quot;auto&quot; /&gt;
&lt;RowDefinition Height=&quot;auto&quot; /&gt;
&lt;RowDefinition Height=&quot;4*&quot; /&gt;
&lt;RowDefinition Height=&quot;4*&quot; /&gt;
&lt;RowDefinition Height=&quot;*&quot; /&gt;
&lt;RowDefinition Height=&quot;20&quot; /&gt;
&lt;/Grid.RowDefinitions&gt;
&lt;Grid Grid.Row=&quot;0&quot; Grid.ColumnSpan=&quot;3&quot;&gt;
&lt;DockPanel&gt;
&lt;Menu DockPanel.Dock=&quot;Top&quot;&gt;
&lt;MenuItem Header=&quot;_File&quot;&gt;
&lt;MenuItem Header=&quot;_Open&quot; /&gt;
&lt;/MenuItem&gt;
&lt;/Menu&gt;
&lt;/DockPanel&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;1&quot;&gt;
&lt;TextBox Text=&quot;{Binding FilterValue, UpdateSourceTrigger=PropertyChanged}&quot; /&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;2&quot; Grid.Column=&quot;1&quot; Background=&quot;DarkGreen&quot;&gt;
&lt;DataGrid x:Name=&quot;Grid_Upper&quot; Grid.Row=&quot;2&quot;
ItemsSource=&quot;{Binding TreeViewCollection}&quot;
AlternatingRowBackground=&quot;GreenYellow&quot;
HeadersVisibility=&quot;Column&quot;  AutoGenerateColumns=&quot;False&quot;
CanUserAddRows=&quot;False&quot; CanUserDeleteRows=&quot;False&quot; CanUserReorderColumns=&quot;True&quot;
CanUserResizeColumns=&quot;True&quot; CanUserResizeRows=&quot;True&quot; CanUserSortColumns=&quot;True&quot;
ScrollViewer.VerticalScrollBarVisibility=&quot;Auto&quot;
VirtualizingStackPanel.IsVirtualizing=&quot;True&quot; VirtualizingStackPanel.IsVirtualizingWhenGrouping=&quot;True&quot;
VirtualizingStackPanel.VirtualizationMode=&quot;Recycling&quot;
&gt;
&lt;DataGrid.Columns&gt;
&lt;DataGridTextColumn Header=&quot;Name&quot; Binding=&quot;{Binding Name}&quot; /&gt;
&lt;DataGridTemplateColumn Header=&quot;Text&quot;&gt;
&lt;DataGridTemplateColumn.CellTemplate&gt;
&lt;DataTemplate&gt;
&lt;TextBlock Text=&quot;{Binding Text}&quot; TextWrapping=&quot;Wrap&quot; Padding=&quot;10,10,10,10&quot; MinWidth=&quot;100&quot; Width=&quot;300&quot; /&gt;
&lt;/DataTemplate&gt;
&lt;/DataGridTemplateColumn.CellTemplate&gt;
&lt;/DataGridTemplateColumn&gt;
&lt;/DataGrid.Columns&gt;
&lt;DataGrid.GroupStyle&gt;
&lt;GroupStyle&gt;
&lt;GroupStyle.HeaderTemplate&gt;
&lt;DataTemplate&gt;
&lt;Label Content=&quot;{Binding Name}&quot; FontWeight=&quot;Bold&quot;/&gt;
&lt;/DataTemplate&gt;
&lt;/GroupStyle.HeaderTemplate&gt;
&lt;/GroupStyle&gt;
&lt;/DataGrid.GroupStyle&gt;
&lt;/DataGrid&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;2&quot; Grid.RowSpan=&quot;2&quot;&gt;
&lt;TreeView x:Name=&quot;TreeViewMyModelList&quot; ItemsSource=&quot;{Binding TreeViewCollection.Groups}&quot;                      
VirtualizingStackPanel.IsVirtualizing=&quot;True&quot;
VirtualizingStackPanel.VirtualizationMode=&quot;Recycling&quot;
&gt;
&lt;Behaviors:Interaction.Triggers&gt;
&lt;Behaviors:EventTrigger EventName=&quot;SelectedItemChanged&quot;&gt;
&lt;/Behaviors:EventTrigger&gt;
&lt;/Behaviors:Interaction.Triggers&gt;
&lt;TreeView.ItemTemplate&gt;
&lt;HierarchicalDataTemplate ItemsSource=&quot;{Binding Path=Items}&quot;&gt;
&lt;TextBlock VerticalAlignment=&quot;Center&quot; Text=&quot;{Binding Path=Name}&quot;&gt;&lt;/TextBlock&gt;
&lt;/HierarchicalDataTemplate&gt;
&lt;/TreeView.ItemTemplate&gt;
&lt;TreeView.ItemContainerStyle&gt;
&lt;Style TargetType=&quot;TreeViewItem&quot;&gt;
&lt;Setter Property=&quot;IsSelected&quot; Value=&quot;{Binding IsSelected, Mode=TwoWay}&quot; /&gt;
&lt;/Style&gt;
&lt;/TreeView.ItemContainerStyle&gt;
&lt;/TreeView&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;3&quot; Grid.Column=&quot;1&quot; Background=&quot;LightBlue&quot;&gt;
&lt;TextBlock&gt;Second Grid shows group of selected item in TreeViewMyModelList&lt;/TextBlock&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;2&quot; Grid.Column=&quot;2&quot; Background=&quot;green&quot; Grid.RowSpan=&quot;2&quot;&gt;
&lt;TextBlock&gt;More information about MyModel&lt;/TextBlock&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;4&quot; Height=&quot;300&quot;&gt;
&lt;/Grid&gt;
&lt;Grid Grid.Row=&quot;5&quot; Grid.ColumnSpan=&quot;3&quot; Background=&quot;Gray&quot;&gt;
&lt;/Grid&gt;
&lt;/Grid&gt;
&lt;/Window&gt;

答案1

得分: 1

ICollectionView用于在TreeViewDataGrid之间共享相同的集合,并且用于排序、分组和筛选。您不需要在这种情况下显式绑定到ICollectionView,因为绑定引擎将隐式使用集合的默认ICollectionView作为绑定源。

虚拟化数据是因为集合中有大约155,000个MyModel对象,这会导致性能问题。DataGrid默认情况下会虚拟化行,但TreeView不会。显示这么多项不明智,您可以考虑实施数据虚拟化和UI虚拟化,以提高性能。用户不会查看155,000个项,您可以允许用户在加载任何项之前应用筛选器。如果仍然有太多项,可以考虑在需要时动态获取项目/树级别。

要实现性能,并且期望显示足够多的项以需要滚动,您必须将ScrollViewer.VerticalScrollBarVisibility设置为Visible。将其设置为Auto会导致ScrollViewer不断测量其布局,以检查是否需要呈现滚动条。

至于两种解决方案,您可以选择根据您的应用程序需要的情况来实现其中一种。第一个解决方案使用TreeView.SelectedItemChanged事件来将选定项传递给DataContext,而第二个解决方案则添加了一个IsSelected属性到项目模型,并使用相关的SelectedUnselected事件来监视选择更改。您可以根据您的需求选择其中一个解决方案。

英文:

> "Why am I using ICollectionView? To share same collection between TreeView and DataGrid. Also for purpose of sorting, grouping and
> filtering."

ICollectionView is not relevant in terms of collection i.e. instance sharing unless you want to allow multiple data views to implement independent sorting/filtering/grouping (which you are not doing - both data views bind to the same ICollectionView). In this case you would have to create an ICollectionView for each data view explicitly (the default view can't be used).

Note, when binding to a collection, the binding engine will always implicitly use the collection's default ICollectionView as a binding source. This means binding to the collection as usual and using its default ICollectionView to e.g. filter/sort/group its conatined items is sufficient. You don't have to bind to the ICollectionView explicitly to see the results.

> "Why am I virtualizing data? There is about 155,000 MyModel objects in my collection so it's quite laggy."

DataGrid is virtualizing rows by default (your configuration is redundant). TreView is not. You would have to modify the TreeView template to enable UI virtualization.
Displaying 155k items is not wise. Aside from UI virtualization you should consider data virtualization too. A user will never view 155k items. Maybe he's interested in 10 items.
You can let the user apply a filter before you load any items. If this still results in too many items, you can consider to fetch items/tree levels dynamically in addition. For example, you preload the first three levels of the pre-filtered tree. Then when the user expands a level you read and add a new level.

If you are concerned about performance and expect to display enough items to require scrolling, you must set ScrollViewer.VerticalScrollBarVisibility to Visible. Setting it to Auto causes the ScrollViewer to measure its layout continuously to check whether the scroll bars have to be rendered or not.

Solution 1

A simple MVVM solution is to handle the TreeView.SelectedItemChanged event and send the value of the read-only TreeView.SelectedItem property to the DataContext:

MainWindow.xaml

&lt;Window&gt;
&lt;Window.Resources&gt;
&lt;!-- Bind the control&#39;s SelectedTreeViewItem property to the DataContext --&gt;
&lt;Style TargetType=&quot;local:MainWindow&quot;&gt;
&lt;Setter Property=&quot;SelectedTreeViewItem&quot;
Value=&quot;{Binding SelectedDataItem}&quot; /&gt;
&lt;/Style&gt;
&lt;/Window.Resources&gt;
&lt;TreeView SelectedItemChanged=&quot;TreeView_SelectedItemChanged&quot; /&gt;
&lt;/Window&gt;

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public object SelectedTreeViewItem
  {
    get =&gt; (object)GetValue(SelectedTreeViewItemProperty);
    set =&gt; SetValue(SelectedTreeViewItemProperty, value);
  }

  public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register(
    &quot;SelectedTreeViewItem&quot;,
    typeof(object),
    typeof(MainWindow),
    new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

  private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs&lt;object&gt; e)
  {
    var treeView = sender as System.Windows.Controls.TreeView;
    this.SelectedTreeViewItem = treeView.SelectedItem;
  }
}

Solution 2

Alternatively, you can add a IsSelected property to the item model. Providing a related Selected and Unselected event allows to monitor selection changes. This solution requires explicit lifetime management of the data items because of the attached event listeners. This becomes very important if the source collection is dynamic (items are added/removed frequently).

TreeViewItemModel

class TreeViewItemModel : INotifyPropertyChanged
{
  // TODO::Implement INotifyPropertyChanged and raise PropertyCHanged event from property setters

  public event EvenetHandler Selected;
  public event EvenetHandler Unselected;

  // TODO::Raise Selected and Unselected event from setter
  public bool IsSelected { get; set; }
}

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  public ObservableCollection&lt;TreeViewItemModel&gt; TreeViewData { get; }

  public void AddTreeViewData(TreeViewItemModel dataItem)
  {
    this.TreeViewData.Add(dataItem);
    dataItem.Selected += OnTreeViewItemSelected;
    dataItem.Selected += OnTreeViewItemUnselected;
  }

  public void RemoveTreeViewData(TreeViewItemModel dataItem)
  {
    this.TreeViewData.Remove(dataItem);
    dataItem.Selected -= OnTreeViewItemSelected;
    dataItem.Selected -= OnTreeViewItemUnselected;
  }

  public void OnTreeViewItemSelected(object sender, EventArgs e)
  {
  }

  public void OnTreeViewItemUnselected(object sender, EventArgs e)
  {
  }
}

MainWindow.xaml

&lt;TreeView&gt;
&lt;TreeView.ItemContainerStyle&gt;
&lt;Style TargetType=&quot;TreeViewItem&quot;&gt;
&lt;!-- Connect the item container to the item --&gt;
&lt;Setter Property=&quot;IsSelected&quot; Value=&quot;{Binding IsSelected}&quot; /&gt;
&lt;/Style&gt;
&lt;/TreeView.ItemContainerStyle&gt;
&lt;/TreeView&gt;

答案2

得分: 0

以下是您要的翻译部分:

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
    public object SelectedTreeViewItem
    {
        get => (object)GetValue(SelectedTreeViewItemProperty);
        set => SetValue(SelectedTreeViewItemProperty, value);
    }

    public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register(
      nameof(SelectedTreeViewItem),
      typeof(object),
      typeof(MainWindow),
      new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var treeView = sender as System.Windows.Controls.TreeView;
        this.SelectedTreeViewItem = treeView.SelectedItem;
    }    
}

MainWindow.xaml

<Window x:Class="WPFUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewmodels="clr-namespace:WPFUI.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MainWindowViewModel}"
        xmlns:local="clr-namespace:WPFUI"
                     
        mc:Ignorable="d"
        FontSize="16"
        Title="Title" Height="450" Width="800"
        >
    <Window.Resources>

        <Style TargetType="local:MainWindow">
            <Setter Property="SelectedTreeViewItem"
              Value="{Binding SelectedDataItem}" />
        </Style>
    </Window.Resources>

    <Grid Background="Black">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="4*" />
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Grid.ColumnSpan="3">
            <DockPanel>
                <Menu DockPanel.Dock="Top">
                    <MenuItem Header="_File">
                        <MenuItem Header="_Open" />
                    </MenuItem>
                </Menu>
            </DockPanel>
        </Grid>

        <Grid Grid.Row="1">
            <TextBox Text="{Binding FilterValue, UpdateSourceTrigger=PropertyChanged}" />
        </Grid>

        <Grid Grid.Row="2" Grid.Column="1" Background="DarkGreen">
            <DataGrid x:Name="Grid_Upper" Grid.Row="2"
                ItemsSource="{Binding TreeViewCollection}"
                AlternatingRowBackground="GreenYellow"
                HeadersVisibility="Column"  AutoGenerateColumns="False"
                CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="True"
                CanUserResizeColumns="True" CanUserResizeRows="True" CanUserSortColumns="True"
                ScrollViewer.VerticalScrollBarVisibility="Visible"
                VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.IsVirtualizingWhenGrouping="True"
                VirtualizingStackPanel.VirtualizationMode="Recycling"
                >

                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                    <DataGridTemplateColumn Header="Text">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Text}" TextWrapping="Wrap" Padding="10,10,10,10" MinWidth="100" Width="300" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>

                <DataGrid.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <Label Content="{Binding Name}" FontWeight="Bold"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </DataGrid.GroupStyle>

            </DataGrid>
        </Grid>


        <Grid Grid.Row="2" Grid.RowSpan="2">
            <TreeView x:Name="TreeViewMyModelList" ItemsSource="{Binding TreeViewCollection.Groups}"                      
                VirtualizingStackPanel.IsVirtualizing="True"
                VirtualizingStackPanel.VirtualizationMode="Recycling"
                      SelectedItemChanged="TreeView_SelectedItemChanged"
                      ScrollViewer.VerticalScrollBarVisibility="Visible"
                >

                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=Items}">
                        <TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}"></TextBlock>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
                <TreeView.ItemContainerStyle>
                    <Style TargetType="TreeViewItem">
                        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                    </Style>
                </TreeView.ItemContainerStyle>

            </TreeView>
        </Grid>

        <Grid Grid.Row="3" Grid.Column="1" Background="LightBlue">
            <TextBlock>Second Grid shows group of selected item in TreeViewMyModelList</TextBlock>
        </Grid>

        <Grid Grid.Row="2" Grid.Column="2" Background="green" Grid.RowSpan="2">
            <!--<TextBlock>More information about MyModel</TextBlock>-->
            <TextBlock Text="{Binding SelectedDataItem.Name}"></TextBlock>
        </Grid>

        <Grid Grid.Row="4" Height="300">
        </Grid>

        <Grid Grid.Row="5" Grid.ColumnSpan="3" Background="Gray">
        </Grid>

    </Grid>
</Window>

MainWindowViewModel.cs

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private string _filterValue = string.Empty;

    [ObservableProperty]
    private object _selectedDataItem;

    private readonly List<LoadedDataFromFile> _loadedDataFromFile;

    private List<MyModel> myModelList;

    public ICollectionView TreeViewCollection { get; }

    public MainWindowViewModel()
    {
        // loading data from file and transforming it into `MyModel` list
        // for this example i populate it here
        myModelList = new List<MyModel>()
        {
            new MyModel {Name="Some_Name1", GroupName="Some_Group_01", ShortName="short_01", Text="some_text", Number=1},
            new MyModel {Name="Some_Name2", GroupName="Some_Group_01", ShortName="short_01", Text="some_text", Number=2},
            new MyModel {Name="Some_Name3", GroupName="Some_Group_02", ShortName="short_01", Text="some_text", Number=3},
            new MyModel {Name="Some_Name4", GroupName="Some_Group_02", ShortName="short_01", Text="some_text", Number=4},

            new MyModel {Name="Some_Name5", GroupName="Some

<details>
<summary>英文:</summary>

Answer above explains everything, but I decided to post full code after edits. I removed `Microsoft.Xaml.Behaviors.Wpf 1.1.39` nuget package since I&#39;m not using it here.

**MainWindow.xaml.cs**
```cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
    public object SelectedTreeViewItem
    {
        get =&gt; (object)GetValue(SelectedTreeViewItemProperty);
        set =&gt; SetValue(SelectedTreeViewItemProperty, value);
    }

    public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register(
      nameof(SelectedTreeViewItem),
      typeof(object),
      typeof(MainWindow),
      new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs&lt;object&gt; e)
    {
        var treeView = sender as System.Windows.Controls.TreeView;
        this.SelectedTreeViewItem = treeView.SelectedItem;
    }    
}

MainWindow.xaml

&lt;Window x:Class=&quot;WPFUI.MainWindow&quot;
        xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
        xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
        xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
        xmlns:viewmodels=&quot;clr-namespace:WPFUI.ViewModels&quot; d:DataContext=&quot;{d:DesignInstance Type=viewmodels:MainWindowViewModel}&quot;
        xmlns:local=&quot;clr-namespace:WPFUI&quot;
                     
        mc:Ignorable=&quot;d&quot;
        FontSize=&quot;16&quot;
        Title=&quot;Title&quot; Height=&quot;450&quot; Width=&quot;800&quot;
        &gt;
    &lt;Window.Resources&gt;

        &lt;Style TargetType=&quot;local:MainWindow&quot;&gt;
            &lt;Setter Property=&quot;SelectedTreeViewItem&quot;
              Value=&quot;{Binding SelectedDataItem}&quot; /&gt;
        &lt;/Style&gt;
    &lt;/Window.Resources&gt;

    &lt;Grid Background=&quot;Black&quot;&gt;

        &lt;Grid.ColumnDefinitions&gt;
            &lt;ColumnDefinition Width=&quot;*&quot;/&gt;
            &lt;ColumnDefinition Width=&quot;4*&quot;/&gt;
            &lt;ColumnDefinition Width=&quot;2*&quot;/&gt;
        &lt;/Grid.ColumnDefinitions&gt;

        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition Height=&quot;auto&quot; /&gt;
            &lt;RowDefinition Height=&quot;auto&quot; /&gt;
            &lt;RowDefinition Height=&quot;4*&quot; /&gt;
            &lt;RowDefinition Height=&quot;4*&quot; /&gt;
            &lt;RowDefinition Height=&quot;*&quot; /&gt;
            &lt;RowDefinition Height=&quot;20&quot; /&gt;
        &lt;/Grid.RowDefinitions&gt;

        &lt;Grid Grid.Row=&quot;0&quot; Grid.ColumnSpan=&quot;3&quot;&gt;
            &lt;DockPanel&gt;
                &lt;Menu DockPanel.Dock=&quot;Top&quot;&gt;
                    &lt;MenuItem Header=&quot;_File&quot;&gt;
                        &lt;MenuItem Header=&quot;_Open&quot; /&gt;
                    &lt;/MenuItem&gt;
                &lt;/Menu&gt;
            &lt;/DockPanel&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;1&quot;&gt;
            &lt;TextBox Text=&quot;{Binding FilterValue, UpdateSourceTrigger=PropertyChanged}&quot; /&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;2&quot; Grid.Column=&quot;1&quot; Background=&quot;DarkGreen&quot;&gt;
            &lt;DataGrid x:Name=&quot;Grid_Upper&quot; Grid.Row=&quot;2&quot;
                ItemsSource=&quot;{Binding TreeViewCollection}&quot;
                AlternatingRowBackground=&quot;GreenYellow&quot;
                HeadersVisibility=&quot;Column&quot;  AutoGenerateColumns=&quot;False&quot;
                CanUserAddRows=&quot;False&quot; CanUserDeleteRows=&quot;False&quot; CanUserReorderColumns=&quot;True&quot;
                CanUserResizeColumns=&quot;True&quot; CanUserResizeRows=&quot;True&quot; CanUserSortColumns=&quot;True&quot;
                ScrollViewer.VerticalScrollBarVisibility=&quot;Visible&quot;
                VirtualizingStackPanel.IsVirtualizing=&quot;True&quot; VirtualizingStackPanel.IsVirtualizingWhenGrouping=&quot;True&quot;
                VirtualizingStackPanel.VirtualizationMode=&quot;Recycling&quot;
                &gt;

                &lt;DataGrid.Columns&gt;
                    &lt;DataGridTextColumn Header=&quot;Name&quot; Binding=&quot;{Binding Name}&quot; /&gt;
                    &lt;DataGridTemplateColumn Header=&quot;Text&quot;&gt;
                        &lt;DataGridTemplateColumn.CellTemplate&gt;
                            &lt;DataTemplate&gt;
                                &lt;TextBlock Text=&quot;{Binding Text}&quot; TextWrapping=&quot;Wrap&quot; Padding=&quot;10,10,10,10&quot; MinWidth=&quot;100&quot; Width=&quot;300&quot; /&gt;

                            &lt;/DataTemplate&gt;
                        &lt;/DataGridTemplateColumn.CellTemplate&gt;
                    &lt;/DataGridTemplateColumn&gt;
                &lt;/DataGrid.Columns&gt;

                &lt;DataGrid.GroupStyle&gt;
                    &lt;GroupStyle&gt;
                        &lt;GroupStyle.HeaderTemplate&gt;
                            &lt;DataTemplate&gt;
                                &lt;Label Content=&quot;{Binding Name}&quot; FontWeight=&quot;Bold&quot;/&gt;
                            &lt;/DataTemplate&gt;
                        &lt;/GroupStyle.HeaderTemplate&gt;
                    &lt;/GroupStyle&gt;
                &lt;/DataGrid.GroupStyle&gt;

            &lt;/DataGrid&gt;
        &lt;/Grid&gt;


        &lt;Grid Grid.Row=&quot;2&quot; Grid.RowSpan=&quot;2&quot;&gt;
            &lt;TreeView x:Name=&quot;TreeViewMyModelList&quot; ItemsSource=&quot;{Binding TreeViewCollection.Groups}&quot;                      
                VirtualizingStackPanel.IsVirtualizing=&quot;True&quot;
                VirtualizingStackPanel.VirtualizationMode=&quot;Recycling&quot;
                      SelectedItemChanged=&quot;TreeView_SelectedItemChanged&quot;
                      ScrollViewer.VerticalScrollBarVisibility=&quot;Visible&quot;
                &gt;

                &lt;TreeView.ItemTemplate&gt;
                    &lt;HierarchicalDataTemplate ItemsSource=&quot;{Binding Path=Items}&quot;&gt;
                        &lt;TextBlock VerticalAlignment=&quot;Center&quot; Text=&quot;{Binding Path=Name}&quot;&gt;&lt;/TextBlock&gt;
                    &lt;/HierarchicalDataTemplate&gt;
                &lt;/TreeView.ItemTemplate&gt;
                &lt;TreeView.ItemContainerStyle&gt;
                    &lt;Style TargetType=&quot;TreeViewItem&quot;&gt;
                        &lt;Setter Property=&quot;IsSelected&quot; Value=&quot;{Binding IsSelected, Mode=TwoWay}&quot; /&gt;
                    &lt;/Style&gt;
                &lt;/TreeView.ItemContainerStyle&gt;

            &lt;/TreeView&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;3&quot; Grid.Column=&quot;1&quot; Background=&quot;LightBlue&quot;&gt;
            &lt;TextBlock&gt;Second Grid shows group of selected item in TreeViewMyModelList&lt;/TextBlock&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;2&quot; Grid.Column=&quot;2&quot; Background=&quot;green&quot; Grid.RowSpan=&quot;2&quot;&gt;
            &lt;!--&lt;TextBlock&gt;More information about MyModel&lt;/TextBlock&gt;--&gt;
            &lt;TextBlock Text=&quot;{Binding SelectedDataItem.Name}&quot;&gt;&lt;/TextBlock&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;4&quot; Height=&quot;300&quot;&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;5&quot; Grid.ColumnSpan=&quot;3&quot; Background=&quot;Gray&quot;&gt;
        &lt;/Grid&gt;

    &lt;/Grid&gt;
&lt;/Window&gt;

MainWindowViewModel.cs

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private string _filterValue = string.Empty;

    [ObservableProperty]
    private object _selectedDataItem;

    private readonly List&lt;LoadedDataFromFile&gt; _loadedDataFromFile;

    private List&lt;MyModel&gt; myModelList;

    public ICollectionView TreeViewCollection { get; }

    public MainWindowViewModel()
    {
        // loading data from file and transforming it into `MyModel` list
        // for this example i populate it here
        myModelList = new List&lt;MyModel&gt;()
        {
            new MyModel {Name=&quot;Some_Name1&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=1},
            new MyModel {Name=&quot;Some_Name2&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=2},
            new MyModel {Name=&quot;Some_Name3&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=3},
            new MyModel {Name=&quot;Some_Name4&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_01&quot;, Text=&quot;some_text&quot;, Number=4},

            new MyModel {Name=&quot;Some_Name5&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=5},
            new MyModel {Name=&quot;Some_Name6&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=6},
            new MyModel {Name=&quot;Some_Name7&quot;, GroupName=&quot;Some_Group_01&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=7},
            new MyModel {Name=&quot;Some_Name8&quot;, GroupName=&quot;Some_Group_02&quot;, ShortName=&quot;short_02&quot;, Text=&quot;some_text&quot;, Number=8},

            new MyModel {Name=&quot;Some_Name9&quot;, GroupName=&quot;Some_Group_05&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=9},
            new MyModel {Name=&quot;Some_Name10&quot;, GroupName=&quot;Some_Group_05&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=10},
            new MyModel {Name=&quot;Some_Name11&quot;, GroupName=&quot;Some_Group_06&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=11},
            new MyModel {Name=&quot;Some_Name12&quot;, GroupName=&quot;Some_Group_07&quot;, ShortName=&quot;short_03&quot;, Text=&quot;some_text&quot;, Number=12},
        };


        TreeViewCollection = CollectionViewSource.GetDefaultView(myModelList);

        TreeViewCollection.Filter = FilterCollection;
        var pgd = new PropertyGroupDescription(nameof(MyModel.ShortName));
        pgd.SortDescriptions.Add(new SortDescription(&quot;Name&quot;, ListSortDirection.Ascending));
        TreeViewCollection.GroupDescriptions.Add(pgd);

        pgd = new PropertyGroupDescription(nameof(MyModel.GroupName));
        pgd.SortDescriptions.Add(new SortDescription(&quot;Name&quot;, ListSortDirection.Ascending));
        TreeViewCollection.GroupDescriptions.Add(pgd);

        TreeViewCollection.SortDescriptions.Add(new SortDescription(nameof(MyModel.Number), ListSortDirection.Ascending));
    }

    private bool FilterCollection(object obj)
    {
        if (obj is not MyModel model)
        {
            return false;
        }

        return model.Name!.ToLower().Contains(FilterValue.ToLower());
    }

    partial void OnFilterValueChanged(string value)
    {
        TreeViewCollection.Refresh();
    }

    partial void OnSelectedDataItemChanged(object value)
    {
        // do something
    }
}

huangapple
  • 本文由 发表于 2023年4月4日 08:52:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/75924725.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定