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 添加图标资源

  1. 双击Resources.resx打开资源编辑器
  2. 点击"添加资源" → "添加现有文件"
  3. 选择图标文件(建议32x32 PNG格式)
  4. 设置资源名称

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界面开发与命令系统:

  1. UI架构:基于WinForms和DockPanel的界面结构
  2. Ribbon工具栏:TabItem、TabButtonGroup、TabButton的创建
  3. 命令系统:CommandClass和CommandMethod的使用
  4. 对话框开发:WinForms对话框的设计和使用
  5. 属性面板:PropertyObserver的实现
  6. 资源管理:图标和其他资源的管理方法
  7. 多语言支持:本地化资源的使用
  8. 快捷键:选项卡和命令的快捷键配置

掌握这些UI开发技术,可以为插件创建专业、易用的用户界面。下一章我们将通过完整的实战案例总结所学内容。


posted @ 2026-01-31 16:03  我才是银古  阅读(2)  评论(0)    收藏  举报