diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..3685367
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,18 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(cmd.exe:*)",
+ "Bash(build_all.bat)",
+ "Bash(powershell.exe:*)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\" -S . -B build_x86 -A Win32)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\" --build build_x86 --config Debug --target EzUI)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\" --build build_x86 --config Release --target EzUI)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\" -S . -B build_x64 -A x64)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\":*)",
+ "Bash(\"C:/Program Files/CMake/bin/cmake.exe\" --build build_x64 --config Release --target EzUI)",
+ "Bash(file:*)",
+ "Bash(./test_build.bat)",
+ "Bash(./verify_scripts.bat:*)"
+ ]
+ }
+}
diff --git a/.gitignore b/.gitignore
index f3e0fd2..76521f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
/build
+/build_x64
+/build_x86
/demo
/_temp
/_bin
+/lib
/temp
/bin
/.vs
diff --git a/BUILD.md b/BUILD.md
new file mode 100644
index 0000000..184b076
--- /dev/null
+++ b/BUILD.md
@@ -0,0 +1,111 @@
+# EzUI 构建说明
+
+## 前置要求
+
+### 必需工具
+
+1. **CMake** (版本 3.0 或更高)
+ - 下载地址:https://cmake.org/download/
+ - 安装后建议将 CMake 添加到系统 PATH 环境变量
+ - 脚本会自动在以下位置查找 CMake:
+ - 系统 PATH 中的 `cmake`
+ - `C:\Program Files\CMake\bin\cmake.exe`
+
+2. **Visual Studio 2022** (包含 C++ 构建工具)
+ - 社区版即可免费使用
+ - 下载地址:https://visualstudio.microsoft.com/downloads/
+ - 安装时需要选择"使用 C++ 的桌面开发"工作负载
+
+### 可选工具
+
+- **Git** - 用于版本控制(如需从 GitHub 克隆代码)
+
+## 快速构建
+
+### 构建所有配置
+```bash
+build_all.bat
+```
+
+这将自动构建以下所有配置:
+- x86 Debug → `lib\EzUI_Debug_Win32.lib`
+- x86 Release → `lib\EzUI_Release_Win32.lib`
+- x64 Debug → `lib\EzUI_Debug_x64.lib`
+- x64 Release → `lib\EzUI_Release_x64.lib`
+
+### 单独构建 x86
+```bash
+build_x86.bat
+```
+
+### 单独构建 x64
+```bash
+build_x64.bat
+```
+
+## Visual Studio 开发
+
+如果需要在 Visual Studio 中开发和调试:
+
+```bash
+configure_vs.bat
+```
+
+这将生成 CMake 解决方案文件,然后可以打开:
+- `build_x86\EzUI.sln` (x86 项目)
+- `build_x64\EzUI.sln` (x64 项目)
+
+## 清理构建输出
+
+```bash
+clean.bat
+```
+
+这将删除以下目录:
+- `build_x86/` - x86 构建目录
+- `build_x64/` - x64 构建目录
+- `lib/` - 静态库输出目录
+
+## 手动 CMake 命令
+
+如果你想使用 CMake 命令行:
+
+```bash
+# 配置 x86
+cmake -S . -B build_x86 -A Win32
+
+# 配置 x64
+cmake -S . -B build_x64 -A x64
+
+# 编译 x86 Debug
+cmake --build build_x86 --config Debug --target EzUI
+
+# 编译 x86 Release
+cmake --build build_x86 --config Release --target EzUI
+
+# 编译 x64 Debug
+cmake --build build_x64 --config Debug --target EzUI
+
+# 编译 x64 Release
+cmake --build build_x64 --config Release --target EzUI
+```
+
+## 输出文件
+
+所有静态库文件将输出到 `lib/` 目录,命名规则为:
+
+| 配置 | 平台 | 输出文件 |
+|------|------|----------|
+| Debug | x86 | `lib\EzUI_Debug_Win32.lib` |
+| Release | x86 | `lib\EzUI_Release_Win32.lib` |
+| Debug | x64 | `lib\EzUI_Debug_x64.lib` |
+| Release | x64 | `lib\EzUI_Release_x64.lib` |
+
+## 构建配置说明
+
+- **始终编译为静态库**:不再支持动态库选项
+- **输出目录**:所有库文件统一输出到 `lib/` 目录
+- **命名规则**:`EzUI_$(Configuration)_$(Platform).lib`
+- **字符编码**:使用 UTF-8 编译 (`/utf-8`)
+- **Unicode**:启用 Unicode 支持
+- **预处理器定义**:静态库会自动定义 `EZUI_STATIC`
diff --git a/CLAUDE.md b/CLAUDE.md
index 093b4b8..4e451f4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,30 +6,66 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
EzUI 是一个基于原生 Win32 消息机制和 Direct2D 的 C++ 桌面 UI 框架,提供类似 Web 前端的 CSS 样式系统和弹性布局。
-## 构建命令
+**核心特性:**
+- 基于 Win32 消息机制,轻量无依赖
+- Direct2D 硬件加速渲染,支持高 DPI
+- CSS 风格样式系统,支持伪类和选择器
+- 弹性布局系统,支持自动尺寸和停靠
+- 事件冒泡机制,灵活的事件处理
+- 控件组合与继承,一切皆控件
-```bash
-build_x86.bat # 构建 32 位静态库
-build_x64.bat # 构建 64 位静态库
+## 快速开始
+
+```cpp
+#include "EzUI/UIManager.h"
+#include "EzUI/Window.h"
+#include "EzUI/Application.h"
+
+using namespace ezui;
+
+class TestForm : public Window {
+private:
+ UIManager umg;
+public:
+ TestForm() : Window(800, 600) {
+ umg.LoadXmlData("");
+ umg.SetupUI(this);
+ }
+};
+
+int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
+ Application app;
+ TestForm form;
+ form.SetText(L"窗口标题");
+ form.Show(nCmdShow);
+ return app.Exec();
+}
```
-CMake 构建:
+## 构建命令
+
+**批处理脚本 (快速构建):**
```bash
-cmake -B build # 默认静态库
-cmake --build build
+build_x86.bat # 生成 32 位并构建 Debug/Release
+build_x64.bat # 生成 64 位并构建 Debug/Release
+build_all.bat # 同时构建 32 位和 64 位
+```
-# 动态库
-cmake -B build -DBUILD_SHARED_LIBS=ON
+**CMake 完整构建流程:**
+```bash
+# 配置阶段
+cmake -S . -B build -A Win32 # 32 位配置
+cmake -S . -B build -A x64 # 64 位配置
+cmake -S . -B build -DBUILD_SHARED_LIBS=ON # 构建动态库
+cmake -S . -B build -DBUILD_DEMO=OFF # 不构建 demo 项目
-# 仅构建库(不构建 demo)
-cmake -B build -DBUILD_DEMO=OFF
-
-# 编译为静态库
-cmake --build build/vs2022_x86 --target EzUI --config Debug
-cmake --build build/vs2022_x86 --target EzUI --config Release
-cmake --build build/vs2022_x64 --target EzUI --config Debug
-cmake --build build/vs2022_x64 --target EzUI --config Release
+# 编译阶段
+cmake --build build --config Debug # Debug 版本
+cmake --build build --config Release # Release 版本
+# 单独构建 EzUI 库
+cmake --build build --target EzUI --config Debug
+cmake --build build --target EzUI --config Release
```
## 核心架构
@@ -42,13 +78,39 @@ cmake --build build/vs2022_x64 --target EzUI --config Release
### 控件系统
所有控件继承自 `Control` 基类,核心控件包括:
-- 基础控件:`Label`, `Button`, `TextBox`, `PictureBox`
-- 选择控件:`CheckBox`, `RadioButton`, `ComboBox`
-- 数据控件:`TableView`, `TreeView`, `ListView` 系列
-- 布局容器:`HLayout`, `VLayout`, `HListView`, `VListView`, `TileListView`, `TabLayout`, `PagedListView`
-- 功能控件:`ScrollBar`, `Menu`, `NotifyIcon`, `ProgressBar`, `Spacer`, `IFrame`, `ShadowBox`
-- 表格控件:`TableView`
-- 动画:`Animation` - 类似 Qt 的过渡动画系统
+
+**基础控件:**
+- `Label` - 文本标签
+- `Button` - 标准按钮
+- `TextBox` - 文本输入框(支持多行)
+- `PictureBox` - 图片显示(支持 GIF 动画)
+
+**选择控件:**
+- `CheckBox` - 复选框
+- `RadioButton` - 单选按钮
+- `ComboBox` - 下拉选择框
+
+**数据控件:**
+- `TableView` - 表格视图
+- `TreeView` - 树形视图
+- `ListView` 系列:`HListView`(横向列表)、`VListView`(纵向列表)、`TileListView`(瓦片式)、`PagedListView`(分页列表)
+
+**布局容器:**
+- `HLayout` / `HBox` - 水平布局容器
+- `VLayout` / `VBox` - 垂直布局容器
+- `TabLayout` - 选项卡切换容器
+- `Spacer` - 弹性/固定间距占位符
+
+**功能控件:**
+- `ScrollBar` / `HScrollBar` / `VScrollBar` - 滚动条
+- `Menu` - 菜单系统
+- `NotifyIcon` - 系统托盘图标
+- `ProgressBar` - 进度条
+- `IFrame` - 内嵌框架(类似 HTML iframe)
+- `ShadowBox` - 阴影容器
+
+**动画系统:**
+- `Animation` - 类似 Qt 的属性过渡动画
### 布局系统
- **尺寸优先级**:比例尺寸 (`SetRateWidth/Height`) > 绝对尺寸 (`SetFixedSize`) > 控件内容大小
@@ -58,16 +120,22 @@ cmake --build build/vs2022_x64 --target EzUI --config Release
### 样式与渲染
- `UIManager` - UI 样式与资源管理,支持 XML 布局加载
-- `UISelector` - CSS 选择器匹配系统
-- `Direct2DRender` - Direct2D 绘图实现
-- `RenderTypes` - 颜色、对齐方式等绘图类型
+- `UISelector` - CSS 选择器匹配系统(支持类选择器、ID 选择器、组合选择器)
+- `Direct2DRender` - Direct2D 绘图实现,支持硬件加速
+- `RenderTypes` - 颜色、对齐方式等绘图类型定义
- `UIDef` - 框架内使用的宏定义集合
- `EzUI.h` - 框架主接口头文件,定义事件类、枚举、全局资源等核心结构
+**CSS 样式特性:**
+- 伪类支持:`:hover`、`:active`、`:checked`、`:disabled`
+- 样式属性:width、height、background-color、border、color、font 等
+- 选择器类型:标签选择器、类选择器、ID 选择器、组合选择器
+
### 事件系统
- 支持事件冒泡机制,可实现事件捕获与穿透
- `NotifyFlags` 控制控件哪些事件需要通知窗口
-- `Event` 枚举定义所有事件类型,支持位运算组合
+- `Event` 枚举定义所有事件类型(鼠标、键盘、绘制等),支持位运算组合
+- 事件穿透控制:通过 `m_hitTestEnabled` 控制命中测试
- Debug 模式下按 F11 可查看布局信息和控件边界
### 线程模型
@@ -79,24 +147,50 @@ cmake --build build/vs2022_x64 --target EzUI --config Release
- 控件树内存由父控件自动管理:`Attach`/`Detach`
- 图片资源通过 `PtrManager` 自动释放
- XML 布局加载后由 `UIManager` 管理生命周期
-- `add_resource_package` 函数支持资源打包为 `.res` 文件
+- 资源打包系统:通过 `add_resource_package` CMake 函数将资源打包为 `.res` 文件
+
+**资源打包流程:**
+1. 在 demo 的 CMakeLists.txt 中调用 `add_resource_package(target source_dir output_file)`
+2. 构建时会自动运行 `ResPackage.exe` 工具
+3. 生成的 `.res` 文件可以在运行时通过 `Resource.h` 中的函数加载
+4. 支持本地文件和资源文件的自动加载(通过 `add_resource_package` 函数)
### 调试技巧
- 在 Debug 模式下运行时,按下 `F11` 可实时查看布局信息,高亮显示控件边界
## Demo 项目
-- `helloWorld` - 基础示例
-- `QQ` - 仿 QQ 登录界面(含资源打包)
-- `kugou` - 酷狗音乐播放器(含 VLC 依赖和资源打包)
-- `ResPackage` - 资源打包工具
-- `Adminstor` / `DemoUi` - 管理界面示例
+- `helloWorld` - 基础示例,演示最简单的窗口创建
+- `ResPackage` - 资源打包工具,用于将资源文件打包为 `.res` 格式
+- `QQ` - 仿 QQ 登录界面,展示资源打包和 XML 布局加载
+- `kugou` - 酷狗音乐播放器,演示 VLC 集成和复杂 UI 交互(需要额外依赖)
+- `TableViewDemo` - 表格控件演示,展示 LayeredWindow + VLayout + TableView + 右键菜单的组合使用
+
+**CMake Demo 目标:**
+- 所有 demo 都使用 `add_executable(... WIN32 ...)` 创建
+- 通过 `add_resource_package(target source_dir output_file)` 自动打包资源
+- 资源文件通过 `source_group` 组织到 VS 的 "res" 筛选项下
## 开发约定
-- 头文件位于 `include/EzUI/` 目录
-- 源文件位于 `sources/` 目录
+**目录结构:**
+- 头文件:`include/EzUI/` 目录
+- 源文件:`sources/` 目录
+- Demo 项目:`demo/` 目录
+
+**核心理念:**
- 一切皆控件,纯代码组合 UI
- 使用 CSS 驱动视觉,结构与样式分离
-- XML 解析使用 `tinyxml` 库
+- XML 布局通过 `UIManager::LoadXmlFile()` 或 `LoadXmlData()` 加载
+
+**CMake 自定义命令:**
+- `add_resource_package(target source_dir output_file)` - 打包资源文件为 `.res` 格式
+- `target` 需要与 `add_executable` 的项目名匹配
+- 资源会在构建时自动打包,程序通过 `Resource.h` 提供的函数访问
+- 需要先构建 `ResPackage` 工具(位于 `demo/ResPackage`)
+
+**窗口绘制机制:**
+- `Window` / `BorderlessWindow`:Windows 自动发送 `WM_PAINT` 消息
+- `LayeredWindow`:需手动发送 `WM_PAINT` 消息进行绘制(支持实时重绘)
+- `PopupWindow`:失焦自动关闭,适用于菜单等临时窗口
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62c98fd..314655e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,17 +15,29 @@ add_definitions(-D_UNICODE -DUNICODE)
project(EzUI)
file(GLOB src ./include/EzUI/*.* ./sources/*.* )
-# 添加一个选项,供用户选择构建静态库或动态库
-option(BUILD_SHARED_LIBS "Build shared library instead of static library" OFF)
-if(BUILD_SHARED_LIBS)
- add_library(EzUI SHARED ${src})
-else()
- add_library(EzUI STATIC ${src})
- target_compile_definitions(EzUI PUBLIC EZUI_STATIC)
-endif()
+
+# 始终编译为静态库
+add_library(EzUI STATIC ${src})
+target_compile_definitions(EzUI PUBLIC EZUI_STATIC)
target_include_directories(EzUI PRIVATE ./include/EzUI)
set_target_properties(EzUI PROPERTIES LINKER_LANGUAGE CXX)
+# 设置库文件输出目录和命名规则
+# 平台标识:根据架构判断 Win32 或 x64
+set(PLATFORM_TAG $,x64,Win32>)
+
+# 设置输出目录为 lib
+set_target_properties(EzUI PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib"
+ ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/lib"
+ ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/lib"
+)
+
+# 设置库文件命名规则为 EzUI_$(Configuration)_$(Platform).lib
+set_target_properties(EzUI PROPERTIES
+ OUTPUT_NAME "EzUI_$_${PLATFORM_TAG}"
+)
+
# 定义函数: 添加资源打包任务并关联到指定项目
function(add_resource_package TARGET_NAME INPUT_DIR OUTPUT_FILE)
set (ProjectName ${TARGET_NAME}_EzUI_ResourcePackager)
diff --git a/build_all.bat b/build_all.bat
new file mode 100644
index 0000000..3690643
--- /dev/null
+++ b/build_all.bat
@@ -0,0 +1,71 @@
+@echo off
+setlocal
+
+set "CMAKE_EXE="
+where cmake >nul 2>&1
+if %errorlevel% equ 0 (
+ set "CMAKE_EXE=cmake"
+ echo Found CMake in PATH
+) else if exist "C:\Program Files\CMake\bin\cmake.exe" (
+ set "CMAKE_EXE=C:\Program Files\CMake\bin\cmake.exe"
+ echo Found CMake at C:\Program Files\CMake\bin\cmake.exe
+) else (
+ echo Error: CMake not found!
+ echo Please install CMake or add it to PATH.
+ echo Download: https://cmake.org/download/
+ pause
+ exit /b 1
+)
+
+echo.
+echo ======================================
+echo EzUI Build Script
+echo Building: x86/x64 Debug/Release
+echo ======================================
+echo.
+
+echo [1/6] Configuring x86...
+"%CMAKE_EXE%" -S . -B build_x86 -A Win32
+if %errorlevel% neq 0 goto error
+
+echo [2/6] Building x86 Debug...
+"%CMAKE_EXE%" --build build_x86 --config Debug --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo [3/6] Building x86 Release...
+"%CMAKE_EXE%" --build build_x86 --config Release --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo [4/6] Configuring x64...
+"%CMAKE_EXE%" -S . -B build_x64 -A x64
+if %errorlevel% neq 0 goto error
+
+echo [5/6] Building x64 Debug...
+"%CMAKE_EXE%" --build build_x64 --config Debug --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo [6/6] Building x64 Release...
+"%CMAKE_EXE%" --build build_x64 --config Release --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo.
+echo ======================================
+echo Build completed successfully!
+echo ======================================
+echo.
+echo Output files:
+echo lib\EzUI_Debug_Win32.lib
+echo lib\EzUI_Release_Win32.lib
+echo lib\EzUI_Debug_x64.lib
+echo lib\EzUI_Release_x64.lib
+echo.
+pause
+exit /b 0
+
+:error
+echo.
+echo ======================================
+echo Build failed!
+echo ======================================
+pause
+exit /b 1
diff --git a/build_x64.bat b/build_x64.bat
index c5116bd..3c38e7d 100644
--- a/build_x64.bat
+++ b/build_x64.bat
@@ -1,4 +1,50 @@
-chcp 65001
@echo off
-cmake -S . -B build_x64 -A x64 -DBUILD_EZUI=OFF
+setlocal
+
+set "CMAKE_EXE="
+where cmake >nul 2>&1
+if %errorlevel% equ 0 (
+ set "CMAKE_EXE=cmake"
+) else if exist "C:\Program Files\CMake\bin\cmake.exe" (
+ set "CMAKE_EXE=C:\Program Files\CMake\bin\cmake.exe"
+) else (
+ echo Error: CMake not found!
+ echo Please install CMake or add it to PATH.
+ pause
+ exit /b 1
+)
+
+echo ======================================
+echo Building EzUI x64
+echo ======================================
+echo.
+
+echo [1/3] Configuring x64...
+"%CMAKE_EXE%" -S . -B build_x64 -A x64
+if %errorlevel% neq 0 goto error
+
+echo [2/3] Building x64 Debug...
+"%CMAKE_EXE%" --build build_x64 --config Debug --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo [3/3] Building x64 Release...
+"%CMAKE_EXE%" --build build_x64 --config Release --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo.
+echo ======================================
+echo x64 build completed!
+echo Output:
+echo lib\EzUI_Debug_x64.lib
+echo lib\EzUI_Release_x64.lib
+echo ======================================
pause
+exit /b 0
+
+:error
+echo.
+echo ======================================
+echo Build failed!
+echo ======================================
+pause
+exit /b 1
diff --git a/build_x86.bat b/build_x86.bat
index 30b6f82..eaebefb 100644
--- a/build_x86.bat
+++ b/build_x86.bat
@@ -1,4 +1,50 @@
-chcp 65001
@echo off
-cmake -S . -B build_x86 -A Win32 -DBUILD_EZUI=OFF
+setlocal
+
+set "CMAKE_EXE="
+where cmake >nul 2>&1
+if %errorlevel% equ 0 (
+ set "CMAKE_EXE=cmake"
+) else if exist "C:\Program Files\CMake\bin\cmake.exe" (
+ set "CMAKE_EXE=C:\Program Files\CMake\bin\cmake.exe"
+) else (
+ echo Error: CMake not found!
+ echo Please install CMake or add it to PATH.
+ pause
+ exit /b 1
+)
+
+echo ======================================
+echo Building EzUI x86
+echo ======================================
+echo.
+
+echo [1/3] Configuring x86...
+"%CMAKE_EXE%" -S . -B build_x86 -A Win32
+if %errorlevel% neq 0 goto error
+
+echo [2/3] Building x86 Debug...
+"%CMAKE_EXE%" --build build_x86 --config Debug --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo [3/3] Building x86 Release...
+"%CMAKE_EXE%" --build build_x86 --config Release --target EzUI
+if %errorlevel% neq 0 goto error
+
+echo.
+echo ======================================
+echo x86 build completed!
+echo Output:
+echo lib\EzUI_Debug_Win32.lib
+echo lib\EzUI_Release_Win32.lib
+echo ======================================
pause
+exit /b 0
+
+:error
+echo.
+echo ======================================
+echo Build failed!
+echo ======================================
+pause
+exit /b 1
diff --git a/clean.bat b/clean.bat
new file mode 100644
index 0000000..d703eef
--- /dev/null
+++ b/clean.bat
@@ -0,0 +1,26 @@
+@echo off
+echo ======================================
+echo Cleaning build output...
+echo ======================================
+echo.
+
+if exist build_x86 (
+ echo Removing build_x86...
+ rmdir /s /q build_x86
+)
+
+if exist build_x64 (
+ echo Removing build_x64...
+ rmdir /s /q build_x64
+)
+
+if exist lib (
+ echo Removing lib...
+ rmdir /s /q lib
+)
+
+echo.
+echo ======================================
+echo Clean completed!
+echo ======================================
+pause
diff --git a/configure_vs.bat b/configure_vs.bat
new file mode 100644
index 0000000..ac3e1e1
--- /dev/null
+++ b/configure_vs.bat
@@ -0,0 +1,48 @@
+@echo off
+setlocal
+
+set "CMAKE_EXE="
+where cmake >nul 2>&1
+if %errorlevel% equ 0 (
+ set "CMAKE_EXE=cmake"
+) else if exist "C:\Program Files\CMake\bin\cmake.exe" (
+ set "CMAKE_EXE=C:\Program Files\CMake\bin\cmake.exe"
+) else (
+ echo Error: CMake not found!
+ echo Please install CMake or add it to PATH.
+ pause
+ exit /b 1
+)
+
+echo ======================================
+echo Configuring for Visual Studio
+echo ======================================
+echo.
+
+echo [1/2] Configuring x86...
+"%CMAKE_EXE%" -S . -B build_x86 -A Win32
+if %errorlevel% neq 0 goto error
+
+echo [2/2] Configuring x64...
+"%CMAKE_EXE%" -S . -B build_x64 -A x64
+if %errorlevel% neq 0 goto error
+
+echo.
+echo ======================================
+echo Configuration completed!
+echo ======================================
+echo.
+echo You can now open:
+echo - build_x86\EzUI.sln (x86)
+echo - build_x64\EzUI.sln (x64)
+echo.
+pause
+exit /b 0
+
+:error
+echo.
+echo ======================================
+echo Configuration failed!
+echo ======================================
+pause
+exit /b 1
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
index 4ba172d..cfc961d 100644
--- a/demo/CMakeLists.txt
+++ b/demo/CMakeLists.txt
@@ -93,3 +93,11 @@ set_property(TARGET QQ PROPERTY FOLDER "demo")
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/kugou/extract${ARCH_NAME}.bat" "$"
COMMENT "Running extract.bat to conditionally extract dll.zip"
)
+
+# TableView Demo - LayeredWindow + VLayout + TableView + 右键菜单
+project(TableViewDemo)
+file(GLOB src TableViewDemo/*.*)
+add_executable(TableViewDemo WIN32 ${src})
+target_include_directories(TableViewDemo PRIVATE ${include})
+target_link_libraries(TableViewDemo PRIVATE EzUI)
+set_property(TARGET TableViewDemo PROPERTY FOLDER "demo")
diff --git a/lib/EzUI_Debug_Win32.lib b/lib/EzUI_Debug_Win32.lib
index c0deb6f..1761179 100644
Binary files a/lib/EzUI_Debug_Win32.lib and b/lib/EzUI_Debug_Win32.lib differ
diff --git a/lib/EzUI_Debug_Win32.pdb b/lib/EzUI_Debug_Win32.pdb
index 4ebcc5d..b906095 100644
Binary files a/lib/EzUI_Debug_Win32.pdb and b/lib/EzUI_Debug_Win32.pdb differ
diff --git a/lib/EzUI_Debug_x64.lib b/lib/EzUI_Debug_x64.lib
index 48cffff..b6c54dd 100644
Binary files a/lib/EzUI_Debug_x64.lib and b/lib/EzUI_Debug_x64.lib differ
diff --git a/lib/EzUI_Debug_x64.pdb b/lib/EzUI_Debug_x64.pdb
index deec7b8..70ab3fc 100644
Binary files a/lib/EzUI_Debug_x64.pdb and b/lib/EzUI_Debug_x64.pdb differ
diff --git a/lib/EzUI_Release_Win32.lib b/lib/EzUI_Release_Win32.lib
index a83679d..30f5da7 100644
Binary files a/lib/EzUI_Release_Win32.lib and b/lib/EzUI_Release_Win32.lib differ
diff --git a/lib/EzUI_Release_x64.lib b/lib/EzUI_Release_x64.lib
index a84eb07..88d25b0 100644
Binary files a/lib/EzUI_Release_x64.lib and b/lib/EzUI_Release_x64.lib differ
diff --git a/sources/TableView.cpp b/sources/TableView.cpp
index d6ca7c4..f02996e 100644
--- a/sources/TableView.cpp
+++ b/sources/TableView.cpp
@@ -1,12 +1,15 @@
#include "TableView.h"
-namespace ezui {
+namespace ezui
+{
- TableView::TableView(Object* parentObject) : Control(parentObject) {
+ TableView::TableView(Object *parentObject) : Control(parentObject)
+ {
Init();
}
- TableView::~TableView() {
+ TableView::~TableView()
+ {
EndEdit(false);
// 注意:m_editBox、m_editCombo、m_editCheck 作为子控件,由父控件自动管理
// 不需要手动 delete,否则会导致双重释放
@@ -15,18 +18,21 @@ namespace ezui {
m_editCheck = nullptr;
}
- void TableView::Init() {
+ void TableView::Init()
+ {
// 初始化垂直滚动条
m_vScrollBar.SetWidth(10);
m_vScrollBar.Parent = this;
- m_vScrollBar.OffsetCallback = [this](int offset) {
+ m_vScrollBar.OffsetCallback = [this](int offset)
+ {
this->OffsetY(offset);
};
// 初始化水平滚动条
m_hScrollBar.SetHeight(10);
m_hScrollBar.Parent = this;
- m_hScrollBar.OffsetCallback = [this](int offset) {
+ m_hScrollBar.OffsetCallback = [this](int offset)
+ {
this->OffsetX(offset);
};
@@ -40,75 +46,92 @@ namespace ezui {
m_editBox->Style.FontFamily = L"Microsoft YaHei";
// 添加到子控件列表,这样才能正确处理事件和绘制
this->Add(m_editBox);
- m_editBox->TextChanged = [this](const UIString& text) {
- if (m_editing && m_editRow >= 0 && m_editCol >= 0) {
+ m_editBox->TextChanged = [this](const UIString &text)
+ {
+ if (m_editing && m_editRow >= 0 && m_editCol >= 0)
+ {
// 实时更新数据
- if (m_editRow < (int)m_data.size() && m_editCol < (int)m_data[m_editRow].size()) {
+ if (m_editRow < (int)m_data.size() && m_editCol < (int)m_data[m_editRow].size())
+ {
m_data[m_editRow][m_editCol].Text = text;
UpdateRowHeight(m_editRow);
-
+
// 同步更新编辑框的高度以匹配新的行高
- if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size()) {
+ if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size())
+ {
int x = GetColumnX(m_editCol);
int y = GetRowY(m_editRow);
int width = m_columns[m_editCol].Width;
- int height = m_rowHeights[m_editRow]; // 使用更新后的行高
+ int height = m_rowHeights[m_editRow]; // 使用更新后的行高
m_editBox->SetRect(Rect(x, y, width, height));
}
-
- if (CellValueChanged) {
- CellValueChanged(m_editRow, m_editCol, text);
- }
+
+ // if (CellValueChanged) {
+ // CellValueChanged(m_editRow, m_editCol, text);
+ // }
}
}
};
}
- int TableView::GetContentWidth() const {
+ int TableView::GetContentWidth() const
+ {
int width = m_firstColumnWidth;
- for (const auto& col : m_columns) {
+ for (const auto &col : m_columns)
+ {
width += col.Width;
}
return width;
}
- int TableView::GetContentHeight() const {
+ int TableView::GetContentHeight() const
+ {
int height = m_headerHeight;
- for (int h : m_rowHeights) {
+ for (int h : m_rowHeights)
+ {
height += h;
}
return height;
}
- int TableView::GetColumnX(int colIndex) const {
+ int TableView::GetColumnX(int colIndex) const
+ {
int x = m_firstColumnWidth - m_scrollOffsetX;
- for (int i = 0; i < colIndex && i < (int)m_columns.size(); ++i) {
+ for (int i = 0; i < colIndex && i < (int)m_columns.size(); ++i)
+ {
x += m_columns[i].Width;
}
return x;
}
- int TableView::GetRowY(int rowIndex) const {
+ int TableView::GetRowY(int rowIndex) const
+ {
int y = m_headerHeight - m_scrollOffsetY;
- for (int i = 0; i < rowIndex && i < (int)m_rowHeights.size(); ++i) {
+ for (int i = 0; i < rowIndex && i < (int)m_rowHeights.size(); ++i)
+ {
y += m_rowHeights[i];
}
return y;
}
- bool TableView::HitTestCell(const Point& pt, int* outRow, int* outCol) const {
+ bool TableView::HitTestCell(const Point &pt, int *outRow, int *outCol) const
+ {
// 检查是否在表头区域
- if (pt.Y < m_headerHeight) {
+ if (pt.Y < m_headerHeight)
+ {
*outRow = -1;
// 检查是否在第一列
- if (pt.X < m_firstColumnWidth) {
+ if (pt.X < m_firstColumnWidth)
+ {
*outCol = -1;
return true;
}
// 检查是否在数据列
int x = m_firstColumnWidth;
- for (int i = 0; i < (int)m_columns.size(); ++i) {
- if (pt.X >= x - m_scrollOffsetX && pt.X < x - m_scrollOffsetX + m_columns[i].Width) {
+ for (int i = 0; i < (int)m_columns.size(); ++i)
+ {
+ if (pt.X >= x - m_scrollOffsetX && pt.X < x - m_scrollOffsetX + m_columns[i].Width)
+ {
*outCol = i;
return true;
}
@@ -119,21 +142,26 @@ namespace ezui {
// 检查行
int y = m_headerHeight;
- for (int row = 0; row < (int)m_rowHeights.size(); ++row) {
+ for (int row = 0; row < (int)m_rowHeights.size(); ++row)
+ {
int rowY = y - m_scrollOffsetY;
- if (pt.Y >= rowY && pt.Y < rowY + m_rowHeights[row]) {
+ if (pt.Y >= rowY && pt.Y < rowY + m_rowHeights[row])
+ {
*outRow = row;
// 检查列
- if (pt.X < m_firstColumnWidth) {
- *outCol = -1; // 第一列
+ if (pt.X < m_firstColumnWidth)
+ {
+ *outCol = -1; // 第一列
return true;
}
int x = m_firstColumnWidth;
- for (int col = 0; col < (int)m_columns.size(); ++col) {
+ for (int col = 0; col < (int)m_columns.size(); ++col)
+ {
int colX = x - m_scrollOffsetX;
- if (pt.X >= colX && pt.X < colX + m_columns[col].Width) {
+ if (pt.X >= colX && pt.X < colX + m_columns[col].Width)
+ {
*outCol = col;
return true;
}
@@ -149,51 +177,61 @@ namespace ezui {
return false;
}
- int TableView::HitTestColumnBorder(const Point& pt) const {
- const int borderHitWidth = 5; // 边界检测宽度
+ int TableView::HitTestColumnBorder(const Point &pt) const
+ {
+ const int borderHitWidth = 5; // 边界检测宽度
// 只在表头区域检测
- if (pt.Y >= m_headerHeight) {
- return -2; // 返回-2表示不在表头区域,不应该显示拖动光标
+ if (pt.Y >= m_headerHeight)
+ {
+ return -2; // 返回-2表示不在表头区域,不应该显示拖动光标
}
// 检测第一列右边界
int x = m_firstColumnWidth;
- if (std::abs(pt.X - x) <= borderHitWidth) {
- return -1; // 返回-1表示第一列边界(特殊处理)
+ if (std::abs(pt.X - x) <= borderHitWidth)
+ {
+ return -1; // 返回-1表示第一列边界(特殊处理)
}
// 检测数据列边界
- for (int i = 0; i < (int)m_columns.size(); ++i) {
+ for (int i = 0; i < (int)m_columns.size(); ++i)
+ {
x += m_columns[i].Width;
int borderX = x - m_scrollOffsetX;
- if (std::abs(pt.X - borderX) <= borderHitWidth) {
+ if (std::abs(pt.X - borderX) <= borderHitWidth)
+ {
return i;
}
}
- return -2; // 未命中任何边界
+ return -2; // 未命中任何边界
}
- bool TableView::HitTestVScrollBar(const Point& pt) {
- if (!m_vScrollBar.IsVisible()) {
+ bool TableView::HitTestVScrollBar(const Point &pt)
+ {
+ if (!m_vScrollBar.IsVisible())
+ {
return false;
}
Rect scrollRect = m_vScrollBar.GetRect();
return scrollRect.Contains(pt);
}
- bool TableView::HitTestHScrollBar(const Point& pt) {
- if (!m_hScrollBar.IsVisible()) {
+ bool TableView::HitTestHScrollBar(const Point &pt)
+ {
+ if (!m_hScrollBar.IsVisible())
+ {
return false;
}
Rect scrollRect = m_hScrollBar.GetRect();
return scrollRect.Contains(pt);
}
- void TableView::DrawHeader(PaintEventArgs& args) {
- auto& g = args.Graphics;
-
+ void TableView::DrawHeader(PaintEventArgs &args)
+ {
+ auto &g = args.Graphics;
+
// 绘制表头背景
Rect headerRect(0, 0, Width(), m_headerHeight);
g.SetColor(m_headerBackColor);
@@ -205,18 +243,20 @@ namespace ezui {
g.FillRectangle(RectF(firstColRect));
// 如果第一列是 CheckBox 类型,绘制全选复选框
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
int checkSize = (std::min)(m_headerHeight - 8, 16);
int checkX = (m_firstColumnWidth - checkSize) / 2;
int checkY = (m_headerHeight - checkSize) / 2;
Rect checkRect(checkX, checkY, checkSize, checkSize);
-
+
// 绘制复选框边框
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(checkRect));
-
+
// 如果全选,绘制勾选标记
- if (m_headerSelectAll) {
+ if (m_headerSelectAll)
+ {
g.SetColor(m_headerForeColor);
// 简单的勾选线
PointF p1(checkX + 3, checkY + checkSize / 2);
@@ -225,7 +265,9 @@ namespace ezui {
g.DrawLine(p1, p2);
g.DrawLine(p2, p3);
}
- } else if (m_firstColumnType == FirstColumnType::Index) {
+ }
+ else if (m_firstColumnType == FirstColumnType::Index)
+ {
// 绘制 "#" 或序号标题
Font font(m_cellFontFamily, m_cellFontSize);
g.SetFont(font);
@@ -235,47 +277,55 @@ namespace ezui {
}
// 绘制第一列边框
- if (m_cellBorderStyle != StrokeStyle::None) {
+ if (m_cellBorderStyle != StrokeStyle::None)
+ {
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(firstColRect));
}
int headerClipWidth = Width() - m_firstColumnWidth;
- if (headerClipWidth > 0) {
+ if (headerClipWidth > 0)
+ {
g.PushAxisAlignedClip(RectF((float)m_firstColumnWidth, 0.0f, (float)headerClipWidth, (float)m_headerHeight));
}
// 绘制数据列表头
int x = m_firstColumnWidth - m_scrollOffsetX;
- for (int i = 0; i < (int)m_columns.size(); ++i) {
- const auto& col = m_columns[i];
-
+ for (int i = 0; i < (int)m_columns.size(); ++i)
+ {
+ const auto &col = m_columns[i];
+
// 裁剪到可见区域
- if (x + col.Width <= m_firstColumnWidth || x >= Width()) {
+ if (x + col.Width <= m_firstColumnWidth || x >= Width())
+ {
x += col.Width;
continue;
}
Rect colRect(x, 0, col.Width, m_headerHeight);
-
+
// 绘制表头文字
Font font(m_cellFontFamily, m_cellFontSize);
g.SetFont(font);
g.SetColor(m_headerForeColor);
-
+
// 添加排序指示器
std::wstring headerText = col.HeaderText.unicode();
- if (col.CurrentSort == SortOrder::Ascending) {
+ if (col.CurrentSort == SortOrder::Ascending)
+ {
headerText = headerText + L" ▲";
- } else if (col.CurrentSort == SortOrder::Descending) {
+ }
+ else if (col.CurrentSort == SortOrder::Descending)
+ {
headerText = headerText + L" ▼";
}
-
+
TextLayout layout(headerText, font, SizeF(col.Width - 4, m_headerHeight), TextAlign::MiddleCenter);
g.DrawTextLayout(layout, PointF(x + 2, 0));
// 绘制边框
- if (m_cellBorderStyle != StrokeStyle::None) {
+ if (m_cellBorderStyle != StrokeStyle::None)
+ {
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(colRect));
}
@@ -283,21 +333,25 @@ namespace ezui {
x += col.Width;
}
- if (headerClipWidth > 0) {
+ if (headerClipWidth > 0)
+ {
g.PopAxisAlignedClip();
}
}
- void TableView::DrawCells(PaintEventArgs& args) {
- auto& g = args.Graphics;
+ void TableView::DrawCells(PaintEventArgs &args)
+ {
+ auto &g = args.Graphics;
int startY = m_headerHeight;
-
- for (int row = 0; row < (int)m_data.size(); ++row) {
+
+ for (int row = 0; row < (int)m_data.size(); ++row)
+ {
int rowY = startY + GetRowY(row) - m_headerHeight;
int rowHeight = m_rowHeights[row];
// 跳过不可见行
- if (rowY + rowHeight <= m_headerHeight || rowY >= Height()) {
+ if (rowY + rowHeight <= m_headerHeight || rowY >= Height())
+ {
continue;
}
@@ -306,17 +360,20 @@ namespace ezui {
DrawFirstColumn(args, row, firstColRect);
int rowClipWidth = Width() - m_firstColumnWidth;
- if (rowClipWidth > 0) {
+ if (rowClipWidth > 0)
+ {
g.PushAxisAlignedClip(RectF((float)m_firstColumnWidth, (float)rowY, (float)rowClipWidth, (float)rowHeight));
}
// 绘制数据列
int x = m_firstColumnWidth - m_scrollOffsetX;
- for (int col = 0; col < (int)m_columns.size(); ++col) {
+ for (int col = 0; col < (int)m_columns.size(); ++col)
+ {
int colWidth = m_columns[col].Width;
// 跳过不可见列
- if (x + colWidth <= m_firstColumnWidth || x >= Width()) {
+ if (x + colWidth <= m_firstColumnWidth || x >= Width())
+ {
x += colWidth;
continue;
}
@@ -326,23 +383,27 @@ namespace ezui {
x += colWidth;
}
- if (rowClipWidth > 0) {
+ if (rowClipWidth > 0)
+ {
g.PopAxisAlignedClip();
}
}
}
- void TableView::DrawFirstColumn(PaintEventArgs& args, int row, const Rect& cellRect) {
- auto& g = args.Graphics;
+ void TableView::DrawFirstColumn(PaintEventArgs &args, int row, const Rect &cellRect)
+ {
+ auto &g = args.Graphics;
// 获取行选中状态背景色
Color backColor = m_cellBackColor;
- if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row]) {
+ if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row])
+ {
backColor = SelectedRowBackColor;
}
-
+
// 如果第一列不是 CheckBox 且当前行被选中,使用选中行背景色
- if (m_firstColumnType != FirstColumnType::CheckBox && row == m_selectedRow) {
+ if (m_firstColumnType != FirstColumnType::CheckBox && row == m_selectedRow)
+ {
backColor = SelectedRowBackColor;
}
@@ -351,25 +412,29 @@ namespace ezui {
g.FillRectangle(RectF(cellRect));
// 根据第一列类型绘制内容
- if (m_firstColumnType == FirstColumnType::Index) {
+ if (m_firstColumnType == FirstColumnType::Index)
+ {
// 绘制序号
Font font(m_cellFontFamily, m_cellFontSize);
g.SetFont(font);
g.SetColor(m_cellForeColor);
- std::wstring indexText = std::to_wstring(row + 1); // 从1开始
+ std::wstring indexText = std::to_wstring(row + 1); // 从1开始
TextLayout layout(indexText, font, SizeF(m_firstColumnWidth - 4, cellRect.Height), TextAlign::MiddleCenter);
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y));
- } else if (m_firstColumnType == FirstColumnType::CheckBox) {
+ }
+ else if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
// 绘制复选框
int checkSize = (std::min)(cellRect.Height - 8, 16);
int checkX = cellRect.X + (m_firstColumnWidth - checkSize) / 2;
int checkY = cellRect.Y + (cellRect.Height - checkSize) / 2;
Rect checkRect(checkX, checkY, checkSize, checkSize);
-
+
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(checkRect));
-
- if (row < (int)m_rowChecked.size() && m_rowChecked[row]) {
+
+ if (row < (int)m_rowChecked.size() && m_rowChecked[row])
+ {
g.SetColor(m_cellForeColor);
PointF p1(checkX + 3, checkY + checkSize / 2);
PointF p2(checkX + checkSize / 3, checkY + checkSize - 4);
@@ -381,53 +446,63 @@ namespace ezui {
// TextBox 类型第一列留空
// 绘制边框
- if (m_cellBorderStyle != StrokeStyle::None) {
+ if (m_cellBorderStyle != StrokeStyle::None)
+ {
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(cellRect));
}
}
- void TableView::DrawCell(PaintEventArgs& args, int row, int col, const Rect& cellRect) {
- auto& g = args.Graphics;
+ void TableView::DrawCell(PaintEventArgs &args, int row, int col, const Rect &cellRect)
+ {
+ auto &g = args.Graphics;
- if (row >= (int)m_data.size() || col >= (int)m_data[row].size()) {
+ if (row >= (int)m_data.size() || col >= (int)m_data[row].size())
+ {
return;
}
- const auto& cellData = m_data[row][col];
- const auto& colInfo = m_columns[col];
+ const auto &cellData = m_data[row][col];
+ const auto &colInfo = m_columns[col];
// 确定背景色
Color backColor = m_cellBackColor;
-
+
// 如果第一列是 CheckBox 且选中,使用选中行背景色
- if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row]) {
+ if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row])
+ {
backColor = SelectedRowBackColor;
}
-
+
// 如果第一列不是 CheckBox 且当前行被选中,使用选中行背景色
- if (m_firstColumnType != FirstColumnType::CheckBox && row == m_selectedRow) {
+ if (m_firstColumnType != FirstColumnType::CheckBox && row == m_selectedRow)
+ {
backColor = SelectedRowBackColor;
}
-
+
// 如果单元格有独立背景色,使用独立设置(优先级高)
- if (cellData.Style.HasBackColor) {
+ if (cellData.Style.HasBackColor)
+ {
backColor = cellData.Style.BackColor;
}
// 如果正在编辑此单元格,跳过内容绘制(只绘制背景),让 TextBox 绘制内容
bool isEditing = m_editing && m_editRow == row && m_editCol == col;
-
+
// 绘制背景(编辑时也需要绘制,但使用白色以匹配 TextBox)
- if (isEditing) {
+ if (isEditing)
+ {
g.SetColor(Color::White);
- } else {
+ }
+ else
+ {
g.SetColor(backColor);
}
g.FillRectangle(RectF(cellRect));
// 如果正在编辑此单元格,跳过内容绘制
- if (isEditing) {
+ if (isEditing)
+ {
return;
}
@@ -436,7 +511,8 @@ namespace ezui {
// 确定前景色
Color foreColor = m_cellForeColor;
- if (cellData.Style.HasForeColor) {
+ if (cellData.Style.HasForeColor)
+ {
foreColor = cellData.Style.ForeColor;
}
@@ -448,28 +524,32 @@ namespace ezui {
// 获取对齐方式
TextAlign textAlign = GetCellTextAlign(row, col);
- switch (colInfo.Type) {
+ switch (colInfo.Type)
+ {
case CellType::TextBox:
- case CellType::ReadOnly: {
+ case CellType::ReadOnly:
+ {
// 绘制文本(禁用自动换行,只根据实际换行符换行)
font.Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
// 使用单元格实际高度,这样Middle和Bottom对齐才能正确显示
- TextLayout layout(cellData.Text.unicode(), font,
- SizeF(cellRect.Width - 4, cellRect.Height - 2), textAlign);
+ TextLayout layout(cellData.Text.unicode(), font,
+ SizeF(cellRect.Width - 4, cellRect.Height - 2), textAlign);
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y + 1));
break;
}
- case CellType::CheckBox: {
+ case CellType::CheckBox:
+ {
// 绘制复选框
int checkSize = (std::min)(cellRect.Height - 8, 16);
int checkX = cellRect.X + (cellRect.Width - checkSize) / 2;
int checkY = cellRect.Y + (cellRect.Height - checkSize) / 2;
Rect checkRect(checkX, checkY, checkSize, checkSize);
-
+
g.SetColor(m_cellBorderColor);
g.DrawRectangle(RectF(checkRect));
-
- if (cellData.Checked) {
+
+ if (cellData.Checked)
+ {
g.SetColor(foreColor);
PointF p1(checkX + 3, checkY + checkSize / 2);
PointF p2(checkX + checkSize / 3, checkY + checkSize - 4);
@@ -479,16 +559,18 @@ namespace ezui {
}
break;
}
- case CellType::ComboBox: {
+ case CellType::ComboBox:
+ {
// 绘制下拉框文本
UIString displayText;
- if (cellData.ComboIndex >= 0 && cellData.ComboIndex < (int)colInfo.ComboItems.size()) {
+ if (cellData.ComboIndex >= 0 && cellData.ComboIndex < (int)colInfo.ComboItems.size())
+ {
displayText = colInfo.ComboItems[cellData.ComboIndex];
}
- TextLayout layout(displayText.unicode(), font,
- SizeF(cellRect.Width - 20, cellRect.Height - 2), TextAlign::MiddleLeft);
+ TextLayout layout(displayText.unicode(), font,
+ SizeF(cellRect.Width - 20, cellRect.Height - 2), TextAlign::MiddleLeft);
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y + 1));
-
+
// 绘制下拉箭头
int arrowSize = 8;
int arrowX = cellRect.GetRight() - arrowSize - 4;
@@ -508,45 +590,59 @@ namespace ezui {
// 绘制边框
StrokeStyle borderStyle = m_cellBorderStyle;
Color borderColor = m_cellBorderColor;
-
- if (cellData.Style.HasBorderStyle) {
+
+ if (cellData.Style.HasBorderStyle)
+ {
borderStyle = cellData.Style.BorderStyle;
}
- if (cellData.Style.HasBorderColor) {
+ if (cellData.Style.HasBorderColor)
+ {
borderColor = cellData.Style.BorderColor;
}
- if (borderStyle != StrokeStyle::None) {
+ if (borderStyle != StrokeStyle::None)
+ {
g.SetColor(borderColor);
// TODO: 支持虚线样式
g.DrawRectangle(RectF(cellRect));
}
}
- void TableView::BeginEdit(int row, int col) {
- if (row < 0 || col < 0 || row >= (int)m_data.size() || col >= (int)m_columns.size()) {
+ void TableView::BeginEdit(int row, int col)
+ {
+ if (row < 0 || col < 0 || row >= (int)m_data.size() || col >= (int)m_columns.size())
+ {
return;
}
- const auto& colInfo = m_columns[col];
-
+ const auto &colInfo = m_columns[col];
+
// ReadOnly 类型不能编辑
- if (colInfo.Type == CellType::ReadOnly) {
+ if (colInfo.Type == CellType::ReadOnly)
+ {
return;
}
- EndEdit(true); // 结束之前的编辑
+ EndEdit(true); // 结束之前的编辑
// 保存编辑前的原始值
- if (colInfo.Type == CellType::TextBox) {
+ if (colInfo.Type == CellType::TextBox)
+ {
m_editOriginalValue = m_data[row][col].Text;
- } else if (colInfo.Type == CellType::CheckBox) {
+ }
+ else if (colInfo.Type == CellType::CheckBox)
+ {
m_editOriginalValue = m_data[row][col].Checked ? L"true" : L"false";
- } else if (colInfo.Type == CellType::ComboBox) {
+ }
+ else if (colInfo.Type == CellType::ComboBox)
+ {
int idx = m_data[row][col].ComboIndex;
- if (idx >= 0 && idx < (int)colInfo.ComboItems.size()) {
+ if (idx >= 0 && idx < (int)colInfo.ComboItems.size())
+ {
m_editOriginalValue = colInfo.ComboItems[idx];
- } else {
+ }
+ else
+ {
m_editOriginalValue = L"";
}
}
@@ -563,8 +659,10 @@ namespace ezui {
Rect editRect(x, y, width, height);
- switch (colInfo.Type) {
- case CellType::TextBox: {
+ switch (colInfo.Type)
+ {
+ case CellType::TextBox:
+ {
// 确保 TextBox 字体与单元格字体一致
m_editBox->Style.FontSize = m_cellFontSize;
m_editBox->Style.FontFamily = m_cellFontFamily;
@@ -578,19 +676,22 @@ namespace ezui {
Invalidate();
break;
}
- case CellType::CheckBox: {
+ case CellType::CheckBox:
+ {
// 直接切换状态
m_data[row][col].Checked = !m_data[row][col].Checked;
m_editing = false;
m_editRow = -1;
m_editCol = -1;
- if (CellValueChanged) {
+ if (CellValueChanged)
+ {
CellValueChanged(row, col, m_data[row][col].Checked ? L"true" : L"false");
}
Invalidate();
break;
}
- case CellType::ComboBox: {
+ case CellType::ComboBox:
+ {
// TODO: 显示下拉列表
// 暂时简化处理,循环选择
int nextIndex = (m_data[row][col].ComboIndex + 1) % (std::max)(1, (int)colInfo.ComboItems.size());
@@ -598,13 +699,15 @@ namespace ezui {
m_editing = false;
m_editRow = -1;
m_editCol = -1;
-
+
UIString newValue;
- if (nextIndex >= 0 && nextIndex < (int)colInfo.ComboItems.size()) {
+ if (nextIndex >= 0 && nextIndex < (int)colInfo.ComboItems.size())
+ {
newValue = colInfo.ComboItems[nextIndex];
- }
- if (CellValueChanged) {
- CellValueChanged(row, col, newValue);
+ if (CellValueChanged)
+ {
+ CellValueChanged(row, col, newValue);
+ }
}
Invalidate();
break;
@@ -617,8 +720,10 @@ namespace ezui {
}
}
- void TableView::EndEdit(bool save) {
- if (!m_editing) {
+ void TableView::EndEdit(bool save)
+ {
+ if (!m_editing)
+ {
return;
}
@@ -626,17 +731,27 @@ namespace ezui {
int editCol = m_editCol;
UIString newValue;
- if (save && editRow >= 0 && editCol >= 0) {
- if (editRow < (int)m_data.size() && editCol < (int)m_data[editRow].size()) {
- const auto& colInfo = m_columns[editCol];
- if (colInfo.Type == CellType::TextBox) {
+ if (save && editRow >= 0 && editCol >= 0)
+ {
+ if (editRow < (int)m_data.size() && editCol < (int)m_data[editRow].size())
+ {
+ const auto &colInfo = m_columns[editCol];
+ if (colInfo.Type == CellType::TextBox)
+ {
newValue = m_editBox->GetText();
m_data[editRow][editCol].Text = newValue;
UpdateRowHeight(editRow);
// 触发编辑完成回调
- if (CellEditFinished && newValue != m_editOriginalValue) {
- CellEditFinished(editRow, editCol, m_editOriginalValue, newValue);
+ // if (CellEditFinished && newValue != m_editOriginalValue)
+ // {
+ // CellEditFinished(editRow, editCol, m_editOriginalValue, newValue);
+ // }
+
+ // 触发内容更改回调
+ if (CellValueChanged && newValue != m_editOriginalValue)
+ {
+ CellValueChanged(editRow, editCol, newValue);
}
}
}
@@ -646,32 +761,38 @@ namespace ezui {
m_editing = false;
m_editRow = -1;
m_editCol = -1;
-
+
Invalidate();
}
- void TableView::UpdateRowHeight(int row) {
- if (row < 0 || row >= (int)m_data.size()) {
+ void TableView::UpdateRowHeight(int row)
+ {
+ if (row < 0 || row >= (int)m_data.size())
+ {
return;
}
int maxHeight = m_defaultRowHeight;
// 遍历该行所有单元格,计算最大需要的高度
- for (int col = 0; col < (int)m_data[row].size() && col < (int)m_columns.size(); ++col) {
- const auto& cellData = m_data[row][col];
- const auto& colInfo = m_columns[col];
+ for (int col = 0; col < (int)m_data[row].size() && col < (int)m_columns.size(); ++col)
+ {
+ const auto &cellData = m_data[row][col];
+ const auto &colInfo = m_columns[col];
- if (colInfo.Type == CellType::TextBox || colInfo.Type == CellType::ReadOnly) {
+ if (colInfo.Type == CellType::TextBox || colInfo.Type == CellType::ReadOnly)
+ {
int lines = CalculateTextLines(cellData.Text, colInfo.Width - 4);
int neededHeight = lines * (m_cellFontSize + 4) + 8;
- if (neededHeight > maxHeight) {
+ if (neededHeight > maxHeight)
+ {
maxHeight = neededHeight;
}
}
}
- if (row < (int)m_rowHeights.size()) {
+ if (row < (int)m_rowHeights.size())
+ {
m_rowHeights[row] = maxHeight;
}
@@ -679,71 +800,86 @@ namespace ezui {
Invalidate();
}
- int TableView::CalculateTextLines(const UIString& text, int width) const {
- if (text.empty() || width <= 0) {
+ int TableView::CalculateTextLines(const UIString &text, int width) const
+ {
+ if (text.empty() || width <= 0)
+ {
return 1;
}
// 由于单元格设置为不自动换行,只根据实际换行符计算行数
std::wstring wtext = text.unicode();
int lines = 1;
- for (wchar_t c : wtext) {
- if (c == L'\n') {
+ for (wchar_t c : wtext)
+ {
+ if (c == L'\n')
+ {
lines++;
}
}
return lines;
}
- void TableView::RefreshScrollBars() {
+ void TableView::RefreshScrollBars()
+ {
int contentWidth = GetContentWidth();
int contentHeight = GetContentHeight();
-
+
// 可视区域大小(初步计算,不考虑滚动条)
int viewWidth = Width();
int viewHeight = Height() - m_headerHeight;
-
+
// 判断是否需要滚动条
bool needVScroll = (contentHeight - m_headerHeight) > viewHeight;
bool needHScroll = contentWidth > viewWidth;
-
+
// 如果需要垂直滚动条,水平可视区域要减去滚动条宽度
- if (needVScroll) {
+ if (needVScroll)
+ {
viewWidth -= m_vScrollBar.Width();
// 重新判断是否需要水平滚动条
needHScroll = contentWidth > viewWidth;
}
-
+
// 如果需要水平滚动条,垂直可视区域要减去滚动条高度
- if (needHScroll) {
+ if (needHScroll)
+ {
viewHeight -= m_hScrollBar.Height();
// 重新判断是否需要垂直滚动条
- if (!needVScroll) {
+ if (!needVScroll)
+ {
needVScroll = (contentHeight - m_headerHeight) > viewHeight;
- if (needVScroll) {
+ if (needVScroll)
+ {
viewWidth -= m_vScrollBar.Width();
}
}
}
// 设置垂直滚动条
- if (needVScroll) {
+ if (needVScroll)
+ {
m_vScrollBar.SetVisible(true);
int vScrollHeight = Height() - m_headerHeight - (needHScroll ? m_hScrollBar.Height() : 0);
- m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
- m_vScrollBar.Width(), vScrollHeight));
- } else {
+ m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
+ m_vScrollBar.Width(), vScrollHeight));
+ }
+ else
+ {
m_vScrollBar.SetVisible(false);
m_scrollOffsetY = 0;
}
// 设置水平滚动条
- if (needHScroll) {
+ if (needHScroll)
+ {
m_hScrollBar.SetVisible(true);
int hScrollWidth = Width() - m_firstColumnWidth - (needVScroll ? m_vScrollBar.Width() : 0);
m_hScrollBar.SetRect(Rect(m_firstColumnWidth, Height() - m_hScrollBar.Height(),
- hScrollWidth, m_hScrollBar.Height()));
- } else {
+ hScrollWidth, m_hScrollBar.Height()));
+ }
+ else
+ {
m_hScrollBar.SetVisible(false);
m_scrollOffsetX = 0;
}
@@ -752,12 +888,15 @@ namespace ezui {
m_hScrollBar.RefreshScroll();
}
- void TableView::OffsetX(int offset) {
+ void TableView::OffsetX(int offset)
+ {
m_scrollOffsetX = -offset;
-
+
// 如果正在编辑,更新编辑框位置
- if (m_editing && m_editBox->IsVisible() && m_editRow >= 0 && m_editCol >= 0) {
- if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size()) {
+ if (m_editing && m_editBox->IsVisible() && m_editRow >= 0 && m_editCol >= 0)
+ {
+ if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size())
+ {
int x = GetColumnX(m_editCol);
int y = GetRowY(m_editRow);
int width = m_columns[m_editCol].Width;
@@ -765,16 +904,19 @@ namespace ezui {
m_editBox->SetRect(Rect(x, y, width, height));
}
}
-
+
Invalidate();
}
- void TableView::OffsetY(int offset) {
+ void TableView::OffsetY(int offset)
+ {
m_scrollOffsetY = -offset;
-
+
// 如果正在编辑,更新编辑框位置
- if (m_editing && m_editBox->IsVisible() && m_editRow >= 0 && m_editCol >= 0) {
- if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size()) {
+ if (m_editing && m_editBox->IsVisible() && m_editRow >= 0 && m_editCol >= 0)
+ {
+ if (m_editCol < (int)m_columns.size() && m_editRow < (int)m_rowHeights.size())
+ {
int x = GetColumnX(m_editCol);
int y = GetRowY(m_editRow);
int width = m_columns[m_editCol].Width;
@@ -782,42 +924,51 @@ namespace ezui {
m_editBox->SetRect(Rect(x, y, width, height));
}
}
-
+
Invalidate();
}
- void TableView::DoSort(int colIndex) {
- if (colIndex < 0 || colIndex >= (int)m_columns.size()) {
+ void TableView::DoSort(int colIndex)
+ {
+ if (colIndex < 0 || colIndex >= (int)m_columns.size())
+ {
return;
}
// 更新排序状态
SortOrder newOrder = SortOrder::Ascending;
- if (m_columns[colIndex].CurrentSort == SortOrder::Ascending) {
+ if (m_columns[colIndex].CurrentSort == SortOrder::Ascending)
+ {
newOrder = SortOrder::Descending;
- } else if (m_columns[colIndex].CurrentSort == SortOrder::Descending) {
+ }
+ else if (m_columns[colIndex].CurrentSort == SortOrder::Descending)
+ {
newOrder = SortOrder::None;
}
// 重置其他列的排序状态
- for (auto& col : m_columns) {
+ for (auto &col : m_columns)
+ {
col.CurrentSort = SortOrder::None;
}
m_columns[colIndex].CurrentSort = newOrder;
- if (newOrder == SortOrder::None) {
+ if (newOrder == SortOrder::None)
+ {
Invalidate();
return;
}
// 创建索引数组进行排序
std::vector indices(m_data.size());
- for (int i = 0; i < (int)indices.size(); ++i) {
+ for (int i = 0; i < (int)indices.size(); ++i)
+ {
indices[i] = i;
}
// 排序
- std::sort(indices.begin(), indices.end(), [this, colIndex, newOrder](int a, int b) {
+ std::sort(indices.begin(), indices.end(), [this, colIndex, newOrder](int a, int b)
+ {
UIString textA, textB;
if (colIndex < (int)m_data[a].size()) {
textA = m_data[a][colIndex].Text;
@@ -830,8 +981,7 @@ namespace ezui {
return textA < textB;
} else {
return textA > textB;
- }
- });
+ } });
// 重新排列数据
std::vector> newData(m_data.size());
@@ -839,16 +989,20 @@ namespace ezui {
std::vector newRowChecked(m_rowChecked.size());
std::vector newRowTextAlign(m_rowTextAlign.size());
- for (int i = 0; i < (int)indices.size(); ++i) {
+ for (int i = 0; i < (int)indices.size(); ++i)
+ {
int oldIndex = indices[i];
newData[i] = std::move(m_data[oldIndex]);
- if (oldIndex < (int)m_rowHeights.size()) {
+ if (oldIndex < (int)m_rowHeights.size())
+ {
newRowHeights[i] = m_rowHeights[oldIndex];
}
- if (oldIndex < (int)m_rowChecked.size()) {
+ if (oldIndex < (int)m_rowChecked.size())
+ {
newRowChecked[i] = m_rowChecked[oldIndex];
}
- if (oldIndex < (int)m_rowTextAlign.size()) {
+ if (oldIndex < (int)m_rowTextAlign.size())
+ {
newRowTextAlign[i] = m_rowTextAlign[oldIndex];
}
}
@@ -863,92 +1017,109 @@ namespace ezui {
// ============ 重写的虚函数 ============
- void TableView::OnPaint(PaintEventArgs& args) {
+ void TableView::OnPaint(PaintEventArgs &args)
+ {
__super::OnPaint(args);
-
+
// 绘制单元格(先绘制,这样表头会覆盖在上面)
DrawCells(args);
-
+
// 绘制表头
DrawHeader(args);
// 绘制滚动条
- if (m_vScrollBar.IsVisible()) {
+ if (m_vScrollBar.IsVisible())
+ {
m_vScrollBar.SendEvent(args);
}
- if (m_hScrollBar.IsVisible()) {
+ if (m_hScrollBar.IsVisible())
+ {
m_hScrollBar.SendEvent(args);
}
// 绘制编辑控件(放在最后确保在最上层)
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
m_editBox->SendEvent(args);
}
}
- void TableView::OnChildPaint(PaintEventArgs& args) {
+ void TableView::OnChildPaint(PaintEventArgs &args)
+ {
// 重写子控件绘制,跳过 m_editBox(因为我们在 OnPaint 中单独处理)
// 其他子控件正常绘制
ViewControls.clear();
Rect rect(0, 0, Width(), Height());
- for (auto& it : GetControls()) {
- if (it == m_editBox) {
- continue; // 跳过编辑框,由 OnPaint 单独处理
+ for (auto &it : GetControls())
+ {
+ if (it == m_editBox)
+ {
+ continue; // 跳过编辑框,由 OnPaint 单独处理
}
- if (rect.IntersectsWith(it->GetRect())) {
+ if (rect.IntersectsWith(it->GetRect()))
+ {
ViewControls.push_back(it);
}
it->SendEvent(args);
}
}
- void TableView::OnLayout() {
+ void TableView::OnLayout()
+ {
// 设置滚动条位置
- m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
- m_vScrollBar.Width(), Height() - m_headerHeight - (m_hScrollBar.IsVisible() ? m_hScrollBar.Height() : 0)));
-
+ m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
+ m_vScrollBar.Width(), Height() - m_headerHeight - (m_hScrollBar.IsVisible() ? m_hScrollBar.Height() : 0)));
+
m_hScrollBar.SetRect(Rect(m_firstColumnWidth, Height() - m_hScrollBar.Height(),
- Width() - m_firstColumnWidth - (m_vScrollBar.IsVisible() ? m_vScrollBar.Width() : 0), m_hScrollBar.Height()));
+ Width() - m_firstColumnWidth - (m_vScrollBar.IsVisible() ? m_vScrollBar.Width() : 0), m_hScrollBar.Height()));
RefreshScrollBars();
__super::OnLayout();
}
- void TableView::OnMouseMove(const MouseEventArgs& args) {
+ void TableView::OnMouseMove(const MouseEventArgs &args)
+ {
__super::OnMouseMove(args);
// 转发事件给编辑框(用于文本选择)
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
Rect editRect = m_editBox->GetRect();
- m_editBox->SendEvent(MouseEventArgs(Event::OnMouseMove,
- Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
- args.Button, args.ZDelta));
+ m_editBox->SendEvent(MouseEventArgs(Event::OnMouseMove,
+ Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
+ args.Button, args.ZDelta));
}
// 转发事件给滚动条
- if (m_vScrollBar.IsVisible()) {
- m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
- Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (m_vScrollBar.IsVisible())
+ {
+ m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
+ Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
+ args.Button, args.ZDelta));
}
- if (m_hScrollBar.IsVisible()) {
- m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
- Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (m_hScrollBar.IsVisible())
+ {
+ m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
+ Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
+ args.Button, args.ZDelta));
}
// 列宽拖动
- if (m_draggingColumn) {
+ if (m_draggingColumn)
+ {
int delta = args.Location.X - m_dragStartX;
int newWidth = (std::max)(20, m_dragStartWidth + delta);
-
- if (m_dragColumnIndex == -1) {
+
+ if (m_dragColumnIndex == -1)
+ {
// 拖动第一列
m_firstColumnWidth = newWidth;
- } else if (m_dragColumnIndex >= 0 && m_dragColumnIndex < (int)m_columns.size()) {
+ }
+ else if (m_dragColumnIndex >= 0 && m_dragColumnIndex < (int)m_columns.size())
+ {
m_columns[m_dragColumnIndex].Width = newWidth;
}
-
+
RefreshScrollBars();
Invalidate();
return;
@@ -956,17 +1127,22 @@ namespace ezui {
// 检测鼠标是否在列边界上
int borderCol = HitTestColumnBorder(args.Location);
- if (borderCol >= -1) { // -1 表示第一列边界
+ if (borderCol >= -1)
+ { // -1 表示第一列边界
// 设置调整大小的光标
Style.Cursor = LoadCursor(Cursor::SIZEWE);
- } else {
+ }
+ else
+ {
Style.Cursor = LoadCursor(Cursor::ARROW);
}
// 更新悬停状态
int row, col;
- if (HitTestCell(args.Location, &row, &col)) {
- if (m_hoverRow != row || m_hoverCol != col) {
+ if (HitTestCell(args.Location, &row, &col))
+ {
+ if (m_hoverRow != row || m_hoverCol != col)
+ {
m_hoverRow = row;
m_hoverCol = col;
Invalidate();
@@ -974,81 +1150,101 @@ namespace ezui {
}
}
- void TableView::OnMouseDown(const MouseEventArgs& args) {
+ void TableView::OnMouseDown(const MouseEventArgs &args)
+ {
__super::OnMouseDown(args);
// 右键单击
- if (args.Button == MouseButton::Right) {
+ if (args.Button == MouseButton::Right)
+ {
int row, col;
- if (HitTestCell(args.Location, &row, &col)) {
- if (RightClick) {
+ if (HitTestCell(args.Location, &row, &col))
+ {
+ if (RightClick)
+ {
RightClick(row, col);
}
}
return;
}
- if (args.Button != MouseButton::Left) {
+ if (args.Button != MouseButton::Left)
+ {
return;
}
// 检查是否点击在编辑框上
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
Rect editRect = m_editBox->GetRect();
- if (editRect.Contains(args.Location)) {
- m_editBox->SendEvent(MouseEventArgs(Event::OnMouseDown,
- Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
- args.Button, args.ZDelta));
+ if (editRect.Contains(args.Location))
+ {
+ m_editBox->SendEvent(MouseEventArgs(Event::OnMouseDown,
+ Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
+ args.Button, args.ZDelta));
return;
}
}
// 检查是否点击在滚动条上
- if (HitTestVScrollBar(args.Location)) {
- m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
- Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (HitTestVScrollBar(args.Location))
+ {
+ m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
+ Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
+ args.Button, args.ZDelta));
return;
}
- if (HitTestHScrollBar(args.Location)) {
- m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
- Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (HitTestHScrollBar(args.Location))
+ {
+ m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
+ Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
+ args.Button, args.ZDelta));
return;
}
// 检测是否点击在列边界上
int borderCol = HitTestColumnBorder(args.Location);
- if (borderCol >= -1) {
+ if (borderCol >= -1)
+ {
m_draggingColumn = true;
m_dragColumnIndex = borderCol;
m_dragStartX = args.Location.X;
- if (borderCol == -1) {
+ if (borderCol == -1)
+ {
m_dragStartWidth = m_firstColumnWidth;
- } else {
+ }
+ else
+ {
m_dragStartWidth = m_columns[borderCol].Width;
}
return;
}
int row, col;
- if (!HitTestCell(args.Location, &row, &col)) {
+ if (!HitTestCell(args.Location, &row, &col))
+ {
EndEdit(true);
return;
}
// 点击表头
- if (row == -1) {
- if (col == -1) {
+ if (row == -1)
+ {
+ if (col == -1)
+ {
// 点击第一列表头(全选)
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
m_headerSelectAll = !m_headerSelectAll;
- for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
+ for (int i = 0; i < (int)m_rowChecked.size(); ++i)
+ {
m_rowChecked[i] = m_headerSelectAll;
}
Invalidate();
}
- } else {
+ }
+ else
+ {
// 点击数据列表头(排序)
DoSort(col);
}
@@ -1056,23 +1252,29 @@ namespace ezui {
}
// 点击第一列
- if (col == -1) {
- if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size()) {
+ if (col == -1)
+ {
+ if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size())
+ {
// CheckBox 模式:切换该行的选中状态
m_rowChecked[row] = !m_rowChecked[row];
-
+
// 更新全选状态
bool allChecked = true;
- for (bool checked : m_rowChecked) {
- if (!checked) {
+ for (bool checked : m_rowChecked)
+ {
+ if (!checked)
+ {
allChecked = false;
break;
}
}
m_headerSelectAll = allChecked;
-
+
Invalidate();
- } else if (m_firstColumnType != FirstColumnType::CheckBox) {
+ }
+ else if (m_firstColumnType != FirstColumnType::CheckBox)
+ {
// 非 CheckBox 模式:单击选中当前行
m_selectedRow = row;
Invalidate();
@@ -1083,12 +1285,14 @@ namespace ezui {
// 点击数据单元格
// 获取单元格类型
CellType cellType = CellType::TextBox;
- if (col >= 0 && col < (int)m_columns.size()) {
+ if (col >= 0 && col < (int)m_columns.size())
+ {
cellType = m_columns[col].Type;
}
// CheckBox 和 ComboBox 单击就触发
- if (cellType == CellType::CheckBox || cellType == CellType::ComboBox) {
+ if (cellType == CellType::CheckBox || cellType == CellType::ComboBox)
+ {
BeginEdit(row, col);
m_lastClickTime = 0;
m_lastClickRow = -1;
@@ -1098,14 +1302,17 @@ namespace ezui {
// TextBox 类型需要双击才进入编辑
ULONGLONG currentTime = ::GetTickCount64();
- if (currentTime - m_lastClickTime < 300 &&
- m_lastClickRow == row && m_lastClickCol == col) {
+ if (currentTime - m_lastClickTime < 300 &&
+ m_lastClickRow == row && m_lastClickCol == col)
+ {
// 双击,开始编辑
BeginEdit(row, col);
- m_lastClickTime = 0; // 重置,避免三击触发
+ m_lastClickTime = 0; // 重置,避免三击触发
m_lastClickRow = -1;
m_lastClickCol = -1;
- } else {
+ }
+ else
+ {
// 单击,记录时间和位置
m_lastClickTime = currentTime;
m_lastClickRow = row;
@@ -1114,247 +1321,301 @@ namespace ezui {
}
}
- void TableView::OnMouseUp(const MouseEventArgs& args) {
+ void TableView::OnMouseUp(const MouseEventArgs &args)
+ {
__super::OnMouseUp(args);
m_draggingColumn = false;
// 转发事件给编辑框
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
Rect editRect = m_editBox->GetRect();
- m_editBox->SendEvent(MouseEventArgs(Event::OnMouseUp,
- Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
- args.Button, args.ZDelta));
+ m_editBox->SendEvent(MouseEventArgs(Event::OnMouseUp,
+ Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
+ args.Button, args.ZDelta));
}
// 转发事件给滚动条
- if (m_vScrollBar.IsVisible()) {
- m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
- Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (m_vScrollBar.IsVisible())
+ {
+ m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
+ Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
+ args.Button, args.ZDelta));
}
- if (m_hScrollBar.IsVisible()) {
- m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
- Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
- args.Button, args.ZDelta));
+ if (m_hScrollBar.IsVisible())
+ {
+ m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
+ Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
+ args.Button, args.ZDelta));
}
}
- void TableView::OnMouseDoubleClick(const MouseEventArgs& args) {
+ void TableView::OnMouseDoubleClick(const MouseEventArgs &args)
+ {
__super::OnMouseDoubleClick(args);
int row, col;
- if (HitTestCell(args.Location, &row, &col) && row >= 0 && col >= 0) {
+ if (HitTestCell(args.Location, &row, &col) && row >= 0 && col >= 0)
+ {
BeginEdit(row, col);
}
}
- void TableView::OnMouseWheel(const MouseEventArgs& args) {
+ void TableView::OnMouseWheel(const MouseEventArgs &args)
+ {
__super::OnMouseWheel(args);
-
- if (m_vScrollBar.IsVisible()) {
+
+ if (m_vScrollBar.IsVisible())
+ {
m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseWheel, args.Location, args.Button, args.ZDelta));
}
}
- void TableView::OnMouseLeave(const MouseEventArgs& args) {
+ void TableView::OnMouseLeave(const MouseEventArgs &args)
+ {
__super::OnMouseLeave(args);
m_hoverRow = -1;
m_hoverCol = -1;
Style.Cursor = LoadCursor(Cursor::ARROW);
}
- void TableView::OnSize(const SizeEventArgs& args) {
+ void TableView::OnSize(const SizeEventArgs &args)
+ {
__super::OnSize(args);
RefreshScrollBars();
}
- void TableView::OnKeyDown(const KeyboardEventArgs& args) {
+ void TableView::OnKeyDown(const KeyboardEventArgs &args)
+ {
__super::OnKeyDown(args);
-
+
// 如果编辑框可见,将键盘事件转发给编辑框
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
m_editBox->SendEvent(args);
}
-
- if (args.wParam == VK_ESCAPE) {
+
+ if (args.wParam == VK_ESCAPE)
+ {
EndEdit(false);
- } else if (args.wParam == VK_RETURN && !m_editing) {
+ }
+ else if (args.wParam == VK_RETURN && !m_editing)
+ {
// 如果不是多行编辑,回车可以结束编辑
}
}
- void TableView::OnKeyChar(const KeyboardEventArgs& args) {
+ void TableView::OnKeyChar(const KeyboardEventArgs &args)
+ {
__super::OnKeyChar(args);
-
+
// 如果编辑框可见,将字符输入事件转发给编辑框
- if (m_editBox->IsVisible()) {
+ if (m_editBox->IsVisible())
+ {
m_editBox->SendEvent(args);
}
}
// ============ 公共接口实现 ============
- void TableView::SetHeaders(const std::vector& headers) {
+ void TableView::SetHeaders(const std::vector &headers)
+ {
m_columns.clear();
- for (const auto& header : headers) {
+ for (const auto &header : headers)
+ {
ColumnInfo col;
col.HeaderText = header;
col.Width = 100;
m_columns.push_back(col);
}
-
+
// 更新现有数据的列数
- for (auto& row : m_data) {
+ for (auto &row : m_data)
+ {
row.resize(m_columns.size());
}
-
+
RefreshScrollBars();
Invalidate();
}
- int TableView::GetColumnCount() const {
+ int TableView::GetColumnCount() const
+ {
return (int)m_columns.size();
}
- void TableView::SetHeaderHeight(int height) {
+ void TableView::SetHeaderHeight(int height)
+ {
m_headerHeight = (std::max)(20, height);
RefreshScrollBars();
Invalidate();
}
- int TableView::GetHeaderHeight() const {
+ int TableView::GetHeaderHeight() const
+ {
return m_headerHeight;
}
- void TableView::SetFirstColumnType(FirstColumnType type) {
+ void TableView::SetFirstColumnType(FirstColumnType type)
+ {
m_firstColumnType = type;
-
+
// 如果切换到 CheckBox 类型,初始化选中状态
- if (type == FirstColumnType::CheckBox) {
+ if (type == FirstColumnType::CheckBox)
+ {
m_rowChecked.resize(m_data.size(), false);
}
-
+
Invalidate();
}
- FirstColumnType TableView::GetFirstColumnType() const {
+ FirstColumnType TableView::GetFirstColumnType() const
+ {
return m_firstColumnType;
}
- void TableView::SetFirstColumnWidth(int width) {
+ void TableView::SetFirstColumnWidth(int width)
+ {
m_firstColumnWidth = (std::max)(20, width);
RefreshScrollBars();
Invalidate();
}
- int TableView::GetFirstColumnWidth() const {
+ int TableView::GetFirstColumnWidth() const
+ {
return m_firstColumnWidth;
}
- void TableView::SetColumnWidth(int colIndex, int width) {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ void TableView::SetColumnWidth(int colIndex, int width)
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
m_columns[colIndex].Width = (std::max)(20, width);
RefreshScrollBars();
Invalidate();
}
}
- int TableView::GetColumnWidth(int colIndex) const {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ int TableView::GetColumnWidth(int colIndex) const
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
return m_columns[colIndex].Width;
}
return 0;
}
- const std::vector TableView::GetColumnWidths() const {
+ const std::vector TableView::GetColumnWidths() const
+ {
std::vector widths;
- for (const auto& col : m_columns) {
+ for (const auto &col : m_columns)
+ {
widths.push_back(col.Width);
}
return widths;
}
- void TableView::SetColumnType(int colIndex, CellType type) {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ void TableView::SetColumnType(int colIndex, CellType type)
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
m_columns[colIndex].Type = type;
Invalidate();
}
}
- CellType TableView::GetColumnType(int colIndex) const {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ CellType TableView::GetColumnType(int colIndex) const
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
return m_columns[colIndex].Type;
}
return CellType::TextBox;
}
- void TableView::SetColumnComboItems(int colIndex, const std::vector& items) {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ void TableView::SetColumnComboItems(int colIndex, const std::vector &items)
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
m_columns[colIndex].ComboItems = items;
}
}
- int TableView::GetRowCount() const {
+ int TableView::GetRowCount() const
+ {
return (int)m_data.size();
}
- void TableView::AddRow() {
+ void TableView::AddRow()
+ {
std::vector newRow(m_columns.size());
m_data.push_back(newRow);
m_rowHeights.push_back(m_defaultRowHeight);
m_rowTextAlign.push_back(m_cellTextAlign); // 添加默认对齐
-
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
m_rowChecked.push_back(false);
}
-
+
RefreshScrollBars();
Invalidate();
}
- void TableView::InsertRow(int rowIndex) {
- if (rowIndex < 0) rowIndex = 0;
- if (rowIndex > (int)m_data.size()) rowIndex = (int)m_data.size();
-
+ void TableView::InsertRow(int rowIndex)
+ {
+ if (rowIndex < 0)
+ rowIndex = 0;
+ if (rowIndex > (int)m_data.size())
+ rowIndex = (int)m_data.size();
+
std::vector newRow(m_columns.size());
m_data.insert(m_data.begin() + rowIndex, newRow);
m_rowHeights.insert(m_rowHeights.begin() + rowIndex, m_defaultRowHeight);
-
+
// 确保rowTextAlign大小与数据一致
- while (m_rowTextAlign.size() < m_data.size()) {
+ while (m_rowTextAlign.size() < m_data.size())
+ {
m_rowTextAlign.push_back(m_cellTextAlign);
}
- if (rowIndex < (int)m_rowTextAlign.size()) {
+ if (rowIndex < (int)m_rowTextAlign.size())
+ {
m_rowTextAlign.insert(m_rowTextAlign.begin() + rowIndex, m_cellTextAlign);
}
-
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
m_rowChecked.insert(m_rowChecked.begin() + rowIndex, false);
}
-
+
RefreshScrollBars();
Invalidate();
}
- void TableView::RemoveRow(int rowIndex) {
- if (rowIndex >= 0 && rowIndex < (int)m_data.size()) {
+ void TableView::RemoveRow(int rowIndex)
+ {
+ if (rowIndex >= 0 && rowIndex < (int)m_data.size())
+ {
m_data.erase(m_data.begin() + rowIndex);
m_rowHeights.erase(m_rowHeights.begin() + rowIndex);
-
- if (rowIndex < (int)m_rowTextAlign.size()) {
+
+ if (rowIndex < (int)m_rowTextAlign.size())
+ {
m_rowTextAlign.erase(m_rowTextAlign.begin() + rowIndex);
}
-
- if (rowIndex < (int)m_rowChecked.size()) {
+
+ if (rowIndex < (int)m_rowChecked.size())
+ {
m_rowChecked.erase(m_rowChecked.begin() + rowIndex);
}
-
+
RefreshScrollBars();
Invalidate();
}
}
- void TableView::ClearRows() {
+ void TableView::ClearRows()
+ {
m_data.clear();
m_rowHeights.clear();
m_rowChecked.clear();
@@ -1366,131 +1627,164 @@ namespace ezui {
Invalidate();
}
- void TableView::AddColumn(const UIString& headerText, int width) {
+ void TableView::AddColumn(const UIString &headerText, int width)
+ {
ColumnInfo col;
col.HeaderText = headerText;
col.Width = (std::max)(20, width);
m_columns.push_back(col);
-
+
// 为所有行添加新列的数据
- for (auto& row : m_data) {
+ for (auto &row : m_data)
+ {
row.push_back(CellData());
}
-
+
RefreshScrollBars();
Invalidate();
}
- void TableView::InsertColumn(int colIndex, const UIString& headerText, int width) {
- if (colIndex < 0) colIndex = 0;
- if (colIndex > (int)m_columns.size()) colIndex = (int)m_columns.size();
-
+ void TableView::InsertColumn(int colIndex, const UIString &headerText, int width)
+ {
+ if (colIndex < 0)
+ colIndex = 0;
+ if (colIndex > (int)m_columns.size())
+ colIndex = (int)m_columns.size();
+
ColumnInfo col;
col.HeaderText = headerText;
col.Width = (std::max)(20, width);
m_columns.insert(m_columns.begin() + colIndex, col);
-
+
// 为所有行插入新列的数据
- for (auto& row : m_data) {
+ for (auto &row : m_data)
+ {
row.insert(row.begin() + colIndex, CellData());
}
-
+
RefreshScrollBars();
Invalidate();
}
- void TableView::RemoveColumn(int colIndex) {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ void TableView::RemoveColumn(int colIndex)
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
m_columns.erase(m_columns.begin() + colIndex);
-
+
// 从所有行删除该列的数据
- for (auto& row : m_data) {
- if (colIndex < (int)row.size()) {
+ for (auto &row : m_data)
+ {
+ if (colIndex < (int)row.size())
+ {
row.erase(row.begin() + colIndex);
}
}
-
+
RefreshScrollBars();
Invalidate();
}
}
- int TableView::GetRowHeight(int rowIndex) const {
- if (rowIndex >= 0 && rowIndex < (int)m_rowHeights.size()) {
+ int TableView::GetRowHeight(int rowIndex) const
+ {
+ if (rowIndex >= 0 && rowIndex < (int)m_rowHeights.size())
+ {
return m_rowHeights[rowIndex];
}
return m_defaultRowHeight;
}
- const std::vector TableView::GetRowHeights() const {
+ const std::vector TableView::GetRowHeights() const
+ {
return m_rowHeights;
}
- void TableView::SetDefaultRowHeight(int height) {
+ void TableView::SetDefaultRowHeight(int height)
+ {
m_defaultRowHeight = (std::max)(20, height);
}
- void TableView::SetData(int row, int col, const UIString& value) {
+ void TableView::SetData(int row, int col, const UIString &value)
+ {
// 自动扩展行
- while (row >= (int)m_data.size()) {
+ while (row >= (int)m_data.size())
+ {
AddRow();
}
-
+
// 确保列足够
- if (col >= 0 && col < (int)m_columns.size()) {
- if (col >= (int)m_data[row].size()) {
+ if (col >= 0 && col < (int)m_columns.size())
+ {
+ if (col >= (int)m_data[row].size())
+ {
m_data[row].resize(m_columns.size());
}
m_data[row][col].Text = value;
UpdateRowHeight(row);
-
- if (CellValueChanged) {
- CellValueChanged(row, col, value);
- }
+
+ // if (CellValueChanged) {
+ // CellValueChanged(row, col, value);
+ // }
}
}
- UIString TableView::GetData(int row, int col) const {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ UIString TableView::GetData(int row, int col) const
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
return m_data[row][col].Text;
}
return UIString();
}
- void TableView::SetRowData(int row, const std::vector& values) {
+ void TableView::SetRowData(int row, const std::vector &values)
+ {
// 自动扩展行
- while (row >= (int)m_data.size()) {
+ while (row >= (int)m_data.size())
+ {
AddRow();
}
-
- for (int col = 0; col < (int)values.size() && col < (int)m_columns.size(); ++col) {
- if (col >= (int)m_data[row].size()) {
+
+ for (int col = 0; col < (int)values.size() && col < (int)m_columns.size(); ++col)
+ {
+ if (col >= (int)m_data[row].size())
+ {
m_data[row].resize(m_columns.size());
}
m_data[row][col].Text = values[col];
}
-
+
UpdateRowHeight(row);
}
- std::vector TableView::GetRowData(int row) const {
+ std::vector TableView::GetRowData(int row) const
+ {
std::vector result;
- if (row >= 0 && row < (int)m_data.size()) {
- for (const auto& cell : m_data[row]) {
+ if (row >= 0 && row < (int)m_data.size())
+ {
+ for (const auto &cell : m_data[row])
+ {
result.push_back(cell.Text);
}
}
return result;
}
- std::vector TableView::GetColData(int col) const {
+ std::vector TableView::GetColData(int col) const
+ {
std::vector result;
- if (col >= 0 && col < (int)m_columns.size()) {
- for (const auto& row : m_data) {
- if (col < (int)row.size()) {
+ if (col >= 0 && col < (int)m_columns.size())
+ {
+ for (const auto &row : m_data)
+ {
+ if (col < (int)row.size())
+ {
result.push_back(row[col].Text);
- } else {
+ }
+ else
+ {
result.push_back(UIString());
}
}
@@ -1498,297 +1792,369 @@ namespace ezui {
return result;
}
- void TableView::SetAllData(const std::vector>& data) {
+ void TableView::SetAllData(const std::vector> &data)
+ {
ClearRows();
-
- for (const auto& rowData : data) {
+
+ for (const auto &rowData : data)
+ {
AddRow();
int row = (int)m_data.size() - 1;
- for (int col = 0; col < (int)rowData.size() && col < (int)m_columns.size(); ++col) {
+ for (int col = 0; col < (int)rowData.size() && col < (int)m_columns.size(); ++col)
+ {
m_data[row][col].Text = rowData[col];
}
UpdateRowHeight(row);
}
}
- std::vector> TableView::GetAllData() const {
+ std::vector> TableView::GetAllData() const
+ {
std::vector> result;
- for (int row = 0; row < (int)m_data.size(); ++row) {
+ for (int row = 0; row < (int)m_data.size(); ++row)
+ {
result.push_back(GetRowData(row));
}
return result;
}
- void TableView::SetCellChecked(int row, int col, bool checked) {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ void TableView::SetCellChecked(int row, int col, bool checked)
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
m_data[row][col].Checked = checked;
Invalidate();
}
}
- bool TableView::GetCellChecked(int row, int col) const {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ bool TableView::GetCellChecked(int row, int col) const
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
return m_data[row][col].Checked;
}
return false;
}
- void TableView::SetCellComboIndex(int row, int col, int index) {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ void TableView::SetCellComboIndex(int row, int col, int index)
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
m_data[row][col].ComboIndex = index;
Invalidate();
}
}
- int TableView::GetCellComboIndex(int row, int col) const {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ int TableView::GetCellComboIndex(int row, int col) const
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
return m_data[row][col].ComboIndex;
}
return -1;
}
- void TableView::SetRowChecked(int row, bool checked) {
- if (m_firstColumnType == FirstColumnType::CheckBox &&
- row >= 0 && row < (int)m_rowChecked.size()) {
+ void TableView::SetRowChecked(int row, bool checked)
+ {
+ if (m_firstColumnType == FirstColumnType::CheckBox &&
+ row >= 0 && row < (int)m_rowChecked.size())
+ {
m_rowChecked[row] = checked;
-
+
// 更新全选状态
bool allChecked = true;
- for (bool c : m_rowChecked) {
- if (!c) {
+ for (bool c : m_rowChecked)
+ {
+ if (!c)
+ {
allChecked = false;
break;
}
}
m_headerSelectAll = allChecked;
-
+
Invalidate();
}
}
- bool TableView::GetRowChecked(int row) const {
- if (row >= 0 && row < (int)m_rowChecked.size()) {
+ bool TableView::GetRowChecked(int row) const
+ {
+ if (row >= 0 && row < (int)m_rowChecked.size())
+ {
return m_rowChecked[row];
}
return false;
}
- std::vector TableView::GetCheckedRows() const {
+ std::vector TableView::GetCheckedRows() const
+ {
std::vector result;
- for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
- if (m_rowChecked[i]) {
+ for (int i = 0; i < (int)m_rowChecked.size(); ++i)
+ {
+ if (m_rowChecked[i])
+ {
result.push_back(i);
}
}
return result;
}
- int TableView::GetSelectedRow() const {
+ int TableView::GetSelectedRow() const
+ {
return m_selectedRow;
}
- void TableView::SetSelectedRow(int row) {
- if (row >= -1 && row < (int)m_data.size()) {
+ void TableView::SetSelectedRow(int row)
+ {
+ if (row >= -1 && row < (int)m_data.size())
+ {
m_selectedRow = row;
Invalidate();
}
}
- void TableView::ClearSelection() {
+ void TableView::ClearSelection()
+ {
m_selectedRow = -1;
Invalidate();
}
- void TableView::SelectAll() {
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+ void TableView::SelectAll()
+ {
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
m_headerSelectAll = true;
- for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
+ for (int i = 0; i < (int)m_rowChecked.size(); ++i)
+ {
m_rowChecked[i] = true;
}
Invalidate();
}
}
- void TableView::DeselectAll() {
- if (m_firstColumnType == FirstColumnType::CheckBox) {
+ void TableView::DeselectAll()
+ {
+ if (m_firstColumnType == FirstColumnType::CheckBox)
+ {
m_headerSelectAll = false;
- for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
+ for (int i = 0; i < (int)m_rowChecked.size(); ++i)
+ {
m_rowChecked[i] = false;
}
Invalidate();
}
}
- void TableView::SetCellBorderSize(int size) {
+ void TableView::SetCellBorderSize(int size)
+ {
m_cellBorderSize = (std::max)(0, size);
Invalidate();
}
- void TableView::SetCellBorderStyle(StrokeStyle style) {
+ void TableView::SetCellBorderStyle(StrokeStyle style)
+ {
m_cellBorderStyle = style;
Invalidate();
}
- void TableView::SetCellBorderColor(const Color& color) {
+ void TableView::SetCellBorderColor(const Color &color)
+ {
m_cellBorderColor = color;
Invalidate();
}
- void TableView::SetCellBackColor(const Color& color) {
+ void TableView::SetCellBackColor(const Color &color)
+ {
m_cellBackColor = color;
Invalidate();
}
- void TableView::SetCellForeColor(const Color& color) {
+ void TableView::SetCellForeColor(const Color &color)
+ {
m_cellForeColor = color;
Invalidate();
}
- void TableView::SetCellFontSize(int size) {
+ void TableView::SetCellFontSize(int size)
+ {
m_cellFontSize = (std::max)(8, size);
// 同步更新编辑框字体
m_editBox->Style.FontSize = m_cellFontSize;
// 更新所有行高
- for (int i = 0; i < (int)m_data.size(); ++i) {
+ for (int i = 0; i < (int)m_data.size(); ++i)
+ {
UpdateRowHeight(i);
}
}
- void TableView::SetCellFontFamily(const std::wstring& fontFamily) {
+ void TableView::SetCellFontFamily(const std::wstring &fontFamily)
+ {
m_cellFontFamily = fontFamily;
// 同步更新编辑框字体
m_editBox->Style.FontFamily = m_cellFontFamily;
// 更新所有行高
- for (int i = 0; i < (int)m_data.size(); ++i) {
+ for (int i = 0; i < (int)m_data.size(); ++i)
+ {
UpdateRowHeight(i);
}
}
- void TableView::SetCellStyle(int row, int col, const CellStyle& style) {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ void TableView::SetCellStyle(int row, int col, const CellStyle &style)
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
m_data[row][col].Style = style;
Invalidate();
}
}
- CellStyle TableView::GetCellStyle(int row, int col) const {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ CellStyle TableView::GetCellStyle(int row, int col) const
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
return m_data[row][col].Style;
}
return CellStyle();
}
- void TableView::ResetCellStyle(int row, int col) {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ void TableView::ResetCellStyle(int row, int col)
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
m_data[row][col].Style.Reset();
Invalidate();
}
}
- void TableView::SetHeaderBackColor(const Color& color) {
+ void TableView::SetHeaderBackColor(const Color &color)
+ {
m_headerBackColor = color;
Invalidate();
}
- void TableView::SetHeaderForeColor(const Color& color) {
+ void TableView::SetHeaderForeColor(const Color &color)
+ {
m_headerForeColor = color;
Invalidate();
}
- void TableView::SetDefaultTextAlign(TextAlign align) {
+ void TableView::SetDefaultTextAlign(TextAlign align)
+ {
m_cellTextAlign = align;
Invalidate();
}
- void TableView::SetColumnTextAlign(int colIndex, TextAlign align) {
- if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
+ void TableView::SetColumnTextAlign(int colIndex, TextAlign align)
+ {
+ if (colIndex >= 0 && colIndex < (int)m_columns.size())
+ {
m_columns[colIndex].CellTextAlign = align;
Invalidate();
}
}
- void TableView::SetRowTextAlign(int rowIndex, TextAlign align) {
+ void TableView::SetRowTextAlign(int rowIndex, TextAlign align)
+ {
// 自动扩展
- while (rowIndex >= (int)m_rowTextAlign.size()) {
+ while (rowIndex >= (int)m_rowTextAlign.size())
+ {
m_rowTextAlign.push_back(m_cellTextAlign); // 使用默认值
}
- if (rowIndex >= 0 && rowIndex < (int)m_rowTextAlign.size()) {
+ if (rowIndex >= 0 && rowIndex < (int)m_rowTextAlign.size())
+ {
m_rowTextAlign[rowIndex] = align;
Invalidate();
}
}
- void TableView::SetCellTextAlign(int row, int col, TextAlign align) {
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ void TableView::SetCellTextAlign(int row, int col, TextAlign align)
+ {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
m_data[row][col].Style.SetTextAlign(align);
Invalidate();
}
}
- TextAlign TableView::GetCellTextAlign(int row, int col) const {
+ TextAlign TableView::GetCellTextAlign(int row, int col) const
+ {
// 优先级:单元格 > 行 > 列 > 默认
- if (row >= 0 && row < (int)m_data.size() &&
- col >= 0 && col < (int)m_data[row].size()) {
+ if (row >= 0 && row < (int)m_data.size() &&
+ col >= 0 && col < (int)m_data[row].size())
+ {
// 单元格级别
- if (m_data[row][col].Style.HasTextAlign) {
+ if (m_data[row][col].Style.HasTextAlign)
+ {
return m_data[row][col].Style.Align;
}
}
// 行级别(只有当行索引在 m_rowTextAlign 范围内才使用)
- if (row >= 0 && row < (int)m_rowTextAlign.size()) {
+ if (row >= 0 && row < (int)m_rowTextAlign.size())
+ {
// 这里应该检查是否真的设置过,但为了兼容性,我们假设设置过就使用
// 如果数组已扩展,就使用该行的对齐方式
return m_rowTextAlign[row];
}
// 列级别
- if (col >= 0 && col < (int)m_columns.size()) {
+ if (col >= 0 && col < (int)m_columns.size())
+ {
return m_columns[col].CellTextAlign;
}
// 默认值
return m_cellTextAlign;
}
- int TableView::GetHoverRow() const {
+ int TableView::GetHoverRow() const
+ {
return m_hoverRow;
}
- int TableView::GetHoverCol() const {
+ int TableView::GetHoverCol() const
+ {
return m_hoverCol;
}
- void TableView::GetHoverCell(int* outRow, int* outCol) const {
- if (outRow) *outRow = m_hoverRow;
- if (outCol) *outCol = m_hoverCol;
+ void TableView::GetHoverCell(int *outRow, int *outCol) const
+ {
+ if (outRow)
+ *outRow = m_hoverRow;
+ if (outCol)
+ *outCol = m_hoverCol;
}
- const Size& TableView::GetContentSize() {
+ const Size &TableView::GetContentSize()
+ {
// 计算内容大小供滚动条使用
m_contentSize.Width = GetContentWidth();
m_contentSize.Height = GetContentHeight();
return m_contentSize;
}
- ScrollBar* TableView::GetVScrollBar() {
+ ScrollBar *TableView::GetVScrollBar()
+ {
return &m_vScrollBar;
}
- ScrollBar* TableView::GetHScrollBar() {
+ ScrollBar *TableView::GetHScrollBar()
+ {
return &m_hScrollBar;
}
- void TableView::SetAttribute(const UIString& key, const UIString& value) {
+ void TableView::SetAttribute(const UIString &key, const UIString &value)
+ {
__super::SetAttribute(key, value);
// 辅助函数:解析整数
- auto parseInt = [](const UIString& v) -> int {
+ auto parseInt = [](const UIString &v) -> int
+ {
UIString tmp = v;
ui_text::Replace(&tmp, "px", "");
tmp = tmp.trim();
@@ -1796,114 +2162,142 @@ namespace ezui {
};
// 辅助函数:解析颜色
- auto parseColor = [](const UIString& v) -> Color {
+ auto parseColor = [](const UIString &v) -> Color
+ {
bool isGood = false;
return Color::Make(v, &isGood);
};
- do {
+ do
+ {
// 表头高度
- if (key == "header-height" || key == "headerheight") {
+ if (key == "header-height" || key == "headerheight")
+ {
SetHeaderHeight(parseInt(value));
break;
}
// 第一列类型
- if (key == "first-column-type" || key == "firstcolumntype") {
- if (value == "index" || value == "number") {
+ if (key == "first-column-type" || key == "firstcolumntype")
+ {
+ if (value == "index" || value == "number")
+ {
SetFirstColumnType(FirstColumnType::Index);
- } else if (value == "checkbox" || value == "check") {
+ }
+ else if (value == "checkbox" || value == "check")
+ {
SetFirstColumnType(FirstColumnType::CheckBox);
- } else {
+ }
+ else
+ {
SetFirstColumnType(FirstColumnType::TextBox);
}
break;
}
// 第一列宽度
- if (key == "first-column-width" || key == "firstcolumnwidth") {
+ if (key == "first-column-width" || key == "firstcolumnwidth")
+ {
SetFirstColumnWidth(parseInt(value));
break;
}
// 默认行高
- if (key == "default-row-height" || key == "defaultrowheight" || key == "row-height" || key == "rowheight") {
+ if (key == "default-row-height" || key == "defaultrowheight" || key == "row-height" || key == "rowheight")
+ {
SetDefaultRowHeight(parseInt(value));
break;
}
// 单元格边框大小
- if (key == "cell-border-size" || key == "cellbordersize" || key == "border-size" || key == "bordersize") {
+ if (key == "cell-border-size" || key == "cellbordersize" || key == "border-size" || key == "bordersize")
+ {
SetCellBorderSize(parseInt(value));
break;
}
// 单元格边框样式
- if (key == "cell-border-style" || key == "cellborderstyle" || key == "border-style" || key == "borderstyle") {
- if (value == "solid") {
+ if (key == "cell-border-style" || key == "cellborderstyle" || key == "border-style" || key == "borderstyle")
+ {
+ if (value == "solid")
+ {
SetCellBorderStyle(StrokeStyle::Solid);
- } else if (value == "dash" || value == "dashed") {
+ }
+ else if (value == "dash" || value == "dashed")
+ {
SetCellBorderStyle(StrokeStyle::Dash);
- } else if (value == "none") {
+ }
+ else if (value == "none")
+ {
SetCellBorderStyle(StrokeStyle::None);
}
break;
}
// 单元格边框颜色
- if (key == "cell-border-color" || key == "cellbordercolor" || key == "border-color" || key == "bordercolor") {
+ if (key == "cell-border-color" || key == "cellbordercolor" || key == "border-color" || key == "bordercolor")
+ {
SetCellBorderColor(parseColor(value));
break;
}
// 单元格背景颜色
- if (key == "cell-back-color" || key == "cellbackcolor" || key == "cell-background" || key == "cellbackground") {
+ if (key == "cell-back-color" || key == "cellbackcolor" || key == "cell-background" || key == "cellbackground")
+ {
SetCellBackColor(parseColor(value));
break;
}
// 单元格前景颜色(文字颜色)
- if (key == "cell-fore-color" || key == "cellforecolor" || key == "cell-color" || key == "cellcolor") {
+ if (key == "cell-fore-color" || key == "cellforecolor" || key == "cell-color" || key == "cellcolor")
+ {
SetCellForeColor(parseColor(value));
break;
}
// 单元格字体大小
- if (key == "cell-font-size" || key == "cellfontsize") {
+ if (key == "cell-font-size" || key == "cellfontsize")
+ {
SetCellFontSize(parseInt(value));
break;
}
// 单元格字体
- if (key == "cell-font-family" || key == "cellfontfamily" || key == "cell-font" || key == "cellfont") {
+ if (key == "cell-font-family" || key == "cellfontfamily" || key == "cell-font" || key == "cellfont")
+ {
SetCellFontFamily(value.unicode());
break;
}
// 表头背景颜色
- if (key == "header-back-color" || key == "headerbackcolor" || key == "header-background" || key == "headerbackground") {
+ if (key == "header-back-color" || key == "headerbackcolor" || key == "header-background" || key == "headerbackground")
+ {
SetHeaderBackColor(parseColor(value));
break;
}
// 表头前景颜色(文字颜色)
- if (key == "header-fore-color" || key == "headerforecolor" || key == "header-color" || key == "headercolor") {
+ if (key == "header-fore-color" || key == "headerforecolor" || key == "header-color" || key == "headercolor")
+ {
SetHeaderForeColor(parseColor(value));
break;
}
// 选中行背景颜色
- if (key == "selected-row-back-color" || key == "selectedrowbackcolor" || key == "selected-color" || key == "selectedcolor") {
+ if (key == "selected-row-back-color" || key == "selectedrowbackcolor" || key == "selected-color" || key == "selectedcolor")
+ {
SelectedRowBackColor = parseColor(value);
Invalidate();
break;
}
// 表头文字(逗号分隔)
- if (key == "headers" || key == "columns") {
+ if (key == "headers" || key == "columns")
+ {
auto headers = value.split(",");
std::vector headerList;
- for (auto& h : headers) {
+ for (auto &h : headers)
+ {
headerList.push_back(h.trim());
}
SetHeaders(headerList);