WPF-利用装饰器实现空间的自由拖动

  • WPF-利用装饰器实现空间的自由拖动已关闭评论
  • 130 次浏览
  • A+
所属分类:.NET技术
摘要

  在项目中经常会遇到类似如下要求的需求,创建允许自由拖动的控件,这样的需求可以使用WPF的装饰器Adorner来实现。

  在项目中经常会遇到类似如下要求的需求,创建允许自由拖动的控件,这样的需求可以使用WPF的装饰器Adorner来实现。

WPF-利用装饰器实现空间的自由拖动

 

一、什么是装饰器?

装饰器是一种特殊类型的FrameworkElement,装饰器始终呈现在被装饰元素的顶部,用于向用户提供可视化提示。装饰器可以在不改变原有控件结构的基础上,将功能点增加到元素中或元素上提供视觉效果等,如WPF的光标效果,焦点效果等都是通过装饰器来实现的。
装饰器是一个始终位于装饰元素或装饰元素集合顶部的呈现图层,其呈现独立与它所绑定的UIElement,WPF中的装饰器是在一个单独的曾AnornerLayer上进行绘制的,该层位于普通控件元素之上,而且允许多个AdornerLayer进行叠加,当加入AdornerLayer层后,Adorner会默认使用其所装饰元素的左上角作为原点进行定位。
  • Adorner 是一个抽象类,所有装饰器的实现都需要继承此类,比如ThumbBorderAdorner
  • AdornerLayer 一个类,表示一个或多个装饰元素的装饰器呈现层
    • 利用AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl)函数,来获取指定控件是否有装饰器布局层
    • 利用Adorner[] adorners = adornerLayer.GetAdorners(userControl);,来查看当前控件的装饰器个数
  • AdornerDecorator 一个类,为可视化树中的子元素提供AdornerLayer
WPF-利用装饰器实现空间的自由拖动

二、装饰器的使用场景

  • 为现有的元素添加额外的装饰,如为Border添加8个装饰矩形
 

三、如何创建自定义的装饰器?

  • 创建一个类,继承自Adorner类
  • 重写此类中需要的函数
    • OnRender(DrawingContext drawingContext) 在派生类中重写,参与由布局系统控制的呈现操作,调用此方法时,不直接使用此元素的呈现指令,而是将其保留供布局和绘制在以后异步使用,可以使用drawingContext 来绘制各种形状以及图形。
    • ArrangeOverride() 为FrameworkElement派生类定位子元素并确定大小,在其中调用Arrange()函数,来定位子元素
    • GetVisualChild() //获取第几个Thumb控件,在构造时使用
  • 简单的装饰可以重写OnRender()函数,在其中绘制所需要的装饰,参照BorderAdorner
  • 复杂一些的如需要可以定义VisualCollection的集合来存放装饰器,重写ArrangeOverride和GetVisualChild函数来实现,参照ThumbBorderAdorner
 

四、给控件使用自定义的Adorner

  • 添加Adorner
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl); if (adornerLayer != null) {     Adorner[] adorners = adornerLayer.GetAdorners(userControl);     if (adorners == null || adorners.Count() == 0)     {         adornerLayer.Add(new ThumbBorderAdorner(userControl)         {             DragCompletedAction = ThumbBorderAdornerDragCompletedActionFunc         });     } }

 

  • 移除 Adorner
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl); if (adornerLayer != null) {     Adorner[] adorners = adornerLayer.GetAdorners(userControl);     if (adorners != null && adorners.Count() > 0)         adornerLayer.Remove(adorners[0]); }

 

五、处理拖拽Thumb时,导致控件范围超出父级容器的情况

  • 即在添加装饰器的控件中添加UserControl_SizeChanged()来处理控件大小和和未知变化
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) {     int tempMargin = 1;  //MarkRectUserControl控件始终在父级容器的1个像素以内      double left = Canvas.GetLeft(this);     if (left < tempMargin)     {         left = tempMargin;         Canvas.SetLeft(this, left);     }      double top = Canvas.GetTop(this);     if (top < tempMargin)     {         top = tempMargin;         Canvas.SetTop(this, top);     }      if (left + this.ActualWidth > canvasParent.ActualWidth - tempMargin)         this.Width = canvasParent.ActualWidth - tempMargin - left;      if (top + this.ActualHeight > canvasParent.ActualHeight - tempMargin)         this.Height = canvasParent.ActualHeight - tempMargin - top;  }

 

六、测试使用

 

<UserControl x:Class="BlogDemo.Views.AdornerTestUserControl"              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"               xmlns:local="clr-namespace:BlogDemo.Views"              mc:Ignorable="d"               Background="White"              d:DesignHeight="450" d:DesignWidth="800"              FontSize="20"              Foreground="Blue"              Loaded="UserControl_Loaded"              >     <Grid>         <Grid.RowDefinitions>             <RowDefinition Height="Auto"/>             <RowDefinition Height="*"/>         </Grid.RowDefinitions>                  <TextBlock Text="请拖拽Thumb控件来改变,控件大小。" HorizontalAlignment="Center" Margin="10"/>          <Canvas x:Name="Canvas_Main" Grid.Row="1">             <Border x:Name="Border_Text" Canvas.Left="200" Canvas.Top="100" Width="200" Height="100" BorderThickness="2" BorderBrush="Green" SizeChanged="Border_Text_SizeChanged"/>         </Canvas>     </Grid> </UserControl>

/// <summary> /// AdornerTestUserControl.xaml 的交互逻辑 /// </summary> public partial class AdornerTestUserControl : UserControl {     public AdornerTestUserControl()     {         InitializeComponent();     }      private void UserControl_Loaded(object sender, RoutedEventArgs e)     {         AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(Border_Text);         if (adornerLayer != null)         {             Adorner[] adorners = adornerLayer.GetAdorners(Border_Text);             if (adorners == null || adorners.Count() == 0)             {                 adornerLayer.Add(new ThumbBorderAdorner(Border_Text));             }         }     }      private void Border_Text_SizeChanged(object sender, SizeChangedEventArgs e)     {         int tempMargin = 100;           double left = Canvas.GetLeft(Border_Text);         if (left < tempMargin)         {             left = tempMargin;             Canvas.SetLeft(Border_Text, left);         }          double top = Canvas.GetTop(Border_Text);         if (top < tempMargin)         {             top = tempMargin;             Canvas.SetTop(Border_Text, top);         }          if (Border_Text.ActualWidth < tempMargin)             Border_Text.Width = tempMargin;          if (Border_Text.ActualHeight < tempMargin)             Border_Text.Height = tempMargin;          if (left + Border_Text.ActualWidth > Canvas_Main.ActualWidth - tempMargin)             Border_Text.Width = Canvas_Main.ActualWidth - tempMargin - left;          if (top + Border_Text.ActualHeight > Canvas_Main.ActualHeight - tempMargin)             Border_Text.Height = Canvas_Main.ActualHeight - tempMargin - top;     } }

 

 

源码地址:https://gitee.com/LiuShuiRuoBing/code_blog