← Back to list

navigating-visual-logical-tree
by christian289
ClaudeCode와 함께하는 .NET 개발 튜토리얼
⭐ 1🍴 0📅 Jan 25, 2026
SKILL.md
name: navigating-visual-logical-tree description: Navigates WPF Visual Tree and Logical Tree with VisualTreeHelper and LogicalTreeHelper patterns. Use when traversing elements, accessing template internals, or understanding event routing.
WPF Visual Tree & Logical Tree Patterns
In WPF, element relationships are represented by two tree structures.
1. Core Differences
1.1 Logical Tree
- Structure of elements explicitly declared in XAML
- Based on Content relationships
- Event routing path
- Inherited property (DataContext, FontSize, etc.) propagation path
1.2 Visual Tree
- Includes all elements actually rendered
- Includes elements inside ControlTemplate
- Basis for Hit Testing
- Determines rendering order
1.3 Comparison Example
<!-- XAML definition -->
<Window>
<Button Content="Click"/>
</Window>
Logical Tree: Visual Tree:
Window Window
└── Button └── Border (inside Button's Template)
└── ContentPresenter
└── TextBlock ("Click")
2. VisualTreeHelper
2.1 Key Methods
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);
}
}
2.2 Finding Children of Specific Type
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;
}
}
2.3 Finding Parent Elements
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;
}
}
3. LogicalTreeHelper
3.1 Key Methods
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);
}
}
3.2 Logical Tree Search
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;
}
}
4. Scenario-based Selection
4.1 Visual Tree Use Scenarios
// 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);
4.2 Logical Tree Use Scenarios
// 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
}
5. Event Routing and Trees
5.1 Bubbling (Upward)
Event propagates along Visual Tree path
Button click → ContentPresenter → Border → Grid → Window
5.2 Tunneling (Downward)
Preview events start from root and propagate downward
Window → Grid → Border → ContentPresenter → Button
5.3 Code Example
// 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;
};
6. Advanced Patterns
For advanced patterns, see references/tree-advanced.md:
- Template Access: OnApplyTemplate, GetTemplateChild patterns
- Performance Optimization: Depth-limited search, cached results
7. Summary Comparison Table
| 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 |
8. References
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
