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

C# 發現之旅第七講:C# 圖形開發進階 - 簽名軌跡擷取與 XML 序列化

     編輯:關於C#

專案目標:可儲存、重繪、匯出圖片的電子簽名元件

簽名軟體使用者介面

功能需求:

  • 使用者啟動/結束簽名模式。
  • 簽名模式下,按住滑鼠繪製筆劃,釋放結束單筆劃;一個簽名可含多筆不連續筆劃。
  • 簽名資料可序列化為 XML 檔案,支援載入還原。
  • 可匯出含簽名圖形的圖片檔案。

文件物件模型 (DOM) 設計

三層樹狀結構對應簽名資料層次:

  1. PointArrayList:可變長度點座標集合,對應單一筆劃軌跡。實作 ICollection、自訂列舉器、Offset 位移、Bounds 外接矩形。
  2. PenMarkInfo:單一簽名資訊。屬性含簽名者、時間、線條顏色/寬度、PointArrayList[] Lines(多筆劃)。標記 [XmlArrayItem(typeof(PointArrayList), ElementName="Line")] 控制 XML 序列化元素名稱。提供 Draw(Graphics, ClipRect) 繪製與 CreateBitmap() 匯出圖片。
  3. PenMarkInfoDocument:整份簽名文件,含多個 PenMarkInfo。封裝 Save(XmlWriter) 與靜態 Load(string) 實現 XML 序列化/反序列化。

C# 列舉器模式深度解析

PointArrayList 實作 ICollection → IEnumerable,支援 foreach 遍歷。自訂列舉器需定義兩類別:實作 IEnumerable(提供 GetEnumerator())與實作 IEnumerator(MoveNext()、Current、Reset())。內部實作無限制,可依需求設計各式遍歷邏輯(反向、篩選、投影等)。

PenMarkInfo 核心繪圖與匯出

public void Draw(Graphics g, Rectangle ClipRectangle) {
    using (Pen pen = new Pen(this.Color, this.LineWidth)) {
        foreach (PointArrayList line in myLines) {
            if (ClipRectangle.IntersectsWith(line.Bounds))
                g.DrawLines(pen, line.ToArray()); // 關鍵:DrawLines 連續繪製 N-1 線段,端點自動連接處理
        }
    }
}
public Bitmap CreateBitmap() {
    Rectangle bounds = this.Bounds;
    Bitmap bmp = new Bitmap(bounds.Width, bounds.Height);
    using (Graphics g = Graphics.FromImage(bmp)) {
        g.TranslateTransform(-bounds.Left, -bounds.Top); // 座標平移至原點
        Draw(g, bounds);
    }
    return bmp;
}

DrawLines 優於多次 DrawLine:相鄰線段端點經連接處理(斜接/圓角/平角),粗線條或放大時不會出現斷裂空隙。CreateBitmap 展示「記憶體中建立圖片」通用模式:new Bitmap(w,h) → Graphics.FromImage(bmp) → 繪圖 → 回傳 Bitmap,適用於 ASP.NET、命令列、Windows Service 等無顯示裝置環境。

XML 序列化與反序列化

public void Save(XmlWriter writer) {
    XmlSerializer ser = new XmlSerializer(this.GetType());
    ser.Serialize(writer, this);
}
public static PenMarkInfoDocument Load(string strFileName) {
    XmlTextReader reader = new XmlTextReader(strFileName);
    XmlSerializer ser = new XmlSerializer(typeof(PenMarkInfoDocument));
    PenMarkInfoDocument doc = (PenMarkInfoDocument)ser.Deserialize(reader);
    reader.Close();
    return doc;
}

XmlSerializer (命名空間 System.Xml.Serialization) 自動依公開屬性/欄位序列化。反序列化必須建立新物件,無法填充既有實例。此機制為 Web Service 基礎:伺服端序列化為 XML → HTTP 傳輸 → 客戶端反序列化為物件。

使用者控制項 PenMarkControl:互動邏輯與繪圖優化

繼承 UserControl,核心功能:

繪製管線 OnPaint

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y); // 捲動座標轉換
    Rectangle ClipRect = e.ClipRectangle;
    ClipRect.Offset(-AutoScrollPosition.X, -AutoScrollPosition.Y);
    ArrayList list = new ArrayList(); list.AddRange(myDocument);
    if (Marking) list.Add(myCurrentInfo); // 包含正在編輯的簽名
    foreach (PenMarkInfo info in list) info.Draw(e.Graphics, ClipRect);
    if (myCurrentInfo != null) {
        Rectangle rect = myCurrentInfo.Bounds;
        ControlPaint.DrawFocusRectangle(e.Graphics, rect); // 繪製焦點虛線框
    }
}

座標轉換關鍵:AutoScrollPosition 為負值(如 (-100,-50) 表示內容向左上捲動 100,50),TranslateTransform 以正值補正,使文件座標 (0,0) 映射至可視區左上角。剪切矩形同理反向轉換為文件座標系。

狀態機:Marking、BeginMark、EndMark

public bool Marking { get { return myCurrentInfo != null && !myDocument.Contains(myCurrentInfo); } }
public void BeginMark(string UserName, int LineWidth) {
    if (myCurrentInfo != null) Invalidate(myCurrentInfo.Bounds); // 刷新舊焦點框
    myCurrentInfo = new PenMarkInfo { Creator = UserName, CreationTime = DateTime.Now, LineWidth = LineWidth };
}
public void EndMark() {
    if (Marking) {
        if (myCurrentInfo.Lines.Count > 0) myDocument.Add(myCurrentInfo);
        else myCurrentInfo = null;
    }
}

滑鼠事件處理與髒矩形優化

OnMouseDown:

  • 簽名模式:初始化 myCurrentLine、LastPoint 開始新筆劃。
  • 非簽名模式:座標轉換後掃描現有簽名筆劃點,若距離平方 < 13 (≈3.6 像素) 則命中,設為 myCurrentInfo 並宣告髒矩形重繪(舊焦點框消失 + 新焦點框出現)。使用 Rectangle.Union 合併新舊外接矩形為單一髒矩形,經座標轉換後呼叫 Invalidate(dirtyRect) 部分重繪。巧用 goto 跳出三層 foreach 迴圈(單一出口原則下的例外實用選擇)。

OnMouseMove(高頻事件):

protected override void OnMouseMove(MouseEventArgs e) {
    base.OnMouseMove(e);
    if (Marking && myCurrentLine != null) {
        using (Graphics g = CreateGraphics()) // 直接取得控制項繪圖面,繞過重繪機制
        using (Pen p = new Pen(myCurrentInfo.Color, myCurrentInfo.LineWidth))
            g.DrawLine(p, LastPoint, new Point(e.X, e.Y)); // 即時回饋
        LastPoint = new Point(e.X, e.Y);
        Point pt = new Point(e.X - AutoScrollPosition.X, e.Y - AutoScrollPosition.Y);
        myCurrentLine.Add(pt); // 記錄文件座標
    }
}

滑鼠移動頻率極高,採 CreateGraphics() 即時繪圖(類似 Win32 GetDC),避免觸發 Invalidate/OnPaint 完整流程,極大降低延遲。同步記錄文件座標至 myCurrentLine。

OnMouseUp:

protected override void OnMouseUp(MouseEventArgs e) {
    base.OnMouseUp(e);
    if (Marking && myCurrentLine != null) {
        Rectangle rect = myCurrentLine.Bounds;
        if (rect.Width > 5 || rect.Height >= 5) // 忽略極微小誤觸筆劃
            myCurrentInfo.Lines.Add(myCurrentLine);
        myCurrentLine = null;
    }
}

類似 Windows 雙擊判斷邏輯:過小筆劃視為誤觸忽略,提升使用者體驗。

髒矩形技術總結

主動宣告介面區域失效時,僅收集真正需重繪區域,計算最小外接矩形(髒矩形),傳遞 Invalidate(dirtyRect)。OnPaint 的 e.ClipRectangle 即為此髒矩形,繪圖邏輯可配合 ClipRectangle.IntersectsWith 跳過非可見區域運算。本專案於切換焦點簽名、滑鼠命中選取時均應用此技術,顯著提升重繪效能。

元件測試與部署

  1. 編譯專案,於工具箱「我的使用者控制項」頁面右鍵「新增/移除項目」,瀏覽選取輸出的 DLL/EXE,勾選 PenMarkControl 加入工具箱。
  2. 表單拖放控制項,撰寫測試程式碼驗證簽名、儲存 XML、載入、匯出圖片等功能。
  3. 專案屬性設為「類別庫」重新編譯,產出 DLL 即為可部署元件。

本講核心收穫

  • 髒矩形與 CreateGraphics() 即時繪圖:兩種不同頻率重繪場景的最佳化策略。
  • 文件物件模型 (DOM) 設計:將業務資料結構化為可序列化、可遍歷、可繪製的物件樹。
  • XML 序列化實戰:XmlSerializer 自動化雙向轉換,XmlArrayItem 控制輸出格式。
  • 記憶體圖片生成通用模式:Bitmap + Graphics.FromImage 脫離顯示裝置繪圖。
  • 座標系轉換與捲動支援:TranslateTransform + AutoScrollPosition 正確處理文件/控制項/螢幕三座標系映射。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved