Skip to content

Commit 5f73f11

Browse files
Fix tree list view bug and demo apps (#3350)
* Fix null propagation issue * Fix demo app pages * Fixing issue with implicit data tempaltes Using a custom content presenter we can retrieve the template. * Removing commented code --------- Co-authored-by: Kevin Bost <[email protected]>
1 parent 4b14f71 commit 5f73f11

File tree

13 files changed

+425
-107
lines changed

13 files changed

+425
-107
lines changed

MaterialDesign3.Demo.Wpf/Domain/TreesViewModel.cs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.ObjectModel;
1+
using System.Collections;
2+
using System.Collections.ObjectModel;
23
using MaterialDesignThemes.Wpf;
34

45
namespace MaterialDesign3Demo.Domain;
@@ -45,6 +46,19 @@ public class Planet
4546
public double Velocity { get; set; }
4647
}
4748

49+
public class TestItem
50+
{
51+
public TestItem? Parent { get; set; }
52+
public string Name { get; }
53+
public ObservableCollection<TestItem> Items { get; }
54+
55+
public TestItem(string name, IEnumerable<TestItem> items)
56+
{
57+
Name = name;
58+
Items = new ObservableCollection<TestItem>(items);
59+
}
60+
}
61+
4862
public sealed class MovieCategory
4963
{
5064
public MovieCategory(string name, params Movie[] movies)
@@ -61,13 +75,26 @@ public MovieCategory(string name, params Movie[] movies)
6175
public sealed class TreesViewModel : ViewModelBase
6276
{
6377
private object? _selectedItem;
78+
private TestItem? _selectedTreeItem;
79+
80+
public ObservableCollection<TestItem> TreeItems { get; } = new();
6481

6582
public ObservableCollection<MovieCategory> MovieCategories { get; }
6683

6784
public AnotherCommandImplementation AddCommand { get; }
6885

6986
public AnotherCommandImplementation RemoveSelectedItemCommand { get; }
7087

88+
public AnotherCommandImplementation AddListTreeItemCommand { get; }
89+
90+
public AnotherCommandImplementation RemoveListTreeItemCommand { get; }
91+
92+
public TestItem? SelectedTreeItem
93+
{
94+
get => _selectedTreeItem;
95+
set => SetProperty(ref _selectedTreeItem, value);
96+
}
97+
7198
public object? SelectedItem
7299
{
73100
get => _selectedItem;
@@ -76,6 +103,68 @@ public object? SelectedItem
76103

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

MaterialDesign3.Demo.Wpf/Trees.xaml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,42 @@
175175
</Grid>
176176
</smtx:XamlDisplay>
177177

178+
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="Multi-Select Tree View:"
179+
Grid.Column="2"/>
180+
<smtx:XamlDisplay Grid.Row="1"
181+
Grid.Column="2"
182+
VerticalContentAlignment="Top"
183+
UniqueKey="trees_3">
184+
<Grid>
185+
<materialDesign:TreeListView MinWidth="220" MaxHeight="450"
186+
ItemsSource="{Binding TreeItems}"
187+
SelectedItem="{Binding SelectedTreeItem}">
188+
<materialDesign:TreeListView.ItemTemplate>
189+
<HierarchicalDataTemplate DataType="{x:Type domain:TestItem}"
190+
ItemsSource="{Binding Items, Mode=OneWay}">
191+
<TextBlock Margin="3,2" Text="{Binding Name, Mode=OneWay}" />
192+
</HierarchicalDataTemplate>
193+
</materialDesign:TreeListView.ItemTemplate>
194+
195+
</materialDesign:TreeListView>
196+
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right">
197+
<Button Command="{Binding AddListTreeItemCommand}"
198+
ToolTip="Add an item"
199+
Content="{materialDesign:PackIcon Kind=Add}"/>
200+
201+
<Button Command="{Binding RemoveListTreeItemCommand}"
202+
ToolTip="Remove selected item(s)"
203+
Content="{materialDesign:PackIcon Kind=Remove}"/>
204+
205+
</StackPanel>
206+
</Grid>
207+
</smtx:XamlDisplay>
208+
178209
<TextBlock Grid.Row="2"
179210
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
180211
Text="Additional node content, syntax 1:" />
181212

182-
<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_3">
213+
<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_4">
183214
<TreeView>
184215
<materialDesign:TreeViewAssist.AdditionalTemplate>
185216
<DataTemplate>
@@ -221,7 +252,7 @@
221252
<smtx:XamlDisplay Grid.Row="3"
222253
Grid.Column="1"
223254
Margin="32,0,0,0"
224-
UniqueKey="trees_4">
255+
UniqueKey="trees_5">
225256
<TreeView>
226257
<materialDesign:TreeViewAssist.AdditionalTemplateSelector>
227258
<domain:TreeExampleSimpleTemplateSelector>
@@ -271,7 +302,7 @@
271302
<smtx:XamlDisplay Grid.Row="3"
272303
Grid.Column="2"
273304
Margin="32,0,0,0"
274-
UniqueKey="trees_5">
305+
UniqueKey="trees_6">
275306
<TreeView MinWidth="220" DisplayMemberPath="Name">
276307
<TreeView.Resources>
277308
<DataTemplate DataType="{x:Type domain:Planet}">
@@ -393,4 +424,4 @@
393424
</smtx:XamlDisplay>
394425
</Grid>
395426
</ScrollViewer>
396-
</UserControl>
427+
</UserControl>

MaterialDesignThemes.UITests/TestBase.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ protected async Task<IVisualElement<T>> LoadXaml<T>(string xaml, params (string
3636
return await App.CreateWindowWith<T>(xaml, additionalNamespaceDeclarations);
3737
}
3838

39-
protected async Task<IVisualElement> LoadUserControl<TControl>()
39+
protected Task<IVisualElement> LoadUserControl<TControl>()
4040
where TControl : UserControl
41+
=> LoadUserControl(typeof(TControl));
42+
43+
protected async Task<IVisualElement> LoadUserControl(Type userControlType)
4144
{
4245
await App.InitializeWithMaterialDesign();
43-
return await App.CreateWindowWithUserControl<TControl>();
46+
return await App.CreateWindowWithUserControl(userControlType);
4447
}
4548

4649
public async Task InitializeAsync() =>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.ObjectModel;
2+
using System.Collections.Specialized;
3+
using System.Threading;
4+
5+
namespace MaterialDesignThemes.UITests.WPF.TreeListViews;
6+
7+
public class TestableCollection<T> : ObservableCollection<T>
8+
{
9+
private int _blockCollectionChanges;
10+
11+
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
12+
{
13+
if (Interlocked.CompareExchange(ref _blockCollectionChanges, 0, 0) == 0)
14+
{
15+
base.OnCollectionChanged(e);
16+
}
17+
}
18+
19+
public void ReplaceAllItems(params T[] newItems)
20+
{
21+
Interlocked.Exchange(ref _blockCollectionChanges, 1);
22+
23+
Clear();
24+
foreach (T newItem in newItems)
25+
{
26+
Add(newItem);
27+
}
28+
29+
Interlocked.Exchange(ref _blockCollectionChanges, 0);
30+
31+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
32+
}
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Diagnostics;
2+
3+
namespace MaterialDesignThemes.UITests.WPF.TreeListViews;
4+
5+
[DebuggerDisplay("{Value} (Children: {Children.Count})")]
6+
public class TreeItem
7+
{
8+
public string Value { get; }
9+
10+
public TreeItem? Parent { get; }
11+
12+
//NB: making the assumption changes occur ont he UI thread
13+
public TestableCollection<TreeItem> Children { get; } = new();
14+
15+
public TreeItem(string value, TreeItem? parent)
16+
{
17+
Value = value;
18+
Parent = parent;
19+
}
20+
}

MaterialDesignThemes.UITests/WPF/TreeListViews/TreeListViewDataBinding.xaml.cs

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System.Collections.ObjectModel;
2-
using System.Collections.Specialized;
3-
using System.Diagnostics;
4-
using System.Threading;
52

63
namespace MaterialDesignThemes.UITests.WPF.TreeListViews;
74

@@ -10,7 +7,7 @@ namespace MaterialDesignThemes.UITests.WPF.TreeListViews;
107
/// </summary>
118
public partial class TreeListViewDataBinding
129
{
13-
//NB: making the assumption changes occur ont he UI thread
10+
//NB: making the assumption changes occur on the UI thread
1411
public ObservableCollection<TreeItem> Items { get; } = new();
1512

1613
public TreeListViewDataBinding()
@@ -127,48 +124,3 @@ private void Reset_OnClick(object sender, RoutedEventArgs e)
127124
}
128125
}
129126
}
130-
131-
[DebuggerDisplay("{Value} (Children: {Children.Count})")]
132-
public class TreeItem
133-
{
134-
public string Value { get; }
135-
136-
public TreeItem? Parent { get; }
137-
138-
//NB: making the assumption changes occur ont he UI thread
139-
public TestableCollection<TreeItem> Children { get; } = new();
140-
141-
public TreeItem(string value, TreeItem? parent)
142-
{
143-
Value = value;
144-
Parent = parent;
145-
}
146-
}
147-
148-
public class TestableCollection<T> : ObservableCollection<T>
149-
{
150-
private int _blockCollectionChanges;
151-
152-
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
153-
{
154-
if (Interlocked.CompareExchange(ref _blockCollectionChanges, 0, 0) == 0)
155-
{
156-
base.OnCollectionChanged(e);
157-
}
158-
}
159-
160-
public void ReplaceAllItems(params T[] newItems)
161-
{
162-
Interlocked.Exchange(ref _blockCollectionChanges, 1);
163-
164-
Clear();
165-
foreach (T newItem in newItems)
166-
{
167-
Add(newItem);
168-
}
169-
170-
Interlocked.Exchange(ref _blockCollectionChanges, 0);
171-
172-
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
173-
}
174-
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<UserControl x:Class="MaterialDesignThemes.UITests.WPF.TreeListViews.TreeListViewImplicitTemplate"
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.Resources>
20+
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}"
21+
ItemsSource="{Binding Children}">
22+
<TextBlock Text="{Binding Value}" />
23+
</HierarchicalDataTemplate>
24+
</materialDesign:TreeListView.Resources>
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)