当前位置 博文首页 > __Meow:浅谈Winform控件开发(一):使用GDI+美化基础窗口
1 /// <summary> 2 /// 表示组成应用程序的用户界面的窗口或对话框。 3 /// </summary> 4 [ToolboxItem(false)] 5 public class XForm : Form 6 ...
随后,我们定义一些常量
1 /// <summary> 2 /// 标题栏高度 3 /// </summary> 4 public const int TitleBarHeight = 30; 5 6 // 边框宽度 7 private const int BorderWidth = 4; 8 // 标题栏图标大小 9 private const int IconSize = 16; 10 // 标题栏按钮大小 11 private const int ButtonWidth = 30; 12 private const int ButtonHeight = 30;
覆盖基类属性FormBorderStyle使base.FormBorderStyle保持None,覆盖基类属性Padding返回或设置正确的内边距
1 /// <summary> 2 /// 获取或设置窗体的边框样式。 3 /// </summary> 4 [Browsable(true)] 5 [Category("Appearance")] 6 [Description("获取或设置窗体的边框样式。")] 7 [DefaultValue(FormBorderStyle.Sizable)] 8 public new FormBorderStyle FormBorderStyle 9 { 10 get => _formBorderStyle; 11 set 12 { 13 _formBorderStyle = value; 14 UpdateStyles(); 15 DrawTitleBar(); 16 } 17 } 18 19 /// <summary> 20 /// 获取或设置窗体的内边距。 21 /// </summary> 22 [Browsable(true)] 23 [Category("Appearance")] 24 [Description("获取或设置窗体的内边距。")] 25 public new Padding Padding 26 { 27 get => new Padding(base.Padding.Left, base.Padding.Top, base.Padding.Right, base.Padding.Bottom - TitleBarHeight); 28 set => base.Padding = new Padding(value.Left, value.Top, value.Right, value.Bottom + TitleBarHeight); 29 }
※最后一步也是最关键的一步:重新定义窗口客户区边界。重写WndProc并处理WM_NCCALCSIZE消息。
1 protected override void WndProc(ref Message m) 2 { 3 switch (m.Msg) 4 { 5 case WM_NCCALCSIZE: 6 { 7 // 自定义客户区 8 if (m.WParam != IntPtr.Zero && _formBorderStyle != FormBorderStyle.None) 9 { 10 NCCALCSIZE_PARAMS @params = (NCCALCSIZE_PARAMS) 11 Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); 12 @params.rgrc[0].Top += TitleBarHeight; 13 @params.rgrc[0].Bottom += TitleBarHeight; 14 Marshal.StructureToPtr(@params, m.LParam, false); 15 m.Result = (IntPtr)(WVR_ALIGNTOP | WVR_ALIGNBOTTOM | WVR_REDRAW); 16 } 17 18 base.WndProc(ref m); 19 break; 20 } 21 ……
1 case WM_NCPAINT: 2 { 3 DrawTitleBar(); 4 m.Result = (IntPtr)1; 5 break; 6 }
DrawTitleBar()方法定义如下:
1 /// <summary> 2 /// 绘制标题栏 3 /// </summary> 4 private void DrawTitleBar() 5 { 6 if (_formBorderStyle == FormBorderStyle.None) 7 return; 8 9 DrawTitleBackgroundTextIcon(); 10 CreateButtonImages(); 11 DrawTitleButtons(); 12 }
首先使用线性渐变画刷绘制标题栏背景、图标、标题文字:
1 /// <summary> 2 /// 绘制标题栏背景、文字、图标 3 /// </summary> 4 private void DrawTitleBackgroundTextIcon() 5 { 6 IntPtr hdc = GetWindowDC(Handle); 7 Graphics g = Graphics.FromHdc(hdc); 8 9 // 标题栏背景 10 using (Brush brsTitleBar = new LinearGradientBrush(TitleBarRectangle, 11 _titleBarStartColor, _titleBarEndColor, LinearGradientMode.Horizontal)) 12 g.FillRectangle(brsTitleBar, TitleBarRectangle); 13 14 // 标题栏图标 15 if (ShowIcon) 16 g.DrawIcon(Icon, new Rectangle( 17 BorderWidth, TitleBarRectangle.Top + (TitleBarRectangle.Height - IconSize) / 2, 18 IconSize, IconSize)); 19 20 // 标题文本 21 const int txtX = BorderWidth + IconSize; 22 SizeF szText = g.MeasureString(Text, SystemFonts.CaptionFont, Width, StringFormat.GenericDefault); 23 using Brush brsText = new SolidBrush(_titleBarForeColor); 24 g.DrawString(Text, 25 SystemFonts.CaptionFont, 26 brsText, 27 new RectangleF(txtX, 28 TitleBarRectangle.Top + (TitleBarRectangle.Bottom - szText.Height) / 2, 29 Width - BorderWidth * 2, 30 TitleBarHeight), 31 StringFormat.GenericDefault); 32 33 g.Dispose(); 34 ReleaseDC(Handle, hdc); 35 }
随后绘制标题栏按钮,犹豫篇幅限制,在此不多赘述,详见源码中CreateButtonImages()与DrawTitleButtons()。
至此,表面工作基本做完了,但这个窗口还不像个窗口,因为最小化、最大化、关闭以及调整窗口大小都不好用。
为什么?因为还有很多工作要做,首先,同样在WndProc中处理WM_NCHITTEST消息,通过m.Result指定当前鼠标位置位于标题栏、最小化按钮、最大化按钮、关闭按钮或上下左右边框
1 case WM_NCHITTEST: 2 { 3 base.WndProc(ref m); 4 5 Point pt = PointToClient(new Point((int)m.LParam & 0xFFFF, (int)m.LParam >> 16 & 0xFFFF)); 6 7 _userSizedOrMoved = true; 8 9 switch (_formBorderStyle) 10 { 11 case FormBorderStyle.None: 12 break; 13 case FormBorderStyle.FixedSingle: 14 case FormBorderStyle.Fixed3D: 15 case FormBorderStyle.FixedDialog: 16 case FormBorderStyle.FixedToolWindow: 17 if (pt.Y < 0) 18 { 19 _userSizedOrMoved = false; 20 m.Result = (IntPtr)HTCAPTION; 21 } 22 23 if (CorrectToLogical(CloseButtonRectangle).Contains(pt)) 24 m.Result = (IntPtr)HTCLOSE; 25 if (CorrectToLogical(MaximizeButtonRectangle).Contains(pt)) 26 m.Result = (IntPtr)HTMAXBUTTON; 27 if (CorrectToLogical(MinimizeButtonRectangle).Contains(pt)) 28 m.Result = (IntPtr)HTMINBUTTON; 29 30 break; 31 case FormBorderStyle.Sizable: 32 case FormBorderStyle.SizableToolWindow: 33 if (pt.Y < 0) 34 { 35 _userSizedOrMoved = false; 36 m.Result = (IntPtr)HTCAPTION; 37 } 38 39 if (CorrectToLogical(CloseButtonRectangle).Contains(pt)) 40 m.Result = (IntPtr)HTCLOSE; 41 if (CorrectToLogical(MaximizeButtonRectangle).Contains(pt)) 42 m.Result = (IntPtr)HTMAXBUTTON; 43 if (CorrectToLogical(MinimizeButtonRectangle).Contains(pt)) 44 m.Result = (IntPtr)HTMINBUTTON; 45 46 if (WindowState == FormWindowState.Maximized) 47 break; 48 49 bool bTop = pt.Y <= -TitleBarHeight + BorderWidth; 50 bool bBottom = pt.Y >= Height - TitleBarHeight - BorderWidth; 51 bool bLeft = pt.X <= BorderWidth; 52 bool bRight = pt.X >= Width - BorderWidth; 53 54 if (bLeft) 55 {