我在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。
答案 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.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执行绑定,这次我们将不得不自己做...我们将修改原始模板以添加{{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>