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);