znlgis 博客

GIS开发与技术分享

第十六章:命令与操作系统

16.1 命令系统概述

16.1.1 命令模式

LightCAD的操作系统基于经典的命令模式(Command Pattern)设计,将用户的每一个操作封装为独立的命令对象。这种设计带来了以下优势:

  • 可撤销/重做:每个命令记录操作前后的状态
  • 命令队列:支持命令的排队执行
  • 宏录制:支持录制和回放操作序列
  • 命令行驱动:支持通过命令行输入执行操作

16.1.2 命令系统架构

命令注册表(CommandRegistry)
    ├── 绘图命令(Draw Commands)
    │   ├── LINE    - 绘制直线
    │   ├── CIRCLE  - 绘制圆
    │   ├── ARC     - 绘制圆弧
    │   ├── PLINE   - 绘制多段线
    │   ├── RECT    - 绘制矩形
    │   └── SPLINE  - 绘制样条曲线
    ├── 编辑命令(Edit Commands)
    │   ├── MOVE    - 移动
    │   ├── COPY    - 复制
    │   ├── ROTATE  - 旋转
    │   ├── SCALE   - 缩放
    │   ├── MIRROR  - 镜像
    │   ├── TRIM    - 修剪
    │   ├── EXTEND  - 延伸
    │   ├── OFFSET  - 偏移
    │   └── FILLET  - 圆角
    ├── 视图命令(View Commands)
    │   ├── ZOOM    - 缩放视图
    │   ├── PAN     - 平移视图
    │   └── VIEW    - 切换视图
    └── 3D命令(3D Commands)
        ├── EXTRUDE - 拉伸
        ├── REVOLVE - 旋转体
        ├── LOFT    - 放样
        └── BOOLEAN - 布尔运算

16.2 命令注册与执行

16.2.1 命令注册表

public class CommandRegistry
{
    private Dictionary<string, CommandInfo> commands = new();
    private Dictionary<string, string> aliases = new();

    /// <summary>
    /// 注册命令
    /// </summary>
    public void Register(CommandInfo command)
    {
        commands[command.Name.ToUpper()] = command;

        // 注册别名
        if (command.Aliases != null)
        {
            foreach (var alias in command.Aliases)
            {
                aliases[alias.ToUpper()] = command.Name.ToUpper();
            }
        }
    }

    /// <summary>
    /// 执行命令
    /// </summary>
    public bool Execute(string commandName)
    {
        var name = commandName.ToUpper();

        // 检查别名
        if (aliases.TryGetValue(name, out var realName))
        {
            name = realName;
        }

        if (commands.TryGetValue(name, out var command))
        {
            command.Action?.Invoke();
            return true;
        }

        return false;
    }

    /// <summary>
    /// 获取所有命令
    /// </summary>
    public IEnumerable<CommandInfo> GetAllCommands()
    {
        return commands.Values;
    }

    /// <summary>
    /// 搜索命令(支持模糊匹配)
    /// </summary>
    public IEnumerable<CommandInfo> Search(string keyword)
    {
        var upper = keyword.ToUpper();
        return commands.Values.Where(c =>
            c.Name.Contains(upper) ||
            c.DisplayName.Contains(keyword, StringComparison.OrdinalIgnoreCase));
    }
}

public class CommandInfo
{
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
    public string[] Aliases { get; set; }
    public Action Action { get; set; }
    public string IconPath { get; set; }
    public Keys ShortcutKey { get; set; }
}

16.2.2 内置命令注册

public class BuiltInCommands
{
    public static void RegisterAll(CommandRegistry registry)
    {
        // 绘图命令
        registry.Register(new CommandInfo
        {
            Name = "LINE",
            DisplayName = "直线",
            Category = "绘图",
            Aliases = new[] { "L" },
            Action = () => ActionManager.StartAction(new DrawLineAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "CIRCLE",
            DisplayName = "圆",
            Category = "绘图",
            Aliases = new[] { "C" },
            Action = () => ActionManager.StartAction(new DrawCircleAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "ARC",
            DisplayName = "圆弧",
            Category = "绘图",
            Aliases = new[] { "A" },
            Action = () => ActionManager.StartAction(new DrawArcAction())
        });

        // 编辑命令
        registry.Register(new CommandInfo
        {
            Name = "MOVE",
            DisplayName = "移动",
            Category = "编辑",
            Aliases = new[] { "M" },
            Action = () => ActionManager.StartAction(new MoveAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "COPY",
            DisplayName = "复制",
            Category = "编辑",
            Aliases = new[] { "CO", "CP" },
            Action = () => ActionManager.StartAction(new CopyAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "ROTATE",
            DisplayName = "旋转",
            Category = "编辑",
            Aliases = new[] { "RO" },
            Action = () => ActionManager.StartAction(new RotateAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "SCALE",
            DisplayName = "缩放",
            Category = "编辑",
            Aliases = new[] { "SC" },
            Action = () => ActionManager.StartAction(new ScaleAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "MIRROR",
            DisplayName = "镜像",
            Category = "编辑",
            Aliases = new[] { "MI" },
            Action = () => ActionManager.StartAction(new MirrorAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "OFFSET",
            DisplayName = "偏移",
            Category = "编辑",
            Aliases = new[] { "O" },
            Action = () => ActionManager.StartAction(new OffsetAction())
        });

        registry.Register(new CommandInfo
        {
            Name = "TRIM",
            DisplayName = "修剪",
            Category = "编辑",
            Aliases = new[] { "TR" },
            Action = () => ActionManager.StartAction(new TrimAction())
        });
    }
}

16.3 撤销/重做系统

16.3.1 操作记录

public class UndoRedoManager
{
    private Stack<IUndoableAction> undoStack = new();
    private Stack<IUndoableAction> redoStack = new();
    private const int MaxUndoLevels = 100;

    /// <summary>
    /// 记录可撤销的操作
    /// </summary>
    public void Record(IUndoableAction action)
    {
        undoStack.Push(action);
        redoStack.Clear(); // 新操作清除重做栈

        // 限制撤销深度
        while (undoStack.Count > MaxUndoLevels)
        {
            // 移除最底部的操作
            var temp = new Stack<IUndoableAction>(undoStack.Reverse());
            temp.Pop();
            undoStack = new Stack<IUndoableAction>(temp.Reverse());
        }
    }

    /// <summary>
    /// 撤销
    /// </summary>
    public bool Undo()
    {
        if (undoStack.Count == 0) return false;

        var action = undoStack.Pop();
        action.Undo();
        redoStack.Push(action);
        return true;
    }

    /// <summary>
    /// 重做
    /// </summary>
    public bool Redo()
    {
        if (redoStack.Count == 0) return false;

        var action = redoStack.Pop();
        action.Redo();
        undoStack.Push(action);
        return true;
    }

    public bool CanUndo => undoStack.Count > 0;
    public bool CanRedo => redoStack.Count > 0;
    public string UndoDescription => undoStack.Count > 0
        ? undoStack.Peek().Description : null;
    public string RedoDescription => redoStack.Count > 0
        ? redoStack.Peek().Description : null;
}

public interface IUndoableAction
{
    string Description { get; }
    void Undo();
    void Redo();
}

16.3.2 具体操作实现

/// <summary>
/// 添加实体操作
/// </summary>
public class AddEntityAction : IUndoableAction
{
    private LcEntity entity;
    private LcDocument document;

    public string Description => $"添加 {entity.TypeName}";

    public AddEntityAction(LcEntity entity, LcDocument document)
    {
        this.entity = entity;
        this.document = document;
    }

    public void Undo()
    {
        document.Entities.Remove(entity);
    }

    public void Redo()
    {
        document.Entities.Add(entity);
    }
}

/// <summary>
/// 删除实体操作
/// </summary>
public class DeleteEntityAction : IUndoableAction
{
    private List<LcEntity> entities;
    private LcDocument document;

    public string Description =>
        $"删除 {entities.Count} 个实体";

    public void Undo()
    {
        foreach (var entity in entities)
        {
            document.Entities.Add(entity);
        }
    }

    public void Redo()
    {
        foreach (var entity in entities)
        {
            document.Entities.Remove(entity);
        }
    }
}

/// <summary>
/// 移动实体操作
/// </summary>
public class MoveEntityAction : IUndoableAction
{
    private List<LcEntity> entities;
    private Vector2d displacement;

    public string Description =>
        $"移动 {entities.Count} 个实体";

    public void Undo()
    {
        var reverseTranslation = Matrix4d.CreateTranslation(
            -displacement.X, -displacement.Y, 0);

        foreach (var entity in entities)
        {
            entity.TransformBy(reverseTranslation);
        }
    }

    public void Redo()
    {
        var translation = Matrix4d.CreateTranslation(
            displacement.X, displacement.Y, 0);

        foreach (var entity in entities)
        {
            entity.TransformBy(translation);
        }
    }
}

16.4 元素操作(ElementAction)

16.4.1 ElementAction概述

ElementAction是LightCAD.Runtime中负责处理高层实体操作的核心类,文件大小达43KB,包含了大量的实体编辑逻辑:

public class ElementAction
{
    private LcDocument document;
    private SelectionManager selectionManager;
    private UndoRedoManager undoManager;

    /// <summary>
    /// 移动选中的实体
    /// </summary>
    public void Move(Vector2d displacement)
    {
        var entities = selectionManager.SelectedEntities.ToList();
        if (entities.Count == 0) return;

        var translation = Matrix4d.CreateTranslation(
            displacement.X, displacement.Y, 0);

        foreach (var entity in entities)
        {
            entity.TransformBy(translation);
        }

        undoManager.Record(new MoveEntityAction
        {
            entities = entities,
            displacement = displacement
        });
    }

    /// <summary>
    /// 复制选中的实体
    /// </summary>
    public void Copy(Vector2d displacement)
    {
        var entities = selectionManager.SelectedEntities.ToList();
        if (entities.Count == 0) return;

        var copies = new List<LcEntity>();
        var translation = Matrix4d.CreateTranslation(
            displacement.X, displacement.Y, 0);

        foreach (var entity in entities)
        {
            var copy = entity.Clone();
            copy.TransformBy(translation);
            document.Entities.Add(copy);
            copies.Add(copy);
        }

        undoManager.Record(new AddEntitiesAction(copies, document));
    }

    /// <summary>
    /// 旋转选中的实体
    /// </summary>
    public void Rotate(Point2d center, double angle)
    {
        var entities = selectionManager.SelectedEntities.ToList();
        if (entities.Count == 0) return;

        var toOrigin = Matrix4d.CreateTranslation(-center.X, -center.Y, 0);
        var rotation = Matrix4d.CreateRotationZ(angle);
        var backToPos = Matrix4d.CreateTranslation(center.X, center.Y, 0);
        var transform = backToPos * rotation * toOrigin;

        foreach (var entity in entities)
        {
            entity.TransformBy(transform);
        }

        undoManager.Record(new TransformEntityAction(
            entities, transform, $"旋转 {entities.Count} 个实体"));
    }

    /// <summary>
    /// 缩放选中的实体
    /// </summary>
    public void Scale(Point2d center, double factor)
    {
        var entities = selectionManager.SelectedEntities.ToList();
        if (entities.Count == 0) return;

        var toOrigin = Matrix4d.CreateTranslation(-center.X, -center.Y, 0);
        var scale = Matrix4d.CreateScale(factor, factor, factor);
        var backToPos = Matrix4d.CreateTranslation(center.X, center.Y, 0);
        var transform = backToPos * scale * toOrigin;

        foreach (var entity in entities)
        {
            entity.TransformBy(transform);
        }

        undoManager.Record(new TransformEntityAction(
            entities, transform, $"缩放 {entities.Count} 个实体"));
    }

    /// <summary>
    /// 镜像选中的实体
    /// </summary>
    public void Mirror(Point2d linePoint1, Point2d linePoint2,
                       bool deleteSource = false)
    {
        var entities = selectionManager.SelectedEntities.ToList();
        if (entities.Count == 0) return;

        var lineDir = (linePoint2 - linePoint1).Normalize();
        var mirrorMatrix = CreateMirrorMatrix(linePoint1, lineDir);

        var mirroredEntities = new List<LcEntity>();
        foreach (var entity in entities)
        {
            var mirrored = entity.Clone();
            mirrored.TransformBy(mirrorMatrix);
            document.Entities.Add(mirrored);
            mirroredEntities.Add(mirrored);
        }

        if (deleteSource)
        {
            foreach (var entity in entities)
            {
                document.Entities.Remove(entity);
            }
        }
    }

    /// <summary>
    /// 偏移
    /// </summary>
    public void Offset(LcEntity entity, double distance, Point2d side)
    {
        LcEntity offsetResult = null;

        switch (entity)
        {
            case LcLine line:
                offsetResult = OffsetLine(line, distance, side);
                break;
            case LcCircle circle:
                offsetResult = OffsetCircle(circle, distance, side);
                break;
            case LcArc arc:
                offsetResult = OffsetArc(arc, distance, side);
                break;
            case LcPolyline polyline:
                offsetResult = polyline.Offset(
                    DetermineOffsetSide(polyline, side) * distance);
                break;
        }

        if (offsetResult != null)
        {
            offsetResult.LayerName = entity.LayerName;
            offsetResult.Color = entity.Color;
            document.Entities.Add(offsetResult);

            undoManager.Record(new AddEntityAction(
                offsetResult, document));
        }
    }
}

16.5 快捷键系统

16.5.1 快捷键映射

public class ShortcutManager
{
    private Dictionary<Keys, string> shortcuts = new();

    public ShortcutManager()
    {
        // 注册默认快捷键
        RegisterDefaults();
    }

    private void RegisterDefaults()
    {
        // 文件操作
        shortcuts[Keys.Control | Keys.N] = "NEW";
        shortcuts[Keys.Control | Keys.O] = "OPEN";
        shortcuts[Keys.Control | Keys.S] = "SAVE";

        // 编辑操作
        shortcuts[Keys.Control | Keys.Z] = "UNDO";
        shortcuts[Keys.Control | Keys.Y] = "REDO";
        shortcuts[Keys.Control | Keys.C] = "COPYCLIP";
        shortcuts[Keys.Control | Keys.V] = "PASTECLIP";
        shortcuts[Keys.Delete] = "ERASE";

        // 绘图操作
        shortcuts[Keys.L] = "LINE";
        shortcuts[Keys.C] = "CIRCLE";
        shortcuts[Keys.A] = "ARC";

        // 视图操作
        shortcuts[Keys.Control | Keys.Shift | Keys.E] = "ZOOMEXTENTS";
    }

    /// <summary>
    /// 处理快捷键
    /// </summary>
    public bool HandleKey(Keys keys)
    {
        if (shortcuts.TryGetValue(keys, out var command))
        {
            CommandRegistry.Instance.Execute(command);
            return true;
        }
        return false;
    }
}

16.6 本章小结

本章详细介绍了LightCAD的命令与操作系统。基于命令模式的设计实现了操作的可撤销/重做,命令注册表提供了统一的命令管理和执行机制。元素操作(ElementAction)封装了移动、复制、旋转、缩放、镜像、偏移等核心编辑功能。快捷键系统为高效操作提供了支持。


上一章第十五章:插件系统与扩展开发

下一章第十七章:数据库与项目管理