WPF中文网

什么是行为?

在WPF中,假如我们需要给Button增加阴影效果,同时又需要给TextBlock增加阴影效果,或者又来了新的需求,连DataGrid也需要增加阴影效果。虽然我们已经学过如何给一个控件增加阴影效果,那就是在UIElement基类中去设置Effect属性,但是,每个控件都要单独去实现一次阴影效果,未免太繁琐。那又该如何偷懒呢?Behavior类(行为)就是专门用来实现这类需求的。

一句话,行为就是某些控件的共同特征的实现。如上所示,既然所有控件都有Effect属性,那干脆将这个特征写成一个行为,哪个控件有这个需求,直接调用这个行为即可。说是调用行为,专业点说,是将这个行业附加到某个控件上。

一、行为基类的定义

首先,我们来看一下Behavior基类的定义,了解大致组成后,我们才知道要怎么使用它。

namespace System.Windows.Interactivity
{
    public abstract class Behavior : Animatable, IAttachedObject
    {
        protected Type AssociatedType { get; }
        protected DependencyObject AssociatedObject { get; }

        public void Attach(DependencyObject dependencyObject);
        public void Detach();
        protected override Freezable CreateInstanceCore();
        protected virtual void OnAttached();
        protected virtual void OnDetaching();
    }
}

从定义上看,第一,它是一个抽象类,所以我们不能直接使用它,而是去继承它,在子类中实现我们自己的行业效果。第二,它继承于Animatable,说明行为是可以动画实现的。第三,它的命名空间为System.Windows.Interactivity,这个空间在哪里?

要使用行为,我们需要引入一个.dll动态库,或者您可以在nuget上找到它。请安装Microsoft.Xaml.Behaviors.Wpf这个组件包。

Behavior泛型基类

namespace System.Windows.Interactivity
{
    public abstract class Behavior<T> : Behavior where T : DependencyObject
    {
        protected Behavior();

        protected T AssociatedObject { get; }
    }
}

其实,我们实际继承的是行为的泛型基类,因为WPF并不知道我们要给哪一种控件定义行为,于是,干脆提供一个泛型基类供我们使用。所以,我们将两个基类合并起来介绍。

AssociatedObject属性:这个属性通常表示一个控件,类型是DependencyObject(依赖对象),也就是说,我们写的行为要给某个控件使用的前提是,这个控件是一个DependencyObject(依赖对象)。

OnAttached():这是一个虚方法,将来在行为中被重写,表示附加一个行为时要执行的业务逻辑。

OnDetaching():这是一个虚方法,将来在行为中被重写,表示分离一个行为时要执行的业务逻辑。

二、行为的开发

接下来,我们假如要给一个TextBlock文字块增加一个阴影效果。那么,就可以编写一个自定义行为TextBehavior,相关代码如下:

public class TextBehavior : Behavior<TextBlock>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
        AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
        AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;
    }

    private void AssociatedObject_MouseEnter(object sender,
        System.Windows.Input.MouseEventArgs e)
    {
        var element = sender as TextBlock;
        DropShadowEffect effect = new DropShadowEffect();
        effect.Color = Colors.Gold;
        effect.ShadowDepth = 0;
        effect.BlurRadius = 15;
        element.Effect = effect;            
    }

    private void AssociatedObject_MouseLeave(object sender, 
        System.Windows.Input.MouseEventArgs e)
    {
        var element = sender as TextBlock;
        DropShadowEffect effect = new DropShadowEffect();
        effect.Color = Colors.Gold;
        effect.ShadowDepth = 0;
        effect.BlurRadius = 15;
        element.Effect = null;
    }
}

在上面的代码中, 我们给AssociatedObject的MouseEnter和MouseLeave分别订阅了两个事件回调函数,在回调函数中去设置DropShadowEffect 效果。注意,这里的AssociatedObject实际上就等于TextBlock控件哦。

在定义好一个行为后,接下来就是在XAML中使用它。

第一步,在XAML中导入命名空间 xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

第二步,编写前端XAML代码,引用TextBehavior行为,完整代码如下:

<Window x:Class="HelloWorld.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM" 
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:behavior="clr-namespace:HelloWorld.Behaviors"
        mc:Ignorable="d" Background="DarkCyan"
        Title="WPF中文网 - 行为 - www.wpfsoft.com" Height="350" Width="500">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Canvas>
        <TextBlock Text="WPF中文网" 
                   Foreground="White" 
                   FontSize="60" 
                   Canvas.Left="92" 
                   Canvas.Top="75">
            <i:Interaction.Behaviors>
                <behavior:TextBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <TextBlock Text="Behavior行为之阴影效果" 
                   Foreground="White" 
                   FontSize="40" 
                   Canvas.Left="21" 
                   Canvas.Top="182">
            <i:Interaction.Behaviors>
                <behavior:TextBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
    </Canvas>
</Window>

最后,运行调试,鼠标移上去,文字将增加阴影,鼠标移开,文字阴影将消失。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:106-《什么是行为》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年11月21日

在上一节中,我们学习了什么是行为,并给TextBlock文字块开发一个阴影行为,但是那个TextBehavior只能给TextBlock控件使用,我们还可以进一步优化这个行为,使之所有控件都可以直接使用。

这一节,我们来优化一下这个阴影效果。新建一个ShadowBehavior类,并继承Behavior基类,T泛型参数改为UIElement。如下所示:

public class ShadowBehavior : Behavior<UIElement>
{
    private DropShadowEffect effect = new DropShadowEffect();
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
        AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
        effect.ShadowDepth = 0;
        effect.BlurRadius = 5;
        effect.Color = Colors.Black;
        AssociatedObject.Effect = effect;
    }
    //鼠标离开去掉阴影
    private void AssociatedObject_MouseLeave(object sender, 
        System.Windows.Input.MouseEventArgs e)
    {
        effect.BlurRadius = 5;
    }
    //鼠标进入增加阴影
    private void AssociatedObject_MouseEnter(object sender, 
        System.Windows.Input.MouseEventArgs e)
    {
        effect.BlurRadius = 15;
    }
}

这次我们将代码做了一些优化,比如只实例化了一个DropShadowEffect 对象,更节省内存。同时,由于Effect 属性定义在UIElement基类中,所以T参数就是UIElement。这样做的好处是,所有控件都可以共享这个行为了。比如下面的Border、TextBlock和Button都可以使用这个阴影效果。

<Window x:Class="HelloWorld.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM" 
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:behavior="clr-namespace:HelloWorld.Behaviors"
        mc:Ignorable="d" Background="DarkCyan"
        Title="WPF中文网 - 行为 - www.wpfsoft.com" Height="350" Width="500">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Canvas>
        <Border Width="50" Height="50" CornerRadius="50" 
                Background="LightGoldenrodYellow" 
                Canvas.Left="228" Canvas.Top="20">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
            </i:Interaction.Behaviors>
        </Border>
        <TextBlock Text="WPF中文网" 
                   Foreground="White" 
                   FontSize="60" 
                   Canvas.Left="92" 
                   Canvas.Top="75">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <TextBlock Text="Behavior行为之阴影效果" 
                   Foreground="White" 
                   FontSize="40" 
                   Canvas.Left="21" 
                   Canvas.Top="182">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <Button Content="退出" Width="80" Height="30" 
                FontSize="14"
                Canvas.Left="375" Canvas.Top="265">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
            </i:Interaction.Behaviors>
        </Button>
    </Canvas>
</Window>

F5运行调试,鼠标放到任意一个控件上,都将出现阴影效果的变化。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:107-《行为之UIElement阴影效果》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年11月21日

关于控件的平移,我们之前学习转换时,曾经对TranslateTransform进行了讲解和示例。在那个示例中,我们只需要设置TranslateTransform的X属性和Y属性,使可以移动一个控件的位置。在学习了行为之后,我们肯定不希望一遍又一遍地去实现控件的移动效果,如果将移动效果做成一个行为,那么,任何一个控件,只要想拥有移动效果,直接调用行为即可,岂不快哉。

实现思路:

1、TranslateTransform平移对象最终赋值给RenderTransform属性,而RenderTransform位于UIElement基类中,所以,行为的泛型T参数应设置为UIElement。

2、TranslateTransform的X和Y是当前控件平移的位置坐标,这个坐标相对于当前控件的父控件而言的,所以在实现行为时,要拿到这个父控件。

3、我们采用鼠标去拖拽一个控件进行平移,所以要获取鼠标按下时的位置和移动过程中的位置,从而才知道TranslateTransform要平移到什么位置。

4、如果当前被移动的控件没有TranslateTransform,我们需要先给它实例化一个TranslateTransform对象。

根据上面的思路,我们来实现这个移动行为。

public class DragBehavior : Behavior<FrameworkElement>
{
    protected bool isDragging = false;
    private Point startPosition;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.MouseDown += AssociatedObject_MouseDown;
        AssociatedObject.MouseMove += AssociatedObject_MouseMove;
        AssociatedObject.MouseUp += AssociatedObject_MouseUp;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
        AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
        AssociatedObject.MouseUp -= AssociatedObject_MouseUp;
    }

    private void AssociatedObject_MouseDown(object sender, 
        System.Windows.Input.MouseButtonEventArgs e)
    {
        isDragging = true;
        var parent = AssociatedObject.Parent as FrameworkElement;
        var clickPosition = e.GetPosition(parent);

        //首次按下时,判断RenderTransform的类型是否为TranslateTransform
        if (!(AssociatedObject.RenderTransform is TranslateTransform))
        { 
            AssociatedObject.RenderTransform = new TranslateTransform(); 
        }

        var transform = AssociatedObject.RenderTransform as TranslateTransform;
        startPosition.X = clickPosition.X - transform.X;   
        startPosition.Y = clickPosition.Y - transform.Y;
        AssociatedObject.CaptureMouse();//强制鼠标捕捉
    }

    private void AssociatedObject_MouseMove(object sender, 
        System.Windows.Input.MouseEventArgs e)
    {
        if (isDragging)
        {
            var parent = AssociatedObject.Parent as FrameworkElement;
            Point currentPosition = e.GetPosition(parent);
            var transform = AssociatedObject.RenderTransform as TranslateTransform;

            transform.X = currentPosition.X - startPosition.X;
            transform.Y = currentPosition.Y - startPosition.Y;
        }
    }
    private void AssociatedObject_MouseUp(object sender, 
        System.Windows.Input.MouseButtonEventArgs e)
    {
        isDragging = false;
        AssociatedObject.ReleaseMouseCapture();//释放鼠标捕捉
    }
}

下一步,我们在需要移动的控件上引用这个行为。

<Window x:Class="HelloWorld.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM" 
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:behavior="clr-namespace:HelloWorld.Behaviors"
        mc:Ignorable="d" Background="DarkCyan"
        Title="WPF中文网 - 行为 - www.wpfsoft.com" Height="350" Width="500">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Canvas>
        <Border Width="50" Height="50" CornerRadius="50" 
                Background="LightGoldenrodYellow" 
                Canvas.Left="228" Canvas.Top="20">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:DragBehavior/>
            </i:Interaction.Behaviors>
        </Border>
        <TextBlock Text="WPF中文网" 
                   Foreground="White" 
                   FontSize="60" 
                   Canvas.Left="92" 
                   Canvas.Top="75">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:DragBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <TextBlock Text="Behavior行为之阴影效果" 
                   Foreground="White" 
                   FontSize="40" 
                   Canvas.Left="21" 
                   Canvas.Top="182">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:DragBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <Button Content="退出" Width="80" Height="30" 
                FontSize="14"
                Canvas.Left="375" Canvas.Top="265">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:DragBehavior/>
            </i:Interaction.Behaviors>
        </Button>
    </Canvas>
</Window>

这是鼠标移动之前的效果。

这是鼠标随意移动之后的效果。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:108-《行为之控件移动效果》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年11月21日

这一节,我们来利用Behavior行为实现控件的缩放效果。要实现此效果,需要在鼠标的滚轮事件下,通过获取Delta值去设置ScaleTransform缩放对象的ScaleX和ScaleY,从而将控件进行放大或缩小。

下面是缩放行为的定义代码:

public class ScaleBehavior : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.MouseWheel += AssociatedObject_MouseWheel;
    }

    private void AssociatedObject_MouseWheel(object sender, 
        System.Windows.Input.MouseWheelEventArgs e)
    {
        //首次滚动时,判断RenderTransform的类型是否为ScaleTransform
        if (!(AssociatedObject.RenderTransform is ScaleTransform))
        {
            AssociatedObject.RenderTransform = new ScaleTransform()
            {
                CenterX = AssociatedObject.ActualWidth / 2,
                CenterY = AssociatedObject.ActualHeight / 2,
            };
        }

        var transform = AssociatedObject.RenderTransform as ScaleTransform;
        double scale = e.Delta * 0.001;
        transform.ScaleX += scale;
        transform.ScaleY += scale;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.MouseWheel -= AssociatedObject_MouseWheel;
    }        
}

然后,我们在上一节的代码中稍作修改,引用这个行为。

<Window x:Class="HelloWorld.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM" 
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:behavior="clr-namespace:HelloWorld.Behaviors"
        mc:Ignorable="d" Background="DarkCyan"
        Title="WPF中文网 - 行为 - www.wpfsoft.com" Height="350" Width="500">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Canvas>
        <Border Width="50" Height="50" CornerRadius="50" 
                Background="LightGoldenrodYellow" 
                Canvas.Left="228" Canvas.Top="20">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:ScaleBehavior/>
            </i:Interaction.Behaviors>
        </Border>
        <TextBlock Text="WPF中文网" 
                   Foreground="White" 
                   FontSize="60" 
                   Canvas.Left="92" 
                   Canvas.Top="75">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:ScaleBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>
        <TextBlock Text="Behavior行为之缩放效果" 
                   Foreground="White" 
                   FontSize="40" 
                   Canvas.Left="21" 
                   Canvas.Top="182">
            <i:Interaction.Behaviors>
                <behavior:ShadowBehavior/>
                <behavior:ScaleBehavior/>
            </i:Interaction.Behaviors>
        </TextBlock>       
    </Canvas>
</Window>

缩放之前的效果

缩放之后的效果

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:109-《行为之控件缩放效果》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年11月21日

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1