1

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 :)

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<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_Group_01", ShortName="short_02", Text="some_text", Number=5},
            new MyModel {Name="Some_Name6", GroupName="Some_Group_01", ShortName="short_02", Text="some_text", Number=6},
            new MyModel {Name="Some_Name7", GroupName="Some_Group_01", ShortName="short_02", Text="some_text", Number=7},
            new MyModel {Name="Some_Name8", GroupName="Some_Group_02", ShortName="short_02", Text="some_text", Number=8},

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


        TreeViewCollection = CollectionViewSource.GetDefaultView(myModelList);

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

        pgd = new PropertyGroupDescription(nameof(MyModel.GroupName));
        pgd.SortDescriptions.Add(new SortDescription("Name", 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

<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>
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Myszax
  • 33
  • 4
  • You can add a IsSelected property to your data model and bind it to the TreeViewItem.IsSelected property. But the most straight forward way is to handle the SelectionChanged event to read the TreeView.SelectedItem property. You can then pass it to the view model either directly or by setting a local dependency property that binds TwoWay to the view model class. **Code-behind does not violate MVVM. It has nothing to do with MVVM. It's a pure compiler feature (partial class). MVVM is only about application level architecture.** – BionicCode Apr 04 '23 at 09:35
  • There is no reason to define a ICollectionView explicitly. Binding to a colection will always bind to the default view implicitly. Even if this wouldn't be the case, the general rules of instance sharing apply to every instance no matter the type e.g. ICollectionView or IList or whatever. The behavior is different when comparing value types to reference types of course. Binding the DataGrid and the TreeView to the same e.g. ObservableCollection will yield the same result. – BionicCode Apr 04 '23 at 09:42
  • Introduction of an explicit ICollectionView is only needed if you want to support independent filtering/sorting/grouping (which you are not doing in your example) for each data view where each view shows the same data. – BionicCode Apr 04 '23 at 09:42

2 Answers2

1

"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

<Window>
  <Window.Resources>

    <!-- Bind the control's SelectedTreeViewItem property to the DataContext -->
    <Style TargetType="local:MainWindow">
      <Setter Property="SelectedTreeViewItem"
              Value="{Binding SelectedDataItem}" />
    </Style>
  </Window.Resources>

  <TreeView SelectedItemChanged="TreeView_SelectedItemChanged" />
</Window>

MainWindow.xaml.cs

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

  public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register(
    "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;
  }
}

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<TreeViewItemModel> 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

<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">

      <!-- Connect the item container to the item -->
      <Setter Property="IsSelected" Value="{Binding IsSelected}" />
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • I'd like to stick to first solution. I don't feel like add `IsSelected` property to `MyModel` then lookup whole collection and find `IsSelected` setted to `True`. Correct me if I'm wrong :) I added `xmlns:local="clr-namespace:WPFUI"` to my `` at the beginning and the rest to **MainWindow.xaml.cs** but got error `The member "SelectedTreeViewItem" is not recognized or is not accessible.` – Myszax Apr 04 '23 at 18:42
  • `SelectedTreeViewItem` is the dependency property defined in the *MainWindow.xaml.cs*. Have you copied the property too? – BionicCode Apr 04 '23 at 19:01
  • Please note that I have omitted all the attributes of the `` element tag to compact the example. The element must contain the usual attributes like `x:Class` and the most common XAML namespace imports. For example `x:Class` tells the compiler the name of the partial class (code-behind file) that maps to the XAML file. Otherwise the compiler won't be able to link the dependency property and other code-behind code like event handlers etc. – BionicCode Apr 04 '23 at 19:03
  • The constructor of `MainWindow` must call `InitializeComponent()` . I left all this default code because I thought it would be clear that this is always required. It's the minimal code which is also provided by the code template of a `Window` in order to make the window compile and display. – BionicCode Apr 04 '23 at 19:09
  • Yes, I get your point. I didn't removed anything. Just add things that I posted above. My Visual Studio seems to be lagged too. I rebuild project and it works like a charm. Thank you for fast respond and for help of course :) I will respond to comments from first post later. Marked your answer as solved. My post after your answer is just to show full changes that I did. – Myszax Apr 04 '23 at 23:06
0

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'm not using it here.

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_Group_01", ShortName="short_02", Text="some_text", Number=5},
            new MyModel {Name="Some_Name6", GroupName="Some_Group_01", ShortName="short_02", Text="some_text", Number=6},
            new MyModel {Name="Some_Name7", GroupName="Some_Group_01", ShortName="short_02", Text="some_text", Number=7},
            new MyModel {Name="Some_Name8", GroupName="Some_Group_02", ShortName="short_02", Text="some_text", Number=8},

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


        TreeViewCollection = CollectionViewSource.GetDefaultView(myModelList);

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

        pgd = new PropertyGroupDescription(nameof(MyModel.GroupName));
        pgd.SortDescriptions.Add(new SortDescription("Name", 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
    }
}
Myszax
  • 33
  • 4