本文整理自《C# 網絡應用編程基礎練習題與答案(九)》,系統梳理 **GDI+ 三大坐標系統**、**Graphics 物件獲取方式**、**繪圖筆刷與變換**、**PictureBox/Panel 滾動瀏覽大圖** 等核心知識點,並提供可直接運行的 WinForms 範例碼。
| 坐標系統 | 原點位置 | 單位 | 可變換 | 用途 |
|---|---|---|---|---|
| 世界坐標 (World) | 邏輯定義 (如文檔左上角) | 自訂 (英寸、毫米、像素...) | ✅ 平移、旋轉、縮放 (`Graphics.Transform`) | 邏輯繪圖座標,與裝置無關 |
| 頁面坐標 (Page) | 用戶端區域左上角 (0,0) | **由 `PageUnit` / `PageScale` 設定** (預設像素) | ❌ 固定參考標準 | 世界座標經 `Transform` 轉換後的中介座標 |
| 裝置坐標 (Device) | 螢幕/印表機物理左上角 | **像素 / 點 (1/72 吋)** | ❌ 硬體決定 | 最終光柵化輸出座標 |
流向:世界座標 → (`Graphics.Transform`) → 頁面座標 → (`PageUnit/Scale`) → 裝置座標。
共同點:皆為左手制 (X 右正、Y 下正),原點 (0,0)。
private void Form_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
// 交叉斜線筆刷:紅前景、黑背景
using (var brush = new HatchBrush(HatchStyle.Cross, Color.Red, Color.Black))
{
var rects = new[]
{
new Rectangle(0, 0, 50, 100),
new Rectangle(50, 100, 100, 50),
new Rectangle(150, 150, 50, 100)
};
g.FillRectangles(brush, rects); // 一次繪製陣列,效能優於迴圈
}
}
核心:**`TextureBrush` + `FillRectangle/Ellipse` + `WrapMode.Clamp` (邊緣拉伸不平鋪)**
private string _imagePath = "";
private int _shapeMode = 0; // 1=矩形, 2=橢圓, 3=圓形
private void btnOpen_Click(object sender, EventArgs e)
{
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
_imagePath = openFileDialog.FileName;
label.Invalidate(); // 觸發重繪
}
}
private void label_Paint(object sender, PaintEventArgs e)
{
if (string.IsNullOrEmpty(_imagePath)) return;
using (var bmp = new Bitmap(_imagePath))
using (var brush = new TextureBrush(bmp, WrapMode.Clamp))
{
var g = e.Graphics;
var rect = label.ClientRectangle;
switch (_shapeMode)
{
case 1: // 矩形填滿
g.FillRectangle(brush, rect);
break;
case 2: // 橢圓填滿控制項範圍
g.FillEllipse(brush, rect);
break;
case 3: // 正圓:以短邊為直徑,置中
int d = Math.Min(rect.Width, rect.Height);
int x = (rect.Width - d) / 2;
int y = (rect.Height - d) / 2;
g.FillEllipse(brush, x, y, d, d);
break;
}
}
}
// 按鈕切換模式
private void btnRect_Click(object s, EventArgs e) { _shapeMode = 1; label.Invalidate(); }
private void btnEllipse_Click(object s, EventArgs e) { _shapeMode = 2; label.Invalidate(); }
private void btnCircle_Click(object s, EventArgs e) { _shapeMode = 3; label.Invalidate(); }
**關鍵**:`Panel.AutoScroll = true`,`PictureBox.SizeMode = AutoSize`,將 `PictureBox` 放入 `Panel`,圖片大於 Panel 時自動出現捲軸。
// 設計器設定:
// panel.AutoScroll = true;
// pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
// pictureBox.Parent = panel; (或設計器拖入)
private void btnOpenImage_Click(object sender, EventArgs e)
{
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
try
{
pictureBox.Image = new Bitmap(openFileDialog.FileName);
// PictureBox 會自動調整大小適應圖片
// Panel 偵測到子控制項超出可視區域,自動顯示捲軸
}
catch (Exception ex)
{
MessageBox.Show($"開啟失敗: {ex.Message}");
}
}
}
**核心**:`TextureBrush` 的 `ScaleTransform`、`RotateTransform`、`TranslateTransform` **疊加順序影響結果** (先縮放、再旋轉、再平移最直觀)。作用於**筆刷座標系**,而非 `Graphics.Transform`。
// UI:5 個 NumericUpDown 對應 ScaleX, ScaleY, RotateAngle, TranslateX, TranslateY
// 其 ValueChanged 事件均呼叫 pictureBox.Invalidate();
private string _imgPath = "";
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
if (string.IsNullOrEmpty(_imgPath)) return;
using (var bmp = new Bitmap(_imgPath))
using (var brush = new TextureBrush(bmp, WrapMode.Clamp))
{
var g = e.Graphics;
// 1. 縮放 (百分比轉小數)
float sx = (float)numScaleX.Value / 100f;
float sy = (float)numScaleY.Value / 100f;
brush.ScaleTransform(sx, sy);
// 2. 旋轉 (度數)
float angle = (float)numRotate.Value;
brush.RotateTransform(angle);
// 3. 平移 (像素)
float tx = (float)numTranslateX.Value;
float ty = (float)numTranslateY.Value;
brush.TranslateTransform(tx, ty);
// 繪製填滿客戶區
g.FillRectangle(brush, this.ClientRectangle);
}
}