WPF: get all controls of a specific type using C#
Introduction
In this article language extension methods are presented to locate controls in containers such as a windows, grid or StackPanel for performing business logic like obtaining a selection from a group of Checkboxes, a selected RadioButton in a group or enabling/disabling specific types of controls.
Base extension method
The base language extension method given a container for dependencyItem parameter will recursively iterate all controls in the container returning an IEnumerable of T.
public static IEnumerable<T> Descendants<T>(DependencyObject dependencyItem) where T : DependencyObject
{
if (dependencyItem != null)
{
for (var index = 0; index < VisualTreeHelper.GetChildrenCount(dependencyItem); index++)
{
var child = VisualTreeHelper.GetChild(dependencyItem, index);
if (child is T dependencyObject)
{
yield return dependencyObject;
}
foreach (var childOfChild in Descendants<T>(child))
{
yield return childOfChild;
}
}
}
}
It's purpose is to iterate all controls and meant to be called with conditions e.g. get all TextBox controls. By itself would not really serve any real purpose. Example, asking for all control in a grid for the following window would return 210 controls.
Also, rather than call the base extension a wrapper would be better with a name that indicates the purpose.
public static List<Control> AllControls(this DependencyObject control) =>
Descendants<Control>(control).ToList();
Beginning call from the window's grid in a button click.
List<Control> allControls = MainGrid.AllControls();
Wrapper extension methods
CheckBoxes
In a real world example a series of CheckBoxes may be presented to get user selections. In the following example this is the XAML to present several CheckBox controls, a button to get selections using CheckBox controls. Focus is on the grid CheckBoxGrid.
<Window x:Class="WpfAppExample1.CheckBoxWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppExample1"
mc:Ignorable="d"
Title="CheckBoxes"
Height="227.571"
Width="206"
WindowStartupLocation="CenterScreen"
WindowStyle="ToolWindow">
<Grid Name="CheckBoxGrid">
<CheckBox Content="C#"
Height="16"
HorizontalAlignment="Left"
Margin="20,20,0,0"
Name="sharpCheckBox"
VerticalAlignment="Top" />
<CheckBox Content="VB.NET"
Height="16"
HorizontalAlignment="Left"
Margin="20,51,0,0"
Name="VisualBasicCheckBox"
VerticalAlignment="Top" />
<CheckBox Content="F#"
Height="16"
HorizontalAlignment="Left"
Margin="20,85,0,0"
Name="functionCheckBox"
VerticalAlignment="Top" />
<CheckBox Content="None"
Height="16"
HorizontalAlignment="Left"
Margin="20,119,0,0"
Name="NoneCheckBox"
VerticalAlignment="Top" />
<Button x:Name="GetCheckedButton"
Content="Get checked"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150"
Margin="20,156,0,0"
Click="GetCheckedButton_Click"/>
</Grid>
</Window>
In the button click event the first extension determines if there are any CheckBox control checked which makes a call to another extension method to get a list of CheckBox which obtains information from the base extension method.
/// <summary>
/// Determine if any CheckBoxes are checked in container
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public static bool CheckListAnyChecked(this DependencyObject control) =>
control.CheckBoxListChecked().Any();
Get all CheckBoxes from the base extension method with a Where condition.
/// <summary>
/// Get all ComboBox controls in a container
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public static List<ComboBox> ComboBoxList(this DependencyObject control) =>
Descendants<ComboBox>(control).ToList();
This checks to see if there are checked CheckBox controls and presents their text.
private void GetCheckedButton_Click(object sender, RoutedEventArgs e)
{
if (CheckBoxGrid.CheckListAnyChecked())
{
var result = string.Join("\n",
CheckBoxGrid.CheckBoxListChecked()
.Select(cb => cb.Content.ToString())
.ToArray());
MessageBox.Show($"Selected languages\n{result}");
}
else
{
MessageBox.Show("Nothing has been selected.");
}
}
An alternate approach which first gets a list of CheckBox controls, applies a Where condition then if there are checked items present them. The main difference is the list of CheckBox controls is retrieved once rather than twice as per the first example.
private void GetCheckedButton_Click(object sender, RoutedEventArgs e)
{
var checkBoxes = CheckBoxGrid.CheckBoxListChecked();
if (checkBoxes.Any(cb => cb.IsChecked == true))
{
var result = string.Join("\n",
checkBoxes
.Select(cb => cb.Content.ToString())
.ToArray());
MessageBox.Show($"Selected languages\n{result}");
}
else
{
MessageBox.Show("Nothing has been selected.");
}
}
Radio Button groups
A common operation is to present a group of radio buttons in a group, sometimes multiple groups of radio buttons. Using the same approach as with CheckBox extension method wrapper over the base extension the same may be done with radio buttons.
In this example there are two groups of radio buttons.
<Window x:Class="WpfAppExample1.RadioButtonWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppExample1"
mc:Ignorable="d"
Title="Radio Buttons"
Height="225"
Width="314"
WindowStartupLocation="CenterScreen"
WindowStyle="ToolWindow">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="13*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<Border Padding="10" Grid.ColumnSpan="2">
<StackPanel x:Name="Stacker1">
<RadioButton x:Name="Windows8RadioButton"
GroupName="OperatingSystemGroup"
Content="Windows 8"
IsChecked="True"/>
<RadioButton x:Name="Windows7RadioButton"
GroupName="OperatingSystemGroup"
Content="Windows 7" />
<RadioButton x:Name="Windows10RadioButton"
GroupName="OperatingSystemGroup"
Content="Windows 10" />
<RadioButton x:Name="Office2007RadioButton"
GroupName="OfficeGroup"
Content="Microsoft Office 2007"
IsChecked="True"/>
<RadioButton x:Name="Office2010RadioButton"
GroupName="OfficeGroup"
Content="Microsoft Office 2010"/>
<RadioButton x:Name="OpenOfficeRadioButton"
GroupName="OfficeGroup"
Content="Open Office"/>
</StackPanel>
</Border>
<Button Grid.Column="0"
x:Name="GetCheckedRadioButtons"
Content="Radio button list"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="137"
Margin="10,120,0,0"
Click="GetCheckedRadioButtons_Click"/>
<Button Grid.Column="0"
x:Name="GetCheckedRadioButtonsByGroupName"
Content="Selected by group"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="137" Margin="10,147,0,0"
Click="GetCheckedRadioButtonsByGroupName_Click"/>
</Grid>
</Window>
To get the selected radio button for office version which is in a group the following extension method is used to work off a specific group.
public static RadioButton SelectedRadioButtonInGroup(this DependencyObject control, string groupName) =>
Descendants<RadioButton>(control)
.FirstOrDefault(radioButton =>
radioButton.IsChecked == true && radioButton.GroupName == groupName);
In this case the container is a StackPanel named Stacker1.
private void GetCheckedRadioButtonsByGroupName_Click(object sender, RoutedEventArgs e)
{
var selectedOperatingSystem = Stacker1
.SelectedRadioButtonInGroup("OperatingSystemGroup");
MessageBox.Show($"Operating system is {selectedOperatingSystem.Content}");
}
To get all radio buttons regardless of a group.
public static List<RadioButton> RadioListAreChecked(this DependencyObject control) =>
Descendants<RadioButton>(control)
.Where(radioButton => radioButton.IsChecked == true).ToList();
Other controls
What applies to the presented extensions targeting specific controls in containers applies to TextBox, TextBlock and other controls.
/// <summary>
/// Get all TextBox controls in a container
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public static List<TextBox> TextBoxList(this DependencyObject control) =>
Descendants<TextBox>(control).ToList();
/// <summary>
/// Get all TextBoxBlock controls in a container
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public static List<TextBlock> TextBoxBlockList(this DependencyObject control) =>
Descendants<TextBlock>(control).ToList();
Enabling/disabling controls
Business logic may dictate that specific controls should be enabled or disabled which can be done using extensions also, for instance the following can enable or disable all CheckBox controls in a container called as needed,
/// <summary>
/// Enable or disable all check boxes in a container
/// </summary>
/// <param name="control"></param>
/// <param name="enable"></param>
public static void EnableCheckBoxes(this DependencyObject control, bool enable = false)
{
foreach (var checkBox in Descendants<CheckBox>(control))
{
checkBox.IsEnabled = enable;
}
}
In some cases one CheckBox should not be touched, in this case provide a parameter to exclude it.
/// <summary>
/// Enable or disable all CheckBox controls excluding one
/// specified in excludeName parameter.
/// </summary>
/// <param name="control">Parent control</param>
/// <param name="enable">true, false</param>
/// <param name="excludeName">Exclude this control</param>
/// <remarks>
/// An adaptation could be having the last parameter an
/// array of CheckBox names.
/// </remarks>
public static void EnableCheckBoxesSpecial(this DependencyObject control, bool enable, string excludeName)
{
foreach (var checkBox in Descendants<CheckBox>(control))
{
if (checkBox.Name != excludeName)
{
checkBox.IsEnabled = enable;
}
}
}
The above may be changed to pass an array of CheckBox names to exclude or even look at the Tag property for a specific value to include or exclude. The following does this with TextBox controls where the Tag = "R". As stated in the summary "R" is a poor choice for a indicator and highly recommend changing it to a meaningful indicator.
/// <summary>
/// Enable or disable a TextBox in a container
///
/// If Tag = R (need a better indicator) this TextBox IsReadOnly is excluded as it's
/// a read only TextBox always per business rules.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control"></param>
/// <param name="enable"></param>
public static void EnableTextBoxes<T>(this DependencyObject control, bool enable = false)
{
foreach (var textBox in Descendants<TextBox>(control))
{
if (textBox.Tag?.ToString() == "R")
{
continue;
}
textBox.IsReadOnly = enable;
textBox.Background = textBox.IsReadOnly ?
new SolidColorBrush(Color.FromArgb(255, 240, 240, 240)):
Brushes.White;
}
}
The following also includes those controls with a specific tag. In the supplied source code the MainWindow has TextBox controls which grow/shrink by default, this extension method prevents this behavior.
/// <summary>
/// By setting Width this prevents horizontal resizing
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control"></param>
/// <param name="width"></param>
/// <remarks>
/// Tag = A where A means nothing, need to have a better indicator
/// </remarks>
public static void SetTextBoxUniversalWidth<T>(this DependencyObject control, int width = 32)
{
foreach (var textBox in Descendants<TextBox>(control))
{
if (textBox.Tag?.ToString() == "A")
{
textBox.Width = width;
}
}
}
Called againsts a StackPanel.
GridStackPanel.SetTextBoxUniversalWidth<StackPanel>();
Windows Forms
For completeness the following are a base for working with controls in Windows Forms projects. These extensions are used exactly as with the WPF extensions.
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace ControlExtensions
{
public static class ControlExtensions
{
/// <summary>
/// Get a collection of a specific type of control from a control or form.
/// </summary>
/// <typeparam name="T">Type of control</typeparam>
/// <param name="control">Control to traverse</param>
/// <returns>IEnumerable of T</returns>
public static IEnumerable<T> Descendants<T>(this Control control) where T : class
{
foreach (Control child in control.Controls)
{
T thisControl = child as T;
if (thisControl != null)
{
yield return (T)thisControl;
}
if (child.HasChildren)
{
foreach (T descendant in Descendants<T>(child))
{
yield return descendant;
}
}
}
}
public static List<TextBox> TextBoxList(this Control container) =>
container.Descendants<TextBox>().ToList();
public static List<Label> LabelList(this Control container) =>
container.Descendants<Label>().ToList();
public static List<DataGridView> DataGridViewList(this Control container) =>
container.Descendants<DataGridView>().ToList();
public static List<ListView> ListViewViewList(this Control container) =>
container.Descendants<ListView>().ToList();
public static List<CheckBox> CheckBoxList(this Control container) =>
container.Descendants<CheckBox>().ToList();
public static List<ComboBox> ComboBoxList(this Control container) =>
container.Descendants<ComboBox>().ToList();
public static List<ListBox> ListBoxList(this Control container) =>
container.Descendants<ListBox>().ToList();
public static List<DateTimePicker> DateTimePickerList(this Control container) =>
container.Descendants<DateTimePicker>().ToList();
public static List<PictureBox> PictureBoxList(this Control container) =>
container.Descendants<PictureBox>().ToList();
public static List<Panel> PanelList(this Control container) =>
container.Descendants<Panel>().ToList();
public static List<GroupBox> GroupBoxList(this Control container) =>
container.Descendants<GroupBox>().ToList();
public static List<Button> ButtonList(this Control container) =>
container.Descendants<Button>().ToList();
public static List<RadioButton> RadioButtonList(this Control container) =>
container.Descendants<RadioButton>().ToList();
public static List<NumericUpDown> NumericUpDownList(this Control container) =>
container.Descendants<NumericUpDown>().ToList();
public static RadioButton RadioButtonChecked(this Control container, bool pChecked = true) =>
container.Descendants<RadioButton>().ToList()
.FirstOrDefault((radioButton) => radioButton.Checked == pChecked);
public static string[] ControlNames(this IEnumerable<Control> container) =>
container.Select((control) =>
control.Name).ToArray();
}
}
Summary
This article has provided language extension methods to access controls in containers such as a Window, a Grid or StackPanel to perform actions against specific controls or obtain information on user selections.
See also
- Finding Element in ItemsControl DataTemplate (ListView/GridView/FlipView) - Window Store Apps
- WPF: Programmatically Selecting and Focusing a Row or Cell in a DataGrid
- WPF: Tips - Lookless Controls
- WinForms get all controls of a specific type VB.NET