引用:https://www.cnblogs.com/encoding/p/5603080.html

其他下载:https://github.com/dongger/MomoForm

据说得有楔子

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

                  

还要来个序

其实,很多年没写过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图片

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

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());

        }

    }

}

  

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

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事件,自绘标题栏

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

protected override void OnPaint(PaintEventArgs e)

        {

            base.OnPaint(e);

            #region draw caption

            using (var brush = new SolidBrush(this.CaptionBackgroundColor))

            {

                e.Graphics.FillRectangle(brush, captionRect);

            }

            this.DrawTitle(e.Graphics);

            this.DrawControlBox(e.Graphics);

            #endregion

            #region draw border

            ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, borderColor, ButtonBorderStyle.Solid);

            #endregion

        }

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

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

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

1

2

3

private int captionHeight;

[Category("标题栏"), Description("标题栏高度"), DefaultValue(typeof(int), "40")]

public int CaptionHeight { get return this.captionHeight; } set this.captionHeight = value; this.SetPadding(); } }

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

1

2

3

private int borderWidth;

[Category("边框"), Description("边框宽度"), DefaultValue(typeof(int), "1")]

public int BorderWidth { get return this.borderWidth; } set this.borderWidth = value; this.SetPadding(); } }

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

1

public new Padding Padding { getset; }

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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);
}

1

<br data-filtered="filtered">

  

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

#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://github.com/dongger/MomoForm

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

[DForm]我也来做自定义Winform之另类标题栏重绘相关推荐

  1. DataTable列自定义渲染切页无效重绘

    DataTable列自定义渲染切页 进行DataTable重绘 jsp代码 table = $("#detail_table").DataTable({language: lang ...

  2. WinForm经典窗体皮肤[重绘]

    先上图看看效果: 1.默认经典 2.蓝色炫雅 3.蓝色经典 4.黑色炫雅 5.黑色经典 注:代码是几年前从网上找到的,好像是在CodeProject上,代码整理中,稍候公开源码... 转载于:http ...

  3. .Net(C#)自定义WinForm控件之小结篇(强力推荐)

    强力推荐的网站:http://www.myfirm.cn/News/dotNetGUIAPI/ 本文转载:http://www.myfirm.cn/News/dotNetUserInterface/2 ...

  4. 背水一战 Windows 10 (20) - 绑定: DataContextChanged, UpdateSourceTrigger, 对绑定的数据做自定义转换...

    背水一战 Windows 10 (20) - 绑定: DataContextChanged, UpdateSourceTrigger, 对绑定的数据做自定义转换 原文:背水一战 Windows 10 ...

  5. 重绘Winform窗体

    本文转载自:http://www.cnblogs.com/encoding/p/5603080.html 按照惯例,先来几张样例图(注:为了展示窗口阴影效果,截图范围向外扩展了些,各位凭想象吧). 还 ...

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

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

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

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

  8. 自定义字体样式引入使用方法、文本阴影、边框阴影、(边框)圆角、渐变、理解重绘与回流、渐进增强和优雅降级的区别

    目录 1.文本阴影 text-shadow 2.边框阴影 box-shadow 3.自定义字体样式方法 引入与使用方法举例: 4.(边框)圆角 5.渐变 6.过渡  transition 7.理论知识 ...

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

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

最新文章

  1. java邮件接收代码,JavaMail入门第四篇 接收邮件(示例代码)
  2. springcloud与dubbo对比:
  3. 自然语言处理ACL2020论文列表
  4. Android6.0------权限申请管理(单个权限和多个权限申请)
  5. Uipath 学习栏目基础教学:6Uipath发送邮件
  6. python中格式化字符串_Python中所有字符串格式化的指南
  7. 用于 Keras 用户使用的 TensorFlow.js layers API
  8. ibatis调用mysql函数
  9. Sql Server 全文检索
  10. Mybatis mapper.xml中常用标签详解
  11. 正三角形二面体群表示为二阶矩阵形式
  12. 解决屏幕大小不一导致页面下方出现多余空白的问题
  13. android plc,基于Android的智能PLC操控软件设计与实现
  14. Android 开发、测试工具资源汇总
  15. 最新Android 9.0 Pie,你想知道的都在这了
  16. odoo10 -- 请假模块
  17. 听声音做钥匙?!慢放开锁音轨,黑客就能破解常用门锁
  18. 华为服务器命令行修改密码,华为IAD命令行配置方法
  19. 荐9个可以帮助你的公众号
  20. 华为nova5 pro怎么升级鸿蒙系统,鸿蒙系统终于来了!这几款华为手机都可以升级了...

热门文章

  1. 暑期打工通讯“省钱神器”之中华通
  2. java做橡皮擦效果_用HTML5 实现橡皮擦的涂抹效果的教程
  3. std::thread使用注意资源回收问题
  4. 真正优秀的质量工程师,都有这些特质
  5. Python爬虫技巧-西瓜视频MP4地址获取
  6. 双休日出差是否是加班?可以要求支付加班费么?
  7. 蓝牙耳机纯知识科普:想要音质好,在选购中一定注意这个问题!
  8. Web3的应用及发展
  9. 基于ASP.NET109学生作业管理系统(web)
  10. Linux预科知识之认识计算机硬件——机箱电源