# EzUI Control 基类权威指南 > 本文档是 EzUI C++ UI 框架的核心参考资料,面向初学者入门。 --- ## 目录 1. [类概述](#1-类概述) 2. [核心属性详解](#2-核心属性详解) 3. [核心方法详解](#3-核心方法详解) 4. [CSS 样式属性映射表](#4-css-样式属性映射表) 5. [事件系统](#5-事件系统) 6. [最佳实践与常见陷阱](#6-最佳实践与常见陷阱) 7. [附录:速查表](#7-附录速查表) --- ## 1. 类概述 ### 1.1 Control 在 UI 库架构中的定位 **Control** 是 EzUI 框架中所有 UI 控件的**基类**,位于 `include/EzUI/Control.h` 中。它继承了 **Object** 类,是构建整个 UI 界面的核心抽象。 ``` Object (基类) │ └── Control (UI控件基类) ├── Label (文本显示) │ └── Button (按钮) ├── TextBox (文本框) ├── ComboBox (下拉框) ├── CheckBox / RadioButton ├── PictureBox (图片) ├── ProgressBar (进度条) ├── HLayout / VLayout (布局容器) ├── HListView / VListView / TileListView / PagedListView (列表) ├── TreeView (树形视图) ├── TableView (表格) ├── TabLayout (标签页) └── ScrollBar / HScrollBar / VScrollBar (滚动条) ``` > **注意**:`Window` 类并非直接继承自 Control,而是继承自 Object。它是顶级窗口,与 Control 是**并行**的类层次结构。 ### 1.2 Control 的核心职责 Control 类承担以下四大职责: | 职责 | 描述 | |------|------| | **渲染** | 管理控件的外观绘制,包括背景、前景、边框 | | **布局** | 计算控件的位置和尺寸,处理子控件的排列 | | **事件处理** | 接收和响应鼠标、键盘等用户交互事件 | | **状态管理** | 维护控件的可见性、启用状态、悬停/按下等状态 | ### 1.3 快速入门示例 ```cpp #include using namespace EzUI; int main() { Application::Init(); Window window; window.SetSize(800, 600); window.SetTitle("Control Demo"); // 创建布局容器 VLayout* layout = new VLayout(); window.Add(layout); // 创建按钮 Button* btn = new Button(); btn->SetText("Click Me"); btn->SetFixedSize(120, 40); btn->SetLocation(100, 100); // 绑定事件 btn->EventHandler = [](Control* sender, EventArgs& args) { if (args.EventType == Event::OnMouseUp) { MessageBox(NULL, "Button Clicked!", "Info", MB_OK); } }; layout->Add(btn); window.Show(); return Application::Run(); } ``` --- ## 2. 核心属性详解 ### 2.1 几何属性 #### 2.1.1 位置属性 | 属性 | 类型 | 作用 | |------|------|------| | `X()` | `int` | 获取控件相对于父容器的 X 坐标 | | `Y()` | `int` | 获取控件相对于父容器的 Y 坐标 | | `SetX(int)` | `void` | 设置 X 坐标 | | `SetY(int)` | `void` | 设置 Y 坐标 | | `SetLocation(const Point&)` | `void` | 同时设置 X 和 Y 坐标 | **代码示例**: ```cpp // 设置位置的三种方式 control->SetX(100); control->SetY(200); // 或 control->SetLocation(Point(100, 200)); // 获取位置 Point pos = control->GetLocation(); // 等价于 Point(control->X(), control->Y()) ``` #### 2.1.2 尺寸属性 | 属性 | 类型 | 作用 | |------|------|------| | `Width()` | `int` | 获取控件宽度 | | `Height()` | `int` | 获取控件高度 | | `SetWidth(int)` | `void` | 设置宽度 | | `SetHeight(int)` | `void` | 设置高度 | | `SetSize(const Size&)` | `void` | 同时设置宽高 | | `SetFixedSize(const Size&)` | `void` | 设置固定尺寸(优先级最高) | | `SetFixedWidth(int)` | `void` | 设置固定宽度 | | `SetFixedHeight(int)` | `void` | 设置固定高度 | | `SetRateWidth(float)` | `void` | 设置宽度为父容器的百分比 (0.0-1.0) | | `SetRateHeight(float)` | `void` | 设置高度为父容器的百分比 (0.0-1.0) | | `GetRect()` | `const Rect&` | 获取控件矩形区域(布局计算后) | **代码示例**: ```cpp // 固定尺寸 control->SetFixedSize(Size(200, 100)); control->SetFixedWidth(200); control->SetFixedHeight(100); // 百分比尺寸(响应式布局) control->SetRateWidth(0.5f); // 宽度为父容器的50% control->SetRateHeight(0.25f); // 高度为父容器的25% // 自动尺寸 control->SetAutoWidth(true); control->SetAutoHeight(true); control->SetAutoSize(true); // 同时设置自动宽高 ``` #### 2.1.3 外边距 (Margin) | 属性 | 类型 | 作用 | |------|------|------| | `Margin` | `Distance` | 设置控件外边距,影响在父容器中的布局 | **代码示例**: ```cpp // 设置外边距 control->Margin.Left = 10; control->Margin.Top = 20; control->Margin.Right = 10; control->Margin.Bottom = 20; // 或使用 Distance 构造函数 control->Margin = Distance(10, 20); // 左+右=10, 上+下=20 ``` #### 2.1.4 停靠样式 (Dock) | 属性 | 类型 | 作用 | |------|------|------| | `GetDockStyle()` | `DockStyle` | 获取停靠样式 | | `SetDockStyle(const DockStyle&)` | `void` | 设置停靠样式 | **DockStyle 枚举值**: | 值 | 描述 | |----|------| | `DockStyle::None` | 不停靠,使用绝对定位 | | `DockStyle::Horizontal` | 水平停靠,从左到右排列 | | `DockStyle::Vertical` | 垂直停靠,从上到下排列 | | `DockStyle::Fill` | 填满剩余空间 | **代码示例**: ```cpp // 填满父容器 control->SetDockStyle(DockStyle::Fill); // 垂直停靠(类似 StackPanel) layout->SetDockStyle(DockStyle::Vertical); ``` --- ### 2.2 视觉属性 #### 2.2.1 可见性 | 属性 | 类型 | 作用 | |------|------|------| | `IsVisible()` | `bool` | 获取控件是否可见 | | `SetVisible(bool)` | `void` | 设置控件可见性 | | `Show()` | `void` | 显示控件(等价于 SetVisible(true)) | | `Hide()` | `void` | 隐藏控件(等价于 SetVisible(false)) | **代码示例**: ```cpp // 显示/隐藏控件 control->SetVisible(true); control->Show(); control->SetVisible(false); control->Hide(); // 判断可见性 if (control->IsVisible()) { // 控件可见 } ``` #### 2.2.2 启用状态 | 属性 | 类型 | 作用 | |------|------|------| | `IsEnabled()` | `bool` | 获取控件是否启用 | | `SetEnabled(bool)` | `void` | 设置控件启用/禁用 | | `SetDisabled(bool)` | `void` | 设置控件禁用/启用 | **代码示例**: ```cpp // 禁用控件(变灰,不可交互) control->SetEnabled(false); control->SetDisabled(true); // 等价 // 启用控件 control->SetEnabled(true); control->SetDisabled(false); ``` #### 2.2.3 样式状态 Control 支持多套样式,分别对应不同的交互状态: | 属性 | 类型 | 作用 | |------|------|------| | `Style` | `ControlStyle` | 静态/默认状态样式 | | `HoverStyle` | `ControlStyle` | 鼠标悬停状态样式 | | `ActiveStyle` | `ControlStyle` | 鼠标按下状态样式 | | `DisabledStyle` | `ControlStyle` | 禁用状态样式 | **ControlStyle 结构**: ```cpp struct ControlStyle { ezui::Border Border; // 边框 Color BackColor = 0; // 背景颜色 Image* BackImage = NULL; // 背景图片 Image* ForeImage = NULL; // 前景图片 std::wstring FontFamily; // 字体名称 int FontSize = 0; // 字体大小 Color ForeColor; // 前景颜色(文字颜色) HCURSOR Cursor = NULL; // 鼠标光标 float Angle = 0; // 旋转角度 (0-360) }; ``` **代码示例**: ```cpp // 编程方式设置样式 control->Style.BackColor = Color(255, 255, 255); // 白色背景 control->Style.Border.Left = 1; // 左边框宽度 control->Style.Border.LeftColor = Color(200, 200, 200); control->Style.FontSize = 14; // 使用 CSS 字符串设置样式(更方便) control->SetStyleSheet(ControlState::Static, "background-color:#FFFFFF;border:1px solid #CCCCCC;font-size:14px"); control->SetStyleSheet(ControlState::Hover, "background-color:#E0E0E0"); ``` --- ### 2.3 层级属性 #### 2.3.1 父子关系 | 属性 | 类型 | 作用 | |------|------|------| | `Parent` | `Control*` | 获取父控件指针(公共属性) | | `SetParent(Control*)` | `void` | 设置父控件 | **代码示例**: ```cpp // 设置父控件(推荐方式) parentLayout->Add(childControl); // 或 childControl->SetParent(parentLayout); // 获取父控件 Control* parent = childControl->Parent; if (parent) { // 父控件存在 } ``` #### 2.3.2 子控件集合 | 方法 | 作用 | |------|------| | `GetControls()` | 获取所有子控件集合 | | `GetViewControls()` | 获取可见的子控件集合 | | `Add(Control*)` | 添加子控件到末尾 | | `Insert(int, Control*)` | 在指定位置插入子控件 | | `Remove(Control*, bool freeCtrl = false)` | 移除子控件 | | `Clear(bool freeChilds = false)` | 清除所有子控件 | | `GetControl(int pos)` | 按索引获取子控件(跳过占位符) | | `IndexOf(Control*)` | 获取子控件索引 | **代码示例**: ```cpp // 添加子控件 VLayout* layout = new VLayout(); layout->Add(new Button()); layout->Add(new Label()); // 插入到指定位置 layout->Insert(0, new Control()); // 插入到开头 // 移除子控件 Control* child = layout->GetControl(0); layout->Remove(child); // 移除但不删除 layout->Remove(child, true); // 移除并删除 // 遍历子控件 for (Control* ctl : layout->GetControls()) { ctl->SetVisible(false); } // 清空子控件 layout->Clear(); // 移除但不删除子控件对象 layout->Clear(true); // 移除并删除子控件对象 ``` --- ### 2.4 标识属性 | 属性 | 类型 | 作用 | |------|------|------| | `Name` | `UIString` | 控件名称(用于查找) | **代码示例**: ```cpp // 设置名称 control->Name = "submitButton"; // 通过名称查找控件 Control* found = window.FindControl("submitButton"); // 查找具有特定属性值的控件 Controls results = window.FindControl("type", "button"); ``` --- ### 2.5 状态属性 #### 2.5.1 控件状态枚举 ```cpp enum class ControlState : int { None = 1, // 无状态 Static = 2, // 静态/默认 Disabled = 4, // 禁用 Checked = 8, // 选中(复选框/单选按钮) Hover = 16, // 鼠标悬停 Active = 32 // 鼠标按下/激活 }; ``` #### 2.5.2 获取状态 | 方法 | 作用 | |------|------| | `State` | 获取当前控件状态 | | `GetLayoutState()` | 获取当前布局状态 | **代码示例**: ```cpp // 判断当前状态 if (control->State == ControlState::Hover) { // 鼠标正在控件上 } if (control->State == ControlState::Active) { // 控件正在被按下 } // 布局状态 LayoutState layoutState = control->GetLayoutState(); if (layoutState == LayoutState::Pend) { // 布局已挂起,需要重新布局 } ``` --- ## 3. 核心方法详解 ### 3.1 构造函数与析构函数 ```cpp // 构造函数 Control(Object* parentObject = NULL); // 析构函数 virtual ~Control(); ``` **行为说明**: - 构造函数接受一个可选的父对象指针 - 如果传入父控件,子控件会被自动添加到父控件的子集合中 - 析构函数会自动销毁所有子控件(递归) **代码示例**: ```cpp // 方式1:创建后手动添加 Control* child = new Control(); parent->Add(child); // 方式2:创建时指定父控件(推荐) Control* child = new Control(parent); // 自动添加到 parent 的子控件集合 ``` --- ### 3.2 布局相关方法 布局是 EzUI 的核心特性之一,采用了**延迟布局**机制以提高性能。 #### 3.2.1 布局状态机 ``` ┌─────────────┐ │ None │ ←─── 布局完成状态 └──────┬──────┘ │ TryPendLayout() ▼ ┌─────────────┐ │ Pend │ ←─── 布局已挂起,等待下次布局 └──────┬──────┘ │ OnLayout() ▼ ┌─────────────┐ │ Layouting │ ←─── 正在布局中 └──────┬──────┘ │ EndLayout() ▼ None ``` #### 3.2.2 核心布局方法 | 方法 | 作用 | |------|------| | `TryPendLayout()` | 挂起布局,请求重新布局 | | `RefreshLayout()` | 强制立即刷新布局 | | `GetLayoutState()` | 获取当前布局状态 | | `IsPendLayout()` | 检查是否挂起了布局 | | `EndLayout()` | 结束当前布局 | | `OnLayout()` | 虚函数,子类可重写实现自定义布局逻辑 | **代码示例**: ```cpp // 修改尺寸后,布局会自动挂起 control->SetFixedSize(Size(200, 100)); // 手动触发布局刷新 control->RefreshLayout(); // 检查布局状态 if (control->IsPendLayout()) { // 需要重新布局 } ``` --- ### 3.3 渲染相关方法 #### 3.3.1 渲染流程 ``` ┌────────────────────────────────────┐ │ OnPaintBefore() │ ← 绘制前处理(可重写) └──────────────┬─────────────────────┘ ▼ ┌────────────────────────────────────┐ │ OnBackgroundPaint() │ ← 绘制背景(可重写) └──────────────┬─────────────────────┘ ▼ ┌────────────────────────────────────┐ │ OnForePaint() │ ← 绘制前景内容(可重写) └──────────────┬─────────────────────┘ ▼ ┌────────────────────────────────────┐ │ OnBorderPaint() │ ← 绘制边框(可重写) └──────────────┬─────────────────────┘ ▼ ┌────────────────────────────────────┐ │ OnChildPaint() │ ← 绘制子控件 └────────────────────────────────────┘ ``` #### 3.3.2 渲染方法列表 | 方法 | 作用 | |------|------| | `OnPaintBefore(PaintEventArgs&)` | 绘制前回调,可重写 | | `OnBackgroundPaint(PaintEventArgs&)` | 绘制背景回调,可重写 | | `OnForePaint(PaintEventArgs&)` | 绘制前景回调,可重写 | | `OnBorderPaint(PaintEventArgs&, const Border&)` | 绘制边框回调,可重写 | | `OnChildPaint(PaintEventArgs&)` | 绘制子控件回调,可重写 | | `Refresh()` | 立即重绘 | | `Invalidate()` | 延迟重绘(更高效) | **代码示例**: ```cpp // 方式1:立即重绘 control->Refresh(); // 方式2:延迟重绘(推荐,多次修改只重绘一次) control->Invalidate(); control->Invalidate(); // 多次调用只重绘一次 // 自定义绘制 class MyControl : public Control { protected: virtual void OnForePaint(PaintEventArgs& e) override { // 获取绘图上下文 Graphics& g = e.Graphics; // 绘制自定义内容 Rect rect = GetRect(); g.DrawString(L"Hello", rect); // 调用基类 Control::OnForePaint(e); } }; ``` --- ### 3.4 事件处理方法 #### 3.4.1 事件分发机制 ``` 用户操作 │ ▼ Window 消息循环 │ ▼ Control::SendEvent(EventArgs) │ ├── ▼ 控件禁用? → 拦截鼠标/键盘事件 │ ▼ Control::OnEvent(EventArgs) │ ├── OnMouseEvent() / OnKeyBoardEvent() │ │ │ ▼ │ 具体事件处理 (OnMouseDown, OnMouseUp...) │ ▼ EventHandler 回调(如果有) │ ▼ Frame::OnNotify()(如果在 NotifyFlags 中) ``` #### 3.4.2 事件处理方法列表 **鼠标事件**: | 方法 | 作用 | |------|------| | `OnMouseEvent(const MouseEventArgs&)` | 鼠标事件总入口 | | `OnMouseEnter(const MouseEventArgs&)` | 鼠标进入 | | `OnMouseLeave(const MouseEventArgs&)` | 鼠标离开 | | `OnMouseMove(const MouseEventArgs&)` | 鼠标移动 | | `OnMouseDown(const MouseEventArgs&)` | 鼠标按下 | | `OnMouseUp(const MouseEventArgs&)` | 鼠标释放 | | `OnMouseWheel(const MouseEventArgs&)` | 鼠标滚轮 | | `OnMouseDoubleClick(const MouseEventArgs&)` | 鼠标双击 | **键盘事件**: | 方法 | 作用 | |------|------| | `OnKeyBoardEvent(const KeyboardEventArgs&)` | 键盘事件总入口 | | `OnKeyDown(const KeyboardEventArgs&)` | 键盘按下 | | `OnKeyUp(const KeyboardEventArgs&)` | 键盘释放 | | `OnKeyChar(const KeyboardEventArgs&)` | 字符输入 | **焦点事件**: | 方法 | 作用 | |------|------| | `OnFocus(const FocusEventArgs&)` | 获得焦点 | | `OnKillFocus(const KillFocusEventArgs&)` | 失去焦点 | **其他事件**: | 方法 | 作用 | |------|------| | `OnMove(const MoveEventArgs&)` | 位置改变 | | `OnSize(const SizeEventArgs&)` | 尺寸改变 | | `OnDpiChange(const DpiChangeEventArgs&)` | DPI 改变 | **代码示例**: ```cpp // 方式1:使用 EventHandler 回调 button->EventHandler = [](Control* sender, EventArgs& args) { switch (args.EventType) { case Event::OnMouseUp: // 处理点击 break; case Event::OnMouseEnter: // 处理鼠标进入 break; } }; // 方式2:重写虚函数(需要继承) class MyButton : public Button { protected: virtual void OnMouseUp(const MouseEventArgs& arg) override { // 自定义处理逻辑 Button::OnMouseUp(arg); // 可选:调用基类 } }; ``` --- ### 3.5 生命周期方法 | 方法 | 作用 | 调用时机 | |------|------|----------| | `OnRemove()` | 移除回调 | 控件从父容器移除时 | | `GetCursor(ControlState)` | 获取光标 | 需要显示特定光标时 | --- ## 4. CSS 样式属性映射表 EzUI 支持使用 CSS 字符串设置控件样式。以下是完整的属性映射: | CSS 属性 | C++ 属性/方法 | 值类型 | 示例 | |----------|---------------|--------|------| | **背景** |||| | `background-color` | `Style.BackColor` | Color | `background-color:#FFFFFF` | | `background-image` | `Style.BackImage` | Image* | `background-image:res.png` | | `background-position` | - | - | (暂不支持) | | `background-size` | - | - | (暂不支持) | | **前景** |||| | `fore-color` / `color` | `Style.ForeColor` | Color | `color:#000000` | | `fore-image` | `Style.ForeImage` | Image* | `fore-image:icon.png` | | `fore-image-size` | - | - | (暂不支持) | | **边框** |||| | `border` | `Style.Border` | Border | `border:1px solid #000000` | | `border-width` | `Style.Border.Left/Top/Right/Bottom` | int | `border-width:1` | | `border-style` | `Style.Border.LeftStyle` | BorderStyle | `border-style:solid` | | `border-color` | `Style.Border.LeftColor` | Color | `border-color:#000000` | | `border-radius` | `Style.Border.LeftRadius` | int | `border-radius:5` | | `border-top-left-radius` | `Style.Border.LeftTopRadius` | int | `border-top-left-radius:5` | | `border-top-right-radius` | `Style.Border.RightTopRadius` | int | `border-top-right-radius:5` | | `border-bottom-left-radius` | `Style.Border.LeftBottomRadius` | int | `border-bottom-left-radius:5` | | `border-bottom-right-radius` | `Style.Border.RightBottomRadius` | int | `border-bottom-right-radius:5` | | `border-left` | `Style.Border.Left` | BorderItem | `border-left:1px solid #000` | | `border-top` | `Style.Border.Top` | BorderItem | `border-top:1px solid #000` | | `border-right` | `Style.Border.Right` | BorderItem | `border-right:1px solid #000` | | `border-bottom` | `Style.Border.Bottom` | BorderItem | `border-bottom:1px solid #000` | | **字体** |||| | `font-size` | `Style.FontSize` | int | `font-size:14` | | `font-family` | `Style.FontFamily` | wstring | `font-family:Microsoft YaHei` | | **光标** |||| | `cursor` | `Style.Cursor` | HCURSOR | `cursor:pointer` | | **交互** |||| | `pointer-events` | `m_hitTestEnabled` | bool | `pointer-events:none` | | **定位** |||| | `x` | `SetX()` | int | `x:100` | | `y` | `SetY()` | int | `y:200` | | `width` | `SetFixedWidth()` | int | `width:200` | | `height` | `SetFixedHeight()` | int | `height:100` | | **外边距** |||| | `margin` | `Margin` | Distance | `margin:10` | | `margin-left` | `Margin.Left` | int | `margin-left:10` | | `margin-top` | `Margin.Top` | int | `margin-top:10` | | `margin-right` | `Margin.Right` | int | `margin-right:10` | | `margin-bottom` | `Margin.Bottom` | int | `margin-bottom:10` | **代码示例**: ```cpp // 设置静态样式 control->SetStyleSheet(ControlState::Static, "background-color:#FFFFFF;" "border:1px solid #CCCCCC;" "font-size:14px;" "font-family:Microsoft YaHei;" "cursor:pointer"); // 设置悬停样式 control->SetStyleSheet(ControlState::Hover, "background-color:#E8E8E8"); // 设置按下样式 control->SetStyleSheet(ControlState::Active, "background-color:#D0D0D0"); // 设置禁用样式 control->SetStyleSheet(ControlState::Disabled, "background-color:#F0F0F0;" "fore-color:#999999"); ``` --- ## 5. 事件系统 ### 5.1 支持的事件类型 事件类型定义在 `EzUI.h` 中: ```cpp enum Event : long long { None = 1, OnMouseWheel = 2, OnMouseEnter = 4, OnMouseMove = 8, OnMouseLeave = 16, OnMouseDoubleClick = 32, OnMouseDown = 64, OnMouseUp = 128, OnKeyDown = 256, OnKeyUp = 512, OnPaint = 1024, OnFocus = 2048, OnKillFocus = 4096, OnKeyChar = 8192, OnMove = 16384, OnSize = 32768, OnRect = 65536, OnDpiChange = 131072, }; ``` **复合事件**: ```cpp OnActive = OnMouseDown | OnMouseUp, // 点击 OnHover = OnMouseEnter | OnMouseLeave, // 悬停 OnMouseDrag = OnMouseDown | OnMouseMove, // 拖拽 OnMouseEvent = ..., // 所有鼠标事件 OnKeyBoardEvent = ... // 所有键盘事件 ``` ### 5.2 事件参数类 | 类名 | 包含信息 | |------|----------| | `EventArgs` | `Event EventType` - 事件类型 | | `MouseEventArgs` | `MouseButton Button`, `int ZDelta`, `Point Location` | | `KeyboardEventArgs` | `WPARAM wParam`, `LPARAM lParam` | | `FocusEventArgs` | `Control* Ctl` - 获得焦点的控件 | | `KillFocusEventArgs` | `Control* Ctl` - 失去焦点的控件 | | `MoveEventArgs` | `Point Location` - 新位置 | | `SizeEventArgs` | `Size NewSize` - 新尺寸 | | `PaintEventArgs` | `Graphics& Graphics`, `HWND`, `Rect`, etc. | ### 5.3 事件订阅与取消订阅 #### 5.3.1 使用 EventHandler 回调 ```cpp // 订阅事件 control->EventHandler = [](Control* sender, EventArgs& args) { // sender: 事件发送者 // args: 事件参数 if (args.EventType == Event::OnMouseUp) { // 处理点击 } }; // 取消订阅(设为空即可) control->EventHandler = nullptr; ``` #### 5.3.2 使用 NotifyFlags 转发事件 ```cpp // 设置事件转发到 Window 的 NotifyHandler control->NotifyFlags = Event::OnMouseEvent | Event::OnKeyBoardEvent; // 在 Window 中处理 window.NotifyHandler = [](Control* sender, EventArgs& args) { // 处理所有子控件转发的事件 }; ``` #### 5.3.3 重写虚函数 ```cpp class MyControl : public Control { protected: virtual void OnMouseDown(const MouseEventArgs& arg) override { // 自定义处理 // 调用基类实现 Control::OnMouseDown(arg); } virtual void OnKeyDown(const KeyboardEventArgs& arg) override { // 处理键盘按下 } }; ``` ### 5.4 自定义事件扩展指南 EzUI 使用 `EventArgs` 作为基础事件参数,可以扩展自定义事件: ```cpp // 自定义事件参数 class MyEventArgs : public EventArgs { public: int CustomData; UIString Message; }; // 触发自定义事件 void MyControl::DoSomething() { MyEventArgs args; args.EventType = (Event)0x10000; // 使用未定义的事件类型 args.CustomData = 42; args.Message = L"Custom Event"; SendEvent(args); } ``` --- ## 6. 最佳实践与常见陷阱 ### 6.1 内存管理 #### 6.1.1 父子控件生命周期 **最佳实践**: ```cpp // 方式1:创建时指定父控件(推荐) VLayout* layout = new VLayout(&window); // window 会负责销毁 layout Button* btn = new Button(layout); // layout 会负责销毁 btn // 方式2:使用 Add 方法 Control* parent = new Control(); Control* child = new Control(); parent->Add(child); // parent 销毁时自动销毁 child ``` **陷阱**: ```cpp // 错误:父控件销毁后,子指针变成悬空指针 Control* child = new Control(); parent->Add(child); delete parent; // child 已被销毁 child->SetVisible(false); // 崩溃! // 错误:重复添加导致ouble free parent->Add(child); parent->Add(child); // 崩溃! ``` #### 6.1.2 Detach 与 Attach ```cpp // Attach:附加图片资源,控件销毁时自动释放图片 Image* img = Image::LoadFromFile(L"test.png"); control->Attach(img); // Detach:分离图片资源,控件销毁时不释放图片 control->Detach(img); // 注意:Detach 后需要手动 delete img ``` --- ### 6.2 线程安全 #### 6.2.1 UI 线程访问规则 **关键原则**:所有 UI 操作必须在**主线程**中执行。 ```cpp // 错误:跨线程修改 UI std::thread t([&]() { control->SetText(L"Hello"); // 可能崩溃! }); t.join(); // 正确:使用 Invoke 同步派发到主线程 std::thread t([&]() { Application::Invoke([&]() { control->SetText(L"Hello"); // 安全 }); }); t.join(); // 正确:使用 BeginInvoke 异步派发 std::thread t([&]() { Application::BeginInvoke([&]() { control->SetText(L"Hello"); // 安全 }); }); t.join(); ``` --- ### 6.3 性能优化 #### 6.3.1 减少重绘 ```cpp // 差:每次修改都立即重绘 control->SetWidth(100); control->Refresh(); control->SetHeight(50); control->Refresh(); // 好:使用 Invalidate 延迟重绘(推荐) control->SetWidth(100); control->SetHeight(50); control->Invalidate(); // 一次重绘 ``` #### 6.3.2 批量更新布局 ```cpp // 差:每次修改都触发布局 layout->SetDockStyle(DockStyle::Vertical); layout->RefreshLayout(); layout->Add(child1); layout->RefreshLayout(); layout->Add(child2); layout->RefreshLayout(); // 好:使用 Invalidate 批量更新 layout->SetDockStyle(DockStyle::Vertical); layout->Add(child1); layout->Add(child2); layout->Invalidate(); // 一次布局 ``` #### 6.3.3 隐藏控件不参与渲染 ```cpp // 设置为不可见后,控件不会参与渲染计算 control->SetVisible(false); control->Hide(); ``` --- ### 6.4 常见编译错误和运行时错误排查 | 错误 | 原因 | 解决方案 | |------|------|----------| | `unresolved external symbol` | 链接库缺失 | 确保链接 EzUI 库 | | `error C2027: use of undefined type` | 头文件包含顺序错误 | 检查 include 顺序 | | 控件不显示 | 未添加到父容器 | 使用 Add() 方法 | | 控件不响应事件 | 不可见或禁用 | 检查 SetVisible/SetEnabled | | 布局不正确 | 未设置尺寸 | 使用 SetFixedSize/SetAutoSize | | 样式不生效 | 样式状态优先级 | 检查 ControlState 设置 | | 内存泄漏 | 未正确管理父子关系 | 使用智能指针或正确配对 new/delete | --- ## 7. 附录:速查表 ### 7.1 常用方法速查 | 操作 | 方法 | |------|------| | 创建控件 | `new Control(parent)` | | 添加子控件 | `parent->Add(child)` | | 设置尺寸 | `SetFixedSize(Size(w, h))` | | 设置位置 | `SetLocation(Point(x, y))` | | 设置样式 | `SetStyleSheet(ControlState::Static, "css")` | | 绑定事件 | `ctrl->EventHandler = [](...){}` | | 显示/隐藏 | `Show()` / `Hide()` | | 启用/禁用 | `SetEnabled(true/false)` | | 刷新绘制 | `Invalidate()` | | 刷新布局 | `RefreshLayout()` | ### 7.2 CSS 速查 ```css /* 按钮样式示例 */ Button { background-color: #0078D4; border: 1px solid #005A9E; border-radius: 4px; color: #FFFFFF; font-size: 14px; font-family: Microsoft YaHei; cursor: pointer; } /* 悬停状态 */ Button:Hover { background-color: #106EBE; } /* 按下状态 */ Button:Active { background-color: #005A9E; } /* 禁用状态 */ Button:Disabled { background-color: #CCCCCC; color: #999999; cursor: not-allowed; } ``` --- *文档版本:1.0* *最后更新:2026-02-20*