本文转载自:http://www.cnblogs.com/encoding/p/5603080.html

按照惯例,先来几张样例图(注:为了展示窗口阴影效果,截图范围向外扩展了些,各位凭想象吧)。

还要来个序

其实,很多年没写过Winform了,前端时间在重构我们公司自己的呼叫中心系统,突然就觉得客户端好丑好丑,对于我这种强迫症晚期患者来说,界面不好看都不知道怎么写代码的,简直就是种折磨,还是满清十大酷刑级别那种。

很多人推荐WPF,不过个人对WPF没啥感觉,而且据说也无法支持2.0,还是采用Winform技术来搞吧。

终于第一节

做Winform皮肤,我了解的无非有2种方式。

1.采用纯图片模式:由专业美工设计各种样式的图片,进行窗口背景图片设置

2.采用GDI+纯代码绘制:参照美工设计的各种样式的图片,使用C#代码绘制出来

第一种方式很简单,但是可复用性不高,性能上面应该也会有一些影响,如果图片太大,窗口拖动等引起的重绘时,会明显有闪烁等情况。

第二种方式很复杂,但是效率和复用性高,开放各种扩张属性之后,可以适用于大部分场景。

以前用了很多次第一种方案,每次新做APP,都得重新设计界面,很不便利。这次,我准备采用第二种方案来做一个公用的皮肤。

关于GDI+,我只能算一个新人,不做具体的介绍,这里只讲我的一些实现方式,计划项目完成后,开源到github。

绘制标题栏

做自定义界面,绕不开一个问题就是绘制标题栏。

每个Winform窗体,可以分为两个部分:非客户区域和客户区域。

非客户区域:表示无法由我们程序猿绘制的部分,具体包括:窗口标题栏,边框

客户区域:表示由我们程序猿绘制的部分,也就是窗体内容,平时我们拖控件都是拖到客户区域

一般自定义窗口的实现方式无非以下种

1.设置窗口为无边框窗口,顶部放一个Panel,设置Panel.Dock=Top,然后在Panel里面绘制logo、标题、按钮等元素。

2.拦截窗口消息,重写WndProc方法,拦截窗口标题绘制消息,由自己手工绘制

很多人会为了简便,采用第一种方式,不过缺点比较明显,对于我来说,最主要的一点就是真正的实现界面,里面的控件元素Dock会受到影响,不利于客户区域布局。

高手牛人会采用第二种方式,不是我这种Winform小白的菜,所以,我采用第三种方式,也是本篇文章的核心思想。

采用无边框窗口,设置窗口Padding.Top为标题栏高度,采用GDI+绘制标题栏元素。

这种方式的好处显而易见

具体实现窗体子控件Dock不受影响

无边框之后,重写窗体拖动事件不需要对标题栏每一个元素进行事件处理

标题栏高度可随时自定义

本文开头的几个截图,标题栏绘制代码如下

绘制标题文字、Logo图片

private void DrawTitle(Graphics g)
{var x = 6 + this.GetBorderWidth();if (this.ShowLogo){g.SmoothingMode = SmoothingMode.AntiAlias;ImageAttributes imgAtt = new ImageAttributes();imgAtt.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);using (var image = this.Icon.ToBitmap()){var rec = new Rectangle(x, (this.captionHeight - 24) / 2, 24, 24);g.DrawImage(image, rec, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imgAtt);}}if (this.ShowTitle){var font = this.titleFont == null ? this.Font : this.titleFont;var fontSize = Size.Ceiling(g.MeasureString(this.Text, font));if (this.CenterTitle){x = (this.Width - fontSize.Width) / 2;}else if (this.ShowLogo){x += 30;}using (var brush = new SolidBrush(this.CaptionForeColor)){g.DrawString(this.Text, font, brush, x, (this.CaptionHeight - fontSize.Height) / 2 + this.GetBorderWidth());}}
}

绘制最小化、最大化、关闭、帮助按钮

private void DrawControlBox(Graphics g)
{if (this.ControlBox){ImageAttributes ImgAtt = new ImageAttributes();ImgAtt.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);var x = this.Width - 32;//var rec = new Rectangle(this.Width - 32, (this.CaptionHeight - 32) / 2 + this.BorderWidth, 32, 32);//var rec = new Rectangle(x, this.BorderWidth, 32, 32);if (this.CloseButtonImage != null){closeRect = new Rectangle(x, 0, 32, 32);using (var brush = new SolidBrush(closeHover ? this.ControlActivedColor : this.ControlBackColor)){g.FillRectangle(brush, closeRect);}g.DrawImage(this.CloseButtonImage, closeRect, 0, 0, this.CloseButtonImage.Width, this.CloseButtonImage.Height, GraphicsUnit.Pixel, ImgAtt);x -= 32;}if (this.MaximizeBox && this.WindowState == FormWindowState.Maximized && this.MaximumNormalButtonImage != null){maxRect = new Rectangle(x, 0, 32, 32);using (var brush = new SolidBrush(maxHover ? this.ControlActivedColor : this.ControlBackColor)){g.FillRectangle(brush, maxRect);}g.DrawImage(this.MaximumNormalButtonImage, maxRect, 0, 0, this.MaximumNormalButtonImage.Width, this.MaximumNormalButtonImage.Height, GraphicsUnit.Pixel, ImgAtt);x -= 32;}else if (this.MaximizeBox && this.WindowState != FormWindowState.Maximized && this.MaximumButtonImage != null){maxRect = new Rectangle(x, 0, 32, 32);using (var brush = new SolidBrush(maxHover ? this.ControlActivedColor : this.ControlBackColor)){g.FillRectangle(brush, maxRect);}g.DrawImage(this.MaximumButtonImage, maxRect, 0, 0, this.MaximumButtonImage.Width, this.MaximumButtonImage.Height, GraphicsUnit.Pixel, ImgAtt);x -= 32;}if (this.MinimizeBox && this.MinimumButtonImage != null){minRect = new Rectangle(x, 0, 32, 32);using (var brush = new SolidBrush(minHover ? this.ControlActivedColor : this.ControlBackColor)){g.FillRectangle(brush, minRect);}g.DrawImage(this.MinimumButtonImage, minRect, 0, 0, this.MinimumButtonImage.Width, this.MinimumButtonImage.Height, GraphicsUnit.Pixel, ImgAtt);x -= 32;}if (base.HelpButton && this.HelpButtonImage != null){helpRect = new Rectangle(x, 0, 32, 32);using (var brush = new SolidBrush(helpHover ? this.ControlActivedColor : this.ControlBackColor)){g.FillRectangle(brush, helpRect);}g.DrawImage(this.HelpButtonImage, helpRect, 0, 0, this.HelpButtonImage.Width, this.HelpButtonImage.Height, GraphicsUnit.Pixel, ImgAtt);x -= 32;}}
}

窗体OnPaint事件,自绘标题栏

protected override void OnPaint(PaintEventArgs e){base.OnPaint(e);#region draw captionusing (var brush = new SolidBrush(this.CaptionBackgroundColor)){e.Graphics.FillRectangle(brush, captionRect);}this.DrawTitle(e.Graphics);this.DrawControlBox(e.Graphics);#endregion#region draw borderControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, borderColor, ButtonBorderStyle.Solid);#endregion}

采用Padding来约束子实现界面的元素布局位置

当我采用了无边框窗体来做自定义皮肤之后,由于去除了非客户区域(标题栏、边框),子实现窗体的坐标位置(0,0)实际上应该会覆盖我的标题栏,不过,反编译.NET源码之后,我发现Form类有一个Padding属性,这个属性继承自Control类,它的作用与CSS中的padding相同。所以,我决定使用这个技术来约束子实现界面的元素布局位置。

每次修改标题栏高度时,需要重新生成窗体的Padding属性

private int captionHeight;
[Category("标题栏"), Description("标题栏高度"), DefaultValue(typeof(int), "40")]
public int CaptionHeight { get { return this.captionHeight; } set { this.captionHeight = value; this.SetPadding(); } }

每次修改边框宽度时,需要重新生成窗体的Padding属性

private int borderWidth;
[Category("边框"), Description("边框宽度"), DefaultValue(typeof(int), "1")]
public int BorderWidth { get { return this.borderWidth; } set { this.borderWidth = value; this.SetPadding(); } }

最后,隐藏掉Padding属性,外部修改无效

public new Padding Padding { get; set; }

附加1:标题栏自绘按钮悬浮背景色修改和单击事件处理

protected override void OnMouseMove(MouseEventArgs e)
{Point p = new Point(e.X, e.Y);captionHover = captionRect.Contains(p);if (captionHover){closeHover = closeRect != Rectangle.Empty && closeRect.Contains(p);minHover = minRect != Rectangle.Empty && minRect.Contains(p);maxHover = maxRect != Rectangle.Empty && maxRect.Contains(p);helpHover = helpRect != Rectangle.Empty && helpRect.Contains(p);this.Invalidate(captionRect);this.Cursor = (closeHover || minHover || maxHover || helpHover) ? Cursors.Hand : Cursors.Default;}else{if (closeHover || minHover || maxHover || helpHover){this.Invalidate(captionRect);closeHover = minHover = maxHover = helpHover = false;}this.Cursor = Cursors.Default;}base.OnMouseMove(e);
}

protected override void OnMouseClick(MouseEventArgs e)
{
var point = new Point(e.X, e.Y);
if (this.closeRect != Rectangle.Empty && this.closeRect.Contains(point))
{
this.Close();
return;
}if (!this.maxRect.IsEmpty && this.maxRect.Contains(point))
{
if (this.WindowState == FormWindowState.Maximized)
{
this.WindowState = FormWindowState.Normal;
}
else
{
this.WindowState = FormWindowState.Maximized;
}this.maxHover = false;
return;
}if (!this.minRect.IsEmpty && this.minRect.Contains(point))
{
this.WindowState = FormWindowState.Minimized;
this.minHover = false;
return;
}if (!this.helpRect.IsEmpty && this.helpRect.Contains(point))
{
this.helpHover = false;
this.Invalidate(this.captionRect);
CancelEventArgs ce = new CancelEventArgs();
base.OnHelpButtonClicked(ce);
return;
}base.OnMouseClick(e);
}

附加2:处理无边框窗体用户调整大小

#region 调整窗口大小const int Guying_HTLEFT = 10;const int Guying_HTRIGHT = 11;const int Guying_HTTOP = 12;const int Guying_HTTOPLEFT = 13;const int Guying_HTTOPRIGHT = 14;const int Guying_HTBOTTOM = 15;const int Guying_HTBOTTOMLEFT = 0x10;const int Guying_HTBOTTOMRIGHT = 17;protected override void WndProc(ref Message m){if (this.closeHover || this.minHover || this.maxHover || this.helpHover){base.WndProc(ref m);return;}if (!this.CustomResizeable){base.WndProc(ref m);return;}switch (m.Msg){case 0x0084:base.WndProc(ref m);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)Guying_HTTOPLEFT;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)Guying_HTBOTTOMLEFT;else m.Result = (IntPtr)Guying_HTLEFT;else if (vPoint.X >= ClientSize.Width - 5)if (vPoint.Y <= 5)m.Result = (IntPtr)Guying_HTTOPRIGHT;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)Guying_HTBOTTOMRIGHT;else m.Result = (IntPtr)Guying_HTRIGHT;else if (vPoint.Y <= 5)m.Result = (IntPtr)Guying_HTTOP;else if (vPoint.Y >= ClientSize.Height - 5)m.Result = (IntPtr)Guying_HTBOTTOM;break;case 0x0201:                //鼠标左键按下的消息  m.Msg = 0x00A1;         //更改消息为非客户区按下鼠标  m.LParam = IntPtr.Zero; //默认值  m.WParam = new IntPtr(2);//鼠标放在标题栏内  base.WndProc(ref m);break;default:base.WndProc(ref m);break;}}#endregion

全类文件,不晓得咋上传附件,所以没传,要的可以找我QQ。

2016年6月22日编辑添加:

  由于本人在北京出差,昨晚上飞机航班延误,根本没想到突然这么多人要源码,无法做到一一回应,请大家谅解,我已经将DForm类上传,感谢“大萝卜控”给我提示。

  请大家点击    这里    下载。

  里面有几张图片,大家可以随便弄下,我出差比较忙,回去之后,再放github,到时再开个单章通知大家。

  关于阴影的部分,大家可以先注释掉代码,完整源码放出之后,就可以了。

  还有哟,我QQ在文章某个地方有显示,没加码。

懒惰,是一个通病。 努力,必会成为一种习惯。

转载于:https://www.cnblogs.com/wpcnblog/p/5773384.html

重绘Winform窗体相关推荐

  1. 【转】【C#】C#重绘windows窗体标题栏和边框

    摘要 windows桌面应用程序都有标准的标题栏和边框,大部分程序也默认使用这些样式,一些对视觉效果要求较高的程序,如QQ, MSN,迅雷等聊天工具的样式则与传统的windows程序大不相同,其中迅雷 ...

  2. Java小白画图板的重绘包括谢尔宾斯基三角形和蕨类图像的重绘

    本作者在构造射线的重绘时出现了错误,将会在以后的文章中贴出来... 画板的重绘 当窗体的状态发生改变时,画板上的缓存会自动清空.屏幕上绘制的图像是将窗体中的对象数据从内存中取出来放到缓存中,再绘制的. ...

  3. winform 异步弹窗窗体_玩转控件:重写/重绘Dev中MessageBox弹窗控件

    很久没有更新博客了,本想着直接发一篇<手撕ERP>系列,从控件重写.重绘,到框架搭建,再到部分模块实现+业务的.但是每次动手的时候,都觉得难以下手.直接从数据库设计开始吧,模块设计还没定下 ...

  4. [DForm]我也来做自定义Winform之另类标题栏重绘

    引用:https://www.cnblogs.com/encoding/p/5603080.html 其他下载:https://github.com/dongger/MomoForm 据说得有楔子 按 ...

  5. C#中关于WinForm中重绘TabControl选项卡标题的问题

    这里说的是每个TabPage的头部,也就是标题,不是工作区域. 最开始用到TabControl的时候,我的每个选项卡是写死的,而后由于项目需求又动态添加了TabControl并生成各个选项卡,而两次我 ...

  6. winform控件大小改变是防止背景重绘导致的闪烁

    在工作中需要做一个伸缩控件,这个自定义控件继承于Panel.这个伸缩控件分为两个部分,头部是一个自定义组件,伸缩控件的背景为灰色,头部背景要求白色.伸缩控件在点击按钮时会重绘,同时他内部的控件也会重绘 ...

  7. c#-winform重绘Tabcontrol控件,标签带Logo图标

    模仿网页浏览器标签重绘Tabcontrol控件,每个标签页左上角的Logo图标可以自定义,当然图标也可以挪到右边,直接上图.

  8. 玩转控件:重写/重绘Dev中MessageBox弹窗控件

    很久没有更新博客了,本想着直接发一篇<手撕ERP>系列,从控件重写.重绘,到框架搭建,再到部分模块实现+业务的.但是每次动手的时候,都觉得难以下手.直接从数据库设计开始吧,模块设计还没定下 ...

  9. C# ListBox控件中item换行 C#重绘ListBox项

    C# ListBox控件中item换行 C#重绘ListBox项 WindowsForm项目开发中,Listbox控件item数据项,只能一条数据显示在一行,有的时候内容很长,体验就非常之差.简直要歇 ...

最新文章

  1. 团队作业8——第二次项目冲刺(Beta阶段)--第六天
  2. KS007基于JSP实现人个人博客系统
  3. JavaScript实现QuickSort快速排序算法(附完整源码)
  4. ruby中的复制 dup clone
  5. html table设置行高_字号与行高
  6. java中scanner与hashmap_Java中HashMap的使用练习
  7. .net环境下ckeditor与ckfinder中文文件链接乱码的问题
  8. 分享一篇文章,博主的经历值得借鉴
  9. 如何清空其他终端上的屏幕
  10. 安装各种工具/第三方库(随时更新)
  11. H5+ 二维码扫描功能
  12. AVR PIC单片机视频教程
  13. 建网站域名服务器那个好,如何选择好建网站的域名?
  14. ​九月简报 | Coinversation先行版DEX——Kaco上线碎片化,TVL最高突破1000w美金
  15. OSChina 周日乱弹 —— 这个野男人是谁
  16. 各进制转换成十进制的方法
  17. 使用VMware镜像文件快速安装Kali linux
  18. 为博客添加樱花飘落的效果
  19. 一文读懂java内部类
  20. 区块链技术的应用领域

热门文章

  1. 学习笔记Kafka(七)—— Kafka 与Spark集成 —— 原理介绍与开发环境配置、实战
  2. 学习笔记Spark(十)—— Spark MLlib应用(2)—— Spark MLlib应用
  3. MySQL This function has none of DETERMINISTIC, NO SQL...错误1418 的原因分析及解决方法
  4. QT5界面操作2:如何用状态栏显示鼠标坐标
  5. Ubuntu 安装docker-engine的三种方法
  6. 计算机专业的分支,计算机专业分支(转载)
  7. docker如何将运行中的容器保存为docker镜像?
  8. arr.sort的排序原理
  9. python快捷键设置_UltraEdit搭建Python IDE环境+设置快捷键
  10. php接口调用实例化,PHP抽象类和接口用法实例详解