【问题标题】:TemplateBinding in EventTriggerEventTrigger 中的模板绑定
【发布时间】:2012-12-19 16:10:32
【问题描述】:

我在 EventTrigger 中有以下绑定:

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

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

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

找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement。 绑定表达式:路径=声音文件;数据项=空;目标元素是“SoundPlayerAction” 哈希码=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.SoundFile 更改为Path=Data。有错吗?

编辑 4:

使用 MakeSoundCommand 的解决方案运行良好。非常感谢 Blachshma。

【问题讨论】:

标签: c# wpf xaml binding custom-controls


【解决方案1】:

嗯,你是对的,尝试在那个特定的地方使用绑定(样式中的一个 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... 我们将其命名为“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 类放入某个 &lt;Window.Resources&gt; 标记中(这是合法的),因此在此版本中,我们将把所有逻辑放入 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 执行绑定,这一次我们将不得不自己做......我们将修改原始模板以添加一个&lt;i:Interaction.Triggers&gt;这将取代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();
        }
    }

其余代码保持不变。 注意:使用的EventToCommandMVVM 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>

【讨论】:

  • 它总是必须更复杂不是吗:) 我更新了答案以允许所有这些代码都在 ResourceDictionary 中
  • 再次感谢。现在我在使用 Blend SDK 库时遇到了麻烦。我安装了 Blend SDK,但找不到 dll System.Windows.Interactivity 和 System.Window.Interactions,因此无法将它们添加为参考。
  • c:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries 并注意我输错了第二个 dll,它的 Microsoft.Expression.Interactions
  • 好的,现在库本身正在构建,但是我包含我的库的项目在开始时崩溃,出现以下异常:{“无法加载文件或程序集'System.Windows.Interactivity , PublicKeyToken=31bf3856ad364e35' 或其依赖项之一。Das System kann die angegebene Datei nicht finden.":"System.Windows.Interactivity, PublicKeyToken=31bf3856ad364e35"}一些文本被放置在灰色框中?
  • @oneoftwo - 尝试将 SDK 库中的所有 dll 复制到您运行项目的目录中。这有帮助吗?
猜你喜欢
  • 1970-01-01
  • 2011-06-06
  • 2014-10-20
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 2016-03-19
  • 1970-01-01
  • 2020-06-15
相关资源
最近更新 更多