Windows Forms 控制項的 Region 屬性決定其可見與可點擊區域。透過掃描圖片像素,建立由不透明像素組成的 GraphicsPath,指派給 Button.Region,即可實現任意不規則形狀按鈕。
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class IrregularButton : Button
{
protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);
this.Cursor = Cursors.Hand;
if (this.Image == null) return;
using (Bitmap bmp = new Bitmap(this.Image))
using (GraphicsPath path = CalculateControlGraphicsPath(bmp))
{
this.Region = new Region(path);
}
}
private static GraphicsPath CalculateControlGraphicsPath(Bitmap bitmap)
{
var path = new GraphicsPath();
Color transparentColor = bitmap.GetPixel(0, 0); // 假設左上角為透明色
for (int y = 0; y < bitmap.Height; y++)
{
int xStart = -1;
for (int x = 0; x < bitmap.Width; x++)
{
bool isOpaque = bitmap.GetPixel(x, y) != transparentColor;
if (isOpaque && xStart == -1)
{
xStart = x; // 找到不透明區段起點
}
else if (!isOpaque && xStart != -1)
{
// 區段結束,加入矩形
path.AddRectangle(new Rectangle(xStart, y, x - xStart, 1));
xStart = -1;
}
}
// 行尾仍在不透明區段
if (xStart != -1)
{
path.AddRectangle(new Rectangle(xStart, y, bitmap.Width - xStart, 1));
}
}
return path;
}
}
Button,設定 Image 屬性指向該圖片,Text 留空,FlatStyle = Flat、FlatAppearance.BorderSize = 0。IrregularButton(或在 Paint 事件掛上述邏輯)。Region 只需建立一次。可在 Image 屬性變更時重算,OnPaint 僅指派快取結果。GetPixel 極慢,應用 Bitmap.LockBits 直接存取記憶體像素資料,速度提升百倍。
private static GraphicsPath CalculatePathFast(Bitmap bmp)
{
var path = new GraphicsPath();
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var data = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
unsafe
{
byte* ptr = (byte*)data.Scan0;
int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
int stride = data.Stride;
byte alphaThreshold = 0; // 完全透明
for (int y = 0; y < bmp.Height; y++)
{
byte* row = ptr + y * stride;
int xStart = -1;
for (int x = 0; x < bmp.Width; x++)
{
byte alpha = row[x * bytesPerPixel + 3]; // ARGB 第 4 位元組
bool opaque = alpha > alphaThreshold;
if (opaque && xStart == -1) xStart = x;
else if (!opaque && xStart != -1)
{
path.AddRectangle(new Rectangle(xStart, y, x - xStart, 1));
xStart = -1;
}
}
if (xStart != -1) path.AddRectangle(new Rectangle(xStart, y, bmp.Width - xStart, 1));
}
}
}
finally { bmp.UnlockBits(data); }
return path;
}
需在專案屬性 → Build → Allow unsafe code 勾選。
Form.Region 同理可做異形視窗。MouseMove 即時判斷滑鼠是否在有效區域,切換游標樣式。Path、Clip 或 PointerEvents 更原生簡單。