真・WPF 按钮拖动和调整大小

真・WPF 按钮拖动和调整大小

独立观察员 2020 年 8 月 29 日

手头有个 Winform 程序,是使用动态生成按钮,然后拖动、调整大小,以此来记录一些坐标数据,最后保存坐标数据的。

在数据量(按钮数量)比较小的时候是使用得挺愉快的,但是,当按钮数上去之后,比如达到四五百个按钮,那就比较痛苦了。具体来说就是,无论是移动窗口,还是拖动滚动条,或者是最小化窗口再还原,都会引起界面重绘,表现为按钮一个接一个地出现。

经过实测,与电脑的性能和 GPU 都没有关系,网上针对 Winform 这个问题的解决方案,比如开启双缓冲等,都大致尝试了,并无任何起色,反而可能更糟。所以就像网友所说,这个要么不要在同一个界面上放置太多控件;要么使用 WPF,毕竟 WPF 采用的是 DirectX 作为底层绘图引擎,而 Winform 则采用传统的 GDI 。由于业务需求,不让在界面上放置过多控件的方案不太可行,或者说暂未想到有什么变通的办法,所以决定改版为 WPF 试试。

 

经过几天的改造,原 Winform 版软件的一小部分功能已改版为 WPF 版,而且成果喜人,同样的按钮数量,现在无论怎样折腾,这几百个按钮就如同钉在了界面上一样,不再能看到他们载入的过程了。在这个改造的过程中,我是将 Winform 版软件中关于按钮拖动和调整大小的代码改造为 WPF 版的,听上去挺简单的,但是还是碰到了一些问题,比如 WPF 屏蔽了鼠标左键的一些事件,需要额外处理一下,还有的就是关于坐标定位的一些问题了,下面将给出一些关键代码,和大家相互交流学习一下。

 

首先,先上一道小菜,解决一下 WPF 按钮控件(Button)中文字自动换行的问题。

不对,还是先看看 Demo 的界面结构吧:

 

其它控件和布局就不说了(最后会给出 Demo 地址),关键的是中间这个 ScrollViewer 包裹的 Canvas,我们生成的按钮都是在这个 Canvas 上的,拖动和调整大小也是。Winform 的布局是依赖于坐标的,WPF 的布局控件则基本是不使用坐标定位的,甚至都不推荐指定大小,而只有 Canvas 布局控件保留了以坐标定位的模式,正好适合我们的需求(之前 Winform 版使用的是 Panel 控件)。

可以看到里面我还注释了一个 Button ,这个就是用来演示我们的 “小菜” 问题(按钮文字自动换行)的。我们先把注释放开,并且只保留其宽和高的设置:

 

可以看到当按钮宽度窄于文本内容时,文本内容并不能进行自动换行,且 Button 控件并没有相关属性进行设置。解决方法就是在按钮中添加 TextBlock 控件,然后设置其 TextWrapping 属性,当然,这里我们不直接这样写,而是使用内容模板:

<Button Width="38" Height="75" ContentTemplate="{DynamicResource DataTemplateButtonWrap}">1A005</Button>

 

这个模板的资源放在 App.xaml 中:

<Application.Resources><DataTemplate x:Key="DataTemplateButtonWrap" DataType="Button"><Grid><TextBlock TextWrapping="Wrap" Text="{TemplateBinding Content}"></TextBlock></Grid></DataTemplate>
</Application.Resources>

 

TextBlock 中使用了 TemplateBinding 将 Button 的 Content “绑架” 到了自己的 Text 中,哈哈。看看效果:

 

至于后台动态绑定资源则是使用 SetResourceReference 方法,后面代码里也有体现。

 

好了,小菜吃完了,开始吃主菜吧:

#region 成员private Control _control;
private int _btnNum = 0;#endregion/// <summary>
/// 设置控件在 Canvas 容器中的位置;
/// </summary>
private void SetControlLocation(Control control, Point point)
{Canvas.SetLeft(control, point.X);Canvas.SetTop(control, point.Y);
}/// <summary>
/// 添加按钮
/// </summary>
private void AddBtnHandler()
{string btnContent = GetBtnContent();Button btn = new Button{Name = "btn" + btnContent,Content = "btn" + btnContent,Width = 80,Height = 20,};_control = btn;AddContorlToCanvas(_control);SetControlLocation(_control, new Point(163, 55));
}/// <summary>
/// 添加控件到界面;
/// </summary>
/// <param name="control"></param>
private void AddContorlToCanvas(Control control)
{control.MouseDown += MyMouseDown;control.MouseLeave += MyMouseLeave;//_control.MouseMove += MyMouseMove;control.KeyDown += MyKeyDown;// 解决鼠标左键无法触发 MouseDown 的问题;control.AddHandler(Button.MouseLeftButtonDownEvent, new MouseButtonEventHandler(MyMouseDown), true);control.AddHandler(Button.MouseMoveEvent, new MouseEventHandler(MyMouseMove), true);CanvasMain.Children.Add(control);if (control is Button){// 模板中设置按钮文字换行 (模板资源在 App.xaml 中);control.SetResourceReference(ContentTemplateProperty, "DataTemplateButtonWrap");_btnNum++;}
}/// <summary>
/// 生成按钮内容
/// </summary>
/// <returns></returns>
private string GetBtnContent()
{return (_btnNum + 1).ToString().PadLeft(3, '0');
}/// <summary>
/// 删除按钮
/// </summary>
private void DelBtnHandler()
{CanvasMain.Children.Remove(_control);
}

 

上面代码是对按钮生成、添加到界面的一些操作逻辑,每个方法都有注释,具体的大家自己看看,这里就不在赘述了。其中 添加控件到界面 的方法 AddContorlToCanvas 中,给控件(本文指的是按钮)添加了 MouseDown、MouseLeave、MouseMove、KeyDown 等鼠标键盘事件,然后开头说过,WPF 屏蔽了 Button 的鼠标左键的一些事件,所以需要使用 AddHandler 进行处理。

 

下面来看看主菜中的精华:

#region 实现窗体内的控件拖动const int Band = 5;
const int BtnMinWidth = 10;
const int BtnMinHeight = 10;
private EnumMousePointPosition _enumMousePointPosition;
private Point _point; // 记录鼠标上次位置;#region btn 按钮拖动/// <summary>
/// 鼠标按下
/// </summary>
private void MyMouseDown(object sender, MouseEventArgs e)
{// 选择当前的按钮Button button = (Button)sender;_control = button;//Point point = e.GetPosition(CanvasMain);// 左键点击按钮后可按 WSAD 进行上下左右移动;if (e.LeftButton == MouseButtonState.Pressed){button.KeyDown += new KeyEventHandler(MyKeyDown);}double left = Canvas.GetLeft(_control);double top = Canvas.GetTop(_control);// 右键点击按钮可向选定方向生成新按钮;if (e.RightButton == MouseButtonState.Pressed){Button btn = new Button{Name = "btn" + GetBtnContent(),Content = GetStrEndNumAddOne(button.Content.ToString())};CheckRepeat(btn.Content.ToString());btn.Width = _control.Width;btn.Height = _control.Height;if (rbUpper.IsChecked == true)// 上{int h = txtUpper.Text.Trim() == "" ? 0 : Convert.ToInt32(txtUpper.Text.Trim());SetControlLocation(btn, new Point(left, top - _control.Height - h));}if (rbLower.IsChecked == true)// 下{int h = txtLower.Text.Trim() == "" ? 0 : Convert.ToInt32(txtLower.Text.Trim());SetControlLocation(btn, new Point(left, top + _control.Height + h));}if (rbLeft.IsChecked == true)// 左{int w = txtLeft.Text.Trim() == "" ? 0 : Convert.ToInt32(txtLeft.Text.Trim());SetControlLocation(btn, new Point(left - _control.Width - w, top));}if (rbRight.IsChecked == true)// 右{int w = txtRight.Text.Trim() == "" ? 0 : Convert.ToInt32(txtRight.Text.Trim());SetControlLocation(btn, new Point(left + _control.Width + w, top));}_control = btn;AddContorlToCanvas(_control);}//TODO 中键点击按钮可进行信息编辑;
}/// <summary>
/// 检查重复内容按钮
/// </summary>
/// <param name="content"></param>
private void CheckRepeat(string content)
{foreach (Control c in CanvasMain.Children){if (c is Button btn){if (content == btn.Content.ToString()){MessageBox.Show("出现重复按钮内容:" + content, "提示");return;}}}
}/// <summary>
/// 获取非纯数字字符串的数值加一结果;
/// </summary>
private string GetStrEndNumAddOne(string str)
{int numberIndex = 0; // 数字部分的起始位置;int charIndex = 0;foreach (char tempchar in str.ToCharArray()){charIndex++;if (!char.IsNumber(tempchar)){numberIndex = charIndex;}}string prefix = str.Substring(0, numberIndex);string numberStrOrigin = str.Remove(0, numberIndex);string numberStrTemp = "";if (numberStrOrigin != ""){numberStrTemp = (Convert.ToInt32(numberStrOrigin) + 1).ToString();}string result = "";if (numberStrOrigin.Length <= numberStrTemp.Length){result = prefix + numberStrTemp;}else{result = prefix + numberStrTemp.PadLeft(numberStrOrigin.Length, '0');}return result;
}/// <summary>
/// 鼠标离开
/// </summary>
private void MyMouseLeave(object sender, EventArgs e)
{_enumMousePointPosition = EnumMousePointPosition.MouseSizeNone;_control.Cursor = Cursors.Arrow;
}/// <summary>
/// 鼠标移动
/// </summary>
private void MyMouseMove(object sender, MouseEventArgs e)
{_control = (Control)sender;double left = Canvas.GetLeft(_control);double top = Canvas.GetTop(_control);Point point = e.GetPosition(CanvasMain);double height = _control.Height;double width = _control.Width;if (e.LeftButton == MouseButtonState.Pressed){switch (_enumMousePointPosition){case EnumMousePointPosition.MouseDrag:SetControlLocation(_control, new Point(left + point.X - _point.X, top + point.Y - _point.Y));break;case EnumMousePointPosition.MouseSizeBottom:height += point.Y - _point.Y;break;case EnumMousePointPosition.MouseSizeBottomRight:width += point.X - _point.X;height += point.Y - _point.Y;break;case EnumMousePointPosition.MouseSizeRight:width += point.X - _point.X;break;case EnumMousePointPosition.MouseSizeTop:SetControlLocation(_control, new Point(left, top + point.Y - _point.Y));height -= (point.Y - _point.Y);break;case EnumMousePointPosition.MouseSizeLeft:SetControlLocation(_control, new Point(left + point.X - _point.X, top));width -= (point.X - _point.X);break;case EnumMousePointPosition.MouseSizeBottomLeft:SetControlLocation(_control, new Point(left + point.X - _point.X, top));width -= (point.X - _point.X);height += point.Y - _point.Y;break;case EnumMousePointPosition.MouseSizeTopRight:SetControlLocation(_control, new Point(left, top + point.Y - _point.Y));width += (point.X - _point.X);height -= (point.Y - _point.Y);break;case EnumMousePointPosition.MouseSizeTopLeft:SetControlLocation(_control, new Point(left + point.X - _point.X, top + point.Y - _point.Y));width -= (point.X - _point.X);height -= (point.Y - _point.Y);break;default:break;}// 记录光标拖动到的当前点_point.X = point.X;_point.Y = point.Y;if (width < BtnMinWidth) width = BtnMinWidth;if (height < BtnMinHeight) height = BtnMinHeight;_control.Width = width;_control.Height = height;}else{_enumMousePointPosition = GetMousePointPosition(_control, e); //' 判断光标的位置状态switch (_enumMousePointPosition) //' 改变光标{case EnumMousePointPosition.MouseSizeNone:_control.Cursor = Cursors.Arrow;       //' 箭头break;case EnumMousePointPosition.MouseDrag:_control.Cursor = Cursors.SizeAll;     //' 四方向break;case EnumMousePointPosition.MouseSizeBottom:_control.Cursor = Cursors.SizeNS;      //' 南北break;case EnumMousePointPosition.MouseSizeTop:_control.Cursor = Cursors.SizeNS;      //' 南北break;case EnumMousePointPosition.MouseSizeLeft:_control.Cursor = Cursors.SizeWE;      //' 东西break;case EnumMousePointPosition.MouseSizeRight:_control.Cursor = Cursors.SizeWE;      //' 东西break;case EnumMousePointPosition.MouseSizeBottomLeft:_control.Cursor = Cursors.SizeNESW;    //' 东北到南西break;case EnumMousePointPosition.MouseSizeBottomRight:_control.Cursor = Cursors.SizeNWSE;    //' 东南到西北break;case EnumMousePointPosition.MouseSizeTopLeft:_control.Cursor = Cursors.SizeNWSE;    //' 东南到西北break;case EnumMousePointPosition.MouseSizeTopRight:_control.Cursor = Cursors.SizeNESW;    //' 东北到南西break;default:break;}}
}/// <summary>
/// 按键 WSAD (上下左右)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MyKeyDown(object sender, KeyEventArgs e)
{double left = Canvas.GetLeft(_control);double top = Canvas.GetTop(_control);switch (e.Key){case Key.W:// 上{SetControlLocation(_control, new Point(left, top-1));break;}case Key.S:// 下{SetControlLocation(_control, new Point(left, top+1));break;}case Key.A:// 左{SetControlLocation(_control, new Point(left-1, top));break;}case Key.D:// 右{SetControlLocation(_control, new Point(left+1, top));break;}}
}#endregion 按钮拖动#region 鼠标位置/// <summary>
/// 鼠标指针位置枚举;
/// </summary>
private enum EnumMousePointPosition
{/// <summary>/// 无/// </summary>MouseSizeNone = 0,/// <summary>/// 拉伸右边框/// </summary>MouseSizeRight = 1,/// <summary>/// 拉伸左边框/// </summary>MouseSizeLeft = 2,/// <summary>/// 拉伸下边框/// </summary>MouseSizeBottom = 3,/// <summary>/// 拉伸上边框/// </summary>MouseSizeTop = 4,/// <summary>/// 拉伸左上角/// </summary>MouseSizeTopLeft = 5,/// <summary>/// 拉伸右上角/// </summary>MouseSizeTopRight = 6,/// <summary>/// 拉伸左下角/// </summary>MouseSizeBottomLeft = 7,/// <summary>/// 拉伸右下角/// </summary>MouseSizeBottomRight = 8,      /// <summary>/// 鼠标拖动/// </summary>MouseDrag = 9
}/// <summary>
/// 获取鼠标指针位置;
/// </summary>
/// <param name="control"></param>
/// <param name="e"></param>
/// <returns></returns>
private EnumMousePointPosition GetMousePointPosition(Control control, MouseEventArgs e)
{Size size = control.RenderSize;Point point = e.GetPosition(control);Point pointCanvas = e.GetPosition(CanvasMain);_point.X = pointCanvas.X;_point.Y = pointCanvas.Y;if ((point.X >= -1 * Band) | (point.X <= size.Width) | (point.Y >= -1 * Band) | (point.Y <= size.Height)){if (point.X < Band){if (point.Y < Band){return EnumMousePointPosition.MouseSizeTopLeft;}else{if (point.Y > -1 * Band + size.Height){return EnumMousePointPosition.MouseSizeBottomLeft;}else{return EnumMousePointPosition.MouseSizeLeft;}}}else{if (point.X > -1 * Band + size.Width){if (point.Y < Band){return EnumMousePointPosition.MouseSizeTopRight;}else{if (point.Y > -1 * Band + size.Height){return EnumMousePointPosition.MouseSizeBottomRight;}else{return EnumMousePointPosition.MouseSizeRight;}}}else{if (point.Y < Band){return EnumMousePointPosition.MouseSizeTop;}else{if (point.Y > -1 * Band + size.Height){return EnumMousePointPosition.MouseSizeBottom;}else{return EnumMousePointPosition.MouseDrag;}}}}}else{return EnumMousePointPosition.MouseSizeNone;}
}#endregion 鼠标位置#endregion 实现窗体内的控件拖动

 

俗话说,Talk is cheap,show me the code。那么既然代码已给出,大家就直接批评指正呗,我也没什么说的了(主要是肚子饿了)。

给个效果图吧:

 

动图:

 

最后给出 Demo 地址:

https://gitee.com/dlgcy/Practice/tree/master/WPFPractice 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308008.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『软件工程11』结构化系统设计:解决软件“怎么做”问题

结构化系统设计——解决软件“做什么”问题一、设计的目标和任务1、目标2、任务3、开发阶段的信息流4、软件设计的重要性5、软件设计的技术观点和管理观点二、设计基础1、结构图&#xff08;体系结构图、模块结构图&#xff09;&#xff08;1&#xff09;分析结构图三者间的关系…

map容器中删除一个元素(value)

一:问题描述 我们想要删除map容器中&#xff0c;一个key值对应的vlaue 二&#xff1a;上码 #include<iostream> #include<map> #include<vector> using namespace std; int main(){map<int,int> m;for(int i 0; i < 4; i){m[i] i1;}//正常输出 …

.Net5发布在即,当心技术断层!

就在上周&#xff0c;.NET5的最后一个预览版&#xff0c;.NET5 Preview.8发布了&#xff0c;更新内容只有几个小bug的修复&#xff0c;已完成.NET5正式版的最后准备&#xff0c;.NET5即将正式面世&#xff01;时光荏苒&#xff0c;回首2016年发布.NET Core1.0至今&#xff0c;已…

『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?

软件测试——详解白盒测试基本概念&#xff0c;四种白盒测试方法一、白盒测试基本概念1、白盒测试的定义2、白盒测试的测试对象3、白盒测试的原则4、白盒测试的分类二、静态白盒测试1、代码检查法&#xff08;1&#xff09;代码审查的定义&#xff08;2&#xff09;代码审查的目…

leetcode76:最小覆盖字串(滑动窗口)

一&#xff1a;题目 二:思路 思路拿别人的&#xff0c;感觉写的很nice!! 渣渣杰只能膜拜大佬的了 1.滑动窗口的思想&#xff1a; left 指针和 ring 指针&#xff0c;保证两个指针之间的字符串包含所需要的全部字符。 2在保证 1 的前提下&#xff0c; 向右移动 left&#x…

Orleans 知多少 | Orleans 中文文档上线

Orleans 简介Orleans是一个跨平台框架&#xff0c;用于构建健壮&#xff0c;可扩展的分布式应用程序Orleans建立在.NET开发人员生产力的基础上&#xff0c;并将其带入了分布式应用程序的世界&#xff0c;例如云服务。Orleans可从单个本地服务器扩展到云中全局分布的高可用性应用…

『软件工程12』软件工程实践方法——软件测试

软件工程实践方法—— 软件测试一、软件测试概述1、软件测试的目的&#xff08;1&#xff09;从用户和开发者角度&#xff08;2&#xff09;Myers软件测试目的2、软件测试的原则3、软件测试的对象4、测试信息流5、测试与软件开发各阶段的关系二、软件测试用例1、黑盒测试概述2、…

leetcode59:螺旋矩阵||(思路+详解)

一:题目 二&#xff1a;思路 1.我们需要模拟数字的变化过程, 2.模拟填充的过程:(顺时针) 填充上行 从左到右 填充右行 从上到下 填充下行 从右向左 填充左行 从下到上 3.分析我们填充数字的过程&#xff0c;比如n3的时候 上行:1,2 右行:3,4 下行:5,6 左行:7,8 这么分析下来我们…

在香蕉派的树莓派系统上配置 Syncthing 自启动(暨 Linux 软件自启服务配置)

在香蕉派的树莓派系统上配置 Syncthing 自启动独立观察员 2020 年 1 月 19 日&#xff08;2020年8月30日 更新&#xff09;首先做个名词解释&#xff0c;” 香蕉派” 是国内一款山寨树莓派的硬件产品&#xff0c;” 树莓派系统” 指的是”raspberrypi”&#xff0c;而”Syncthi…

『软件工程13』浅谈面向对象方法,统一建模语言UML

浅谈面向对象方法UML一、UML的含义二、UML的主要内容1、UML的概念模型2、UML概念模型图例三、UML的基本构造块1、UML中的事物&#xff08;1&#xff09;UML中的四种事物&#xff08;2&#xff09;UML中各种事物的图示法2、UML中的四种关系&#xff08;1&#xff09;依赖&#x…

leetcode54:螺旋矩阵

一:题目 二&#xff1a;上码 class Solution { public:vector<int> spiralOrder(vector<vector<int>>& matrix) {vector<int> ans;int startx 0,starty 0;int n matrix.size();//求出行int m matrix[0].size();//求出列int loop m/2 < n/…

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选

vue的基本使用和高级特性&#xff0c;周边插件vuex和vue-router一、vue的使用1、vue-cli2、基本使用&#xff08;1&#xff09;模板&#xff08;插值&#xff0c;指令&#xff09;&#xff08;2&#xff09;computed和watch&#xff08;3&#xff09;class和style&#xff08;4…

数据库单表千万行 LIKE 搜索优化手记

我们经常在数据库中使用 LIKE 操作符来完成对数据的模糊搜索&#xff0c;LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。如果需要查找客户表中所有姓氏是“张”的数据&#xff0c;可以使用下面的 SQL 语句&#xff1a;SELECT * FROM Customer WHERE Name LIKE 张%如果需要…

Mybatis第一个程序

一:整体流程 这个流程可以根据官方文档进行编写 mybatis的官方文档 二:实现 1:创建一个表 CREATE DATABASE mybatis;CREATE TABLE user(id INT NOT NULL PRIMARY KEY,name VARCHAR(30) NOT NULL DEFAULT ,pwd VARCHAR(20) NOT NULL DEFAULT )ENGINE INNODB;INSERT I…

基于.NetCore3.1系列 —— 日志记录之初识Serilog

前言对内置日志系统的整体实现进行了介绍之后&#xff0c;可以通过使用内置记录器来实现日志的输出路径。而在实际项目开发中&#xff0c;使用第三方日志框架&#xff08;如&#xff1a;Log4Net、NLog、Loggr、Serilog、Sentry 等&#xff09;来记录也是非常多的。首先一般基础…

手把手教你剖析vue响应式原理,监听数据不再迷茫

Object.defineProperty实现vue响应式原理一、组件化基础1、“很久以前”的组件化&#xff08;1&#xff09;asp jsp php 时代&#xff08;2&#xff09;nodejs2、数据驱动视图&#xff08;MVVM&#xff0c;setState&#xff09;&#xff08;1&#xff09;数据驱动视图 - Vue MV…

leetcode:203. 移除链表元素(两种方法)

一:题目 二:上码 1:方法一&#xff1a;(虚拟一个首结点) class Solution { public:ListNode* removeElements(ListNode* head, int val) {//1.虚拟一个头结点 这样就不用单独处理了ListNode * virtuals new ListNode(0);//给其开辟个空间并且赋初值virtuals->next head…

面试中的网红虚拟DOM,你知多少呢?深入解读diff算法

深入浅出虚拟DOM和diff算法一、虚拟DOM&#xff08;Vitual DOM&#xff09;1、虚拟DOM&#xff08;Vitual DOM&#xff09;和diff的关系2、真实DOM的渲染过程3、虚拟DOM是什么&#xff1f;4、解决方案 - vdom&#xff08;1&#xff09;问题引出&#xff08;2&#xff09;vdom如…

Blazor带我重玩前端(六)

本文主要讨论Blazor事件内容&#xff0c;由于blazor事件部分很多&#xff0c;所以会分成上下两篇&#xff0c;本文为第二篇。双向绑定概述如图所示当点击单项绑定的时候&#xff0c;MyOnewayComponent里的属性值会发生变化&#xff0c;这种变化是单项的&#xff0c;仅仅只是本地…

leetcode707:设计链表(增删差)

一:题目 二:上码 class MyLinkedList { public://定义链表节点结构体struct LinkedNode {int val;LinkedNode* next;LinkedNode(int val):val(val), next(nullptr){}};// 初始化链表MyLinkedList() {node new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点&#xff0…