navigating-visual-logical-tree

Installation
SKILL.md

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

Related skills
Installs
4
GitHub Stars
28
First Seen
Feb 28, 2026