c# - EventTrigger 中的模板绑定(bind)

标签 c# wpf xaml binding custom-controls

我在 EventTrigger 中有以下绑定(bind):

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

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

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

Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SoundFile; DataItem=null; target element is 'SoundPlayerAction' HashCode=46763000); target property is 'Source' (type 'Uri')

绑定(bind)表达式有什么问题?

编辑:

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

<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。

最佳答案

嗯,你是对的,尝试在特定位置(样式内的 EventTrigger)使用绑定(bind)是相当痛苦的。

我发现解决此问题的最佳方法是同时使用 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 绑定(bind)到 Data属性(property)。这将允许用户从 SoundPlayerAction 访问它.

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

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

我们绑定(bind)到 StaticResource(BindingProxy 类的实例),而不是您使用的常规绑定(bind)。自 Data属性绑定(bind)到窗口的 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 执行绑定(bind),这次我们必须自己完成...我们将修改原始模板以添加 <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事件并调用名为 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>

关于c# - EventTrigger 中的模板绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13956447/

相关文章:

c# - 如何通过在asp.net中选择下拉菜单选项来刷新结果顺序

c# - ItemsControl 中 WPF 中的可拖动对象?

wpf - VisualStateManager 没有像宣传的那样工作

c# - 如何复制新 DropBox UWP 应用中的窗口透明效果

c# - 绑定(bind)Xaml位图图像

c# - 如何使用 LINQ Where 获取泛型类型?

c# - 如何使用 .NET 3.5 从 XML 文件中读取处理指令

c# - LINQ - groupBy 与几个组中的项目

c# - 与 ContentTemplate 中的 Bindings 和 DataContext 混淆

c# - 在 UWP XAML 中动态绑定(bind) NavigationViewItem && NavigationViewItemHeader