Skip to content

Commit 8ae873a

Browse files
feat: integrate VisualTreeHelper for WPF visual tree traversal parity
- Added `VisualTreeHelper.cs` to expose `GetChildrenCount`, `GetChild`, and `GetParent`. - Delegated logic to existing `UIElement` methods `VisualChildrenCount`, `GetVisualChild`, and `Parent`. - Added accompanying unit tests in `VisualTreeHelperTests`. Co-authored-by: tedd <493224+tedd@users.noreply.github.com>
1 parent ca7524e commit 8ae873a

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

.jules/forge.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,10 @@
142142
- Systematically removed `OnDataContextChanged` overrides in `Border`, `DialogBox`, `Table`, `TuiWindow`, and `Grid`.
143143
- Removed explicit `DataContext` local value setters when assigning child elements (e.g., `PushOverlay` in `TuiWindow.cs`, `Content` setter in `DialogBox.cs`).
144144
- Delegated context propagation fully to `UIElement.OnPropertyChanged` which naturally handles `IsInherited` property traversal, matching the exact WPF hierarchical structure and eliminating false-positive `HasLocalValue` states on visual children.
145+
146+
## 2024-05-15 - VisualTreeHelper Parity Integration
147+
**Observation:** Discovered a parity deficit where the `VisualTreeHelper` utility class was completely missing. The TUI framework exposed `VisualChildrenCount` and `GetVisualChild` directly on `UIElement`, but WPF delegates visual tree traversal explicitly to the `VisualTreeHelper` API to decouple the logical/visual tree navigation mechanisms from individual component interfaces.
148+
**Strategic Action:**
149+
- Engineered the `VisualTreeHelper` static class.
150+
- Implemented standard visual tree traversal methods `GetChildrenCount`, `GetChild`, and `GetParent`.
151+
- Delegated the calls to the existing `VisualChildrenCount`, `GetVisualChild`, and `Parent` infrastructure on `UIElement`, bringing the framework structurally closer to standard XAML paradigms for visual hit-testing and control template tree navigation.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using Xunit;
3+
using Tedd.TUI;
4+
5+
namespace Tedd.TUI.Tests
6+
{
7+
public class VisualTreeHelperTests
8+
{
9+
[Fact]
10+
public void GetChildrenCount_NullReference_ThrowsArgumentNullException()
11+
{
12+
Assert.Throws<ArgumentNullException>(() => VisualTreeHelper.GetChildrenCount(null!));
13+
}
14+
15+
[Fact]
16+
public void GetChild_NullReference_ThrowsArgumentNullException()
17+
{
18+
Assert.Throws<ArgumentNullException>(() => VisualTreeHelper.GetChild(null!, 0));
19+
}
20+
21+
[Fact]
22+
public void GetParent_NullReference_ThrowsArgumentNullException()
23+
{
24+
Assert.Throws<ArgumentNullException>(() => VisualTreeHelper.GetParent(null!));
25+
}
26+
27+
[Fact]
28+
public void GetChildrenCount_ReturnsVisualChildrenCount()
29+
{
30+
var panel = new StackPanel();
31+
panel.Children.Add(new Button());
32+
panel.Children.Add(new TextBlock());
33+
34+
Assert.Equal(2, VisualTreeHelper.GetChildrenCount(panel));
35+
}
36+
37+
[Fact]
38+
public void GetChild_ReturnsVisualChild()
39+
{
40+
var panel = new StackPanel();
41+
var button = new Button();
42+
var textBlock = new TextBlock();
43+
panel.Children.Add(button);
44+
panel.Children.Add(textBlock);
45+
46+
Assert.Same(button, VisualTreeHelper.GetChild(panel, 0));
47+
Assert.Same(textBlock, VisualTreeHelper.GetChild(panel, 1));
48+
}
49+
50+
[Fact]
51+
public void GetParent_ReturnsParent()
52+
{
53+
var panel = new StackPanel();
54+
var button = new Button();
55+
panel.Children.Add(button);
56+
57+
Assert.Same(panel, VisualTreeHelper.GetParent(button));
58+
}
59+
}
60+
}

src/Tedd.TUI/VisualTreeHelper.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Tedd.TUI;
4+
5+
namespace Tedd.TUI
6+
{
7+
public static class VisualTreeHelper
8+
{
9+
public static int GetChildrenCount(UIElement reference)
10+
{
11+
if (reference == null) throw new ArgumentNullException(nameof(reference));
12+
return reference.VisualChildrenCount;
13+
}
14+
15+
public static UIElement GetChild(UIElement reference, int childIndex)
16+
{
17+
if (reference == null) throw new ArgumentNullException(nameof(reference));
18+
return reference.GetVisualChild(childIndex);
19+
}
20+
21+
public static UIElement? GetParent(UIElement reference)
22+
{
23+
if (reference == null) throw new ArgumentNullException(nameof(reference));
24+
return reference.Parent; // TUI maps VisualParent to Parent mostly, or maybe we need a dedicated VisualParent in the future?
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)