将XAML行为附加到同一类型的所有控件

3

我有一个InvokeCommandAction,它附加到一个TextBoxGotFocus事件上,代码如下:

<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>

这样做很好,但我有几十个带有相同设置的TextBox。我不想为每一个重复编写代码,而是希望将该触发器附加到所有类型为{x:Type TextBox}的控件。

通常情况下,我会在Resources部分设置属性,如下所示:

<UserControl.Resources>
    <Style TargetType="TextBlock">
        <Setter Property="Padding" Value="5,0,0,0" />
        <Setter Property="VerticalAlignment" Value="Center" />
    </Style>
</UserControl.Resources>

很遗憾,这种方法不适用于Triggers

附加属性"Triggers"只能应用于从"DependencyObject"派生的类型。

理想情况下,我想要做这样的事情:

<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>

我只需要设置每个文本框的 Tag 属性来指定要显示的消息。我这么做是正确的吗?我需要改用 ControlTemplate 或类似的东西吗?
编辑:
我在这里看到了一个类似的问题:Interaction Triggers in Style in ResourceDictionary WPF 阅读该问题的答案后,我尝试了以下操作:
<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>

那么就像这样将其分配给控件:

然后分配给一个控件,如下所示:

<ContentControl Grid.Row="0"
                Grid.Column="1"
                Width="40"
                HorizontalAlignment="Right"
                Content="{StaticResource TextBoxWithTag}"
                Tag="test tag" />

这个也不起作用,仍然抱怨:
“Triggers”附加属性只能应用于派生自“DependencyObject”的类型。
编辑2:
这是“GotFocusCommand”的信息。它设置一个字符串的值,该字符串绑定到一个“TextBlock”。
这在我的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;
}

然后是XAML代码:
<TextBlock Grid.Row="2"
           Grid.Column="0"
           Grid.ColumnSpan="2"
           HorizontalAlignment="Center"
           Text="{Binding CardInfoText}" />

你如何显示那个消息?或者说,你需要什么来显示它?参考目标文本框就足够了吗? - Evk
我在我的ViewModel中有一个DelegateCommand叫做GotFocusCommand,它设置了一个值,我将把它绑定到一个TextBlock上。我也会附加到这个问题上。 - dub stylee
1个回答

8

有几种方法可以实现你想要的功能。以下是其中一种示例:

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> {

}

在这里,我们声明了一个附加属性,它接受一组事件触发器(来自Interactivity程序集)。当设置了此属性时,我们只需附加所有这些触发器,就像i:Interaction.Triggers所做的那样。然后像这样使用:

<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>

请注意我是如何绑定到TextBox.Tag属性的。您不能像在问题中那样直接绑定,因为你的数据上下文将是视图模型(带有GotFocusCommand)。
还要注意将触发器集合作为单独的元素移动到资源字典中,并为其设置x:Shared="false"。这会导致每次访问此属性时创建新的触发器集合,因此每个TextBox都将拥有自己的一组触发器。
然后任何内容。
<TextBox Text="Test" Tag="test message" />

将在文本框的数据上下文中调用GotFocusCommand,参数为“test message”。


这对于单个 TextBox 很好用。但是如果我添加第二个,就会出现异常:在 triggers.Add(trigger); 行上出现 An instance of a trigger cannot be attached to more than one object at a time. 的异常。 - dub stylee
非常好。现在我只需要为每个TextBox设置Tag,而不需要为每个单独的文本框重复附加行为。谢谢! - dub stylee
x:shared="false" 在WinRT中似乎不受支持,请参阅此帖子:https://dev59.com/9Xvaa4cB1Zd3GeqPJe6w 有什么解决办法吗? - Cool Breeze
@CoolBreeze 不确定,但可以尝试在 OnAttachedTriggersChanged 中克隆触发器。因此,您有一个 EventTriggerCollection,在其中不直接添加触发器,而是克隆每个触发器(通过 new EventTrigger(trigger.RoutedEvent) 并将所有操作复制到新触发器),然后使用该克隆。 - Evk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接