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

WinForm 表單設計器核心實現:虛線邊框、手柄縮放、對齊線吸附與多選操作

     編輯:關於C#

效果預覽

實現類 Visual Studio 設計器體驗:

  • 控件拖拽移動、八向手柄縮放;
  • 移動/縮放過程中實時繪製對齊線(紅色虛線),自動吸附對齊(閾值 5px);
  • 框選多控件,集體移動/縮放;
  • 虛線邊框(TargetSizeLine)+ 手柄(TargetSizeDot)組合為 TargetSizeGroup 統一管理。

核心類結構


+------------------+       1..4       +-----------------+
| TargetSizeGroup  | <>-------------- | TargetSizeLine  |  (四條邊:Top/Bottom/Left/Right)
| - Lines[]        |                  | - Rect          |
| - Dots[]         |       8          | - Cursor        |
| - OnMouseDown    | <>-------------- | TargetSizeDot   |  (八個手柄:四角+四邊中點)
| - OnMouseMove    |                  | - Direction     |
| - OnMouseUp      |                  | - Cursor        |
+------------------+                  +-----------------+
        ^
        | 綁定
        |
+------------------+
| UserControlTarget|  (繼承 UserControl,作為被設計的目標控件)
| - SizeGroup      |
+------------------+

關鍵實現細節

1. 方向枚舉與游標映射


public enum TargetDirection {
    None, TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left
}

public static Cursor GetCursor(TargetDirection dir) => dir switch {
    TargetDirection.TopLeft or TargetDirection.BottomRight => Cursors.SizeNWSE,
    TargetDirection.TopRight or TargetDirection.BottomLeft => Cursors.SizeNESW,
    TargetDirection.Top or TargetDirection.Bottom => Cursors.SizeNS,
    TargetDirection.Left or TargetDirection.Right => Cursors.SizeWE,
    _ => Cursors.Default
};

2. 手柄與邊框繪製 (OnPaint)


protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    var g = e.Graphics;
    // 繪製虛線邊框
    using (var pen = new Pen(Color.Gray, 1) { DashStyle = DashStyle.Dash }) {
        g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
    }
    // 繪製 8 個手柄 (6x6 實心方塊)
    using (var brush = new SolidBrush(Color.White)) {
        foreach (var dot in _dots) {
            g.FillRectangle(brush, dot.Rect);
            g.DrawRectangle(Pens.Gray, dot.Rect);
        }
    }
}

3. 滑鼠事件處理核心邏輯


private TargetDirection _hitDirection;
private Point _mouseStart;
private Rectangle _targetStartRect;

protected override void OnMouseDown(MouseEventArgs e) {
    _hitDirection = HitTest(e.Location); // 判斷點擊落在哪個手柄/邊框/內部
    if (_hitDirection != TargetDirection.None) {
        _mouseStart = e.Location;
        _targetStartRect = new Rectangle(Left, Top, Width, Height);
        Capture = true; // 獨佔滑鼠捕獲
    }
    base.OnMouseDown(e);
}

protected override void OnMouseMove(MouseEventArgs e) {
    if (!Capture) return;
    int dx = e.X - _mouseStart.X;
    int dy = e.Y - _mouseStart.Y;

    Rectangle newRect = _targetStartRect;
    // 根據方向計算新矩形
    switch (_hitDirection) {
        case TargetDirection.TopLeft:
            newRect = new Rectangle(_targetStartRect.X + dx, _targetStartRect.Y + dy,
                _targetStartRect.Width - dx, _targetStartRect.Height - dy);
            break;
        case TargetDirection.Top:
            newRect = new Rectangle(_targetStartRect.X, _targetStartRect.Y + dy,
                _targetStartRect.Width, _targetStartRect.Height - dy);
            break;
            // ... 其他 6 向同理
        case TargetDirection.None: // 內部拖拽移動
            newRect = new Rectangle(_targetStartRect.X + dx, _targetStartRect.Y + dy,
                _targetStartRect.Width, _targetStartRect.Height);
            break;
    }

    // 對齊線吸附邏輯
    newRect = ApplySnapLines(newRect, _hitDirection);

    // 應用新位置/大小
    SetBounds(newRect.X, newRect.Y, newRect.Width, newRect.Height);
    Invalidate(); // 重繪手柄
    base.OnMouseMove(e);
}

protected override void OnMouseUp(MouseEventArgs e) {
    Capture = false;
    _hitDirection = TargetDirection.None;
    base.OnMouseUp(e);
}

4. 對齊線吸附算法 (ApplySnapLines)


private const int SnapThreshold = 5; // 像素

private Rectangle ApplySnapLines(Rectangle rect, TargetDirection dragDir) {
    var lines = new List();
    // 收集容器內其他控件的邊緣線
    foreach (Control c in Parent.Controls) {
        if (c == this) continue;
        lines.Add(new SnapLine(SnapLineType.Vertical, c.Left, c.Top, c.Bottom));   // Left
        lines.Add(new SnapLine(SnapLineType.Vertical, c.Right, c.Top, c.Bottom));  // Right
        lines.Add(new SnapLine(SnapLineType.Horizontal, c.Top, c.Left, c.Right));  // Top
        lines.Add(new SnapLine(SnapLineType.Horizontal, c.Bottom, c.Left, c.Right)); // Bottom
    }
    // 容器邊界也作為吸附線
    lines.Add(new SnapLine(SnapLineType.Vertical, 0, 0, Parent.ClientSize.Height));
    lines.Add(new SnapLine(SnapLineType.Vertical, Parent.ClientSize.Width, 0, Parent.ClientSize.Height));
    lines.Add(new SnapLine(SnapLineType.Horizontal, 0, 0, Parent.ClientSize.Width));
    lines.Add(new SnapLine(SnapLineType.Horizontal, Parent.ClientSize.Height, 0, Parent.ClientSize.Width));

    int offsetX = 0, offsetY = 0;
    var snapLinesToDraw = new List();

    // 檢查水平吸附
    if (dragDir != TargetDirection.Left && dragDir != TargetDirection.Right) {
        foreach (var line in lines.Where(l => l.Type == SnapLineType.Vertical)) {
            if (Math.Abs(rect.Left - line.Position) < SnapThreshold) {
                offsetX = line.Position - rect.Left;
                snapLinesToDraw.Add(line);
            }
            if (Math.Abs(rect.Right - line.Position) < SnapThreshold) {
                offsetX = line.Position - rect.Right;
                snapLinesToDraw.Add(line);
            }
            if (Math.Abs(rect.X + rect.Width / 2 - line.Position) < SnapThreshold) { // 中心線
                offsetX = line.Position - (rect.X + rect.Width / 2);
                snapLinesToDraw.Add(line);
            }
        }
    }
    // 檢查垂直吸附 (同理)
    // ...

    // 記錄需繪製的吸附線,供容器 OnPaint 繪製紅色虛線
    _snapLinesToDraw = snapLinesToDraw;

    return new Rectangle(rect.X + offsetX, rect.Y + offsetY, rect.Width, rect.Height);
}

5. 多選框選與集體操作


// 容器級別處理框選
private Point _selectStart;
private bool _isSelecting;

protected override void OnMouseDown(MouseEventArgs e) {
    if (e.Button == MouseButtons.Left && !_isDraggingControl) {
        _selectStart = e.Location;
        _isSelecting = true;
    }
    base.OnMouseDown(e);
}

protected override void OnMouseMove(MouseEventArgs e) {
    if (_isSelecting) {
        // 繪製橡皮筋矩形
        _selectionRect = GetRect(_selectStart, e.Location);
        Invalidate();
    }
    base.OnMouseMove(e);
}

protected override void OnMouseUp(MouseEventArgs e) {
    if (_isSelecting) {
        _isSelecting = false;
        // 篩選框內控件
        _selectedControls = Parent.Controls.Cast()
            .Where(c => c != this && _selectionRect.IntersectsWith(c.Bounds))
            .ToList();
        Invalidate(); // 繪製選中邊框
    }
    base.OnMouseUp(e);
}

// 集體移動:記錄所有選中控件的初始位置,移動時同步偏移
// 集體縮放:以選中區域中心為基準,按比例縮放各控件

工程化建議

  1. 雙緩衝:容器設定 DoubleBuffered = true,消除拖動閃爍。
  2. 命令模式支援 Undo/Redo:每次移動/縮放生成 MoveCommand/ResizeCommand 推入命令棧。
  3. 屬性網格聯動:選中控件改變時更新 PropertyGrid.SelectedObject,修改屬性時反向調用 SetBounds。
  4. 序列化持久化:設計時佈局輸出 JSON/XML,運行時動態加載實例化控件樹。
  5. 效能優化:大量控件時,吸附線計算改用空間索引(QuadTree/KD-Tree)將 O(N) 降為 O(log N)。

總結

WinForm 設計器核心在於 TargetSizeGroup 封裝「邊框+手柄」幾何邏輯,HitTest 判斷操作意圖,OnMouseMove 計算新幾何並套用 SnapLines 吸附。多選、對齊線、屬性網格聯動是工程化落地的關鍵細節。本文提供的核心算法與類結構可直接作為自定義報表設計器、流程圖編輯器、低代碼頁面構建器的基石。

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