我在 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.Interactivity
和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 执行绑定(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/