2

我在 EventTrigger 中有以下绑定:

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

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

问题是:无论如何都没有调用转换器。输出窗口显示以下错误:

找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement。绑定表达式:路径=声音文件;数据项=空;目标元素是 'SoundPlayerAction' HashCode=46763000); 目标属性是“源”(类型“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.SoundFilePath=Data. 有什么错误吗?

编辑4:

MakeSoundCommand 的解决方案运行良好。非常感谢布拉赫什玛。

4

1 回答 1

2

好吧,您是对的,尝试在该特定位置使用绑定(样式中的 EventTrigger)非常痛苦。

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

要在您的情况下解决它,首先创建一个可冻结的类,我们将其称为 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... 我们将其命名为“代理”:

<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. 作为额外的奖励,因为所有这些都是通过Source={Binding您完成的,您仍然可以调用您的 soundFileConverter 将字符串转换为 URI。

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

所以在我们的资源字典中,我们将使用System.Windows.Interactivityand Microsoft.Expression.Interactions(两者都可以通过Blend SDK免费获得)

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

由于在以前的版本中,我们可以依靠通过 Window.Resources 执行的绑定,这次我们将不得不自己做......我们将修改原始模板以添加一个<i:Interaction.Triggers>将替换EventTrigger但允许通过专用命令播放的声音:

<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,....

它位于 TextBlock 之前,它的作用是处理PreviewMouseDown事件并调用名为soundCommandtype的命令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中的一个,可以在这里下载

最后结果:

通用的.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>
于 2012-12-22T00:45:54.310 回答