In WPF, how to add an EventHandler for a FrameworkElement designed in Template?

13,871

Solution 1

Using the OnApplyTemplate approach will work if you if you're working with the ControlTemplate for a Control. For example, if you've subclassed TextBox you could do this like

public class MyTextBox : TextBox
{
    public override void OnApplyTemplate()
    {
        MySlider MySlider = GetTemplateChild("MySlider") as MySlider;
        if (MySlider != null)
        {
            MySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(MySlider_ValueChanged);
        }
        base.OnApplyTemplate();
    }
    void MySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        //...
    }
}

I don't think this approach will work in your situation however. You could use the Loaded event for ListBoxItem and find the Slider in the visual tree in the event handler

<ListBox ...>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <EventSetter Event="Loaded" Handler="ListBoxItem_Loaded"/>
        </Style>
    </ListBox.ItemContainerStyle>
    <!--...-->
</ListBox>

Code behind

private void ListBoxItem_Loaded(object sender, RoutedEventArgs e)
{
    ListBoxItem listBoxItem = sender as ListBoxItem;
    Slider MySlider = GetVisualChild<Slider>(listBoxItem);
    MySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(MySlider_ValueChanged);
}
void MySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{

}

GetVisualChild

private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
    T child = default(T);

    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
        if (child != null)
        {
            break;
        }
    }
    return child;
}

Solution 2

Little know fact is that ResourceDictionaries can hold CodeBehind as well..

As a general rule of thumb I don't think that putting DataTemplates in ResourceDictionaries is a good idea to begin with (your question being an example for one of the reasons), this is how you can solve it:

XAML:

<ResourceDictionary 
    x:Class="WpfApplication24.Dictionary1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DataTemplate x:Key="MyDataTemplate">
        <StackPanel>
        <TextBlock Text="Hello" />
            <Slider ValueChanged="ValueChanged"/>
        </StackPanel>
    </DataTemplate>
</ResourceDictionary>

and code behind:

namespace WpfApplication24
{
    public partial class Dictionary1 : ResourceDictionary
    {

        public void ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            Debug.Write("Hello");
        }

    }
}

Anyhow, as Meleak said above me - OnApplyTemplate is only relevant for Control Templates and not Data Templates.

Share:
13,871
Boris
Author by

Boris

Updated on June 09, 2022

Comments

  • Boris
    Boris about 2 years

    I have defined the following DataTemplate for ListBox items in an external resource dictionary:

    <DataTemplate x:Key="MyListBoxItemTemplate" DataType="{x:Type entities:Track}">
        <StackPanel>       
            <TextBlock Text="Here's the slider:" />
            <Slider Name="MySlider" Height="23" Minimum="0" />
        </StackPanel>
    </DataTemplate>
    

    I need to provide an event handler method for Slider's ValueChanged event. I don't know where am I supposed to write that code as it is impractical to specify event handler for a control within a template.

    I've been googling for the solution and found that I should add the event handler in the override of the OnApplyTemplate() method. My guess is that it should look something like this or similar:

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        // Is the following initialization even going to work!?!?
        Slider MySlider = this.FindName("MySlider") as Slider;
        SeekSlider.ValueChanged += 
            new RoutedPropertyChangedEventHandler<double>(SeekSlider_ValueChanged);
    }
    

    But where should I write this method? Does OnApplyTemplate overriding only applies to ControlTemplates or is my scenario included as well? Should I provide ControlTemplate instead of DataTemplate? Is the body of the method I have provided correct?

    Please help. Thanks.

  • Boris
    Boris over 13 years
    Erno, thank you for your answer. I will be waiting for a solution which I would be able to understand more easily. I have never used Commands before and I don't understand the explanation provided.
  • Emond
    Emond over 13 years
    I added a good tutorial that explains and shows standard commands and custom commands.
  • Boris
    Boris over 13 years
    Thanks Erno, I skimmed it (as I am far too busy at the moment) and it appears logical. Thank you for helping me understand the concept of commands in WPF.
  • Boris
    Boris over 13 years
    Thank you Elad. I see the point you're trying to make. It all looks much clearer now and I think I will start developing a ControlTemplate :)
  • Emond
    Emond over 13 years
    I thought a bit about this and I do not like it. A ValueChanged method on a ResourceDictionary is out of place. It is nice to know that there is a code-behind for resource dictionaries but this smells.
  • Elad Katz
    Elad Katz over 13 years
    I very much agree.. I'm even taking it a step forward to say that Imo datatemplates altogether don't belong in resource dictionaries
  • Peter Huber
    Peter Huber about 4 years
    The Loaded event is not always useful, for example when Visibility is Collapsed and gets set to Visible only later. In that case Loaded get raised without the template being applied. Once the control gets visible and the template applied, loaded does not fire again.