UI界面开发与命令系统
第十一章:UI界面开发与命令系统
11.1 LightCAD UI架构
11.1.1 UI框架概述
LightCAD的UI基于Windows Forms和DockPanel组件构建:
┌─────────────────────────────────────────────────────────┐
│ 主窗口 (MainForm) │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────┐ │
│ │ Ribbon 工具栏 │ │
│ │ TabItem → TabButtonGroup → TabButton │ │
│ └─────────────────────────────────────────────────┘ │
├──────────────┬─────────────────────┬────────────────────┤
│ │ │ │
│ 左侧面板 │ 绘图区域 │ 右侧面板 │
│ (工具箱) │ (Viewport) │ (属性面板) │
│ │ │ │
├──────────────┴─────────────────────┴────────────────────┤
│ 命令行窗口 │
│ (CommandWindow) │
├─────────────────────────────────────────────────────────┤
│ 状态栏 │
└─────────────────────────────────────────────────────────┘
11.1.2 核心UI类
| 类名 | 功能 |
|---|---|
| TabItem | Ribbon选项卡 |
| TabButtonGroup | 按钮组 |
| TabButton | 工具按钮 |
| AppRuntime.UISystem | UI系统管理器 |
| ICommandController | 命令控制器接口 |
11.2 Ribbon工具栏开发
11.2.1 TabItem结构
public class TabItem
{
/// <summary>
/// 内部名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 显示文本
/// </summary>
public string Text { get; set; }
/// <summary>
/// 快捷键
/// </summary>
public string ShortcutKey { get; set; }
/// <summary>
/// 按钮组列表
/// </summary>
public List<TabButtonGroup> ButtonGroups { get; set; }
}
public class TabButtonGroup
{
/// <summary>
/// 按钮列表
/// </summary>
public List<TabButton> Buttons { get; set; }
/// <summary>
/// 分组名称
/// </summary>
public string Name { get; set; }
}
public class TabButton
{
/// <summary>
/// 按钮名称(对应命令名)
/// </summary>
public string Name { get; set; }
/// <summary>
/// 显示文本
/// </summary>
public string Text { get; set; }
/// <summary>
/// 图标
/// </summary>
public System.Drawing.Image Icon { get; set; }
/// <summary>
/// 是否为命令按钮
/// </summary>
public bool IsCommand { get; set; }
/// <summary>
/// 按钮宽度
/// </summary>
public int Width { get; set; }
/// <summary>
/// 下拉菜单按钮
/// </summary>
public List<TabButton> DropDowns { get; set; }
/// <summary>
/// 工具提示
/// </summary>
public string ToolTip { get; set; }
}
11.2.2 创建完整TabItem
public static TabItem LayoutItem = new TabItem
{
Name = "LayoutMajor",
Text = "场布",
ShortcutKey = "ALT-L",
ButtonGroups = new List<TabButtonGroup>
{
// 第一组:项目管理
new TabButtonGroup
{
Name = "ProjectGroup",
Buttons = new List<TabButton>
{
new TabButton
{
Name = "CreateProject",
Text = "创建项目",
Icon = Properties.Resources.创建项目,
IsCommand = true,
Width = 70,
ToolTip = "创建新的场布项目"
}
}
},
// 第二组:绿化设施
new TabButtonGroup
{
Name = "GreenGroup",
Buttons = new List<TabButton>
{
new TabButton
{
Name = "Lawn",
Text = "草坪",
Icon = Properties.Resources.草地,
IsCommand = true,
DropDowns = new List<TabButton>
{
new TabButton
{
Name = "Lawn",
Text = "任意绘制",
Icon = Properties.Resources.草地,
IsCommand = true
},
new TabButton
{
Name = "LawnRec",
Text = "矩形绘制",
Icon = Properties.Resources.草地,
IsCommand = true
},
new TabButton
{
Name = "LawnChange",
Text = "转换多段线",
Icon = Properties.Resources.草地,
IsCommand = true
}
}
}
}
},
// 第三组:土方工程
new TabButtonGroup
{
Name = "EarthworkGroup",
Buttons = new List<TabButton>
{
new TabButton
{
Name = "FoundationPit",
Text = "基坑",
Icon = Properties.Resources.基坑,
IsCommand = true,
DropDowns = new List<TabButton>
{
new TabButton { Name = "FoundationPit", Text = "任意绘制", IsCommand = true },
new TabButton { Name = "FoundationPitRec", Text = "矩形绘制", IsCommand = true }
}
},
new TabButton
{
Name = "Earthwork",
Text = "土方回填",
Icon = Properties.Resources.土方回填,
IsCommand = true
}
}
},
// 第四组:道路设施
new TabButtonGroup
{
Name = "RoadGroup",
Buttons = new List<TabButton>
{
new TabButton { Name = "Road", Text = "道路", Icon = Properties.Resources.道路, IsCommand = true },
new TabButton { Name = "Berm", Text = "出土道路", Icon = Properties.Resources.出土道路, IsCommand = true },
new TabButton { Name = "Ground", Text = "硬化地面", Icon = Properties.Resources.硬化, IsCommand = true }
}
},
// 第五组:安全设施
new TabButtonGroup
{
Name = "SafetyGroup",
Buttons = new List<TabButton>
{
new TabButton { Name = "Fence", Text = "围栏", Icon = Properties.Resources.围栏, IsCommand = true },
new TabButton { Name = "Barrier", Text = "防护栏杆", Icon = Properties.Resources.防护栏, IsCommand = true }
}
},
// 第六组:建筑设施
new TabButtonGroup
{
Name = "BuildingGroup",
Buttons = new List<TabButton>
{
new TabButton { Name = "PlanBuild", Text = "拟建建筑", Icon = Properties.Resources.建筑, IsCommand = true },
new TabButton { Name = "PlateBuilding", Text = "板房", Icon = Properties.Resources.板房, IsCommand = true }
}
}
}
};
11.2.3 注册TabItem
public void Completed()
{
// 添加到UI系统
AppRuntime.UISystem.AddInitTabItems(new TabItem[] { LayoutItem });
}
11.3 命令系统
11.3.1 CommandClass特性
/// <summary>
/// 标记包含命令方法的类
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CommandClassAttribute : Attribute
{
}
11.3.2 CommandMethod特性
/// <summary>
/// 标记命令方法
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class CommandMethodAttribute : Attribute
{
/// <summary>
/// 命令名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 快捷键/简写
/// </summary>
public string ShortCuts { get; set; }
}
11.3.3 命令实现
[CommandClass]
public class LayoutCmds
{
/// <summary>
/// 草坪命令 - 任意绘制
/// </summary>
[CommandMethod(Name = "Lawn", ShortCuts = "LW")]
public CommandResult DrawLawn(IDocumentEditor docEditor, string[] args)
{
try
{
var lawnAction = new LawnAction(docEditor);
lawnAction.ExecCreatePoly(args);
return CommandResult.Succ();
}
catch (Exception ex)
{
docEditor.GetCommandController().WriteError(ex.Message);
return CommandResult.Fail(ex.Message);
}
}
/// <summary>
/// 草坪命令 - 矩形绘制
/// </summary>
[CommandMethod(Name = "LawnRec", ShortCuts = "LWRC")]
public CommandResult DrawLawnRec(IDocumentEditor docEditor, string[] args)
{
var lawnAction = new LawnAction(docEditor);
lawnAction.ExecCreateRec(args);
return CommandResult.Succ();
}
/// <summary>
/// 草坪命令 - 转换多段线
/// </summary>
[CommandMethod(Name = "LawnChange", ShortCuts = "LWCH")]
public CommandResult DrawLawnChange(IDocumentEditor docEditor, string[] args)
{
var lawnAction = new LawnAction(docEditor);
lawnAction.ExecCreate(args);
return CommandResult.Succ();
}
/// <summary>
/// 基坑命令
/// </summary>
[CommandMethod(Name = "FoundationPit", ShortCuts = "FDP")]
public CommandResult DrawFoundationPit(IDocumentEditor docEditor, string[] args)
{
var fdpAction = new FoundationPitAction(docEditor);
fdpAction.ExecCreatePoly(args);
return CommandResult.Succ();
}
/// <summary>
/// 围栏命令
/// </summary>
[CommandMethod(Name = "Fence", ShortCuts = "FN")]
public CommandResult DrawFence(IDocumentEditor docEditor, string[] args)
{
var fenceAction = new FenceAction(docEditor);
fenceAction.ExecCreate(args);
return CommandResult.Succ();
}
}
11.3.4 命令控制器
public interface ICommandController
{
/// <summary>
/// 输出信息
/// </summary>
void WriteInfo(string message);
/// <summary>
/// 输出警告
/// </summary>
void WriteWarning(string message);
/// <summary>
/// 输出错误
/// </summary>
void WriteError(string message);
/// <summary>
/// 清空输出
/// </summary>
void Clear();
}
// 使用示例
public void ExecCreate(string[] args)
{
commandCtrl.WriteInfo("命令:Lawn");
commandCtrl.WriteInfo("绘制草坪轮廓中...");
// 执行操作...
if (success)
{
commandCtrl.WriteInfo("草坪创建成功");
}
else
{
commandCtrl.WriteError("草坪创建失败");
}
}
11.4 对话框开发
11.4.1 WinForms对话框
public partial class PlateGroupSet : Form
{
public PlateBuildGroupSettings Settings { get; private set; }
public PlateGroupSet()
{
InitializeComponent();
LoadDefaults();
}
private void InitializeComponent()
{
this.Text = "板房楼栋设置";
this.Size = new Size(400, 500);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
// 创建控件
CreateControls();
}
private void CreateControls()
{
int y = 20;
int labelWidth = 100;
int controlWidth = 200;
int controlLeft = labelWidth + 30;
// 楼栋名称
var lblName = new Label { Text = "楼栋名称:", Location = new Point(20, y), Size = new Size(labelWidth, 25) };
txtBuildingName = new TextBox { Location = new Point(controlLeft, y), Size = new Size(controlWidth, 25) };
Controls.Add(lblName);
Controls.Add(txtBuildingName);
y += 35;
// 楼层数
var lblFloors = new Label { Text = "楼层数:", Location = new Point(20, y), Size = new Size(labelWidth, 25) };
numFloorCount = new NumericUpDown
{
Location = new Point(controlLeft, y),
Size = new Size(controlWidth, 25),
Minimum = 1,
Maximum = 10,
Value = 2
};
Controls.Add(lblFloors);
Controls.Add(numFloorCount);
y += 35;
// 每层房间数X
var lblRoomsX = new Label { Text = "X方向房间数:", Location = new Point(20, y), Size = new Size(labelWidth, 25) };
numRoomsX = new NumericUpDown
{
Location = new Point(controlLeft, y),
Size = new Size(controlWidth, 25),
Minimum = 1,
Maximum = 20,
Value = 5
};
Controls.Add(lblRoomsX);
Controls.Add(numRoomsX);
// ... 更多控件
// 确定/取消按钮
y = ClientSize.Height - 60;
btnOK = new Button
{
Text = "确定",
Location = new Point(ClientSize.Width - 180, y),
Size = new Size(75, 30),
DialogResult = DialogResult.OK
};
btnOK.Click += BtnOK_Click;
btnCancel = new Button
{
Text = "取消",
Location = new Point(ClientSize.Width - 95, y),
Size = new Size(75, 30),
DialogResult = DialogResult.Cancel
};
Controls.Add(btnOK);
Controls.Add(btnCancel);
AcceptButton = btnOK;
CancelButton = btnCancel;
}
private void BtnOK_Click(object sender, EventArgs e)
{
// 验证输入
if (string.IsNullOrWhiteSpace(txtBuildingName.Text))
{
MessageBox.Show("请输入楼栋名称", "验证失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtBuildingName.Focus();
return;
}
// 保存设置
Settings = new PlateBuildGroupSettings
{
BuildingName = txtBuildingName.Text,
FloorCount = (int)numFloorCount.Value,
RoomsPerFloorX = (int)numRoomsX.Value,
// ...
};
DialogResult = DialogResult.OK;
Close();
}
// 控件声明
private TextBox txtBuildingName;
private NumericUpDown numFloorCount;
private NumericUpDown numRoomsX;
private Button btnOK;
private Button btnCancel;
}
11.4.2 使用对话框
public async void ExecCreate(string[] args = null)
{
var pointInputer = new PointInputer(docEditor);
// 获取放置位置
commandCtrl.WriteInfo("指定楼栋放置位置:");
var result = await pointInputer.Execute();
if (result.Status != InputStatus.OK) return;
// 显示设置对话框
using (var settingForm = new PlateGroupSet())
{
if (settingForm.ShowDialog() == DialogResult.OK)
{
CreateBuildGroup(result.Point, settingForm.Settings);
}
else
{
commandCtrl.WriteInfo("操作已取消");
}
}
}
11.5 属性面板
11.5.1 PropertyObserver
public class PropertyObserver
{
/// <summary>
/// 属性名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// 分类名称
/// </summary>
public string CategoryName { get; set; }
/// <summary>
/// 分类显示名称
/// </summary>
public string CategoryDisplayName { get; set; }
/// <summary>
/// 属性类型
/// </summary>
public PropertyType PropType { get; set; }
/// <summary>
/// 值获取器
/// </summary>
public Func<LcElement, object> Getter { get; set; }
/// <summary>
/// 值设置器
/// </summary>
public Action<LcElement, object> Setter { get; set; }
/// <summary>
/// 下拉选项来源(PropType为Array时使用)
/// </summary>
public Func<LcElement, string[]> Source { get; set; }
}
public enum PropertyType
{
String,
Double,
Int,
Bool,
Array, // 下拉选择
Color,
Point,
Custom
}
11.5.2 实现属性编辑器
public override List<PropertyObserver> GetPropertyObservers()
{
return new List<PropertyObserver>
{
// 文本属性
new PropertyObserver
{
Name = "Name",
DisplayName = "名称",
CategoryName = "Basic",
CategoryDisplayName = "基本信息",
PropType = PropertyType.String,
Getter = (ele) => (ele as MyElement).Name,
Setter = (ele, value) => (ele as MyElement).Name = value.ToString()
},
// 数值属性
new PropertyObserver
{
Name = "Width",
DisplayName = "宽度",
CategoryName = "Geometry",
CategoryDisplayName = "几何",
PropType = PropertyType.Double,
Getter = (ele) => (ele as MyElement).Width,
Setter = (ele, value) =>
{
if (double.TryParse(value.ToString(), out var width) && width > 0)
{
(ele as MyElement).Width = width;
}
}
},
// 下拉选择属性
new PropertyObserver
{
Name = "Type",
DisplayName = "类型",
CategoryName = "Basic",
CategoryDisplayName = "基本信息",
PropType = PropertyType.Array,
Source = (ele) => new[] { "类型A", "类型B", "类型C" },
Getter = (ele) => (ele as MyElement).TypeName,
Setter = (ele, value) => (ele as MyElement).TypeName = value.ToString()
},
// 布尔属性
new PropertyObserver
{
Name = "Visible",
DisplayName = "可见",
CategoryName = "Display",
CategoryDisplayName = "显示",
PropType = PropertyType.Bool,
Getter = (ele) => (ele as MyElement).IsVisible,
Setter = (ele, value) => (ele as MyElement).IsVisible = Convert.ToBoolean(value)
}
};
}
11.6 资源管理
11.6.1 Resources.resx
在项目中创建资源文件管理图标等资源:
Properties/
├── Resources.resx
└── Resources.Designer.cs (自动生成)
11.6.2 添加图标资源
- 双击Resources.resx打开资源编辑器
- 点击"添加资源" → "添加现有文件"
- 选择图标文件(建议32x32 PNG格式)
- 设置资源名称
11.6.3 使用资源
// 自动生成的访问器
new TabButton
{
Name = "Lawn",
Text = "草坪",
Icon = Properties.Resources.草地, // 资源名称
IsCommand = true
}
11.6.4 动态加载资源
private static System.Drawing.Image LoadEmbeddedResource(string resourceName)
{
var assembly = Assembly.GetExecutingAssembly();
var fullName = $"{assembly.GetName().Name}.Resources.{resourceName}";
using (var stream = assembly.GetManifestResourceStream(fullName))
{
if (stream == null)
throw new FileNotFoundException($"Resource not found: {fullName}");
return System.Drawing.Image.FromStream(stream);
}
}
11.7 多语言支持
11.7.1 资源文件命名
Resources.resx # 默认语言(中文)
Resources.en.resx # 英文
Resources.ja.resx # 日文
11.7.2 使用本地化资源
// 自动根据当前文化选择资源
var text = Properties.Resources.ButtonText;
// 手动设置文化
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
var englishText = Properties.Resources.ButtonText;
11.8 快捷键与热键
11.8.1 TabItem快捷键
new TabItem
{
Name = "LayoutMajor",
Text = "场布",
ShortcutKey = "ALT-L", // ALT+L切换到此选项卡
// ...
}
11.8.2 命令快捷键
[CommandMethod(Name = "Lawn", ShortCuts = "LW")]
public CommandResult DrawLawn(IDocumentEditor docEditor, string[] args)
{
// 在命令行输入"LW"即可执行此命令
}
11.9 本章小结
本章详细介绍了FY_Layout的UI界面开发与命令系统:
- UI架构:基于WinForms和DockPanel的界面结构
- Ribbon工具栏:TabItem、TabButtonGroup、TabButton的创建
- 命令系统:CommandClass和CommandMethod的使用
- 对话框开发:WinForms对话框的设计和使用
- 属性面板:PropertyObserver的实现
- 资源管理:图标和其他资源的管理方法
- 多语言支持:本地化资源的使用
- 快捷键:选项卡和命令的快捷键配置
掌握这些UI开发技术,可以为插件创建专业、易用的用户界面。下一章我们将通过完整的实战案例总结所学内容。

浙公网安备 33010602011771号