1

I have TabControl:

<TabControl Name="tabControl"                   
            VerticalAlignment="Top"
            HorizontalAlignment="Stretch">
    <TabControl.Items>
        <TabItem  x:Name="tab1" Header="ABC">                       
            <TabItem.ContentTemplate>                            
                <DataTemplate>
                    <ScrollViewer Name="ScrollViewer">
                        <StackPanel Orientation="Vertical">
                            <TextBox Name="txt1" HorizontalAlignment="Center" Margin="0,26,0,0" />
                            <ListBox Name="listBox" DataContext="{Binding Items, Mode=TwoWay}"  />
                        </StackPanel>
                    </ScrollViewer>
                </DataTemplate>
            </TabItem.ContentTemplate>
        </TabItem>
    </TabControl.Items>
</TabControl>

How I can get listbox programmatically in C# code?

I have tried below code and myContentPresenter.ContentTemplate shows null.

TabItem myListBoxItem = (TabItem)(tabControl.ItemContainerGenerator.ContainerFromItem(tabControl.SelectedItem));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
ListBox listBox = (ListBox)myDataTemplate.FindName("listBox", myContentPresenter);
James Z
  • 12,209
  • 10
  • 24
  • 44
vishal
  • 596
  • 2
  • 12
  • 31
  • You haven't provided a good [mcve] showing the context of the code in which you want to get that object's reference. Not that there aren't already plenty of questions on Stack Overflow addressing this basic type of scenario; if you'd spend a little time looking, I'm sure you'd find the answer you want. That said, the answer you _need_ is that you shouldn't be trying to get the `ListBox` object in code-behind anyway. Whatever it is you think you want to do with that object, you should be handling via a view model instead, probably the one you use for the `TabItem`. Again, no context, no answer. – Peter Duniho Jun 12 '17 at 06:54
  • If you can't find the `ListBox` then put a handler on `Loaded` event for your `ListBox`, then put a break point in VS in that handler, when you hit the break point hover over the sender parameter of your solution and then you will see the visual tree, then you can find the `ListBox`. – XAMlMAX Jun 12 '17 at 07:21
  • From where do you try to get the control? (Constructor, Loaded event, some other event, ...?) and is the Tab that contains your control currently active or is there a different active Tab while you try to find the control? – grek40 Jun 12 '17 at 09:24
  • @grek40: I am trying to get control in SelectionChanged event of TabControl. and yes it is currently active tab – vishal Jun 12 '17 at 10:07

3 Answers3

3

Building on @mm8 approach, the following solution will find the ListBox by name instead of by type:

XAML

<TabControl x:Name="tabControl1" SelectionChanged="tabControl1_SelectionChanged">
    <TabItem  x:Name="tab1" Header="ABC">
        <TabItem.ContentTemplate>
            ...

Code

private void tabControl1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Dispatcher.BeginInvoke(new Action(() => TabItem_UpdateHandler()));
}


void TabItem_UpdateHandler()
{
    ContentPresenter myContentPresenter = tabControl1.Template.FindName("PART_SelectedContentHost", tabControl1) as ContentPresenter;
    if (myContentPresenter.ContentTemplate == tab1.ContentTemplate)
    {
        myContentPresenter.ApplyTemplate();
        var lb1 = myContentPresenter.ContentTemplate.FindName("listBox", myContentPresenter) as ListBox;
    }
}
grek40
  • 13,113
  • 1
  • 24
  • 50
  • What's wrong with my approach of using the dispatcher? It works wonders for me. – mm8 Jun 12 '17 at 13:19
  • @mm8 probably nothing wrong with it. Just an incomplete answer since you didn't bother to utilize the controls name. But the more I think about it the more I think you are right with using the Dispatcher for my mentioned problem. Probably my fail here, but you could still use the FindName approach inside your dispatcher ;) – grek40 Jun 12 '17 at 13:22
  • There is no need to "bother" about the name of the ListBox since there only is one ListBox present in this case. – mm8 Jun 12 '17 at 13:24
  • 1
    @mm8 I guess it's time to give you a +1 since I rely on your Dispatcher hint now – grek40 Jun 12 '17 at 13:31
  • @mm8 Thanks for your help. – vishal Jun 13 '17 at 06:43
  • @grek Thanks, I would go for your's approach. Works like charm. – vishal Jun 13 '17 at 06:43
2

You can use the following function to get the Visual Child of a WPF control:

private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
    for (int childCount = 0; childCount < VisualTreeHelper.GetChildrenCount(parent); childCount ++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, childCount);
        if (child != null && child is T)
            return (T)child;
        else
        {
            T childOfChild = FindVisualChild<T>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

Usage:

ListBox lb = MainWindow.FindVisualChild<ListBox>(tabControl);
Abhishek
  • 2,925
  • 4
  • 34
  • 59
2

The ListBox is not a visual child of the TabItem but it is a visual child of the TabControl itself provided that the "ABC" tab is actually selected.

You need to wait for it to get added to the visual tree before you can access it though. This should work:

private void tabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (tabControl.SelectedItem == tab1)
    {
        tabControl.Dispatcher.BeginInvoke(new Action(() =>
        {
            ListBox lb = FindVisualChild<ListBox>(tabControl);
            MessageBox.Show(lb.Items.Count.ToString());
        }));
    }
}

Only the elements of the currently visible TabItem are added to the visual tree. When you switch tabs, the invisible elements are removed.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • The part about `TabControl.Content` instead of `TabItem` is valid, but shouldn't he still utilize `ContentTemplate` and `FindName` as in the last two code lines in question (just with different ContentPresenter)? – grek40 Jun 12 '17 at 10:36
  • No. A DataTemplate is just a template. He needs to get a reference to the actual instance of the ListBox regardless of whether this was created using a template. – mm8 Jun 12 '17 at 10:37
  • However, without considering the ElementName, this will break as soon as multiple `ListBox`es are involved. – grek40 Jun 12 '17 at 10:39
  • That's just a matter of using a helper method that returns *all* elements of a given type and then filter them by the name for example: https://stackoverflow.com/questions/974598/find-all-controls-in-wpf-window-by-type. However, this is not really what the OP is asking so what is your point? – mm8 Jun 12 '17 at 10:41