Search code examples
wpfbindingdatatemplatetabcontrol

Accessing methods of a control that is in a Content Template


Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:

public class MyClass
{
    public String Name { get; set; }
    public String Description { get; set; }
}

I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:

MyControl.xaml:

<UserControl
    x:Class="WpfApplication1.MyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    mc:Ignorable="d" 
    d:DesignHeight="300"
    d:DesignWidth="300"
    xmlns:this="clr-namespace:WpfApplication1">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="48" />
        </Grid.ColumnDefinitions>

        <TextBlock
            x:Name="txtDescription"
            Grid.Column="0"
            Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />

        <Button
            x:Name="btnBold"
            Grid.Column="1"
            Content="Bold"
            Click="btnBold_Click" />
    </Grid>
</UserControl>

MyControl.xaml.cs:

public partial class MyControl : UserControl
{
    public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));

    public String Description
    {
        get { return GetValue(MyControl.DescriptionProperty) as String; }
        set { SetValue(MyControl.DescriptionProperty, value); }
    }

    public MyControl()
    {
        InitializeComponent();
    }

    private void btnBold_Click(object sender, RoutedEventArgs e)
    {
        ToggleBold();
    }

    public void ToggleBold()
    {
        if (txtDescription.FontWeight == FontWeights.Bold)
        {
            btnBold.Content = "Bold";
            txtDescription.FontWeight = FontWeights.Normal;
        }
        else
        {
            btnBold.Content = "Unbold";
            txtDescription.FontWeight = FontWeights.Bold;
        }
    }
}

In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:

MainWindow.xaml:

<Window
    x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Height="350"
    Width="525"
    xmlns:this="clr-namespace:WpfApplication1">
    <Grid>
        <TabControl x:Name="tabItems">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </TabControl.ItemTemplate>

            <TabControl.ContentTemplate>
                <DataTemplate>
                    <this:MyControl
                        Description="{Binding Description}" />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<MyClass> myClasses = new List<MyClass>();
        myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
        myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });

        tabItems.ItemsSource = myClasses;
    }
}

When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.

My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:

MainWindow.xaml:

<Window
    x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Height="350"
    Width="525"
    xmlns:this="clr-namespace:WpfApplication1">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />            
        </Grid.RowDefinitions>

        <TabControl x:Name="tabItems" Grid.Row="0">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </TabControl.ItemTemplate>

            <TabControl.ContentTemplate>
                <DataTemplate>
                    <this:MyControl
                        x:Name="myControl"
                        Description="{Binding Description}"/>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

        <Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
    </Grid>
</Window>

MainWindow.xaml.cs:

...

private void Button_Click(object sender, RoutedEventArgs e)
{
    MyClass myClass = tabItems.SelectedItem as MyClass;
    MyControl myControl;

    ///get the instance of myControl that is contained
    ///in the content template of tabItems for the
    ///myClass item

    myControl.ToggleBold();
}

...

I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.

Any help would be hugely appreciated.


Solution

  • You can navigate the VisualTree to find the control you're looking for.

    For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:

    var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
    if (myControl != null)
        myControl.ToggleBold();