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,一经查实,立即删除!

相关文章

Vue 源码解读(10)—— 编译器 之 生成渲染函数

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 前言 这篇文章是 Vue 编译器的最后一部分&#xff0c;前两部分分别是&#xff1a;Vue 源码解读&#xff08;8&#xff09;…

蓝牙PROFILE

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

Laravel Session 遇到的坑

这两天遇到了一个很奇怪的问题&#xff0c;更新session &#xff0c;session的值不变。经过一番追查&#xff0c;终于找到问题&#xff0c;并搞明白了原理。写这篇博客记录下。 框架版本 Laravel 5.4 问题 先来描述下问题&#xff0c;我在我们项目基础的Middleware中&#xff0…

bootstrap 中这段代码 使bundles 失败

bootstrap 中这段代码 使bundles 失败 _:-ms-fullscreen, :root input[type"date"], _:-ms-fullscreen, :root input[type"time"], _:-ms-fullscreen, :root input[type"datetime-local"], _:-ms-fullscreen, :root input[type"month"…

敏捷结果30天之第十二天:效率角色-你是启动者还是完成者

一.学习1.启动者&#xff1a;善于思考新想法&#xff0c;有太多想法还未开始&#xff0c;喜欢启动一些新事物&#xff0c;但是当事物成型之后就会离开去寻找下一个创新点。2.完成者&#xff1a;喜欢通过从头到尾的做完一件完整的事情来获得成就满足感。知道自己属于那种效率角色…

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…

BZOJ 1444: [Jsoi2009]有趣的游戏 [AC自动机 高斯消元]

1444: [Jsoi2009]有趣的游戏 题意&#xff1a;每种字母出现概率\(p_i\)&#xff0c;有一些长度len的字符串&#xff0c;求他们出现的概率 套路DP的话&#xff0c;\(f[i][j]\) i个字符走到节点j的概率&#xff0c;建出转移矩阵来矩乘几十次可以认为是无穷个字符&#xff0c;就得…

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…

查找字符串中要查找的字符串最后一次出现的位置

C Code 123456789101112131415161718192021222324#include <stdio.h>#include <string.h>//查找字符串中要查找的字符串最后一次出现的位置 char *strrstr (const char*string, const char*str){char *index NULL;char *ret NULL;int i 0;do{index strstr(stri…

基于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…

【转】/usr/bin/python^M: bad interpreter: No such file

转自&#xff1a;http://hanbaobao2005.blog.51cto.com/647054/635256今天在WingIDE下写了个脚本&#xff0c;传到服务器执行后提示&#xff1a; -bash: /usr/bin/autocrorder: /usr/bin/python^M: bad interpreter: No such file or directory 分析&#xff1a; 这是不同系统编…

PyTorch常用参数初始化方法详解

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 1、均匀分布初始化 torch.nn.init.uniform_(tensor, a0, b1) 从均匀分布U(a, b)中采样&#xff0c;初始化张量。  参数…

sql语句中的删除操作

drop: drop table tb; 删除内容和定义&#xff0c;释放空间。简单来说就是把整个表去掉。以后不能再新增数据&#xff0c;除非新增一个表。 truncate&#xff1a; truncate table tb; 删除内容、释放空间但不删除定义&#xff0c;即只是清空数&#xff0c;不会删除表的数据结构…

[面试题]事件循环经典面试题解析

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 基础概念 进程是计算机已经运行的程序,线程是操作系统能够进行运算调度的最小单位,它被包含在进程中.浏览器中每开一个Tab…

CC254x--OSAL

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

CLR 与 C++的常用类型转换笔记

1. System::String 转换到 const wchar_t* const wchar_t* ToUnmanagedUnicode( System::String^ str ){ pin_ptr<const WCHAR> nativeString1 PtrToStringChars( str ); return (const wchar_t*)nativeString1;} 2. const wchar_t* / const char* 转换到 System::Strin…

mysql跨节点join——federated引擎

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