From wpf-dev-pack
Navigates WPF Visual and Logical Trees using VisualTreeHelper and LogicalTreeHelper. Useful for traversing elements, accessing template internals, and understanding event routing.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
In WPF, element relationships are represented by two tree structures.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
In WPF, element relationships are represented by two tree structures.
<!-- XAML definition -->
<Window>
<Button Content="Click"/>
</Window>
Logical Tree: Visual Tree:
Window Window
└── Button └── Border (inside Button's Template)
└── ContentPresenter
└── TextBlock ("Click")
namespace MyApp.Helpers;
using System.Windows;
using System.Windows.Media;
public static class VisualTreeHelperEx
{
/// <summary>
/// Get child element count
/// </summary>
public static int GetChildCount(DependencyObject parent)
{
return VisualTreeHelper.GetChildrenCount(parent);
}
/// <summary>
/// Get child element by index
/// </summary>
public static DependencyObject? GetChild(DependencyObject parent, int index)
{
return VisualTreeHelper.GetChild(parent, index);
}
/// <summary>
/// Get parent element
/// </summary>
public static DependencyObject? GetParent(DependencyObject child)
{
return VisualTreeHelper.GetParent(child);
}
}
namespace MyApp.Helpers;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
public static class VisualTreeSearcher
{
/// <summary>
/// Find all child elements of specific type
/// </summary>
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
yield return typedChild;
}
// Recursive search
foreach (var descendant in FindVisualChildren<T>(child))
{
yield return descendant;
}
}
}
/// <summary>
/// Find first child element of specific type
/// </summary>
public static T? FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
var result = FindVisualChild<T>(child);
if (result is not null)
{
return result;
}
}
return null;
}
/// <summary>
/// Find child element by name
/// </summary>
public static T? FindVisualChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
{
var childCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T element && element.Name == name)
{
return element;
}
var result = FindVisualChildByName<T>(child, name);
if (result is not null)
{
return result;
}
}
return null;
}
}
namespace MyApp.Helpers;
using System.Windows;
using System.Windows.Media;
public static class VisualParentSearcher
{
/// <summary>
/// Find parent element of specific type
/// </summary>
public static T? FindVisualParent<T>(DependencyObject child) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(child);
while (parent is not null)
{
if (parent is T typedParent)
{
return typedParent;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
/// <summary>
/// Find parent element matching condition
/// </summary>
public static DependencyObject? FindVisualParent(
DependencyObject child,
Func<DependencyObject, bool> predicate)
{
var parent = VisualTreeHelper.GetParent(child);
while (parent is not null)
{
if (predicate(parent))
{
return parent;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
namespace MyApp.Helpers;
using System.Collections;
using System.Windows;
public static class LogicalTreeHelperEx
{
/// <summary>
/// Enumerate child elements
/// </summary>
public static IEnumerable GetLogicalChildren(DependencyObject parent)
{
return LogicalTreeHelper.GetChildren(parent);
}
/// <summary>
/// Get parent element
/// </summary>
public static DependencyObject? GetLogicalParent(DependencyObject child)
{
return LogicalTreeHelper.GetParent(child);
}
}
namespace MyApp.Helpers;
using System.Collections.Generic;
using System.Windows;
public static class LogicalTreeSearcher
{
/// <summary>
/// Find all logical children of specific type
/// </summary>
public static IEnumerable<T> FindLogicalChildren<T>(DependencyObject parent) where T : DependencyObject
{
foreach (var child in LogicalTreeHelper.GetChildren(parent))
{
if (child is T typedChild)
{
yield return typedChild;
}
if (child is DependencyObject depObj)
{
foreach (var descendant in FindLogicalChildren<T>(depObj))
{
yield return descendant;
}
}
}
}
/// <summary>
/// Find logical parent of specific type
/// </summary>
public static T? FindLogicalParent<T>(DependencyObject child) where T : DependencyObject
{
var parent = LogicalTreeHelper.GetParent(child);
while (parent is not null)
{
if (parent is T typedParent)
{
return typedParent;
}
parent = LogicalTreeHelper.GetParent(parent);
}
return null;
}
}
// 1. Access elements inside template
var scrollViewer = VisualTreeSearcher.FindVisualChild<ScrollViewer>(listBox);
// 2. Register focus event to all TextBoxes
foreach (var textBox in VisualTreeSearcher.FindVisualChildren<TextBox>(window))
{
textBox.GotFocus += OnTextBoxGotFocus;
}
// 3. Find ListBoxItem of clicked element
var listBoxItem = VisualParentSearcher.FindVisualParent<ListBoxItem>(clickedElement);
// 1. Check DataContext inheritance path
var dataContextSource = LogicalTreeSearcher.FindLogicalParent<FrameworkElement>(element);
// 2. Process only explicitly declared children
foreach (var button in LogicalTreeSearcher.FindLogicalChildren<Button>(panel))
{
// Buttons inside ControlTemplate are excluded
}
Event propagates along Visual Tree path
Button click → ContentPresenter → Border → Grid → Window
Preview events start from root and propagate downward
Window → Grid → Border → ContentPresenter → Button
// PreviewMouseDown: Tunneling (Window → Target)
window.PreviewMouseDown += (s, e) =>
{
// Check target element
var target = e.OriginalSource as DependencyObject;
// Check parent in Visual Tree
var button = VisualParentSearcher.FindVisualParent<Button>(target);
if (button is not null)
{
// Click inside button area
}
};
// MouseDown: Bubbling (Target → Window)
button.MouseDown += (s, e) =>
{
// Stop bubbling if already handled
e.Handled = true;
};
For advanced patterns, see references/tree-advanced.md:
| Aspect | Visual Tree | Logical Tree |
|---|---|---|
| Included elements | All rendered elements | XAML-declared only |
| Template internals | Included | Not included |
| Helper class | VisualTreeHelper | LogicalTreeHelper |
| Use case | Rendering, Hit Test | Inherited properties, structure |
| Completion time | After Loaded | Immediately on creation |