Skip to content

Commit 9127235

Browse files
authored
NEW FEATURE: Multi-select TreeView (#3333)
Implementing new Multi-select TreeView. This is based on a ListView.
1 parent c9df365 commit 9127235

25 files changed

+3217
-23
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,5 @@ $RECYCLE.BIN/
241241

242242
#React.JS
243243
.module-cache
244+
245+
*.lutconfig

MainDemo.Wpf/Domain/TreesViewModel.cs

+93-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Collections.ObjectModel;
1+
using System.Collections;
2+
using System.Collections.ObjectModel;
3+
using System.Windows.Documents;
24
using MaterialDesignThemes.Wpf;
35

46
namespace MaterialDesignDemo.Domain;
@@ -45,6 +47,19 @@ public class Planet
4547
public double Velocity { get; set; }
4648
}
4749

50+
public class TestItem
51+
{
52+
public TestItem? Parent { get; set; }
53+
public string Name { get; }
54+
public ObservableCollection<TestItem> Items { get; }
55+
56+
public TestItem(string name, IEnumerable<TestItem> items)
57+
{
58+
Name = name;
59+
Items = new ObservableCollection<TestItem>(items);
60+
}
61+
}
62+
4863
public sealed class MovieCategory
4964
{
5065
public MovieCategory(string name, params Movie[] movies)
@@ -61,13 +76,26 @@ public MovieCategory(string name, params Movie[] movies)
6176
public sealed class TreesViewModel : ViewModelBase
6277
{
6378
private object? _selectedItem;
79+
private TestItem? _selectedTreeItem;
80+
81+
public ObservableCollection<TestItem> TreeItems { get; } = new();
6482

6583
public ObservableCollection<MovieCategory> MovieCategories { get; }
6684

6785
public AnotherCommandImplementation AddCommand { get; }
6886

6987
public AnotherCommandImplementation RemoveSelectedItemCommand { get; }
7088

89+
public AnotherCommandImplementation AddListTreeItemCommand { get; }
90+
91+
public AnotherCommandImplementation RemoveListTreeItemCommand { get; }
92+
93+
public TestItem? SelectedTreeItem
94+
{
95+
get => _selectedTreeItem;
96+
set => SetProperty(ref _selectedTreeItem, value);
97+
}
98+
7199
public object? SelectedItem
72100
{
73101
get => _selectedItem;
@@ -76,6 +104,68 @@ public object? SelectedItem
76104

77105
public TreesViewModel()
78106
{
107+
Random random = new();
108+
for(int i = 0; i < 10; i++)
109+
{
110+
TreeItems.Add(CreateTestItem(random, 1));
111+
}
112+
113+
static TestItem CreateTestItem(Random random, int depth)
114+
{
115+
int numberOfChildren = depth < 5 ? random.Next(0, 6) : 0;
116+
var children = Enumerable.Range(0, numberOfChildren).Select(_ => CreateTestItem(random, depth + 1));
117+
var rv = new TestItem(GenerateString(random.Next(4, 10)), children);
118+
foreach(var child in rv.Items)
119+
{
120+
child.Parent = rv;
121+
}
122+
return rv;
123+
}
124+
125+
AddListTreeItemCommand = new(_ =>
126+
{
127+
if (SelectedTreeItem is { } treeItem)
128+
{
129+
var newItem = CreateTestItem(random, 1);
130+
newItem.Parent = treeItem;
131+
treeItem.Items.Add(newItem);
132+
}
133+
else
134+
{
135+
TreeItems.Add(CreateTestItem(random, 1));
136+
}
137+
});
138+
139+
RemoveListTreeItemCommand = new(items =>
140+
{
141+
if (items is IEnumerable enumerable)
142+
{
143+
foreach(TestItem testItem in enumerable)
144+
{
145+
if (testItem.Parent is { } parent)
146+
{
147+
parent.Items.Remove(testItem);
148+
}
149+
else
150+
{
151+
TreeItems.Remove(testItem);
152+
}
153+
}
154+
}
155+
if (SelectedTreeItem is { } selectedItem)
156+
{
157+
if (selectedItem.Parent is { } parent)
158+
{
159+
parent.Items.Remove(selectedItem);
160+
}
161+
else
162+
{
163+
TreeItems.Remove(selectedItem);
164+
}
165+
SelectedTreeItem = null;
166+
}
167+
});
168+
79169
MovieCategories = new ObservableCollection<MovieCategory>
80170
{
81171
new MovieCategory("Action",
@@ -87,6 +177,8 @@ public TreesViewModel()
87177
new Movie("EuroTrip", "Jeff Schaffer")
88178
)
89179
};
180+
MovieCategories.Add(MovieCategories[0]);
181+
MovieCategories.Add(MovieCategories[1]);
90182

91183
AddCommand = new AnotherCommandImplementation(
92184
_ =>

MainDemo.Wpf/MainWindow.xaml

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
xmlns:system="clr-namespace:System;assembly=mscorlib"
99
Title="Material Design in XAML"
1010
Width="1150"
11-
MaxHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}}"
1211
d:DataContext="{d:DesignInstance domain:MainWindowViewModel}"
1312
AutomationProperties.Name="{Binding Title, RelativeSource={RelativeSource Self}}"
1413
Icon="favicon.ico"

MainDemo.Wpf/Properties/launchSettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"Demo App": {
44
"commandName": "Project",
5-
"commandLineArgs": "-p Home -t Inherit -f LeftToRight"
5+
"commandLineArgs": "-p Trees -t Inherit -f LeftToRight"
66
}
77
}
88
}

MainDemo.Wpf/Trees.xaml

+47-9
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131

3232
<Grid.RowDefinitions>
3333
<RowDefinition Height="Auto" />
34-
<RowDefinition Height="Auto" />
34+
<RowDefinition Height="Auto" MaxHeight="450" />
3535
<RowDefinition Height="Auto" />
3636
<RowDefinition Height="Auto" />
3737
</Grid.RowDefinitions>
38-
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="Basic Tree View:" />
3938

39+
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="Basic Tree View:" />
4040
<smtx:XamlDisplay Grid.Row="1"
4141
Grid.Column="0"
4242
VerticalContentAlignment="Top"
@@ -113,7 +113,6 @@
113113
Margin="32,0,0,0"
114114
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
115115
Text="MVVM/Binding:" />
116-
117116
<smtx:XamlDisplay Grid.Row="1"
118117
Grid.Column="1"
119118
Margin="32,0,0,0"
@@ -175,11 +174,52 @@
175174
</Grid>
176175
</smtx:XamlDisplay>
177176

177+
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="Multi-Select Tree View:"
178+
Grid.Column="2"/>
179+
<smtx:XamlDisplay Grid.Row="1"
180+
Grid.Column="2"
181+
VerticalContentAlignment="Top"
182+
UniqueKey="trees_3">
183+
<Grid>
184+
<materialDesign:TreeListView MinWidth="220" MaxHeight="450"
185+
ItemsSource="{Binding TreeItems}"
186+
SelectedItem="{Binding SelectedTreeItem}">
187+
<materialDesign:TreeListView.Resources>
188+
<HierarchicalDataTemplate DataType="{x:Type domain:TestItem}"
189+
ItemsSource="{Binding Items, Mode=OneTime}">
190+
<TextBlock Margin="3,2" Text="{Binding Name, Mode=OneTime}" />
191+
</HierarchicalDataTemplate>
192+
193+
<HierarchicalDataTemplate DataType="{x:Type domain:MovieCategory}"
194+
ItemsSource="{Binding Movies, Mode=OneTime}">
195+
<TextBlock Margin="3,2" Text="{Binding Name, Mode=OneTime}" />
196+
</HierarchicalDataTemplate>
197+
198+
<DataTemplate DataType="{x:Type domain:Movie}">
199+
<TextBlock Margin="3,2"
200+
Text="{Binding Name, Mode=OneTime}"
201+
ToolTip="{Binding Director, Mode=OneTime}" />
202+
</DataTemplate>
203+
</materialDesign:TreeListView.Resources>
204+
205+
</materialDesign:TreeListView>
206+
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right">
207+
<Button Command="{Binding AddListTreeItemCommand}"
208+
ToolTip="Add an item"
209+
Content="{materialDesign:PackIcon Kind=Add}"/>
210+
211+
<Button Command="{Binding RemoveListTreeItemCommand}"
212+
ToolTip="Remove selected item(s)"
213+
Content="{materialDesign:PackIcon Kind=Remove}"/>
214+
215+
</StackPanel>
216+
</Grid>
217+
</smtx:XamlDisplay>
218+
178219
<TextBlock Grid.Row="2"
179220
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
180221
Text="Additional node content, syntax 1:" />
181-
182-
<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_3">
222+
<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_4">
183223
<TreeView>
184224
<materialDesign:TreeViewAssist.AdditionalTemplate>
185225
<DataTemplate>
@@ -217,11 +257,10 @@
217257
Margin="32,0,0,0"
218258
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
219259
Text="Additional node content, syntax 2:" />
220-
221260
<smtx:XamlDisplay Grid.Row="3"
222261
Grid.Column="1"
223262
Margin="32,0,0,0"
224-
UniqueKey="trees_4">
263+
UniqueKey="trees_5">
225264
<TreeView>
226265
<materialDesign:TreeViewAssist.AdditionalTemplateSelector>
227266
<domain:TreeExampleSimpleTemplateSelector>
@@ -267,11 +306,10 @@
267306
Margin="32,0,0,0"
268307
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
269308
Text="Additional node content, showcase:" />
270-
271309
<smtx:XamlDisplay Grid.Row="3"
272310
Grid.Column="2"
273311
Margin="32,0,0,0"
274-
UniqueKey="trees_5">
312+
UniqueKey="trees_6">
275313
<TreeView MinWidth="220" DisplayMemberPath="Name">
276314
<TreeView.Resources>
277315
<DataTemplate DataType="{x:Type domain:Planet}">

MaterialDesignThemes.UITests/TestBase.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
[assembly: GenerateHelpers(typeof(ColorPicker))]
99
[assembly: GenerateHelpers(typeof(DialogHost))]
1010
[assembly: GenerateHelpers(typeof(AutoSuggestBox))]
11+
[assembly: GenerateHelpers(typeof(TreeListView))]
12+
[assembly: GenerateHelpers(typeof(TreeListViewItem))]
1113

1214
namespace MaterialDesignThemes.UITests;
1315

1416
public abstract class TestBase : IAsyncLifetime
1517
{
16-
protected bool AttachedDebugger { get; set; } = true;
18+
protected bool AttachedDebuggerToRemoteProcess { get; set; } = true;
1719
protected ITestOutputHelper Output { get; }
1820

1921
[NotNull]
@@ -44,7 +46,7 @@ protected async Task<IVisualElement> LoadUserControl<TControl>()
4446
public async Task InitializeAsync() =>
4547
App = await XamlTest.App.StartRemote(new AppOptions
4648
{
47-
AllowVisualStudioDebuggerAttach = AttachedDebugger,
49+
AllowVisualStudioDebuggerAttach = AttachedDebuggerToRemoteProcess,
4850
LogMessage = Output.WriteLine
4951
});
5052
public async Task DisposeAsync() => await App.DisposeAsync();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<UserControl x:Class="MaterialDesignThemes.UITests.WPF.TreeListViews.TreeListViewDataBinding"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:local="clr-namespace:MaterialDesignThemes.UITests.WPF.TreeListViews"
7+
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
8+
mc:Ignorable="d"
9+
DataContext="{Binding RelativeSource={RelativeSource Self}}"
10+
d:DesignHeight="450" d:DesignWidth="800">
11+
<Grid>
12+
<Grid.RowDefinitions>
13+
<RowDefinition />
14+
<RowDefinition Height="Auto" />
15+
</Grid.RowDefinitions>
16+
<materialDesign:TreeListView
17+
x:Name="TreeListView"
18+
ItemsSource="{Binding Items}">
19+
<materialDesign:TreeListView.ItemTemplate>
20+
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}"
21+
ItemsSource="{Binding Children}">
22+
<TextBlock Text="{Binding Value}" />
23+
</HierarchicalDataTemplate>
24+
</materialDesign:TreeListView.ItemTemplate>
25+
</materialDesign:TreeListView>
26+
<StackPanel Grid.Row="1" Orientation="Horizontal">
27+
<Button Content="Add" Click="Add_OnClick" />
28+
<Button Content="Remove" Click="Remove_OnClick" />
29+
<Button Content="Replace" Click="Replace_OnClick" />
30+
<Button Content="Down" Click="MoveDown_OnClick" />
31+
<Button Content="Up" Click="MoveUp_OnClick" />
32+
<Button Content="Reset" Click="Reset_OnClick" />
33+
</StackPanel>
34+
</Grid>
35+
</UserControl>

0 commit comments

Comments
 (0)