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

一、什么是装饰器?

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

二、装饰器的使用场景

  • 为现有的元素添加额外的装饰,如为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

 

WPF-利用装饰器实现控件的自由拖动的更多相关文章

  1. 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程

    反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑)   背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...

  2. 基于 WPF 平台的 ActiveReports Viewer控件

    ActiveReports 报表控件致力于为组织和个人提供最出色的报表解决方案,多年来ActiveReports已经提供了 Windows Forms.Web.Silverlight和Flash平台的 ...

  3. WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)

    Windows Community Toolkit 再次更新到 5.0.以前可以在 WPF 中使用有限的 UWP 控件,而现在有了 WindowsXamlHost,则可以使用更多 UWP 原生控件了. ...

  4. WPF自定义LED风格数字显示控件

    原文:WPF自定义LED风格数字显示控件 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/de ...

  5. flask中的endpoint、自定义转化器、与djnago中session区别、利用装饰器实现登录认证

    flask路由中的endpoint 与自定义转化器 ''' endpoint主要用于 反向解析, 例如:login函数中配的路由是/login,其中endpoint='lg' 则在其他函数,可以用 u ...

  6. 利用foreach对页面控件的遍历 及三目运算符的使用

    1.利用foreach对页面控件的遍历 及三目运算符的使用 利用div将一组CheckBox放在一起用于遍历 <body> <form id="form1" ru ...

  7. WPF自定义控件(一)の控件分类

    一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...

  8. WPF不同线程之间的控件的访问

    原文:WPF不同线程之间的控件的访问 WPF不同线程之间的控件是不同访问的,为了能够访问其他线程之间的控件,需要用Dispatcher.Invoke执行一个新的活动即可. 例如: public voi ...

  9. WPF 动画:同为控件不同命 - 简书

    原文:WPF 动画:同为控件不同命 - 简书 1. 及格与优秀 读大学的时候,有一门课的作业是用 PPT 展示. 但是我们很多同学都把 PPT 当做 Word 来用,就单纯地往里面堆文字. 大家都单纯 ...

  10. WPF 控件库——可拖动选项卡的TabControl

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

随机推荐

  1. 分类树,我从2s优化到0.1s

    前言 Java技术突击网站:http://www.susan.net.cn 分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中. 但就是这样一个简单的分类树查询功能,我们却优化了5次. ...

  2. 全网最详细解读《GIN-HOW POWERFUL ARE GRAPH NEURAL NETWORKS》!!!

    Abstract + Introduction GNNs 大都遵循一个递归邻居聚合的方法,经过 k 次迭代聚合,一个节点所表征的特征向量能够捕捉到距离其 k-hop 邻域的邻居节点的特征,然后还可以通 ...

  3. vscode运行java输出至指定文件夹

    一.前言 最近呢,需要用vscode编写一点小的java程序,也就是单java文件,但是呢,我发现coderunner运行java,一个java文件编译出一个class文件,这也太乱了!不符合我简约的 ...

  4. Qt数据可视化项目

    一.创建项目(步骤按常规来即可) 二.ui设计界面 控件的使用 默认视角:Label标签和horizontalSlider标签 水平旋转:Label标签和horizontalSlider标签 垂直旋转 ...

  5. Tomcat处理http请求之源码分析

    本文将从请求获取与包装处理.请求传递给Container.Container处理请求流程,这3部分来讲述一次http穿梭之旅. 1 请求包装处理 tomcat组件Connector在启动的时候会监听端 ...

  6. 「高频面试题」Redis的持久化方式有哪些?

    RDB RDB(Redis DataBase)持久化是把当前Redis中全部数据生成快照保存在硬盘上.RDB持久化可以手动触发,也可以自动触发.save和bgsave命令都可以手动触发RDB持久化.除 ...

  7. drf之频率类源码

    1 频率类 写一个类,继承SimpleRateThrottle,重写get_cache_key,返回[ip,用户id]什么,就以什么做限制,编写类属性 scope = 字符串,在配置文件中配置 'DE ...

  8. Taurus.mvc .Net Core 微服务开源框架发布V3.1.7:让分布式应用更高效。

    前言: 自首个带微服务版本的框架发布:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 已经过去快1年了,在这近一年的时间里,版本经历了N个版本的迭代. ...

  9. ISIS 综合实验;BGP 实验

    目录 ISIS 综合实验 实验拓扑 实验需求 实验步骤 1.配置相应接口IP地址及环回口地址 2.配置 IS-IS,要求全网互通,R8的Loop X口暂不宣告 3. R1和R3直连,要求 R3 成为 ...

  10. 使用CNI网络插件(calico)实现docker容器跨主机互联

    目录 一.系统环境 二.前言 三.CNI网络插件简介 四.常见的几种CNI网络插件对比 五.Calico网络之间是如何通信的 六.配置calico让物理机A上的docker容器c1可以访问物理机B上的 ...