diff --git a/CLAUDE.md b/CLAUDE.md index 04eb6a4..859d2ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,12 +13,12 @@ The project uses CMake for building. Prebuilt libraries exist in `lib/` for both ### Build Static Library (EzUI) ```bash -# Generate and build Debug x64 +# Generate and build (choose one) cmake -G "Visual Studio 17 2022" -A x64 -S . -B build/x64 -cmake --build build/x64 --config Debug +cmake -G "Visual Studio 17 2022" -A Win32 -S . -B build/x86 -# Generate and build Release x64 -cmake -G "Visual Studio 17 2022" -A x64 -S . -B build/x64 +# Build Debug or Release +cmake --build build/x64 --config Debug cmake --build build/x64 --config Release # Or use build.bat to build all configurations at once @@ -69,9 +69,9 @@ Key demo projects in `demo/`: Object ├── Control (base for all widgets) │ ├── Window (top-level, owns HWND) -│ ├── BorderlessWindow -│ ├── LayeredWindow -│ ├── PopupWindow +│ ├── BorderlessWindow (frameless window) +│ ├── LayeredWindow (transparency support) +│ ├── PopupWindow (modal/popup dialogs) │ ├── Label │ ├── Button │ ├── TextBox @@ -87,7 +87,7 @@ Object │ ├── TreeView │ ├── TableView │ └── TabLayout -└── IFrame (container frame) +└── IFrame (container frame for embedding) ``` ### Event System @@ -99,7 +99,7 @@ Events defined in [include/EzUI/EzUI.h](include/EzUI/EzUI.h): - Paint: OnPaint - Layout: OnMove, OnSize -ControlState enum: None, Static, Disabled, Checked, Hover, Active +ControlState enum (bit flags): None(1), Static(2), Disabled(4), Checked(8), Hover(16), Active(32) ### Key Patterns @@ -108,6 +108,10 @@ ControlState enum: None, Static, Disabled, Checked, Hover, Active 3. **Styling**: ControlStyle supports inheritance. Use SetStyleSheet() for CSS-like styling 4. **XML Loading**: Window::LoadXml() loads UI from XML files +### Additional Resources + +- [doc/Control_Guide.md](doc/Control_Guide.md) - Comprehensive guide for the Control class, including CSS property mapping, event handling, and best practices + ### File Organization - `include/EzUI/` - Public headers diff --git a/doc/Control_Guide.md b/doc/Control_Guide.md new file mode 100644 index 0000000..06557a5 --- /dev/null +++ b/doc/Control_Guide.md @@ -0,0 +1,1076 @@ +# 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*