Navigating the Tree

When you first learned about trees, you also learned about tree traversal algorithms. This is one reason that WPF is organized into a tree - the rendering process actually uses a tree traversal algorithm to determine how large to make each control!

You can also traverse the tree yourself, by exploring Child, Children, or Parent properties. For example, if we needed to gain access to the ListSwitcher from the ShoppingList in the previous example, you could reach it by invoking:

   ListSwitcher switcher = this.Parent.Parent.Parent as ListSwitcher;

In this example, this is our ShoppingList, the first Parent is the Border containing the ShoppingList, the second Parent is the Grid containing that Border, and the third Parent is the actual ListSwitcher. We have to cast it to be a ListSwitcher because the type of the Parent property is a DependencyObject (a common base class of all controls).

Of course, this is a rather brittle way of finding an ancestor, because if we add any nodes to the element tree (perhaps move the Grid within a DockPanel), we’ll need to rewrite it. It would be better to use a loop to iteratively climb the tree until we find the control we’re looking for. This is greatly aided by the LogicalTreeHelper library, which provides standardized static methods for accessing parents and children in the elements tree:

// Start climbing the tree from this node
DependencyObject parent = this;
do
{
    // Get this node's parent
    parent = LogicalTreeHelper.GetParent(parent);
}
// Invariant: there is a parent element, and it is not a ListSwitcher 
while(!(parent is null || parent is ListSwitcher));
// If we get to this point, parent is either null, or the ListSwitcher we're looking for

Searching the ancestors is a relatively easy task, as each node in the tree has only one parent. Searching the descendants takes more work, as each node may have many children, with children of their own.

This approach works well for complex applications with complex GUIs, where it is infeasible to keep references around. However, for our simple application here, it might make more sense to refactor the ShoppingList class to keep track of the ListSwitcher that created it, i.e.:

using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ShoppingList.xaml
    /// </summary>
    public partial class ShoppingList : UserControl
    {
        /// <summary>
        /// The ListSwitcher that created this list
        /// </summary>
        private ListSwitcher listSwitcher;

        /// <summary>
        /// Constructs a new ShoppingList
        /// </summary>
        public ShoppingList(ListSwitcher listSwitcher)
        {
            InitializeComponent();
            this.listSwitcher = listSwitcher;
        }

        /// <summary>
        /// Adds the item in the itemTextBox to the itemsListView
        /// </summary>
        /// <param name="sender">The object sending the event</param>
        /// <param name="e">The events describing the event</param>
        void AddItemToList(object sender, RoutedEventArgs e)
        {
            // Make sure there's an item to add
            if (itemTextBox.Text.Length == 0) return;
            // Add the item to the list
            itemsListView.Items.Add(itemTextBox.Text);
            // Clear the text box
            itemTextBox.Clear();
        }
    }
}

However, this approach now tightly couples the ListSwitcher and ShoppingList - we can no longer use the ShoppingList for other contexts without a ListSwitcher.

If we instead employed the the traversal algorithm detailed above:

using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ShoppingList.xaml
    /// </summary>
    public partial class ShoppingList : UserControl
    {
        /// <summary>
        /// The ListSwitcher that created this list
        /// </summary>
        private ListSwitcher listSwitcher { 
            get {
                DependencyObject parent = this;
                do
                {
                    // Get this node's parent
                    parent = LogicalTreeHelper.GetParent(parent);
                }
                // Invariant: there is a parent element, and it is not a ListSwitcher 
                while(!(parent is null || parent is ListSwitcher));
                return parent;
            }
        }

        /// <summary>
        /// Constructs a new ShoppingList
        /// </summary>
        public ShoppingList()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Adds the item in the itemTextBox to the itemsListView
        /// </summary>
        /// <param name="sender">The object sending the event</param>
        /// <param name="e">The events describing the event</param>
        void AddItemToList(object sender, RoutedEventArgs e)
        {
            // Make sure there's an item to add
            if (itemTextBox.Text.Length == 0) return;
            // Add the item to the list
            itemsListView.Items.Add(itemTextBox.Text);
            // Clear the text box
            itemTextBox.Clear();
        }
    }
}

We could invoke the listSwitcher property to get the ancestor ListSwitcher. If this control is being used without one, the value will be Null.