• WPF行为


    行为是一款重用用户界面代码的更有挑战性的工具。其基本思想是:使用行为封装一些通用UI功能。如果具有适当的行为,可使用一两行XAML标记将其附加到任意元素,从而节省编写和调试代码的工作。

    样式提供了重用一组属性设置的实用方法。它们帮助构建一致的、组织良好的界面迈出了重要的第一步——但它们还有许多限制。

    问题是在典型的应用程序中,属性设置仅是用户界面基础结构的一小部分。甚至最基本的程序通常也需要大量的用户界面代码,这些代码与应用程序的功能无关。在许多程序中,用于用户界面任务的代码(如驱动动画、实现平滑效果、维护用户界面状态,以及支持诸如拖放、缩放以及停靠等用户急么特性),无论是在数量上还是复杂性上都超出了业务代码。许多这类代码是通用的,这意味着在创建每个WPF对象中需要编写相同的内容。所有这些工作几乎都是单调乏味的。

    为回应这一挑战,ExpressionBlend创作者开发了称为行为(Behavior)的特征。其思想很简单:创建封装了一些通用用户界面功能的行为,这一功能可以是基本功能(如启动故事板或导航到超链接),也可以是复杂功能(如处理多点触摸交互,或构件使用实时物理引擎的碰撞模型)。一旦构建功能,就可将他们添加到任意应用程序的另一个控件中,具体方法是将该控件链接到适当的行为并设置行为的属性。

    自定义控件时另一个在应用程序中(或在多个应用程序之间)重用用户界面功能的技术。然而,自定义控件必须作为可视化内容和代码的紧密链接包进行创建。尽管自定义控件非常强大,但却不能适应于需要大量具有类似功能的不同控件的情况(例如,为一组不同的元素添加鼠标悬停渲染效果)。因此,样式、行为以及自定义控件都是互补的。

    获取行为支持

    有一个问题,重用用户界面代码通用块的基础结构不是WPF的一部分。反而,它被捆绑到ExpressionBlend。这是因为行为开始是作为ExpressionBlend的设计时特性引入的。事实上,ExpressionBlend仍是通过将行为拖动到需要行为的控件上来添加行为的唯一工具。但这并不意味着行为只能用于ExpressionBlend,只需要付出很少的努力就可以在VisualStudio应用程序中创建和使用行为。只需要手动编写标记,而不是使用工具箱。

    为了获得支持行为的程序集,有两种选择:

    1、可安装Expression Blend或Expression Blend For Visual Studio ,所有这些版本都包含Visual Studio中的行为功能所需的程序集,但您只能通关Expression Blend For Visual Studio 在Blend环境中创建和编辑WPF应用程序。

    2、可安装 Expression Blend SDK

    无论是使用Expression Blend IDE还是SDK,最终要使用的两个程序集是:

    System.Windows.Interactivity.dll 这个程序集定义了支持行为的基本类。它是行为特征的基础

    Microsoft.Expression.Interactions.dll 这个程序集通过添加可选的以核心行为类为基础的动作和触发器类,增加了一些有用的扩展。

    理解行为模型

    行为特性具有两个版本,一个版本旨在为Silverlight 添加行为支持,Silverlight 是Microsoft 的针对浏览器的富客户端插件;而另一个版本是针对WPF设计的。尽管这两个版本提供了相同的特性,但行为特性和Silverlight 领域更吻合,因为它弥补了更大的鸿沟。与WPF不同,Silverlight 不支持触发器,所以实现行为的程序集也实现触发器更合理。然而,WPF支持触发器,行为特性包含自己的触发器系统,而触发器系统与WPF模型不匹配,这确实令人感到有些困惑。

    问题在于具有类似名称的这两个特性有部分重合但不完全相同。在WPF中,触发器最重要的角色是构建灵活的样式和控件模板。在触发器的帮助下,样式和模板变得更加智能;例如,当一些属性发生变化时可视化效果。然而,ExpressionBlend中的触发器具有不同的目的。通过使用可视化设计工具,允许您为应用程序添加简单功能。换句话说,WPF触发器支持更强大的样式和控件模板。而ExpressionBlend触发器支持快速的不需要代码的应用程序设计。

    那么,对于使用WPF的普通开发人员来说所有这些意味着什么呢?下面是几条指导原则:

    1、行为模型不是WPF的核心部分,所以行为不像样式和模板那样确定。换句话说,可编写不使用行为的WPF应用程序,但如果不是样式和模板,就不能创建比“Hello World” 演示更复杂的WPF应用程序。

    2、如果在ExpressionBlend上耗费大量时间,或希望为其他ExpressionBlend用户开发组件,您可能会对ExpressionBlend中的触发器特性感兴趣。尽管和WPF中的触发器系统使用相同的名称,但它们不相互重叠,您可以同时使用这两者。

    3、如果不使用ExpressionBlend,可完全略过其触发器特性——但仍应分析ExpressionBlend提供的功能完整的行为类。这是因为行为比ExpressionBlend的触发器更强大也更常用。

    创建行为

    行为旨在封装一些UI功能,从而可以不必编写代码就能够将其应用到元素上。从另一个角度看,每个行为都为元素提供了一个服务。该服务通常涉及监听几个不同的事件并执行几个相关的操作。例如,为文本框提供水印,如果文本框为空,并且当前没有焦点,那么会以清淡的字体显示提示信息。当文本框具有焦点时,启动行为并删除水印文本。

    为更好的理解行为,最好自己创建一个行为。这里实现一个为任意元素提供使用鼠标在Canvas面板上拖动元素的功能。对于单个元素实现该功能的基本步骤是非常简单的——代码监听鼠标事件并修改设置相应的Canvas坐标的附加属性。但通过付出更多一点的努力,可将该代码转换为可重用的行为,该行为可为Canvas面板上的所有元素提供拖动支持。

    在任何行为中,第一步是覆盖OnAttached() 和 OnDetaching() 方法。当调用OnAttached() 方法时,可通过 AssociatedObject 属性访问放置行为的元素,并可关联事件处理程序。当调用 OnDetaching() 方法时,移除事件处理程序。

    1. public class DragInCanvasBehavior : Behavior<UIElement>
    2. {
    3. private Canvas? canvas;
    4. protected override void OnAttached()
    5. {
    6. base.OnAttached();
    7. // Hook up event handlers.
    8. this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
    9. this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    10. this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
    11. }
    12. protected override void OnDetaching()
    13. {
    14. base.OnDetaching();
    15. // Detach event handlers.
    16. this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
    17. this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    18. this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
    19. }
    20. // Keep track of when the element is being dragged.
    21. private bool isDragging = false;
    22. // When the element is clicked, record the exact position
    23. // where the click is made.
    24. private Point mouseOffset;
    25. private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    26. {
    27. // Find the canvas.
    28. if (canvas == null)
    29. canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas;
    30. // Dragging mode begins.
    31. isDragging = true;
    32. // Get the position of the click relative to the element
    33. // (so the top-left corner of the element is (0,0).
    34. mouseOffset = e.GetPosition(AssociatedObject);
    35. // Capture the mouse. This way you'll keep receiveing
    36. // the MouseMove event even if the user jerks the mouse
    37. // off the element.
    38. AssociatedObject.CaptureMouse();
    39. }
    40. private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    41. {
    42. if (isDragging)
    43. {
    44. // Get the position of the element relative to the Canvas.
    45. Point point = e.GetPosition(canvas);
    46. // Move the element.
    47. AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
    48. AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
    49. }
    50. }
    51. private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    52. {
    53. if (isDragging)
    54. {
    55. AssociatedObject.ReleaseMouseCapture();
    56. isDragging = false;
    57. }
    58. }
    59. }

    DragInCanvasBehavior 类在OnAttached()方法中为 MouseLeftButtonDown、MouseMove、MouseLeftButtonUp 事件添加事件处理程序,在OnDetaching()方法中移除这些事件处理程序。这里通过AssociatedObject 获得此行为附加到的对象,并通过可视化树查找附加对象的父元素来获得Canvas对象。

    使用行为

    为使用该行为,只需要使用 Interaction.Behaviors 附加属性在Canvas面板中添加任意元素。这里添加了三个形状(其中的一个Ellipse没有添加行为)、一个TextBlock、一个Button。测试下来发现除了没有添加行为的Ellipse无法拖动外,Button也无法拖动。这是因为Button中的MouseLeftButtonDown和MouseLeftButtonUp事件无法触发,Button本身响应Click事件,相当于将MouseLeftButtonDown和MouseLeftButtonUp事件抑制了,转换成了Click事件。

    1. "200">
    2. "10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60">
    3. "10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
    4. "80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
    5. "TestBlock" Canvas.Left="280" Canvas.Top="170" Width="100" Height="30">

    使用行为触发器

    行为触发器通常是继承自 System.Windows.Interactivity.TriggerAction类 或是 System.Windows.Interactivity.TargetedTriggerAction类。主要实现Invoke函数用来响应触发器事件。

    1. public class FadeOutAction : TargetedTriggerAction<UIElement>
    2. {
    3. // The default fade out time is 2 seconds.
    4. public static readonly DependencyProperty DurationProperty =
    5. DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeOutAction), new PropertyMetadata(TimeSpan.FromSeconds(2)));
    6. public TimeSpan Duration
    7. {
    8. get { return (TimeSpan)GetValue(FadeOutAction.DurationProperty); }
    9. set { SetValue(FadeOutAction.DurationProperty, value); }
    10. }
    11. private Storyboard fadeStoryboard = new Storyboard();
    12. private DoubleAnimation fadeAnimation = new DoubleAnimation();
    13. public FadeOutAction()
    14. {
    15. fadeStoryboard.Children.Add(fadeAnimation);
    16. }
    17. protected override void Invoke(object args)
    18. {
    19. // Make sure the storyboard isn't already running.
    20. fadeStoryboard.Stop();
    21. // Set up the storyboard.
    22. Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity"));
    23. Storyboard.SetTarget(fadeAnimation, this.Target);
    24. // Set up the animation.
    25. // It's important to do this at the last possible instant,
    26. // in case the value for the Duration property changes.
    27. fadeAnimation.To = 0;
    28. fadeAnimation.Duration = Duration;
    29. fadeStoryboard.Begin();
    30. }
    31. }
    32. public class FadeInAction : TargetedTriggerAction<UIElement>
    33. {
    34. // The default fade in is 0.5 seconds.
    35. public static readonly DependencyProperty DurationProperty =
    36. DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeInAction), new PropertyMetadata(TimeSpan.FromSeconds(0.5)));
    37. public TimeSpan Duration
    38. {
    39. get { return (TimeSpan)GetValue(FadeInAction.DurationProperty); }
    40. set { SetValue(FadeInAction.DurationProperty, value); }
    41. }
    42. private Storyboard fadeStoryboard = new Storyboard();
    43. private DoubleAnimation fadeAnimation = new DoubleAnimation();
    44. public FadeInAction()
    45. {
    46. fadeStoryboard.Children.Add(fadeAnimation);
    47. }
    48. protected override void Invoke(object args)
    49. {
    50. // Make sure the storyboard isn't already running.
    51. fadeStoryboard.Stop();
    52. // Set up the storyboard.
    53. Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity"));
    54. Storyboard.SetTarget(fadeAnimation, this.Target);
    55. // Set up the animation.
    56. fadeAnimation.To = 1;
    57. fadeAnimation.Duration = Duration;
    58. fadeStoryboard.Begin();
    59. }
    60. }

     使用行为触发器则需要使用 Interaction.Triggers 附加属性,在其中添加触发器:

    1. "Horizontal" Margin="3,15">
    2. "Click">
    3. "border" />
    4. "Click">
    5. "border" />
    6. "border" Background="Orange" BorderBrush="Black" BorderThickness="1" Margin="3,0" Opacity="0.5">
    7. "5" FontSize="17" TextWrapping="Wrap" Text="I'm the target of the FadeOutAction and FadeInAction.">

    完整的测试代码如下:

    MainWindow.xaml

    1. "TestBehavior.MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:TestBehavior"
    7. xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    8. mc:Ignorable="d"
    9. Title="MainWindow" Height="500" Width="800">
    10. "200">
    11. "10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60">
    12. "10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
    13. "80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
    14. "TestBlock" Canvas.Left="280" Canvas.Top="170" Width="100" Height="30">
    15. "Horizontal" Margin="3,15">
    16. "Click">
    17. "border" />
    18. "Click">
    19. "border" />
    20. "border" Background="Orange" BorderBrush="Black" BorderThickness="1" Margin="3,0" Opacity="0.5">
    21. "5" FontSize="17" TextWrapping="Wrap" Text="I'm the target of the FadeOutAction and FadeInAction.">
    22. "Click">
    23. "test.mp3" />

    MainWindow.xaml.cs

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. using System.Windows;
    7. using System.Windows.Controls;
    8. using System.Windows.Controls.Primitives;
    9. using System.Windows.Data;
    10. using System.Windows.Documents;
    11. using System.Windows.Input;
    12. using System.Windows.Interactivity;
    13. using System.Windows.Media;
    14. using System.Windows.Media.Animation;
    15. using System.Windows.Media.Imaging;
    16. using System.Windows.Navigation;
    17. using System.Windows.Shapes;
    18. namespace TestBehavior;
    19. public class DragInCanvasBehavior : Behavior<UIElement>
    20. {
    21. private Canvas? canvas;
    22. protected override void OnAttached()
    23. {
    24. base.OnAttached();
    25. // Hook up event handlers.
    26. this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
    27. this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    28. this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
    29. }
    30. protected override void OnDetaching()
    31. {
    32. base.OnDetaching();
    33. // Detach event handlers.
    34. this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
    35. this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    36. this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
    37. }
    38. // Keep track of when the element is being dragged.
    39. private bool isDragging = false;
    40. // When the element is clicked, record the exact position
    41. // where the click is made.
    42. private Point mouseOffset;
    43. private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    44. {
    45. // Find the canvas.
    46. if (canvas == null)
    47. canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas;
    48. // Dragging mode begins.
    49. isDragging = true;
    50. // Get the position of the click relative to the element
    51. // (so the top-left corner of the element is (0,0).
    52. mouseOffset = e.GetPosition(AssociatedObject);
    53. // Capture the mouse. This way you'll keep receiveing
    54. // the MouseMove event even if the user jerks the mouse
    55. // off the element.
    56. AssociatedObject.CaptureMouse();
    57. }
    58. private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    59. {
    60. if (isDragging)
    61. {
    62. // Get the position of the element relative to the Canvas.
    63. Point point = e.GetPosition(canvas);
    64. // Move the element.
    65. AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
    66. AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
    67. }
    68. }
    69. private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    70. {
    71. if (isDragging)
    72. {
    73. AssociatedObject.ReleaseMouseCapture();
    74. isDragging = false;
    75. }
    76. }
    77. }
    78. public class FadeOutAction : TargetedTriggerAction<UIElement>
    79. {
    80. // The default fade out time is 2 seconds.
    81. public static readonly DependencyProperty DurationProperty =
    82. DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeOutAction), new PropertyMetadata(TimeSpan.FromSeconds(2)));
    83. public TimeSpan Duration
    84. {
    85. get { return (TimeSpan)GetValue(FadeOutAction.DurationProperty); }
    86. set { SetValue(FadeOutAction.DurationProperty, value); }
    87. }
    88. private Storyboard fadeStoryboard = new Storyboard();
    89. private DoubleAnimation fadeAnimation = new DoubleAnimation();
    90. public FadeOutAction()
    91. {
    92. fadeStoryboard.Children.Add(fadeAnimation);
    93. }
    94. protected override void Invoke(object args)
    95. {
    96. // Make sure the storyboard isn't already running.
    97. fadeStoryboard.Stop();
    98. // Set up the storyboard.
    99. Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity"));
    100. Storyboard.SetTarget(fadeAnimation, this.Target);
    101. // Set up the animation.
    102. // It's important to do this at the last possible instant,
    103. // in case the value for the Duration property changes.
    104. fadeAnimation.To = 0;
    105. fadeAnimation.Duration = Duration;
    106. fadeStoryboard.Begin();
    107. }
    108. }
    109. public class FadeInAction : TargetedTriggerAction<UIElement>
    110. {
    111. // The default fade in is 0.5 seconds.
    112. public static readonly DependencyProperty DurationProperty =
    113. DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeInAction), new PropertyMetadata(TimeSpan.FromSeconds(0.5)));
    114. public TimeSpan Duration
    115. {
    116. get { return (TimeSpan)GetValue(FadeInAction.DurationProperty); }
    117. set { SetValue(FadeInAction.DurationProperty, value); }
    118. }
    119. private Storyboard fadeStoryboard = new Storyboard();
    120. private DoubleAnimation fadeAnimation = new DoubleAnimation();
    121. public FadeInAction()
    122. {
    123. fadeStoryboard.Children.Add(fadeAnimation);
    124. }
    125. protected override void Invoke(object args)
    126. {
    127. // Make sure the storyboard isn't already running.
    128. fadeStoryboard.Stop();
    129. // Set up the storyboard.
    130. Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity"));
    131. Storyboard.SetTarget(fadeAnimation, this.Target);
    132. // Set up the animation.
    133. fadeAnimation.To = 1;
    134. fadeAnimation.Duration = Duration;
    135. fadeStoryboard.Begin();
    136. }
    137. }
    138. [DefaultTrigger(typeof(ButtonBase), typeof(System.Windows.Interactivity.EventTrigger), new object[] { "Click" })]
    139. [DefaultTrigger(typeof(Shape), typeof(System.Windows.Interactivity.EventTrigger), new object[] { "MouseEnter" })]
    140. [DefaultTrigger(typeof(UIElement), typeof(System.Windows.Interactivity.EventTrigger), new object[] { "MouseLeftButtonDown" })]
    141. public class PlaySoundAction : TriggerAction<FrameworkElement>
    142. {
    143. public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Uri), typeof(PlaySoundAction), new PropertyMetadata(null));
    144. public Uri Source
    145. {
    146. get { return (Uri)GetValue(PlaySoundAction.SourceProperty); }
    147. set { SetValue(PlaySoundAction.SourceProperty, value); }
    148. }
    149. protected override void Invoke(object args)
    150. {
    151. // Find a place to insert the MediaElement.
    152. Panel? container = FindContainer();
    153. if (container != null)
    154. {
    155. // Create and configure the MediaElement.
    156. MediaElement media = new MediaElement();
    157. media.Source = this.Source;
    158. // Hook up handlers that will clean up when playback finishes.
    159. media.MediaEnded += delegate
    160. {
    161. container.Children.Remove(media);
    162. };
    163. media.MediaFailed += delegate
    164. {
    165. container.Children.Remove(media);
    166. };
    167. // Add the MediaElement and begin playback.
    168. container.Children.Add(media);
    169. }
    170. }
    171. private Panel? FindContainer()
    172. {
    173. FrameworkElement? element = this.AssociatedObject;
    174. // Search for some sort of panel where the MediaElement can be inserted.
    175. while (element != null)
    176. {
    177. if (element is Panel)
    178. return (Panel)element;
    179. element = VisualTreeHelper.GetParent(element) as FrameworkElement;
    180. }
    181. return null;
    182. }
    183. }
    184. ///
    185. /// Interaction logic for MainWindow.xaml
    186. ///
    187. public partial class MainWindow : Window
    188. {
    189. public MainWindow()
    190. {
    191. InitializeComponent();
    192. }
    193. }

  • 相关阅读:
    【Linux】日志分析与管理
    SAP ABAP OBJECTS_NOT_CHARLIKE
    输入输出及中断技术——微机第六章学习笔记
    学生管理系统(半成品)
    JAVA 集合框架(二) List集合详解和常用方法
    【ChatGPT】无需代理使用ChatGPT
    JVM启动参数大全
    2024年度CCF-阿里云瑶池科研基金正式发布
    ORACLE修改service_name服务名
    如何用 DDD 给 DDD 建模,破解 DDD 的魔法?
  • 原文地址:https://blog.csdn.net/xunmeng2002/article/details/132964873