Skip to content

Commit

Permalink
Fix tree list view bug and demo apps (#3350)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
nicolaihenriksen and Keboo authored Oct 29, 2023
1 parent 4b14f71 commit 5f73f11
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 107 deletions.
91 changes: 90 additions & 1 deletion MaterialDesign3.Demo.Wpf/Domain/TreesViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.ObjectModel;
using MaterialDesignThemes.Wpf;

namespace MaterialDesign3Demo.Domain;
Expand Down Expand Up @@ -45,6 +46,19 @@ public class Planet
public double Velocity { get; set; }
}

public class TestItem
{
public TestItem? Parent { get; set; }
public string Name { get; }
public ObservableCollection<TestItem> Items { get; }

public TestItem(string name, IEnumerable<TestItem> items)
{
Name = name;
Items = new ObservableCollection<TestItem>(items);
}
}

public sealed class MovieCategory
{
public MovieCategory(string name, params Movie[] movies)
Expand All @@ -61,13 +75,26 @@ public MovieCategory(string name, params Movie[] movies)
public sealed class TreesViewModel : ViewModelBase
{
private object? _selectedItem;
private TestItem? _selectedTreeItem;

public ObservableCollection<TestItem> TreeItems { get; } = new();

public ObservableCollection<MovieCategory> MovieCategories { get; }

public AnotherCommandImplementation AddCommand { get; }

public AnotherCommandImplementation RemoveSelectedItemCommand { get; }

public AnotherCommandImplementation AddListTreeItemCommand { get; }

public AnotherCommandImplementation RemoveListTreeItemCommand { get; }

public TestItem? SelectedTreeItem
{
get => _selectedTreeItem;
set => SetProperty(ref _selectedTreeItem, value);
}

public object? SelectedItem
{
get => _selectedItem;
Expand All @@ -76,6 +103,68 @@ public object? SelectedItem

public TreesViewModel()
{
Random random = new();
for (int i = 0; i < 10; i++)
{
TreeItems.Add(CreateTestItem(random, 1));
}

static TestItem CreateTestItem(Random random, int depth)
{
int numberOfChildren = depth < 5 ? random.Next(0, 6) : 0;
var children = Enumerable.Range(0, numberOfChildren).Select(_ => CreateTestItem(random, depth + 1));
var rv = new TestItem(GenerateString(random.Next(4, 10)), children);
foreach (var child in rv.Items)
{
child.Parent = rv;
}
return rv;
}

AddListTreeItemCommand = new(_ =>
{
if (SelectedTreeItem is { } treeItem)
{
var newItem = CreateTestItem(random, 1);
newItem.Parent = treeItem;
treeItem.Items.Add(newItem);
}
else
{
TreeItems.Add(CreateTestItem(random, 1));
}
});

RemoveListTreeItemCommand = new(items =>
{
if (items is IEnumerable enumerable)
{
foreach (TestItem testItem in enumerable)
{
if (testItem.Parent is { } parent)
{
parent.Items.Remove(testItem);
}
else
{
TreeItems.Remove(testItem);
}
}
}
if (SelectedTreeItem is { } selectedItem)
{
if (selectedItem.Parent is { } parent)
{
parent.Items.Remove(selectedItem);
}
else
{
TreeItems.Remove(selectedItem);
}
SelectedTreeItem = null;
}
});

MovieCategories = new ObservableCollection<MovieCategory>
{
new MovieCategory("Action",
Expand Down
39 changes: 35 additions & 4 deletions MaterialDesign3.Demo.Wpf/Trees.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,42 @@
</Grid>
</smtx:XamlDisplay>

<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="Multi-Select Tree View:"
Grid.Column="2"/>
<smtx:XamlDisplay Grid.Row="1"
Grid.Column="2"
VerticalContentAlignment="Top"
UniqueKey="trees_3">
<Grid>
<materialDesign:TreeListView MinWidth="220" MaxHeight="450"
ItemsSource="{Binding TreeItems}"
SelectedItem="{Binding SelectedTreeItem}">
<materialDesign:TreeListView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type domain:TestItem}"
ItemsSource="{Binding Items, Mode=OneWay}">
<TextBlock Margin="3,2" Text="{Binding Name, Mode=OneWay}" />
</HierarchicalDataTemplate>
</materialDesign:TreeListView.ItemTemplate>

</materialDesign:TreeListView>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<Button Command="{Binding AddListTreeItemCommand}"
ToolTip="Add an item"
Content="{materialDesign:PackIcon Kind=Add}"/>

<Button Command="{Binding RemoveListTreeItemCommand}"
ToolTip="Remove selected item(s)"
Content="{materialDesign:PackIcon Kind=Remove}"/>

</StackPanel>
</Grid>
</smtx:XamlDisplay>

<TextBlock Grid.Row="2"
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
Text="Additional node content, syntax 1:" />

<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_3">
<smtx:XamlDisplay Grid.Row="3" UniqueKey="trees_4">
<TreeView>
<materialDesign:TreeViewAssist.AdditionalTemplate>
<DataTemplate>
Expand Down Expand Up @@ -221,7 +252,7 @@
<smtx:XamlDisplay Grid.Row="3"
Grid.Column="1"
Margin="32,0,0,0"
UniqueKey="trees_4">
UniqueKey="trees_5">
<TreeView>
<materialDesign:TreeViewAssist.AdditionalTemplateSelector>
<domain:TreeExampleSimpleTemplateSelector>
Expand Down Expand Up @@ -271,7 +302,7 @@
<smtx:XamlDisplay Grid.Row="3"
Grid.Column="2"
Margin="32,0,0,0"
UniqueKey="trees_5">
UniqueKey="trees_6">
<TreeView MinWidth="220" DisplayMemberPath="Name">
<TreeView.Resources>
<DataTemplate DataType="{x:Type domain:Planet}">
Expand Down Expand Up @@ -393,4 +424,4 @@
</smtx:XamlDisplay>
</Grid>
</ScrollViewer>
</UserControl>
</UserControl>
7 changes: 5 additions & 2 deletions MaterialDesignThemes.UITests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ protected async Task<IVisualElement<T>> LoadXaml<T>(string xaml, params (string
return await App.CreateWindowWith<T>(xaml, additionalNamespaceDeclarations);
}

protected async Task<IVisualElement> LoadUserControl<TControl>()
protected Task<IVisualElement> LoadUserControl<TControl>()
where TControl : UserControl
=> LoadUserControl(typeof(TControl));

protected async Task<IVisualElement> LoadUserControl(Type userControlType)
{
await App.InitializeWithMaterialDesign();
return await App.CreateWindowWithUserControl<TControl>();
return await App.CreateWindowWithUserControl(userControlType);
}

public async Task InitializeAsync() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Threading;

namespace MaterialDesignThemes.UITests.WPF.TreeListViews;

public class TestableCollection<T> : ObservableCollection<T>
{
private int _blockCollectionChanges;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (Interlocked.CompareExchange(ref _blockCollectionChanges, 0, 0) == 0)
{
base.OnCollectionChanged(e);
}
}

public void ReplaceAllItems(params T[] newItems)
{
Interlocked.Exchange(ref _blockCollectionChanges, 1);

Clear();
foreach (T newItem in newItems)
{
Add(newItem);
}

Interlocked.Exchange(ref _blockCollectionChanges, 0);

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
20 changes: 20 additions & 0 deletions MaterialDesignThemes.UITests/WPF/TreeListViews/TreeItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Diagnostics;

namespace MaterialDesignThemes.UITests.WPF.TreeListViews;

[DebuggerDisplay("{Value} (Children: {Children.Count})")]
public class TreeItem
{
public string Value { get; }

public TreeItem? Parent { get; }

//NB: making the assumption changes occur ont he UI thread
public TestableCollection<TreeItem> Children { get; } = new();

public TreeItem(string value, TreeItem? parent)
{
Value = value;
Parent = parent;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Threading;

namespace MaterialDesignThemes.UITests.WPF.TreeListViews;

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

public TreeListViewDataBinding()
Expand Down Expand Up @@ -127,48 +124,3 @@ private void Reset_OnClick(object sender, RoutedEventArgs e)
}
}
}

[DebuggerDisplay("{Value} (Children: {Children.Count})")]
public class TreeItem
{
public string Value { get; }

public TreeItem? Parent { get; }

//NB: making the assumption changes occur ont he UI thread
public TestableCollection<TreeItem> Children { get; } = new();

public TreeItem(string value, TreeItem? parent)
{
Value = value;
Parent = parent;
}
}

public class TestableCollection<T> : ObservableCollection<T>
{
private int _blockCollectionChanges;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (Interlocked.CompareExchange(ref _blockCollectionChanges, 0, 0) == 0)
{
base.OnCollectionChanged(e);
}
}

public void ReplaceAllItems(params T[] newItems)
{
Interlocked.Exchange(ref _blockCollectionChanges, 1);

Clear();
foreach (T newItem in newItems)
{
Add(newItem);
}

Interlocked.Exchange(ref _blockCollectionChanges, 0);

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<UserControl x:Class="MaterialDesignThemes.UITests.WPF.TreeListViews.TreeListViewImplicitTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MaterialDesignThemes.UITests.WPF.TreeListViews"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<materialDesign:TreeListView
x:Name="TreeListView"
ItemsSource="{Binding Items}">
<materialDesign:TreeListView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Value}" />
</HierarchicalDataTemplate>
</materialDesign:TreeListView.Resources>
</materialDesign:TreeListView>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Add" Click="Add_OnClick" />
<Button Content="Remove" Click="Remove_OnClick" />
<Button Content="Replace" Click="Replace_OnClick" />
<Button Content="Down" Click="MoveDown_OnClick" />
<Button Content="Up" Click="MoveUp_OnClick" />
<Button Content="Reset" Click="Reset_OnClick" />
</StackPanel>
</Grid>
</UserControl>
Loading

0 comments on commit 5f73f11

Please sign in to comment.