6

I have the following C# code to find children of a DepedendencyObject:

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
    {
        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);

            T childType = child as T;
            if (childType == null)
            {
                foreach (var other in FindVisualChildren<T>(child))
                    yield return other;
            }
            else
            {
                yield return (T)child;
            }
        }
    }

When I loop through the TabItems in the XAML posted at the bottom, passing each TabItem to the above method, asking it to find all Expanders, it returns nothing. Also, I am making this request in an event handler attached to the Loaded event of each tab item.



                                <TextBlock Text="Number of Parts" Grid.Column="0"/>                                   
                                <ComboBox Grid.Column="2"                                              
                                          Margin="0,0,0,2"                                                                                                                                        
                                          />                                                              
                            </Grid>
                        </Expander>
                        <Expander Header="Date/Time Format" 
                  Margin="5,0,5,0"
                   Padding="3,3,0,0"
                  IsExpanded="True" >
                            <Grid Margin="20,4,0,4">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="25"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>                                                                  
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="Date/Time Format" Grid.Row="0"/>
                                <ComboBox Name="cmbDateTimeFormats"                                             
                                          Grid.Row="0" Grid.Column="2"/>
                            </Grid>
                        </Expander>                           
                    </StackPanel>                       
                </DockPanel>
            </Border>                
        </TabItem>
        <TabItem Header="Profile">
            <Border  >
                <DockPanel LastChildFill="False">
                    <StackPanel DockPanel.Dock="Top">
                        <GroupBox Header="Local" 
                              Margin="5,8" Padding="3,3,0,0"
                              >                                 
                            <Grid Margin="20,4,0,4">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="25"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition  />
                                    <ColumnDefinition  />
                                    <ColumnDefinition  />

                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>

                                <Button Content="Location..." Grid.Row="0" Name="btnProfLoc"    />
                                <TextBlock Text="{Binding ProfileLocation}" Grid.Row="0" Grid.Column="2"/>

                                <Button Name="btnSaveProfile" Height="25"
                                        Margin="2,5,0,0" Grid.Row="1"                                           
                                        Padding="2,1" >                                    
                                    <StackPanel Orientation="Horizontal">                                       
                                        <TextBlock Text="Save" Margin="5,0"/>
                                    </StackPanel>
                                </Button>

                                <Button Name="btnLoadProfile" Height="25"
                                        Margin="2,5,0,0" Grid.Row="2"                                           
                                        Padding="2,1" >
                                    <StackPanel Orientation="Horizontal">

                                        <TextBlock Text="Load" Margin="5,0"/>
                                    </StackPanel>
                                </Button>

                                <Button Name="btnResetProfile" Height="25"
                                        Margin="2,5,0,0" Grid.Row="3"                                           
                                        Padding="2,1" >
                                    <StackPanel Orientation="Horizontal">                                         
                                        <TextBlock Text="Reset" Margin="5,0"/>
                                    </StackPanel>
                                </Button>                                    
                            </Grid>
                        </GroupBox>                       
                    </StackPanel>
                    <StackPanel 
                    DockPanel.Dock="Bottom" Orientation="Horizontal">

                    </StackPanel>
                </DockPanel>
            </Border>                
        </TabItem>
    </TabControl>

Any guesses what is wrong with my approach? I have not tried in this particular custom control but this method has been used to find children of a given type in another custom control. Main difference is that the items I am looking for are children of TabItems.

H.B.
  • 166,899
  • 29
  • 327
  • 400
Klaus Nji
  • 18,107
  • 29
  • 105
  • 185
  • The fact that you walk the visual tree is what is wrong with this approach. What is your goal and what makes you think that this is the way it should be achieved? – H.B. Aug 24 '12 at 20:43
  • Your FindVsiaulChildren might be wrong...try this implementation...http://stackoverflow.com/questions/974598/find-all-controls-in-wpf-window-by-type ... also notice the commetn Visual children may not be loaded/visible....so use LogicalTreeHelpers. – Colin Smith Aug 24 '12 at 21:04
  • @H.B. My I need to find all Expanders controls in the TabItem after is has been loaded. I am also currently using this same method in other parts of the application to find objects of a given type, Expanders in particular, and it does the trick. Just not for TabItems. What is wrong with my approach? – Klaus Nji Aug 27 '12 at 14:13
  • @colinsmith, my FindVsiaulChildren does same thing as the reference you posted. I will look into LogicalTreeHelpers. Thanks. – Klaus Nji Aug 27 '12 at 14:14
  • @KNji: Finding the expanders is not your goal, it's already a step on the wrong way, your actual goal has to be that you want to do something with those expanders. As to why this is wrong see the first point of [this answer](http://stackoverflow.com/a/12003996/546730). – H.B. Aug 27 '12 at 15:12
  • @H.B, thanks for the informative link, which I think is the answer for more of an architectural question. Will definitely take it into consideration if I cannot get an answer. – Klaus Nji Aug 27 '12 at 16:28

1 Answers1

3

The controls within a tab don't seem to be the children of a TabItem in the visual tree. They are the children of a TabControl.

You can see what I mean if you add the following code to your app.. and include a button on the tab with a click handler that reports the button's Path.

public string Id(object control)
{
    if (control is UIElement)
    {
        string id = ((UIElement)control).GetValue(AutomationProperties.AutomationIdProperty).ToString();
        id += "(" + control.GetType().Name + ")";
        return id;
    }
    return "not a ui element";
}

private static T FindParent<T>(DependencyObject child)
    where T : DependencyObject
{
    if (child == null) return null; 
    var parent = VisualTreeHelper.GetParent(child);
    return parent as T ?? FindParent<T>(parent);
}

public string Path(object control)
{
    if ( control == null ) return "";
    var path = Id(control);
    var parent = FindParent<FrameworkElement>(control as UIElement);
    if (parent != null ) path = Path(parent) +"/"+ path;
    return path;
}

For my application I get the following: "MainForm(MainPage)/(Grid)/(StackPanel)/TabControl(TabControl)/(Grid)/(Grid)/(Border)/(ContentPresenter)/(StackPanel)/Button(Button)"

Notice the TabControl, but no TabItem.

If I hook up to events from the TabItem itself I get the following path: "MainForm(MainPage)/(Grid)/(StackPanel)/TabControl(TabControl)/(Grid)/(Grid)/(TabPanel)/MyTabItem(TabItem)"

This shows the items don't exist within the TabItem in the visual tree, but as children of the TabControl. (Which sucks.) Note: they are virtualized and realized as you change tabs.

Nigel Thorne
  • 21,158
  • 3
  • 35
  • 51