Winfrom中From控件的重绘
重绘目的:
- 1. 满足非默认主题下的标题栏样式
- 2. 在保留停靠功能的同时进行重绘。
代码如下:
1 public partial class FormEx: Form 2 { 3 public FormEx() 4 { 5 InitializeComponent(); 6 TitleBar.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(TitleBar, true, null); 7 Icon = Properties.Resources.shark; 8 CloseButtonImage = Properties.Resources.close; 9 MaximumButtonImage = Properties.Resources.window_max; 10 MaximumNormalButtonImage = Properties.Resources.window; 11 MinimumButtonImage = Properties.Resources.window_min; 12 CaptionBackgroundColor = Color.FromArgb(230, 230, 230); 13 CaptionHeight = 30; 14 BackColor = Color.White; 15 ControlBackColor = Color.Transparent; 16 this.TransparencyKey = boderColor; 17 ControlActivedColor = DrawHelper.GetNearColor(Color.White, 0, -35, -24, -30); 18 19 TitleBar.SendToBack(); 20 21 base.SetStyle( 22 ControlStyles.UserPaint | 23 ControlStyles.AllPaintingInWmPaint | 24 ControlStyles.OptimizedDoubleBuffer | 25 ControlStyles.ResizeRedraw | 26 ControlStyles.SupportsTransparentBackColor| 27 ControlStyles.DoubleBuffer, true); 28 base.AutoScaleMode = AutoScaleMode.None; 29 } 30 31 #region 公开变量 32 [Category("标题栏"), Description("关闭按钮图片")] 33 public Image CloseButtonImage { get; set; } 34 35 [Category("标题栏"), Description("最大化按钮图片")] 36 public Image MaximumButtonImage { get; set; } 37 38 [Category("标题栏"), Description("最大化默认按钮图片")] 39 public Image MaximumNormalButtonImage { get; set; } 40 41 [Category("标题栏"), Description("最小化按钮图片")] 42 public Image MinimumButtonImage { get; set; } 43 44 [Category("标题栏"), Description("标题栏按钮鼠标悬浮背景色"), DefaultValue(typeof(Color), "#000000")] 45 public Color ControlActivedColor { get; set; } 46 47 [Category("标题栏"), Description("标题栏按钮默认状态背景色"), DefaultValue(typeof(Color))] 48 public Color ControlBackColor { get; set; } 49 50 private int captionHeight; 51 [Category("标题栏"), Description("标题栏高度"), DefaultValue(typeof(int), "30")] 52 public int CaptionHeight { get { return captionHeight; } set { captionHeight = value; TitleBar.Height = value; } } 53 54 [Category("标题栏"), Description("标题位置")] 55 public ContentAlignment TitleAlign { set; get; } = ContentAlignment.MiddleLeft; 56 57 private Color captionBackgroundColor; 58 [Category("标题栏"), Description("标题栏背景颜色"), DefaultValue(typeof(Color), "White")] 59 public Color CaptionBackgroundColor 60 { 61 get { return captionBackgroundColor; } 62 set 63 { 64 captionBackgroundColor = value; 65 TitleBar.BackColor = captionBackgroundColor; 66 } 67 } 68 #endregion 69 70 #region 私有变量 71 72 private MouseState _mouseState = MouseState.Out; 73 private Rectangle closeRect = Rectangle.Empty; //关闭按钮范围 74 private Rectangle maxRect = Rectangle.Empty; //最大化按钮范围 75 private Rectangle minRect = Rectangle.Empty; //最小化按钮范围 76 private Rectangle captionRect; //标题范围 77 78 private int btnH = 24; //按钮大小 79 private int boderWidth = 8; //边框宽度 80 private Color boderColor = Color.FromArgb(254, 254, 253); 81 #endregion 82 83 #region 重绘事件 84 private void TitleBar_Paint(object sender, PaintEventArgs e) 85 { 86 captionRect = new Rectangle(0, 0, TitleBar.Width, TitleBar.Height); 87 DrawTitle(e.Graphics); 88 DrawControlButton(e.Graphics); 89 } 90 91 protected override void OnPaint(PaintEventArgs e) 92 { 93 base.OnPaint(e); 94 DrawBound(e.Graphics); 95 } 96 97 /// <summary> 98 /// 绘制边框 99 /// </summary> 100 private void DrawBound(Graphics g) 101 { 102 Pen pen = new Pen(boderColor, boderWidth * 2); 103 Rectangle rectangle = new Rectangle(0, 0, Width, Height); 104 g.DrawRectangle(pen, rectangle); 105 pen = new Pen(Color.Black, 1); 106 rectangle = new Rectangle(boderWidth - 1, boderWidth - 1, Width - boderWidth * 2 + 1, Height - boderWidth * 2 + 1); 107 g.DrawRectangle(pen, rectangle); 108 Padding = new Padding(boderWidth); 109 } 110 111 /// <summary> 112 /// 绘制控制按钮 113 /// </summary> 114 private void DrawControlButton(Graphics g) 115 { 116 int x = ClientSize.Width - btnH - 15 - boderWidth; 117 if (CloseButtonImage != null) 118 { 119 DrawButtonImage(ref closeRect, ref x, _mouseState == MouseState.CloseHover, g, CloseButtonImage); 120 } 121 if (MaximizeBox) 122 { 123 if (WindowState == FormWindowState.Maximized && MaximumNormalButtonImage != null) 124 DrawButtonImage(ref maxRect, ref x, _mouseState == MouseState.MaxHover, g, MaximumNormalButtonImage); 125 else if (MaximizeBox && WindowState != FormWindowState.Maximized && MaximumButtonImage != null) 126 DrawButtonImage(ref maxRect, ref x, _mouseState == MouseState.MaxHover, g, MaximumButtonImage); 127 } 128 if (MinimizeBox && MinimumButtonImage != null) 129 { 130 DrawButtonImage(ref minRect, ref x, _mouseState == MouseState.MinHover, g, MinimumButtonImage); 131 } 132 } 133 134 private void DrawButtonImage(ref Rectangle rect, ref int x, bool isHover, Graphics g, Image image) 135 { 136 //rect = new Rectangle(x, (captionHeight - btnH) / 2, btnH, btnH); 137 rect = new Rectangle(x, 0, btnH, btnH); 138 Brush brush = new SolidBrush(isHover ? ControlActivedColor : ControlBackColor); 139 g.FillRectangle(brush, rect); 140 g.DrawImage(image, rect); 141 if (image == CloseButtonImage) 142 rect = new Rectangle(x, 0, btnH + boderWidth, btnH); 143 x -= btnH; 144 } 145 146 /// <summary> 147 /// 绘制标题 148 /// </summary> 149 private void DrawTitle(Graphics g) 150 { 151 int x = 6; 152 if (ShowIcon && Icon != null) 153 { 154 g.SmoothingMode = SmoothingMode.AntiAlias; 155 ImageAttributes image = new ImageAttributes(); 156 image.SetWrapMode(WrapMode.TileFlipXY); 157 using (Bitmap bitmap = Icon.ToBitmap()) 158 { 159 Rectangle rec = new Rectangle(x, (captionHeight - btnH) / 2, CaptionHeight - 6, CaptionHeight - 6); 160 g.DrawImage(bitmap, rec, 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, image); 161 } 162 x += 35; 163 } 164 if (!string.IsNullOrEmpty(Text)) 165 { 166 int fontHeight = Size.Ceiling(g.MeasureString("Text", TitleBar.Font)).Height; 167 int fontWidth = Size.Ceiling(g.MeasureString(Text, TitleBar.Font)).Width; 168 Brush brush = new SolidBrush(ForeColor); 169 if (TitleAlign == ContentAlignment.MiddleLeft) 170 g.DrawString(Text, TitleBar.Font, brush, x, (CaptionHeight - fontHeight) / 2); 171 else if (TitleAlign == ContentAlignment.TopCenter) 172 g.DrawString(Text, TitleBar.Font, brush, (Width - fontWidth)/2, boderWidth); 173 174 175 } 176 } 177 #endregion 178 179 #region 其他事件 180 private void TitleBar_MouseClick(object sender, MouseEventArgs e) 181 { 182 if (e.Clicks != 1 || e.Button != MouseButtons.Left) 183 return; 184 switch (_mouseState) 185 { 186 case MouseState.CloseHover: 187 Close(); 188 break; 189 case MouseState.MaxHover: 190 WindowState = WindowState == FormWindowState.Maximized ? FormWindowState.Normal : FormWindowState.Maximized; 191 return; 192 case MouseState.MinHover: 193 WindowState = FormWindowState.Minimized; 194 return; 195 } 196 _mouseState = MouseState.Normal; 197 } 198 199 private void TitleBar_MouseDown(object sender, MouseEventArgs e) 200 { 201 if (_mouseState != MouseState.CaptionHover) 202 return; 203 204 if (e.Clicks == 1) 205 { 206 Win32API.ReleaseCapture(); 207 Win32API.SendMessage(Handle, Win32API.WM_SYSCOMMAND, Win32API.SC_MOVE + Win32API.HTCAPTION, 0); 208 } 209 else if (e.Clicks == 2 && e.Button == MouseButtons.Left) 210 { 211 WindowState = WindowState == FormWindowState.Maximized ? FormWindowState.Normal : FormWindowState.Maximized; 212 } 213 } 214 215 private void TitleBar_MouseMove(object sender, MouseEventArgs e) 216 { 217 Point p = new Point(e.X, e.Y); 218 if (closeRect != Rectangle.Empty && closeRect.Contains(p)) 219 _mouseState = MouseState.CloseHover; 220 else if (minRect != Rectangle.Empty && minRect.Contains(p)) 221 _mouseState = MouseState.MinHover; 222 else if (maxRect != Rectangle.Empty && maxRect.Contains(p)) 223 _mouseState = MouseState.MaxHover; 224 else if (captionRect != Rectangle.Empty && captionRect.Contains(p)) 225 _mouseState = MouseState.CaptionHover; 226 else 227 _mouseState = MouseState.Normal; 228 229 Invalidate(captionRect, true); 230 } 231 232 private void TitleBar_MouseLeave(object sender, EventArgs e) 233 { 234 _mouseState = MouseState.Out; 235 Invalidate(captionRect, true); 236 } 237 238 private void FormEx_SizeChanged(object sender, EventArgs e) 239 { 240 Invalidate(captionRect, true); 241 } 242 #endregion 243 244 #region 调整窗口大小 245 246 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) 247 { 248 if (width == Width + 16) 249 return; 250 base.SetBoundsCore(x, y, width, height, specified); 251 } 252 253 protected override void WndProc(ref Message m) 254 { 255 if (_mouseState == MouseState.CloseHover || _mouseState == MouseState.MinHover) 256 { 257 base.WndProc(ref m); 258 return; 259 } 260 switch (m.Msg) 261 { 262 case 0x0084: 263 base.WndProc(ref m); 264 Point vPoint = new Point((int)m.LParam & 0xFFFF, 265 (int)m.LParam >> 16 & 0xFFFF); 266 vPoint = PointToClient(vPoint); 267 if (vPoint.X <= 10) 268 if (vPoint.Y <= 10) 269 m.Result = (IntPtr)Win32API.Guying_HTTOPLEFT; 270 else if (vPoint.Y >= ClientSize.Height - 10) 271 m.Result = (IntPtr)Win32API.Guying_HTBOTTOMLEFT; 272 else m.Result = (IntPtr)Win32API.Guying_HTLEFT; 273 else if (vPoint.X >= ClientSize.Width - 10) 274 if (vPoint.Y <= 10) 275 m.Result = (IntPtr)Win32API.Guying_HTTOPRIGHT; 276 else if (vPoint.Y >= ClientSize.Height - 10) 277 m.Result = (IntPtr)Win32API.Guying_HTBOTTOMRIGHT; 278 else m.Result = (IntPtr)Win32API.Guying_HTRIGHT; 279 else if (vPoint.Y <= 10) 280 m.Result = (IntPtr)Win32API.Guying_HTTOP; 281 else if (vPoint.Y >= ClientSize.Height - 10) 282 m.Result = (IntPtr)Win32API.Guying_HTBOTTOM; 283 break; 284 case 0x0201: //鼠标左键按下的消息 285 m.Msg = 0x00A1; //更改消息为非客户区按下鼠标 286 m.LParam = IntPtr.Zero; //默认值 287 m.WParam = new IntPtr(2); //鼠标放在标题栏内 288 base.WndProc(ref m); 289 break; 290 case 0x0083: 291 if (m.WParam != IntPtr.Zero) 292 { 293 NCCALCSIZE_PARAMS rcsize = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); 294 Marshal.StructureToPtr(rcsize, m.LParam, false); 295 } 296 m.Result = new IntPtr(1); 297 break; 298 default: 299 base.WndProc(ref m); 300 break; 301 } 302 } 303 #endregion 304 } 305 306 public enum MouseState 307 { 308 Normal, 309 MaxHover, 310 MinHover, 311 CloseHover, 312 CaptionHover, 313 Out, 314 }
因为主要目的是为了保留窗体是停靠功能,所以不能使用无边框然后直接重绘标题栏。拦截了window绘制有效区域标题栏的消息,仍然存在很多不完善的地方,如拦截边框消息后没有重新绘制原边框位置的图形,导致了原区域的透明等等。