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