Back to list
christian289

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);
    }
}
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

AspectVisual TreeLogical Tree
Included elementsAll rendered elementsXAML-declared only
Template internalsIncludedNot included
Helper classVisualTreeHelperLogicalTreeHelper
Use caseRendering, Hit TestInherited properties, structure
Completion timeAfter LoadedImmediately 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