第十六章:命令与操作系统
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)封装了移动、复制、旋转、缩放、镜像、偏移等核心编辑功能。快捷键系统为高效操作提供了支持。
上一章:第十五章:插件系统与扩展开发
下一章:第十七章:数据库与项目管理