Skip to content

Commit dc94343

Browse files
authored
This makes the automation tree look more like a regular tree view (#3411)
This ends up displaying nested nodes as child automation peers of their parent.
1 parent 244cbf1 commit dc94343

File tree

4 files changed

+122
-5
lines changed

4 files changed

+122
-5
lines changed

MaterialDesignThemes.Wpf.Tests/Internal/TreeListViewItemsCollectionTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,45 @@ public void GetParent_WithNestedItem_ReturnsParent()
643643
Assert.Null(treeListViewItemsCollection.GetParent(8));
644644
}
645645

646+
[Fact]
647+
public void GetDirectChildrenIndexes_GetsExpectedChildrenIndexes()
648+
{
649+
//Arrange
650+
ObservableCollection<string> boundCollection = new() { "0", "1", "2" };
651+
TreeListViewItemsCollection treeListViewItemsCollection = new(boundCollection);
652+
treeListViewItemsCollection.InsertWithLevel(2, "1_0", 1);
653+
treeListViewItemsCollection.InsertWithLevel(3, "1_1", 1);
654+
treeListViewItemsCollection.InsertWithLevel(4, "1_2", 1);
655+
treeListViewItemsCollection.InsertWithLevel(5, "1_2_0", 2);
656+
treeListViewItemsCollection.InsertWithLevel(6, "1_2_1", 2);
657+
treeListViewItemsCollection.InsertWithLevel(7, "1_2_2", 2);
658+
659+
/*
660+
* 0. 0
661+
* 1. 1
662+
* 2. 1_0
663+
* 3. 1_1
664+
* 4. 1_2
665+
* 5. 1_2_0
666+
* 6. 1_2_1
667+
* 7. 1_2_2
668+
* 8. 2
669+
*/
670+
671+
672+
//Act/Assert
673+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(0));
674+
Assert.Equal(new[] { 2, 3, 4 }, treeListViewItemsCollection.GetDirectChildrenIndexes(1));
675+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(2));
676+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(3));
677+
Assert.Equal(new[] { 5, 6, 7 }, treeListViewItemsCollection.GetDirectChildrenIndexes(4));
678+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(5));
679+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(6));
680+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(7));
681+
Assert.Empty(treeListViewItemsCollection.GetDirectChildrenIndexes(8));
682+
683+
}
684+
646685
private class TestableCollection<T> : ObservableCollection<T>
647686
{
648687
private int _blockCollectionChanges;

MaterialDesignThemes.Wpf/Automation/Peers/TreeListViewItemAutomationPeer.cs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Windows.Automation.Peers;
1+
using System.Windows.Automation.Peers;
2+
using System.Windows.Automation.Provider;
23

34
namespace MaterialDesignThemes.Wpf.Automation.Peers;
45

@@ -8,15 +9,64 @@ public TreeListViewAutomationPeer(TreeListView owner) : base(owner)
89
{
910
}
1011

12+
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Tree;
13+
14+
protected override string GetClassNameCore() => "TreeListView";
15+
16+
protected override List<AutomationPeer>? GetChildrenCore()
17+
{
18+
ItemsControl owner = (ItemsControl)Owner;
19+
ItemCollection items = owner.Items;
20+
List<AutomationPeer>? children = null;
21+
22+
if (items.Count > 0)
23+
{
24+
children = new List<AutomationPeer>(items.Count);
25+
for (int i = 0; i < items.Count; i++)
26+
{
27+
TreeListViewItem? treeViewItem = owner.ItemContainerGenerator.ContainerFromIndex(i) as TreeListViewItem;
28+
//We grab top level items only
29+
if (treeViewItem is { Level: 0 })
30+
{
31+
children.Add(CreateItemAutomationPeer(items[i]));
32+
}
33+
}
34+
}
35+
return children;
36+
}
37+
1138
protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
12-
=> new TreeListViewItemAutomationPeer(item, this);
39+
{
40+
return new TreeListViewItemAutomationPeer(item, this);
41+
}
1342
}
1443

1544
public class TreeListViewItemAutomationPeer : ListBoxItemAutomationPeer
1645
{
1746
public TreeListViewItemAutomationPeer(object owner, SelectorAutomationPeer selectorAutomationPeer)
1847
: base(owner, selectorAutomationPeer)
1948
{
49+
50+
}
51+
52+
protected override List<AutomationPeer> GetChildrenCore()
53+
{
54+
List<AutomationPeer> rv = base.GetChildrenCore();
55+
56+
if (ItemsControlAutomationPeer is TreeListViewAutomationPeer { Owner: TreeListView treeListView } itemAutomationPeer &&
57+
treeListView.ItemContainerGenerator.ContainerFromItem(Item) is TreeListViewItem treeListViewItem)
58+
{
59+
int index = treeListView.ItemContainerGenerator.IndexFromContainer(treeListViewItem);
60+
if (index >= 0)
61+
{
62+
foreach (int childIndex in treeListView.InternalItemsSource?.GetDirectChildrenIndexes(index) ?? Array.Empty<int>())
63+
{
64+
rv.Add(new TreeListViewItemAutomationPeer(treeListView.Items[childIndex], itemAutomationPeer));
65+
}
66+
}
67+
}
68+
69+
return rv;
2070
}
2171

2272
public override object GetPattern(PatternInterface patternInterface)
@@ -27,4 +77,7 @@ public override object GetPattern(PatternInterface patternInterface)
2777
}
2878
return base.GetPattern(patternInterface);
2979
}
80+
81+
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.TreeItem;
82+
protected override string GetClassNameCore() => "TreeListViewItem";
3083
}

MaterialDesignThemes.Wpf/Internal/TreeListViewItemsCollection.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private int GetPriorNonRootLevelItemsCount(int index, int startingIndex = 0)
4545

4646
if (priorRootLevelItems > index)
4747
{
48-
// We've have passed the provided index, which means we've found a non-prior root level item; bail out.
48+
// We've have passed the provided index, which means we've found a non-prior root parentLevel item; bail out.
4949
break;
5050
}
5151
}
@@ -65,7 +65,7 @@ public void InsertWithLevel(int index, object? item, int level)
6565
{
6666
if (level < 0) throw new ArgumentOutOfRangeException(nameof(level), level, "Item level must not be negative");
6767

68-
//Always allowed to request previous item level + 1 as this is inserting a "child"
68+
//Always allowed to request previous item parentLevel + 1 as this is inserting a "child"
6969
int previousItemLevel = index > 0 ? ItemLevels[index - 1] : -1;
7070
if (level > previousItemLevel + 1)
7171
{
@@ -100,6 +100,31 @@ public void InsertWithLevel(int index, object? item, int level)
100100
return null;
101101
}
102102

103+
public IEnumerable<int> GetDirectChildrenIndexes(int index)
104+
{
105+
if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index));
106+
107+
return GetDirectChildrenIndexesImplementation(index);
108+
109+
IEnumerable<int> GetDirectChildrenIndexesImplementation(int index)
110+
{
111+
int parentLevel = ItemLevels[index];
112+
113+
for (int i = index + 1; i < ItemLevels.Count; i++)
114+
{
115+
int level = ItemLevels[i];
116+
if (level == parentLevel + 1)
117+
{
118+
yield return i;
119+
}
120+
if (level <= parentLevel)
121+
{
122+
yield break;
123+
}
124+
}
125+
}
126+
}
127+
103128
protected override void RemoveItem(int index)
104129
{
105130
int priorNonRootLevelItems = GetPriorNonRootLevelItemsCount(index);

MaterialDesignThemes.Wpf/TreeListView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public double LevelIndentSize
1818
public static readonly DependencyProperty LevelIndentSizeProperty =
1919
DependencyProperty.Register(nameof(LevelIndentSize), typeof(double), typeof(TreeListView), new PropertyMetadata(16.0));
2020

21-
private TreeListViewItemsCollection? InternalItemsSource { get; set; }
21+
internal TreeListViewItemsCollection? InternalItemsSource { get; set; }
2222

2323
static TreeListView()
2424
{

0 commit comments

Comments
 (0)