首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 自訂控制項開發入門:基於 .NET SDK 建立時鐘控制項完整實戰

     編輯:關於C語言

前言

Windows Forms 自訂控制項是豐富 UI 元件庫的核心技能。本文以建立一個類比時鐘控制項為例,完整演示:專案結構建立、Win32 API 呼叫取得系統時間、GDI+ 繪製表盤/指針、屬性系統整合(分類、描述、預設值)、重寫 OnPaint、資源管理、設計時支援、測試宿主兩種方式。代碼基於 .NET Framework 1.1 SDK 範本,原理通用於後續所有版本。

專案建立與範本準備

  1. 複製 SDK 範本:...\Samples\QuickStart\winforms\samples\Cs\WritingControls\helloworldcontrol → MyControl
  2. 重命名檔案:HelloWorldControl.cs → MyControl.cs、.resx 同步
  3. 修改專案檔:Makefile、HostApp.cs 內類別名稱
  4. nmake all 建立 MyControl.dll、MyControl.exe (宿主測試)

核心代碼實現

1. Win32 互操作:取得高精度本地時間


using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class SYSTEMTIME {
    public ushort wYear, wMonth, wDayOfWeek, wDay;
    public ushort wHour, wMinute, wSecond, wMilliseconds;
}

public static class NativeMethods {
    [DllImport("kernel32.dll")]
    public static extern void GetLocalTime(out SYSTEMTIME lpSystemTime);
}

為何不用 DateTime.Now?教學展示 P/Invoke;實務中 DateTime.Now 精度足夠(~15ms),GetLocalTime 同樣受系統時鐘解析度限制。

2. 控制項類別骨架


using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Timers;

public class MyControl : Control {
    // 成員欄位
    private Color _handColor = Color.White;
    private Color _faceColor = Color.Blue;
    private bool _active = false;
    private Timer _timer;

    // 建構函數
    public MyControl() {
        SetStyle(ControlStyles.Opaque, false);
        SetStyle(ControlStyles.ResizeRedraw, true);
        _timer = new Timer(1000); // 1 秒
        _timer.Elapsed += OnTimerTick;
    }

    // 屬性系統整合
    [Category("Clock"), Description("指針顏色"), DefaultValue(typeof(Color), "White")]
    public Color HandColor {
        get => _handColor;
        set { _handColor = value; Invalidate(); Update(); }
    }

    [Category("Clock"), Description("表盤顏色"), DefaultValue(typeof(Color), "Blue")]
    public Color FaceColor {
        get => _faceColor;
        set { _faceColor = value; Invalidate(); Update(); }
    }

    [Category("Clock"), Description("是否運行")]
    public bool Active {
        get => _active;
        set { _active = value; _timer.Enabled = value; }
    }

    // 重寫 OnPaint:核心繪製邏輯
    protected override void OnPaint(PaintEventArgs pe) {
        base.OnPaint(pe);
        Graphics g = pe.Graphics;
        Rectangle rc = ClientRectangle;

        // 1. 繪製表盤背景
        using (Brush faceBrush = new SolidBrush(_faceColor)) {
            g.FillEllipse(faceBrush, rc);
        }

        // 2. 取得系統時間
        NativeMethods.GetLocalTime(out SYSTEMTIME st);

        // 3. 繪製指針 (時/分/秒)
        Point center = new Point(rc.Width / 2, rc.Height / 2);
        int radius = Math.Min(rc.Width, rc.Height) / 2 - 5;

        DrawHand(g, center, radius * 0.5f, (st.wHour % 12) * 30 + st.wMinute * 0.5f, _handColor, 3); // 時針
        DrawHand(g, center, radius * 0.8f, st.wMinute * 6 + st.wSecond * 0.1f, _handColor, 2);       // 分針
        DrawHand(g, center, radius * 0.9f, st.wSecond * 6, Color.Red, 1);                              // 秒針
    }

    // 輔助:繪製單一指針
    private void DrawHand(Graphics g, Point center, float length, float angleDeg, Color color, int width) {
        using (Pen pen = new Pen(color, width)) {
            double rad = (angleDeg - 90) * Math.PI / 180.0; // 12 點鐘方向為 -90 度
            PointF end = new PointF(
                center.X + length * (float)Math.Cos(rad),
                center.Y + length * (float)Math.Sin(rad));
            g.DrawLine(pen, center, end);
        }
    }

    // Timer 事件:觸發重繪
    private void OnTimerTick(object sender, ElapsedEventArgs e) {
        if (InvokeRequired) {
            Invoke(new Action(Invalidate));
        } else {
            Invalidate();
        }
    }

    // 資源清理
    protected override void Dispose(bool disposing) {
        if (disposing) {
            _timer?.Dispose();
        }
        base.Dispose(disposing);
    }
}

關鍵技術點解析

  1. SetStyle:ResizeRedraw 尺寸變化自動 Invalidate;Opaque 告知不透明,減少閃爍
  2. 屬性特性:[Category]、[Description]、[DefaultValue] 讓屬性在設計器屬性視窗正確顯式、支援重設
  3. Invalidate/Update:Invalidate() 標記需重繪,Update() 強制同步重繪(屬性設值時即時反饋)
  4. Timer 執行緒安全:System.Timers.Timer 在 ThreadPool 執行緒觸發 → 必須 InvokeRequired/Invoke 回 UI 執行緒呼叫 Invalidate
  5. using 區塊:Graphics、Pen、Brush 實作 IDisposable,及時釋放 GDI 物件,防止句柄洩漏
  6. 座標轉換:數學極座標 → 螢幕笛卡兒座標,12 點鐘方向為 -90° (或 270°)

測試與設計時驗證

方式一:專案自帶宿主程式


// HostApp.cs 片段
Application.Run(new HostForm()); // HostForm 包含 MyControl 實例

方式二:Visual Studio 設計器工具箱

  1. 建立新 WinForms 專案
  2. 工具箱右鍵 → 選擇項目 → 瀏覽 → 選 MyControl.dll
  3. 拖曳 MyControl 到表單 → 屬性視窗調整 HandColor、FaceColor、Active
  4. F5 執行 → 看到即時運行的時鐘

進階擴展方向

  • 雙緩衝:SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true) 消除閃爍
  • 設計時序列化:[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 支援巢狀屬性
  • 智慧標籤:實作 IComponent、DesignerActionList 提供設計器右鍵快速操作
  • 資源內嵌:圖片、字體內嵌於組件,Assembly.GetManifestResourceStream 載入
  • .NET Core / 5+ 遷移:專案 SDK 化、System.Windows.Forms NuGet、設計器支援 Visual Studio 16.6+

總結

自訂控制項開發核心三步:繼承 Control、重寫 OnPaint、屬性系統整合。本文時鐘控制項展示了 P/Invoke、GDI+ 繪圖、Timer 跨執行緒 UI 更新、資源釋放、設計時屬性支援等完整工程實踐。掌握此流程,即可構建儀表板、圖表、專業輸入控制項等高階 UI 元件,構建屬於團隊的專屬控制項庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved