WPF Behavior 、Trigger详解|概述、使用场景、区别、示例、源码如何实现

9 篇文章 0 订阅
4 篇文章 0 订阅

Trigger

Trigger概述

Trigger(触发器)用于在特定条件下自动更改控件的样式或行为。

可以看作是一个条件检查器和动作执行器的组合。当满足某个特定条件时(例如属性值、数据绑定值等),Trigger会自动更改UI元素的属性、样式甚至行为。

Trigger可以应用于Style、ControlTemplate和DataTemplate。

Trigger种类及用法

WPF 中有几种不同类型的 Trigger

  • Property Trigger:当依赖属性的值发生变化时触发。
  • Data Trigger:当绑定的数据发生变化时触发。
  • Event Trigger:当路由事件发生时触发。
  • MultiTrigger:当多个条件同时满足时触发。
  • MultiDataTrigger:当多个数据绑定条件同时满足时触发。

1. Property Trigger:当依赖属性的值发生变化时触发。

作用:当控件的某个依赖属性达到特定值时触发。

  • 示例代码
<StackPanel Orientation="Horizontal">
            <TextBlock
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Background="Transparent"
                Text="Property Trigger:当依赖属性的值发生变化时触发。"
                TextAlignment="Center"
                TextWrapping="Wrap" />
            <Rectangle
                Width="100"
                Height="100">
                <Rectangle.Style>
                    <Style TargetType="Rectangle">
                        <Setter Property="Fill" Value="Green" />
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Fill" Value="Red" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Rectangle.Style>
            </Rectangle>
        </StackPanel>

在这里插入图片描述

2. DataTrigger:当绑定的数据发生变化时触发。

作用:当绑定的数据满足某个条件时触发。

<StackPanel Orientation="Horizontal">
    <ToggleButton
        Name="toggleButton"
        Content="控制IsEnable属性的Toggle Button" />
    <TextBlock
        Margin="10"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Text="DataTrigger:当绑定到的数据 toggleButton的IsChecked属性 ,发生变化时触发。">
        <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Style.Triggers>
                    <DataTrigger
                        Binding="{Binding IsChecked,
                                          ElementName=toggleButton}"
                        Value="False">
                        <Setter Property="Foreground" Value="Pink" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</StackPanel>

在这里插入图片描述

3. 事件触发器 (EventTrigger)

作用:当控件的某个事件被触发时

Event Trigger 用于在路由事件发生时执行操作,通常用于动画。

示例
<Button
    Width="500"
    Height="100"
    Margin="10"
    Content="EventTrigger “Button.Click” 事件触发时,透明度发生变化">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        AutoReverse="True"
                        Storyboard.TargetProperty="Opacity"
                        From="1.0"
                        To="0.0"
                        Duration="0:0:1" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

在这个示例中,当按钮被点击时,会触发一个动画,使按钮的透明度从 1.0 变为 0.0,然后再恢复。
在这里插入图片描述

4. 多条件触发器 (MultiTrigger) 和 多数据触发器 (MultiDataTrigger)

MultiDataTrigger

MultiDataTrigger 用于在多个数据绑定条件同时满足时执行操作。

示例
<StackPanel
    Margin="10"
    HorizontalAlignment="Center"
    Orientation="Horizontal">
    <ToggleButton
        Name="toggleButton1"
        Content="条件1" />
    <ToggleButton
        Name="toggleButton2"
        Content="条件2" />
    <TextBlock
        Margin="10"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Text="toggleButton1 和 toggleButton2 同时为True时,">
        <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Path=IsChecked, ElementName=toggleButton1}" Value="True" />
                            <Condition Binding="{Binding Path=IsChecked, ElementName=toggleButton2}" Value="True" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Foreground" Value="Purple" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</StackPanel>

在这里插入图片描述

MultiTrigger

MultiTrigger 用于在多个条件同时满足时执行操作。

示例
<Button
    Width="300"
    Height="50"
    Margin="10"
    Content="当鼠标移入并按下时,按钮背景变为深蓝色"
    OverridesDefaultStyle="True">
    <Button.Style>
        <Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border
                            x:Name="Border"
                            Background="{TemplateBinding Background}">
                            <ContentPresenter
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center" />
                        </Border>
                        <ControlTemplate.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True" />
                                    <Condition Property="IsPressed" Value="True" />
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Border" Property="Background" Value="DarkBlue" />
                            </MultiTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Style>
</Button>

在这里插入图片描述

在这个示例中,当按钮被按下且鼠标悬停在按钮上时,按钮的背景颜色会变为深蓝色。

Behavior

Behavior 概述

Behavior 是一种可重用的、封装的行为逻辑,可以附加到任何控件上,在运行时动态改变控件的行为,而无需修改控件本身的代码或创建自定义控件,实现简洁和模块化的交互逻辑,通常用于实现更复杂的交互逻辑。

主要特点:

  • 可重用性:Behavior 可以封装通用的界面功能,实现代码重用。
  • 独立性:Behavior 逻辑独立,不需要特定的触发器。
  • 扩展性:除了系统提供的 Behavior,还可以自定义 Behavior 以实现特定功能。

使用场景

  • 封装复杂逻辑:将复杂的交互逻辑封装在 Behavior 中,使代码更清晰、更易维护。
  • 提高可重用性:Behaviors 可以在多个控件之间重用,减少重复代码。
  • 与 MVVM 模式结合:Behaviors 可以与 MVVM 模式很好地结合,使 ViewModel 保持干净,不包含 UI 逻辑。

官方文档

Behavior的基本结构

  1. 继承Behavior类

Behavior类通常继承自Behavior<T>,其中T是行为所附加的元素类型。例如,Behavior<UIElement>表示该行为可以附加到任何UIElement类型的元素上。

  1. 重写OnAttached和OnDetaching方法
  • OnAttached:当行为附加到元素时调用。
  • OnDetaching:当行为从元素分离时调用。
  1. 使用AssociatedObject
    AssociatedObject属性表示行为所附加的对象,可以在行为中访问和操作该对象

使用 Behavior 的步骤

1. 添加必要的 DLL 库

要使用 Behavior,可以通过 NuGet 进行安装:
在这里插入图片描述

Behavior并不是WPF的内置组件,因此需要通过NuGet包进行安装。
要使用行为(Behavior),请通过NuGet安装 Microsoft.Xaml.Behaviors.Wpf 包。同时,在XAML文件中添加以下命名空间:xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

2. 创建自定义 Behavior 类 —— 大小缩放Behavior

要创建自定义Behavior,需要遵循以下步骤:

  1. 创建一个类,继承自System.Windows.Interactivity.Behavior<T>,其中T是要应用Behavior的UI元素的类型。
  2. 在类中定义需要的依赖属性,用于配置Behavior的行为。
  3. 重写OnAttachedOnDetaching方法,分别处理Behavior附加到元素时的逻辑和Behavior从元素上移除时的逻辑。比如添加/移除事件订阅,初始化数据等
  4. OnPropertyChanged方法中处理属性值更改时的逻辑。
  5. 在XAML中使用自定义Behavior,将其附加到目标UI元素。
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace BehaviorDemo
{
    // 定义一个行为类,用于实现鼠标滚轮缩放功能
    public class ZoomWithWheelBehavior : Behavior<FrameworkElement>
    {
        // 定义一个缩放变换对象
        private ScaleTransform _scaleTransform = new ScaleTransform();
        private Point _origin; // 缩放起点
        private Point _start;  // 鼠标左键按下时的位置

        // 定义依赖属性 ScaleFactor,用于设置缩放因子
        public static readonly DependencyProperty ScaleFactorProperty =
            DependencyProperty.Register("ScaleFactor", typeof(double), typeof(ZoomWithWheelBehavior), new PropertyMetadata(1.0));

        public double ScaleFactor
        {
            get { return (double)GetValue(ScaleFactorProperty); }
            set { SetValue(ScaleFactorProperty, value); }
        }

        // 定义依赖属性 MinScale,用于设置最小缩放比例
        public static readonly DependencyProperty MinScaleProperty =
            DependencyProperty.Register("MinScale", typeof(double), typeof(ZoomWithWheelBehavior), new PropertyMetadata(0.1));

        public double MinScale
        {
            get { return (double)GetValue(MinScaleProperty); }
            set { SetValue(MinScaleProperty, value); }
        }

        // 定义依赖属性 MaxScale,用于设置最大缩放比例
        public static readonly DependencyProperty MaxScaleProperty =
            DependencyProperty.Register("MaxScale", typeof(double), typeof(ZoomWithWheelBehavior), new PropertyMetadata(10.0));

        public double MaxScale
        {
            get { return (double)GetValue(MaxScaleProperty); }
            set { SetValue(MaxScaleProperty, value); }
        }

        // 当行为附加到控件时调用
        protected override void OnAttached()
        {
            base.OnAttached();
            var transformGroup = new TransformGroup();
            transformGroup.Children.Add(_scaleTransform);
            AssociatedObject.RenderTransform = transformGroup;
            AssociatedObject.MouseWheel += OnMouseWheel;
            AssociatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;
        }

        // 当行为从控件分离时调用
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MouseWheel -= OnMouseWheel;
            AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
        }

        // 处理鼠标左键按下事件
        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _start = e.GetPosition(AssociatedObject);
            _origin = new Point(_scaleTransform.ScaleX, _scaleTransform.ScaleY);
            AssociatedObject.CaptureMouse();
        }

        // 处理鼠标滚轮事件
        private void OnMouseWheel(object sender, MouseWheelEventArgs e)
        {
            double zoom = e.Delta > 0 ? ScaleFactor : -ScaleFactor;
            double newScaleX = _scaleTransform.ScaleX + zoom;
            double newScaleY = _scaleTransform.ScaleY + zoom;

            // 确保新的缩放比例在最小和最大缩放比例之间
            if (newScaleX >= MinScale && newScaleX <= MaxScale && newScaleY >= MinScale && newScaleY <= MaxScale)
            {
                _scaleTransform.ScaleX = newScaleX;
                _scaleTransform.ScaleY = newScaleY;
            }
        }
    }
}

3. 在 XAML 中使用 Behavior

在 XAML 文件中引用自定义的 Behavior 类:

<Window
    x:Class="BehaviorDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:local="clr-namespace:BehaviorDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">

    <StackPanel Orientation="Vertical">
        <Border
            Width="100"
            Height="100"
            Padding="5"
            Background="LightGray">
            <i:Interaction.Behaviors>
                <local:ZoomWithWheelBehavior
                    MaxScale="3.0"
                    MinScale="0.5"
                    ScaleFactor="0.1" />
            </i:Interaction.Behaviors>
        </Border>
    </StackPanel>
</Window>

Behavior的最佳实践

  • 尽量保持Behavior的通用性,避免过于具体的实现。
  • 避免在Behavior中包含过多的逻辑,以免导致代码难以维护。
  • 使用依赖属性来配置Behavior的行为,以便在XAML中进行设置。
  • 在适当的时候使用事件和命令,以便与其他控件或框架集成。

WPF中的Trigger和Behavior的关系与区别

关系
  • 互补性: Trigger和Behavior都用于增强控件的交互性和功能性,但侧重点不同。Trigger专注于样式变化,Behavior则提供更丰富的交互逻辑。
  • 组合使用: 在某些复杂场景下,Trigger和Behavior可以组合使用,以实现更灵活的交互设计。例如,通过Trigger改变样式,同时通过Behavior处理具体的逻辑
区别
  • 实现方式: Trigger通常直接在XAML中使用,而Behavior则需要引入Interactions库,并在代码和XAML中配合使用
  • 功能范围: Trigger专注于样式变化,基于属性或事件触发;Behavior则提供更多交互功能,不仅限于样式变化。
  • 复杂度:Triggers 适用于简单的属性变化和事件处理,而 Behaviors 更适合复杂的交互逻辑。
  • 扩展性: Behavior提供了更强的扩展性,可以通过编写自定义Behavior来实现更多功能。Trigger虽然也有一定的扩展性,但在处理复杂逻辑时较为局限
  • 可重用性:Behaviors 更具可重用性,可以在不同控件之间共享,而 Triggers 通常绑定到特定控件。

Behavior 源码,学习如何实现检测事件发生,并执行对应命令

我下载了Behavior 源码,想从中简单学习一下,是如何实现检测到事件,并在事件触发时执行对应的命令。

其核心逻辑,其实是通过附加属性注册和获取事件名称和命令,并通过反射获取事件、并将事件处理程序添加到事件中,以实现当被附加的控件发生该事件时,执行预期的命令。如下代码

 // 当 EventName 属性改变时的回调方法
    private static void OnEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            string eventName = e.NewValue as string;
            if (!string.IsNullOrEmpty(eventName))
            {
                // 获取事件信息
                EventInfo eventInfo = element.GetType().GetEvent(eventName);
                if (eventInfo != null)
                {
                    // 验证事件是否有效
                    if (IsValidEvent(eventInfo))
                    {
                        // 获取 OnEventTriggered 方法的信息
                        MethodInfo methodInfo = typeof(EventTriggerBehavior).GetMethod("OnEventTriggered", BindingFlags.NonPublic | BindingFlags.Static);

                        // 创建事件处理程序委托
                        Delegate eventHandler = Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo);

                        // 将事件处理程序添加到事件中
                        eventInfo.AddEventHandler(element, eventHandler);
                    }
                }
            }
        }
    }

详细实现步骤

  1. 注册附加属性:使用 DependencyProperty.RegisterAttached 方法注册 EventNamePropertyCommandProperty
  2. 附加属性的 Get/Set 方法:定义静态方法 GetEventNameSetEventNameGetCommandSetCommand,用于获取和设置附加属性的值。
  3. 事件名称改变时的处理:在 OnEventNameChanged 方法中,根据新的事件名称查找事件,并验证事件是否有效。如果有效,则创建事件处理程序委托并添加到事件中。
  4. 事件处理程序:定义 OnEventTriggered 方法,当事件触发时,获取并执行命令。
  5. 事件验证:在 IsValidEvent 方法中,验证事件处理程序的参数类型是否符合要求。

示例代码


    public static class EventTriggerBehavior
    {
        // 注册附加属性 EventName,用于存储事件名称
        public static readonly DependencyProperty EventNameProperty =
            DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(EventTriggerBehavior), new PropertyMetadata(null, OnEventNameChanged));

        // 注册附加属性 Command,用于存储命令
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventTriggerBehavior), new PropertyMetadata(null));

        // 获取 EventName 附加属性的值
        public static string GetEventName(DependencyObject obj)
        {
            return (string)obj.GetValue(EventNameProperty);
        }

        // 设置 EventName 附加属性的值
        public static void SetEventName(DependencyObject obj, string value)
        {
            obj.SetValue(EventNameProperty, value);
        }

        // 获取 Command 附加属性的值
        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        // 设置 Command 附加属性的值
        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        // 当 EventName 属性改变时的回调方法
        private static void OnEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is UIElement element)
            {
                string eventName = e.NewValue as string;
                if (!string.IsNullOrEmpty(eventName))
                {
                    // 获取事件信息
                    EventInfo eventInfo = element.GetType().GetEvent(eventName);
                    if (eventInfo != null)
                    {
                        // 验证事件是否有效
                        if (IsValidEvent(eventInfo))
                        {
                            // 获取 OnEventTriggered 方法的信息
                            MethodInfo methodInfo = typeof(EventTriggerBehavior).GetMethod("OnEventTriggered", BindingFlags.NonPublic | BindingFlags.Static);

                            // 创建事件处理程序委托
                            Delegate eventHandler = Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo);

                            // 将事件处理程序添加到事件中
                            eventInfo.AddEventHandler(element, eventHandler);
                        }
                    }
                }
            }
        }

        // 验证事件是否有效
        private static bool IsValidEvent(EventInfo eventInfo)
        {
            Type eventHandlerType = eventInfo.EventHandlerType;
            if (typeof(Delegate).IsAssignableFrom(eventInfo.EventHandlerType))
            {
                MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
                ParameterInfo[] parameters = invokeMethod.GetParameters();
                // 验证事件处理程序的参数是否符合要求
                return parameters.Length == 2 && typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType);
            }
            return false;
        }

        // 事件触发时的回调方法
        private static void OnEventTriggered(object sender, EventArgs e)
        {
            if (sender is DependencyObject dependencyObject)
            {
                // 获取并执行命令
                ICommand command = GetCommand(dependencyObject);
                if (command != null && command.CanExecute(null))
                {
                    command.Execute(null);
                }
            }
        }
    }

本文示例

效果

在这里插入图片描述

本文示例源码:https://github.com/Nita121388/NitasDemo/tree/main/06BehaviorDemo

参考

  1. microsoft/XamlBehaviorsWpf:GitHub 上 WPF XAML 行为的主页。 — microsoft/XamlBehaviorsWpf: Home for WPF XAML Behaviors on GitHub.
  2. 使用 Microsoft.Xaml.Behaviors 在 WPF 中处理命令和事件参数 - 非法关键字 - 博客园 (cnblogs.com)
  3. wpf中Interaction.Behaviors详解 - 维尔维尔 - 博客园 (cnblogs.com)
  4. 示例:WPF应用Behaviors封装的动画加载子项_c# wpf animationbehavior-CSDN博客
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值