Poma
Poma

Reputation: 8484

How to declare event handlers inside ControlTemplate?

I have the following ControlTemplate:

<ControlTemplate>
    <Grid VerticalAlignment="Stretch" HorizontalAlignment="Left" Width="400">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="18" />
            <ColumnDefinition Width="20*" />
            <ColumnDefinition Width="20*" />
            <ColumnDefinition Width="20*" />
            <ColumnDefinition Width="45" />
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="1" Template="{StaticResource watermark}" HorizontalAlignment="Stretch" Margin="4,0,0,4" Tag="Number" />
        <TextBox Grid.Column="2" Template="{StaticResource watermark}" HorizontalAlignment="Stretch" Margin="4,0,0,4" Tag="Login" />
        <TextBox Grid.Column="3" Template="{StaticResource watermark}" HorizontalAlignment="Stretch" Margin="4,0,0,4" Tag="Password" />
        <Button Grid.Column="4" HorizontalAlignment="Stretch" Content="Add" Margin="4,0,0,4" Click="AddUser_Click"/>
    </Grid>
</ControlTemplate>

How should I write AddUser_Click to get access to textboxes Text properties?

upd: just to make it clear. I know how to connect Click event handler here. The question is how to read contents of textboxes in it since I cant give them name because they are in a template.

Upvotes: 10

Views: 14733

Answers (4)

Declan Taylor
Declan Taylor

Reputation: 405

I had no luck finding my checkbox, PART_CheckBox, with the

 this.Template.FindName("control name", this)

method but

 GetTemplateChild("control name")

worked.

'ResourceDictionary' stub

<Style x:Key="OGrid" TargetType="{x:Type local:OrientationGrid}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:OrientationGrid}" x:Name="PART_Control">
                    <CheckBox  x:Name="PART_CheckBox"/>
                            ...

Custom Control, OrientationGrid, stub:

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        CheckBox chkbx = GetTemplateChild("PART_CheckBox") as CheckBox;
        chkbx.Checked += Chkbx_Checked;
    }

    private void Chkbx_Checked(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Event Raised");
    }
    ...

answer based on: WPF get element from template in code

Upvotes: 1

Shimmy Weitzhandler
Shimmy Weitzhandler

Reputation: 104781

The event handler is searched in the class that the x:Class directive of current file points to, which allows you adding the event handler inline without bothering to override the class and override the OnApplyTemplate with adding handlers hassle, regardless of the place that you declare the ControlTemplate.

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">
  <Button Content="asdf">
    <Button.Style>
      <Style TargetType="Button">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="Button">
              <Button Content="{TemplateBinding Content}" Click="Button_Click"/>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </Button.Style>
  </Button>
</Window>

MainWindow.xaml.cs:

using System.Windows;
namespace WpfApplication1
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      MessageBox.Show("Button clicked!");
    }
  }
}

Upvotes: 6

Fredrik Hedblad
Fredrik Hedblad

Reputation: 84666

If you have access to the templatedparent (SelectedItem, FindVisualParent etc.) you can do this if you apply Names to the TextBoxes. Example if ControlTemplate is for ComboBoxItem.

private void AddUser_Click(object sender, RoutedEventArgs e)
{
    ComboBoxItem comboBoxItem = GetVisualParent<ComboBoxItem>(button);
    TextBox textBox = comboBoxItem.Template.FindName("numberTextBox", comboBoxItem) as TextBox;
    //...
}

Another way to get the TextBoxes within the ControlTemplate would be to use the Visual Tree. Something like this

private void AddUser_Click(object sender, RoutedEventArgs e)
{
    Button button = sender as Button;
    Grid parentGrid = GetVisualParent<Grid>(button);
    List<TextBox> textBoxes = GetVisualChildCollection<TextBox>(parentGrid);
    foreach (TextBox textBox in textBoxes)
    {
        if (textBox.Tag == "Number")
        {
            // Do something..
        }
        else if (textBox.Tag == "Login")
        {
            // Do something..
        }
        else if (textBox.Tag == "Password")
        {
            // Do something..
        }
    }
}

And an implementation of GetVisualParent and GetVisualChildCollection

public static T GetVisualParent<T>(object childObject) where T : Visual
{
    DependencyObject child = childObject as DependencyObject;
    // iteratively traverse the visual tree
    while ((child != null) && !(child is T))
    {
        child = VisualTreeHelper.GetParent(child);
    }
    return child as T;
}

public static List<T> GetVisualChildCollection<T>(object parent) where T : Visual
{
    List<T> visualCollection = new List<T>();
    GetVisualChildCollection(parent as DependencyObject, visualCollection);
    return visualCollection;
}
private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual
{
    int count = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < count; i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        if (child is T)
        {
            visualCollection.Add(child as T);
        }
        else if (child != null)
        {
            GetVisualChildCollection(child, visualCollection);
        }
    }
}

Upvotes: 2

treehouse
treehouse

Reputation: 2531

What you could do is give Button a name "PART_Button". Then override OnApplyTemplate method in control. In code, you can do

var btn = this.Template.FindName("PART_Button", this) as Button;
btn.Click += ...

Upvotes: 23

Related Questions