I have the following Binding in an EventTrigger:
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown">
<SoundPlayerAction Source="{Binding Path=SoundFile, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource soundFileConverter}}" />
</EventTrigger>
...
The process is the following: The custom control (that’s its template) has a property named SoundFile, an enum type. In the converter this enum value should be converted to an Uri to pass it to the SoundPlayerAction.
Here’s the problem: The converter isn’t called anyway. The output window presents the following error:
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’)
What’s wrong with the binding expression?
EDIT:
For a better overview, here’s the entire template of the control:
<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>
EDIT 2:
I tried it in another way: Setting the name attribute of the SoundPlayerAction to PART_SoundPlayerAction and retrieving it from the code-behind with GetTemplateChild. But GetTemplateChild returns always null. That’s really annoying. Nothing seems to work…
EDIT 3:
Now with the answer of Blachshma I got it that the converter is called during the initalization of the control. But not when the property is changing. Furthermore, the value which is returned by the converter, isn’t applied as Source to the SoundPlayerAction.
I implemented the 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));
}
And I changed the Path=Data.SoundFile to Path=Data. Is there any mistake?
EDIT 4:
The solution with the MakeSoundCommand is working perfectly. Thanks a lot to Blachshma.
Well you’re right, trying to use binding in that specific place (An EventTrigger inside a style) is quite a pain.
I’ve found that the best way to solve this issue is to use both Freezables (to inherit the DataContext) together with static BindingProxies…
To solve it in your case, start by making a freezable class, we’ll call it BindingProxy:
So our BindingProxy class implements
Freezableand exposes a property namedData. This will be the Property which will hold the DataContext.Now, in our resources, we’ll create a StaticResource that uses this class… We’ll name it “proxy“:
Notice, that we bind the Window’s DataContext into the
Dataproperty. This will allow use to access it from theSoundPlayerAction.Finally, let’s update the SoundPlayerAction to work with our proxy:
Instead of the regular binding you used, we’re binding to a StaticResource, the instance of our BindingProxy class. Since the
Dataproperty is binded to the DataContext of the window, we can get the SoundFile property usingData.SoundFile.And as an added bonus, since all this is done via
Source={Bindingyou can stil call your soundFileConverter to convert the string to a URI.Update:
OP does not want to put the
BindingProxyclass inside some<Window.Resources>tag every time he uses this control (which is legitimate), so in this version, we’re going to put all the logic inside the ResourceDictionary and modify a bit of XAML so that it continues to work..So in our resource dictionary, we’re going to use
System.Windows.InteractivityandMicrosoft.Expression.Interactions(both freely available through the Blend SDK)Since in the previous version we could count on the binding being preformed through the Window.Resources, this time we’re going to have to do it ourselves… We’ll modify the original template to add an
<i:Interaction.Triggers>which will replace theEventTriggerbut allows sounds to play through a dedicated command:This is placed right before the TextBlock, what it does is handle the
PreviewMouseDownevent and calls the a command namedsoundCommandof typeMakeSoundCommand.This is the implementation of MakeSoundCommand:
The rest of the code remains the same.
Note: The
EventToCommandused is the one from the MVVM Light Toolkit and can be downloaded hereFinal Result:
Generic.xaml: