zoukankan      html  css  js  c++  java
  • [DForm]我也来做自定义Winform之另类标题栏重绘

    据说得有楔子

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

        image       image    image    image   image

    还要来个序

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

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

  • 相关阅读:
    20191208浙江自然博物馆寒武纪迸发专题展
    Spring Crest-英特尔出品的神经网络训练场景加速卡
    20191203动物园玉皇山凤凰山南宋皇城遗址凤凰山玉皇山动物园
    博观而约取-观展攻略
    20191130周六浙江美术馆纤维艺术特展
    git clone下载速度很慢的解决方法
    Jupyter notebook使用技巧积累
    (转)Python--matplotlib绘图可视化知识点整理
    Pygame一些不错教程平时收集....
    pygame.Surface.get_at
  • 原文地址:https://www.cnblogs.com/encoding/p/5603080.html
Copyright © 2011-2022 走看看