C# winform 自定义皮肤制作

最近要做个软件正在做技术准备,由于WINFORM生成的窗体很丑陋,一个好的软件除了功能性很重要外,UI的体验也是不容忽视的。习惯性的在网上搜素了下,换肤控件也有好几款,但是有些用起来不是很好用,好点的也要花很多银子哦,而且毕竟是别人写的,心里总不是个滋味,所以决定自己尝试着写写看,花了一个晚上终于做出来了个DEMO,貌似还不错,贴图如下(图片是直接是用的暴风影音的,寒自己一个。。)image

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

下面和大家分享下。

首先分析下皮肤的制作原理,我的理解是把整个窗体(去边框后)划分为9个区域(如果有更复杂的界面,可以划分更多),有图有真相:

1

然后准备皮肤素材,切图,我的切图如下:

image

 

 

 

接着可以开工了:

1.初始化图片资源变量

  protected int formMinX = 0;//最小化按钮的X坐标protected int formMaxX = 0;//最大化按钮的X坐标protected int formCloseX = 0;//关闭按钮的X坐标protected int formTitleMarginLeft = 0;//标题栏的左边界protected int formTitleMarginRight = 0;//标题栏的右边界Image imgTopLeft = (Image)Resources.topleft;//窗体顶部左上角图片Image imgTopRight = (Image)Resources.topright;//窗体顶部右上角图片Image imgTopMiddle = (Image)Resources.topstretch;//窗体顶部中间图片Image imgBottomLeft = (Image)Resources.bottomLeft;//窗体底部左下角图片Image imgBottonRight = (Image)Resources.bottomRight;//窗体底部右下角图片Image imgBottonmMiddle = (Image)Resources.bottomstretch;//窗体底部中间图片Image imgMiddleLeft = (Image)Resources.LeftDrag_Mid;//窗体中部左边框图片Image imgMiddleRight = (Image)Resources.RightDrag_Mid;//窗体中部右边框图片Image imgFormMin = (Image)Resources.skin_btn_min;//最小化按钮Image imgFormMax = (Image)Resources.skin_btn_max;//最大化按钮Image imgFormClose = (Image)Resources.skin_btn_close;//关闭按钮Image imgFormRestore = (Image)Resources.skin_btn_restore;//还原按钮

 

2.重写OnPaint事件。代码直接贴上来(比较简单,就是计算图片要绘制到窗体的坐标,然后把图片绘到窗体上)

        protected override void OnPaint(PaintEventArgs e){base.OnPaint(e);this.BackColor = Color.Black;//绘制皮肤Graphics g = e.Graphics;//绘制窗体顶部左上角图片g.DrawImage(imgTopLeft, 0, 0, imgTopLeft.Width, imgTopLeft.Height);int topRightX = e.ClipRectangle.Width - imgTopRight.Width;//绘制窗体顶部右上角图片g.DrawImage(imgTopRight, topRightX, 0, imgTopRight.Width, imgTopRight.Height);int topMiddleWidth= e.ClipRectangle.Width - (imgTopLeft.Width + imgTopRight.Width) + 4;//绘制窗体顶部中间图片(标题栏)formTitleMarginLeft = imgTopLeft.Width;formTitleMarginRight = topRightX;g.DrawImage(imgTopMiddle, imgTopLeft.Width, 0, topMiddleWidth, imgTopMiddle.Height);//绘制窗体底部左下角图片g.DrawImage(imgBottomLeft, 0, e.ClipRectangle.Height - imgBottomLeft.Height, imgBottomLeft.Width, imgBottomLeft.Height);//绘制窗体底部右下角图片g.DrawImage(imgBottonRight, e.ClipRectangle.Width - imgBottomLeft.Width, e.ClipRectangle.Height - imgBottonRight.Height, imgBottonRight.Width, imgBottonRight.Height);//绘制窗体底部中间图片g.DrawImage(imgBottonmMiddle, imgBottomLeft.Width, e.ClipRectangle.Height - imgBottonmMiddle.Height, e.ClipRectangle.Width - (imgBottomLeft.Width + imgBottomLeft.Width) + 4, imgBottonmMiddle.Height);//画左右边框g.DrawImage(imgMiddleLeft, 0, imgTopLeft.Height, imgMiddleLeft.Width, e.ClipRectangle.Height - (imgTopLeft.Height + imgBottomLeft.Height));g.DrawImage(imgMiddleRight, e.ClipRectangle.Width - imgMiddleRight.Width, imgTopRight.Height, imgMiddleRight.Width, e.ClipRectangle.Height - (imgTopLeft.Height + imgBottomLeft.Height));//画右上角按钮(最小化,最大化,关闭)formMinX = topRightX;g.DrawImage(imgFormMin, topRightX, 0, imgFormMin.Width, imgFormMin.Height);if (this.WindowState == FormWindowState.Maximized){imgFormMax = imgFormRestore;}elseimgFormMax = (Image)Resources.skin_btn_max;formMaxX = topRightX + imgFormMin.Width;g.DrawImage(imgFormMax, topRightX + imgFormMin.Width, 0, imgFormMax.Width, imgFormMax.Height);formCloseX = topRightX + imgFormMax.Width + imgFormMin.Width;g.DrawImage(imgFormClose, topRightX + imgFormMax.Width + imgFormMin.Width, 0, imgFormClose.Width, imgFormClose.Height);}

3.当窗体大小发生变化的时候同样要重绘,所以重写OnSizeChanged的事件

         protected override void OnSizeChanged(EventArgs e){base.OnSizeChanged(e);Invalidate();//强制窗体重绘}

OK,这样就完成了皮肤的绘制。接下来我们解决的问题有:

1.如何用鼠标拖拽改变无边框的大小

2.如何用鼠标移动无边框窗体

3.如何让绘制的最小化,最大化,关闭按钮执行操作(响应事件)

 

对于第1个问题拖拽改变无边框的大小,可以重写消息处理函数WndProc(除了这个我找不到其它好的方法了,如果哪个朋友知道请告诉我一声)

        const int WM_NCHITTEST = 0x0084;const int HTLEFT = 10;const int HTRIGHT = 11;const int HTTOP = 12;const int HTTOPLEFT = 13;const int HTTOPRIGHT = 14;const int HTBOTTOM = 15;const int HTBOTTOMLEFT = 0x10;const int HTBOTTOMRIGHT = 17;protected override void WndProc(ref Message m){base.WndProc(ref m);switch (m.Msg){case WM_NCHITTEST:Point vPoint = new Point((int)m.LParam & 0xFFFF,(int)m.LParam >> 16 & 0xFFFF);vPoint = PointToClient(vPoint);if (vPoint.X <= 5)if (vPoint.Y <= 5)m.Result = (IntPtr)HTTOPLEFT;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)HTBOTTOMLEFT;else m.Result = (IntPtr)HTLEFT;else if (vPoint.X >= ClientSize.Width - 5)if (vPoint.Y <= 5)m.Result = (IntPtr)HTTOPRIGHT;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)HTBOTTOMRIGHT;else m.Result = (IntPtr)HTRIGHT;else if (vPoint.Y <= 5)m.Result = (IntPtr)HTTOP;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)HTBOTTOM;break;}} 

 

第2个问题鼠标移动无边框窗体网上一般有三种方法(详见:[转]C#无边框窗体移动的三种方法)

其中有两种是用WINDOWS消息机制来完成,但是我发现如果用消息机制来处理会造成鼠标的双击或者单击事件不能使用,这一点让我很纠结,所以就采用了最原始的处理方式。

        private Point mouseOffset; //记录鼠标指针的坐标private bool isMouseDown = false; //记录鼠标按键是否按下private void Main_MouseDown(object sender, MouseEventArgs e){if (e.Button == MouseButtons.Left){mouseOffset = new Point(-e.X, -e.Y);isMouseDown = true;}}
        private void Main_MouseUp(object sender, MouseEventArgs e){if (e.Button == MouseButtons.Left){isMouseDown = false;}}private void Main_MouseMove(object sender, MouseEventArgs e){if (isMouseDown){Point mousePos = Control.MousePosition;mousePos.Offset(mouseOffset.X, mouseOffset.Y);Location = mousePos;}}
第3个问题我的思路是处理鼠标单击事件(这就是为什么我放弃了用消息处理机制来解决问题2),根据鼠标的坐标位置判断是在点击哪个图片来触发对应的事件
        private void Main_MouseClick(object sender, MouseEventArgs e){//判断鼠标是否点的是右上角按钮区域if (e.X >= formMinX && e.X <= formMinX + Resources.skin_btn_min.Width && e.Y <= imgFormMin.Height){this.WindowState = FormWindowState.Minimized;}if (e.X >= formMaxX && e.X <= formMaxX + Resources.skin_btn_max.Width && e.Y <= imgFormMax.Height){if (this.WindowState != FormWindowState.Maximized)this.WindowState = FormWindowState.Maximized;elsethis.WindowState = FormWindowState.Normal;}if (e.X >= formCloseX && e.X <= formCloseX + Resources.skin_btn_close.Width && e.Y <= imgFormClose.Height){Application.Exit();}}

然后最后的问题就是解决双击“标题栏”来最大化或者还原窗体了,我的思路是在绘制窗体的时候,就记录标题栏的边界坐标值,然后在双击事件中,根据鼠标的坐标位置来判断触发最大化(还原)事件。

        private void Main_MouseDoubleClick(object sender, MouseEventArgs e){if (e.X >= formTitleMarginLeft && e.X <= formTitleMarginRight && e.Y <= imgTopMiddle.Height){if (this.WindowState != FormWindowState.Maximized)this.WindowState = FormWindowState.Maximized;elsethis.WindowState = FormWindowState.Normal;}}

 

OK,整个皮肤的制作基本就完成了,乍一看貌似基本功能都实现了,但是如果想实现动态换肤,还是很麻烦的,下篇文章我会给出个动态换肤的解决方案和实现源码。

就这样了,欢迎拍砖。(源码下载) 

转载于:https://www.cnblogs.com/haiyabtx/archive/2012/09/21/2697190.html

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

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

相关文章

蓝牙PROFILE

Bluetooth的一个很重要特性&#xff0c;就是所有的Bluetooth产品都无须实现全部 的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容&#xff0c;Bluetooth规范中定义了Profile。Profile定义了设备如何实现一种连接或者应用&#xff0c;你可以把Profile理解为连接层或者…

netty系列之:EventLoop,EventLoopGroup和netty的默认实现

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 目录* 简介 EventLoopGroup和EventLoopEventLoopGroup在netty中的默认实现EventLoop在netty中的默认实现总结 简介 在net…

Oracle安装部署之RedHat安装Oracle11g_R2

硬件配置 内存 &#xff1a;≥1G 硬盘空间&#xff1a;≥10G 上传oracle11g安装包&#xff1a; putty上用wcw用户登录&#xff0c;通过ftp服务上传oracle安装文件到/home/wcw目录下解压 #unzip linux_11gR2_database_1of2.zip #unzip linux_11gR2_database_2of2.zip 检查和安装…

Fans没信心,回家继续修行

今天在CSDN上看了一篇的文章&#xff0c;感觉自己实在是太菜了&#xff0c;以至于对毕业之后从事IT行业没有了任何信心。现在也不清楚&#xff0c;自己能否在it行业混下去。自己的技术实在是一个水啊。8号就要回家了&#xff0c;兄弟姐妹们如果有事情&#xff0c;请发短信至 15…

基于SqlSugar的数据库访问处理的封装,支持多数据库并使之适应于实际业务开发中

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 在我的各种开发框架中&#xff0c;数据访问有的基于微软企业库&#xff0c;有的基于EFCore的实体框架&#xff0c;两者各有其…

Unity 实现物体破碎效果(转)

感谢网友分享&#xff0c;原文地址&#xff08;How to Make an Object Shatter Into Smaller Fragments in Unity&#xff09;&#xff0c;中文翻译地址&#xff08;Unity实现物体破碎效果&#xff09; In this tutorial I will show you how to create a simple shattering ef…

CC254x--OSAL

OSAL运行原理 蓝牙协议栈PROFILE、所有的应用程序、驱动等都是围绕着OSAL组织运行的。OSAL&#xff08;Operating System Abstraction Layer&#xff09;操作系统抽象层&#xff0c;它不是一个真正的操作系统&#xff08;它没有 Context Switch 上下文切换功能&#xff09;&am…

mysql跨节点join——federated引擎

一、 什么是federated引擎 mysql中的federated类似于oracle中的dblink。 federated是一个专门针对远程数据库的实现&#xff0c;一般情况下在本地数据库中建表会在数据库目录中生成相对应的表定义文件&#xff0c;并同时生成相对应的数据文件。 [图] 但是通过federated引擎创建…

【阅读SpringMVC源码】手把手带你debug验证SpringMVC执行流程

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 ✿ 阅读源码思路&#xff1a; 先跳过非重点&#xff0c;深入每个方法&#xff0c;进入的时候可以把整个可以理一下方法的执…

Zabbix监控(十六):分布式监控-Zabbix Proxy

说明&#xff1a;Zabbix支持分布式监控&#xff0c;利用Proxy代理功能&#xff0c;在其他网络环境中部署代理服务器&#xff0c;将监控数据汇总到Zabbix主服务器&#xff0c;实现多网络的分布式监控&#xff0c;集中监控。1、分布式监控原理Zabbix proxy和Zabbix server一样&am…

CC254x--BLE

BLE协议栈 BLE体系结构&#xff0c;着重了解GAP和GATT。 PHY物理层在2.4GHz的ISM频段中跳频识别。LL连接层&#xff1a;控制设备的状态。设备可能有5中状态&#xff1a;就绪standby&#xff0c;广播advertising&#xff0c;搜索scanning&#xff0c;初始化initiating和连接con…

Azure Container App(一)应用介绍

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 一&#xff0c;引言 容器技术正日益成为打包、部署应用程序的第一选择。Azure 提供了许多使用容器的选项。例如&#xff0…

怎样配置键盘最方便,以及一些设计的思考

使用Emacs的人&#xff0c;如果肯折腾&#xff0c;肯定有重新映射键盘的经历。我原来经常看到的是把Ctrl和Capslock交换&#xff0c;但是我感觉没什么道理&#xff0c;因为Ctrl已经用的很熟练了&#xff0c;换了反而不方便&#xff0c;而且对其他程序影响太大。那么我们就要使用…

profile、服务、特征、属性之间的关系

一个profile有很多的服务&#xff0c;一个服务又有很多的特性&#xff0c;一个特性中又有几种属性条目组成。 profile&#xff08;数据配置文件&#xff09; 一个profile文件可以包含一个或者多个服务&#xff0c;一个profile文件包含需要的服务的信息或者为对等设备如何交互的…

机器学习实战 | SKLearn最全应用指南

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 作者&#xff1a;韩信子ShowMeAI 教程地址&#xff1a;http://www.showmeai.tech/tutorials/41 本文地址&#xff1a;http…

Scheme语言入门

2019独角兽企业重金招聘Python工程师标准>>> Scheme语言入门 最早听说 LISP&#xff0c;是 Stallman 的 GNU Emacs 中将 LISP 作为嵌入语言&#xff0c;定制和增强 Emacs。GNU Emacs 是一个文本编辑器&#xff0c;文本就是一种符号&#xff0c;而 Lisp 正好就是针对…

如何将docker 镜像上传到docker hub仓库

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 如何将docker 镜像上传到docker hub仓库 目录* 如何将docker 镜像上传到docker hub仓库 背景 1.注册docker hub账号 2.…

ThinkPHP框架 _ 学习3

【路由解析】 通过url地址get参数找到指定的控制器&#xff0c;并进行对应方法调用请求 http://网址/index.php?m模块名称&c控制器&a方法 以上url地址信息代码不够优雅、不安全。 tp框架url地址可以由以下四种 http://网址/index.php?mXX&cXX&aXX 基本get模…

The slave I/O thread stops(equal MySQL server ids)

在学习replication时遇到了如下问题&#xff1a;显然看到Slave_IO_Running 为NO 表示有问题&#xff1b;到日志里查看&#xff0c;错误如下&#xff1a;position 98100121 17:09:03 [ERROR] The slave I/O thread stops because master and slave have equal MySQL server ids;…