Files
EzUI/doc/Control_Guide.md
2026-02-20 21:58:42 +08:00

1077 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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*