In WPF, how to add an EventHandler for a Framework

2019-02-14 07:29发布

问题:

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.

回答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;
}


回答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.



回答3:

Have a look at this question and answer.

I think that using Commands is the best option.

EDIT A good tutorial



回答4:

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>


回答5:

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>