Files
EzUI/doc/Control_Guide.md

1077 lines
28 KiB
Markdown
Raw Normal View History

2026-02-20 21:58:42 +08:00
# 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 <EzUI.h>
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*