
功能需求:
三層樹狀結構對應簽名資料層次:
ICollection、自訂列舉器、Offset 位移、Bounds 外接矩形。PointArrayList[] Lines(多筆劃)。標記 [XmlArrayItem(typeof(PointArrayList), ElementName="Line")] 控制 XML 序列化元素名稱。提供 Draw(Graphics, ClipRect) 繪製與 CreateBitmap() 匯出圖片。PenMarkInfo。封裝 Save(XmlWriter) 與靜態 Load(string) 實現 XML 序列化/反序列化。PointArrayList 實作 ICollection → IEnumerable,支援 foreach 遍歷。自訂列舉器需定義兩類別:實作 IEnumerable(提供 GetEnumerator())與實作 IEnumerator(MoveNext()、Current、Reset())。內部實作無限制,可依需求設計各式遍歷邏輯(反向、篩選、投影等)。
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 等無顯示裝置環境。
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,核心功能:
OnPaintprotected 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、EndMarkpublic 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 開始新筆劃。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 跳過非可見區域運算。本專案於切換焦點簽名、滑鼠命中選取時均應用此技術,顯著提升重繪效能。
PenMarkControl 加入工具箱。CreateGraphics() 即時繪圖:兩種不同頻率重繪場景的最佳化策略。XmlSerializer 自動化雙向轉換,XmlArrayItem 控制輸出格式。Bitmap + Graphics.FromImage 脫離顯示裝置繪圖。TranslateTransform + AutoScrollPosition 正確處理文件/控制項/螢幕三座標系映射。