EventTrigger中的TemplateBinding

时间:2012-12-19 16:10:32

标签: c# wpf xaml binding custom-controls

我在EventTrigger中有以下Binding:

<ControlTemplate.Triggers>
    <EventTrigger RoutedEvent="PreviewMouseDown">
        <SoundPlayerAction Source="{Binding Path=SoundFile, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource soundFileConverter}}" />
    </EventTrigger>
    ...

该过程如下:自定义控件(即其模板)具有名为 SoundFile 的属性,即枚举类型。在转换器中,此枚举值应转换为Uri以将其传递给SoundPlayerAction。

问题在于:无论如何都不会调用转换器。输出窗口显示以下错误:

  

无法为目标元素找到管理FrameworkElement或FrameworkContentElement。       BindingExpression:路径=音效档;的DataItem = NULL;目标元素是'SoundPlayerAction'       的HashCode = 46763000); target属性为'Source'(类型'Uri')

绑定表达式有什么问题?

修改

为了更好地概述,这是控件的整个模板:

<Style TargetType="{x:Type controls:Button}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:Button}">
                <Border Name="Border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="Transparent"
                        BorderThickness="0">
                    <Border.CornerRadius>
                        <MultiBinding Converter="{StaticResource areaCornerRadiusConverter}">
                            <MultiBinding.Bindings>
                                <Binding Path="RoundType" RelativeSource="{RelativeSource TemplatedParent}" />
                                <Binding Path="ActualHeight" RelativeSource="{RelativeSource TemplatedParent}" />
                            </MultiBinding.Bindings>
                        </MultiBinding>
                    </Border.CornerRadius>
                    <TextBlock Margin="{Binding Path=RoundType,
                                                RelativeSource={RelativeSource TemplatedParent},
                                                Converter={StaticResource buttonMarginConverter}}"
                               FontSize="{TemplateBinding FontSize}"
                               Style="{StaticResource innerTextBlock}"
                               Text="{TemplateBinding Text}" />
                </Border>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="PreviewMouseDown">
                        <SoundPlayerAction Source="{Binding Path=SoundFile, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource soundFileConverter}}" />
                    </EventTrigger>                        
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

编辑2:

我以另一种方式尝试:将SoundPlayerAction的name属性设置为 PART_SoundPlayerAction ,并使用 GetTemplateChild 从代码隐藏中检索它。但是 GetTemplateChild 总是返回null。这真的很烦人。似乎没什么用......

编辑3:

现在有了Blachshma的答案,我得到了转换器在控件的初始化期间被调用。但不是在财产改变的时候。此外,转换器返回的值不会作为Source应用于SoundPlayerAction。

我实现了BindingProxy:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public SoundFile Data
    {
        get { return (SoundFile)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(SoundFile), typeof(BindingProxy), new UIPropertyMetadata(SoundFile.None));
}

我将Path=Data.SoundFile更改为Path=Data。有什么错吗?

编辑4:

使用MakeSoundCommand的解决方案非常有效。非常感谢Blachshma。

1 个答案:

答案 0 :(得分:2)

你是对的,尝试在特定的地方使用绑定(一个样式中的EventTrigger)是一个非常痛苦的事。

我发现解决此问题的最佳方法是同时使用Freezables(继承DataContext)和静态BindingProxies ...

要在你的情况下解决它,首先制作一个freezable类,我们称之为BindingProxy:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

因此,我们的BindingProxy类实现了Freezable并公开了一个名为Data的属性。这将是将保存DataContext的属性。

现在,在我们的资源中,我们将创建一个使用此类的StaticResource ...我们将其命名为“ proxy ”:

<Window.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />

注意,我们将Window的DataContext绑定到Data属性中。这将允许用户从SoundPlayerAction访问它。

最后,让我们更新SoundPlayerAction以使用我们的代理:

<EventTrigger RoutedEvent="PreviewMouseDown">
    <SoundPlayerAction Source="{Binding Source={StaticResource proxy}, Path=Data.SoundFile,Converter={StaticResource soundFileConverter}}" />
</EventTrigger>   

我们将绑定到一个StaticResource(我们的BindingProxy类的实例),而不是您使用的常规绑定。由于Data属性绑定到窗口的DataContext,我们可以使用Data.SoundFile获取SoundFile属性。 作为一个额外的好处,因为所有这些都是通过Source={Binding完成的,你可以调用你的soundFileConverter将字符串转换为URI。

<强>更新 每次使用此控件(这是合法的)时,OP都不希望将BindingProxy类置于某个<Window.Resources>标记内,因此在此版本中,我们将把所有逻辑放在ResourceDictionary中并修改一点XAML,以便它继续工作..

因此,在我们的资源词典中,我们将使用System.Windows.InteractivityMicrosoft.Expression.Interactions(均可通过Blend SDK免费提供)

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

因为在之前的版本中,我们可以指望通过Window.Resources执行绑定,这次我们将不得不自己做...我们将修改原始模板以添加{{1它将替换<i:Interaction.Triggers>但允许声音通过专用命令播放:

EventTrigger

它位于TextBlock之前,它所处理的是处理<local:MakeSoundCommand x:Key="soundCommand"/> <Style TargetType="{x:Type controls:Button}" > .... .... <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseDown"> <local:EventToCommand Command="{StaticResource soundCommand}" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SoundFile, Converter={StaticResource soundFileConverter}}" /> </i:EventTrigger> </i:Interaction.Triggers> <TextBlock Margin="{Binding Path=RoundType,.... 事件并调用类型PreviewMouseDown的名为soundCommand的命令。

这是 MakeSoundCommand

的实现
MakeSoundCommand

其余代码保持不变。 注意:public class MakeSoundCommand : ICommand { public bool CanExecute(object parameter) { return true; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { Uri uri = parameter as Uri; if (uri != null) { StreamResourceInfo sri = Application.GetResourceStream(uri); SoundPlayer simpleSound = new SoundPlayer(sri.Stream); simpleSound.Play(); } } 使用的EventToCommand来自MVVM Light Toolkit,可以下载here

最终结果:

Generic.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
                xmlns:local="<YOUR NAMESPACE>">

<local:MakeSoundCommand x:Key="soundCommand" />
<local:SoundFileToUriConverter:Key="soundFileConverter" />
<Style TargetType="{x:Type controls:Button}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType=" {x:Type controls:Button}">
                <Border Name="Border"
                    Background="{TemplateBinding Background}"
                    BorderBrush="Transparent"
                    BorderThickness="0">
                    <Border.CornerRadius>
                      ....
                    </Border.CornerRadius>
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="PreviewMouseDown">
                          <local:EventToCommand Command="{StaticResource soundCommand}" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SoundFile, Converter={StaticResource soundFileConverter}}" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <TextBlock Margin="{Binding Path=RoundType,
                                            RelativeSource={RelativeSource TemplatedParent}}" .... />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>