none
Finding all controls of a given type across a TabControl

    Question

  • I've been trying to figure out a problem i've been having with finding all controls of a given type across a TabControl. I'm currently using the VisualTreeHelper class but this only finds controls on the currently viewed tab.

    Effectively, each TabItem contains a UserControl which is being passed into the following routine:

    private void getControlsList(Visual control)
    {
        int ChildNumber = VisualTreeHelper.GetChildrenCount(control);
    
        for (int i = 0; i <= ChildNumber - 1; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(control, i);
            if (v.GetType() == typeof(AdvancedTextBox))
                atbCollection.Add((AdvancedTextBox)v);
    
            if (VisualTreeHelper.GetChildrenCount(v) > 0)
            {
                getControlsList(v);
            }
        }
    }


    The looping through my tabs works, but I still can only find the controls of a given type on the currently viewed tab. From what i've read, this is a limitation of the VisualTreeHelper so is there another way I can find all controls of a given type across all tabs?

    Thanks
    Friday, May 08, 2009 3:16 PM

Answers

  • You could try to use this function:

            public static Visual GetDescendantByType(Visual element, Type type)
            {
                if (element == null) return null;
                if (element.GetType() == type) return element;
                Visual foundElement = null;
                if (element is FrameworkElement)
                    (element as FrameworkElement).ApplyTemplate();
                for (int i = 0;
                    i < VisualTreeHelper.GetChildrenCount(element); i++)
                {
                    Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                    foundElement = GetDescendantByType(visual, type);
                    if (foundElement != null)
                        break;
                }
                return foundElement;
            }
    The input element is the container such as Canvas, the Type is the type you want to search.

    Hope it helps.

    Please tell me whether it works.
    • Proposed as answer by Tao Liang Monday, May 11, 2009 9:04 AM
    • Marked as answer by Tao Liang Thursday, May 14, 2009 2:01 AM
    Monday, May 11, 2009 9:04 AM
  • I think the key issue is whether the AdvancedTextBox is in the selected TabItem or not.

    If the AdvancedTextBox is in the selected TabItem, WPF will add the AdvancedTextBoxinto the VisualTree and measure, arrange and render it.
    So you can find the AdvancedTextBox at this time.

    But if the AdvancedTextBox is not in the selected TabItem, WPF will not add it into the VisualTree, no measure, no arrange, no render.

    The reason is that the WPF designer want to optimize the performance of TabControl. Suppose there are 5 TabItems, and each TabItem contains alot of children. If WPF program have to construct and render all the children, it will be very slow. But if TabControl only handle the children just in the current selected TabItem, much memory will be saved.

    If you look through the template of TabControl, you will find that the content of TabControl will only be the selected TabItem:
    Content="{TemplateBinding TabControl.SelectedContent}"

    <Grid
          ClipToBounds="True"
          SnapsToDevicePixels="True"
          KeyboardNavigation.TabNavigation="Local">
          <Grid.ColumnDefinitions>
             <ColumnDefinition
                Name="ColumnDefinition0" />
             <ColumnDefinition
                Width="0"
                Name="ColumnDefinition1" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
             <RowDefinition
                Height="Auto"
                Name="RowDefinition0" />
             <RowDefinition
                Height="*"
                Name="RowDefinition1" />
          </Grid.RowDefinitions>
          <TabPanel
             Background="#00FFFFFF"
             IsItemsHost="True"
             Name="HeaderPanel"
             Margin="2,2,2,0"
             KeyboardNavigation.TabIndex="1"
             Panel.ZIndex="1"
             Grid.Column="0"
             Grid.Row="0" />
          <Grid
             Name="ContentPanel"
             KeyboardNavigation.TabIndex="2"
             KeyboardNavigation.TabNavigation="Local"
             KeyboardNavigation.DirectionalNavigation="Contained"
             Grid.Column="0"
             Grid.Row="1">
             <mwt:ClassicBorderDecorator
                Background="{TemplateBinding Panel.Background}"
                BorderStyle="Raised"
                BorderBrush="{TemplateBinding Border.BorderBrush}"
                BorderThickness="{TemplateBinding Border.BorderThickness}">
                <ContentPresenter
                   Content="{TemplateBinding TabControl.SelectedContent}"
                   ContentTemplate="{TemplateBinding TabControl.SelectedContentTemplate}"
                   ContentStringFormat="{TemplateBinding TabControl.SelectedContentStringFormat}"
                   ContentSource="SelectedContent"
                   Name="PART_SelectedContentHost"
                   Margin="2,2,2,2"
                   SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
             </mwt:ClassicBorderDecorator>
          </Grid>
       </Grid>
    Below is a test project that can demostrate this feature.
    Run the program and select TabItem 1, then click the find Button. The AdvancedTextBox will be found.

    But if you select TabItem 2, the AdvancedTextBox can not be found. Because the AdvancedTextBox is removed from the VisualTree when you select TabItem 2.

    Hope it helps.

    Test project

    XAML
    <Window x:Class="FindAdvancedTextBox.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:FindAdvancedTextBox"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <Button Name="btnFind" Click="btnFind_Click"    >Find AdvancedTextBox</Button>
            <TabControl Name="Container">
                <TabControl.Items>
                    <TabItem Header="1">
                        <StackPanel Height="100">
                            <local:AdvancedTextBox x:Name="txt1"> 
                            </local:AdvancedTextBox>
                        </StackPanel>
                    </TabItem>
                    <TabItem Header="2">
                        <StackPanel Height="100">
                        </StackPanel>
                    </TabItem>
                </TabControl.Items>
            </TabControl>
        </StackPanel>
    </Window>
    

    C#
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    namespace FindAdvancedTextBox
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void btnFind_Click(object sender, RoutedEventArgs e)
            {
                //Find
                AdvancedTextBox findresult = GetDescendantByType(Container, typeof(AdvancedTextBox)) as AdvancedTextBox;
                if (findresult == null)
                {
                    MessageBox.Show("Not Find");
                }
                else
                {
                    MessageBox.Show("Find");
                }
            }
            public static Visual GetDescendantByType(Visual element, Type type)
            {
                if (element == null) return null;
                if (element.GetType() == type) return element;
                Visual foundElement = null;
                if (element is FrameworkElement)
                    (element as FrameworkElement).ApplyTemplate();
                for (int i = 0;
                    i < VisualTreeHelper.GetChildrenCount(element); i++)
                {
                    Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                    foundElement = GetDescendantByType(visual, type);
                    if (foundElement != null)
                        break;
                }
                return foundElement;
            }
        }
        public class AdvancedTextBox : TextBox
        { 
        }
    }
    

    • Proposed as answer by Tao Liang Wednesday, May 13, 2009 8:13 AM
    • Marked as answer by Tao Liang Thursday, May 14, 2009 2:01 AM
    Wednesday, May 13, 2009 8:13 AM

All replies

  • It's bacause other controls does not exist in Visual tree, they are in Logical tree. Here basic print dump example of both. It can be very good start for you.
    Tamir http://khason.net/
    If your question was answered, please mark it.
    • Marked as answer by Tao Liang Monday, May 11, 2009 1:49 AM
    • Unmarked as answer by _tom_A Monday, May 11, 2009 8:40 AM
    Friday, May 08, 2009 6:43 PM
  • Thanks for that - using the LogicalTree did find controls more reliably. However, it doesn't appear to pick up controls that are created dynamically as part of a DataTemplate which is a problem. I'm I just missing something simple here?
    Monday, May 11, 2009 8:43 AM
  • You could try to use this function:

            public static Visual GetDescendantByType(Visual element, Type type)
            {
                if (element == null) return null;
                if (element.GetType() == type) return element;
                Visual foundElement = null;
                if (element is FrameworkElement)
                    (element as FrameworkElement).ApplyTemplate();
                for (int i = 0;
                    i < VisualTreeHelper.GetChildrenCount(element); i++)
                {
                    Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                    foundElement = GetDescendantByType(visual, type);
                    if (foundElement != null)
                        break;
                }
                return foundElement;
            }
    The input element is the container such as Canvas, the Type is the type you want to search.

    Hope it helps.

    Please tell me whether it works.
    • Proposed as answer by Tao Liang Monday, May 11, 2009 9:04 AM
    • Marked as answer by Tao Liang Thursday, May 14, 2009 2:01 AM
    Monday, May 11, 2009 9:04 AM
  • Or could you paste the full version of your code?
    Monday, May 11, 2009 9:05 AM
  • I can't unfortunately post all of my code as it's pretty large and wont run outside of our intranet, but the routine I'm currently using is as follows:

    private void getControlsList(object control)
    {
        if (control is FrameworkElement)
        {
            IEnumerable children = LogicalTreeHelper.GetChildren((FrameworkElement)control);
            foreach (object child in children)
            {
                if (child.GetType() == typeof(AdvancedTextBox) 
                    && !atbCollection.Contains((AdvancedTextBox)child))
                    atbCollection.Add((AdvancedTextBox)child); // atbCollection = List<AdvancedTextBox>
    
                if (child is FrameworkElement)
                {
                    IEnumerable subItems = LogicalTreeHelper.GetChildren((FrameworkElement)child);
                    foreach (object subItem in subItems)
                    {
                        getControlsList(child);
                    }
                }
            }
        }
    }

    I've not tried your code yet but will do once i figure out how to use it in conjunction with my stuff :)
    • Edited by _tom_A Monday, May 11, 2009 10:11 AM
    Monday, May 11, 2009 10:09 AM
  • I have tried the following using the code provided and I'm still not able to find any instance of AdvancedTextBox that is created dynamically - all others are fine still.

    private void getControlsList(object control)
    {
        if (control is FrameworkElement)
        {
            IEnumerable children = LogicalTreeHelper.GetChildren((FrameworkElement)control);
            foreach (object child in children)
            {
                if (child.GetType() == typeof(AdvancedTextBox) && !atbCollection.Contains((AdvancedTextBox)child))
                    atbCollection.Add((AdvancedTextBox)child);
    
                getDescendantByType(child as Visual);
    
                if (child is FrameworkElement)
                {
                    IEnumerable subItems = LogicalTreeHelper.GetChildren((FrameworkElement)child);
                    foreach (object subItem in subItems)
                    {
                        getControlsList(child);
                    }
                }
            }
        }
    }
    
    private Visual getDescendantByType(Visual element)
    {
        if (element == null) 
            return null;
    
        if (element.GetType() == typeof(AdvancedTextBox))
        {
            if (!atbCollection.Contains((AdvancedTextBox)element))
                atbCollection.Add((AdvancedTextBox)element);
            return element;
        }
        
        Visual foundElement = null;
        if (element is FrameworkElement)
            (element as FrameworkElement).ApplyTemplate();
    
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
        {
            Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
            foundElement = getDescendantByType(visual);
            if (foundElement != null)
                break;
        }
        return foundElement;
    }

    Am I missing something still?
    Monday, May 11, 2009 1:34 PM
  • very stange
    Monday, May 11, 2009 2:25 PM
  • Can you paste the definition of AdvancedTextBox?
    Does it has border or adorner?
    Wednesday, May 13, 2009 2:38 AM
  • AdvancedTextBox is essentially a wrapper around a normal TextBox but has a couple of extra bits which essentially count the number of spelling mistakes contained with in it (what i am trying to do is get the total number of spelling mistakes across all controls). I've not changed anything layout wise from a regular TextBox.
    Wednesday, May 13, 2009 6:58 AM
  • I've been trying to figure out a problem i've been having with finding all controls of a given type across a TabControl. I'm currently using the VisualTreeHelper class but this only finds controls on the currently viewed tab.

    Effectively, each TabItem contains a UserControl which is being passed into the following routine:

    private
     void
     getControlsList(Visual control)
    {
        int
     ChildNumber = VisualTreeHelper.GetChildrenCount(control);
    
        for
     (int
     i = 0; i <= ChildNumber - 1; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(control, i);
            if
     (v.GetType() == typeof
    (AdvancedTextBox))
                atbCollection.Add((AdvancedTextBox)v);
    
            if
     (VisualTreeHelper.GetChildrenCount(v) > 0)
            {
                getControlsList(v);
            }
        }
    }
    


    The looping through my tabs works, but I still can only find the controls of a given type on the currently viewed tab. From what i've read, this is a limitation of the VisualTreeHelper so is there another way I can find all controls of a given type across all tabs?

    Thanks
    Where is the AdvancedTextBox? In the current selected TabItem? Or not in the current selected TabItem?

    Wednesday, May 13, 2009 7:56 AM
  • It could be in either as there are n of them at any given time. When the app first loads and populates, I want to check all AdvancedTextBoxes spelling error counts regardless of the currently selected TabItem.

    This is the loop I'm using to iterate through my TabControl:

    foreach (System.Windows.Controls.TabItem item in tabControl.Items)
    {
        UserControl uc = item.Content as UserControl;
        getControlsList(uc);
    }

    thanks for your help so far :)
    Wednesday, May 13, 2009 8:05 AM
  • I think the key issue is whether the AdvancedTextBox is in the selected TabItem or not.

    If the AdvancedTextBox is in the selected TabItem, WPF will add the AdvancedTextBoxinto the VisualTree and measure, arrange and render it.
    So you can find the AdvancedTextBox at this time.

    But if the AdvancedTextBox is not in the selected TabItem, WPF will not add it into the VisualTree, no measure, no arrange, no render.

    The reason is that the WPF designer want to optimize the performance of TabControl. Suppose there are 5 TabItems, and each TabItem contains alot of children. If WPF program have to construct and render all the children, it will be very slow. But if TabControl only handle the children just in the current selected TabItem, much memory will be saved.

    If you look through the template of TabControl, you will find that the content of TabControl will only be the selected TabItem:
    Content="{TemplateBinding TabControl.SelectedContent}"

    <Grid
          ClipToBounds="True"
          SnapsToDevicePixels="True"
          KeyboardNavigation.TabNavigation="Local">
          <Grid.ColumnDefinitions>
             <ColumnDefinition
                Name="ColumnDefinition0" />
             <ColumnDefinition
                Width="0"
                Name="ColumnDefinition1" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
             <RowDefinition
                Height="Auto"
                Name="RowDefinition0" />
             <RowDefinition
                Height="*"
                Name="RowDefinition1" />
          </Grid.RowDefinitions>
          <TabPanel
             Background="#00FFFFFF"
             IsItemsHost="True"
             Name="HeaderPanel"
             Margin="2,2,2,0"
             KeyboardNavigation.TabIndex="1"
             Panel.ZIndex="1"
             Grid.Column="0"
             Grid.Row="0" />
          <Grid
             Name="ContentPanel"
             KeyboardNavigation.TabIndex="2"
             KeyboardNavigation.TabNavigation="Local"
             KeyboardNavigation.DirectionalNavigation="Contained"
             Grid.Column="0"
             Grid.Row="1">
             <mwt:ClassicBorderDecorator
                Background="{TemplateBinding Panel.Background}"
                BorderStyle="Raised"
                BorderBrush="{TemplateBinding Border.BorderBrush}"
                BorderThickness="{TemplateBinding Border.BorderThickness}">
                <ContentPresenter
                   Content="{TemplateBinding TabControl.SelectedContent}"
                   ContentTemplate="{TemplateBinding TabControl.SelectedContentTemplate}"
                   ContentStringFormat="{TemplateBinding TabControl.SelectedContentStringFormat}"
                   ContentSource="SelectedContent"
                   Name="PART_SelectedContentHost"
                   Margin="2,2,2,2"
                   SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
             </mwt:ClassicBorderDecorator>
          </Grid>
       </Grid>
    Below is a test project that can demostrate this feature.
    Run the program and select TabItem 1, then click the find Button. The AdvancedTextBox will be found.

    But if you select TabItem 2, the AdvancedTextBox can not be found. Because the AdvancedTextBox is removed from the VisualTree when you select TabItem 2.

    Hope it helps.

    Test project

    XAML
    <Window x:Class="FindAdvancedTextBox.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:FindAdvancedTextBox"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <Button Name="btnFind" Click="btnFind_Click"    >Find AdvancedTextBox</Button>
            <TabControl Name="Container">
                <TabControl.Items>
                    <TabItem Header="1">
                        <StackPanel Height="100">
                            <local:AdvancedTextBox x:Name="txt1"> 
                            </local:AdvancedTextBox>
                        </StackPanel>
                    </TabItem>
                    <TabItem Header="2">
                        <StackPanel Height="100">
                        </StackPanel>
                    </TabItem>
                </TabControl.Items>
            </TabControl>
        </StackPanel>
    </Window>
    

    C#
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    namespace FindAdvancedTextBox
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void btnFind_Click(object sender, RoutedEventArgs e)
            {
                //Find
                AdvancedTextBox findresult = GetDescendantByType(Container, typeof(AdvancedTextBox)) as AdvancedTextBox;
                if (findresult == null)
                {
                    MessageBox.Show("Not Find");
                }
                else
                {
                    MessageBox.Show("Find");
                }
            }
            public static Visual GetDescendantByType(Visual element, Type type)
            {
                if (element == null) return null;
                if (element.GetType() == type) return element;
                Visual foundElement = null;
                if (element is FrameworkElement)
                    (element as FrameworkElement).ApplyTemplate();
                for (int i = 0;
                    i < VisualTreeHelper.GetChildrenCount(element); i++)
                {
                    Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                    foundElement = GetDescendantByType(visual, type);
                    if (foundElement != null)
                        break;
                }
                return foundElement;
            }
        }
        public class AdvancedTextBox : TextBox
        { 
        }
    }
    

    • Proposed as answer by Tao Liang Wednesday, May 13, 2009 8:13 AM
    • Marked as answer by Tao Liang Thursday, May 14, 2009 2:01 AM
    Wednesday, May 13, 2009 8:13 AM
  • Thanks for that.

    I understand the problem but was hoping there would be a relatively simple solution that doesn't involve me having to keep references to the AdvancedTextBoxes somewhere. It doesn't seem like it unfortunately so will have to look into a less tidy solution to get what i'm after.

    Thanks for your help again :)
    Wednesday, May 13, 2009 8:27 AM