在視窗標題列加入按鈕並非新技術(VC++ 時代即可實現),但有朋友詢問 C# WinForm 如何做,順手以 C# 實作一個範例。核心原理相同:覆寫視窗程序 (WndProc) 處理非用戶區訊息 (WM_NCxxxx),但過程中學到兩個 C# 技巧:
WndProc 即可攔截視窗訊息,無需呼叫 SetWindowLong API。Graphics.FromHdc() 將 Windows API 的 HDC 轉為 System.Drawing.Graphics,即可使用 GDI+ 繪圖功能,避開繁瑣的 GDI API 呼叫。完整程式碼示範:建立自訂標題列按鈕區域、處理 WM_NCPAINT 繪製漸層按鈕、處理 WM_NCLBUTTONDOWN 偵測點擊、處理 SizeChanged 更新按鈕位置。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsApplication2
{
public class Form1 : System.Windows.Forms.Form
{
private Rectangle m_rect = new Rectangle(205, 4, 16, 16);
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("User32.dll")]
private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case 0x85: // WM_NCPAINT
case 0x86: // WM_NCACTIVATE
IntPtr hDC = GetWindowDC(m.HWnd);
Graphics gs = Graphics.FromHdc(hDC);
gs.FillRectangle(new LinearGradientBrush(m_rect, Color.Pink, Color.Purple, LinearGradientMode.BackwardDiagonal), m_rect);
StringFormat strFmt = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
gs.DrawString("✓", this.Font, Brushes.BlanchedAlmond, m_rect, strFmt);
gs.Dispose();
ReleaseDC(m.HWnd, hDC);
break;
case 0xA1: // WM_NCLBUTTONDOWN
Point mousePoint = new Point((int)m.LParam);
mousePoint.Offset(-this.Left, -this.Top);
if (m_rect.Contains(mousePoint)) MessageBox.Show("hello");
break;
}
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
m_rect.X = this.Bounds.Width - 95;
m_rect.Y = 4;
m_rect.Width = m_rect.Height = 16;
}
[STAThread]
static void Main() { Application.Run(new Form1()); }
}
}