VS足够强大,强大到只需动动鼠标就可以写出个基本的界面出来,但是其自带的控件都是千篇一律的样式,对于追求完美的我而言,实在是忍不下去了,只好自己亲自动手对其进行改造----继承已有的控件,再对其相关的消息或事件进行处理。窗体Form作为界面的主体部分,必先对其进行美化,在窗体自绘的过程中,需要使用到GDI+,如若对GDI+不是很了解的同学可移步我的CSDN博客或者搜索下相关的介绍。
这篇文章将要介绍到的内容:
实现效果演示:
对于无边框窗体圆角矩形的处理,我这里采用的是使用API函数CreateRoundRectRgn,相比于自己用GDI+写的处理圆角的函数,效果要稍微好点,至少线条在圆角处过渡的比较平滑,为了便于复用,我把其封装到窗体自绘辅助类RenderHlper的SetFormRoundRectRgn函数中:
/// <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 命名空间:
[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参数为定义的窗体圆角半径属性:
protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); RenderHelper.SetFormRoundRectRgn(this, Radius); }
当把窗体的FormBorderStyle属性调整为FormBorderStyle.None时,此时,窗体的大小改变不了,同时窗体不能移动。要想实现无边框窗体大小的改变与移动,可采用如下方法:
(1)重写窗体的过程WndProc:
主要是对WM_NCHITTEST消息进行处理,根据事件的发生位置来进行不同方向箭头的调整,窗体大小改变与移动的函数:
//调整窗体大小 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); }
重写窗体过程:
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消息来实现无边框窗体的移动,具体的实现代码如下:
/// <summary> /// 移动窗体 /// </summary> public static void MoveWindow(Form form) { Win32.ReleaseCapture(); Win32.SendMessage(form.Handle, Win32.WM_NCLBUTTONDOWN, Win32.HTCAPTION, 0); }
调用窗体移动函数:
protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { Render.MoveWindow(this); } }
边框的绘制:边框的绘制使用用PS制作好的图片来进行贴图操作,在贴图的过程中使用九宫图贴图方法,保证此边框图片能满足任何大小的窗体。
窗体边框的实现:此部分主要涉及到对CS_DropSHADOW的了解,只要在窗口的ClassStyle添加此样式即可,关键代码如下:
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消息进行忽略,应用代码如下:
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样式中,此方法对解决窗体挂有很多子控件时窗体闪烁的现象特别明显,应用代码如下:
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-/ 苦笑。