WPF-利用装饰器实现控件的自由拖动
在项目中经常会遇到类似如下要求的需求,创建允许自由拖动的控件,这样的需求可以使用WPF的装饰器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;
}
}
WPF-利用装饰器实现控件的自由拖动的更多相关文章
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) 背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...
- 基于 WPF 平台的 ActiveReports Viewer控件
ActiveReports 报表控件致力于为组织和个人提供最出色的报表解决方案,多年来ActiveReports已经提供了 Windows Forms.Web.Silverlight和Flash平台的 ...
- WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
Windows Community Toolkit 再次更新到 5.0.以前可以在 WPF 中使用有限的 UWP 控件,而现在有了 WindowsXamlHost,则可以使用更多 UWP 原生控件了. ...
- WPF自定义LED风格数字显示控件
原文:WPF自定义LED风格数字显示控件 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/de ...
- flask中的endpoint、自定义转化器、与djnago中session区别、利用装饰器实现登录认证
flask路由中的endpoint 与自定义转化器 ''' endpoint主要用于 反向解析, 例如:login函数中配的路由是/login,其中endpoint='lg' 则在其他函数,可以用 u ...
- 利用foreach对页面控件的遍历 及三目运算符的使用
1.利用foreach对页面控件的遍历 及三目运算符的使用 利用div将一组CheckBox放在一起用于遍历 <body> <form id="form1" ru ...
- WPF自定义控件(一)の控件分类
一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...
- WPF不同线程之间的控件的访问
原文:WPF不同线程之间的控件的访问 WPF不同线程之间的控件是不同访问的,为了能够访问其他线程之间的控件,需要用Dispatcher.Invoke执行一个新的活动即可. 例如: public voi ...
- WPF 动画:同为控件不同命 - 简书
原文:WPF 动画:同为控件不同命 - 简书 1. 及格与优秀 读大学的时候,有一门课的作业是用 PPT 展示. 但是我们很多同学都把 PPT 当做 Word 来用,就单纯地往里面堆文字. 大家都单纯 ...
- WPF 控件库——可拖动选项卡的TabControl
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
随机推荐
- IBM小型机 - 登录Web控制台
前言: IBM 小型机没有VGA或者HDMI接口,只能通过web或者串口的方式,配置和查看设备的硬件信息: 我们可以通过两种方式获取小型机的IP,并通过浏览器访问. 操作步骤: 1.服务器接通电源,直 ...
- operation not supported on selected printer
operation not supported on selected printer 版本原因 解决办法, 每次打印前先选择一次打印机属性,弹窗点确认还是取消都行,最后返回再点打印即可
- 源码解析:数据批量导入bukl_crete()原理
在Django中需要向数据库中插入多条数据(list).使用如下方法,每次save()的时候都会访问一次数据库.导致性能问题: for i in resultlist: p = Account(nam ...
- Kubernetes GoRoutineMap工具包代码详解
1.概述 GoRoutineMap 定义了一种类型,可以运行具有名称的 goroutine 并跟踪它们的状态.它防止创建具有相同名称的多个goroutine,并且在上一个具有该名称的 goroutin ...
- 让你的 conda “回滚”到以前版本的环境
我现在使用 Anaconda 作为我的主要 Python 发行版,同样,我们公司也将它用于所有开发人员机器以及他们的服务器.然而,前几天我在浏览一些论坛技术文章时遇到了一个我以前从未知道的 conda ...
- 在 Linux 上给用户赋予指定目录的读写权限
在 Linux 上指定目录的读写权限赋予用户,有两种方法可以实现这个目标:第一种是使用 ACL (访问控制列表),第二种是创建用户组来管理文件权限,下面会一一介绍.为了完成这个教程,我们将使用以下设置 ...
- 从0搭建Vue3组件库(九):VitePress 搭建部署组件库文档
VitePress 搭建组件库文档 当我们组件库完成的时候,一个详细的使用文档是必不可少的.本篇文章将介绍如何使用 VitePress 快速搭建一个组件库文档站点并部署到GitHub上 安装 首先新建 ...
- 传统软件如何SaaS化改造,10个问答带你掌握最优解
摘要:如果您所在企业希望实行SaaS化改造,可访问了解华为云开发者技术团队的SaaS支持计划. 本文分享自华为云社区<[云享问答]第1期:传统软件如何SaaS化改造,10个问答带你掌握最优解!& ...
- 记一次 .NET 某药材管理系统 卡死分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他们在查询报表的时候发现程序的稳定性会受到影响,但服务器的内存,CPU都是正常的,让我帮忙看下怎么回事,问了下程序的稳定性指的是什么?指的是卡死,那既 ...
- 前端热力图组件heatMapGD中国地图 中国热力地图 广东省热力地图 广东省地图 地市选择
快速实现前端中国热力地图 广东省热力地图 广东省地图, 请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12407 # china 广东省热力 ...