Find controls by name in WPF with C#

[find controls]

This example lets you find controls by entering their names. When you enter a control’s name and a color and click Apply, the program changes the selected control’s background color.

You can use XAML code to give names to the controls in your application and the C# code behind can refer to the controls by those names. If you want to find a control by its name at run time, however, you have a bit of work to do.

XAML Code

To use the program, you need to know the names of its controls. The program uses the following XAML code. The control names are highlighted in blue so they are easy to see.

<Window x:Class="howto_wpf_find_control_by_name.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="howto_wpf_find_control_by_name"
    Height="220" Width="250"
    Name="MainWindow">
    <Grid Name="Grid1" Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Width" Value="75" />
                <Setter Property="Height" Value="30"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="Margin" Value="10"/>
            </Style>
            <Style TargetType="Label">
                <Setter Property="Width" Value="60" />
                <Setter Property="Margin" Value="5" />
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="Width" Value="75" />
                <Setter Property="VerticalContentAlignment" Value="Center" />
                <Setter Property="Margin" Value="5" />
            </Style>
        </Grid.Resources>

        <Button Name="Button1" Grid.Row="0" Grid.Column="0">Button1</Button>
        <Button Name="Button2" Grid.Row="0" Grid.Column="1">Button2</Button>
        
        <StackPanel Name="StackPanel1"
            Grid.Row="1" Grid.ColumnSpan="2"
            HorizontalAlignment="Center">
            <StackPanel Name="StackPanel2" Orientation="Horizontal">
                <Label Name="Label1">Control:</Label>
                <TextBox Name="TextBox1">TextBox1</TextBox>
            </StackPanel>
            <StackPanel Name="StackPanel3" Orientation="Horizontal">
                <Label Name="Label2">Color:</Label>
                <TextBox Name="TextBox2">Pink</TextBox>
            </StackPanel>
            <Button Name="Button3" Click="Button3_Click" IsDefault="True">Apply</Button> 
        </StackPanel> 
    </Grid>
</Window>

C# Code

The controls in a WPF application form a hierarchy. To find controls by name, you can start at the top of the hierarchy and recursively work your way down through the controls until you find the one that you want. Unfortunately WPF controls don’t provide a simple Children collection that you can examine. Instead you need to use the VisualTreeHelper class’s GetChildrenCount method to see how many children a control has. You then need to us the same class’s GetChild method to get the children.

The following FindDescendant method searches a control hierarchy for a control with the given name.

// Find a descendant control by name.
private static DependencyObject FindDescendant(
    DependencyObject parent, string name)
{
    // See if this object has the target name.
    FrameworkElement element = parent as FrameworkElement;
    if ((element != null) && (element.Name == name)) return parent;

    // Recursively check the children.
    int num_children = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < num_children; i++)
    {
        // See if this child has the target name.
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        DependencyObject descendant = FindDescendant(child, name);
        if (descendant != null) return descendant;
    }

    // We didn't find a descendant with the target name.
    return null;
}

The method first converts the parent control into a FrameworkElement. If the result is not null (indicating that the parent actually inherits from FrameworkElement) and the element has the desired name, then the method simply returns it.

Notice that the code compares the names as they are. That means, for example, that you cannot search for a control named TextBox1 by entering the name textbox1. You can modify the code to make it case-insensitive if you like.

If the parent control does not have the target name, the program uses the VisualTreeHelper class to see how many children the parent has. It then loops through the children and recursively calls FindDescendant for each.

If one of the recursive calls returns a non-null descendant with the correct name, the method returns it. If none of the recursive calls finds the desired control, the method return null to tell method calls farther up the call stack that the target control is not present in this part of the control hierarchy.

When you click the Apply button, the following code executes.

private void Button3_Click(object sender, RoutedEventArgs e)
{
    // Find the object by name.
    string name = TextBox1.Text;
    DependencyObject descendant = FindDescendant(MainWindow, name);
    if (descendant == null)
    {
        MessageBox.Show("There such control " + name,
            "No Such Control", MessageBoxButton.OK);
        return;
    }

    // Get the color by name.
    string color_name = TextBox2.Text;
    Color color;
    try
    {
        color = (Color)ColorConverter.ConvertFromString(color_name);
    }
    catch
    {
        MessageBox.Show("No such color " + color_name,
            "No Such Color", MessageBoxButton.OK);
        return;
    }

    try
    {
        // Apply the color.
        // See if the object is a Panel.
        if (descendant is Panel)
        {
            Panel panel = descendant as Panel;
            panel.Background = new SolidColorBrush(color);
        }
        else if (descendant is Control)
        {
            Control control = descendant as Control;
            control.Background = new SolidColorBrush(color);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code is actually error handling in case you enter an invalid control name or color.

The code first gets the control name that you entered and calls FindDescendant to find it. The program uses its main window as the top of the control hierarchy to search. If you look back at the way the FindDescendant method works, you’ll see that it starts by checking the parent control to see if it has the target name. That means you can enter MainWindow as a control name and the program will find the top-level window.

If the FindDescendant method returns null, the code displays an error message and returns.

If the method found a control, the program gets the color name that you entered. It then uses the ColorConverter class’s ConvertFromString method to convert that name into a Color. Note that the ConvertFromString method is case-insensitive so LightBlue, lightblue, and LIGHTblue will all work. The color must be a single word and spelling is important, however, so LiteBlue and Light Blue will not work.

If the ConvertFromString method throws an exception, the code displays an error message and returns.

If the program successfully obtains the control and color, it tries to set the control’s background color. To do that, the program determines whether the object is a Panel or Control. If the object is of one of those types, the code converts it into that type and sets its Background property to an appropriate brush.

This is all a bit weird because the object that the program finds is actually a DependencyObject. Unfortunately that class does not have a Background property. Descendant classes such as Panel, Control, TextBlock, and others have the property.

The Panel class is an ancestor of classes such as Grid and StackPanel, which are used by the program. Similarly the Control class is an ancestor of the Label, Button, and TextBox classes used by the program.

The program looks for Panel and Control objects because it uses them. You might need to look for other kinds of controls, such as TextBlock if your program uses them.

Conclusion

Finding controls recursively isn’t new. You can use a similar technique to find controls in Windows Forms applications, too. WPF makes it all a bit more awkward by making you use the VisualTreeHelper class and by placing the Background property in several different classes, but the general approach is similar.

Download the example to see additional details and to experiment with the program.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in controls, wpf and tagged , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.