Windows Forms 應用程式若需超越標準控制項的視覺表現,GDI+ 繪圖功能是最佳選擇。本文示範三種經典動畫效果——眨眼、彈跳球、文字閃爍——完整展示 System.Drawing 命名空間核心類別(Graphics、Bitmap、Brush、Font)與 Timer 定時器驅動重繪的實作模式。
private Image[] arrImages = new Image[4];
private void Form_Load(object sender, EventArgs e)
{
AssemblyInfoClass asm = new AssemblyInfoClass();
this.Text = asm.AsmFQName;
arrImages[0] = Properties.Resources.Eye1;
arrImages[1] = Properties.Resources.Eye2;
arrImages[2] = Properties.Resources.Eye3;
arrImages[3] = Properties.Resources.Eye4;
}
使用 VS2005 內建「關於方塊」範本,於 Load 事件填入組件中繼資料:
private void AboutBox_Load(object sender, EventArgs e)
{
AssemblyInfoClass asm = new AssemblyInfoClass();
labelProductName.Text = "產品名稱:" + asm.Product;
labelVersion.Text = "版本:" + asm.Version;
labelCopyright.Text = "版權宣告:" + asm.Copyright;
labelCompanyName.Text = "公司名稱:" + asm.Company;
textBoxDescription.Text = "細部描述:" + asm.Description;
}
選單項目叫用:new AboutBox().Show();
三個 RadioButton 控制項共用 CheckedChanged 事件,依選項設定 Timer.Interval 並觸發重繪:
private void RadioButtons_CheckedChanged(object sender, EventArgs e)
{
if (optWink.Checked) tmrAnimation.Interval = WINK_TIMER_INTERVAL;
else if (optBall.Checked) tmrAnimation.Interval = BALL_TIMER_INTERVAL;
else if (optText.Checked) tmrAnimation.Interval = TEXT_TIMER_INTERVAL;
OnResize(EventArgs.Empty); // 觸發初始繪製
}
protected override void OnResize(EventArgs ea)
{
Graphics grfx = CreateGraphics();
if (optWink.Checked)
{
this.Refresh(); // 眨眼模式僅需清空重繪
}
else if (optBall.Checked)
{
grfx.Clear(BackColor);
// 計算球半徑、移動步長、位圖尺寸
double dblRadius = Math.Min(ClientSize.Width / grfx.DpiX, ClientSize.Height / grfx.DpiY) / intBallSize;
intBallRadiusX = (int)(dblRadius * grfx.DpiX);
intBallRadiusY = (int)(dblRadius * grfx.DpiY);
intBallMoveX = (int)Math.Max(1, intBallRadiusX / intMoveSize);
intBallMoveY = (int)Math.Max(1, intBallRadiusY / intMoveSize);
// 建立離屏位圖並繪製球體
bitmap = new Bitmap(intBallBitmapWidth, intBallBitmapHeight);
grfx = Graphics.FromImage(bitmap);
grfx.Clear(BackColor);
grfx.FillEllipse(Brushes.Red, new Rectangle(intBallMoveX, intBallMoveY, 2 * intBallRadiusX, 2 * intBallRadiusY));
intBallPositionX = ClientSize.Width / 2;
intBallPositionY = ClientSize.Height / 2;
}
else if (optText.Checked)
{
grfx.Clear(BackColor); // 文字模式僅清空
}
}
private void tmrAnimation_Tick(object sender, EventArgs e)
{
Graphics grfx = CreateGraphics();
// 1. 眨眼動畫:循環播放四張圖片
if (optWink.Checked)
{
grfx.DrawImage(arrImages[intCurrentImage],
(ClientSize.Width - arrImages[intCurrentImage].Width) / 2,
(ClientSize.Height - arrImages[intCurrentImage].Height) / 2);
intCurrentImage += direction; // direction = 1 或 -1
if (intCurrentImage == 3) direction = -1;
else if (intCurrentImage == 0) direction = 1;
}
// 2. 彈跳球:更新位置、邊界檢測、碰撞反彈
else if (optBall.Checked)
{
grfx.DrawImage(bitmap,
intBallPositionX - intBallBitmapWidth / 2,
intBallPositionY - intBallBitmapHeight / 2);
intBallPositionX += intBallMoveX;
intBallPositionY += intBallMoveY;
// 左右邊界
if (intBallPositionX + intBallRadiusX >= ClientSize.Width || intBallPositionX - intBallRadiusX <= 0)
{
intBallMoveX = -intBallMoveX;
SystemSounds.Beep.Play();
}
// 上下邊界
if (intBallPositionY + intBallRadiusY >= ClientSize.Height || intBallPositionY - intBallRadiusY <= 75)
{
intBallMoveY = -intBallMoveY;
SystemSounds.Beep.Play();
}
}
// 3. 文字閃爍:線性漸層筆刷位移產生流光效果
else if (optText.Checked)
{
Font font = new Font("Microsoft Sans Serif", 48, FontStyle.Bold);
string strText = "章立民研究室";
SizeF textSize = grfx.MeasureString(strText, font);
PointF textPos = new PointF((ClientSize.Width - textSize.Width) / 2, (ClientSize.Height - textSize.Height) / 2);
PointF gradStart = new PointF(0, 0);
PointF gradEnd = new PointF(intCurrentGradientShift, 200);
LinearGradientBrush brush = new LinearGradientBrush(gradStart, gradEnd, Color.Blue, BackColor);
grfx.DrawString(strText, font, brush, textPos);
intCurrentGradientShift += intGradiantStep;
if (intCurrentGradientShift == 500) intGradiantStep = -5;
else if (intCurrentGradientShift == -50) intGradiantStep = 5;
}
}
Bitmap + Graphics.FromImage 預先繪製球體,Tick 事件僅 DrawImage,減少閃爍。DpiX/DpiY 實現裝置獨立單位換算。LinearGradientBrush 端點座標產生流光閃爍。CreateGraphics() 繪製,正式專案建議啟用表單 DoubleBuffered = true 或繼承 Control 覆寫 OnPaint。本範例展示 GDI+ 核心繪圖流程,可擴展至複雜遊戲迴圈、儀表板視覺化等場景。