I have an InvokeCommandAction
that I have that is attached to the GotFocus
event of a TextBox
like so:
<TextBox Grid.Row="0"
Grid.Column="1"
Width="40"
HorizontalAlignment="Right">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="Enter data [message to be displayed]" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
It works just fine this way, but I have dozens of TextBox
es with this same setup. Rather than repeating the code (as I am currently doing for every one), I am hoping to just attach that trigger to all controls of type {x:Type TextBox}
.
Normally, I would set properties in the Resources
section, like this:
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="Padding" Value="5,0,0,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>
Unfortunately, this will not work for Triggers
:
The attached property "Triggers" can only be applied to types that are derived from "DependencyObject".
Ideally, I would like to do something like this:
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding Tag}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Style>
</UserControl.Resources>
Where I then just need to set the Tag
property of each TextBox
to specify the message to be displayed. Am I on the right track? Do I need to change it to use a ControlTemplate
or something like that?
EDIT
I have seen a similar question here: Interaction Triggers in Style in ResourceDictionary WPF
After reading the answers for that question, I tried the following:
<UserControl.Resources>
<TextBox x:Key="TextBoxWithTag">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Self}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl.Resources>
Then assigning to a control like so:
<ContentControl Grid.Row="0"
Grid.Column="1"
Width="40"
HorizontalAlignment="Right"
Content="{StaticResource TextBoxWithTag}"
Tag="test tag" />
This also does not work, still complaining about:
The attached property "Triggers" can only be applied to types that are derived from "DependencyObject".
EDIT 2
Here is the GotFocusCommand
information. It sets the value of a string
that has a TextBlock
bound to it.
This is in my ViewModel
:
private ICommand _gotFocusCommand;
public ICommand GotFocusCommand
{
get
{
if (_gotFocusCommand == null)
{
_gotFocusCommand = new DelegateCommand<string>(TextBoxGotFocus);
}
return _gotFocusCommand;
}
}
private void TextBoxGotFocus(string infoText)
{
CardInfoText = infoText;
}
Then the XAML:
<TextBlock Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Text="{Binding CardInfoText}" />
There are several ways to do what you want. One example:
public static class UIBehaviors {
public static readonly DependencyProperty AttachedTriggersProperty = DependencyProperty.RegisterAttached(
"AttachedTriggers", typeof (EventTriggerCollection), typeof (UIBehaviors), new PropertyMetadata(null, OnAttachedTriggersChanged));
private static void OnAttachedTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var triggers = Interaction.GetTriggers(d);
if (e.OldValue != null) {
foreach (var trigger in (EventTriggerCollection) e.OldValue) {
triggers.Remove(trigger);
}
}
if (e.NewValue != null) {
foreach (var trigger in (EventTriggerCollection) e.NewValue) {
triggers.Add(trigger);
}
}
}
public static void SetAttachedTriggers(DependencyObject element, EventTriggerCollection value) {
element.SetValue(AttachedTriggersProperty, value);
}
public static EventTriggerCollection GetAttachedTriggers(DependencyObject element) {
return (EventTriggerCollection) element.GetValue(AttachedTriggersProperty);
}
}
public class EventTriggerCollection : Collection<EventTrigger> {
}
Here we declare attached property which accepts set of EventTrigger (from Interactivity assembly). When this property is set, we just attach all those triggers, like i:Interaction.Triggers will do. Then use it like this:
<Window.Resources>
<local:EventTriggerCollection x:Shared="False" x:Key="textBoxTriggers">
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TextBox}, Path=Tag}" />
</i:EventTrigger>
</local:EventTriggerCollection>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:UIBehaviors.AttachedTriggers" Value="{StaticResource textBoxTriggers}"/>
</Style>
</Window.Resources>
Note how I bind to TextBox.Tag property. You cannot just bind to it as in your question, because your data context will be view model (with GotFocusCommand). Also note how triggers collection is moved as a separate element in resource dictionary, and x:Shared="false" set for it. This will cause creating new set of triggers each time this property is accessed, so each TextBox will have it's own set of triggers.
Then any
<TextBox Text="Test" Tag="test message" />
Will call GotFocusCommand on data context of TextBox, with "test message" as parameter.