Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 411
Boris Avatar asked Jan 21 '23 09:01

Boris


2 Answers

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;
}
like image 81
Fredrik Hedblad Avatar answered Jan 23 '23 00:01

Fredrik Hedblad


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.

like image 24
Elad Katz Avatar answered Jan 22 '23 23:01

Elad Katz