4

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.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Boris
  • 9,986
  • 34
  • 110
  • 147

5 Answers5

7

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;
}
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • 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. – Peter Huber May 05 '20 at 10:32
3

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.

Elad Katz
  • 7,483
  • 5
  • 35
  • 66
  • 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` :) – Boris Feb 26 '11 at 12:41
  • 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. – Emond Feb 27 '11 at 13:43
  • I very much agree.. I'm even taking it a step forward to say that Imo datatemplates altogether don't belong in resource dictionaries – Elad Katz Feb 27 '11 at 14:55
0

You can use EventSetter in the style you are setting the template with:

<Style TargetType="{x:Type ListBoxItem}">
      <EventSetter Event="MouseWheel" Handler="GroupListBox_MouseWheel" />
      <Setter Property="Template" ... />
</Style>
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
ezolotko
  • 1,723
  • 1
  • 21
  • 21
0

Method 1: Use your own control inherited from Slider:

public class SpecialSlider : Slider
{
    public SpecialSlider()
    {
        ValueChanged += OnValueChanged;
    }

    private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        // ...
    }
}  

Method 2: use behaviors from System.Windows.Interactivity.dll assembly (available through the NuGet):

public class SpecialSliderBehavior : Behavior<Slider>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.ValueChanged += OnValueChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.ValueChanged -= OnValueChanged;
    }

    private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        // ...
    }
}

This is how to attach it:

...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<DataTemplate x:Key="MyListBoxItemTemplate" DataType="{x:Type entities:Track}">
    <Slider Name="MySlider">
        <i:Interaction.Behaviors>
            <SpecialSliderBehavior />
        </i:Interaction.Behaviors>
    </Slider>
</DataTemplate>
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
0

Have a look at this question and answer.

I think that using Commands is the best option.

EDIT A good tutorial

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115
  • 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. – Boris Feb 24 '11 at 23:53
  • I added a good tutorial that explains and shows standard commands and custom commands. – Emond Feb 25 '11 at 08:17
  • 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 Feb 26 '11 at 12:39