feat: add a new awesome feature

This commit is contained in:
Ysm-04
2025-09-22 15:08:49 +08:00
parent bd4588731b
commit 9f2b175b17
25 changed files with 1962 additions and 616 deletions

84
.gitignore vendored
View File

@@ -1,25 +1,13 @@
# 编译生成文件
*.exe
*.ilk
*.obj
*.pdb
*.o
*.a
*.lib
*.dll
*.exp
*.so
*.dylib
# 编译输出目录
/build/
/bin/
/lib/
# 构建目录
build/
bin/
obj/
Debug/
Release/
x64/
x86/
Win32/
# CMake 生成文件
/CMakeFiles/
/CMakeCache.txt
/cmake_install.cmake
/Makefile
# Visual Studio 文件
.vs/
@@ -27,51 +15,15 @@ Win32/
*.vcxproj
*.vcxproj.filters
*.vcxproj.user
*.suo
*.ncb
*.user
*.sdf
ipch/
*.aps
*.res
*.tlog
*.lastbuildstate
*.idb
*.pch
# CMake 生成文件
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
Makefile
*.cmake
*.cmake.*
CTestTestfile.cmake
Testing/
_deps/
# 文档生成
docs/html/
docs/latex/
docs/man/
docs/rtf/
docs/xml/
# 编译产物
*.obj
*.pdb
*.log
*.exe
*.dll
*.lib
# 临时文件
*.tmp
*.temp
*.log
*.swp
*.swo
# EasyX 临时文件
*.jpg
*.bmp
*.png
*.tga
# 其他
.DS_Store
Thumbs.db
ehthumbs.db
Desktop.ini
*.cache
*.zip

View File

@@ -5,6 +5,104 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [v2.0.0] - 2025-09-21
### 概述
v2.0.0 为一次重大升级major release。本次发布新增对话框与消息框工厂Dialog / MessageBox并对事件分发、API语义与内部资源管理做了若干重要修复和改进。
部分 API/行为发生不向后兼容的变化breaking changes
### 新增
- **对话框系统**:
- 新增 `Dialog` 类,继承自 `Canvas`,用于构建模态与非模态对话框
- 新增 `MessageBox` 工厂类,提供 `ShowModal`(同步阻塞)与 `ShowAsync`异步回调两种便捷API
- 支持六种标准消息框类型:`OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore`
- 自动处理对话框布局、背景保存与恢复、事件传播和生命周期管理
- **事件系统增强**:
- 所有控件的 `handleEvent` 方法现在返回 `bool` 类型,表示是否消费了事件
- 引入事件消费机制,支持更精细的事件传播控制
- Window 类的事件循环现在优先处理对话框事件
- **控件状态管理**:
- Control 基类新增 `dirty` 标志和 `setDirty()` 方法,统一管理重绘状态
- 所有控件现在都实现了 `IsVisible()``model()` 方法
- **API 增强**:
- Button 类新增 `setButtonFalseColor()` 方法
- TextBox 类的 `setMaxCharLen()` 现在接受 `size_t` 类型参数
- Window 类新增对话框管理方法和去重检测机制
### 重大变更Breaking Changes
- **API 签名变更**:
- 所有控件的 `handleEvent(const ExMessage& msg)` 方法从返回 `void` 改为返回 `bool`
- Control 基类新增纯虚函数 `IsVisible() const``model() const`,所有派生类必须实现
- **资源管理变更**:
- Control 基类的样式保存从栈对象改为堆对象,使用指针管理
- 增强了资源释放的安全性,所有资源都在析构函数中正确释放
- **事件处理逻辑**:
- Window 的 `runEventLoop()` 方法完全重写,现在优先处理对话框事件
- 引入了事件消费机制,事件被消费后不会继续传播
### 修复Fixed
- **内存管理**:
- 修复 `Button::setFillIma()` 的内存泄漏问题
- 修复 Control 基类析构函数中的资源释放问题
- 修复 Dialog 类背景图像资源管理问题
- **布局与渲染**:
- 修复 `Table` 组件的分页计算、列宽和行高越界问题
- 修复 `Table``pX` 坐标累加错误导致的布局错乱
- 修复 `Canvas::draw()` 中导致子控件不被绘制的 dirty 判定问题
- 修复 `TextBox::draw()``restoreStyle()` 的不对称调用问题
- **事件处理**:
- 修复窗口事件分发逻辑,确保对话框/顶层控件优先处理事件
- 修复鼠标移出按钮区域时状态更新不及时的问题
- 修复非模态对话框事件处理中的竞争条件
- **其他问题**:
- 修复文本测量和渲染中的潜在错误
- 修复对话框关闭后背景恢复不完全的问题
- 修复多对话框场景下的 z-order 管理问题
### 改进Changed
- **代码质量**:
- 重构了大量内部 API增强异常安全性与可维护性
- 使用智能指针和现代 C++ 特性替代裸 new/delete
- 统一了代码风格和命名约定
- **性能优化**:
- 优化了事件处理流程,减少不必要的重绘
- 改进了对话框背景保存和恢复机制
- 减少了内存分配和拷贝操作
- **文档与示例**:
- 为所有新增功能添加了详细的使用示例
- 完善了代码注释和 API 文档
- 更新了 README 和 CHANGELOG 反映最新变化
## [1.1.0] - 2025-09-08
### 新增
- **Window 类 API 增强**:
- 添加了完整的获取器(getter)方法集,提高类的封装性和可用性
- `getHwnd()` - 获取窗口句柄,便于与原生 Windows API 集成
- `getWidth()` - 获取窗口宽度
- `getHeight()` - 获取窗口高度
- `getHeadline()` - 获取窗口标题
- `getBkcolor()` - 获取窗口背景颜色
- `getBkImage()` - 获取窗口背景图片指针
- `getControls()` - 获取控件管理容器的引用,允许迭代和操作已添加的控件
### 改进
- **API 一致性**: 为所有重要属性提供了对称的设置器(setter)和获取器(getter)方法
- **代码文档**: 进一步完善了类注释,使其更加清晰和专业
## [1.0.0] - 2025-09-08
### 发布摘要

View File

@@ -1,130 +1,19 @@
cmake_minimum_required(VERSION 3.15)
project(StellarX VERSION 1.0.0 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.10)
# 设置C++标准
# 项目定义
project(StellarX VERSION 2.0.0 LANGUAGES CXX)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Windows特定设置
if(WIN32)
add_definitions(-DWIN32 -D_WINDOWS)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# 包含头文件目录(目前头文件都在根目录)
include_directories(${CMAKE_SOURCE_DIR})
# 设置Windows子系统
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS")
endif()
# 设置输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 包含目录
include_directories(include)
# Windows API库
if(WIN32)
find_library(GDI32_LIBRARY gdi32)
find_library(USER32_LIBRARY user32)
find_library(KERNEL32_LIBRARY kernel32)
set(WIN32_LIBS ${GDI32_LIBRARY} ${USER32_LIBRARY} ${KERNEL32_LIBRARY})
endif()
# 创建库
file(GLOB_RECURSE SOURCES "src/*.cpp")
add_library(StellarX STATIC ${SOURCES})
# 链接Windows库
if(WIN32)
target_link_libraries(StellarX ${WIN32_LIBS})
endif()
# 设置库属性
set_target_properties(StellarX PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
PUBLIC_HEADER "include/StellarX/StellarX.h"
OUTPUT_NAME "StellarX"
# 源文件收集
file(GLOB_RECURSE SOURCES
"${CMAKE_SOURCE_DIR}/*.cpp"
)
# 安装规则
install(DIRECTORY include/StellarX DESTINATION include)
install(TARGETS StellarX
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/StellarX
)
# 示例程序
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples)
file(GLOB EXAMPLE_SOURCES "examples/*.cpp")
foreach(example_source ${EXAMPLE_SOURCES})
get_filename_component(example_name ${example_source} NAME_WE)
add_executable(${example_name} ${example_source})
target_link_libraries(${example_name} StellarX ${WIN32_LIBS})
# 设置Windows子系统
if(WIN32)
set_target_properties(${example_name} PROPERTIES
LINK_FLAGS "/SUBSYSTEM:WINDOWS"
)
endif()
# 安装示例
install(TARGETS ${example_name} DESTINATION bin)
endforeach()
endif()
# 文档生成选项
option(BUILD_DOCS "Build documentation" OFF)
if(BUILD_DOCS)
find_package(Doxygen)
if(Doxygen_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
set(DOXYGEN_PROJECT_NAME "StellarX GUI Framework")
set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION})
set(DOXYGEN_PROJECT_BRIEF "A lightweight, modular C++ GUI framework for Windows")
set(DOXYGEN_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_PRIVATE YES)
set(DOXYGEN_EXTRACT_STATIC YES)
set(DOXYGEN_SOURCE_BROWSER YES)
set(DOXYGEN_GENERATE_TREEVIEW YES)
set(DOXYGEN_HAVE_DOT YES)
set(DOXYGEN_CALL_GRAPH YES)
set(DOXYGEN_CALLER_GRAPH YES)
doxygen_add_docs(
docs
${DOXYGEN_INPUT}
COMMENT "Generate HTML documentation"
)
install(DIRECTORY ${DOXYGEN_OUTPUT_DIRECTORY}/html DESTINATION docs)
endif()
endif()
# 打包配置
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_DESCRIPTION "A lightweight, modular C++ GUI framework for Windows")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "星垣 (StellarX) - A lightweight Windows GUI framework")
set(CPACK_PACKAGE_VENDOR "StellarX Development Team")
set(CPACK_PACKAGE_CONTACT "contact@stellarx.dev")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "StellarX")
set(CPACK_NSIS_MODIFY_PATH ON)
if(WIN32)
set(CPACK_GENERATOR "ZIP;NSIS")
else()
set(CPACK_GENERATOR "ZIP")
endif()
include(CPack)
# 生成可执行文件
add_executable(StellarX ${SOURCES})

154
README.md
View File

@@ -1,7 +1,7 @@
# 星垣 (StellarX) GUI Framework
![Version](https://img.shields.io/badge/Version-1.0.0-brightgreen.svg) <!-- 新增版本徽章 -->
![Download](https://img.shields.io/badge/Download-1.0.0_Release-blue.svg) <!-- 新增下载徽章 -->
![Version](https://img.shields.io/badge/Version-2.0.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-1.0.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -22,7 +22,7 @@
星垣框架采用经典的**面向对象**和**模块化**设计,项目结构清晰规范:
```
```markdown
StellarX/
├── include/ # 头文件目录
│ └── StellarX/ # 框架头文件
@@ -34,6 +34,8 @@ StellarX/
│ ├── Label.h # 标签控件
│ ├── TextBox.h # 文本框控件
│ ├── Canvas.h # 画布容器
│ ├── Dialog.h # 对话框控件v2.0.0新增)
│ ├── MessageBox.h # 消息框工厂v2.0.0新增)
│ └── Table.h # 表格控件
├── src/ # 源文件目录
│ ├── Control.cpp
@@ -42,7 +44,9 @@ StellarX/
│ ├── Label.cpp
│ ├── TextBox.cpp
│ ├── Canvas.cpp
── Table.cpp
── Table.cpp
│ ├── Dialog.cpp # v2.0.0新增
│ └── MessageBox.cpp # v2.0.0新增
├── examples/ # 示例代码目录
│ └── demo.cpp # 基础演示
├── docs/ # 文档目录
@@ -56,6 +60,7 @@ StellarX/
```
**设计理念:**
1. **单一职责原则 (SRP)**: 每个类/文件只负责一件事。
2. **依赖倒置原则 (DIP)**: 高层模块(如`Window`)不依赖低层模块(如`Button`),二者都依赖其抽象(`Control`)。
3. **开闭原则 (OCP)**: 通过继承`Control`基类,可以轻松扩展新的控件,而无需修改现有代码。
@@ -66,10 +71,12 @@ StellarX/
- **极致的轻量级**: 核心库编译后仅 ~1.2MB,无任何外部依赖。生成的应用程序小巧玲珑。
- **清晰的模块化架构**: 使用`CoreTypes.h`统一管理所有类型,消除重复定义,极大提升可维护性。
- **原生C++性能**: 直接基于EasyX和Win32 API提供接近原生的执行效率内存占用极低通常<10MB
- **丰富的控件**: 提供按钮、标签、文本框、表格、画布等常用控件,满足基本桌面应用需求
- **完整的控件体系**: 按钮Button、标签Label、文本框TextBox、画布Canvas、表格Table、对话框Dialog与消息框工厂MessageBox
- **高度可定制化**: 从控件颜色、形状(矩形、圆角、圆形、椭圆)到填充模式、字体样式,均有详尽枚举支持,可轻松定制。
- **简洁直观的API**: 采用经典的面向对象设计,代码即文档,学习成本极低。
- **标准项目结构**: 采用标准的include/src分离结构支持CMake构建易于集成和使用。
- **增强的事件系统**: v2.0.0引入事件消费机制,所有`handleEvent`方法返回`bool`表示是否消费事件,支持更精细的事件传播控制。
- **对话框系统**: 新增完整的对话框支持,包括模态和非模态对话框,自动处理背景保存和恢复。
## ⚡ 快速开始5分钟上手
@@ -126,8 +133,8 @@ StellarX/
#include "StellarX.h"
// 程序入口点请使用WinMain以获得更好的兼容性
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) {
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
// 1. 创建一个640x480的窗口背景为白色标题为"我的应用"
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
@@ -140,10 +147,22 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
);
// 3. 为按钮设置点击事件使用Lambda表达式
myButton->setOnClickListener([]() {
MessageBox(nullptr, "Hello, 星垣!", "问候", MB_OK | MB_ICONINFORMATION);
myButton->setOnClickListener([&mainWindow]() {
// 使用消息框工厂创建模态对话框
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"欢迎使用星垣GUI\r\n作者我在人间做废物",
"问候",
StellarX::MessageBoxType::OKCancel
);
// 处理对话框结果
if (result == StellarX::MessageBoxResult::OK) {
// 用户点击了确定按钮
}
});
// 4. (可选)设置按钮样式
myButton->textStyle.nHeight = 20;
myButton->textStyle.color = RGB(0, 0, 128); // 深蓝色文字
@@ -162,7 +181,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
}
```
5. **编译并运行!** 您将看到一个带有蓝色圆角按钮的窗口,点击它将会弹出消息框。
1. **编译并运行!** 您将看到一个带有蓝色圆角按钮的窗口,点击它将会弹出消息框。
## 📚 核心类型详解 (`CoreTypes.h`)
@@ -171,22 +190,28 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
### 枚举类型 (Enums)
| 枚举类型 | 描述 | 常用值 |
| :----------------- | :----------- | :----------------------------------------------------------- |
| :--------------------- | :----------- | :----------------------------------------------------------- |
| **`ControlShape`** | 控件几何形状 | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`等 |
| **`ButtonMode`** | 按钮行为模式 | `NORMAL`(普通), `TOGGLE`(切换), `DISABLED`(禁用) |
| **`TextBoxMode`** | 文本框模式 | `INPUT_MODE`(输入), `READONLY_MODE`(只读) |
| **`FillMode`** | 图形填充模式 | `SOLID`(实心), `NULL`(空心), `HATCHED`(图案)等 |
| **`FillStyle`** | 图案填充样式 | `HORIZONTAL`(水平线), `CROSS`(十字线)等 |
| **`LineStyle`** | 边框线型 | `SOLID`(实线), `DASH`(虚线), `DOT`(点线)等 |
| **`MessageBoxType`** | 消息框类型 | `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore` |
| **`MessageBoxResult`** | 消息框结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
### 结构体 (Structs)
| 结构体 | 描述 |
| :---------------- | :----------------------------------------------------------- |
| :----------------- | :----------------------------------------------------------- |
| **`ControlText`** | 封装了所有文本样式属性,包括字体、大小、颜色、粗体、斜体、下划线、删除线等。 |
| **`RouRectangle`** | 定义圆角矩形的圆角椭圆尺寸,包含宽度和高度属性。 |
**使用示例:**
```cpp
cpp
```c++
// 创建一个复杂的文本样式
StellarX::ControlText myStyle;
myStyle.nHeight = 25; // 字体高度
@@ -200,6 +225,8 @@ myLabel->textStyle = myStyle;
myButton->textStyle = myStyle;
```
## 🧩 控件库大全
### 1. 基础控件
@@ -220,21 +247,25 @@ myButton->textStyle = myStyle;
### 3. 高级控件
| 控件 | 头文件 | 描述 | 关键特性 |
| :-------- | :-------- | :------- | :----------------------------------------------------------- |
| :--------- | :--------- | :------- | :----------------------------------------------------------- |
| **Table** | `Table.h` | 数据表格 | **框架功能亮点**,支持分页显示、自定义表头和数据、自动计算列宽、翻页按钮。 |
| **Dialog** | `Dialog.h` | 对话框类 | 实现完整的对话框功能,支持多种按钮组合和异步结果回调。自动处理布局、背景保存恢复和生命周期管理。 |
**表格控件示例:**
```cpp
cpp
```c++
// 创建一个表格
auto myTable = std::make_unique<StellarX::Table>(50, 50);
auto myTable = std::make_unique<Table>(50, 50);
// 设置表头
myTable->setHeaders({ "ID", "姓名", "年龄", "职业" });
// 添加数据行
myTable->addDataRow({ "1", "张三", "25", "工程师" });
myTable->addDataRow({ "2", "李四", "30", "设计师" });
myTable->addDataRow({ "3", "王五", "28", "产品经理" });
myTable->setData({ "1", "张三", "25", "工程师" });
myTable->setData({ "2", "李四", "30", "设计师" });
myTable->setData({ "3", "王五", "28", "产品经理" });
// 设置每页显示2行
myTable->setRowsPerPage(2);
@@ -242,44 +273,110 @@ myTable->setRowsPerPage(2);
// 设置表格样式
myTable->textStyle.nHeight = 16;
myTable->setTableBorder(RGB(50, 50, 50));
myTable->setTableBackground(RGB(240, 240, 240));
myTable->setTableBk(RGB(240, 240, 240));
// 添加到窗口
mainWindow.addControl(std::move(myTable));
```
### 4. 静态工厂类
| 控件 | 头文件 | 描述 | 关键特性 |
| :------------- | :------------- | :--------------- | :----------------------------------------------------------- |
| **MessageBox** | `MessageBox.h` | 对话框的工厂调用 | 静态方法调用,无需实例化,自动处理模态和非模态的逻辑差异集成到窗口的对话框管理系统中提供去重机制防止重复对话框 |
**消息框使用示例:**
cpp
```c++
// 模态消息框(阻塞直到关闭)
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"确认要执行此操作吗?",
"确认",
StellarX::MessageBoxType::YesNo
);
if (result == StellarX::MessageBoxResult::Yes)
{
// 用户选择了"是"
}
// 非模态消息框(异步回调)
StellarX::MessageBox::ShowAsync(
mainWindow,
"操作已完成",
"提示",
StellarX::MessageBoxType::OK,
[](StellarX::MessageBoxResult result) {
// 异步处理结果
}
);
```
## 🔧 高级主题与最佳实践
### 1. 自定义控件
您可以通过继承`Control`基类来创建自定义控件。只需实现`draw()`和`handleEvent()`两个纯虚函数即可。
```cpp
class MyCustomControl : public StellarX::Control {
cpp
```c++
class MyCustomControl : public Control {
public:
MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
void draw() override {
saveStyle();
// 您的自定义绘制逻辑
setfillcolor(RGB(255, 100, 100));
fillrectangle(x, y, x + width, y + height);
restoreStyle();
}
void handleEvent(const ExMessage& msg) override {
bool handleEvent(const ExMessage& msg) override {
// 您的自定义事件处理逻辑
if (msg.message == WM_LBUTTONDOWN && isInControl(msg.x, msg.y)) {
if (msg.message == WM_LBUTTONDOWN &&
msg.x > x && msg.x < x + width &&
msg.y > y && msg.y < y + height) {
// 处理点击
return true; // 事件已消费
}
return false; // 事件未消费
}
bool IsVisible() const override { return true; }
bool model() const override { return false; }
};
```
### 2. 布局管理
当前版本星垣主要采用**绝对定位**。对于简单布局,您可以通过计算坐标来实现。对于复杂布局,可以考虑:
- 在`Canvas`中嵌套控件,实现相对定位。
- 自行实现简单的流式布局或网格布局管理器。
### 3. 性能优化
- **脏矩形渲染**: 框架内部已实现,控件状态改变时`dirty=true`,仅在需要时重绘。
- **图像资源**: 使用`IMAGE`对象加载图片后,可重复使用,避免多次加载。
- **减少循环内操作**: 在`draw()`和`handleEvent()`中避免进行重型计算。
- **事件消费机制**: 合理使用事件消费返回值,避免不必要的事件传播。
### 4. 对话框使用技巧
- **模态对话框**: 使用`ShowModal`方法,会阻塞当前线程直到对话框关闭,适合需要用户确认的重要操作。
- **非模态对话框**: 使用`ShowAsync`方法,不会阻塞主线程,适合提示性信息或后台任务。
- **对话框去重**: 框架内置了非模态对话框去重机制,防止同一消息的多个对话框同时出现。
## ⚠️ 重要限制与适用场景
@@ -292,6 +389,7 @@ public:
- **复杂的商业软件前端**: 缺乏高级控件(如树形图、富文本框、选项卡、高级列表等)和成熟的自动布局管理器。
**如果您需要开发上述类型的应用,请考虑使用以下成熟方案:**
- **Qt**: 功能极其强大,跨平台,适合大型商业应用。
- **wxWidgets**: 原生外观,跨平台。
- **Dear ImGui**: 即时模式GUI非常适合工具和调试界面。
@@ -302,10 +400,12 @@ public:
本项目采用 **MIT 许可证**。
您可以自由地:
- 使用、复制、修改、合并、出版发行、散布、再授权及销售本框架的副本。
- 将其用于私人或商业项目。
唯一要求是:
- 请在您的项目中保留原始的版权声明。
详见项目根目录的 [LICENSE](LICENSE) 文件。
@@ -326,7 +426,7 @@ public:
- 感谢 [EasyX Graphics Library](https://easyx.cn/) 为这个项目提供了简单易用的图形基础使得用C++教学GUI编程成为可能。
- 感谢所有追求**简洁、高效、清晰**编码理念的开发者,你们是"星垣"诞生的灵感源泉。
---
------
**星辰大海,代码为舟。**
@@ -335,9 +435,11 @@ public:
## 📞 支持与反馈
如果您在使用过程中遇到问题或有任何建议:
1. 查看 [示例代码](examples/) 获取使用参考
2. 查阅 [更新日志](CHANGELOG.md) 了解最新变化
3. 在GitHub仓库提交Issue反馈问题
---
------
*星垣框架 - 轻若尘埃,繁星为界*

View File

@@ -1,39 +1,55 @@
/**
* @file demo.cpp
* @brief 一个简单的演示程序,展示 StellarX GUI 框架的基本用法。
* @description 创建一个带有按钮的窗口,点击按钮会改变其文本。
* @description 创建一个带有按钮的窗口,点击按钮会弹出对话框
*/
#include <StellarX/StellarX.h>
#include <iostream>
// 只需包含这一个头文件即可使用所有功能
#include "StellarX.h"
int main()
// 程序入口点请使用WinMain以获得更好的兼容性
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
// 创建一个窗口 (Windows平台)
Window mainWindow(800, 600, NULL, RGB(240, 240, 240), "StellarX 演示程序");
// 1. 创建一个640x480的窗口背景为白色标题为"我的应用"
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
// 创建一个按钮
auto myButton = std::make_unique<Button>(300, 250, 200, 80, "点击我!", StellarX::ButtonMode::NORMAL, StellarX::ControlShape::ROUND_RECTANGLE);
// 2. 创建一个按钮 (使用智能指针管理)
auto myButton = std::make_unique<Button>(
250, 200, 140, 40, // x, y, 宽度, 高度
"点击我", // 按钮文本
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::ROUND_RECTANGLE
);
// 为按钮点击事件设置一个回调函数
myButton->setOnClickListener([&myButton]() {
std::cout << "按钮被点击了!" << std::endl;
// 点击后改变按钮文本作为视觉反馈
static bool = false;
if () {
myButton->setButtonText("点击我!");
// 3. 为按钮设置点击事件使用Lambda表达式
myButton->setOnClickListener([&mainWindow]() {
// 使用消息框工厂创建模态对话框
auto result = StellarX::MessageBox::ShowModal(
mainWindow,
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
"问候",
StellarX::MessageBoxType::OKCancel
);
// 处理对话框结果
if (result == StellarX::MessageBoxResult::OK) {
// 用户点击了确定按钮
}
else {
myButton->setButtonText("被点过了!");
}
= !;
});
// 将按钮添加到窗口
// 4. (可选)设置按钮样式
myButton->textStyle.nHeight = 20;
myButton->textStyle.color = RGB(0, 0, 128); // 深蓝色文字
myButton->setButtonBorder(RGB(0, 128, 255)); // 蓝色边框
// 5. 将按钮添加到窗口
mainWindow.addControl(std::move(myButton));
// 绘制窗口并运行事件循环 (Windows消息循环)
// 6. 绘制窗口
mainWindow.draw();
// 7. 进入消息循环,等待用户交互
mainWindow.runEventLoop();
return 0;

View File

@@ -1,13 +1,21 @@
/*******************************************************************************
* @文件: Button.h
* @摘要: 按钮控件
/*******************************************************************************
* @: Button
* @摘要: 多功能按钮控件,支持多种状态和样式
* @描述:
* 提供多种样式和行为的按钮控件,支持点击、悬停、切换等状态。
* 继承自Control基类是框架的核心交互组件之一
* 提供完整的按钮功能,包括普通点击、切换模式、禁用状态。
* 支持多种形状(矩形、圆形、椭圆等)和丰富的视觉样式
* 通过回调函数机制实现灵活的交互逻辑。
*
* @特性:
* - 支持三种工作模式:普通、切换、禁用
* - 八种几何形状,各有边框和无边框版本
* - 自定义颜色(默认、悬停、点击状态)
* - 多种填充模式(纯色、图案、图像)
* - 完整的鼠标事件处理(点击、悬停、移出)
*
* @使用场景: 作为主要交互控件,用于触发动作或表示状态
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
* @日期: September 2025
******************************************************************************/
#pragma once
#include "Control.h"
@@ -18,7 +26,6 @@ class Button : public Control
std::string text; // 按钮上的文字
bool click; // 是否被点击
bool hover; // 是否被悬停
bool dirty = true; // 是否重绘
COLORREF buttonTrueColor; // 按钮被点击后的颜色
COLORREF buttonFalseColor; // 按钮未被点击的颜色
@@ -28,7 +35,7 @@ class Button : public Control
StellarX::ButtonMode mode; // 按钮模式
StellarX::ControlShape shape; // 按钮形状
int buttonFillMode = BS_SOLID; //按钮填充模式
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
@@ -47,9 +54,6 @@ public:
StellarX::ControlText textStyle; // 按钮文字样式
public:
/*************************************************************************/
/********************************构造函数*********************************/
/*************************************************************************/
//默认按钮颜色
Button(int x, int y, int width, int height, const std::string text,
@@ -65,24 +69,13 @@ public:
//析构函数 释放图形指针内存
~Button();
/*************************************************************************/
/********************************Set方法**********************************/
/*************************************************************************/
//绘制按钮
void draw() override;
//按钮事件处理
void handleEvent(const ExMessage& msg) override;
bool handleEvent(const ExMessage& msg) override;
//设置回调函数
//************************************
// 名称: setOnClickListener | setOnToggleOnListener | setOnToggleOffListener
// 全名: Button::setOnClickListener
// 访问: public
// 返回类型: void
// Parameter: const std::function<> & & callback 设置回调函数 传入回调函数名即可,不需要传入(),不需要传入参数,不需要传入返回值
// 如果要传入参数可以使用lambda表达式
//************************************
void setOnClickListener(const std::function<void()>&& callback);
//设置TOGGLE模式下被点击的回调函数
void setOnToggleOnListener(const std::function<void()>&& callback);
@@ -91,17 +84,19 @@ public:
//设置按钮模式
void setbuttonMode(StellarX::ButtonMode mode);
//设置圆角矩形椭圆宽度
int setROUND_RECTANGLEwidth(int width);
void setROUND_RECTANGLEwidth(int width);
//设置圆角矩形椭圆高度
int setROUND_RECTANGLEheight(int height);
void setROUND_RECTANGLEheight(int height);
//设置按钮填充模式
void setFillMode(int mode);
void setFillMode(StellarX::FillMode mode);
//设置按钮填充图案
void setFillIma(StellarX::FillStyle ima);
//设置按钮填充图像
void setFillIma(std::string imaName);
//设置按钮边框颜色
void setButtonBorder(COLORREF Border);
//设置按钮未被点击颜色
void setButtonFalseColor(COLORREF color);
//设置按钮文本
void setButtonText(const char* text);
void setButtonText(std::string text);
@@ -111,10 +106,6 @@ public:
//判断按钮是否被点击
bool isClicked() const;
/*************************************************************************/
/********************************Get方法**********************************/
/*************************************************************************/
//获取按钮文字
std::string getButtonText() const;
const char* getButtonText_c() const;
@@ -123,7 +114,7 @@ public:
//获取按钮形状
StellarX::ControlShape getButtonShape() const;
//获取按钮填充模式
int getFillMode() const;
StellarX::FillMode getFillMode() const;
//获取按钮填充图案
StellarX::FillStyle getFillIma() const;
//获取按钮填充图像
@@ -134,6 +125,14 @@ public:
COLORREF getButtonTextColor() const;
//获取按钮文字样式
StellarX::ControlText getButtonTextStyle() const;
//获取按钮宽度
int getButtonWidth() const;
//获取按钮高度
int getButtonHeight() const;
//获取按钮横坐标
int getButtonX() const;
//获取按钮纵坐标
int getButtonY() const;
private:
//初始化按钮
@@ -142,5 +141,10 @@ private:
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
//判断鼠标是否在椭圆按钮内
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
//检查是否对话框是否可见
bool IsVisible() const override { return false; }
//获取对话框类型
bool model() const override { return false; }
};

View File

@@ -1,15 +1,27 @@
#pragma once
/*******************************************************************************
* @类: Canvas
* @摘要: 画布容器控件,用于分组和管理子控件
* @描述:
* 作为其他控件的父容器,提供统一的背景和边框样式。
* 负责将事件传递给子控件并管理它们的绘制顺序。
*
* @特性:
* - 支持四种矩形形状(普通、圆角,各有边框和无边框版本)
* - 可自定义填充模式、边框颜色和背景颜色
* - 自动管理子控件的生命周期和事件传递
* - 支持嵌套容器结构
*
* @使用场景: 用于分组相关控件、实现复杂布局或作为对话框基础
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
// 画布容器控件,可以作为其他控件的父容器
// 功能:
// - 包含和管理子控件
// - 将事件传递给子控件
// - 提供统一的背景和边框
// 使用场景: 用于分组相关控件或实现复杂布局
class Canvas : public Control
{
private:
protected:
std::vector<std::unique_ptr<Control>> controls;
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
@@ -21,12 +33,20 @@ private:
COLORREF canvasBorderClor = RGB(0, 0, 0);//边框颜色
COLORREF canvasBkClor = RGB(255,255,255); //背景颜色
void clearAllControls(); // 清除所有子控件
//检查是否对话框是否可见
bool IsVisible() const override { return false; }
//获取对话框类型
bool model() const override { return false; }
public:
Canvas();
Canvas(int x, int y, int width, int height);
~Canvas() {}
//绘制容器及其子控件
void draw() override;
void handleEvent(const ExMessage& msg) override;
bool handleEvent(const ExMessage& msg) override;
//添加控件
void addControl(std::unique_ptr<Control> control);

View File

@@ -1,19 +1,28 @@
#pragma once
/*********************************************************************
* \文件: Control.h
* \描述 控件基类,所有控件都继承自此类。
* 提供控件的一些基本属性和方法。
/*******************************************************************************
* @类: Control
* @摘要: 所有控件的抽象基类,定义通用接口和基础功能
* @描述:
* 提供控件的基本属性和方法,包括位置、尺寸、重绘标记等
* 实现绘图状态保存和恢复机制,确保控件绘制不影响全局状态。
*
* \作者: 我在人间做废物
* \日期: September 2025
*********************************************************************/
* @特性:
* - 定义控件基本属性(坐标、尺寸、脏标记)
* - 提供绘图状态管理saveStyle/restoreStyle
* - 声明纯虚接口draw、handleEvent等
* - 支持移动语义,禁止拷贝语义
*
* @使用场景: 作为所有具体控件类的基类,不直接实例化
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include <vector>
#include <memory>
#include <easyx.h>
#undef MessageBox
#include <iostream>
#include <string>
#include <functional>
#include <initializer_list>
#include "CoreTypes.h"
class Control
@@ -21,14 +30,15 @@ class Control
protected:
int x, y; // 左上角坐标
int width, height; // 控件尺寸
bool dirty = true; // 是否重绘
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
LOGFONT currentFont; // 保存当前字体样式和颜色
COLORREF currentColor = 0;
COLORREF currentBkColor = 0; // 保存当前填充色
COLORREF currentBorderColor = 0; // 边框颜色
LINESTYLE* currentLineStyle = new LINESTYLE; // 保存当前线型
LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色
COLORREF* currentColor = new COLORREF();
COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
public:
Control(const Control&) = delete;
@@ -42,10 +52,21 @@ public:
}
virtual ~Control() {
delete currentFont;
delete currentColor;
delete currentBkColor;
delete currentBorderColor;
delete currentLineStyle;
currentLineStyle = nullptr;
}
currentFont = nullptr;
currentColor = nullptr;
currentBkColor = nullptr;
currentBorderColor = nullptr;
currentLineStyle = nullptr;
}
protected:
// 获取位置和尺寸
int getX() const { return x; }
int getY() const { return y; }
@@ -53,9 +74,15 @@ public:
int getHeight() const { return height; }
int getRight() const { return x + width; }
int getBottom() const { return y + height; }
public:
//设置是否重绘
void setDirty(bool dirty) { this->dirty = dirty; }
virtual void draw() = 0;
virtual void handleEvent(const ExMessage& msg) = 0;
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
//用来检查非模态对话框是否可见,其他控件不用实现
virtual bool IsVisible() const = 0;
//用来检查对话框是否模态,其他控件不用实现
virtual bool model()const = 0;
protected:
void saveStyle();

View File

@@ -1,7 +1,6 @@
#pragma once
/*******************************************************************************
/*******************************************************************************
* @文件: CoreTypes.h
* @摘要: 星垣框架核心类型定义文件
* @摘要: 星垣(StellarX)框架核心类型定义文件
* @描述:
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型。
@@ -9,11 +8,27 @@
* @作者: 我在人间做废物
* @日期: September 2025
******************************************************************************/
#include"easyX.h"
#pragma once
#include "easyx.h"
/**
* @命名空间: StellarX
*
* @详细说明:
* 集中定义框架中使用的所有枚举类型和结构体,确保类型一致性。
* 这是框架的类型系统基础,所有控件都依赖于此文件中定义的类型
*
* @使用示例:
* StellarX::FillStyle::Horizontal - 填充样式
*
* @备注:
* 不用单独包含此头文件已在StellarX.h中包含包含唯一对外头文件即可
*/
namespace StellarX
{
/**
* @枚举类名称: hatchStyle
* @枚举类名称: FillStyle
* @功能描述: 用来定义控件填充图案的枚举类
*
* @详细说明:
@@ -31,14 +46,13 @@ namespace StellarX
* DiagCross - 网格
*
* @使用示例:
* // 示例代码展示如何使用此枚举类
* hatchStyle var = hatchStyle::Horizontal;
* FillStyle var = FillStyle::Horizontal;
*
* @备注:
* 此枚举类仅支持图案填充模式
* 枚举类在使用时,需要使用::进行调用,还要注意大小写
*/
enum class FillStyle {
enum class FillStyle
{
Horizontal = HS_HORIZONTAL, // 水平线
Vertical = HS_VERTICAL, // 垂直线
FDiagonal = HS_FDIAGONAL, // 反斜线
@@ -46,6 +60,7 @@ namespace StellarX
Cross = HS_CROSS, // 十字
DiagCross = HS_DIAGCROSS // 网格
};
/**
* @枚举类名称: FillMode
* @功能描述: 用来定义控件填充模式的枚举类
@@ -64,11 +79,7 @@ namespace StellarX
* DibPattern - 自定义图像填充
*
* @使用示例:
* // 示例代码展示如何使用此枚举类
* FillMode var = FillMode::Solid;
*
* @备注:
* 枚举类在使用时,需要使用::进行调用,还要注意大小写
*/
enum class FillMode
{
@@ -78,8 +89,9 @@ namespace StellarX
Pattern = BS_PATTERN, // 自定义图案填充
DibPattern = BS_DIBPATTERN // 自定义图像填充
};
/**
* @枚举类名称: linStyle
* @枚举类名称: LineStyle
* @功能描述: 此枚举类用来定义控件边框线型
*
* @详细说明:
@@ -97,13 +109,10 @@ namespace StellarX
* Null // 无线
*
* @使用示例:
* // 示例代码展示如何使用此枚举类
* LineStyle var = LineStyle::Solid;
*
* @备注:
* 枚举类在使用时,需要使用::进行调用,还要注意大小写
*/
enum class LineStyle {
enum class LineStyle
{
Solid = PS_SOLID, // 实线
Dash = PS_DASH, // 虚线
Dot = PS_DOT, // 点线
@@ -130,15 +139,12 @@ namespace StellarX
* bool bItalic = false; - 是否斜体
* bool bUnderline = false; - 是否下划线
* bool bStrikeOut = false; - 是否删除线
* bool operator!=(const ControlText& text);
* ControlText& operator=(const ControlText& text
*/
struct ControlText
{
int nHeight = 0; //- 字体高度
int nWidth = 0; //- 字体宽度 如果为0则自适应
LPCTSTR lpszFace = "宋体"; //- 字体名称
LPCTSTR lpszFace = "微软雅黑"; //- 字体名称
COLORREF color = RGB(0, 0, 0); //- 字体颜色
int nEscapement = 0; //- 字符串旋转角度
int nOrientation = 0; //- 字符旋转角度
@@ -149,7 +155,6 @@ namespace StellarX
bool operator!=(const ControlText& text);
ControlText& operator=(const ControlText& text);
};
/**
@@ -163,18 +168,15 @@ namespace StellarX
* @取值说明:
* RECTANGLE = 1, //有边框矩形
* B_RECTANGLE, //无边框矩形
* ROUND_RECTANGLE, //有边框圆角矩形
* B_ROUND_RECTANGLE, //无边框圆角矩形
* CIRCLE, //有边框圆形
* B_CIRCLE, //无边框圆形
* ELLIPSE, //有边框椭圆
* B_ELLIPSE //无边框椭圆
*
* @使用示例:
* ControlShape shape = ELLIPSE;
* ControlShape shape = ControlShape::ELLIPSE;
*
* @备注:
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
@@ -183,16 +185,14 @@ namespace StellarX
{
RECTANGLE = 1, //有边框矩形
B_RECTANGLE, //无边框矩形
ROUND_RECTANGLE, //有边框圆角矩形
B_ROUND_RECTANGLE, //无边框圆角矩形
CIRCLE, //有边框圆形
B_CIRCLE, //无边框圆形
ELLIPSE, //有边框椭圆
B_ELLIPSE //无边框椭圆
};
/**
* @枚举类名称: TextBoxmode
* @功能描述: 定义了文本框的两种模式
@@ -203,13 +203,6 @@ namespace StellarX
* @取值说明:
* INPUT_MODE, // 用户可输入模式
* READONLY_MODE // 只读模式
*
* @使用示例:
* // 示例代码展示如何使用此枚举类
* StellarX::TextBoxmode var = EnumClassName::VALUE1;
*
* @备注:
* 枚举类的特性、与普通枚举的区别
*/
enum class TextBoxmode
{
@@ -219,7 +212,7 @@ namespace StellarX
/**
* @枚举名称: ButtonMode
* @功能描述: brief
* @功能描述: 定义按钮的工作模式
*
* @详细说明:
* 根据按钮的工作模式,按钮可以有不同的行为。
@@ -231,18 +224,52 @@ namespace StellarX
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
*
* @使用示例:
* Button b1(100, 100, 120, 120, "测试按钮", RGB(128, 0, 0), RGB(255, 9, 9));
*
* ButtonMode mode = ButtonMode::NORMAL;
*/
enum class ButtonMode
{
NORMAL = 1, //普通模式,点击后触发回调,但不会保持状态。
TOGGLE, //切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
DISABLED //禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
NORMAL = 1, //普通模式
TOGGLE, //切换模式
DISABLED //禁用模式
};
/**
* @结构体名称: RouRectangle
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
*
* @详细说明:
* 需要修改控件圆角矩形样式时的圆角椭圆。
*
* @成员说明:
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
*/
struct RouRectangle
{
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
};
};
// 消息框类型
enum class MessageBoxType
{
OK, // 只有确定按钮
OKCancel, // 确定和取消按钮
YesNo, // 是和否按钮
YesNoCancel, // 是、否和取消按钮
RetryCancel, // 重试和取消按钮
AbortRetryIgnore, // 中止、重试和忽略按钮
};
// 消息框返回值
enum class MessageBoxResult
{
OK = 1, // 确定按钮
Cancel = 2, // 取消按钮
Yes = 6, // 是按钮
No = 7, // 否按钮
Abort = 3, // 中止按钮
Retry = 4, // 重试按钮
Ignore = 5 // 忽略按钮
};
}

139
include/StellarX/Dialog.h Normal file
View File

@@ -0,0 +1,139 @@
/*******************************************************************************
* @类: Dialog
* @摘要: 模态和非模态对话框控件,提供丰富的消息框功能
* @描述:
* 实现完整的对话框功能,支持多种按钮组合和异步结果回调。
* 自动处理布局、背景保存恢复和生命周期管理。
*
* @特性:
* - 支持六种标准消息框类型OK、YesNo、YesNoCancel等
* - 模态和非模态两种工作模式
* - 自动文本换行和尺寸计算
* - 背景保存和恢复机制
* - 工厂模式下的去重检测
*
* @使用场景: 用户提示、确认操作、输入请求等交互场景
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include"StellarX.h"
#define closeButtonWidth 20 //关闭按钮宽度
#define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度
#define functionButtonWidth 70 //按钮宽度
#define functionButtonHeight 30 //按钮高度
#define buttonMargin 10 //按钮间距
#define buttonAreaHeight 50 //按钮区域高度
#define titleToTextMargin 10 //标题到文本的距离
#define textToBorderMargin 10 //文本到边框的距离
class Dialog : public Canvas
{
Window& hWnd; //窗口引用
int textWidth = 0; //文本宽度
int textHeight = 0; //文本高度
int buttonNum = 0; // 按钮数量
int BorderWidth = 2; //边框宽度
IMAGE* saveBkImage = nullptr; // 用于保存背景图像
int saveBkX = 0, saveBkY = 0; // 保存背景的位置
int saveBkWidth = 0, saveBkHeight = 0; // 保存背景的尺寸
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
std::string titleText = "提示"; //标题文本
std::unique_ptr<Label> title = nullptr; //标题标签
std::string message; //提示信息
std::vector<std::string> lines; //消息内容按行分割
bool needsInitialization = true; //是否需要初始化
bool close = false; //是否关闭
bool modal = true; //是否模态
bool isVisible = false; //是否可见
COLORREF backgroundColor = RGB(240, 240, 240); //背景颜色
COLORREF borderColor = RGB(100, 100, 100); //边框颜色
COLORREF buttonTrueColor = RGB(211, 190, 190); //按钮被点击颜色
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
Button* closeButton = nullptr; //关闭按钮
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
public:
bool shouldClose = false; //是否应该关闭
bool isCleaning = false; //是否正在清理
bool pendingCleanup = false; //延迟清理
StellarX::ControlText textStyle; // 字体样式
// 清理方法声明
void performDelayedCleanup();
//工厂模式下调用非模态对话框时用来返回结果
std::function<void(StellarX::MessageBoxResult)> resultCallback;
//设置非模态获取结果的回调函数
void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb);
//获取对话框消息,用以去重
std::string GetCaption() const;
public:
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
~Dialog();
//绘制对话框
void draw() override;
//事件处理
bool handleEvent(const ExMessage& msg) override;
// 设置标题
void SetTitle(const std::string& title);
// 设置消息内容
void SetMessage(const std::string& message);
// 设置对话框类型
void SetType(StellarX::MessageBoxType type);
// 设置模态属性
void SetModal(bool modal);
// 设置对话框结果
void SetResult(StellarX::MessageBoxResult result);
// 获取对话框结果
StellarX::MessageBoxResult GetResult() const;
// 获取模态属性
bool getModal() const;
// 检查是否可见
bool IsVisible() const override;
//获取对话框类型
bool model() const override;
// 显示对话框
void Show();
// 关闭对话框
void Close();
private:
// 初始化按钮
void initButtons();
// 初始化关闭按钮
void initCloseButton();
// 初始化标题
void initTitle();
// 按行分割消息内容
void splitMessageLines();
// 获取文本大小
void getTextSize();
//初始化对话框尺寸
void initDialogSize();
// 初始化对话框
void initializeDialog();
// 清除所有控件
void clearControls();
//创建对话框按钮
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
};

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* @类: MessageBox
* @摘要: 消息框工厂类,提供简化的对话框创建接口
* @描述:
* 封装对话框的创建和显示逻辑,提供静态方法供快速调用。
* 支持模态阻塞和非模态回调两种使用方式。
*
* @特性:
* - 静态方法调用,无需实例化
* - 自动处理模态和非模态的逻辑差异
* - 集成到窗口的对话框管理系统中
* - 提供去重机制防止重复对话框
*
* @使用场景: 快速创建标准消息框,减少样板代码
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include <string>
#include <functional>
#include "CoreTypes.h"
#include "Dialog.h"
#include "Window.h"
namespace StellarX
{
class MessageBox
{
public:
// 模态:阻塞直到关闭,返回结果
static MessageBoxResult ShowModal(Window& wnd,
const std::string& text,
const std::string& caption = "提示",
MessageBoxType type = MessageBoxType::OK);
// 非模态:立即返回,通过回调异步获取结果
static void ShowAsync(Window& wnd,
const std::string& text,
const std::string& caption = "提示",
MessageBoxType type = MessageBoxType::OK,
std::function<void(MessageBoxResult)> onResult = nullptr);
};
} // namespace StellarX

View File

@@ -1,7 +1,7 @@
/*******************************************************************************
* @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v1.0.0
* @版本: v2.0.0
* @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库提供简洁易用的API和丰富的控件。
@@ -10,8 +10,8 @@
* 内部包含顺序经过精心设计,确保所有依赖关系正确解析。
*
* @作者: 我在人间做废物
* @邮箱: [3150131407@qq.com][]
* @仓库: https://github.com/Ysm-04/StellarX
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
* @仓库: [https://github.com/Ysm-04/StellarX]
*
* @许可证: MIT License
* @版权: Copyright (c) 2025 我在人间做废物
@@ -23,6 +23,8 @@
* 1. CoreTypes.h - 基础类型定义
* 2. Control.h - 控件基类
* 3. 其他具体控件头文件
* 4. Dialog继承自 CanvasDialog 为可包含子控件的对话框容器)
* 5.MessageBox对话框工厂提供便捷的模态/非模态调用方式
******************************************************************************/
#pragma once
@@ -35,3 +37,5 @@
#include"TextBox.h"
#include"Canvas.h"
#include"Table.h"
#include"Dialog.h"
#include"MessageBox.h"

View File

@@ -1,10 +1,23 @@
#pragma once
/*******************************************************************************
* @类: Label
* @摘要: 简单文本标签控件,用于显示静态文本
* @描述:
* 提供基本的文本显示功能,支持透明背景和自定义样式。
* 不支持用户交互,专注于文本呈现。
*
* @特性:
* - 支持背景透明/不透明模式
* - 完整的文本样式控制(字体、颜色、效果)
* - 自动适应文本内容
* - 轻量级无事件处理开销
*
* @使用场景: 显示说明文字、标题、状态信息等静态内容
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
// 标签控件,用于显示文本
// 特点:
// - 支持背景透明/不透明模式
// - 不支持用户交互(无事件处理)
// - 自动适应文本内容大小
class Label : public Control
{
@@ -13,14 +26,16 @@ class Label : public Control
COLORREF textBkColor; //标签背景颜色
bool textBkDisap = false; //标签背景是否透明
//标签事件处理(标签无事件)不实现具体代码
void handleEvent(const ExMessage& msg) override {}
bool handleEvent(const ExMessage& msg) override { return false; }
public:
StellarX::ControlText textStyle; //标签文本样式
public:
Label();
Label(int x, int y, std::string text = "标签",COLORREF textcolor = BLACK, COLORREF bkColor= RGB(255,255,255));
//绘标签
void draw() override;
//设置标签背景是否透明
void setTextdisap(bool key);
@@ -31,5 +46,12 @@ public:
//设置标签文本
void setText(std::string text);
private:
//检查是否对话框是否可见
bool IsVisible() const override { return false; }
//获取对话框类型
bool model() const override { return false; }
};

View File

@@ -1,19 +1,20 @@
/*******************************************************************************
* @文件: Table.h
* @摘要: 高级表格控件,支持分页显示
* @: Table
* @摘要: 高级表格控件,支持分页显示和大数据量展示
* @描述:
* 提供完整的数据表格功能,包括表头、数据行、分页导航按钮
* 自动计算列宽行高,支持自定义样式
* 提供完整的数据表格功能,包括表头、数据行、分页导航
* 自动计算列宽行高,支持自定义样式和交互。
*
* @实现机制:
* 1. 使用二维向量存储数据
* 2. 通过分页算法计算显示范围
* 3. 使用内部按钮和标签实现分页UI
* 4. 通过背景缓存优化渲染性能
* @特性:
* - 自动分页和页码计算
* - 可配置的每页行数
* - 自定义边框样式和填充模式
* - 翻页按钮和页码显示
* - 背景缓存优化渲染性能
*
* @使用场景: 数据展示、报表生成、记录浏览等表格需求
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
* @日期: September 2025
******************************************************************************/
#pragma once
@@ -40,7 +41,6 @@ private:
int totalPages = 1; // 总页数
bool isShowPageButton = true; // 是否显示翻页按钮
bool dirty = true; // 是否需要重绘
bool isNeedDrawHeaders = true; // 是否需要绘制表头
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
@@ -67,6 +67,11 @@ private:
void drawHeader(); //绘制表头
void drawPageNum(); //绘制页码信息
void drawButton(); //绘制翻页按钮
//检查是否对话框是否可见
bool IsVisible() const override { return false; }
//获取对话框类型
bool model() const override { return false; }
public:
StellarX::ControlText textStyle; // 文本样式
@@ -77,15 +82,13 @@ public:
// 绘制表格
void draw() override;
//事件处理
void handleEvent(const ExMessage& msg) override;
//************************** 设置属性 *****************************/
bool handleEvent(const ExMessage& msg) override;
//设置表头
void setHeaders(std::initializer_list<std::string> headers);
//设置表格数据
void setData(const std::vector<std::string>& data);
void setData(const std::initializer_list<std::vector<std::string>>& data);
void setData(std::vector<std::string> data);
void setData(std::initializer_list<std::vector<std::string>> data);
//设置每页显示的行数
void setRowsPerPage(int rows);
//设置是否显示翻页按钮

View File

@@ -1,34 +1,49 @@
#pragma once
/*******************************************************************************
* @类: TextBox
* @摘要: 文本框控件,支持输入和只读两种模式
* @描述:
* 提供文本输入和显示功能集成EasyX的InputBox用于数据输入。
* 支持有限的形状样式和视觉定制。
*
* @特性:
* - 两种工作模式:输入模式和只读模式
* - 最大字符长度限制
* - 集成系统输入框简化文本输入
* - 支持四种矩形形状变体
*
* @使用场景: 数据输入、文本显示、表单字段等
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#include "Control.h"
// 文本框控件,支持输入和只读两种模式
// 特殊说明:
// - 在INPUT_MODE下点击会调用EasyX的InputBox
// - 在READONLY_MODE下点击会显示信息对话框
// - 最大字符长度受maxCharLen限制
class TextBox : public Control
{
std::string text; //文本
StellarX::TextBoxmode mode; //模式
StellarX::ControlShape shape; //形状
bool dirty = true; //是否重绘
bool click = false; //是否点击
int maxCharLen = 10;//最大字符长度
size_t maxCharLen = 10;//最大字符长度
COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色
COLORREF textBoxBorderClor = RGB(0,0,0); //边框颜色
//检查是否对话框是否可见
bool IsVisible() const override { return false; }
//获取对话框类型
bool model() const override { return false; }
public:
StellarX::ControlText textStyle; //文本样式
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
void draw() override;
void handleEvent(const ExMessage& msg) override;
bool handleEvent(const ExMessage& msg) override;
//设置模式
void setMode(StellarX::TextBoxmode mode);
//设置可输入最大字符长度
void setMaxCharLen(int len);
void setMaxCharLen(size_t len);
//设置形状
void setTextBoxshape(StellarX::ControlShape shape);
//设置边框颜色

View File

@@ -1,21 +1,23 @@
#pragma once
#include "Control.h"
//窗口模式
//#define EX_DBLCLKS 1 - 在绘图窗口中支持鼠标双击事件。
//#define EX_NOCLOSE 2 - 禁用绘图窗口的关闭按钮。
//#define EX_NOMINIMIZE 3 - 禁用绘图窗口的最小化按钮。
//#define EX_SHOWCONSOLE 4 - 显示控制台窗口。
#pragma once
#include"Control.h"
/*******************************************************************************
* @类: Window
* @摘要: 应用程序主窗口类
* @摘要: 应用程序主窗口类,管理窗口生命周期和消息循环
* @描述:
* 负责创建和管理应用程序主窗口,所有控件的根容器。
* 处理消息循环、事件分发和窗口生命周期管理
* 创建和管理应用程序主窗口,作为所有控件的根容器。
* 处理消息分发、事件循环和渲染调度
*
* @重要说明:
* - 使用 initgraph() 创建窗口
* - 使用 BeginBatchDraw()/EndBatchDraw() 实现双缓冲
* - 使用 getmessage() 处理消息循环
* @特性:
* - 多种窗口模式配置(双缓冲、控制台等)
* - 背景图片和颜色支持
* - 集成的对话框管理系统
* - 完整的消息处理循环
* - 控件和对话框的生命周期管理
*
* @使用场景: 应用程序主窗口GUI程序的入口和核心
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
class Window
{
@@ -27,8 +29,12 @@ class Window
COLORREF wBkcolor = BLACK; //窗口背景
IMAGE* background = nullptr; //窗口背景图片
std::vector<std::unique_ptr<Control>> controls; //控件管理
std::vector<std::unique_ptr<Control>> dialogs; //对话框管理
public:
bool dialogClose = false; //是否有对话框关闭
Window(int width, int height, int mode);
Window(int width, int height, int mode, COLORREF bkcloc);
Window(int width, int height, int mode , COLORREF bkcloc, std::string headline = "窗口");
@@ -47,6 +53,25 @@ public:
void setHeadline(std::string headline);
//添加控件
void addControl(std::unique_ptr<Control> control);
//添加对话框
void addDialog(std::unique_ptr<Control> dialogs);
//检查是否已有对话框显示用于去重,防止工厂模式调用非模态对话框,多次打开污染对话框背景快照
bool hasNonModalDialogWithCaption(const std::string& caption) const;
//获取窗口句柄
HWND getHwnd() const;
//获取窗口宽度
int getWidth() const;
//获取窗口高度
int getHeight() const;
//获取窗口标题
std::string getHeadline() const;
//获取窗口背景颜色
COLORREF getBkcolor() const;
//获取窗口背景图片
IMAGE* getBkImage() const;
//获取控件管理
std::vector<std::unique_ptr<Control>>& getControls();
};

View File

@@ -1,4 +1,4 @@
#include "StellarX/Button.h"
#include "Button.h"
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
: Control(x, y, width, height)
@@ -52,7 +52,6 @@ void Button::draw()
}
else
{
// 确保点击状态的颜色正确显示
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
if (click)
setfillcolor(buttonTrueColor);
@@ -77,7 +76,7 @@ void Button::draw()
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
}
//设置按钮填充模式
setfillstyle(buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
//获取字符串像素高度和宽度
if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height)
@@ -132,10 +131,11 @@ void Button::draw()
}
// 处理鼠标事件,检测点击和悬停状态
// 根据按钮模式和形状进行不同的处理
void Button::handleEvent(const ExMessage& msg)
bool Button::handleEvent(const ExMessage& msg)
{
bool oldHover = hover;
bool oldClick = click;
bool consume = false;//是否消耗事件
// 检测悬停状态(根据不同形状)
switch (shape)
@@ -163,13 +163,15 @@ void Button::handleEvent(const ExMessage& msg)
{
click = true;
dirty = true;
consume = true;
}
else if (mode == StellarX::ButtonMode::TOGGLE)
{
// TOGGLE模式在鼠标释放时处理
}
}
// 处理鼠标释放事件
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
{
if (mode == StellarX::ButtonMode::NORMAL && click)
@@ -177,7 +179,8 @@ void Button::handleEvent(const ExMessage& msg)
if (onClickCallback) onClickCallback();
click = false;
dirty = true;
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
consume = true;
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY);
}
else if (mode == StellarX::ButtonMode::TOGGLE)
@@ -186,11 +189,13 @@ void Button::handleEvent(const ExMessage& msg)
if (click && onToggleOnCallback) onToggleOnCallback();
else if (!click && onToggleOffCallback) onToggleOffCallback();
dirty = true;
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
consume = true;
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY);
}
}
// 处理鼠标移出区域的情况
else if (msg.message == WM_MOUSEMOVE)
{
if (!hover && mode == StellarX::ButtonMode::NORMAL && click)
@@ -215,6 +220,7 @@ void Button::handleEvent(const ExMessage& msg)
{
draw();
}
return consume;
}
void Button::setOnClickListener(const std::function<void()>&& callback)
@@ -237,14 +243,17 @@ void Button::setbuttonMode(StellarX::ButtonMode mode)
this->mode = mode;
}
int Button::setROUND_RECTANGLEwidth(int width)
void Button::setROUND_RECTANGLEwidth(int width)
{
return rouRectangleSize.ROUND_RECTANGLEwidth = width;
rouRectangleSize.ROUND_RECTANGLEwidth = width;
this->dirty = true; // 标记需要重绘
}
int Button::setROUND_RECTANGLEheight(int height)
void Button::setROUND_RECTANGLEheight(int height)
{
return rouRectangleSize.ROUND_RECTANGLEheight = height;
rouRectangleSize.ROUND_RECTANGLEheight = height;
this->dirty = true; // 标记需要重绘
}
bool Button::isClicked() const
@@ -252,26 +261,41 @@ bool Button::isClicked() const
return this->click;
}
void Button::setFillMode(int mode)
void Button::setFillMode(StellarX::FillMode mode)
{
buttonFillMode = mode;
this->dirty = true; // 标记需要重绘
}
void Button::setFillIma(StellarX::FillStyle ima)
{
buttonFillIma = ima;
this->dirty = true;
}
void Button::setFillIma(std::string imaNAme)
{
if (buttonFileIMAGE)
{
delete buttonFileIMAGE;
buttonFileIMAGE = nullptr;
}
buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(),width-x,height-y);
loadimage(buttonFileIMAGE, imaNAme.c_str(),width,height);
this->dirty = true;
}
void Button::setButtonBorder(COLORREF Border)
{
buttonBorderColor = Border;
this->dirty = true;
}
void Button::setButtonFalseColor(COLORREF color)
{
this->buttonFalseColor = color;
this->dirty = true;
}
void Button::setButtonText(const char* text)
@@ -279,6 +303,7 @@ void Button::setButtonText(const char* text)
this->text = std::string(text);
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
this->text_height = textheight(LPCTSTR(this->text.c_str()));
this->dirty = true;
}
void Button::setButtonText(std::string text)
@@ -292,6 +317,7 @@ void Button::setButtonText(std::string text)
void Button::setButtonShape(StellarX::ControlShape shape)
{
this->shape = shape;
this->dirty = true;
}
@@ -315,7 +341,7 @@ StellarX::ControlShape Button::getButtonShape() const
return this->shape;
}
int Button::getFillMode() const
StellarX::FillMode Button::getFillMode() const
{
return this->buttonFillMode;
}
@@ -345,6 +371,26 @@ StellarX::ControlText Button::getButtonTextStyle() const
return this->textStyle;
}
int Button::getButtonWidth() const
{
return this->width;
}
int Button::getButtonHeight() const
{
return this->height;
}
int Button::getButtonX() const
{
return this->x;
}
int Button::getButtonY() const
{
return this->y;
}
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
{

View File

@@ -1,10 +1,21 @@
#include "StellarX/Canvas.h"
#include "Canvas.h"
void Canvas::clearAllControls()
{
controls.clear();
}
Canvas::Canvas()
:Control(0,0,100,100)
{
}
Canvas::Canvas(int x, int y, int width, int height)
:Control(x, y, width, height) {}
void Canvas::draw()
{
if (!dirty)return;
saveStyle();
setlinecolor(canvasBorderClor);//设置线色
@@ -34,18 +45,21 @@ void Canvas::draw()
restoreStyle();
dirty = false; //标记画布不需要重绘
}
void Canvas::handleEvent(const ExMessage& msg)
bool Canvas::handleEvent(const ExMessage& msg)
{
for (auto& control : controls) {
control->handleEvent(msg);
}
for (auto& control : controls)
if (control->handleEvent(msg))
return true;//事件被消费短路传递立即返回true 否则返回false
return false;
}
void Canvas::addControl(std::unique_ptr<Control> control)
{
controls.push_back(std::move(control));
dirty = true;
}
void Canvas::setShape(StellarX::ControlShape shape)
@@ -57,12 +71,14 @@ void Canvas::setShape(StellarX::ControlShape shape)
case StellarX::ControlShape::ROUND_RECTANGLE:
case StellarX::ControlShape::B_ROUND_RECTANGLE:
this->shape = shape;
dirty = true;
break;
case StellarX::ControlShape::CIRCLE:
case StellarX::ControlShape::B_CIRCLE:
case StellarX::ControlShape::ELLIPSE:
case StellarX::ControlShape::B_ELLIPSE:
this->shape = StellarX::ControlShape::RECTANGLE;
dirty = true;
break;
}
}
@@ -70,26 +86,31 @@ void Canvas::setShape(StellarX::ControlShape shape)
void Canvas::setCanvasfillMode(StellarX::FillMode mode)
{
this->canvasFillMode = mode;
dirty = true;
}
void Canvas::setBorderColor(COLORREF color)
{
this->canvasBorderClor = color;
dirty = true;
}
void Canvas::setCanvasBkColor(COLORREF color)
{
this->canvasBkClor = color;
dirty = true;
}
void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
{
this->canvasLineStyle = style;
dirty = true;
}
void Canvas::setLinewidth(int width)
{
this->canvaslinewidth = width;
dirty = true;
}

View File

@@ -1,4 +1,5 @@
#include "StellarX/Control.h"
#include "Control.h"
#include<assert.h>
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
{
@@ -45,20 +46,21 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
// 在控件绘制前调用,确保不会影响全局绘图状态
void Control::saveStyle()
{
gettextstyle(&currentFont); // 获取当前字体样式
currentColor = gettextcolor(); // 获取当前字体颜色
currentBorderColor = getlinecolor(); //保存当前边框颜色
gettextstyle(currentFont); // 获取当前字体样式
*currentColor = gettextcolor(); // 获取当前字体颜色
*currentBorderColor = getlinecolor(); //保存当前边框颜色
getlinestyle(currentLineStyle); //保存当前线型
currentBkColor = getfillcolor(); //保存当前填充色
*currentBkColor = getfillcolor(); //保存当前填充色
}
// 恢复之前保存的绘图状态
// 在控件绘制完成后调用,恢复全局绘图状态
void Control::restoreStyle()
{
settextstyle(&currentFont); // 恢复默认字体样式
settextcolor(currentColor); // 恢复默认字体颜色
setfillcolor(currentBkColor);
settextstyle(currentFont); // 恢复默认字体样式
settextcolor(*currentColor); // 恢复默认字体颜色
setfillcolor(*currentBkColor);
setlinestyle(currentLineStyle);
setlinecolor(currentBorderColor);
setlinecolor(*currentBorderColor);
setfillstyle(BS_SOLID);//恢复填充
}

673
src/Dialog.cpp Normal file
View File

@@ -0,0 +1,673 @@
#include "Dialog.h"
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal)
: Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text)
{
initializeDialog();
}
Dialog::~Dialog()
{
}
void Dialog::draw()
{
if(!isVisible)
{
// 如果对话框不可见且需要清理,执行清理
if (pendingCleanup && !isCleaning)
{
performDelayedCleanup();
}
return;
}
// 如果需要初始化,则执行初始化
if (needsInitialization && isVisible)
{
initDialogSize();
needsInitialization = false;
}
if (dirty && isVisible)
{
// 保存当前绘图状态
saveStyle();
// 保存背景(仅在第一次绘制时)
if (saveBkImage == nullptr) {
saveBkX = x - BorderWidth;
saveBkY = y - BorderWidth;
saveBkWidth = width + 2 * BorderWidth;
saveBkHeight = height + 2 * BorderWidth;
saveBkImage = new IMAGE(saveBkWidth, saveBkHeight);
getimage(saveBkImage, saveBkX, saveBkY, saveBkWidth, saveBkHeight);
}
Canvas::setBorderColor(this->borderColor);
Canvas::setLinewidth(this->BorderWidth);
Canvas::setCanvasBkColor(this->backgroundColor);
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
//设置所有控件为脏状态
for(auto& c :this->controls)
c->setDirty(true);
Canvas::draw();
//绘制消息文本
settextcolor(textStyle.color);
//设置字体样式
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
for (auto line:lines)
{
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
outtextxy(tx, ty, LPCTSTR(line.c_str()));
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
}
// 恢复绘图状态
restoreStyle();
dirty = false;
}
}
bool Dialog::handleEvent(const ExMessage& msg)
{
bool consume = false;
if (!isVisible)
{
if (pendingCleanup && !isCleaning)
{
performDelayedCleanup();
}
return false;
}
// 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning)
return false;
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
if (modal && msg.message == WM_LBUTTONUP &&
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
{
std::cout << "\a" << std::endl;
// 模态对话框不允许点击外部区域
return true;
}
// 将事件传递给子控件处理
if (!consume)
consume = Canvas::handleEvent(msg);
// 每次事件处理后检查是否需要执行延迟清理
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
return consume;
}
void Dialog::SetTitle(const std::string& title)
{
this->titleText = title;
if (this->title)
{
this->title->setText(title);
}
dirty = true;
}
void Dialog::SetMessage(const std::string& message)
{
this->message = message;
splitMessageLines();
getTextSize();
dirty = true;
}
void Dialog::SetType(StellarX::MessageBoxType type)
{
this->type = type;
// 重新初始化按钮
initButtons();
dirty = true;
}
void Dialog::SetModal(bool modal)
{
this->modal = modal;
}
void Dialog::SetResult(StellarX::MessageBoxResult result)
{
this->result = result;
}
StellarX::MessageBoxResult Dialog::GetResult() const
{
return this->result;
}
bool Dialog::getModal() const
{
return modal;
}
bool Dialog::IsVisible() const
{
return isVisible;
}
bool Dialog::model() const
{
return modal;
}
void Dialog::Show()
{
if (pendingCleanup)
performDelayedCleanup();
isVisible = true;
dirty = true;
needsInitialization = true;
close = false;
shouldClose = false;
if (modal)
{
// 模态对话框需要阻塞当前线程直到对话框关闭
while (isVisible && !close)
{
// 处理消息
ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
{
handleEvent(msg);
// 检查是否需要关闭
if (shouldClose)
{
Close();
break;
}
}
// 重绘
if (dirty)
{
draw();
FlushBatchDraw();
}
// 避免CPU占用过高
Sleep(10);
}
// 模态对话框关闭后执行清理
if (pendingCleanup && !isCleaning)
{
performDelayedCleanup();
}
}
else
{
// 非模态对话框只需标记为可见,由主循环处理
dirty = true;
}
}
void Dialog::Close()
{
if (!isVisible) return;
isVisible = false;
close = true;
dirty = true;
pendingCleanup = true; // 只标记需要清理,不立即执行
auto& c = hWnd.getControls();
for(auto& control:c)
control->setDirty(true);
// 工厂模式下非模态触发回调 返回结果
if (resultCallback&& !modal)
resultCallback(this->result);
}
void Dialog::initButtons()
{
controls.clear();
switch (this->type)
{
case StellarX::MessageBoxType::OK: // 只有确定按钮
{
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定"
);
okbutton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::OK);
this->Close(); });
okbutton->textStyle = this->textStyle;
this->addControl(std::move(okbutton));
}
break;
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
{
auto okButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定"
);
okButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::OK);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
(okButton.get()->getButtonX() + okButton.get()->getButtonWidth() + buttonMargin),
okButton.get()->getButtonY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
okButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(okButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::YesNo: // 是和否按钮
{
auto yesButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
""
);
yesButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true;
this->Close(); });
auto noButton = createDialogButton(
(yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin),
yesButton.get()->getButtonY(),
""
);
noButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true;
this->Close(); });
yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle;
this->addControl(std::move(yesButton));
this->addControl(std::move(noButton));
}
break;
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
{
auto yesButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
""
);
yesButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true;
this->Close(); });
auto noButton = createDialogButton(
yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin,
yesButton.get()->getButtonY(),
""
);
noButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
noButton.get()->getButtonX() + noButton.get()->getButtonWidth() + buttonMargin,
noButton.get()->getButtonY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(yesButton));
this->addControl(std::move(noButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
{
auto retryButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"重试"
);
retryButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
retryButton.get()->getButtonY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
retryButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(retryButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
{
auto abortButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"中止"
);
abortButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Abort);
this->hWnd.dialogClose = true;
this->Close();
});
auto retryButton = createDialogButton(
abortButton.get()->getButtonX() + abortButton.get()->getButtonWidth() + buttonMargin,
abortButton.get()->getButtonY(),
"重试"
);
retryButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true;
this->Close();
});
auto ignoreButton = createDialogButton(
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
retryButton.get()->getButtonY(),
"忽略"
);
ignoreButton.get()->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Ignore);
this->hWnd.dialogClose = true;
this->Close();
});
abortButton->textStyle = this->textStyle;
retryButton->textStyle = this->textStyle;
ignoreButton->textStyle = this->textStyle;
this->addControl(std::move(abortButton));
this->addControl(std::move(retryButton));
this->addControl(std::move(ignoreButton));
}
break;
}
}
void Dialog::initCloseButton()
{
//初始化关闭按钮
auto but = std::make_unique<Button>
(
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
"×", // 按钮文本
RGB(255, 0, 0), // 按钮被点击颜色
this->canvasBkClor, // 按钮背景颜色
RGB(255, 0, 0), // 按钮被悬停颜色
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::B_RECTANGLE
);
but.get()->setButtonFalseColor(this->backgroundColor);
but->setOnClickListener([this]() {
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
this->closeButton = but.get();
this->addControl(std::move(but));
}
void Dialog::initTitle()
{
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color);
title->setTextdisap(true);
title->textStyle = this->textStyle;
this->addControl(std::move(title));
}
void Dialog::splitMessageLines()
{
lines.clear(); // 先清空现有的行
std::string currentLine;
for (size_t i = 0; i < message.length(); i++) {
// 处理 换行符 \r\n \n \r
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n'))
{
if (!currentLine.empty()) {
lines.push_back(currentLine);
currentLine.clear();
}
if (message[i] == '\r' && message[i + 1] == '\n')
i++;
continue;
}
currentLine += message[i];
}
// 添加最后一行(如果有内容)
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
// 如果消息为空,至少添加一个空行
if (lines.empty()) {
lines.push_back("");
}
}
void Dialog::getTextSize()
{
saveStyle();
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
for (auto text : lines)
{
int w = textwidth(LPCTSTR(text.c_str()));
int h = textheight(LPCTSTR(text.c_str()));
if (this->textHeight < h)
this->textHeight = h;
if (this->textWidth < w)
this->textWidth = w;
}
restoreStyle();
}
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
void Dialog::initDialogSize()
{
splitMessageLines(); // 分割消息行
getTextSize(); // 获取文本最大尺寸
// 获取功能按钮数量
switch (this->type)
{
case StellarX::MessageBoxType::OK: // 只有确定按钮
buttonNum = 1;
break;
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
case StellarX::MessageBoxType::YesNo: // 是和否按钮
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
buttonNum = 2;
break;
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
buttonNum = 3;
break;
}
// 计算按钮区域宽度
int buttonAreaWidth = buttonNum * functionButtonWidth +
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0);
// 计算文本区域宽度(包括边距)
int textAreaWidth = textWidth + textToBorderMargin * 2 ;
// 对话框宽度取两者中的较大值,并确保最小宽度
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
this->width = this->width > 200 ? this->width : 200;
// 计算对话框高度
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距
this->height = closeButtonHeight + // 标题栏高度
titleToTextMargin + // 标题到文本的间距
textAreaHeight + // 文本区域高度
buttonAreaHeight; // 按钮区域高度
// 居中定位对话框
this->x = (hWnd.getWidth() - this->width) / 2;
this->y = (hWnd.getHeight() - this->height) / 2;
//this->textStyle.nWidth = 10;
this->textStyle.nHeight = 20;
initButtons(); // 初始化按钮
initTitle(); // 初始化标题标签
initCloseButton(); // 初始化关闭按钮
}
void Dialog::initializeDialog()
{
needsInitialization = true;
pendingCleanup = false;
isCleaning = false;
close = false;
isVisible = false;
}
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
// 此方法在对话框不可见且被标记为待清理时由 draw() 或 handleEvent() 调用。
void Dialog::performDelayedCleanup()
{
if (isCleaning) return;
isCleaning = true;
// 清除所有控件
controls.clear();
// 重置指针
closeButton = nullptr;
title.reset();
// 释放背景图像资源
if (saveBkImage)
{
// 恢复背景
putimage(saveBkX, saveBkY, saveBkImage);
delete saveBkImage;
saveBkImage = nullptr;
}
// 重置状态
needsInitialization = true;
pendingCleanup = false;
isCleaning = false;
shouldClose = false;
}
void Dialog::SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb)
{
resultCallback = std::move(cb);
}
std::string Dialog::GetCaption() const
{
return titleText;
}
void Dialog::clearControls()
{
controls.clear();
// 重置按钮指针
closeButton = nullptr;
title.reset(); // 释放标题资源
}
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
{
auto btn = std::make_unique<Button>(
x, y, functionButtonWidth, functionButtonHeight,
text,
buttonTrueColor, // 点击色
buttonFalseColor, // 背景色
buttonHoverColor, // 悬停色
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::RECTANGLE
);
return btn;
}

32
src/MessageBox.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "MessageBox.h"
namespace StellarX
{
MessageBoxResult MessageBox::ShowModal(Window& wnd,const std::string& text,const std::string& caption,
MessageBoxType type)
{
Dialog dlg(wnd, caption, text, type, true); // 模态
dlg.Show();
return dlg.GetResult();
}
void MessageBox::ShowAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
std::function<void(MessageBoxResult)> onResult)
{
//去重,如果窗口内已有相同的对话框被触发,则不再创建
if (wnd.hasNonModalDialogWithCaption(caption))
{
std::cout << "\a" << std::endl;
return;
}
auto dlg = std::make_unique<Dialog>(wnd, caption, text, type, false); // 非模态
Dialog* dlgPtr = dlg.get();
// 设置回调
if (onResult)
dlgPtr->SetResultCallback(std::move(onResult));
// 交给 Window 管理生命周期
wnd.addDialog(std::move(dlg));
dlgPtr->Show();
}
}

View File

@@ -1,4 +1,4 @@
#include "StellarX/Label.h"
#include "Label.h"
Label::Label()
:Control(0, 0, 0, 0)
@@ -18,6 +18,8 @@ Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColo
void Label::draw()
{
if(dirty)
{
saveStyle();
if (textBkDisap)
setbkmode(TRANSPARENT); //设置背景透明
@@ -30,26 +32,32 @@ void Label::draw()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
outtextxy(x,y, LPCTSTR(text.c_str()));
outtextxy(x, y, LPCTSTR(text.c_str()));
restoreStyle();
dirty = false;
}
}
void Label::setTextdisap(bool key)
{
textBkDisap = key;
this->dirty = true;
}
void Label::setTextColor(COLORREF color)
{
textColor = color;
this->dirty = true;
}
void Label::setTextBkColor(COLORREF color)
{
textBkColor = color;
this->dirty = true;
}
void Label::setText(std::string text)
{
this->text = text;
this->dirty = true;
}

View File

@@ -1,4 +1,4 @@
#include "StellarX/Table.h"
#include "Table.h"
// 绘制表格的当前页
// 使用双循环绘制行和列,考虑分页偏移
void Table::drawTable()
@@ -7,9 +7,9 @@ void Table::drawTable()
dY = uY;
uY = dY + lineHeights.at(0) + 10;
for (int i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
for (size_t i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
{
for (int j = 0; j < data[i].size(); j++)
for (size_t j = 0; j < data[i].size(); j++)
{
uX = dX + colWidths.at(j) + 20;
fillrectangle(dX, dY, uX, uY);
@@ -28,7 +28,7 @@ void Table::drawHeader()
{
uY = dY + lineHeights.at(0) + 10;
for(int i = 0; i < headers.size(); i++)
for(size_t i = 0; i < headers.size(); i++)
{
uX = dX + colWidths.at(i) + 20;
fillrectangle(dX, dY, uX, uY);
@@ -38,9 +38,8 @@ void Table::drawHeader()
}
// 初始化文本宽度和高度计算
// 遍历所有数据和表头,计算每列的最大宽度和行高
// 此方法在数据变更时自动调用
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
// 为后续绘制表格单元格提供尺寸依据。此计算在数据变更时自动触发。
void Table::initTextWaH()
{
this->colWidths.resize(this->headers.size());
@@ -48,9 +47,9 @@ void Table::initTextWaH()
int width = 0;
int height = 0;
//计算数据尺寸
for (int i = 0; i < data.size(); i++)
for (size_t i = 0; i < data.size(); i++)
{
for (int j = 0; j < data[i].size(); j++)
for (size_t j = 0; j < data[i].size(); j++)
{
width = textwidth(LPCTSTR(data[i][j].c_str()));
height = textheight(LPCTSTR(data[i][j].c_str()));
@@ -61,7 +60,7 @@ void Table::initTextWaH()
}
}
for (int i = 0; i < this->headers.size(); i++)
for (size_t i = 0; i < this->headers.size(); i++)
{
width = textwidth(LPCTSTR(headers[i].c_str()));
height = textheight(LPCTSTR(headers[i].c_str()));
@@ -73,7 +72,7 @@ void Table::initTextWaH()
// 计算表格总宽度和高度
this->width = 0;
for (int i = 0; i < colWidths.size(); i++)
for (size_t i = 0; i < colWidths.size(); i++)
this->width += colWidths.at(i) + 20;
LINESTYLE currentStyle;
@@ -81,6 +80,9 @@ void Table::initTextWaH()
this->height = lineHeights.at(0) * (rowsPerPage + 1) + rowsPerPage * 10+20 ; // 表头+数据行+页码区域
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
// 避免重叠绘制造成的残影问题。
// 如果背景图像不存在或尺寸不匹配,创建或重新创建
if (saveBkImage == nullptr) {
saveBkImage = new IMAGE(width, height);
@@ -95,11 +97,15 @@ void Table::initButton()
{
int x1, x2;
int y1, y2;
x1 = pX - 70;
x1 = pX - 72;
x2 = pX + textwidth(LPCTSTR(pageNumtext.c_str())) + 10;
y1 = y2 = pY;
this->prevButton = new Button(x1, y1, 60, textheight(LPCTSTR(pageNumtext.c_str())), "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
this->nextButton = new Button(x2, y2, 60, textheight(LPCTSTR(pageNumtext.c_str())), "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
this->prevButton = new Button(x1, y1, 62, textheight(LPCTSTR(pageNumtext.c_str())), "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
this->nextButton = new Button(x2, y2, 62, textheight(LPCTSTR(pageNumtext.c_str())), "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
this->prevButton->textStyle = this->textStyle;
this->nextButton->textStyle = this->textStyle;
this->prevButton->setFillMode(tableFillMode);
this->nextButton->setFillMode(tableFillMode);
prevButton->setOnClickListener([this]()
{if (this->currentPage > 1)
{
@@ -120,23 +126,25 @@ void Table::initButton()
void Table::initPageNum()
{
if (0 == pY)
pY = uY + lineHeights.at(0) * rowsPerPage + rowsPerPage * 10+10;
for (int i = 0; i < colWidths.size(); i++)
pY = uY + lineHeights.at(0) * rowsPerPage + rowsPerPage * 10+20;
for (size_t i = 0; i < colWidths.size(); i++)
this->pX += colWidths.at(i) + 20;
this->pX -= textwidth(LPCTSTR(pageNumtext.c_str()));
this->pX /= 2;
this->pX += x;
this->pageNum = new Label(this->pX, pY, pageNumtext);
//pageNum->setTextdisap(true);
pageNum->textStyle = this->textStyle;
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true);
}
void Table::drawPageNum()
{
if (nullptr == pageNum)
initPageNum();
pageNumtext = std::to_string(currentPage);
pageNumtext += "页/第";
pageNumtext = "";
pageNumtext+= std::to_string(currentPage);
pageNumtext += "页/共";
pageNumtext += std::to_string(totalPages);
pageNumtext += "";
pageNum->setText(pageNumtext);
@@ -154,9 +162,8 @@ void Table::drawButton()
}
Table::Table(int x, int y)
:Control(x, y, 0,0)
:Control(x, y, 0, 0)
{
//this->saveBkImage = new IMAGE(this->width,this->height);
}
Table::~Table()
@@ -200,12 +207,15 @@ void Table::draw()
isNeedCellSize = false;
}
// 绘制表格之前捕获背景
// 只有在第一次绘制或者尺寸变化时才需要重新捕获背景
// 优化性能:仅在首次绘制表格尺寸变化时捕获背景图像。
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
// 避免重叠绘制造成的残影问题。
static bool firstDraw = true;
if (firstDraw || isNeedDrawHeaders) {
if (firstDraw || isNeedDrawHeaders)
{
// 确保在绘制任何表格内容之前捕获背景
if (saveBkImage) {
if (saveBkImage)
{
// 临时恢复样式,确保捕获正确的背景
restoreStyle();
if(tableBorderWidth>1)
@@ -229,7 +239,8 @@ void Table::draw()
}
// 恢复背景(清除旧内容)
if (saveBkImage) {
if (saveBkImage)
{
if (tableBorderWidth > 1)
putimage(this->x - tableBorderWidth, this->y - tableBorderWidth, saveBkImage);
else
@@ -260,15 +271,18 @@ void Table::draw()
}
}
void Table::handleEvent(const ExMessage& msg)
bool Table::handleEvent(const ExMessage& msg)
{
bool consume = false;
if(!this->isShowPageButton)
return;
return consume;
else
{
prevButton->handleEvent(msg);
nextButton->handleEvent(msg);
consume = prevButton->handleEvent(msg);
if (!consume)
consume = nextButton->handleEvent(msg);
}
return consume;
}
void Table::setHeaders(std::initializer_list<std::string> headers)
@@ -281,21 +295,32 @@ void Table::setHeaders(std::initializer_list<std::string> headers)
dirty = true;
}
void Table::setData(const std::vector<std::string>& data)
void Table::setData( std::vector<std::string> data)
{
if (data.size() < headers.size())
for (int i = 0; data.size() <= headers.size(); i++)
data.push_back("");
this->data.push_back(data);
totalPages = (this->data.size() + rowsPerPage - 1) / rowsPerPage;
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
dirty = true;
}
void Table::setData(const std::initializer_list<std::vector<std::string>>& data)
void Table::setData( std::initializer_list<std::vector<std::string>> data)
{
for (auto lis : data)
if (lis.size() < headers.size())
{
for (size_t i = lis.size(); i< headers.size(); i++)
lis.push_back("");
this->data.push_back(lis);
totalPages = (this->data.size() + rowsPerPage - 1) / rowsPerPage;
}
else
this->data.push_back(lis);
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
@@ -305,7 +330,7 @@ void Table::setData(const std::initializer_list<std::vector<std::string>>& data)
void Table::setRowsPerPage(int rows)
{
this->rowsPerPage = rows;
totalPages = (data.size() + rowsPerPage - 1) / rowsPerPage;
totalPages = ((int)data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
@@ -315,16 +340,19 @@ void Table::setRowsPerPage(int rows)
void Table::showPageButton(bool isShow)
{
this->isShowPageButton = isShow;
this->dirty = true;
}
void Table::setTableBorder(COLORREF color)
{
this->tableBorderClor = color;
this->dirty = true;
}
void Table::setTableBk(COLORREF color)
{
this->tableBkClor = color;
this->dirty = true;
}
void Table::setTableFillMode(StellarX::FillMode mode)
@@ -333,16 +361,30 @@ void Table::setTableFillMode(StellarX::FillMode mode)
this->tableFillMode = mode;
else
this->tableFillMode = StellarX::FillMode::Solid;
this->prevButton->textStyle = this->textStyle;
this->nextButton->textStyle = this->textStyle;
this->prevButton->setFillMode(tableFillMode);
this->nextButton->setFillMode(tableFillMode);
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true);
this->prevButton->setDirty(true);
this->nextButton->setDirty(true);
this->prevButton->setDirty(true);
this->nextButton->setDirty(true);
this->dirty = true;
}
void Table::setTableLineStyle(StellarX::LineStyle style)
{
this->tableLineStyle = style;
this->dirty = true;
}
void Table::setTableBorderWidth(int width)
{
this->tableBorderWidth = width;
this->dirty = true;
}
int Table::getCurrentPage() const
@@ -352,7 +394,7 @@ int Table::getCurrentPage() const
int Table::getTotalPages() const
{
return this->totalPages;;
return this->totalPages;
}
int Table::getRowsPerPage() const

View File

@@ -1,5 +1,5 @@
// TextBox.cpp
#include "StellarX/TextBox.h"
#include "TextBox.h"
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
@@ -48,16 +48,17 @@ void TextBox::draw()
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break;
}
}
restoreStyle();
dirty = false; //标记不需要重绘
}
}
void TextBox::handleEvent(const ExMessage& msg)
bool TextBox::handleEvent(const ExMessage& msg)
{
bool hover = false;
bool oldClick = click;
bool consume = false;
switch (shape)
{
@@ -66,17 +67,22 @@ void TextBox::handleEvent(const ExMessage& msg)
case StellarX::ControlShape::ROUND_RECTANGLE:
case StellarX::ControlShape::B_ROUND_RECTANGLE:
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
consume = false;
break;
}
if (hover && msg.message == WM_LBUTTONUP)
{
click = true;
if(StellarX::TextBoxmode::INPUT_MODE == mode)
dirty = InputBox(LPTSTR(text.c_str()), maxCharLen,"输入框",NULL,text.c_str(), NULL ,NULL,false);
{
dirty = InputBox(LPTSTR(text.c_str()), (int)maxCharLen, "输入框", NULL, text.c_str(), NULL, NULL, false);
consume = true;
}
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
{
dirty = false;
InputBox(NULL, maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
consume = true;
}
flushmessage(EX_MOUSE | EX_KEY);
}
@@ -85,17 +91,20 @@ void TextBox::handleEvent(const ExMessage& msg)
if (click)
click = false;
return consume;
}
void TextBox::setMode(StellarX::TextBoxmode mode)
{
this->mode = mode;
this->dirty = true;
}
void TextBox::setMaxCharLen(int len)
void TextBox::setMaxCharLen(size_t len)
{
if (len > 0)
maxCharLen = len;
this->dirty = true;
}
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
@@ -107,12 +116,14 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
case StellarX::ControlShape::ROUND_RECTANGLE:
case StellarX::ControlShape::B_ROUND_RECTANGLE:
this->shape = shape;
this->dirty = true;
break;
case StellarX::ControlShape::CIRCLE:
case StellarX::ControlShape::B_CIRCLE:
case StellarX::ControlShape::ELLIPSE:
case StellarX::ControlShape::B_ELLIPSE:
this->shape = StellarX::ControlShape::RECTANGLE;
this->dirty = true;
break;
}
}
@@ -120,11 +131,13 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
void TextBox::setTextBoxBorder(COLORREF color)
{
textBoxBorderClor = color;
this->dirty = true;
}
void TextBox::setTextBoxBk(COLORREF color)
{
textBoxBkClor = color;
this->dirty = true;
}
std::string TextBox::getText() const

View File

@@ -1,4 +1,5 @@
#include "StellarX/Window.h"
#include "Window.h"
#include"Dialog.h"
Window::Window(int width, int height, int mode)
{
@@ -59,24 +60,93 @@ void Window::draw(std::string pImgFile)
BeginBatchDraw(); // 开始批量绘制
for (auto& control : controls)
control->draw();
for(auto& d: dialogs)
d->draw();
EndBatchDraw(); // 结束批量绘制
}
// 运行主事件循环,处理用户输入和窗口消息
// 此方法会阻塞直到窗口关闭
// 主消息循环优先级:对话框 > 普通控件。
// 重绘策略:为保证视觉一致性,每次有对话框状态变化(打开/关闭)时,
// 会强制重绘所有控件。先绘制普通控件,再绘制对话框(确保对话框在最上层)。
void Window::runEventLoop()
{
ExMessage msg;
bool running = true;
while (running) {
msg = getmessage(EX_MOUSE | EX_KEY);
if (msg.message == WM_CLOSE) {
while (running)
{
bool consume = false;// 是否处理了消息
// 处理所有消息
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
{
if (msg.message == WM_CLOSE)
{
running = false;
continue;
break;
}
// 优先处理对话框事件
for (auto& d : dialogs)
{
if (d->IsVisible() && !d->model())
consume = d->handleEvent(msg);
if (consume)
break;
}
if(!consume)
for (auto& c : controls)
c->handleEvent(msg);
flushmessage(EX_MOUSE |EX_KEY |EX_CHAR|EX_WINDOW);
{
consume = c->handleEvent(msg);
if (consume)
break;
}
}
//如果有对话框打开或者关闭强制重绘
bool needredraw = false;
for (auto& d : dialogs)
{
needredraw = d->IsVisible();
if (needredraw)break;
}
if (needredraw || dialogClose)
{
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
// 以便它们能及时更新悬停状态hover否则悬停状态可能保持错误状态。
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE先分发给控件更新 hover 状态
POINT pt;
if (GetCursorPos(&pt))
{
ScreenToClient(this->hWnd, &pt);
ExMessage mm;
mm.message = WM_MOUSEMOVE;
mm.x =(short) pt.x;
mm.y =(short) pt.y;
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
for (auto& c : controls)
c->handleEvent(mm);
}
BeginBatchDraw();
// 先绘制普通控件
for (auto& c : controls)
c->draw();
// 然后绘制对话框(确保对话框在最上层)
for (auto& d : dialogs)
{
if (!d->model() && d->IsVisible())
d->setDirty(true);
d->draw();
}
EndBatchDraw();
needredraw = false;
}
// 避免CPU占用过高
Sleep(10);
}
}
@@ -108,6 +178,59 @@ void Window::addControl(std::unique_ptr<Control> control)
this->controls.push_back(std::move(control));
}
void Window::addDialog(std::unique_ptr<Control> dialogs)
{
this->dialogs.push_back(std::move(dialogs));
}
bool Window::hasNonModalDialogWithCaption(const std::string& caption) const {
for (const auto& dptr : dialogs) {
if (!dptr) continue;
// 只检查 Dialog 类型的控件
Dialog* d = dynamic_cast<Dialog*>(dptr.get());
//检查是否有非模态对话框可见,并且消息内容一致
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption)
return true;
}
return false;
}
HWND Window::getHwnd() const
{
return hWnd;
}
int Window::getWidth() const
{
return this->width;
}
int Window::getHeight() const
{
return this->height;
}
std::string Window::getHeadline() const
{
return this->headline;
}
COLORREF Window::getBkcolor() const
{
return this->wBkcolor;
}
IMAGE* Window::getBkImage() const
{
return this->background;
}
std::vector<std::unique_ptr<Control>>& Window::getControls()
{
return this->controls;
}