Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Logical / Visual Tree Walking in WPF

0.00/5 (No votes)
15 Feb 2016 1  
Advanced custom logical and visual tree walking methods

Introduction

In the WPF world, the UIElement walking methods are limited. There are times when being able to navigate between UI components can be beneficial. Using design patterns, such as MVVM, can eliminate the need for this type of control management. The advanced walking methods shown here are a good alternative to the native .NET methodology.

Background

A basic understanding of what the logical and visual trees are in WPF.

Using the Code

The methods of the LogicalVisualTreeHelper have been made extension methods for a DependencyObject type. This makes using them from a visual control really easy.

For example, to get the main Grid of a WPF application with the application window for a reference...

Grid grid = this.GetVisualChild<Grid>();

Or:

Grid grid = this.GetLogicalChild<Grid>();

The first will traverse through the application's Visual Tree while the second traverses through the Logical Tree. Though the underlying concepts are rather complex, these methods are extremely easy to use. Some controls in WPF, like the ContextMenu, break the Visual and Logical Trees. Control types like these are out of the reach of these walking methods.

Points of Interest

There are a few things to keep in mind. When is it best to use the Visual Tree or Logical Tree? Is one a better choice than the other?

Logical Tree

The Logical Tree is the best choice and should be used first in an attempt to acquire children or parents. You can read more here on MSDN.

The simplified version is because the Logical Tree is a direct relationship between parent and child. Even the FrameworkElement's Parent property documentation page says... logical parent. (Example above)

Visual Tree

The Visual Tree will be significantly larger than the Logical Tree. It contains all controls currently shown on screen. More information can be found on MSDN about it. This should be your second choice for attempting to acquire child or parent objects. (Example above)

Ancestors and Descendants

Sometimes using the Logical or Visual Trees alone is not enough to reach the desired control. Sometimes the use of both will be necessary. For these times extension methods for GetAncestor(s) and GetDescendant(s) have been added that will attempt to close that gap.

Grid grid = this.GetDescendant<Grid>();

This also works for going up both trees.

Window window = grid.GetAncestor<Window>();

Nameless vs Named Controls

There are overloads for each method that accept a string that represents a name to look for. When specifying a name the type of control has to derive from FrameworkElement or FrameworkContentElement (where the name property originates from).

Nameless Controls

When using the signature for the methods that do not require a name to be specified, the first instance of the type is returned. If there are several nested grids, a call to GetLogicalChild<Grid> will return the first grid encountered.

Named Controls

When using the signature for the methods that do require a string for name, the name is matched as well as the type. If the name can't be matched, then nothing is returned.

Notes

This was originally written before the .NET Framework 4.6 was released with its improved support for tree traversal. However, the include methods with their generic signatures may prove to be more convenient even if they do not replace or improve upon the native .NET implementation.

A Quick Example

XAML

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid Grid.Column="0">
        <Border BorderBrush="LightBlue" 
        BorderThickness="1" CornerRadius="5" Margin="5">
            <TabControl Margin="5">
                <TabItem Header="One">
                    <TextBox />
                </TabItem>
                <TabItem Header="Two">
                    <Button Content="Button" 
                    HorizontalAlignment="Center" VerticalAlignment="Center" />
                </TabItem>
            </TabControl>
        </Border>
    </Grid>
    <Grid Grid.Column="1">
        <Border BorderBrush="LightBlue" 
        BorderThickness="1" CornerRadius="5" Margin="5">
            <Grid>
                <Border BorderBrush="Black" BorderThickness="1" 
                CornerRadius="5" Margin="5">
                    <TabControl Margin="5" TabStripPlacement="Right">
                        <TabItem Header="Three">
                            <TabControl Margin="5" 
                            TabStripPlacement="Bottom">
                                <TabItem Header="Five">
                                    <Button Content="Button" 
                                    HorizontalAlignment="Center" 
						VerticalAlignment="Center" />
                                </TabItem>
                                <TabItem x:Name="deepTabItem" 
                                Header="Six">
                                    <TextBlock x:Name="deepTextBlock" 
                                    Text="More text" />
                                </TabItem>
                            </TabControl>
                        </TabItem>
                        <TabItem Header="Four">
                            <TextBlock Text="Text" />
                        </TabItem>
                    </TabControl>
                </Border>
            </Grid>
        </Border>
    </Grid>
</Grid>

C#

Grid grid = this.GetVisualChild<Grid>();
Grid grid2 = this.GetLogicalChild<Grid>();
Grid grid3 = this.GetDescendant<Grid>();

HashSet<TabItem> tabItems = this.GetDescendants<TabItem>();
HashSet<TextBlock> textBlocks = this.GetDescendants<TextBlock>();

TabItem firsTabItemEncounter = this.GetDescendant<TabItem>();
TextBlock firstTextBlockEncounter = this.GetDescendant<TextBlock>();

TabItem namedTabItem = this.GetDescendant<TabItem>("deepTabItem");
TextBlock namedTextBlock = this.GetDescendant<TextBlock>("deepTextBlock");

Window window = namedTextBlock.GetAncestor<Window>();

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here