C#中窗体Form的美化

     VS足够强大,强大到只需动动鼠标就可以写出个基本的界面出来,但是其自带的控件都是千篇一律的样式,对于追求完美的我而言,实在是忍不下去了,只好自己亲自动手对其进行改造----继承已有的控件,再对其相关的消息或事件进行处理。窗体Form作为界面的主体部分,必先对其进行美化,在窗体自绘的过程中,需要使用到GDI+,如若对GDI+不是很了解的同学可移步我的CSDN博客或者搜索下相关的介绍。

这篇文章将要介绍到的内容:

 

实现效果演示:

 

 

代码下载

 

一:窗体圆角的处理

    对于无边框窗体圆角矩形的处理,我这里采用的是使用API函数CreateRoundRectRgn,相比于自己用GDI+写的处理圆角的函数,效果要稍微好点,至少线条在圆角处过渡的比较平滑,为了便于复用,我把其封装到窗体自绘辅助类RenderHlper的SetFormRoundRectRgn函数中:

View Code
        /// <summary>
        /// 设置窗体的圆角矩形
        /// </summary>
        /// <param name="form">需要设置的窗体</param>
        /// <param name="rgnRadius">圆角矩形的半径</param>
        public static void SetFormRoundRectRgn(Form form, int rgnRadius)
        {
            int hRgn = 0;
            hRgn = Win32.CreateRoundRectRgn(0, 0, form.Width + 1, form.Height + 1, rgnRadius, rgnRadius);
            Win32.SetWindowRgn(form.Handle, hRgn, true);
            Win32.DeleteObject(hRgn);
        }

 

 此处需要把所需要的API函数引用到类Win32中,引用的时候注意添加 System.Runtime.InteropServices 命名空间:

View Code
        [DllImport("gdi32.dll")]
        public static extern int CreateRoundRectRgn(int x1, int y1, int x2, int y2, int x3, int y3);

        [DllImport("user32.dll")]
        public static extern int SetWindowRgn(IntPtr hwnd, int hRgn, Boolean bRedraw);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Ansi)]
        public static extern int DeleteObject(int hObject);

 

重写窗体的OnSizeChanged事件,并在此事件中调用SetFormRoundRectRgn,此处的Radius参数为定义的窗体圆角半径属性:

View Code
       protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            RenderHelper.SetFormRoundRectRgn(this, Radius);
        }

 

二:无边框窗体大小的改变与移动

      当把窗体的FormBorderStyle属性调整为FormBorderStyle.None时,此时,窗体的大小改变不了,同时窗体不能移动。要想实现无边框窗体大小的改变与移动,可采用如下方法:

(1)重写窗体的过程WndProc:

 主要是对WM_NCHITTEST消息进行处理,根据事件的发生位置来进行不同方向箭头的调整,窗体大小改变与移动的函数:

View Code
         //调整窗体大小
        private void WmNcHitTest(ref Message m)
        {
            int wparam = m.LParam.ToInt32();
            Point mouseLocation = new Point(RenderHelper.LOWORD(wparam),RenderHelper.HIWORD(wparam));
            mouseLocation = PointToClient(mouseLocation);

            if (WindowState != FormWindowState.Maximized )
            {
                if (CanResize == true)
                {
                    if (mouseLocation.X < 5 && mouseLocation.Y < 5)
                    {
                        m.Result = new IntPtr(Win32.HTTOPLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 5 && mouseLocation.Y < 5)
                    {
                        m.Result = new IntPtr(Win32.HTTOPRIGHT);
                        return;
                    }

                    if (mouseLocation.X < 5 && mouseLocation.Y > Height - 5)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOMLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 5 && mouseLocation.Y > Height - 5)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOMRIGHT);
                        return;
                    }

                    if (mouseLocation.Y < 3)
                    {
                        m.Result = new IntPtr(Win32.HTTOP);
                        return;
                    }

                    if (mouseLocation.Y > Height - 3)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOM);
                        return;
                    }

                    if (mouseLocation.X < 3)
                    {
                        m.Result = new IntPtr(Win32.HTLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 3)
                    {
                        m.Result = new IntPtr(Win32.HTRIGHT);
                        return;
                    }
                }
            }
            m.Result = new IntPtr(Win32.HTCAPTION);
        }

 重写窗体过程:

View Code
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32.WM_NCHITTEST:
                    WmNcHitTest(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

 (2)对于仅仅只想实现窗体的移动而不改变窗体的大小,可以重写OnMouseDown事件中发送HTCAPTION消息来实现无边框窗体的移动,具体的实现代码如下:

View Code
        /// <summary>
        /// 移动窗体
      /// </summary>
        public static void MoveWindow(Form form)
        {
            Win32.ReleaseCapture();
            Win32.SendMessage(form.Handle, Win32.WM_NCLBUTTONDOWN, Win32.HTCAPTION, 0);
        }

调用窗体移动函数:

View Code
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
            {
                Render.MoveWindow(this);
            }
        }

 

三:窗体边框的绘制与边框阴影的实现

 边框的绘制:边框的绘制使用用PS制作好的图片来进行贴图操作,在贴图的过程中使用九宫图贴图方法,保证此边框图片能满足任何大小的窗体。

 窗体边框的实现:此部分主要涉及到对CS_DropSHADOW的了解,只要在窗口的ClassStyle添加此样式即可,关键代码如下:

View Code
       protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                if (!DesignMode)
                {
                    cp.ClassStyle |= (int)  ClassStyle.CS_DropSHADOW;             
             }
                return cp;
            }
        }

 

四:系统按钮的绘制与事件处理

 此部分是所有部分中最难的部分,在此部分中既要实现系统按钮不同状态下(鼠标操作改变按钮状态)的绘制,还有对其相应的事件进行处理,所以我创建了2个类:SystemButton类和SystemButtonManager类。SystemButton类表示系统按钮类,而SystemButtonManager的功能是对系统按钮各个状态与事件的管理。类SystemButtonManager的类图如下所示:

 

 属性、方法、事件的功能介绍如下表:

 

     对于类SystemButtonManager,主要是管理三个系统按钮的状态与事件,其他特别要介绍的是定义的系统按钮状态索引器,根据提供的索引来获取或者设置按钮的当前状态。

 

五:窗体标题栏的绘制

     标题栏的绘制主要涉及到窗体Icon图标的绘制与窗体标题的绘制,绘制的过程中定义了2个属性:IconRect,TextRect,分别对应着图标的坐标矩形与窗体标题的坐标矩形,图标与标题的绘制在这个矩形中绘制,需要提醒的时,图标的绘制需要注意到是否窗体的ShowIcon属性。

 

六:解决窗体闪烁的问题

     在窗体的自绘过程中,当调整窗体的大小等操作而触发窗体重绘,此时窗体的闪烁现象更为明显,相信大部分同学在自定义控件的过程中或多或多的出现这种问题,对于此问题,每个人又不同的解决方法,这里我提供四种解决方案类解决窗体的闪烁:

方法一:第一个容易想到的是采用双缓冲机制来进行图形的绘制,对双缓冲不了解的同学可以参考下我的另外一篇文章《浅谈C#中是双缓冲》

方法二:当将CS_DropSHADOW样式添加到窗体的ClassStyle样式中可以明显的解决窗体闪烁的现象。代码见本文的第三部分--窗体边框的绘制与边框阴影的实现。

方法三:当窗体进行重绘时,对WM_ERASEBKGND消息进行忽略,应用代码如下:

View Code
       protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32.WM_ERASEBKGND:
                    m.Result = IntPtr.Zero;
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

方法四:将WS_CLIPCHILDREN样式添加到窗体的ExStyle样式中,此方法对解决窗体挂有很多子控件时窗体闪烁的现象特别明显,应用代码如下:

View Code
       protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                if (!DesignMode)
                {
                    cp.ExStyle |= (int)WindowStyle.WS_CLIPCHILDREN;                 
                }
                return cp;

            }
        }

 

 

注:本博客所有文章均为作者个人原创 ,如若转载,请标记文章出处:http://www.cnblogs.com/Keep-Silence-/   苦笑。

 

posted @ 2013-01-09 16:42  Parker.Cao  阅读(35531)  评论(17编辑  收藏  举报