Click here to Skip to main content
15,890,043 members
Articles / Desktop Programming / XAML
Tip/Trick

Logical / Visual Tree Walking in WPF

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
15 Feb 2016CPOL3 min read 10.4K   9   4  
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...

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

Or:

C#
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.

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

This also works for going up both trees.

C#
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

XML
<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#

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect SL-X
United States United States
Developed various languages over the years. Found C# and fell in love with it. Been doing it ever since. Love WPF or ASP.Net MVC with the Razor view engine as front-end technologies. As a developer I want to make development easier for all developers. I like designing and providing tools for other programmers to use.

Comments and Discussions

 
-- There are no messages in this forum --