11 Commits

Author SHA1 Message Date
0c1cf2938f feat: add a new awesome feature 2025-12-20 17:29:03 +08:00
aa0fa8d320 feat: add a new awesome feature 2025-12-05 20:05:10 +08:00
43564ef675 feat: add a new awesome feature 2025-12-04 14:25:56 +08:00
53dc237e46 feat: add a new awesome feature 2025-11-30 20:09:16 +08:00
46febdb973 feat: add a new awesome feature 2025-11-30 19:05:58 +08:00
f05962954f feat: add a new awesome feature 2025-11-20 01:59:53 +08:00
58d4e8ab2f feat: add a new awesome feature 2025-11-19 15:14:39 +08:00
5420bfd644 feat: add a new awesome feature 2025-11-08 01:06:37 +08:00
cc08187ced feat: add a new awesome feature 2025-11-05 13:33:55 +08:00
c10e72b3fe feat: add a new awesome feature 2025-11-04 21:27:57 +08:00
6218ba54e3 Add GitHub Actions workflow to mirror to GitCode 2025-11-04 21:15:10 +08:00
37 changed files with 5412 additions and 5270 deletions

29
.github/workflows/mirror-to-gitcode.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Mirror to GitCode
on:
push:
branches: [ "**" ] # 任意分支推送触发
create:
tags: [ "*" ] # 新建 tag 触发
workflow_dispatch: # 支持手动触发
permissions:
contents: read
jobs:
mirror:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 必须拿全历史+所有 tag
- name: Push --mirror to GitCode
env:
GITCODE_USER: ${{ secrets.GITCODE_USER }}
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
run: |
set -e
git config --global user.name "mirror-bot"
git config --global user.email "mirror-bot@users.noreply.github.com"
# 如果你的命名空间不是个人用户,而是组织,请把 ${GITCODE_USER} 换成组织名
git remote add gitcode https://${GITCODE_USER}:${GITCODE_TOKEN}@gitcode.com/${GITCODE_USER}/StellarX.git
git push --prune --mirror gitcode

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[中文文档](CHANGELOG.md) [中文文档](CHANGELOG.md)
## [v2.3.2] - 2025 - 12 - 20
### ✨ Added
- **Table: runtime reset for headers and data:** added `Table::clearHeaders()`, `Table::clearData()`, and `Table::resetTable()`. This allows a single `Table` instance to dynamically update its headers/data at runtime, and triggers the required recalculation (cell sizing / pagination info) and redraw.
- **TextBox: password mode:** added `PASSWORD_MODE` to `TextBoxmode`. User input is stored internally, while the render layer displays masked characters (e.g., `*`). The real text can be retrieved via `TextBox::getText()`.
### ⚙️ Changed
- **TabControl: clarified default active page semantics:**
- Calling `TabControl::setActiveIndex()` **before the first draw** now only records the default active index; it no longer immediately triggers the tab button click callback.
- **After the first draw completes**, if a default active index was set, the active state is applied and the active page is drawn (if the index is out of range, the last page is activated by default).
- Calling `TabControl::setActiveIndex()` **during runtime (non-first draw)** switches the active page immediately when the index is valid; out-of-range indices are ignored.
### ✅ Fixed
- **TabControl::setActiveIndex crash when called before drawing:** fixed a null-pointer access caused by triggering the tab button click callback before initialization. The default activation is now applied after the first draw completes, preventing crashes and ensuring the active page is rendered on first draw.
- **TabControl rendering glitches when toggling visibility (hidden -> visible):** fixed multi-page overlap/ghosting caused by non-active pages being incorrectly drawn after `setIsVisible(false) -> setIsVisible(true)`. Now, when TabControl is visible, only the active page is visible/drawable; if there is no active page, nothing is drawn.
## [v2.3.1] - 2025-11-30
### 🙏 Acknowledgements
- Special thanks to user [@To-KongBai](https://github.com/To-KongBai) for providing stable reproduction steps and key phenomenon comparisons (container nested child control coordinate transformation issue), which helped us quickly identify and fix the control coordinate transformation problem in deeply nested containers. ([Issues#6](https://github.com/Ysm-04/StellarX/issues/6))
- In the upcoming website (currently undergoing ICP filing), we plan to add a contributors' wall to acknowledge users. We welcome everyone to report bugs or share interfaces created with StellarX, and we will carefully read and include acknowledgements.
- Sincere thanks to every user who reports bugs—your feedback makes StellarX more stable and robust.
### ⚙️ Changes
- **Dialog Background Snapshot Mechanism:** `Dialog` no longer captures and destroys its own snapshot. The methods for capturing and restoring snapshots have been **removed** from `Dialog`, and the base class `Canvas` now handles all snapshot management. The `draw` method in `Dialog` no longer deals with snapshots.
- **Timing Adjustment for Window and Control Redrawing on Size Change:** In the main event loop, when the window size changes, control sizes are processed first. Old snapshots are discarded, followed by the redrawing of the window with the new size, and finally, the controls are redrawn.
### ✅ Fixes
- **Child Control Coordinate Transformation in Nested Containers:** `Canvas` overrides the base class's `setX/Y` methods to synchronize the global coordinates of child controls when the parent container's global coordinates change. This prevents child controls in nested containers from incorrectly treating the container's relative coordinates as global coordinates.
- **Solid Background Window Title Not Applied:** In the `Window`'s `draw()` method, the window title is forcibly set to ensure that the title passed when creating the window is applied correctly, preventing the issue where the window title doesn't take effect.
- **Tab Control Background Residue when Changing Coordinates:** In the overridden `setX/Y` methods of `TabControl`, all tabs and their child controls are forced to discard snapshots when the tab's coordinates change. This prevents background residue caused by incorrect snapshot restoration order after modifying coordinates.
- **Table Page Number Label and Pagination Button Misalignment when Changing Coordinates:** In `Table`'s `setX/Y`, the `isNeedButtonAndPageNum` state is reset to `true`, ensuring that the pagination button and page number label are recalculated and remain centered directly beneath the table when redrawn.
## [v2.3.0] - 2025-11-18
### ✨ Added
- Introduced `LayoutMode` adaptive layout mode and `Anchor` anchor points. Controls can now call `setLayoutMode` to set layout mode and `steAnchor` to set anchor points, enabling controls to adapt to window changes during resizing. Dialog controls only recalculate their position during window resizing to maintain center alignment.
- Added `adaptiveLayout()` API to `Window`, called by the main event loop to recalculate control positions and sizes based on anchor points during window resizing, enabling dual-anchored controls (left-right or top-bottom) to automatically stretch with window changes.
### ⚙️ Changed
- **Optimized Window Resizing Mechanism**: Refactored `WndProcThunk`, `runEventLoop`, and `pumpResizeIfNeeded` to uniformly record window size changes and perform one-time repainting at the end of the event loop, preventing jitter and sequencing issues caused by repeated drawing during resizing.
- **Added Dialog Size Scheduling Interface**: Introduced `Window::scheduleResizeFromModal()`, used in conjunction with `pumpResizeIfNeeded()`. During modal dialog display, the parent window can update client area size in real time and relayout child controls during unified finalization, while the dialog maintains its original size.
- **Redraw Sequence Optimization**: Replaced `InvalidateRect` with `ValidateRect` during the finalization phase after window size changes, preventing the system from sending additional `WM_PAINT` messages that could cause reentrant drawing.
- **Other Improvements**: Fixed delayed background snapshot updates for table pagination buttons and page number labels; improved dialog background capture logic.
### ✅ Fixed
- **Modal Dialog Resizing Fix**: Resolved the issue where window resizing during modal dialog display prevented underlying controls from updating their sizes and positions according to anchor points; simultaneously eliminated ghosting artifacts caused by repeated dialog redrawing.
- **Drawing Sequence Disorder Fix**: Addressed sporadic drawing sequence disorders, control ghosting, and border flickering during window resizing, ensuring controls are drawn in the order they were added.
- **Stability Fixes**: Corrected abnormal frames caused by sudden window size changes in certain scenarios; resolved delayed background snapshot updates for tables and dialogs under edge conditions.
## [v2.2.2] - 2025 - 11- 08
### ⚙️ Changes
- Modified the coordinate transfer method for the Canvas container. Child control coordinates are now passed as relative coordinates (with the origin at the top-left corner of the container, obtainable via the `getX`/`getY` interface), instead of the original global coordinates. Child control coordinates can now be set to negative values.
- The example under `examples\register-viewer` has been updated to the latest version, aligning container child controls to use relative coordinates.
### ✅ Fixes
- Fixed jittering/bouncing and flickering when resizing the window (left/top edges):
- `WM_SIZING` only clamps the minimum size; `WM_GETMINMAXINFO` sets the window-level minimum tracking size.
- Freezes redrawing during dragging and handles resizing uniformly upon release; `WM_SIZE` only records the new size without interfering with drawing.
- Disabled `WM_ERASEBKGND` background erasing and removed `CS_HREDRAW`/`CS_VREDRAW` to reduce flickering.
- Fixed issues related to dialog boxes:
- Resolved occasional residual functional buttons after closing a dialog.
- Fixed issues where window resizing failed to redraw or displayed a corrupted background when a modal dialog was active.
- Fixed delayed updates of background snapshots for the table control's pagination buttons and page number labels during window changes.
## [v2.2.1] - 2025-11-04 ## [v2.2.1] - 2025-11-04
==This release is a hotfix for v2.2.0== ==This release is a hotfix for v2.2.0==

View File

@@ -7,6 +7,88 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
[English document](CHANGELOG.en.md) [English document](CHANGELOG.en.md)
## [v2.3.2] - 2025 - 12 - 20
### ✨ 新增
- **Table 支持运行期重置表头与数据:**新增 `Table::clearHeaders()``Table::clearData()``Table::resetTable()`,允许同一 `Table` 在运行过程中动态切换表头与数据,并触发必要的单元格尺寸/分页信息重算与重绘。
- **TextBox 新增密码模式:**`TextBoxmode` 新增 `PASSWORD_MODE`;输入内容内部保存,绘制层面使用掩码字符(如 `*`)替代显示,真实文本可通过 `TextBox::getText()` 获取。
### ⚙️ 变更
- **TabControl 默认激活页语义明确化:**
- 首次绘制前调用 `TabControl::setActiveIndex()`:仅记录默认激活索引,不再立即触发页签按钮点击回调;
- 首次绘制完成后:若设置了默认激活索引则应用激活状态并绘制激活页(索引越界时默认激活最后一个页);
- 程序运行过程中调用 `TabControl::setActiveIndex()`:索引合法则立即切换激活页并绘制;索引越界则不做处理。
### ✅ 修复
- **TabControl::setActiveIndex 绘制前调用导致程序中断:**修复绘制前设置默认激活索引时触发空指针访问的问题;现在默认激活逻辑延后到首次绘制完成后再生效,避免崩溃并保证首次绘制即可绘制激活页。
- **TabControl 由不可见设置为可见时绘制错乱:**修复 `setIsVisible(false) -> setIsVisible(true)` 后非激活页被错误绘制导致的多页重叠/残影;现在 TabControl 可见时仅激活页可见/可绘制,无激活页则不绘制任何页。
## [v2.3.1] - 2025 - 11 - 30
### 🙏 鸣谢
- 感谢用户 [@To-KongBai](https://github.com/To-KongBai) 提供稳定复现步骤与关键现象对比(容器嵌套孙控件坐标转换问题),帮助我们快速确认多容器嵌套时的控件坐标转换问题并修复。([Issues#6](https://github.com/Ysm-04/StellarX/issues/6)
- 在即将上线的官网中ICP备案中我们计划加入一个贡献者鸣谢墙欢迎各位用户反馈BUG或者分享自己用星垣做的界面我们将认真阅读并收录鸣谢
- 真诚的感谢每一位反馈BUG的用户你们的反馈将使星垣更加稳定和健壮
### ✨ 新增
新增一个登录界面Demo在主仓库**examples/**目录下
### ⚙️ 变更
- **Dialog背景快照机制**`Dialog`不在自己抓取和销毁快照,**删除**重载的抓取和恢复快照的方法,完全交由基类`Canvas`处理,`Dialog``draw`方法中不在处理快照
- **窗口变化重绘时控件和窗口重绘的时机调整:**主事件循环中窗口大小发生变化时先处理控件尺寸,并回贴和释放旧快照,然后再重绘新尺寸窗口,最后绘制控件
### ✅ 修复
- **容器嵌套时子控件坐标转化:**`Canvas`重写了基类的`setX/Y`方法,在容器全局坐标发生变化时同步修改子控件的全局坐标,防止在容器嵌套时,容器的相对坐标被子控件当成全局坐标处理
- **纯色背景窗口标题不生效:**在`Window``draw()`方法中强制设置窗口标题,以防止,创建窗口时传递的窗口标题不生效
- **选项卡控件页签打开时动态改变坐标背景残留:**在`TabControl`重写的`setX/Y`方法中,当选项卡的坐标发生变化时强制让所有页签以及页和子控件丢一次快照,防止,在修改坐标后因,快照恢复顺序引起的选项卡激活页残留
- **Table动态改变坐标页码标签和翻页按钮错乱**在`Table`控件的`setX/Y`中重置`isNeedButtonAndPageNum`状态为真,在绘制时重新计算翻页按钮和页码标签的位置以保持在表格正下方居中位置显示
## [v2.3.0] - 2025 - 11 - 18
### ✨ 新增
- 新增`LayoutMode `自适应布局模式和`Anchor`锚点,控件中可以调用 `setLayoutMode`
设置布局模式以及`steAnchor`设置锚点,达到窗口拉伸时控件自适应窗口变化。对话框控件在窗口变化时只会重新计算位置,来保证居中显示
- `Window`新增`adaptiveLayout()`这个API由主事件循环调用在窗口拉伸时根据锚点重新计算控件位置和尺寸使左右/上下双锚定控件随窗口变化自动伸缩。
### ⚙️ 变更
- **优化窗口尺寸调整机制**:重构 `WndProcThunk``runEventLoop``pumpResizeIfNeeded`,统一记录窗口尺寸变化并在事件循环尾部一次性重绘,避免拉伸过程中重复绘制引发抖动与顺序错乱。
- **新增对话框尺寸调度接口**:引入 `Window::scheduleResizeFromModal()`,配合 `pumpResizeIfNeeded()` 使用。模态对话框显示期间,父窗口可实时更新客户区尺寸并在统一收口时重新布局子控件,对话框自身尺寸保持不变。
- **重绘顺序优化**:在窗口尺寸变化后的收口阶段使用 `ValidateRect` 代替 `InvalidateRect`,避免系统再次发送 `WM_PAINT` 导致重入绘制。
- **其他改进**:修复表格翻页按钮与页码标签等元素背景快照更新不及时的问题;改进对话框背景捕捉逻辑。
### ✅ 修复
- **模态对话框拉伸修复**:解决了模态对话框弹出时,窗口拉伸无法让底层控件按照锚点更新尺寸和位置的问题;同时避免对话框反复重绘导致残影。
- **绘制顺序错乱修复**:解决窗口拉伸时偶发的绘制顺序紊乱、控件残影和边框闪烁问题,确保控件按添加顺序依次绘制。
- **稳定性修复**:修正某些情况下窗口尺寸突变导致的异常帧;解决表格和对话框背景快照在边界条件下未及时更新的问题。
## [v2.2.2] - 2025 - 11- 08
### ⚙️ 变更
- Canvas容器坐标传递方式改变子控件坐标由原来的传递全局坐标改为传递相对坐标坐标原点为容器的左上角坐标可通过getX/Y接口获得可以设置子控件坐标为负值
- examples\register-viewer下的案例已同步修改为最新同步容器子控件为相对坐标
### ✅ 修复
- 修复窗口拉伸(左/上边)时的抖动/弹回与闪烁
- `WM_SIZING` 仅做最小尺寸夹紧;`WM_GETMINMAXINFO` 设置窗口级最小轨迹
- 拖拽期冻结重绘,松手统一收口;`WM_SIZE` 只记录尺寸不抢绘制
- 禁用 `WM_ERASEBKGND` 擦背景并移除 `CS_HREDRAW/CS_VREDRAW`,减少闪烁
- 对话框的相关问题
- 对话框关闭后概率出现功能按钮残留
- 模态对话框触发时,窗口拉伸无法重绘或背景错乱
- 表格控件在窗口变化时其翻页按钮和页码标签背景快照更新不及时的问题
## [v2.2.1] - 2025 - 11 - 4 ## [v2.2.1] - 2025 - 11 - 4
==此版本为v2.2.0的修复版本== ==此版本为v2.2.0的修复版本==

View File

@@ -7,8 +7,8 @@
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total) ![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX) [![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg) ![Version](https://img.shields.io/badge/Version-2.3.2-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg) ![Download](https://img.shields.io/badge/Download-2.3.2_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white) ![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows) ![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -25,24 +25,6 @@ This is a **teaching-grade and tooling-grade** framework that helps developers u
------ ------
## **🆕 v2.2.1 (Hotfix for v2.2.0)**
- Addressed a flickering issue that occurred when using the Canvas and TabControl containers.
- Fixed issues where border remnants and functional buttons could persist after closing a Dialog.
For details, please refer to the [CHANGELOG.en](CHANGELOG.en.md).
## Whats new in v2.2.0
- **New TabControl for multi-page tabbed UIs:** With `TabControl`, its easy to create a tabbed layout. Tabs can be arranged on the top, bottom, left, or right, and clicking switches the displayed page. Suitable for settings panels and multi-view switching.
- **Enhanced control show/hide and resize responsiveness:** All controls now share a unified interface (`setIsVisible`) to toggle visibility. When a container control is hidden, its child controls automatically hide/show with it. Meanwhile, we introduce `onWindowResize` for controls to respond to window size changes so elements update in sync after resizing, eliminating artifacts or misalignment.
- **Refined text-style mechanism:** The Label control now uses a unified `ControlText` style structure. Developers can easily customize font, color, size, etc. (replacing older interfaces, and more flexible). Button Tooltips also support richer customization and different texts for toggle states.
- **Other improvements:** Dialog management gains de-duplication to prevent identical prompts from popping up repeatedly. Several bug fixes and refresh optimizations further improve stability.
See `CHANGELOG.md / CHANGELOG.en.md` for the full list.
------
## 📦 Project Structure & Design Philosophy ## 📦 Project Structure & Design Philosophy
StellarX adopts classic **OOP** and **modular** design with a clear structure: StellarX adopts classic **OOP** and **modular** design with a clear structure:
@@ -181,6 +163,11 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
| `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` | | `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` | | `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` |
| Enum | Description | Common values |
| ------------ | ---------------------- | -------------------------------------------- |
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
| `Anchor` | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
### Structs ### Structs
| Struct | Description | | Struct | Description |

View File

@@ -2,13 +2,20 @@
[English document](README.en.md) [English document](README.en.md)
官网地址https://stellarx-gui.top
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
[![Mirror on GitCode](https://img.shields.io/badge/Mirror-GitCode-blue)](https://gitcode.com/Ysm-04/StellarX)
------ ------
![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total) ![GitHub all releases](https://img.shields.io/github/downloads/Ysm-04/StellarX/total)
[![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX) [![Star GitHub Repo](https://img.shields.io/github/stars/Ysm-04/StellarX.svg?style=social&label=Star%20This%20Repo)](https://github.com/Ysm-04/StellarX)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg) ![Version](https://img.shields.io/badge/Version-2.3.2-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg) ![Download](https://img.shields.io/badge/Download-2.3.2_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white) ![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows) ![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -25,24 +32,26 @@
--- ---
## 🆕v2.2.1v2.2.0修复版)
- 解决了使用Canvas和TabControl容器时出现频闪问题
- 修复了Dialog对话框关闭时概率出边边框残留和功能按钮残留问题
详情参考[更新日志](CHANGELOG.md)
## 🆕V2.3.2——重要更新
## V2.2.0 有何变化 ### 新增
- **新增 TabControl 控件,实现多页面选项卡界面:** 通过 `TabControl` 可以轻松创建选项卡式布局,支持页签在上下左右排列、点击切换显示不同内容页面。适用于设置面板、多视图切换等场景 - **Table 支持运行期重置表头与数据:**新增 `Table::clearHeaders()``Table::clearData()``Table::resetTable()`,允许同一 `Table` 在运行过程中动态切换表头与数据,并触发必要的单元格尺寸/分页信息重算与重绘
- **控件显隐与布局响应能力增强:** 现在所有控件都可以使用统一接口动态隐藏或显示(`setIsVisible`),容器控件隐藏时其内部子控件会自动随之隐藏/显示。与此同时,引入控件对窗口尺寸变化的响应机制(`onWindowResize`),窗口拉伸后界面各元素可协调更新,杜绝拉伸过程中出现残影或错位 - **TextBox 新增密码模式:**`TextBoxmode` 新增 `PASSWORD_MODE`;输入内容内部保存,绘制层面使用掩码字符(如 `*`)替代显示,真实文本可通过 `TextBox::getText()` 获取
- **文本样式机制完善:** Label 控件改用统一的文本样式结构 `ControlText`,开发者可方便地设置字体、颜色、大小等属性来定制 Label 的外观替代旧接口更加灵活。Button 的 Tooltip 提示也支持更丰富的定制和针对切换状态的不同提示文本。
- **其他改进:** 框架底层的对话框管理增加了防重复弹出相同提示的机制,修复了一些细节 Bug 并优化了刷新效率,进一步提升了稳定性。
详见 `CHANGELOG.md / CHANGELOG.en.md` 获取完整更新列表。 ### ⚙️ 变更
- **TabControl 默认激活页语义明确化:**
- 首次绘制前调用 `TabControl::setActiveIndex()`:仅记录默认激活索引,不再立即触发页签按钮点击回调;
- 首次绘制完成后:若设置了默认激活索引则应用激活状态并绘制激活页(索引越界时默认激活最后一个页);
- 程序运行过程中调用 `TabControl::setActiveIndex()`:索引合法则立即切换激活页并绘制;索引越界则不做处理。
### ✅ 修复
- **TabControl::setActiveIndex 绘制前调用导致程序中断:**修复绘制前设置默认激活索引时触发空指针访问的问题;现在默认激活逻辑延后到首次绘制完成后再生效,避免崩溃并保证首次绘制即可绘制激活页。
- **TabControl 由不可见设置为可见时绘制错乱:**修复 `setIsVisible(false) -> setIsVisible(true)` 后非激活页被错误绘制导致的多页重叠/残影;现在 TabControl 可见时仅激活页可见/可绘制,无激活页则不绘制任何页。
--- ---
@@ -184,6 +193,13 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` | | `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` | | `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
| 枚举 | 描述 | 常用值 |
| ------------ | ---------------------- | -------------------------------------------- |
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
| Anchor | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
### 结构体 ### 结构体
| 结构体 | 描述 | | 结构体 | 描述 |

View File

@@ -24,7 +24,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
// 3. 为按钮设置点击事件使用Lambda表达式 // 3. 为按钮设置点击事件使用Lambda表达式
myButton->setOnClickListener([&mainWindow]() { myButton->setOnClickListener([&mainWindow]() {
// 使用消息框工厂创建模态对话框 // 使用消息框工厂创建模态对话框
auto result = StellarX::MessageBox::ShowModal( auto result = StellarX::MessageBox::showModal(
mainWindow, mainWindow,
"欢迎使用星垣GUI\r\n作者:我在人间做废物", "欢迎使用星垣GUI\r\n作者:我在人间做废物",
"问候", "问候",

View File

@@ -7,12 +7,12 @@
auto blackColor = RGB(202, 255, 255); auto blackColor = RGB(202, 255, 255);
char initData[33] = "00000000000000000000000000000000";//初始数据 char initData[33] = "00000000000000000000000000000000";//初始数据
bool gSigned = false; //是否为有符号数 bool gSigned = false; //是否为有符号数
void main() int main()
{ {
Window mainWindow(700,500,NULL,RGB(255,255,255), "寄存器查看工具 V1.0——我在人间做废物 (同类工具定制3150131407(Q / V))"); Window mainWindow(700, 510, NULL, RGB(255, 255, 255), "寄存器查看工具 V1.0——我在人间做废物 (同类工具定制3150131407(Q / V))");
//选择区控件 //选择区控件
auto selectionAreaLabel = std::make_unique<Label>(18, 0,"32位选择区"); auto selectionAreaLabel = std::make_unique<Label>(18, 0, "32位选择区");
selectionAreaLabel->setTextdisap(true); selectionAreaLabel->setTextdisap(true);
std::vector<std::unique_ptr<Label>>selectionAreaButtonLabel; std::vector<std::unique_ptr<Label>>selectionAreaButtonLabel;
std::vector<std::unique_ptr<Button>>selectionAreaButton; std::vector<std::unique_ptr<Button>>selectionAreaButton;
@@ -21,22 +21,22 @@ void main()
selectionArea->setCanvasBkColor(blackColor); selectionArea->setCanvasBkColor(blackColor);
selectionArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); selectionArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
for (int y = 0; y < 2; y ++) for (int y = 0; y < 2; y++)
{ {
std::ostringstream os; std::ostringstream os;
for (int x = 0; x <16; x++) for (int x = 0; x < 16; x++)
{ {
if (0 == y) if (0 == y)
{ {
selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 40 + 28 * (x / 4), 26, "", RGB(208, 208, 208))); selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 25 + 28 * (x / 4), 26, "", RGB(208, 208, 208)));
os << std::setw(2) << std::setfill('0') << 31 - x; os << std::setw(2) << std::setfill('0') << 31 - x;
selectionAreaButtonLabel.back()->setText(os.str()); selectionAreaButtonLabel.back()->setText(os.str());
selectionAreaButtonLabel.back()->setTextdisap(true); selectionAreaButtonLabel.back()->setTextdisap(true);
selectionAreaButton.push_back( selectionAreaButton.push_back(
std::make_unique<Button>(x * 35 + 42 + 28 * (x / 4), 58,20,32,"0", std::make_unique<Button>(x * 35 + 27 + 28 * (x / 4), 58, 25, 30, "0",
blackColor,RGB(171, 196, 220),StellarX::ButtonMode::TOGGLE)); blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE));
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152); selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE); selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get()); selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
@@ -55,19 +55,19 @@ void main()
} }
else else
{ {
selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 40 + 28 * (x / 4), 90, "", RGB(208, 208, 208))); selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 25 + 28 * (x / 4), 90, "", RGB(208, 208, 208)));
os << std::setw(2) << std::setfill('0') << 15-x; os << std::setw(2) << std::setfill('0') << 15 - x;
selectionAreaButtonLabel.back()->setText(os.str()); selectionAreaButtonLabel.back()->setText(os.str());
selectionAreaButtonLabel.back()->setTextdisap(true); selectionAreaButtonLabel.back()->setTextdisap(true);
selectionAreaButton.push_back( selectionAreaButton.push_back(
std::make_unique<Button>(x * 35 + 42 + 28 * (x / 4), 120, 20, 32, "0", std::make_unique<Button>(x * 35 + 27 + 28 * (x / 4), 120, 25, 30, "0",
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE)); blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE));
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152); selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE); selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get()); selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
int k =15 - x; int k = 15 - x;
selectionAreaButton.back()->setOnToggleOnListener([k,btn = selectionAreaButton_ptr.back()]() selectionAreaButton.back()->setOnToggleOnListener([k, btn = selectionAreaButton_ptr.back()]()
{ {
btn->setButtonText("1"); btn->setButtonText("1");
initData[k] = '1'; initData[k] = '1';
@@ -78,8 +78,8 @@ void main()
initData[k] = '0'; initData[k] = '0';
}); });
} }
os.str(""); os.str("");
os.clear(); os.clear();
} }
} }
@@ -90,25 +90,34 @@ void main()
selectionArea->addControl(std::move(s)); selectionArea->addControl(std::move(s));
//功能区控件 //功能区控件
//功能区总容器 //功能区总容器
auto function = std::make_unique<Canvas>(0, 0, 0, 0); auto function = std::make_unique<Canvas>(10, 170, 680, 70);
function->setCanvasfillMode(StellarX::FillMode::Null); function->setCanvasfillMode(StellarX::FillMode::Null);
function->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
auto bitInvert = std::make_unique<Canvas>(10,170,220,70); function->setCanvasBkColor(blackColor);
auto leftShift = std::make_unique<Canvas>(240, 170, 220, 70); auto bitInvert_que = std::make_unique<Canvas>(0, 0, 220, 70);
auto rightShift = std::make_unique<Canvas>(470, 170, 220, 70); auto leftShift_que = std::make_unique<Canvas>(230, 0, 220, 70);
auto rightShift_que = std::make_unique<Canvas>(460, 0, 220, 70);
auto bitInvert = bitInvert_que.get();
auto leftShift = leftShift_que.get();
auto rightShift = rightShift_que.get();
bitInvert->setCanvasBkColor(blackColor); bitInvert->setCanvasBkColor(blackColor);
bitInvert->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); bitInvert->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
leftShift->setCanvasBkColor(blackColor); leftShift->setCanvasBkColor(blackColor);
leftShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); leftShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
rightShift->setCanvasBkColor(blackColor); rightShift->setCanvasBkColor(blackColor);
rightShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); rightShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
function->addControl(std::move(bitInvert_que));
auto bitInvertLabel = std::make_unique<Label>(18,160,"位取反"); function->addControl(std::move(leftShift_que));
function->addControl(std::move(rightShift_que));
auto bitInvertLabel = std::make_unique<Label>(13, -10, "位取反");
bitInvertLabel->setTextdisap(true); bitInvertLabel->setTextdisap(true);
auto leftShiftLabel = std::make_unique<Label>(248, 160, "左移位"); auto leftShiftLabel = std::make_unique<Label>(13, -10, "左移位");
leftShiftLabel->setTextdisap(true); leftShiftLabel->setTextdisap(true);
auto rightShiftLabel = std::make_unique<Label>(478, 160, "右移位"); auto rightShiftLabel = std::make_unique<Label>(13, -10, "右移位");
rightShiftLabel->setTextdisap(true); rightShiftLabel->setTextdisap(true);
// ====== 公用小工具====== // ====== 公用小工具======
@@ -139,22 +148,22 @@ void main()
//取反区控件 //取反区控件
std::array<std::unique_ptr<Label>, 4> bitInvertFunctionLabel; std::array<std::unique_ptr<Label>, 4> bitInvertFunctionLabel;
bitInvertFunctionLabel[0] = std::make_unique<Label>(35, 180, "低位"); bitInvertFunctionLabel[0] = std::make_unique<Label>(30, 10, "低位");
bitInvertFunctionLabel[1] = std::make_unique<Label>(90, 180, "高位"); bitInvertFunctionLabel[1] = std::make_unique<Label>(90, 10, "高位");
bitInvertFunctionLabel[2] = std::make_unique<Label>(15, 198, ""); bitInvertFunctionLabel[2] = std::make_unique<Label>(15, 38, "");
bitInvertFunctionLabel[3] = std::make_unique<Label>(75, 198, ""); bitInvertFunctionLabel[3] = std::make_unique<Label>(75, 38, "");
std::array<std::unique_ptr<TextBox>, 2> bitInvertFunctionTextBox; std::array<std::unique_ptr<TextBox>, 2> bitInvertFunctionTextBox;
bitInvertFunctionTextBox[0] = std::make_unique<TextBox>(35, 203, 35, 30, "0"); bitInvertFunctionTextBox[0] = std::make_unique<TextBox>(35, 35, 35, 30, "0");
bitInvertFunctionTextBox[1] = std::make_unique<TextBox>(95, 203, 35, 30, "0"); bitInvertFunctionTextBox[1] = std::make_unique<TextBox>(95, 35, 35, 30, "0");
auto invL = bitInvertFunctionTextBox[0].get(); auto invL = bitInvertFunctionTextBox[0].get();
auto invH = bitInvertFunctionTextBox[1].get(); auto invH = bitInvertFunctionTextBox[1].get();
auto bitInvertFunctionButton = std::make_unique<Button>(150,195, 70, 35, "位取反", auto bitInvertFunctionButton = std::make_unique<Button>(135, 35, 80, 30, "位取反",
blackColor, RGB(171, 196, 220)); blackColor, RGB(171, 196, 220));
bitInvertFunctionButton->textStyle.color = RGB(226, 116, 152); bitInvertFunctionButton->textStyle.color = RGB(226, 116, 152);
bitInvertFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE); bitInvertFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
auto bitInvertFunctionButton_ptr = bitInvertFunctionButton.get(); auto bitInvertFunctionButton_ptr = bitInvertFunctionButton.get();
bitInvert->addControl(std::move(bitInvertFunctionButton)); bitInvert->addControl(std::move(bitInvertFunctionButton));
bitInvert->addControl(std::move(bitInvertLabel)); bitInvert->addControl(std::move(bitInvertLabel));
for (auto& b : bitInvertFunctionTextBox) for (auto& b : bitInvertFunctionTextBox)
@@ -171,22 +180,22 @@ void main()
bitInvert->addControl(std::move(b)); bitInvert->addControl(std::move(b));
} }
//左移控件 //左移控件
auto leftShiftFunctionLabel = std::make_unique<Label>(435, 198, ""); auto leftShiftFunctionLabel = std::make_unique<Label>(198, 30, "");
leftShiftFunctionLabel->setTextdisap(true); leftShiftFunctionLabel->setTextdisap(true);
auto leftShiftFunctionTextBox = std::make_unique<TextBox>(325, 195, 100, 30, "0"); auto leftShiftFunctionTextBox = std::make_unique<TextBox>(90, 30, 100, 30, "0");
leftShiftFunctionTextBox->setMaxCharLen(3); leftShiftFunctionTextBox->setMaxCharLen(3);
leftShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152); leftShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152);
leftShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142)); leftShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
leftShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE); leftShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
auto shlBox = leftShiftFunctionTextBox.get(); auto shlBox = leftShiftFunctionTextBox.get();
auto leftShiftFunctionButton = std::make_unique<Button>(250, 195, 60, 30, "左移", auto leftShiftFunctionButton = std::make_unique<Button>(15, 30, 60, 30, "左移",
blackColor, RGB(171, 196, 220)); blackColor, RGB(171, 196, 220));
leftShiftFunctionButton->textStyle.color = RGB(226, 116, 152); leftShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
leftShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE); leftShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
auto leftShiftFunctionButton_ptr = leftShiftFunctionButton.get(); auto leftShiftFunctionButton_ptr = leftShiftFunctionButton.get();
leftShift->addControl(std::move(leftShiftFunctionButton)); leftShift->addControl(std::move(leftShiftFunctionButton));
leftShift->addControl(std::move(leftShiftFunctionTextBox)); leftShift->addControl(std::move(leftShiftFunctionTextBox));
@@ -194,47 +203,44 @@ void main()
leftShift->addControl(std::move(leftShiftFunctionLabel)); leftShift->addControl(std::move(leftShiftFunctionLabel));
//右移控件 //右移控件
auto rightShiftFunctionLabel = std::make_unique<Label>(665, 198, ""); auto rightShiftFunctionLabel = std::make_unique<Label>(198, 30, "");
rightShiftFunctionLabel->setTextdisap(true); rightShiftFunctionLabel->setTextdisap(true);
auto rightShiftFunctionTextBox = std::make_unique<TextBox>(555, 195, 100, 30, "0"); auto rightShiftFunctionTextBox = std::make_unique<TextBox>(90, 30, 100, 30, "0");
rightShiftFunctionTextBox->setMaxCharLen(3); rightShiftFunctionTextBox->setMaxCharLen(3);
rightShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152); rightShiftFunctionTextBox->textStyle.color = RGB(226, 116, 152);
rightShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142)); rightShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
rightShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE); rightShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
auto shrBox = rightShiftFunctionTextBox.get(); auto shrBox = rightShiftFunctionTextBox.get();
auto rightShiftFunctionButton = std::make_unique<Button>(480, 195, 60, 30, "右移", auto rightShiftFunctionButton = std::make_unique<Button>(15, 30, 60, 30, "右移",
blackColor, RGB(171, 196, 220)); blackColor, RGB(171, 196, 220));
rightShiftFunctionButton->textStyle.color = RGB(226, 116, 152); rightShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
rightShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE); rightShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
auto rightShiftFunctionButton_ptr = rightShiftFunctionButton.get(); auto rightShiftFunctionButton_ptr = rightShiftFunctionButton.get();
rightShift->addControl(std::move(rightShiftFunctionButton)); rightShift->addControl(std::move(rightShiftFunctionButton));
rightShift->addControl(std::move(rightShiftFunctionTextBox)); rightShift->addControl(std::move(rightShiftFunctionTextBox));
rightShift->addControl(std::move(rightShiftLabel)); rightShift->addControl(std::move(rightShiftLabel));
rightShift->addControl(std::move(rightShiftFunctionLabel)); rightShift->addControl(std::move(rightShiftFunctionLabel));
function->addControl(std::move(bitInvert));
function->addControl(std::move(leftShift));
function->addControl(std::move(rightShift));
//显示区控件 //显示区控件
//数值显示 //数值显示
auto NumericalDisplayArea = std::make_unique<Canvas>(10, 255, 680, 70); auto NumericalDisplayArea = std::make_unique<Canvas>(10, 255, 680, 70);
NumericalDisplayArea->setCanvasBkColor(blackColor); NumericalDisplayArea->setCanvasBkColor(blackColor);
NumericalDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); NumericalDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
std::array<std::unique_ptr<Label>, 3> NumericalDisplayAreaLabel; std::array<std::unique_ptr<Label>, 3> NumericalDisplayAreaLabel;
NumericalDisplayAreaLabel[0] = std::make_unique<Label>(18, 245, "数值显示区"); NumericalDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "数值显示区");
NumericalDisplayAreaLabel[1] = std::make_unique<Label>(20, 278, "十六进制"); NumericalDisplayAreaLabel[1] = std::make_unique<Label>(20, 25, "十六进制");
NumericalDisplayAreaLabel[2] = std::make_unique<Label>(330, 278, "十进制"); NumericalDisplayAreaLabel[2] = std::make_unique<Label>(330, 25, "十进制");
std::array<std::unique_ptr<TextBox>, 2> NumericalDisplayAreaTextBox; std::array<std::unique_ptr<TextBox>, 2> NumericalDisplayAreaTextBox;
NumericalDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 275, 200, 30, "0"); NumericalDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 25, 200, 30, "0");
NumericalDisplayAreaTextBox[1] = std::make_unique<TextBox>(400, 275, 200, 30, "0"); NumericalDisplayAreaTextBox[1] = std::make_unique<TextBox>(400, 25, 200, 30, "0");
auto hex = NumericalDisplayAreaTextBox[0].get(); auto hex = NumericalDisplayAreaTextBox[0].get();
auto dec = NumericalDisplayAreaTextBox[1].get(); auto dec = NumericalDisplayAreaTextBox[1].get();
@@ -257,15 +263,15 @@ void main()
auto BinaryDisplayArea = std::make_unique<Canvas>(10, 335, 680, 110); auto BinaryDisplayArea = std::make_unique<Canvas>(10, 335, 680, 110);
BinaryDisplayArea->setCanvasBkColor(blackColor); BinaryDisplayArea->setCanvasBkColor(blackColor);
BinaryDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); BinaryDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
std::array<std::unique_ptr<Label>, 3> BinaryDisplayAreaLabel; std::array<std::unique_ptr<Label>, 3> BinaryDisplayAreaLabel;
BinaryDisplayAreaLabel[0] = std::make_unique<Label>(18, 325, "二进制显示区"); BinaryDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "二进制显示区");
BinaryDisplayAreaLabel[1] = std::make_unique<Label>(35, 353, "上次值"); BinaryDisplayAreaLabel[1] = std::make_unique<Label>(35, 20, "上次值");
BinaryDisplayAreaLabel[2] = std::make_unique<Label>(35, 400, "本次值"); BinaryDisplayAreaLabel[2] = std::make_unique<Label>(35, 67, "本次值");
std::array<std::unique_ptr<TextBox>, 2> BinaryDisplayAreaTextBox; std::array<std::unique_ptr<TextBox>, 2> BinaryDisplayAreaTextBox;
BinaryDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 350, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000"); BinaryDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 20, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000");
BinaryDisplayAreaTextBox[1] = std::make_unique<TextBox>(110, 400, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000"); BinaryDisplayAreaTextBox[1] = std::make_unique<TextBox>(110, 67, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000");
auto Last = BinaryDisplayAreaTextBox[0].get(); auto Last = BinaryDisplayAreaTextBox[0].get();
auto This = BinaryDisplayAreaTextBox[1].get(); auto This = BinaryDisplayAreaTextBox[1].get();
@@ -366,17 +372,17 @@ void main()
auto configuration = std::make_unique<Canvas>(10, 455, 680, 40); auto configuration = std::make_unique<Canvas>(10, 455, 680, 40);
configuration->setCanvasBkColor(blackColor); configuration->setCanvasBkColor(blackColor);
configuration->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE); configuration->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
auto configurationLabel = std::make_unique<Label>(20, 445, "配置区"); auto configurationLabel = std::make_unique<Label>(20, -10, "配置区");
configurationLabel->setTextdisap(true); configurationLabel->setTextdisap(true);
std::array<std::unique_ptr<Button>,2> configurationButton; std::array<std::unique_ptr<Button>, 2> configurationButton;
configurationButton[0] = std::make_unique<Button>(450, 465, 80, 20, "一键置0", configurationButton[0] = std::make_unique<Button>(420, 10, 90, 20, "一键置0",
blackColor, RGB(171, 196, 220)); blackColor, RGB(171, 196, 220));
configurationButton[0]->textStyle.color = RGB(226, 116, 152); configurationButton[0]->textStyle.color = RGB(226, 116, 152);
configurationButton[0]->setButtonShape(StellarX::ControlShape::B_RECTANGLE); configurationButton[0]->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
configurationButton[1] = std::make_unique<Button>(550, 465, 80, 20, "一键置1", configurationButton[1] = std::make_unique<Button>(530, 10, 90, 20, "一键置1",
blackColor, RGB(171, 196, 220)); blackColor, RGB(171, 196, 220));
configurationButton[1]->textStyle.color = RGB(226, 116, 152); configurationButton[1]->textStyle.color = RGB(226, 116, 152);
configurationButton[1]->setButtonShape(StellarX::ControlShape::B_RECTANGLE); configurationButton[1]->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -419,16 +425,16 @@ void main()
auto signedToggle = std::make_unique<Button>( auto signedToggle = std::make_unique<Button>(
350, 465, 80, 20, "无符号", 330, 10, 80, 20, "无符号",
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE); blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE);
signedToggle->textStyle.color = RGB(226, 116, 152); signedToggle->textStyle.color = RGB(226, 116, 152);
signedToggle->setButtonShape(StellarX::ControlShape::B_RECTANGLE); signedToggle->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
auto* signedTogglePtr = signedToggle.get(); auto* signedTogglePtr = signedToggle.get();
signedTogglePtr->setOnToggleOnListener([&]() { signedTogglePtr->setOnToggleOnListener([&]() {
gSigned = true; gSigned = true;
signedTogglePtr->setButtonText("有符号"); signedTogglePtr->setButtonText("有符号");
StellarX::MessageBox::showModal(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
// 立即刷新十进制显示:用当前位图算出新值,仅改 dec // 立即刷新十进制显示:用当前位图算出新值,仅改 dec
auto cur = snapshotBits(); auto cur = snapshotBits();
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }(); const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
@@ -438,19 +444,20 @@ void main()
signedTogglePtr->setOnToggleOffListener([&]() { signedTogglePtr->setOnToggleOffListener([&]() {
gSigned = false; gSigned = false;
signedTogglePtr->setButtonText("无符号"); signedTogglePtr->setButtonText("无符号");
StellarX::MessageBox::showAsync(mainWindow, "无符号模式下,\n所有位均为数值位。", "无符号模式");
auto cur = snapshotBits(); auto cur = snapshotBits();
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }(); const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
dec->setText(std::to_string(u)); dec->setText(std::to_string(u));
}); });
signedTogglePtr->enableTooltip(true);
signedTogglePtr->setTooltipTextsForToggle("切换无符号模式", "切换有符号模式");
configuration->addControl(std::move(configurationButton[0])); configuration->addControl(std::move(configurationButton[0]));
configuration->addControl(std::move(configurationButton[1])); configuration->addControl(std::move(configurationButton[1]));
configuration->addControl(std::move(signedToggle)); configuration->addControl(std::move(signedToggle));
configuration->addControl(std::move(configurationLabel)); configuration->addControl(std::move(configurationLabel));
mainWindow.addControl(std::move(selectionArea)); mainWindow.addControl(std::move(selectionArea));
mainWindow.addControl(std::move(function)); mainWindow.addControl(std::move(function));
mainWindow.addControl(std::move(NumericalDisplayArea)); mainWindow.addControl(std::move(NumericalDisplayArea));
@@ -459,4 +466,4 @@ void main()
mainWindow.draw(); mainWindow.draw();
return mainWindow.runEventLoop(); return mainWindow.runEventLoop();
} }

View File

@@ -0,0 +1,127 @@
// 本Demo基于 StellarX 构建,轻量级的 Windows GUI 框架。
#include"StellarX.h"
int main()
{
Window win(1300, 800, NULL, RGB(255, 255, 0), "记账管理系统");
/*********登录界面***********/
//标签
std::unique_ptr<Label> logIn_label[3];
Label* p[3];
logIn_label[0] = std::make_unique<Label>(90, 150, "欢迎使用餐馆记账管理系统");
logIn_label[1] = std::make_unique<Label>(400, 300, "账号");
logIn_label[2] = std::make_unique<Label>(400, 400, "密码");
p[0] = logIn_label[0].get();
for (auto& log : logIn_label)
{
log->setTextdisap(true);
log->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
log->textStyle.lpszFace = "华文行楷";
}
logIn_label[0]->textStyle.nHeight = 100;
logIn_label[0]->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::NoAnchor);
logIn_label[1]->textStyle.nHeight = 50;
logIn_label[1]->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
logIn_label[2]->textStyle.nHeight = 50;
logIn_label[2]->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
//输入框
std::unique_ptr<TextBox> logIn_textBox[2];
TextBox* logIn_textBox_ptr[2];
logIn_textBox[0] = std::make_unique<TextBox>(500, 295, 450, 50);
logIn_textBox[1] = std::make_unique<TextBox>(500, 395, 450, 50);
logIn_textBox_ptr[0] = logIn_textBox[0].get();
logIn_textBox_ptr[1] = logIn_textBox[1].get();
for (auto& tb : logIn_textBox)
{
tb->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
tb->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
tb->setMaxCharLen(15);
}
//按钮
std::unique_ptr<Button> logIn_Button[2];
Button* logIn_Button_ptr[2];
logIn_Button[0] = std::make_unique<Button>(350, 500, 300, 50, "管理员登录");
logIn_Button[1] = std::make_unique<Button>(750, 500, 300, 50, "操作员登录");
logIn_Button_ptr[0] = logIn_Button[0].get();
logIn_Button_ptr[1] = logIn_Button[1].get();
for (auto& b : logIn_Button)
{
b->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
b->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
}
//log画布
auto log_Canvas = std::make_unique<Canvas>(0, 0, 1300, 800);
Canvas* log_Canvas_ptr = log_Canvas.get();
log_Canvas_ptr->setCanvasfillMode(StellarX::FillMode::Null);
log_Canvas_ptr->setShape(StellarX::ControlShape::B_RECTANGLE);
log_Canvas_ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
log_Canvas_ptr->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::Top);
//将log界面控价加入logCanvas统一管理
for (auto& b : logIn_Button)
log_Canvas_ptr->addControl(std::move(b));
for (auto& tb : logIn_textBox)
log_Canvas_ptr->addControl(std::move(tb));
for (auto& la : logIn_label)
log_Canvas_ptr->addControl(std::move(la));
/**************业务UI****************/
auto tabControl = std::make_unique<TabControl>(10, 10, 1280, 780);
auto tabControl_ptr = tabControl.get();
tabControl_ptr->setIsVisible(false);
tabControl_ptr->setCanvasfillMode(StellarX::FillMode::Null);
tabControl_ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
tabControl_ptr->setTabPlacement(StellarX::TabPlacement::Left);
tabControl_ptr->setTabBarHeight(100);
//添加页签及页
auto tabP = std::make_pair(std::make_unique<Button>(0, 0, 100, 100, "点餐"), std::make_unique<Canvas>(0, 0, 1290, 790));
tabP.second->setCanvasfillMode(StellarX::FillMode::Null);
tabP.second->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
tabControl_ptr->add(std::move(tabP));
/*------------login事件-------------*/
//管理员登录按钮事件
logIn_Button_ptr[0]->setOnClickListener([&p, &tabControl_ptr, &log_Canvas_ptr, &logIn_textBox_ptr, &win]()
{
if ("\0" == logIn_textBox_ptr[0]->getText() || "\0" == logIn_textBox_ptr[1]->getText())
{
if ("\0" == logIn_textBox_ptr[0]->getText())logIn_textBox_ptr[0]->setTextBoxBk(RGB(255, 0, 0));
if ("\0" == logIn_textBox_ptr[1]->getText())logIn_textBox_ptr[1]->setTextBoxBk(RGB(255, 0, 0));
std::cout << "\a";
StellarX::MessageBox::showModal(win, "账号或密码不能为空!", "提示");
}
else
{
log_Canvas_ptr->setIsVisible(false);
tabControl_ptr->setIsVisible(true);
win.draw("image\\bk1.jpg");
}
});
//操作员登录按钮事件
logIn_Button_ptr[1]->setOnClickListener([&tabControl_ptr, &log_Canvas_ptr, &logIn_textBox_ptr, &win]()
{
if ("\0" == logIn_textBox_ptr[0]->getText() || "\0" == logIn_textBox_ptr[1]->getText())
{
if ("\0" == logIn_textBox_ptr[0]->getText())logIn_textBox_ptr[0]->setTextBoxBk(RGB(255, 0, 0));
if ("\0" == logIn_textBox_ptr[1]->getText())logIn_textBox_ptr[1]->setTextBoxBk(RGB(255, 0, 0));
std::cout << "\a";
StellarX::MessageBox::showModal(win, "账号或密码不能为空!", "提示");
}
else
{
log_Canvas_ptr->setIsVisible(false);
tabControl_ptr->setIsVisible(true);
win.draw("image\\bk1.jpg");
}
});
win.addControl(std::move(log_Canvas));
win.addControl(std::move(tabControl));
win.draw("image\\bk1.jpg");
return win.runEventLoop();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
image/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
image/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -21,174 +21,170 @@
#include "Control.h" #include "Control.h"
#include"label.h" #include"label.h"
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色 #define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
#define TEXTMARGINS_X 6 #define TEXTMARGINS_X 6
#define TEXTMARGINS_Y 4 #define TEXTMARGINS_Y 4
constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算
constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算
class Button : public Control class Button : public Control
{ {
std::string text; // 按钮上的文字
std::string text; // 按钮上的文字 bool click; // 是否被点击
bool click; // 是否被点击 bool hover; // 是否被悬停
bool hover; // 是否被悬停
std::string cutText; // 切割后的文本 std::string cutText; // 切割后的文本
bool needCutText = true; // 是否需要切割文本 bool needCutText = true; // 是否需要切割文本
bool isUseCutText = false; // 是否使用切割文本 bool isUseCutText = false; // 是否使用切割文本
int padX = TEXTMARGINS_X; // 文本最小左右内边距 int padX = TEXTMARGINS_X; // 文本最小左右内边距
int padY = TEXTMARGINS_Y; // 文本最小上下内边距 int padY = TEXTMARGINS_Y; // 文本最小上下内边距
COLORREF buttonTrueColor; // 按钮被点击后的颜色 COLORREF buttonTrueColor; // 按钮被点击后的颜色
COLORREF buttonFalseColor; // 按钮未被点击的颜色 COLORREF buttonFalseColor; // 按钮未被点击的颜色
COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色 COLORREF buttonHoverColor; // 按钮被鼠标悬停的颜色
COLORREF buttonBorderColor = RGB(0,0,0);// 按钮边框颜色 COLORREF buttonBorderColor = RGB(0, 0, 0);// 按钮边框颜色
StellarX::ButtonMode mode; // 按钮模式 StellarX::ButtonMode mode; // 按钮模式
StellarX::ControlShape shape; // 按钮形状 StellarX::ControlShape shape; // 按钮形状
StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式 StellarX::FillMode buttonFillMode = StellarX::FillMode::Solid; //按钮填充模式
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案 StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像 IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
std::function<void()> onClickCallback; //回调函数
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
std::function<void()> onClickCallback; //回调函数 std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
StellarX::ControlText oldStyle = textStyle; // 按钮文字样式 StellarX::ControlText oldStyle = textStyle; // 按钮文字样式
int oldtext_width = -1; int oldtext_width = -1;
int oldtext_height = -1; int oldtext_height = -1;
int text_width = 0; int text_width = 0;
int text_height = 0; int text_height = 0;
// === Tooltip === // === Tooltip ===
bool tipEnabled = false; // 是否启用 bool tipEnabled = false; // 是否启用
bool tipVisible = false; // 当前是否显示 bool tipVisible = false; // 当前是否显示
bool tipFollowCursor = false; // 是否跟随鼠标 bool tipFollowCursor = false; // 是否跟随鼠标
bool tipUserOverride = false; // 是否用户自定义了tip文本 bool tipUserOverride = false; // 是否用户自定义了tip文本
int tipDelayMs = 1000; // 延时(毫秒) int tipDelayMs = 1000; // 延时(毫秒)
int tipOffsetX = 12; // 相对鼠标偏移 int tipOffsetX = 12; // 相对鼠标偏移
int tipOffsetY = 18; int tipOffsetY = 18;
ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳 ULONGLONG tipHoverTick = 0; // 开始悬停的时间戳
int lastMouseX = 0; // 最新鼠标位置(用于定位) int lastMouseX = 0; // 最新鼠标位置(用于定位)
int lastMouseY = 0; int lastMouseY = 0;
std::string tipTextClick; //NORMAL 模式下用 std::string tipTextClick; //NORMAL 模式下用
std::string tipTextOn; // click==true 时用 std::string tipTextOn; // click==true 时用
std::string tipTextOff; // click==false 时用 std::string tipTextOff; // click==false 时用
Label tipLabel; // 直接复用Label作为提示 Label tipLabel; // 直接复用Label作为提示
public:
StellarX::ControlText textStyle; // 按钮文字样式
public: public:
StellarX::ControlText textStyle; // 按钮文字样式
//默认按钮颜色
Button(int x, int y, int width, int height, const std::string text,
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
//自定义按钮未被点击和被点击颜色
Button(int x, int y, int width, int height, const std::string text,
COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL,
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
//自定义按钮颜色和悬停颜色
Button(int x, int y, int width, int height, const std::string text,
COLORREF ct, COLORREF cf,COLORREF ch,
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
//析构函数 释放图形指针内存
~Button();
//绘制按钮 public:
void draw() override;
//按钮事件处理
bool handleEvent(const ExMessage& msg) override;
//设置回调函数
void setOnClickListener(const std::function<void()>&& callback);
//设置TOGGLE模式下被点击的回调函数
void setOnToggleOnListener(const std::function<void()>&& callback);
//设置TOGGLE模式下取消点击的回调函数
void setOnToggleOffListener(const std::function<void()>&& callback);
//设置按钮模式
void setbuttonMode(StellarX::ButtonMode mode);
//设置圆角矩形椭圆宽度
void setROUND_RECTANGLEwidth(int width);
//设置圆角矩形椭圆高度
void setROUND_RECTANGLEheight(int height);
//设置按钮填充模式
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);
//设置按钮形状
void setButtonShape(StellarX::ControlShape shape);
//设置按钮点击状态
void setButtonClick(BOOL click);
//判断按钮是否被点击 //默认按钮颜色
bool isClicked() const; Button(int x, int y, int width, int height, const std::string text,
StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
//获取按钮文字 //自定义按钮未被点击和被点击颜色
std::string getButtonText() const; Button(int x, int y, int width, int height, const std::string text,
const char* getButtonText_c() const; COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL,
//获取按钮模式 StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
StellarX::ButtonMode getButtonMode() const; //自定义按钮颜色和悬停颜色
//获取按钮形状 Button(int x, int y, int width, int height, const std::string text,
StellarX::ControlShape getButtonShape() const; COLORREF ct, COLORREF cf, COLORREF ch,
//获取按钮填充模式 StellarX::ButtonMode mode = StellarX::ButtonMode::NORMAL, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
StellarX::FillMode getFillMode() const; //析构函数 释放图形指针内存
~Button();
//绘制按钮
void draw() override;
//按钮事件处理
bool handleEvent(const ExMessage& msg) override;
//设置回调函数
void setOnClickListener(const std::function<void()>&& callback);
//设置TOGGLE模式下被点击的回调函数
void setOnToggleOnListener(const std::function<void()>&& callback);
//设置TOGGLE模式下取消点击的回调函数
void setOnToggleOffListener(const std::function<void()>&& callback);
//设置按钮模式
void setbuttonMode(StellarX::ButtonMode mode);
//设置圆角矩形椭圆宽度
void setROUND_RECTANGLEwidth(int width);
//设置圆角矩形椭圆高度
void setROUND_RECTANGLEheight(int height);
//设置按钮填充模式
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);
//设置按钮形状
void setButtonShape(StellarX::ControlShape shape);
//设置按钮点击状态
void setButtonClick(BOOL click);
//判断按钮是否被点击
bool isClicked() const;
//获取按钮文字
std::string getButtonText() const;
const char* getButtonText_c() const;
//获取按钮模式
StellarX::ButtonMode getButtonMode() const;
//获取按钮形状
StellarX::ControlShape getButtonShape() const;
//获取按钮填充模式
StellarX::FillMode getFillMode() const;
//获取按钮填充图案 //获取按钮填充图案
StellarX::FillStyle getFillIma() const; StellarX::FillStyle getFillIma() const;
//获取按钮填充图像 //获取按钮填充图像
IMAGE* getFillImaImage() const; IMAGE* getFillImaImage() const;
//获取按钮边框颜色 //获取按钮边框颜色
COLORREF getButtonBorder() const; COLORREF getButtonBorder() const;
//获取按钮文字颜色 //获取按钮文字颜色
COLORREF getButtonTextColor() const; COLORREF getButtonTextColor() const;
//获取按钮文字样式 //获取按钮文字样式
StellarX::ControlText getButtonTextStyle() const; StellarX::ControlText getButtonTextStyle() const;
//获取按钮宽度 //获取按钮宽度
int getButtonWidth() const; int getButtonWidth() const;
//获取按钮高度 //获取按钮高度
int getButtonHeight() const; int getButtonHeight() const;
public: public:
// === Tooltip API=== // === Tooltip API===
//设置是否启用提示框 //设置是否启用提示框
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; } void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
//设置提示框延时 //设置提示框延时
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); } void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
//设置提示框是否跟随鼠标 //设置提示框是否跟随鼠标
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; } void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
//设置提示框位置偏移 //设置提示框位置偏移
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; } void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
//设置提示框样式 //设置提示框样式
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent); void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
//设置提示框文本 //设置提示框文本
void setTooltipText(const std::string& s){ tipTextClick = s; tipUserOverride = true; } void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText); void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
private: private:
//初始化按钮 //初始化按钮
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch); void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
//判断鼠标是否在圆形按钮内 //判断鼠标是否在圆形按钮内
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius); 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 isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
//获取对话框类型 //获取对话框类型
bool model() const override { return false; } bool model() const override { return false; }
void cutButtonText(); void cutButtonText();
// 统一隐藏&恢复背景 // 统一隐藏&恢复背景
void hideTooltip(); void hideTooltip();
// 根据当前 click 状态选择文案 // 根据当前 click 状态选择文案
void refreshTooltipTextForState(); void refreshTooltipTextForState();
}; };

View File

@@ -18,53 +18,56 @@
#pragma once #pragma once
#include "Control.h" #include "Control.h"
#include"Table.h"
class Canvas : public Control class Canvas : public Control
{ {
protected: protected:
std::vector<std::unique_ptr<Control>> controls; std::vector<std::unique_ptr<Control>> controls;
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
StellarX::FillMode canvasFillMode = StellarX::FillMode::Solid; //容器填充模式
StellarX::LineStyle canvasLineStyle = StellarX::LineStyle::Solid; //线型
int canvaslinewidth = 1; //线宽
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
COLORREF canvasBkClor = RGB(255,255,255); //背景颜色
StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE; //容器形状
StellarX::FillMode canvasFillMode = StellarX::FillMode::Solid; //容器填充模式
StellarX::LineStyle canvasLineStyle = StellarX::LineStyle::Solid; //线型
int canvaslinewidth = 1; //线宽
// 清除所有子控件 COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
COLORREF canvasBkClor = RGB(255, 255, 255); //背景颜色
// 清除所有子控件
void clearAllControls(); void clearAllControls();
public: public:
Canvas(); Canvas();
Canvas(int x, int y, int width, int height); Canvas(int x, int y, int width, int height);
~Canvas() {} ~Canvas() {}
//绘制容器及其子控件
void draw() override;
bool handleEvent(const ExMessage& msg) override;
//添加控件
void addControl(std::unique_ptr<Control> control);
//设置容器样式
void setShape(StellarX::ControlShape shape);
//设置容器填充模式
void setCanvasfillMode(StellarX::FillMode mode);
//设置容器边框颜色
void setBorderColor(COLORREF color);
//设置填充颜色
void setCanvasBkColor(COLORREF color);
//设置线形
void setCanvasLineStyle(StellarX::LineStyle style);
//设置线段宽度
void setLinewidth(int width);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void setDirty(bool dirty) override;
void onWindowResize() override;
void requestRepaint(Control* parent)override;
//获取子控件列表
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
private:
//用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; };
};
void setX(int x)override;
void setY(int y)override;
//绘制容器及其子控件
void draw() override;
bool handleEvent(const ExMessage& msg) override;
//添加控件
void addControl(std::unique_ptr<Control> control);
//设置容器样式
void setShape(StellarX::ControlShape shape);
//设置容器填充模式
void setCanvasfillMode(StellarX::FillMode mode);
//设置容器边框颜色
void setBorderColor(COLORREF color);
//设置填充颜色
void setCanvasBkColor(COLORREF color);
//设置线形
void setCanvasLineStyle(StellarX::LineStyle style);
//设置线段宽度
void setLinewidth(int width);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void setDirty(bool dirty) override;
void onWindowResize() override;
void requestRepaint(Control* parent)override;
//获取子控件列表
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
private:
//用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; };
};

View File

@@ -9,23 +9,20 @@
* - 定义控件基本属性(坐标、尺寸、脏标记) * - 定义控件基本属性(坐标、尺寸、脏标记)
* - 提供绘图状态管理saveStyle/restoreStyle * - 提供绘图状态管理saveStyle/restoreStyle
* - 声明纯虚接口draw、handleEvent等 * - 声明纯虚接口draw、handleEvent等
* - 支持移动语义,禁止拷贝语义 * - 禁止移动语义,禁止拷贝语义
* *
* @使用场景: 作为所有具体控件类的基类,不直接实例化 * @使用场景: 作为所有具体控件类的基类,不直接实例化
* @所属框架: 星垣(StellarX) GUI框架 * @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物 * @作者: 我在人间做废物
******************************************************************************/ ******************************************************************************/
#pragma once #pragma once
#ifndef _WIN32_WINNT #ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 #define _WIN32_WINNT 0x0600
#endif #endif
#ifndef WINVER #ifndef WINVER
#define WINVER _WIN32_WINNT #define WINVER _WIN32_WINNT
#endif #endif
#include <windows.h> #include <windows.h>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <easyx.h> #include <easyx.h>
@@ -38,108 +35,119 @@
class Control class Control
{ {
protected: protected:
std::string id; // 控件ID std::string id; // 控件ID
int localx,x, localy,y; // 左上角坐标 int localx, x, localy, y; // 左上角坐标
int localWidth,width, localHeight,height; // 控件尺寸 int localWidth, width, localHeight, height; // 控件尺寸
Control* parent = nullptr; // 父控件 Control* parent = nullptr; // 父控件
bool dirty = true; // 是否重绘 bool dirty = true; // 是否重绘
bool show = true; // 是否显示 bool show = true; // 是否显示
/* == 背景快照 == */ /* == 布局模式 == */
IMAGE* saveBkImage = nullptr; StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标 StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸 StellarX::Anchor anchor_2 = StellarX::Anchor::Right; // 锚点
bool hasSnap = false; // 当前是否持有有效快照
StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度 /* == 背景快照 == */
IMAGE* saveBkImage = nullptr;
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
int saveWidth = 0, saveHeight = 0; // 快照保存尺寸
bool hasSnap = false; // 当前是否持有有效快照
LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色 StellarX::RouRectangle rouRectangleSize; // 圆角矩形椭圆宽度和高度
COLORREF* currentColor = new COLORREF();
COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
Control(const Control&) = delete; LOGFONT* currentFont = new LOGFONT(); // 保存当前字体样式和颜色
Control& operator=(const Control&) = delete; COLORREF* currentColor = new COLORREF();
Control(Control&&) = delete; COLORREF* currentBkColor = new COLORREF(); // 保存当前填充色
Control& operator=(Control&&) = delete; COLORREF* currentBorderColor = new COLORREF(); // 边框颜色
LINESTYLE* currentLineStyle = new LINESTYLE(); // 保存当前线型
Control() : localx(0),x(0), localy(0),y(0), localWidth(100),width(100),height(100), localHeight(100) {} Control(const Control&) = delete;
Control(int x, int y, int width, int height) Control& operator=(const Control&) = delete;
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height){} Control(Control&&) = delete;
Control& operator=(Control&&) = delete;
Control() : localx(0), x(0), localy(0), y(0), localWidth(100), width(100), height(100), localHeight(100) {}
Control(int x, int y, int width, int height)
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height) {
}
public: public:
virtual ~Control() virtual ~Control()
{ {
delete currentFont; delete currentFont;
delete currentColor; delete currentColor;
delete currentBkColor; delete currentBkColor;
delete currentBorderColor; delete currentBorderColor;
delete currentLineStyle; delete currentLineStyle;
currentFont = nullptr; currentFont = nullptr;
currentColor = nullptr; currentColor = nullptr;
currentBkColor = nullptr; currentBkColor = nullptr;
currentBorderColor = nullptr; currentBorderColor = nullptr;
currentLineStyle = nullptr; currentLineStyle = nullptr;
discardBackground(); discardBackground();
} }
protected: protected:
//向上请求重绘 //向上请求重绘
virtual void requestRepaint(Control* parent); virtual void requestRepaint(Control* parent);
//根控件/无父时触发重绘 //根控件/无父时触发重绘
virtual void onRequestRepaintAsRoot(); virtual void onRequestRepaintAsRoot();
protected: protected:
//保存背景快照 //保存背景快照
virtual void saveBackground(int x, int y, int w, int h); virtual void saveBackground(int x, int y, int w, int h);
// putimage 回屏 // putimage 回屏
virtual void restBackground(); virtual void restBackground();
// 释放快照(窗口重绘/尺寸变化后必须作废) // 释放快照(窗口重绘/尺寸变化后必须作废)
void discardBackground(); void discardBackground();
public: public:
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位 //释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
void updateBackground(); void updateBackground();
//窗口变化丢快照 //窗口变化丢快照
virtual void onWindowResize(); virtual void onWindowResize();
// 获取位置和尺寸 // 获取位置和尺寸
int getX() const { return x; } int getX() const { return x; }
int getY() const { return y; } int getY() const { return y; }
int getWidth() const { return width; } int getWidth() const { return width; }
int getHeight() const { return height; } int getHeight() const { return height; }
int getRight() const { return x + width; } int getRight() const { return x + width; }
int getBottom() const { return y + height; } int getBottom() const { return y + height; }
int getLocalX() const { return localx; }
int getLocalY() const { return localy; }
int getLocalWidth() const { return localWidth; }
int getLocalHeight() const { return localHeight; }
int getLocalRight() const { return localx + localWidth; }
int getLocalBottom() const { return localy + localHeight; }
void setX(int x) { this->x = x; dirty = true; } int getLocalX() const { return localx; }
void setY(int y) { this->y = y; dirty = true; } int getLocalY() const { return localy; }
void setWidth(int width) { this->width = width; dirty = true; } int getLocalWidth() const { return localWidth; }
void setHeight(int height) { this->height = height; dirty = true; } int getLocalHeight() const { return localHeight; }
int getLocalRight() const { return localx + localWidth; }
int getLocalBottom() const { return localy + localHeight; }
virtual void setX(int x) { this->x = x; dirty = true; }
virtual void setY(int y) { this->y = y; dirty = true; }
virtual void setWidth(int width) { this->width = width; dirty = true; }
virtual void setHeight(int height) { this->height = height; dirty = true; }
public: public:
virtual void draw() = 0; virtual void draw() = 0;
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费 virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
//设置是否显示 //设置是否显示
virtual void setIsVisible(bool show); virtual void setIsVisible(bool show);
//设置父容器指针 //设置父容器指针
void setParent(Control* parent) { this->parent = parent; } void setParent(Control* parent) { this->parent = parent; }
//设置是否重绘 //设置是否重绘
virtual void setDirty(bool dirty) { this->dirty = dirty; } virtual void setDirty(bool dirty) { this->dirty = dirty; }
//检查控件是否可见
//检查控件是否可见 bool IsVisible() const { return show; };
bool IsVisible() const { return show; }; //获取控件id
//获取控件id std::string getId() const { return id; }
std::string getId() const { return id; } //检查是否为脏
//检查是否为脏 bool isDirty() { return dirty; }
bool isDirty() { return dirty; } //用来检查对话框是否模态,其他控件不用实现
//用来检查对话框是否模态,其他控件不用实现 virtual bool model()const = 0;
virtual bool model()const = 0; //布局
void setLayoutMode(StellarX::LayoutMode layoutMode_);
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
StellarX::Anchor getAnchor_1() const;
StellarX::Anchor getAnchor_2() const;
StellarX::LayoutMode getLayoutMode() const;
protected: protected:
void saveStyle(); void saveStyle();
void restoreStyle(); void restoreStyle();
}; };

View File

@@ -27,320 +27,364 @@
*/ */
namespace StellarX namespace StellarX
{ {
/** /**
* @枚举类名称: FillStyle * @枚举类名称: FillStyle
* @功能描述: 用来定义控件填充图案的枚举类 * @功能描述: 用来定义控件填充图案的枚举类
* *
* @详细说明: * @详细说明:
* 根据此枚举类可以自定义控件填充图案 * 根据此枚举类可以自定义控件填充图案
* 可以在控件初始化时设置填充图案 * 可以在控件初始化时设置填充图案
* 根据具体情况选择不同的填充图案 * 根据具体情况选择不同的填充图案
* 默认填充图案为水平线 * 默认填充图案为水平线
* *
* @取值说明: * @取值说明:
* Horizontal - 水平线 * Horizontal - 水平线
* Vertical - 垂直线 * Vertical - 垂直线
* FDiagonal - 反斜线 * FDiagonal - 反斜线
* BDiagonal - 正斜线 * BDiagonal - 正斜线
* Cross - 十字 * Cross - 十字
* DiagCross - 网格 * DiagCross - 网格
* *
* @使用示例: * @使用示例:
* FillStyle var = FillStyle::Horizontal; * FillStyle var = FillStyle::Horizontal;
* *
* @备注: * @备注:
* 此枚举类仅支持图案填充模式 * 此枚举类仅支持图案填充模式
*/ */
enum class FillStyle enum class FillStyle
{ {
Horizontal = HS_HORIZONTAL, // 水平线 Horizontal = HS_HORIZONTAL, // 水平线
Vertical = HS_VERTICAL, // 垂直线 Vertical = HS_VERTICAL, // 垂直线
FDiagonal = HS_FDIAGONAL, // 反斜线 FDiagonal = HS_FDIAGONAL, // 反斜线
BDiagonal = HS_BDIAGONAL, // 正斜线 BDiagonal = HS_BDIAGONAL, // 正斜线
Cross = HS_CROSS, // 十字 Cross = HS_CROSS, // 十字
DiagCross = HS_DIAGCROSS // 网格 DiagCross = HS_DIAGCROSS // 网格
}; };
/** /**
* @枚举类名称: FillMode * @枚举类名称: FillMode
* @功能描述: 用来定义控件填充模式的枚举类 * @功能描述: 用来定义控件填充模式的枚举类
* *
* @详细说明: * @详细说明:
* 根据此枚举类可以自定义控件填充模式 * 根据此枚举类可以自定义控件填充模式
* 可以在控件初始化时设置填充模式 * 可以在控件初始化时设置填充模式
* 根据具体情况选择不同的填充模式 * 根据具体情况选择不同的填充模式
* 默认填充模式为固实填充 * 默认填充模式为固实填充
* *
* @取值说明: * @取值说明:
* Solid - 固实填充 * Solid - 固实填充
* Null - 不填充 * Null - 不填充
* Hatched - 图案填充 * Hatched - 图案填充
* Pattern - 自定义图案填充 * Pattern - 自定义图案填充
* DibPattern - 自定义图像填充 * DibPattern - 自定义图像填充
* *
* @使用示例: * @使用示例:
* FillMode var = FillMode::Solid; * FillMode var = FillMode::Solid;
*/ */
enum class FillMode enum class FillMode
{ {
Solid = BS_SOLID, //固实填充 Solid = BS_SOLID, //固实填充
Null = BS_NULL, // 不填充 Null = BS_NULL, // 不填充
Hatched = BS_HATCHED, // 图案填充 Hatched = BS_HATCHED, // 图案填充
Pattern = BS_PATTERN, // 自定义图案填充 Pattern = BS_PATTERN, // 自定义图案填充
DibPattern = BS_DIBPATTERN // 自定义图像填充 DibPattern = BS_DIBPATTERN // 自定义图像填充
}; };
/** /**
* @枚举类名称: LineStyle * @枚举类名称: LineStyle
* @功能描述: 此枚举类用来定义控件边框线型 * @功能描述: 此枚举类用来定义控件边框线型
* *
* @详细说明: * @详细说明:
* 根据此枚举类可以自定义控件边框线型 * 根据此枚举类可以自定义控件边框线型
* 可以在控件初始化时设置边框线型 * 可以在控件初始化时设置边框线型
* 根据具体情况选择不同的线型 * 根据具体情况选择不同的线型
* 默认线型为实线 * 默认线型为实线
* *
* @取值说明: * @取值说明:
* Solid // 实线 * Solid // 实线
* Dash // 虚线 * Dash // 虚线
* Dot // 点线 * Dot // 点线
* DashDot // 点划线 * DashDot // 点划线
* DashDotDot // 双点划线 * DashDotDot // 双点划线
* Null // 无线 * Null // 无线
* *
* @使用示例: * @使用示例:
* LineStyle var = LineStyle::Solid; * LineStyle var = LineStyle::Solid;
*/ */
enum class LineStyle enum class LineStyle
{ {
Solid = PS_SOLID, // 实线 Solid = PS_SOLID, // 实线
Dash = PS_DASH, // 虚线 Dash = PS_DASH, // 虚线
Dot = PS_DOT, // 点线 Dot = PS_DOT, // 点线
DashDot = PS_DASHDOT, // 点划线 DashDot = PS_DASHDOT, // 点划线
DashDotDot = PS_DASHDOTDOT, // 双点划线 DashDotDot = PS_DASHDOTDOT, // 双点划线
Null = PS_NULL // 无线 Null = PS_NULL // 无线
}; };
/** /**
* @结构体名称: ControlText * @结构体名称: ControlText
* @功能描述: 控件字体样式 可以自定义不同的样式 * @功能描述: 控件字体样式 可以自定义不同的样式
* *
* @详细说明: * @详细说明:
* 主要使用的场景为:需要修改或想自定义控件字体大小,字体样式,颜色等 * 主要使用的场景为:需要修改或想自定义控件字体大小,字体样式,颜色等
* *
* @成员说明: * @成员说明:
* int nHeight = 0; - 字体高度 * int nHeight = 0; - 字体高度
* int nWidth = 0; - 字体宽度 如果为0则自适应 * int nWidth = 0; - 字体宽度 如果为0则自适应
* LPCTSTR lpszFace = "宋体"; - 字体名称 * LPCTSTR lpszFace = "宋体"; - 字体名称
* COLORREF color = RGB(0, 0, 0); - 字体颜色 * COLORREF color = RGB(0, 0, 0); - 字体颜色
* int nEscapement = 0; - 字符串旋转角度 * int nEscapement = 0; - 字符串旋转角度
* int nOrientation = 0; - 字符旋转角度 * int nOrientation = 0; - 字符旋转角度
* int nWeight = 0; - 字体粗细 范围0~1000 0表示默认 * int nWeight = 0; - 字体粗细 范围0~1000 0表示默认
* bool bItalic = false; - 是否斜体 * bool bItalic = false; - 是否斜体
* bool bUnderline = false; - 是否下划线 * bool bUnderline = false; - 是否下划线
* bool bStrikeOut = false; - 是否删除线 * bool bStrikeOut = false; - 是否删除线
*/ */
struct ControlText struct ControlText
{ {
int nHeight = 0; //- 字体高度 int nHeight = 0; //- 字体高度
int nWidth = 0; //- 字体宽度 如果为0则自适应 int nWidth = 0; //- 字体宽度 如果为0则自适应
LPCTSTR lpszFace = "微软雅黑"; //- 字体名称 LPCTSTR lpszFace = "微软雅黑"; //- 字体名称
COLORREF color = RGB(0, 0, 0); //- 字体颜色 COLORREF color = RGB(0, 0, 0); //- 字体颜色
int nEscapement = 0; //- 字符串旋转角度 int nEscapement = 0; //- 字符串旋转角度
int nOrientation = 0; //- 字符旋转角度 int nOrientation = 0; //- 字符旋转角度
int nWeight = 0; //- 字体粗细 范围0~1000 0表示默认 int nWeight = 0; //- 字体粗细 范围0~1000 0表示默认
bool bItalic = false; //- 是否斜体 bool bItalic = false; //- 是否斜体
bool bUnderline = false; //- 是否下划线 bool bUnderline = false; //- 是否下划线
bool bStrikeOut = false; //- 是否删除线 bool bStrikeOut = false; //- 是否删除线
bool operator!=(const ControlText& text); bool operator!=(const ControlText& text);
ControlText& operator=(const ControlText& text); ControlText& operator=(const ControlText& text);
}; };
/** /**
* @枚举名称: ControlShape * @枚举名称: ControlShape
* @功能描述: 枚举控件的不同几何样式 * @功能描述: 枚举控件的不同几何样式
* *
* @详细说明: * @详细说明:
* 定义了四种(有无边框算一种)不同的几何样式,可以根据具体需求 * 定义了四种(有无边框算一种)不同的几何样式,可以根据具体需求
* 自定义控件的形状。 * 自定义控件的形状。
* *
* @取值说明: * @取值说明:
* RECTANGLE = 1, //有边框矩形 * RECTANGLE = 1, //有边框矩形
* B_RECTANGLE, //无边框矩形 * B_RECTANGLE, //无边框矩形
* ROUND_RECTANGLE, //有边框圆角矩形 * ROUND_RECTANGLE, //有边框圆角矩形
* B_ROUND_RECTANGLE, //无边框圆角矩形 * B_ROUND_RECTANGLE, //无边框圆角矩形
* CIRCLE, //有边框圆形 * CIRCLE, //有边框圆形
* B_CIRCLE, //无边框圆形 * B_CIRCLE, //无边框圆形
* ELLIPSE, //有边框椭圆 * ELLIPSE, //有边框椭圆
* B_ELLIPSE //无边框椭圆 * B_ELLIPSE //无边框椭圆
* *
* @使用示例: * @使用示例:
* ControlShape shape = ControlShape::ELLIPSE; * ControlShape shape = ControlShape::ELLIPSE;
* *
* @备注: * @备注:
* 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。 * 按钮类支持所有形状,部分控件只支持部分形状,具体请参考控件类。
*/ */
enum class ControlShape enum class ControlShape
{ {
RECTANGLE = 1, //有边框矩形 RECTANGLE = 1, //有边框矩形
B_RECTANGLE, //无边框矩形 B_RECTANGLE, //无边框矩形
ROUND_RECTANGLE, //有边框圆角矩形 ROUND_RECTANGLE, //有边框圆角矩形
B_ROUND_RECTANGLE, //无边框圆角矩形 B_ROUND_RECTANGLE, //无边框圆角矩形
CIRCLE, //有边框圆形 CIRCLE, //有边框圆形
B_CIRCLE, //无边框圆形 B_CIRCLE, //无边框圆形
ELLIPSE, //有边框椭圆 ELLIPSE, //有边框椭圆
B_ELLIPSE //无边框椭圆 B_ELLIPSE //无边框椭圆
}; };
/** /**
* @枚举类名称: TextBoxmode * @枚举类名称: TextBoxmode
* @功能描述: 定义了文本框的两种模式 * @功能描述: 定义了文本框的两种模式
* *
* @详细说明: * @详细说明:
* 需要限制文本框是否接受用户输入时使用 * 需要限制文本框是否接受用户输入时使用
* *
* @取值说明: * @取值说明:
* INPUT_MODE, // 用户可输入模式 * INPUT_MODE, // 用户可输入模式
* READONLY_MODE // 只读模式 * READONLY_MODE // 只读模式
*/ */
enum class TextBoxmode enum class TextBoxmode
{ {
INPUT_MODE, // 用户可输入模式 INPUT_MODE, // 用户可输入模式
READONLY_MODE // 只读模式 READONLY_MODE, // 只读模式
}; PASSWORD_MODE// 密码模式
};
/** /**
* @枚举名称: ButtonMode * @枚举名称: ButtonMode
* @功能描述: 定义按钮的工作模式 * @功能描述: 定义按钮的工作模式
* *
* @详细说明: * @详细说明:
* 根据按钮的工作模式,按钮可以有不同的行为。 * 根据按钮的工作模式,按钮可以有不同的行为。
* 用户可以在具体情况下设置按钮的工作模式。 * 用户可以在具体情况下设置按钮的工作模式。
* *
* @取值说明: * @取值说明:
* NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。 * NORMAL = 1, - 普通模式,点击后触发回调,但不会保持状态。
* TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。 * TOGGLE, - 切换模式,点击后会在选中和未选中之间切换,触发不同的回调函数。
* DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。 * DISABLED - 禁用模式,按钮不可点击,显示为灰色,文本显示删除线。
* *
* @使用示例: * @使用示例:
* ButtonMode mode = ButtonMode::NORMAL; * ButtonMode mode = ButtonMode::NORMAL;
*/ */
enum class ButtonMode enum class ButtonMode
{ {
NORMAL = 1, //普通模式 NORMAL = 1, //普通模式
TOGGLE, //切换模式 TOGGLE, //切换模式
DISABLED //禁用模式 DISABLED //禁用模式
}; };
/** /**
* @结构体名称: RouRectangle * @结构体名称: RouRectangle
* @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸 * @功能描述: 定义了控件圆角矩形样式时圆角的椭圆尺寸
* *
* @详细说明: * @详细说明:
* 需要修改控件圆角矩形样式时的圆角椭圆。 * 需要修改控件圆角矩形样式时的圆角椭圆。
* *
* @成员说明: * @成员说明:
* int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。 * int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
* int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。 * int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
*/ */
struct RouRectangle struct RouRectangle
{ {
int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。 int ROUND_RECTANGLEwidth = 20; //构成圆角矩形的圆角的椭圆的宽度。
int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。 int ROUND_RECTANGLEheight = 20; //构成圆角矩形的圆角的椭圆的高度。
}; };
// 消息框类型 // 消息框类型
enum class MessageBoxType enum class MessageBoxType
{ {
OK, // 只有确定按钮 OK, // 只有确定按钮
OKCancel, // 确定和取消按钮 OKCancel, // 确定和取消按钮
YesNo, // 是和否按钮 YesNo, // 是和否按钮
YesNoCancel, // 是、否和取消按钮 YesNoCancel, // 是、否和取消按钮
RetryCancel, // 重试和取消按钮 RetryCancel, // 重试和取消按钮
AbortRetryIgnore, // 中止、重试和忽略按钮 AbortRetryIgnore, // 中止、重试和忽略按钮
}; };
// 消息框返回值 // 消息框返回值
enum class MessageBoxResult enum class MessageBoxResult
{ {
OK = 1, // 确定按钮 OK = 1, // 确定按钮
Cancel = 2, // 取消按钮 Cancel = 2, // 取消按钮
Yes = 6, // 是按钮 Yes = 6, // 是按钮
No = 7, // 否按钮 No = 7, // 否按钮
Abort = 3, // 中止按钮 Abort = 3, // 中止按钮
Retry = 4, // 重试按钮 Retry = 4, // 重试按钮
Ignore = 5 // 忽略按钮 Ignore = 5 // 忽略按钮
}; };
#if 0 //布局管理器相关 —待实现— #if 0 //布局管理器相关 —待实现—
/* /*
* *
*@枚举名称: LayoutKind *@枚举名称: LayoutKind
* @功能描述 : 定义布局管理类型 * @功能描述 : 定义布局管理类型
* *
*@详细说明 : *@详细说明 :
* 根据布局管理类型,控件可以有不同的布局方式。 * 根据布局管理类型,控件可以有不同的布局方式。
* 用户可以在具体情况下设置布局管理类型。 * 用户可以在具体情况下设置布局管理类型。
* *
* @取值说明 : * @取值说明 :
* Absolute不管保持子控件自己的坐标向后兼容 * Absolute不管保持子控件自己的坐标向后兼容
* HBox 水平方向排队;支持固定宽、权重分配、对齐、拉伸。 * HBox 水平方向排队;支持固定宽、权重分配、对齐、拉伸。
* VBox 竖直方向排队;同上。 * VBox 竖直方向排队;同上。
* Grid网格按行列摆支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。 * Grid网格按行列摆支持固定/自适应/权重行列;支持跨行/跨列;单元内对齐/拉伸。
* *
*/ */
// 布局类型 // 布局类型
enum class LayoutKind enum class LayoutKind
{ {
Absolute = 1, Absolute = 1,
HBox, HBox,
VBox, VBox,
Grid, Grid,
Flow, Flow,
Stack Stack
}; };
// 布局参数 // 布局参数
struct LayoutParams struct LayoutParams
{ {
// 边距左、右、上、下 // 边距左、右、上、下
int marginL = 0, marginR = 0, marginT = 0, marginB = 0; int marginL = 0, marginR = 0, marginT = 0, marginB = 0;
// 固定尺寸(>=0 强制;-1 用控件当前尺寸) // 固定尺寸(>=0 强制;-1 用控件当前尺寸)
int fixedW = -1, fixedH = -1; int fixedW = -1, fixedH = -1;
// 主轴权重HBox=宽度、VBox=高度、Grid见下 // 主轴权重HBox=宽度、VBox=高度、Grid见下
float weight = 0.f; float weight = 0.f;
// 对齐(非拉伸时生效) // 对齐(非拉伸时生效)
enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 }; enum Align { Start = 0, Center = 1, End = 2, Stretch = 3 };
int alignX = Start; // HBox: 次轴=YVBox: 次轴=XGrid: 单元内 int alignX = Start; // HBox: 次轴=YVBox: 次轴=XGrid: 单元内
int alignY = Start; // Grid 控制单元内垂直HBox / VBox通常只用 alignX int alignY = Start; // Grid 控制单元内垂直HBox / VBox通常只用 alignX
// Grid 专用(可先不做) // Grid 专用(可先不做)
int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1; int gridRow = 0, gridCol = 0, rowSpan = 1, colSpan = 1;
// Flow 专用(可先不做) // Flow 专用(可先不做)
int flowBreak = 0; // 1=强制换行 int flowBreak = 0; // 1=强制换行
}; };
#endif #endif
/* /*
* @枚举名称: TabPlacement * @枚举名称: TabPlacement
* @功能描述: 定义了选项卡页签的不同位置 * @功能描述: 定义了选项卡页签的不同位置
* *
* @详细说明: * @详细说明:
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。 * 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
* *
* @成员说明: * @成员说明:
* Top, - 选项卡页签位于顶部 * Top, - 选项卡页签位于顶部
* Bottom, - 选项卡页签位于底部 * Bottom, - 选项卡页签位于底部
* Left, - 选项卡页签位于左侧 * Left, - 选项卡页签位于左侧
* Right - 选项卡页签位于右侧 * Right - 选项卡页签位于右侧
* *
* @使用示例: * @使用示例:
* TabPlacement placement = TabPlacement::Top; * TabPlacement placement = TabPlacement::Top;
*/ */
enum class TabPlacement enum class TabPlacement
{ {
Top, Top,
Bottom, Bottom,
Left, Left,
Right Right
}; };
/*
* @枚举名称: LayoutMode
* @功能描述: 定义了两种布局模式
*
* @详细说明:
* 根据不同模式,在窗口拉伸时采用不同的布局策略
*
* @成员说明:
* Fixed, - 固定布局
* AnchorToEdges - 锚定布局
*
* @使用示例:
* LayoutMode mode = LayoutMode::Fixed;
*/
enum class LayoutMode
{
Fixed,
AnchorToEdges
};
/*
* @枚举名称: Anchor
* @功能描述: 定义了控件相对于窗口锚定的位置
*
* @详细说明:
* 根据不同的锚定位置,有不同的拉伸策略
*
* @成员说明:
* Top, - 锚定上边,控件上边与窗口上侧距离保持不变
* Bottom, - 锚定底边,控件底边与窗口底边距离保持不变
* Left, - 锚定左边,控件左边与窗口左侧距离保持不变
* Right - 锚定右边,控件上边与窗口右侧距离保持不变
*
* @使用示例:
* Anchor a = Anchor::Top;
*/
enum class Anchor
{
NoAnchor = 0,
Left = 1,
Right,
Top,
Bottom
};
} }

View File

@@ -30,7 +30,7 @@
#define titleToTextMargin 10 //标题到文本的距离 #define titleToTextMargin 10 //标题到文本的距离
#define textToBorderMargin 10 //文本到边框的距离 #define textToBorderMargin 10 //文本到边框的距离
#define BorderWidth 3 //边框宽度 #define BorderWidth 3 //边框宽度
class Dialog : public Canvas class Dialog : public Canvas
{ {
Window& hWnd; //窗口引用 Window& hWnd; //窗口引用
@@ -45,19 +45,17 @@ class Dialog : public Canvas
std::string message; //提示信息 std::string message; //提示信息
std::vector<std::string> lines; //消息内容按行分割 std::vector<std::string> lines; //消息内容按行分割
bool needsInitialization = true; //是否需要初始化
bool needsInitialization = true; //是否需要初始化
bool close = false; //是否关闭 bool close = false; //是否关闭
bool modal = true; //是否模态 bool modal = true; //是否模态
COLORREF backgroundColor = RGB(240, 240, 240); //背景颜色 COLORREF backgroundColor = RGB(240, 240, 240); //背景颜色
COLORREF borderColor = RGB(100, 100, 100); //边框颜色 COLORREF borderColor = RGB(100, 100, 100); //边框颜色
COLORREF buttonTrueColor = RGB(211, 190, 190); //按钮被点击颜色 COLORREF buttonTrueColor = RGB(211, 190, 190); //按钮被点击颜色
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色 COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色 COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
Button* closeButton = nullptr; //关闭按钮 Button* closeButton = nullptr; //关闭按钮
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果 StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
@@ -76,8 +74,7 @@ public:
//获取对话框消息,用以去重 //获取对话框消息,用以去重
std::string GetCaption() const; std::string GetCaption() const;
//获取对话框消息,用以去重 //获取对话框消息,用以去重
std::string GetText() const; std::string GetText() const;
public: public:
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true); Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
@@ -105,31 +102,27 @@ public:
// 显示对话框 // 显示对话框
void Show(); void Show();
// 关闭对话框 // 关闭对话框
void Close(); void Close();
//初始化 //初始化
void setInitialization(bool init); void setInitialization(bool init);
private: private:
// 初始化按钮 // 初始化按钮
void initButtons(); void initButtons();
// 初始化关闭按钮 // 初始化关闭按钮
void initCloseButton(); void initCloseButton();
// 初始化标题 // 初始化标题
void initTitle(); void initTitle();
// 按行分割消息内容 // 按行分割消息内容
void splitMessageLines(); void splitMessageLines();
// 获取文本大小 // 获取文本大小
void getTextSize(); void getTextSize();
//初始化对话框尺寸 //初始化对话框尺寸
void initDialogSize(); void initDialogSize();
void saveBackground(int x, int y, int w, int h)override; void addControl(std::unique_ptr<Control> control);
void restBackground()override;
// 清除所有控件 // 清除所有控件
void clearControls(); void clearControls();
//创建对话框按钮 //创建对话框按钮
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text); std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
void requestRepaint(Control* parent) override; void requestRepaint(Control* parent) override;

View File

@@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
* @文件: StellarX.h * @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件 * @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v2.2.1 * @版本: v2.3.2
* @描述: * @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。 * 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库提供简洁易用的API和丰富的控件。 * 基于EasyX图形库提供简洁易用的API和丰富的控件。
@@ -11,6 +11,7 @@
* *
* @作者: 我在人间做废物 * @作者: 我在人间做废物
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com] * @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
* @官网https://stellarx-gui.top/
* @仓库: [https://github.com/Ysm-04/StellarX] * @仓库: [https://github.com/Ysm-04/StellarX]
* *
* @许可证: MIT License * @许可证: MIT License

View File

@@ -25,44 +25,48 @@
#define BUTMINWIDTH 30 #define BUTMINWIDTH 30
class TabControl :public Canvas class TabControl :public Canvas
{ {
int tabBarHeight = BUTMINWIDTH; //页签栏高度 int tabBarHeight = BUTMINWIDTH; //页签栏高度
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top ; //页签排列方式 bool IsFirstDraw = true; //首次绘制标记
std::vector<std::pair<std::unique_ptr<Button>,std::unique_ptr<Canvas>>> controls; //页签/页列表 int defaultActivation = -1; //默认激活页签索引
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
private: private:
using Canvas::addControl; // 禁止外部误用 using Canvas::addControl; // 禁止外部误用
void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载 void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载
private: private:
inline void initTabBar(); inline void initTabBar();
inline void initTabPage(); inline void initTabPage();
public: public:
TabControl(); TabControl();
TabControl(int x,int y,int width,int height); TabControl(int x, int y, int width, int height);
~TabControl(); ~TabControl();
void setX(int x)override;
void setY(int y)override;
void draw() override; void draw() override;
bool handleEvent(const ExMessage& msg) override; bool handleEvent(const ExMessage& msg) override;
//添加页签+页
void add(std::pair<std::unique_ptr<Button> ,std::unique_ptr<Canvas>>&& control);
//添加为某个页添加控件
void add(std::string tabText,std::unique_ptr<Control> control);
//设置页签位置
void setTabPlacement(StellarX::TabPlacement placement);
//设置页签栏高度 两侧排列时为宽度
void setTabBarHeight(int height);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void onWindowResize() override;
//获取当前激活页签索引
int getActiveIndex() const;
//设置当前激活页签索引
void setActiveIndex(int idx);
//获取页签数量
int count() const;
//通过页签文本返回索引
int indexOf(const std::string& tabText) const;
void setDirty(bool dirty) override;
void requestRepaint(Control* parent)override;
};
//添加页签+页
void add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control);
//添加为某个页添加控件
void add(std::string tabText, std::unique_ptr<Control> control);
//设置页签位置
void setTabPlacement(StellarX::TabPlacement placement);
//设置页签栏高度 两侧排列时为宽度
void setTabBarHeight(int height);
//设置不可见后传递给子控件重写
void setIsVisible(bool visible) override;
void onWindowResize() override;
//获取当前激活页签索引
int getActiveIndex() const;
//设置当前激活页签索引
void setActiveIndex(int idx);
//获取页签数量
int count() const;
//通过页签文本返回索引
int indexOf(const std::string& tabText) const;
void setDirty(bool dirty) override;
void requestRepaint(Control* parent)override;
};

View File

@@ -21,28 +21,26 @@
class Label : public Control class Label : public Control
{ {
std::string text; //标签文本 std::string text; //标签文本
COLORREF textBkColor; //标签背景颜色 COLORREF textBkColor; //标签背景颜色
bool textBkDisap = false; //标签背景是否透明 bool textBkDisap = false; //标签背景是否透明
//标签事件处理(标签无事件)不实现具体代码 //标签事件处理(标签无事件)不实现具体代码
bool handleEvent(const ExMessage& msg) override { return false; } bool handleEvent(const ExMessage& msg) override { return false; }
//用来检查对话框是否模态,此控件不做实现 //用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; }; bool model() const override { return false; };
public: public:
StellarX::ControlText textStyle; //标签文本样式 StellarX::ControlText textStyle; //标签文本样式
public: public:
Label(); Label();
Label(int x, int y, std::string text = "标签",COLORREF textcolor = BLACK, COLORREF bkColor= RGB(255,255,255)); Label(int x, int y, std::string text = "标签", COLORREF textcolor = BLACK, COLORREF bkColor = RGB(255, 255, 255));
void draw() override; void draw() override;
void hide(); void hide();
//设置标签背景是否透明 //设置标签背景是否透明
void setTextdisap(bool key); void setTextdisap(bool key);
//设置标签背景颜色 //设置标签背景颜色
void setTextBkColor(COLORREF color); void setTextBkColor(COLORREF color);
//设置标签文本 //设置标签文本
void setText(std::string text); void setText(std::string text);
}; };

View File

@@ -16,7 +16,7 @@
* @所属框架: 星垣(StellarX) GUI框架 * @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物 * @作者: 我在人间做废物
******************************************************************************/ ******************************************************************************/
#pragma once #pragma once
#include "Control.h" #include "Control.h"
#include "Button.h" #include "Button.h"
@@ -49,7 +49,6 @@
#define TABLE_STR_PAGE_MID "页/共" #define TABLE_STR_PAGE_MID "页/共"
#define TABLE_STR_PAGE_SUFFIX "页" #define TABLE_STR_PAGE_SUFFIX "页"
class Table :public Control class Table :public Control
{ {
private: private:
@@ -64,16 +63,17 @@ private:
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数 int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
int currentPage = 1; // 当前页码 int currentPage = 1; // 当前页码
int totalPages = 1; // 总页数 int totalPages = 1; // 总页数
bool isShowPageButton = true; // 是否显示翻页按钮 bool isShowPageButton = true; // 是否显示翻页按钮
bool isNeedDrawHeaders = true; // 是否需要绘制表头 bool isNeedDrawHeaders = true; // 是否需要绘制表头
bool isNeedCellSize = true; // 是否需要计算单元格尺寸 bool isNeedCellSize = true; // 是否需要计算单元格尺寸
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
Button* prevButton = nullptr; // 上一页按钮 Button* prevButton = nullptr; // 上一页按钮
Button* nextButton = nullptr; // 下一页按钮 Button* nextButton = nullptr; // 下一页按钮
Label* pageNum = nullptr; //页码文本 Label* pageNum = nullptr; //页码文本
int dX = x, dY = y; // 单元格的开始坐标 int dX = x, dY = y; // 单元格的开始坐标
int uX = x, uY = y; // 单元格的结束坐标 int uX = x, uY = y; // 单元格的结束坐标
@@ -98,7 +98,10 @@ private:
bool model() const override { return false; }; bool model() const override { return false; };
public: public:
StellarX::ControlText textStyle; // 文本样式 StellarX::ControlText textStyle; // 文本样式
void setX(int x) override;
void setY(int y) override;
void setWidth(int width) override;
void setHeight(int height) override;
public: public:
Table(int x, int y); Table(int x, int y);
~Table(); ~Table();
@@ -127,6 +130,14 @@ public:
void setTableLineStyle(StellarX::LineStyle style); void setTableLineStyle(StellarX::LineStyle style);
//设置边框宽度 //设置边框宽度
void setTableBorderWidth(int width); void setTableBorderWidth(int width);
//清空表头
void clearHeaders();
//清空表格数据
void clearData();
//清空表头和数据
void resetTable();
//窗口变化丢快照+标脏
void onWindowResize() override;
//************************** 获取属性 *****************************/ //************************** 获取属性 *****************************/
@@ -147,12 +158,12 @@ public:
//获取线型 //获取线型
StellarX::LineStyle getTableLineStyle() const; StellarX::LineStyle getTableLineStyle() const;
//获取表头 //获取表头
std::vector<std::string> getHeaders () const; std::vector<std::string> getHeaders() const;
//获取表格数据 //获取表格数据
std::vector<std::vector<std::string>> getData() const; std::vector<std::vector<std::string>> getData() const;
//获取表格边框宽度 //获取表格边框宽度
int getTableBorderWidth() const; int getTableBorderWidth() const;
//获取表格尺寸
int getTableWidth() const;
int getTableHeight() const;
}; };

View File

@@ -18,42 +18,39 @@
#pragma once #pragma once
#include "Control.h" #include "Control.h"
class TextBox : public Control class TextBox : public Control
{ {
std::string text; //文本 std::string text; //文本
StellarX::TextBoxmode mode; //模式 StellarX::TextBoxmode mode; //模式
StellarX::ControlShape shape; //形状 StellarX::ControlShape shape; //形状
bool click = false; //是否点击 bool click = false; //是否点击
size_t maxCharLen = 10;//最大字符长度 size_t maxCharLen = 10;//最大字符长度
COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色 COLORREF textBoxBkClor = RGB(255, 255, 255); //背景颜色
COLORREF textBoxBorderClor = RGB(0,0,0); //边框颜色 COLORREF textBoxBorderClor = RGB(0, 0, 0); //边框颜色
public: public:
StellarX::ControlText textStyle; //文本样式 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); 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 draw() override;
bool handleEvent(const ExMessage& msg) override; bool handleEvent(const ExMessage& msg) override;
//设置模式 //设置模式
void setMode(StellarX::TextBoxmode mode); void setMode(StellarX::TextBoxmode mode);
//设置可输入最大字符长度 //设置可输入最大字符长度
void setMaxCharLen(size_t len); void setMaxCharLen(size_t len);
//设置形状 //设置形状
void setTextBoxshape(StellarX::ControlShape shape); void setTextBoxshape(StellarX::ControlShape shape);
//设置边框颜色 //设置边框颜色
void setTextBoxBorder(COLORREF color); void setTextBoxBorder(COLORREF color);
//设置背景颜色 //设置背景颜色
void setTextBoxBk(COLORREF color); void setTextBoxBk(COLORREF color);
//设置文本 //设置文本
void setText(std::string text); void setText(std::string text);
//获取文本 //获取文本
std::string getText() const; std::string getText() const;
private: private:
//用来检查对话框是否模态,此控件不做实现 //用来检查对话框是否模态,此控件不做实现
bool model() const override { return false; }; bool model() const override { return false; };
}; };

View File

@@ -1,86 +1,108 @@
/**
* Window头文件
*
* 设计目标:
* - 提供一个基于 Win32 + EasyX 的“可拉伸且稳定不抖”的窗口容器。
* - 通过消息过程子类化WndProcThunk接管关键消息WM_SIZING/WM_SIZE/...)。
* - 将“几何变化记录pendingW/H”与“统一收口重绘needResizeDirty”解耦。
*
* 关键点(与 .cpp 中实现对应):
* - isSizing处于交互拉伸阶段时冻结重绘松手后统一重绘防止抖动。
* - WM_SIZING只做“最小尺寸夹紧”不回滚矩形、不做对齐把其余交给系统。
* - WM_GETMINMAXINFO按最小“客户区”换算到“窗口矩形”提供系统层最小轨迹值。
* - runEventLoop只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
*/
#pragma once #pragma once
#include"Control.h"
/******************************************************************************* #include "Control.h"
* @类: Window #include <string>
* @摘要: 应用程序主窗口类,管理窗口生命周期和消息循环 #include <vector>
* @描述: #include <memory>
* 创建和管理应用程序主窗口,作为所有控件的根容器。 #include <windows.h>
* 处理消息分发、事件循环和渲染调度。
*
* @特性:
* - 多种窗口模式配置(双缓冲、控制台等)
* - 背景图片和颜色支持
* - 集成的对话框管理系统
* - 完整的消息处理循环
* - 控件和对话框的生命周期管理
*
* @使用场景: 应用程序主窗口GUI程序的入口和核心
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
class Window class Window
{ {
int width; //窗口宽度 // —— 尺寸状态 ——(绘制尺寸与待应用尺寸分离;收口时一次性更新)
int height; //窗口高度 int width; // 当前有效宽(已应用到画布/控件的客户区宽)
int windowMode = NULL; //窗口模式 int height; // 当前有效高(已应用到画布/控件的客户区高)
int localwidth; // 基准宽(创建时的宽度)
int localheight; // 基准高(创建是的高度)
int pendingW; // 待应用宽WM_SIZE/拉伸中记录用)
int pendingH; // 待应用高
int minClientW; // 业务设定的最小客户区宽(用于 GETMINMAXINFO 与 SIZING 夹紧)
int minClientH; // 业务设定的最小客户区高
int windowMode = NULL; // EasyX 初始化模式EX_SHOWCONSOLE/EX_TOPMOST/...
bool needResizeDirty = false; // 统一收口重绘标志(置位后在事件环末尾处理)
bool isSizing = false; // 是否处于拖拽阶段ENTER/EXIT SIZEMOVE 切换)
// --- 尺寸变化去抖用 --- // —— 原生窗口句柄与子类化钩子 ——(子类化 EasyX 的窗口过程以拦截关键消息)
int pendingW; HWND hWnd = NULL; // EasyX 初始化后的窗口句柄
int pendingH; WNDPROC oldWndProc = nullptr; // 保存旧过程CallWindowProc 回落)
bool needResizeDirty = false; bool procHooked = false; // 避免重复子类化
static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l); // 静态过程分发到 this
HWND hWnd = NULL; //窗口句柄 // —— 绘制相关 ——(是否使用合成双缓冲、窗口标题、背景等)
std::string headline; //窗口标题 bool useComposited = true; // 是否启用 WS_EX_COMPOSITED部分机器可能增加一帧观感延迟
COLORREF wBkcolor = BLACK; //窗口背景 std::string headline; // 窗口标题文本
IMAGE* background = nullptr; //窗口背景图片 COLORREF wBkcolor = BLACK; // 纯色背景(无背景图时使用)
std::string bkImageFile; //窗口背景图片文件名 IMAGE* background = nullptr; // 背景图对象指针(存在时优先绘制)
std::vector<std::unique_ptr<Control>> controls; //控件管理 std::string bkImageFile; // 背景图文件路径loadimage 用)
std::vector<std::unique_ptr<Control>> dialogs; //对话框管理
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
std::vector<std::unique_ptr<Control>> controls;
std::vector<std::unique_ptr<Control>> dialogs;
public: public:
bool dialogClose = false; //是否有对话框关闭 bool dialogClose = false; // 项目内使用的状态位
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
Window(int width, int height, int mode); 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);
Window(int width, int height, int mode , COLORREF bkcloc, std::string headline = "窗口"); Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
~Window(); ~Window();
//绘制窗口
void draw(); // —— 绘制与事件循环 ——draw* 完成一次全量绘制runEventLoop 驱动事件与统一收口)
void draw(std::string pImgFile); void draw(); // 纯色背景版本
//事件循环 void draw(std::string pImgFile); // 背景图版本
int runEventLoop(); int runEventLoop(); // 主事件循环PeekMessage + 统一收口重绘)
//设置窗口背景图片
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
void setBkImage(std::string pImgFile); void setBkImage(std::string pImgFile);
//设置窗口背景颜色
void setBkcolor(COLORREF c); void setBkcolor(COLORREF c);
//设置窗口标题
void setHeadline(std::string headline); void setHeadline(std::string headline);
//添加控件
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
void addControl(std::unique_ptr<Control> control); void addControl(std::unique_ptr<Control> control);
//添加对话框 void addDialog(std::unique_ptr<Control> dialogs);
void addDialog(std::unique_ptr<Control> dialogs);
//检查是否已有对话框显示用于去重,防止工厂模式调用非模态对话框,多次打开污染对话框背景快照
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const; bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const;
//获取窗口句柄 // —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
HWND getHwnd() const; HWND getHwnd() const;
//获取窗口宽度 int getWidth() const;
int getWidth() const; int getHeight() const;
//获取窗口高度
int getHeight() const;
//获取窗口标题
std::string getHeadline() const; std::string getHeadline() const;
//获取窗口背景颜色 COLORREF getBkcolor() const;
COLORREF getBkcolor() const;
//获取窗口背景图片
IMAGE* getBkImage() const; IMAGE* getBkImage() const;
//获取窗口背景图片文件名
std::string getBkImageFile() const; std::string getBkImageFile() const;
//获取控件管理
std::vector<std::unique_ptr<Control>>& getControls(); std::vector<std::unique_ptr<Control>>& getControls();
// —— 配置开关 ——(动态调整最小客户区、合成双缓冲)
inline void setMinClientSize(int w, int h)
{
// 仅更新阈值;实际约束在 WM_GETMINMAXINFO/WM_SIZING 中生效
minClientW = w;
minClientH = h;
}
inline void setComposited(bool on)
{
// 更新标志;真正应用在 draw()/样式 SetWindowLongEx + SWP_FRAMECHANGED
useComposited = on;
}
void processWindowMessage(const ExMessage& msg); // 处理 EX_WINDOW 中的 WM_SIZE 等
void pumpResizeIfNeeded(); // 执行一次统一收口重绘
void scheduleResizeFromModal(int w, int h);
private:
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
}; };

View File

@@ -22,7 +22,7 @@ static inline int gbk_char_len(const std::string& s, size_t i)
{ {
unsigned char b = (unsigned char)s[i]; unsigned char b = (unsigned char)s[i];
if (b <= 0x7F) return 1; // ASCII if (b <= 0x7F) return 1; // ASCII
if (b >= 0x81 && b <= 0xFE && i + 1 < s.size()) if (b >= 0x81 && b <= 0xFE && i + 1 < s.size())
{ {
unsigned char b2 = (unsigned char)s[i + 1]; unsigned char b2 = (unsigned char)s[i + 1];
if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F) return 2; // 合法双字节 if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F) return 2; // 合法双字节
@@ -30,10 +30,10 @@ static inline int gbk_char_len(const std::string& s, size_t i)
return 1; // 容错 return 1; // 容错
} }
static inline void rtrim_spaces_gbk(std::string& s) static inline void rtrim_spaces_gbk(std::string& s)
{ {
while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格 while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格
while (s.size() >= 2) while (s.size() >= 2)
{ // 全角空格 A1 A1 { // 全角空格 A1 A1
unsigned char a = (unsigned char)s[s.size() - 2]; unsigned char a = (unsigned char)s[s.size() - 2];
unsigned char b = (unsigned char)s[s.size() - 1]; unsigned char b = (unsigned char)s[s.size() - 1];
@@ -42,26 +42,26 @@ static inline void rtrim_spaces_gbk(std::string& s)
} }
} }
static inline bool is_ascii_only(const std::string& s) static inline bool is_ascii_only(const std::string& s)
{ {
for (unsigned char c : s) if (c > 0x7F) return false; for (unsigned char c : s) if (c > 0x7F) return false;
return true; return true;
} }
static inline bool is_word_boundary_char(unsigned char c) static inline bool is_word_boundary_char(unsigned char c)
{ {
return c == ' ' || c == '-' || c == '_' || c == '/' || c == '\\' || c == '.' || c == ':'; return c == ' ' || c == '-' || c == '_' || c == '/' || c == '\\' || c == '.' || c == ':';
} }
// 英文优先策略:优先在“词边界”回退,再退化到逐字符;省略号为 "..." // 英文优先策略:优先在“词边界”回退,再退化到逐字符;省略号为 "..."
static std::string ellipsize_ascii_pref(const std::string& text, int maxW) static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
{ {
if (maxW <= 0) return ""; if (maxW <= 0) return "";
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text; if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
const std::string ell = "..."; const std::string ell = "...";
int ellW = textwidth(LPCTSTR(ell.c_str())); int ellW = textwidth(LPCTSTR(ell.c_str()));
if (ellW > maxW) if (ellW > maxW)
{ // 连 ... 都放不下 { // 连 ... 都放不下
std::string e = ell; std::string e = ell;
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back(); while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
@@ -71,7 +71,7 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
// 先找到能放下的最长前缀 // 先找到能放下的最长前缀
size_t i = 0, lastFit = 0; size_t i = 0, lastFit = 0;
while (i < text.size()) while (i < text.size())
{ {
int clen = gbk_char_len(text, i); int clen = gbk_char_len(text, i);
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen; size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
@@ -83,7 +83,7 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
// 在已适配前缀范围内,向左找最近的词边界 // 在已适配前缀范围内,向左找最近的词边界
size_t cutPos = lastFit; size_t cutPos = lastFit;
for (size_t k = lastFit; k > 0; --k) for (size_t k = lastFit; k > 0; --k)
{ {
unsigned char c = (unsigned char)text[k - 1]; unsigned char c = (unsigned char)text[k - 1];
if (c <= 0x7F && is_word_boundary_char(c)) { cutPos = k - 1; break; } if (c <= 0x7F && is_word_boundary_char(c)) { cutPos = k - 1; break; }
@@ -96,14 +96,14 @@ static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
} }
// 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…" // 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…"
static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "") static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "")
{ {
if (maxW <= 0) return ""; if (maxW <= 0) return "";
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text; if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
std::string ell = ellipsis ? ellipsis : ""; std::string ell = ellipsis ? ellipsis : "";
int ellW = textwidth(LPCTSTR(ell.c_str())); int ellW = textwidth(LPCTSTR(ell.c_str()));
if (ellW > maxW) if (ellW > maxW)
{ // 连省略号都放不下 { // 连省略号都放不下
std::string e = ell; std::string e = ell;
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back(); while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
@@ -112,7 +112,7 @@ static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const c
const int limit = maxW - ellW; const int limit = maxW - ellW;
size_t i = 0, lastFit = 0; size_t i = 0, lastFit = 0;
while (i < text.size()) while (i < text.size())
{ {
int clen = gbk_char_len(text, i); int clen = gbk_char_len(text, i);
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen; size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
@@ -163,11 +163,10 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式 tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
} }
Button::~Button() Button::~Button()
{ {
if (buttonFileIMAGE) if (buttonFileIMAGE)
delete buttonFileIMAGE; delete buttonFileIMAGE;
buttonFileIMAGE = nullptr; buttonFileIMAGE = nullptr;
} }
@@ -224,7 +223,7 @@ void Button::draw()
//设置按钮填充模式 //设置按钮填充模式
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE); setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height); saveBackground(this->x, this->y, (this->width + bordWith), (this->height + bordHeight));
// 恢复背景(清除旧内容) // 恢复背景(清除旧内容)
restBackground(); restBackground();
//根据按钮形状绘制 //根据按钮形状绘制
@@ -274,7 +273,6 @@ void Button::draw()
restoreStyle();//恢复默认字体样式和颜色 restoreStyle();//恢复默认字体样式和颜色
dirty = false; //标记按钮不需要重绘 dirty = false; //标记按钮不需要重绘
} }
// 处理鼠标事件,检测点击和悬停状态 // 处理鼠标事件,检测点击和悬停状态
// 根据按钮模式和形状进行不同的处理 // 根据按钮模式和形状进行不同的处理
@@ -314,7 +312,6 @@ bool Button::handleEvent(const ExMessage& msg)
// 处理鼠标点击事件 // 处理鼠标点击事件
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED) if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
{ {
if (mode == StellarX::ButtonMode::NORMAL) if (mode == StellarX::ButtonMode::NORMAL)
{ {
click = true; click = true;
@@ -327,7 +324,7 @@ bool Button::handleEvent(const ExMessage& msg)
} }
} }
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。 // NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。 // TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED) else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
{ {
hideTooltip(); // 隐藏悬停提示 hideTooltip(); // 隐藏悬停提示
@@ -349,7 +346,7 @@ bool Button::handleEvent(const ExMessage& msg)
dirty = true; dirty = true;
consume = true; consume = true;
refreshTooltipTextForState(); refreshTooltipTextForState();
hideTooltip(); hideTooltip();
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理 // 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY); flushmessage(EX_MOUSE | EX_KEY);
} }
@@ -367,23 +364,23 @@ bool Button::handleEvent(const ExMessage& msg)
dirty = true; dirty = true;
} }
if (tipEnabled) if (tipEnabled)
{ {
if (hover && !oldHover) if (hover && !oldHover)
{ {
// 刚刚进入悬停:开计时,暂不显示 // 刚刚进入悬停:开计时,暂不显示
tipHoverTick = GetTickCount64(); tipHoverTick = GetTickCount64();
tipVisible = false; tipVisible = false;
} }
if (!hover && oldHover) if (!hover && oldHover)
{ {
// 刚移出:立即隐藏 // 刚移出:立即隐藏
hideTooltip(); hideTooltip();
} }
if (hover && !tipVisible) if (hover && !tipVisible)
{ {
// 到点就显示 // 到点就显示
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs) if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
{ {
tipVisible = true; tipVisible = true;
@@ -420,18 +417,18 @@ bool Button::handleEvent(const ExMessage& msg)
if (tipEnabled && tipVisible) if (tipEnabled && tipVisible)
tipLabel.draw(); tipLabel.draw();
return consume; return consume;
} }
void Button::setOnClickListener(const std::function<void()>&& callback) void Button::setOnClickListener(const std::function<void()>&& callback)
{ {
this->onClickCallback = callback; this->onClickCallback = callback;
} }
void Button::setOnToggleOnListener(const std::function<void()>&& callback) void Button::setOnToggleOnListener(const std::function<void()>&& callback)
{ {
this->onToggleOnCallback = callback; this->onToggleOnCallback = callback;
} }
void Button::setOnToggleOffListener(const std::function<void()>&& callback) void Button::setOnToggleOffListener(const std::function<void()>&& callback)
{ {
@@ -443,37 +440,36 @@ void Button::setbuttonMode(StellarX::ButtonMode mode)
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED) if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
textStyle.bStrikeOut = false; textStyle.bStrikeOut = false;
//取值范围参考 buttMode的枚举注释 //取值范围参考 buttMode的枚举注释
this->mode = mode; this->mode = mode;
dirty = true; // 标记需要重绘 dirty = true; // 标记需要重绘
} }
void Button::setROUND_RECTANGLEwidth(int width) void Button::setROUND_RECTANGLEwidth(int width)
{ {
rouRectangleSize.ROUND_RECTANGLEwidth = width; rouRectangleSize.ROUND_RECTANGLEwidth = width;
this->dirty = true; // 标记需要重绘 this->dirty = true; // 标记需要重绘
} }
void Button::setROUND_RECTANGLEheight(int height) void Button::setROUND_RECTANGLEheight(int height)
{ {
rouRectangleSize.ROUND_RECTANGLEheight = height; rouRectangleSize.ROUND_RECTANGLEheight = height;
this->dirty = true; // 标记需要重绘 this->dirty = true; // 标记需要重绘
} }
bool Button::isClicked() const bool Button::isClicked() const
{ {
return this->click; return this->click;
} }
void Button::setFillMode(StellarX::FillMode mode) void Button::setFillMode(StellarX::FillMode mode)
{ {
this->buttonFillMode = mode; this->buttonFillMode = mode;
this->dirty = true; // 标记需要重绘 this->dirty = true; // 标记需要重绘
} }
void Button::setFillIma(StellarX::FillStyle ima) void Button::setFillIma(StellarX::FillStyle ima)
{ {
buttonFillIma = ima; buttonFillIma = ima;
this->dirty = true; this->dirty = true;
} }
@@ -484,16 +480,15 @@ void Button::setFillIma(std::string imaNAme)
delete buttonFileIMAGE; delete buttonFileIMAGE;
buttonFileIMAGE = nullptr; buttonFileIMAGE = nullptr;
} }
buttonFileIMAGE = new IMAGE; buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(),width,height); loadimage(buttonFileIMAGE, imaNAme.c_str(), width, height);
this->dirty = true; this->dirty = true;
} }
void Button::setButtonBorder(COLORREF Border) void Button::setButtonBorder(COLORREF Border)
{ {
buttonBorderColor = Border; buttonBorderColor = Border;
this->dirty = true; this->dirty = true;
} }
void Button::setButtonFalseColor(COLORREF color) void Button::setButtonFalseColor(COLORREF color)
@@ -504,7 +499,7 @@ void Button::setButtonFalseColor(COLORREF color)
void Button::setButtonText(const char* text) void Button::setButtonText(const char* text)
{ {
this->text = std::string(text); this->text = std::string(text);
this->text_width = textwidth(LPCTSTR(this->text.c_str())); this->text_width = textwidth(LPCTSTR(this->text.c_str()));
this->text_height = textheight(LPCTSTR(this->text.c_str())); this->text_height = textheight(LPCTSTR(this->text.c_str()));
this->dirty = true; this->dirty = true;
@@ -526,7 +521,7 @@ void Button::setButtonText(std::string text)
void Button::setButtonShape(StellarX::ControlShape shape) void Button::setButtonShape(StellarX::ControlShape shape)
{ {
this->shape = shape; this->shape = shape;
this->dirty = true; this->dirty = true;
this->needCutText = true; this->needCutText = true;
} }
@@ -535,7 +530,7 @@ void Button::setButtonShape(StellarX::ControlShape shape)
void Button::setButtonClick(BOOL click) void Button::setButtonClick(BOOL click)
{ {
this->click = click; this->click = click;
if (mode == StellarX::ButtonMode::NORMAL && click) if (mode == StellarX::ButtonMode::NORMAL && click)
{ {
if (onClickCallback) onClickCallback(); if (onClickCallback) onClickCallback();
@@ -550,7 +545,7 @@ void Button::setButtonClick(BOOL click)
else if (!click && onToggleOffCallback) onToggleOffCallback(); else if (!click && onToggleOffCallback) onToggleOffCallback();
dirty = true; dirty = true;
refreshTooltipTextForState(); refreshTooltipTextForState();
hideTooltip(); hideTooltip();
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理 // 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
flushmessage(EX_MOUSE | EX_KEY); flushmessage(EX_MOUSE | EX_KEY);
} }
@@ -558,55 +553,54 @@ void Button::setButtonClick(BOOL click)
requestRepaint(parent); requestRepaint(parent);
} }
std::string Button::getButtonText() const std::string Button::getButtonText() const
{ {
return this->text; return this->text;
} }
const char* Button::getButtonText_c() const const char* Button::getButtonText_c() const
{ {
return this->text.c_str(); return this->text.c_str();
} }
StellarX::ButtonMode Button::getButtonMode() const StellarX::ButtonMode Button::getButtonMode() const
{ {
return this->mode; return this->mode;
} }
StellarX::ControlShape Button::getButtonShape() const StellarX::ControlShape Button::getButtonShape() const
{ {
return this->shape; return this->shape;
} }
StellarX::FillMode Button::getFillMode() const StellarX::FillMode Button::getFillMode() const
{ {
return this->buttonFillMode; return this->buttonFillMode;
} }
StellarX::FillStyle Button::getFillIma() const StellarX::FillStyle Button::getFillIma() const
{ {
return this->buttonFillIma; return this->buttonFillIma;
} }
IMAGE* Button::getFillImaImage() const IMAGE* Button::getFillImaImage() const
{ {
return this->buttonFileIMAGE; return this->buttonFileIMAGE;
} }
COLORREF Button::getButtonBorder() const COLORREF Button::getButtonBorder() const
{ {
return this->buttonBorderColor; return this->buttonBorderColor;
} }
COLORREF Button::getButtonTextColor() const COLORREF Button::getButtonTextColor() const
{ {
return this->textStyle.color; return this->textStyle.color;
} }
StellarX::ControlText Button::getButtonTextStyle() const StellarX::ControlText Button::getButtonTextStyle() const
{ {
return this->textStyle; return this->textStyle;
} }
int Button::getButtonWidth() const int Button::getButtonWidth() const
@@ -619,15 +613,13 @@ int Button::getButtonHeight() const
return this->height; return this->height;
} }
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius) bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
{ {
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2)); double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
if (dis <= radius) if (dis <= radius)
return true; return true;
else else
return false; return false;
} }
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height) bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
@@ -636,15 +628,15 @@ bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, i
int centerY = (y + height) / 2; int centerY = (y + height) / 2;
int majorAxis = (width - x) / 2; int majorAxis = (width - x) / 2;
int minorAxis = (height - y) / 2; int minorAxis = (height - y) / 2;
double dx = mouseX - centerX; double dx = mouseX - centerX;
double dy = mouseY - centerY; double dy = mouseY - centerY;
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis); double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
// 判断鼠标是否在椭圆内 // 判断鼠标是否在椭圆内
if (normalizedDistance <= 1.0) if (normalizedDistance <= 1.0)
return true; return true;
else else
return false; return false;
} }
void Button::cutButtonText() void Button::cutButtonText()
@@ -659,18 +651,16 @@ void Button::cutButtonText()
} }
// 放不下按语言偏好裁切ASCII→词边界CJK→逐字符不撕裂双字节 // 放不下按语言偏好裁切ASCII→词边界CJK→逐字符不撕裂双字节
if (is_ascii_only(this->text)) if (is_ascii_only(this->text))
{ {
cutText = ellipsize_ascii_pref(this->text, contentW); // "..." cutText = ellipsize_ascii_pref(this->text, contentW); // "..."
} }
else else
{ {
cutText = ellipsize_cjk_pref(this->text, contentW, ""); // 全角省略号 cutText = ellipsize_cjk_pref(this->text, contentW, ""); // 全角省略号
} }
isUseCutText = true; isUseCutText = true;
needCutText = false; needCutText = false;
} }
void Button::hideTooltip() void Button::hideTooltip()
@@ -686,11 +676,8 @@ void Button::hideTooltip()
void Button::refreshTooltipTextForState() void Button::refreshTooltipTextForState()
{ {
if (tipUserOverride) return; // 用户显式设置过 tipText保持不变 if (tipUserOverride) return; // 用户显式设置过 tipText保持不变
if(mode==StellarX::ButtonMode::NORMAL) if (mode == StellarX::ButtonMode::NORMAL)
tipLabel.setText(tipTextClick); tipLabel.setText(tipTextClick);
else if(mode==StellarX::ButtonMode::TOGGLE) else if (mode == StellarX::ButtonMode::TOGGLE)
tipLabel.setText(click ? tipTextOn : tipTextOff); tipLabel.setText(click ? tipTextOn : tipTextOff);
} }

View File

@@ -7,36 +7,81 @@ Canvas::Canvas()
} }
Canvas::Canvas(int x, int y, int width, int height) Canvas::Canvas(int x, int y, int width, int height)
:Control(x, y, width, height) :Control(x, y, width, height)
{ {
this->id = "Canvas"; this->id = "Canvas";
} }
void Canvas::setX(int x)
{
this->x = x;
for (auto& c : controls)
{
c->onWindowResize();
c->setX(c->getLocalX() + this->x);
}
dirty = true;
}
void Canvas::setY(int y)
{
this->y = y;
for (auto& c : controls)
{
c->onWindowResize();
c->setY(c->getLocalY() + this->y);
}
dirty = true;
}
void Canvas::clearAllControls() void Canvas::clearAllControls()
{ {
controls.clear(); controls.clear();
} }
void Canvas::draw() void Canvas::draw()
{ {
if (!dirty||!show)return; if (!dirty || !show)
{
for (auto& control : controls)
if (auto c = dynamic_cast<Table*>(control.get()))
c->draw();
return;
}
saveStyle(); saveStyle();
setlinecolor(canvasBorderClor);//设置线色 setlinecolor(canvasBorderClor);//设置线色
setfillcolor(canvasBkClor);//设置填充色 if (StellarX::FillMode::Null != canvasFillMode)
setfillcolor(canvasBkClor);//设置填充色
setfillstyle((int)canvasFillMode);//设置填充模式 setfillstyle((int)canvasFillMode);//设置填充模式
setlinestyle((int)canvasLineStyle, canvaslinewidth); setlinestyle((int)canvasLineStyle, canvaslinewidth);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) // 在绘制画布之前,先恢复并更新背景快照:
saveBackground(x, y, width, height); // 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
// 恢复背景(清除旧内容) // 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
int margin = canvaslinewidth > 1 ? canvaslinewidth : 1;
if (hasSnap)
{
// 恢复旧快照,清除上一次绘制
restBackground();
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
if (!saveBkImage || saveBkX != this->x - margin || saveBkY != this->y - margin || saveWidth != this->width + margin * 2 || saveHeight != this->height + margin * 2)
{
discardBackground();
saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
}
}
else
{
// 首次绘制或没有快照时直接抓取背景
saveBackground(this->x - margin, this->y - margin, this->width + margin * 2, this->height + margin * 2);
}
// 再次恢复最新快照,确保绘制区域干净
restBackground(); restBackground();
//根据画布形状绘制 //根据画布形状绘制
switch (shape) switch (shape)
{ {
case StellarX::ControlShape::RECTANGLE: case StellarX::ControlShape::RECTANGLE:
fillrectangle(x,y,x+width,y+height);//有边框填充矩形 fillrectangle(x, y, x + width, y + height);//有边框填充矩形
break; break;
case StellarX::ControlShape::B_RECTANGLE: case StellarX::ControlShape::B_RECTANGLE:
solidrectangle(x, y, x + width, y + height);//无边框填充矩形 solidrectangle(x, y, x + width, y + height);//无边框填充矩形
@@ -54,7 +99,7 @@ void Canvas::draw()
control->setDirty(true); control->setDirty(true);
control->draw(); control->draw();
} }
restoreStyle(); restoreStyle();
dirty = false; //标记画布不需要重绘 dirty = false; //标记画布不需要重绘
} }
@@ -76,6 +121,9 @@ bool Canvas::handleEvent(const ExMessage& msg)
void Canvas::addControl(std::unique_ptr<Control> control) void Canvas::addControl(std::unique_ptr<Control> control)
{ {
//坐标转化
control->setX(control->getLocalX() + this->x);
control->setY(control->getLocalY() + this->y);
control->setParent(this); control->setParent(this);
controls.push_back(std::move(control)); controls.push_back(std::move(control));
dirty = true; dirty = true;
@@ -116,7 +164,7 @@ void Canvas::setBorderColor(COLORREF color)
void Canvas::setCanvasBkColor(COLORREF color) void Canvas::setCanvasBkColor(COLORREF color)
{ {
this->canvasBkClor = color; this->canvasBkClor = color;
dirty = true; dirty = true;
} }
@@ -126,7 +174,6 @@ void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
dirty = true; dirty = true;
} }
void Canvas::setLinewidth(int width) void Canvas::setLinewidth(int width)
{ {
this->canvaslinewidth = width; this->canvaslinewidth = width;
@@ -149,15 +196,179 @@ void Canvas::setIsVisible(bool visible)
void Canvas::setDirty(bool dirty) void Canvas::setDirty(bool dirty)
{ {
this->dirty = dirty; this->dirty = dirty;
for(auto& control : controls) for (auto& control : controls)
control->setDirty(dirty); control->setDirty(dirty);
} }
void Canvas::onWindowResize() void Canvas::onWindowResize()
{ {
Control::onWindowResize(); // 先处理自 // 先处理自身的快照等逻辑
for (auto& ch : controls) // 再转发给所有子控件 Control::onWindowResize();
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
int origParentW = this->localWidth;
int origParentH = this->localHeight;
// 当前容器的新尺寸
int finalW = this->width;
int finalH = this->height;
// 当前容器的新坐标(全局坐标)
int parentX = this->x;
int parentY = this->y;
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
for (auto& ch : controls)
{
// Only adjust when using anchor-to-edges layout
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
{
// Determine whether this child is a Table; tables keep their height constant
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
// Unpack anchors
auto a1 = ch->getAnchor_1();
auto a2 = ch->getAnchor_2();
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom);
// If it's a table, treat as anchored left and right horizontally and anchored top vertically by default.
if (isTable)
{
anchorLeft = true;
anchorRight = true;
// If no explicit vertical anchor was provided, default to top.
if (!(anchorTop || anchorBottom))
{
anchorTop = true;
}
}
// Compute new X and width
int newX = ch->getX();
int newWidth = ch->getWidth();
if (anchorLeft && anchorRight)
{
// Scale horizontally relative to parent's size.
if (origParentW > 0)
{
// Maintain proportional position and size based on original local values.
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
}
else
{
// Fallback: keep original
newX = parentX + ch->getLocalX();
newWidth = ch->getLocalWidth();
}
}
else if (anchorLeft && !anchorRight)
{
// Only left anchored: keep original width and left margin.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
else if (!anchorLeft && anchorRight)
{
// Only right anchored: keep original width and right margin.
newWidth = ch->getLocalWidth();
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
newX = parentX + finalW - origRightDist - newWidth;
}
else
{
// No horizontal anchor: position relative to parent's left and width unchanged.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
ch->setX(newX);
ch->setWidth(newWidth);
// Compute new Y and height
int newY = ch->getY();
int newHeight = ch->getHeight();
if (isTable)
{
// Table: Height remains constant; adjust Y based on anchors.
newHeight = ch->getLocalHeight();
if (anchorTop && anchorBottom)
{
// If both top and bottom anchored, scale Y but keep height.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: default to top
newY = parentY + ch->getLocalY();
}
}
else
{
if (anchorTop && anchorBottom)
{
// Scale vertically relative to parent's size.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
newHeight = ch->getLocalHeight();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only: keep height constant
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only: keep height and adjust Y relative to bottom
newHeight = ch->getLocalHeight();
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: position relative to parent's top, height constant.
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
}
ch->setY(newY);
ch->setHeight(newHeight);
}
// Always forward the window resize event to the child (recursively).
ch->onWindowResize(); ch->onWindowResize();
}
} }
void Canvas::requestRepaint(Control* parent) void Canvas::requestRepaint(Control* parent)
@@ -166,14 +377,8 @@ void Canvas::requestRepaint(Control* parent)
{ {
for (auto& control : controls) for (auto& control : controls)
if (control->isDirty() && control->IsVisible()) if (control->isDirty() && control->IsVisible())
{
control->draw(); control->draw();
break;
}
} }
else else
onRequestRepaintAsRoot(); onRequestRepaintAsRoot();
} }

View File

@@ -44,9 +44,14 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
} }
void Control::setIsVisible(bool show) void Control::setIsVisible(bool show)
{ {
this->show = show;
dirty = true;
if (!show) if (!show)
this->updateBackground(); this->updateBackground();
this->show = show; else
requestRepaint(parent);
} }
void Control::onWindowResize() void Control::onWindowResize()
{ {
@@ -54,6 +59,27 @@ void Control::onWindowResize()
discardBackground(); discardBackground();
setDirty(true); setDirty(true);
} }
void Control::setLayoutMode(StellarX::LayoutMode layoutMode_)
{
this->layoutMode = layoutMode_;
}
void Control::setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2)
{
this->anchor_1 = anchor_1;
this->anchor_2 = anchor_2;
}
StellarX::Anchor Control::getAnchor_1() const
{
return this->anchor_1;
}
StellarX::Anchor Control::getAnchor_2() const
{
return this->anchor_2;
}
StellarX::LayoutMode Control::getLayoutMode() const
{
return this->layoutMode;
}
// 保存当前的绘图状态(字体、颜色、线型等) // 保存当前的绘图状态(字体、颜色、线型等)
// 在控件绘制前调用,确保不会影响全局绘图状态 // 在控件绘制前调用,确保不会影响全局绘图状态
void Control::saveStyle() void Control::saveStyle()
@@ -123,6 +149,7 @@ void Control::discardBackground()
{ {
if (saveBkImage) if (saveBkImage)
{ {
restBackground();
delete saveBkImage; delete saveBkImage;
saveBkImage = nullptr; saveBkImage = nullptr;
} }

View File

@@ -1,21 +1,19 @@
#include "Dialog.h" #include "Dialog.h"
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal) 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) : Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
{ {
this->id = "Dialog"; this->id = "Dialog";
show = false; show = false;
} }
Dialog::~Dialog() Dialog::~Dialog()
{ {
} }
void Dialog::draw() void Dialog::draw()
{ {
if(!show) if (!show)
{ {
// 如果对话框不可见且需要清理,执行清理 // 如果对话框不可见且需要清理,执行清理
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
@@ -24,16 +22,16 @@ void Dialog::draw()
} }
return; return;
} }
// 如果需要初始化,则执行初始化 // 如果需要初始化,则执行初始化
if (needsInitialization && show) if (needsInitialization && show)
{ {
initDialogSize(); initDialogSize();
needsInitialization = false; needsInitialization = false;
} }
if (dirty && show) if (dirty && show)
{ {
// 保存当前绘图状态 // 保存当前绘图状态
saveStyle(); saveStyle();
Canvas::setBorderColor(this->borderColor); Canvas::setBorderColor(this->borderColor);
@@ -41,12 +39,6 @@ void Dialog::draw()
Canvas::setCanvasBkColor(this->backgroundColor); Canvas::setCanvasBkColor(this->backgroundColor);
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE); Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
//设置所有控件为脏状态
/*for(auto& c :this->controls)
c->setDirty(true);*/
restBackground();
Canvas::draw(); Canvas::draw();
//绘制消息文本 //绘制消息文本
@@ -56,25 +48,22 @@ void Dialog::draw()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标 int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
for (auto line:lines) for (auto& line : lines)
{ {
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标 int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
outtextxy(tx, ty, LPCTSTR(line.c_str())); outtextxy(tx, ty, LPCTSTR(line.c_str()));
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距 ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
} }
// 恢复绘图状态 // 恢复绘图状态
restoreStyle(); restoreStyle();
dirty = false; dirty = false;
} }
} }
bool Dialog::handleEvent(const ExMessage& msg) bool Dialog::handleEvent(const ExMessage& msg)
{ {
bool consume = false; bool consume = false;
@@ -86,7 +75,7 @@ bool Dialog::handleEvent(const ExMessage& msg)
} }
return false; return false;
} }
// 如果正在清理或标记为待清理,则不处理事件 // 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning) if (pendingCleanup || isCleaning)
return false; return false;
@@ -100,8 +89,8 @@ bool Dialog::handleEvent(const ExMessage& msg)
} }
// 将事件传递给子控件处理 // 将事件传递给子控件处理
if (!consume) if (!consume)
consume = Canvas::handleEvent(msg); consume = Canvas::handleEvent(msg);
// 每次事件处理后检查是否需要执行延迟清理 // 每次事件处理后检查是否需要执行延迟清理
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
@@ -140,7 +129,6 @@ void Dialog::SetModal(bool modal)
this->modal = modal; this->modal = modal;
} }
void Dialog::SetResult(StellarX::MessageBoxResult result) void Dialog::SetResult(StellarX::MessageBoxResult result)
{ {
this->result = result; this->result = result;
@@ -168,35 +156,73 @@ void Dialog::Show()
shouldClose = false; shouldClose = false;
if (modal) if (modal)
{ {
// 模态对话框需要阻塞当前线程直到对话框关闭 // 模态对话框需要阻塞当前线程直到对话框关闭
while (show && !close) if (modal)
{ {
// 记录当前窗口客户区尺寸,供轮询对比
// 处理消息 RECT rc0;
ExMessage msg; GetClientRect(hWnd.getHwnd(), &rc0);
if (peekmessage(&msg, EX_MOUSE | EX_KEY)) int lastW = rc0.right - rc0.left;
{ int lastH = rc0.bottom - rc0.top;
handleEvent(msg);
// 检查是否需要关闭 while (show && !close)
if (shouldClose) {
// ① 轮询窗口尺寸(不依赖 WM_SIZE
RECT rc;
GetClientRect(hWnd.getHwnd(), &rc);
const int cw = rc.right - rc.left;
const int ch = rc.bottom - rc.top;
if (cw != lastW || ch != lastH)
{ {
Close(); lastW = cw;
break; lastH = ch;
// 通知父窗口:有新尺寸 → 标记 needResizeDirty
hWnd.scheduleResizeFromModal(cw, ch);
// 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态)
hWnd.pumpResizeIfNeeded();
// 这只模态在新尺寸下重建布局 / 重抓背景 → 本帧要画自己
setInitialization(true);
setDirty(true);
} }
// ② 处理这只对话框的鼠标/键盘(沿用你原来 EX_MOUSE | EX_KEY
ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
{
handleEvent(msg);
if (shouldClose)
{
Close();
break;
}
}
// ③ 最后一笔:只画这只模态,保证永远在最上层
if (dirty)
{
BeginBatchDraw();
this->draw(); // 注意:不要 requestRepaint(parent),只画自己
EndBatchDraw();
dirty = false;
}
Sleep(10);
} }
// 重绘 if (pendingCleanup && !isCleaning)
if (dirty) performDelayedCleanup();
{
requestRepaint(parent);
FlushBatchDraw();
}
// 避免CPU占用过高
Sleep(10);
} }
else
{
// 非模态仍由主循环托管
dirty = true;
}
// 模态对话框关闭后执行清理 // 模态对话框关闭后执行清理
if (pendingCleanup && !isCleaning) if (pendingCleanup && !isCleaning)
performDelayedCleanup(); performDelayedCleanup();
@@ -206,8 +232,6 @@ void Dialog::Show()
dirty = true; dirty = true;
} }
void Dialog::Close() void Dialog::Close()
{ {
if (!show) return; if (!show) return;
@@ -216,13 +240,10 @@ void Dialog::Close()
close = true; close = true;
dirty = true; dirty = true;
pendingCleanup = true; // 只标记需要清理,不立即执行 pendingCleanup = true; // 只标记需要清理,不立即执行
// 工厂模式下非模态触发回调 返回结果 // 工厂模式下非模态触发回调 返回结果
if (resultCallback&& !modal) if (resultCallback && !modal)
resultCallback(this->result); resultCallback(this->result);
} }
void Dialog::setInitialization(bool init) void Dialog::setInitialization(bool init)
@@ -231,10 +252,10 @@ void Dialog::setInitialization(bool init)
{ {
initDialogSize(); initDialogSize();
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth)); saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
this->dirty = true;
} }
} }
void Dialog::initButtons() void Dialog::initButtons()
{ {
controls.clear(); controls.clear();
@@ -246,48 +267,48 @@ void Dialog::initButtons()
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定" "确定"
); );
okbutton->setOnClickListener([this]() okbutton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::OK); this->SetResult(StellarX::MessageBoxResult::OK);
this->Close(); }); this->Close(); });
okbutton->textStyle = this->textStyle; okbutton->textStyle = this->textStyle;
this->addControl(std::move(okbutton)); this->addControl(std::move(okbutton));
} }
break; break;
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮 case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
{ {
auto okButton = createDialogButton( auto okButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2), (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定" "确定"
); );
okButton->setOnClickListener([this]() okButton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::OK); this->SetResult(StellarX::MessageBoxResult::OK);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin), (okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
okButton.get()->getY(), okButton.get()->getY(),
"取消" "取消"
); );
cancelButton->setOnClickListener([this]() cancelButton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::Cancel); this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
okButton->textStyle = this->textStyle; okButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle; cancelButton->textStyle = this->textStyle;
this->addControl(std::move(okButton)); this->addControl(std::move(okButton));
this->addControl(std::move(cancelButton)); this->addControl(std::move(cancelButton));
} }
break; break;
case StellarX::MessageBoxType::YesNo: // 是和否按钮 case StellarX::MessageBoxType::YesNo: // 是和否按钮
{ {
auto yesButton = createDialogButton( auto yesButton = createDialogButton(
@@ -299,7 +320,7 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Yes); this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
auto noButton = createDialogButton( auto noButton = createDialogButton(
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin), (yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
@@ -310,7 +331,7 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::No); this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
yesButton->textStyle = this->textStyle; yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle; noButton->textStyle = this->textStyle;
@@ -318,7 +339,7 @@ void Dialog::initButtons()
this->addControl(std::move(yesButton)); this->addControl(std::move(yesButton));
this->addControl(std::move(noButton)); this->addControl(std::move(noButton));
} }
break; break;
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮 case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
{ {
auto yesButton = createDialogButton( auto yesButton = createDialogButton(
@@ -330,18 +351,18 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Yes); this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
auto noButton = createDialogButton( auto noButton = createDialogButton(
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin, yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
yesButton.get()->getY(), yesButton.get()->getY(),
"" ""
); );
noButton->setOnClickListener([this]() noButton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::No); this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin, noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
@@ -352,18 +373,17 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Cancel); this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
yesButton->textStyle = this->textStyle; yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle; noButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle; cancelButton->textStyle = this->textStyle;
this->addControl(std::move(yesButton)); this->addControl(std::move(yesButton));
this->addControl(std::move(noButton)); this->addControl(std::move(noButton));
this->addControl(std::move(cancelButton)); this->addControl(std::move(cancelButton));
} }
break; break;
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮 case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
{ {
auto retryButton = createDialogButton( auto retryButton = createDialogButton(
@@ -375,18 +395,18 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Retry); this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
auto cancelButton = createDialogButton( auto cancelButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin, retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
retryButton.get()->getY(), retryButton.get()->getY(),
"取消" "取消"
); );
cancelButton->setOnClickListener([this]() cancelButton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::Cancel); this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
retryButton->textStyle = this->textStyle; retryButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle; cancelButton->textStyle = this->textStyle;
@@ -394,11 +414,11 @@ void Dialog::initButtons()
this->addControl(std::move(retryButton)); this->addControl(std::move(retryButton));
this->addControl(std::move(cancelButton)); this->addControl(std::move(cancelButton));
} }
break; break;
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮 case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
{ {
auto abortButton = createDialogButton( auto abortButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2), (this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2), ((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"中止" "中止"
); );
@@ -406,18 +426,18 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Abort); this->SetResult(StellarX::MessageBoxResult::Abort);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); this->Close();
}); });
auto retryButton = createDialogButton( auto retryButton = createDialogButton(
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin, abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
abortButton.get()->getY(), abortButton.get()->getY(),
"重试" "重试"
); );
retryButton->setOnClickListener([this]() retryButton->setOnClickListener([this]()
{ {
this->SetResult(StellarX::MessageBoxResult::Retry); this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); this->Close();
}); });
auto ignoreButton = createDialogButton( auto ignoreButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin, retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
@@ -428,7 +448,7 @@ void Dialog::initButtons()
{ {
this->SetResult(StellarX::MessageBoxResult::Ignore); this->SetResult(StellarX::MessageBoxResult::Ignore);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); this->Close();
}); });
abortButton->textStyle = this->textStyle; abortButton->textStyle = this->textStyle;
@@ -439,7 +459,7 @@ void Dialog::initButtons()
this->addControl(std::move(retryButton)); this->addControl(std::move(retryButton));
this->addControl(std::move(ignoreButton)); this->addControl(std::move(ignoreButton));
} }
break; break;
} }
} }
@@ -448,7 +468,7 @@ void Dialog::initCloseButton()
//初始化关闭按钮 //初始化关闭按钮
auto but = std::make_unique<Button> auto but = std::make_unique<Button>
( (
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight, (this->x + this->width - closeButtonWidth) - 3, (this->y + 3), closeButtonWidth - 1, closeButtonHeight,
"X", // 按钮文本 "X", // 按钮文本
RGB(255, 0, 0), // 按钮被点击颜色 RGB(255, 0, 0), // 按钮被点击颜色
this->canvasBkClor, // 按钮背景颜色 this->canvasBkClor, // 按钮背景颜色
@@ -462,14 +482,14 @@ void Dialog::initCloseButton()
this->SetResult(StellarX::MessageBoxResult::Cancel); this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true; this->hWnd.dialogClose = true;
this->Close(); }); this->Close(); });
this->closeButton = but.get(); this->closeButton = but.get();
this->addControl(std::move(but)); this->addControl(std::move(but));
} }
void Dialog::initTitle() void Dialog::initTitle()
{ {
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color); this->title = std::make_unique<Label>(this->x + 5, this->y + 5, titleText, textStyle.color);
title->setTextdisap(true); title->setTextdisap(true);
title->textStyle = this->textStyle; title->textStyle = this->textStyle;
@@ -483,13 +503,13 @@ void Dialog::splitMessageLines()
std::string currentLine; std::string currentLine;
for (size_t i = 0; i < message.length(); i++) { for (size_t i = 0; i < message.length(); i++) {
// 处理 换行符 \r\n \n \r // 处理 换行符 \r\n \n \r
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n')) if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n') || (message[i] == '\r' && message[i + 1] == '\n'))
{ {
if (!currentLine.empty()) { if (!currentLine.empty()) {
lines.push_back(currentLine); lines.push_back(currentLine);
currentLine.clear(); currentLine.clear();
} }
if (message[i] == '\r' && message[i + 1] == '\n') if (message[i] == '\r' && message[i + 1] == '\n')
i++; i++;
continue; continue;
@@ -499,13 +519,13 @@ void Dialog::splitMessageLines()
} }
// 添加最后一行(如果有内容) // 添加最后一行(如果有内容)
if (!currentLine.empty()) if (!currentLine.empty())
{ {
lines.push_back(currentLine); lines.push_back(currentLine);
} }
// 如果消息为空,至少添加一个空行 // 如果消息为空,至少添加一个空行
if (lines.empty()) if (lines.empty())
{ {
lines.push_back(""); lines.push_back("");
} }
@@ -517,7 +537,7 @@ void Dialog::getTextSize()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
for (auto text : lines) for (auto& text : lines)
{ {
int w = textwidth(LPCTSTR(text.c_str())); int w = textwidth(LPCTSTR(text.c_str()));
int h = textheight(LPCTSTR(text.c_str())); int h = textheight(LPCTSTR(text.c_str()));
@@ -554,10 +574,10 @@ void Dialog::initDialogSize()
// 计算按钮区域宽度 // 计算按钮区域宽度
int buttonAreaWidth = buttonNum * functionButtonWidth + int buttonAreaWidth = buttonNum * functionButtonWidth +
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0); (buttonNum > 0 ? (buttonNum + 1) * buttonMargin : 0);
// 计算文本区域宽度(包括边距) // 计算文本区域宽度(包括边距)
int textAreaWidth = textWidth + textToBorderMargin * 2 ; int textAreaWidth = textWidth + textToBorderMargin * 2;
// 对话框宽度取两者中的较大值,并确保最小宽度 // 对话框宽度取两者中的较大值,并确保最小宽度
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth; this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
@@ -565,7 +585,7 @@ void Dialog::initDialogSize()
// 计算对话框高度 // 计算对话框高度
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距 // 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距 int textAreaHeight = textHeight * (int)lines.size() + 5 * ((int)lines.size() - 1); // 文本行高+行间距
this->height = closeButtonHeight + // 标题栏高度 this->height = closeButtonHeight + // 标题栏高度
titleToTextMargin + // 标题到文本的间距 titleToTextMargin + // 标题到文本的间距
textAreaHeight + // 文本区域高度 textAreaHeight + // 文本区域高度
@@ -583,30 +603,11 @@ void Dialog::initDialogSize()
initCloseButton(); // 初始化关闭按钮 initCloseButton(); // 初始化关闭按钮
} }
void Dialog::saveBackground(int x, int y, int w, int h) void Dialog::addControl(std::unique_ptr<Control> control)
{ {
if (w <= 0 || h <= 0) return; control->setParent(this);
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h; controls.push_back(std::move(control));
if (saveBkImage) dirty = true;
{
//尺寸变了才重建,避免反复 new/delete
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
{
delete saveBkImage; saveBkImage = nullptr;
}
}
if (!saveBkImage) saveBkImage = new IMAGE(w + BorderWidth*2, h + BorderWidth*2);
SetWorkingImage(nullptr); // ★抓屏幕
getimage(saveBkImage, x - BorderWidth, y - BorderWidth, w + BorderWidth*2, h+ BorderWidth*2);
hasSnap = true;
}
void Dialog::restBackground()
{
if (!hasSnap || !saveBkImage) return;
// 直接回贴屏幕(与抓取一致)
SetWorkingImage(nullptr);
putimage(saveBkX - BorderWidth, saveBkY - BorderWidth,saveBkImage);
} }
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、 // 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
@@ -621,7 +622,7 @@ void Dialog::performDelayedCleanup()
auto& c = hWnd.getControls(); auto& c = hWnd.getControls();
for (auto& control : c) for (auto& control : c)
control->setDirty(true); control->setDirty(true);
controls.clear(); controls.clear();
// 重置指针 // 重置指针
@@ -634,6 +635,23 @@ void Dialog::performDelayedCleanup()
FlushBatchDraw(); FlushBatchDraw();
discardBackground(); discardBackground();
} }
if (!(saveBkImage && hasSnap))
{
// 没有背景快照:强制一次完整重绘,立即擦掉残影
hWnd.pumpResizeIfNeeded(); // 如果正好有尺寸标志,顺便统一收口
// 即使没有尺寸变化,也重绘一帧
BeginBatchDraw();
// 背景
if (hWnd.getBkImage() && !hWnd.getBkImageFile().empty())
putimage(0, 0, hWnd.getBkImage());
else { setbkcolor(hWnd.getBkcolor()); cleardevice(); }
// 所有普通控件
for (auto& c : hWnd.getControls()) c->draw();
// 其他对话框this 已经 show=false会早退不绘
// 注意:此处若有容器管理,需要按你的现状遍历 dialogs 再 draw
EndBatchDraw();
FlushBatchDraw();
}
// 重置状态 // 重置状态
needsInitialization = true; needsInitialization = true;
pendingCleanup = false; pendingCleanup = false;
@@ -675,7 +693,7 @@ std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::stri
StellarX::ButtonMode::NORMAL, StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::RECTANGLE StellarX::ControlShape::RECTANGLE
); );
return btn; return btn;
} }
@@ -684,13 +702,9 @@ void Dialog::requestRepaint(Control* parent)
if (this == parent) if (this == parent)
{ {
for (auto& control : controls) for (auto& control : controls)
if (control->isDirty()&&control->IsVisible()) if (control->isDirty() && control->IsVisible())
{
control->draw(); control->draw();
break;
}
} }
else else
onRequestRepaintAsRoot(); onRequestRepaintAsRoot();
} }

View File

@@ -2,7 +2,7 @@
namespace StellarX namespace StellarX
{ {
MessageBoxResult MessageBox::showModal(Window& wnd,const std::string& text,const std::string& caption, MessageBoxResult MessageBox::showModal(Window& wnd, const std::string& text, const std::string& caption,
MessageBoxType type) MessageBoxType type)
{ {
Dialog dlg(wnd, caption, text, type, true); // 模态 Dialog dlg(wnd, caption, text, type, true); // 模态
@@ -11,13 +11,13 @@ namespace StellarX
return dlg.GetResult(); return dlg.GetResult();
} }
void MessageBox::showAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type, void MessageBox::showAsync(Window& wnd, const std::string& text, const std::string& caption, MessageBoxType type,
std::function<void(MessageBoxResult)> onResult) std::function<void(MessageBoxResult)> onResult)
{ {
//去重,如果窗口内已有相同的对话框被触发,则不再创建 //去重,如果窗口内已有相同的对话框被触发,则不再创建
if (wnd.hasNonModalDialogWithCaption(caption, text)) if (wnd.hasNonModalDialogWithCaption(caption, text))
{ {
std::cout << "\a" << std::endl; std::cout << "\a" << std::endl;
return; return;
} }
auto dlg = std::make_unique<Dialog>(wnd, caption, text, auto dlg = std::make_unique<Dialog>(wnd, caption, text,
@@ -31,4 +31,4 @@ namespace StellarX
wnd.addDialog(std::move(dlg)); wnd.addDialog(std::move(dlg));
dlgPtr->Show(); dlgPtr->Show();
} }
} }

View File

@@ -33,7 +33,7 @@ inline void TabControl::initTabBar()
for (auto& c : controls) for (auto& c : controls)
{ {
c.first->setX(this->x + i * butW); c.first->setX(this->x + i * butW);
c.first->setY(this->y+this->height - tabBarHeight); c.first->setY(this->y + this->height - tabBarHeight);
i++; i++;
} }
break; break;
@@ -41,18 +41,18 @@ inline void TabControl::initTabBar()
for (auto& c : controls) for (auto& c : controls)
{ {
c.first->setX(this->x); c.first->setX(this->x);
c.first->setY(this->y+i* butH); c.first->setY(this->y + i * butH);
i++; i++;
} }
break; break;
case StellarX::TabPlacement::Right: case StellarX::TabPlacement::Right:
for (auto& c : controls) for (auto& c : controls)
{ {
c.first->setX(this->x+this->width - tabBarHeight); c.first->setX(this->x + this->width - tabBarHeight);
c.first->setY(this->y + i * butH); c.first->setY(this->y + i * butH);
i++; i++;
} }
break; break;
default: default:
break; break;
} }
@@ -147,7 +147,7 @@ inline void TabControl::initTabPage()
} }
} }
TabControl::TabControl():Canvas() TabControl::TabControl() :Canvas()
{ {
this->id = "TabControl"; this->id = "TabControl";
} }
@@ -162,13 +162,36 @@ TabControl::~TabControl()
{ {
} }
void TabControl::setX(int x)
{
this->x = x;
initTabBar();
initTabPage();
dirty = true;
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
}
void TabControl::setY(int y)
{
this->y = y;
initTabBar();
initTabPage();
dirty = true;
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
}
void TabControl::draw() void TabControl::draw()
{ {
if (!dirty || !show)return; if (!dirty || !show)return;
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) // 绘制画布背景和基本形状及其子画布控件
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容)
restBackground();
Canvas::draw(); Canvas::draw();
for (auto& c : controls) for (auto& c : controls)
{ {
@@ -176,13 +199,21 @@ void TabControl::draw()
c.first->draw(); c.first->draw();
} }
for (auto& c : controls) for (auto& c : controls)
if(c.second->IsVisible()) {
{ c.second->setDirty(true);
c.second->setDirty(true); c.second->draw();
c.second->draw(); }
}
dirty = false;
// 首次绘制时处理默认激活页签
if (IsFirstDraw)
{
if (defaultActivation >= 0 && defaultActivation < (int)controls.size())
controls[defaultActivation].first->setButtonClick(true);
else if (defaultActivation >= (int)controls.size())//索引越界则激活最后一个
controls[controls.size() - 1].first->setButtonClick(true);
IsFirstDraw = false;//避免重复处理
}
dirty = false;
} }
bool TabControl::handleEvent(const ExMessage& msg) bool TabControl::handleEvent(const ExMessage& msg)
@@ -196,7 +227,7 @@ bool TabControl::handleEvent(const ExMessage& msg)
break; break;
} }
for (auto& c : controls) for (auto& c : controls)
if(c.second->IsVisible()) if (c.second->IsVisible())
if (c.second->handleEvent(msg)) if (c.second->handleEvent(msg))
{ {
consume = true; consume = true;
@@ -216,7 +247,8 @@ void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>
controls[idx].first->setParent(this); controls[idx].first->setParent(this);
controls[idx].first->enableTooltip(true); controls[idx].first->enableTooltip(true);
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE); controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
controls[idx].first->setOnToggleOnListener([this,idx]()
controls[idx].first->setOnToggleOnListener([this, idx]()
{ {
controls[idx].second->setIsVisible(true); controls[idx].second->setIsVisible(true);
controls[idx].second->onWindowResize(); controls[idx].second->onWindowResize();
@@ -230,10 +262,10 @@ void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>
} }
dirty = true; dirty = true;
}); });
controls[idx].first->setOnToggleOffListener([this,idx]() controls[idx].first->setOnToggleOffListener([this, idx]()
{ {
controls[idx].second->setIsVisible(false); controls[idx].second->setIsVisible(false);
dirty = true; dirty = true;
}); });
controls[idx].second->setParent(this); controls[idx].second->setParent(this);
@@ -249,12 +281,11 @@ void TabControl::add(std::string tabText, std::unique_ptr<Control> control)
if (tab.first->getButtonText() == tabText) if (tab.first->getButtonText() == tabText)
{ {
control->setParent(tab.second.get()); control->setParent(tab.second.get());
control->setIsVisible( tab.second->IsVisible()); control->setIsVisible(tab.second->IsVisible());
tab.second->addControl(std::move(control)); tab.second->addControl(std::move(control));
break; break;
} }
} }
} }
void TabControl::setTabPlacement(StellarX::TabPlacement placement) void TabControl::setTabPlacement(StellarX::TabPlacement placement)
@@ -263,7 +294,6 @@ void TabControl::setTabPlacement(StellarX::TabPlacement placement)
setDirty(true); setDirty(true);
initTabBar(); initTabBar();
initTabPage(); initTabPage();
} }
void TabControl::setTabBarHeight(int height) void TabControl::setTabBarHeight(int height)
@@ -277,26 +307,42 @@ void TabControl::setTabBarHeight(int height)
void TabControl::setIsVisible(bool visible) void TabControl::setIsVisible(bool visible)
{ {
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑 // 先让基类 Canvas 处理自己的回贴/丢快照逻辑
Canvas::setIsVisible(visible); // <--- 新增 Canvas::setIsVisible(visible);
this->show = visible;
for (auto& tab : controls) for (auto& tab : controls)
{ {
tab.first->setIsVisible(visible); if(true == visible)
//页也要跟着关/开,否则它们会保留旧的 saveBkImage {
tab.second->setIsVisible(visible); tab.first->setIsVisible(visible);
tab.second->setDirty(true); //页也要跟着关/开,否则它们会保留旧的 saveBkImage
if (tab.first->isClicked())
tab.second->setIsVisible(true);
else
tab.second->setIsVisible(false);
tab.second->setDirty(true);
}
else
{
tab.first->setIsVisible(visible);
tab.second->setIsVisible(visible);
}
} }
} }
void TabControl::onWindowResize() void TabControl::onWindowResize()
{ {
// 调用基类的窗口变化处理,丢弃快照并标记脏
Control::onWindowResize(); Control::onWindowResize();
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
initTabBar();
initTabPage();
// 转发窗口尺寸变化给所有页签按钮和页面
for (auto& c : controls) for (auto& c : controls)
{ {
c.first->onWindowResize(); c.first->onWindowResize();
c.second->onWindowResize(); c.second->onWindowResize();
} }
// 尺寸变化后需要重绘自身
dirty = true;
} }
int TabControl::getActiveIndex() const int TabControl::getActiveIndex() const
@@ -313,20 +359,13 @@ int TabControl::getActiveIndex() const
void TabControl::setActiveIndex(int idx) void TabControl::setActiveIndex(int idx)
{ {
if (idx < 0 || idx > controls.size() - 1) return; if (IsFirstDraw)
if (controls[idx].first->getButtonMode() == StellarX::ButtonMode::DISABLED)return; defaultActivation = idx;
if (controls[idx].first->isClicked())
{
if (controls[idx].second->IsVisible())
return;
else
controls[idx].second->setIsVisible(true);
}
else else
{ {
controls[idx].first->setButtonClick(true); if (idx >= 0 && idx < controls.size())
controls[idx].first->setButtonClick(true);
} }
} }
int TabControl::count() const int TabControl::count() const
@@ -337,13 +376,13 @@ int TabControl::count() const
int TabControl::indexOf(const std::string& tabText) const int TabControl::indexOf(const std::string& tabText) const
{ {
int idx = -1; int idx = -1;
for(auto& c : controls) for (auto& c : controls)
{ {
idx++; idx++;
if (c.first->getButtonText() == tabText) if (c.first->getButtonText() == tabText)
return idx; return idx;
} }
return idx; return idx;
} }
@@ -364,18 +403,12 @@ void TabControl::requestRepaint(Control* parent)
for (auto& control : controls) for (auto& control : controls)
{ {
if (control.first->isDirty() && control.first->IsVisible()) if (control.first->isDirty() && control.first->IsVisible())
{
control.first->draw(); control.first->draw();
break; else if (control.second->isDirty() && control.second->IsVisible())
}
else if (control.second->isDirty()&&control.second->IsVisible())
{
control.second->draw(); control.second->draw();
break;
}
} }
} }
else else
onRequestRepaintAsRoot(); onRequestRepaintAsRoot();
} }

View File

@@ -5,7 +5,7 @@ Label::Label()
{ {
this->id = "Label"; this->id = "Label";
this->text = "默认标签"; this->text = "默认标签";
textStyle.color = RGB(0,0,0); textStyle.color = RGB(0, 0, 0);
textBkColor = RGB(255, 255, 255);; //默认白色背景 textBkColor = RGB(255, 255, 255);; //默认白色背景
} }
@@ -40,7 +40,7 @@ void Label::draw()
this->height = textheight(text.c_str()); this->height = textheight(text.c_str());
} }
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(this->x, this->y,this->width,this->height); saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容) // 恢复背景(清除旧内容)
restBackground(); restBackground();
outtextxy(x, y, LPCTSTR(text.c_str())); outtextxy(x, y, LPCTSTR(text.c_str()));
@@ -71,5 +71,4 @@ void Label::setText(std::string text)
{ {
this->text = text; this->text = text;
this->dirty = true; this->dirty = true;
}
}

View File

@@ -17,7 +17,7 @@ void Table::drawTable()
{ {
for (size_t j = 0; j < data[i].size(); ++j) for (size_t j = 0; j < data[i].size(); ++j)
{ {
uX = dX + colWidths.at(j) + TABLE_COL_GAP; uX = dX + colWidths.at(j) + TABLE_COL_GAP;
fillrectangle(dX, dY, uX, uY); fillrectangle(dX, dY, uX, uY);
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(data[i][j].c_str())); outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(data[i][j].c_str()));
dX += colWidths.at(j) + TABLE_COL_GAP; dX += colWidths.at(j) + TABLE_COL_GAP;
@@ -26,12 +26,10 @@ void Table::drawTable()
dY = uY; dY = uY;
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA; uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
} }
} }
void Table::drawHeader() void Table::drawHeader()
{ {
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0; const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
// 内容区原点 = x+border, y+border // 内容区原点 = x+border, y+border
dX = x + border; dX = x + border;
@@ -61,9 +59,9 @@ void Table::initTextWaH()
lineHeights.assign(headers.size(), 0); lineHeights.assign(headers.size(), 0);
// 先看数据 // 先看数据
for (size_t i = 0; i < data.size(); ++i) for (size_t i = 0; i < data.size(); ++i)
{ {
for (size_t j = 0; j < data[i].size(); ++j) for (size_t j = 0; j < data[i].size(); ++j)
{ {
const int w = textwidth(LPCTSTR(data[i][j].c_str())); const int w = textwidth(LPCTSTR(data[i][j].c_str()));
const int h = textheight(LPCTSTR(data[i][j].c_str())); const int h = textheight(LPCTSTR(data[i][j].c_str()));
@@ -72,7 +70,7 @@ void Table::initTextWaH()
} }
} }
// 再用表头更新(谁大取谁) // 再用表头更新(谁大取谁)
for (size_t j = 0; j < headers.size(); ++j) for (size_t j = 0; j < headers.size(); ++j)
{ {
const int w = textwidth(LPCTSTR(headers[j].c_str())); const int w = textwidth(LPCTSTR(headers[j].c_str()));
const int h = textheight(LPCTSTR(headers[j].c_str())); const int h = textheight(LPCTSTR(headers[j].c_str()));
@@ -82,16 +80,20 @@ void Table::initTextWaH()
// 用“所有列的最大行高”作为一行的基准高度 // 用“所有列的最大行高”作为一行的基准高度
int maxLineH = 0; int maxLineH = 0;
for (int h : lineHeights) for (int h : lineHeights)
if (h > maxLineH) if (h > maxLineH)
maxLineH = h; maxLineH = h;
// 列的像素宽 = 内容宽 + 左右 padding // 列宽包含左右 padding在计算完最大文本宽度后加上 2*padX 作为单元格内边距
for (size_t j = 0; j < colWidths.size(); ++j) {
colWidths[j] += 2 * padX;
}
// 表内容总宽 = Σ(列宽 + 列间距) // 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0; int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j) for (size_t j = 0; j < colWidths.size(); ++j)
contentW += (colWidths[j] + 2 * padX) + colGap; contentW += colWidths[j] + colGap;
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding // 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY; const int headerH = maxLineH + 2 * padY;
@@ -109,6 +111,9 @@ void Table::initTextWaH()
// 最终表宽/高:内容 + 对称边框 // 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1); this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1); this->height = headerH + rowsH + footerH + (border << 1);
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
this->localWidth = this->width;
this->localHeight = this->height;
} }
void Table::initButton() void Table::initButton()
@@ -124,7 +129,6 @@ void Table::initButton()
int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2; int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2;
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2; int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
int btnH = lblH + padV * 2; int btnH = lblH + padV * 2;
// 基于“页码标签”的矩形来摆放: // 基于“页码标签”的矩形来摆放:
// prev 在页码左侧 gap 处next 在右侧 gap 处Y 对齐 pY // prev 在页码左侧 gap 处next 在右侧 gap 处Y 对齐 pY
@@ -153,24 +157,25 @@ void Table::initButton()
prevButton->setFillMode(tableFillMode); prevButton->setFillMode(tableFillMode);
nextButton->setFillMode(tableFillMode); nextButton->setFillMode(tableFillMode);
prevButton->setOnClickListener([this]() prevButton->setOnClickListener([this]()
{ {
if (currentPage > 1) if (currentPage > 1)
{ {
--currentPage; --currentPage;
dirty = true; dirty = true;
if (pageNum) pageNum->setDirty(true); if (pageNum) pageNum->setDirty(true);
} }
}); });
nextButton->setOnClickListener([this]() nextButton->setOnClickListener([this]()
{ {
if (currentPage < totalPages) if (currentPage < totalPages)
{ {
++currentPage; ++currentPage;
dirty = true; dirty = true;
if (pageNum) pageNum->setDirty(true); if (pageNum) pageNum->setDirty(true);
} }
}); });
isNeedButtonAndPageNum = false;
} }
void Table::initPageNum() void Table::initPageNum()
@@ -191,11 +196,11 @@ void Table::initPageNum()
// 按理来说 x + (this->width - textW) / 2;就可以 // 按理来说 x + (this->width - textW) / 2;就可以
// 但是在绘制时发现控件偏右因此减去40 // 但是在绘制时发现控件偏右因此减去40
int textW = textwidth(LPCTSTR(pageNumtext.c_str())); int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
pX = x + TABLE_PAGE_TEXT_OFFSET_X +(this->width - textW) / 2; pX = x + TABLE_PAGE_TEXT_OFFSET_X + (this->width - textW) / 2;
if (!pageNum) if (!pageNum)
pageNum = new Label(pX, pY, pageNumtext); pageNum = new Label(pX, pY, pageNumtext);
else else
{ {
pageNum->setX(pX); pageNum->setX(pX);
pageNum->setY(pY); pageNum->setY(pY);
@@ -208,25 +213,23 @@ void Table::initPageNum()
void Table::drawPageNum() void Table::drawPageNum()
{ {
pageNumtext = ""; pageNumtext = "";
pageNumtext+= std::to_string(currentPage); pageNumtext += std::to_string(currentPage);
pageNumtext += "页/共"; pageNumtext += "页/共";
pageNumtext += std::to_string(totalPages); pageNumtext += std::to_string(totalPages);
pageNumtext += ""; pageNumtext += "";
if (nullptr == pageNum) if (nullptr == pageNum || isNeedButtonAndPageNum)
initPageNum(); initPageNum();
pageNum->setText(pageNumtext); pageNum->setText(pageNumtext);
pageNum->textStyle = this->textStyle; pageNum->textStyle = this->textStyle;
if (StellarX::FillMode::Null == tableFillMode) if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true); pageNum->setTextdisap(true);
pageNum->draw(); pageNum->draw();
} }
void Table::drawButton() void Table::drawButton()
{ {
if (nullptr == prevButton || nullptr == nextButton) if ((nullptr == prevButton || nullptr == nextButton) || isNeedButtonAndPageNum)
initButton(); initButton();
this->prevButton->textStyle = this->textStyle; this->prevButton->textStyle = this->textStyle;
@@ -239,7 +242,59 @@ void Table::drawButton()
this->nextButton->setDirty(true); this->nextButton->setDirty(true);
prevButton->draw(); prevButton->draw();
nextButton->draw(); nextButton->draw();
}
void Table::setX(int x)
{
this->x = x;
isNeedButtonAndPageNum = true;
dirty = true;
}
void Table::setY(int y)
{
this->y = y;
isNeedButtonAndPageNum = true;
dirty = true;
}
void Table::setWidth(int width)
{
// 调整列宽以匹配新的表格总宽度。不修改 localWidth避免累计误差。
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
const int ncols = static_cast<int>(colWidths.size());
if (ncols <= 0) {
this->width = width;
isNeedButtonAndPageNum = true;
return;
}
int diff = width - this->width;
// 基础增量:整除部分
int baseChange = diff / ncols;
int remainder = diff % ncols;
for (int i = 0; i < ncols; ++i) {
int change = baseChange;
if (remainder > 0) {
change += 1;
remainder -= 1;
}
else if (remainder < 0) {
change -= 1;
remainder += 1;
}
int newWidth = colWidths[i] + change;
// 限制最小宽度为 1防止出现负值
if (newWidth < 1) newWidth = 1;
colWidths[i] = newWidth;
}
this->width = width;
// 需要重新布局页脚元素
isNeedButtonAndPageNum = true;
}
void Table::setHeight(int height)
{
//高度不变
} }
Table::Table(int x, int y) Table::Table(int x, int y)
@@ -317,18 +372,35 @@ void Table::draw()
setfillstyle((int)tableFillMode); setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT); setbkmode(TRANSPARENT);
} }
//确保在绘制任何表格内容之前捕获背景 // 在绘制前先恢复并更新背景快照:
// 临时恢复样式,确保捕获正确的背景 // 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
if ((!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height)||!saveBkImage) if (hasSnap)
{
// 始终先恢复旧背景,清除上一帧内容
restBackground();
// 当尺寸变化或缓存图像无效时,需要重新截图
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
{
discardBackground();
saveBackground(this->x, this->y, this->width, this->height);
}
}
else
{
// 首次绘制时无背景缓存,直接抓取
saveBackground(this->x, this->y, this->width, this->height); saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景(清除旧内容) }
// 恢复最新的背景,保证绘制区域干净
restBackground(); restBackground();
// 绘制表头 // 绘制表头
dX = x; //dX = x;
dY = y; //dY = y;
drawHeader(); if(isNeedDrawHeaders)
this->isNeedDrawHeaders = false; {
drawHeader();
this->isNeedDrawHeaders = false;
}
// 绘制当前页 // 绘制当前页
drawTable(); drawTable();
// 绘制页码标签 // 绘制页码标签
@@ -337,7 +409,6 @@ void Table::draw()
// 绘制翻页按钮 // 绘制翻页按钮
if (this->isShowPageButton) if (this->isShowPageButton)
drawButton(); drawButton();
// 恢复绘图状态 // 恢复绘图状态
restoreStyle(); restoreStyle();
@@ -347,14 +418,14 @@ void Table::draw()
bool Table::handleEvent(const ExMessage& msg) bool Table::handleEvent(const ExMessage& msg)
{ {
if(!show)return false; if (!show)return false;
bool consume = false; bool consume = false;
if(!this->isShowPageButton) if (!this->isShowPageButton)
return consume; return consume;
else else
{ {
if(prevButton)consume = prevButton->handleEvent(msg); if (prevButton)consume = prevButton->handleEvent(msg);
if (nextButton&&!consume) if (nextButton && !consume)
consume = nextButton->handleEvent(msg); consume = nextButton->handleEvent(msg);
} }
if (dirty) if (dirty)
@@ -365,14 +436,14 @@ bool Table::handleEvent(const ExMessage& msg)
void Table::setHeaders(std::initializer_list<std::string> headers) void Table::setHeaders(std::initializer_list<std::string> headers)
{ {
this->headers.clear(); this->headers.clear();
for (auto lis : headers) for (auto& lis : headers)
this->headers.push_back(lis); this->headers.push_back(lis);
isNeedCellSize = true; // 标记需要重新计算单元格尺寸 isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedDrawHeaders = true; // 标记需要重新绘制表头 isNeedDrawHeaders = true; // 标记需要重新绘制表头
dirty = true; dirty = true;
} }
void Table::setData( std::vector<std::string> data) void Table::setData(std::vector<std::string> data)
{ {
if (data.size() < headers.size()) if (data.size() < headers.size())
for (int i = 0; data.size() <= headers.size(); i++) for (int i = 0; data.size() <= headers.size(); i++)
@@ -385,18 +456,18 @@ void Table::setData( std::vector<std::string> data)
dirty = true; dirty = true;
} }
void Table::setData( std::initializer_list<std::vector<std::string>> data) void Table::setData(std::initializer_list<std::vector<std::string>> data)
{ {
for (auto lis : data) for (auto lis : data)
if (lis.size() < headers.size()) if (lis.size() < headers.size())
{ {
for (size_t i = lis.size(); i< headers.size(); i++) for (size_t i = lis.size(); i < headers.size(); i++)
lis.push_back(""); lis.push_back("");
this->data.push_back(lis); this->data.push_back(lis);
} }
else else
this->data.push_back(lis); this->data.push_back(lis);
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage; totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1) if (totalPages < 1)
totalPages = 1; totalPages = 1;
@@ -464,6 +535,42 @@ void Table::setTableBorderWidth(int width)
this->dirty = true; this->dirty = true;
} }
void Table::clearHeaders()
{
this->headers.clear();
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedDrawHeaders = true; // 标记需要重新绘制表头
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
dirty = true;
}
void Table::clearData()
{
this->data.clear();
this->currentPage = 1;
this->totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
dirty = true;
}
void Table::resetTable()
{
clearHeaders();
clearData();
}
void Table::onWindowResize()
{
Control::onWindowResize(); // 先处理自己
if (this->prevButton && this->nextButton && this->pageNum)
{
prevButton->onWindowResize();
nextButton->onWindowResize();
pageNum->onWindowResize();
}
}
int Table::getCurrentPage() const int Table::getCurrentPage() const
{ {
return this->currentPage; return this->currentPage;
@@ -519,4 +626,15 @@ int Table::getTableBorderWidth() const
return this->tableBorderWidth; return this->tableBorderWidth;
} }
int Table::getTableWidth() const
{
int temp = 0;
for (auto& w : colWidths)
temp += w;
return temp;
}
int Table::getTableHeight() const
{
return 0;
}

View File

@@ -1,161 +1,188 @@
// TextBox.cpp // TextBox.cpp
#include "TextBox.h" #include "TextBox.h"
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape) TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
:Control(x,y,width,height),text(text), mode(mode), shape(shape) :Control(x, y, width, height), text(text), mode(mode), shape(shape)
{ {
this->id = "TextBox"; this->id = "TextBox";
} }
void TextBox::draw() void TextBox::draw()
{ {
if (dirty && show) if (dirty && show)
{ {
saveStyle(); saveStyle();
setfillcolor(textBoxBkClor); setfillcolor(textBoxBkClor);
setlinecolor(textBoxBorderClor); setlinecolor(textBoxBorderClor);
if (textStyle.nHeight > height) if (textStyle.nHeight > height)
textStyle.nHeight = height; textStyle.nHeight = height;
if (textStyle.nWidth > width) if (textStyle.nWidth > width)
textStyle.nWidth = width; textStyle.nWidth = width;
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace, settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight, textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
settextcolor(textStyle.color);
setbkmode(TRANSPARENT);
int text_width = textwidth(LPCTSTR(text.c_str()));
int text_height = textheight(LPCTSTR(text.c_str()));
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage) settextcolor(textStyle.color);
saveBackground(this->x, this->y, this->width, this->height); setbkmode(TRANSPARENT);
// 恢复背景(清除旧内容)
restBackground(); int text_width = 0;
//根据形状绘制 int text_height = 0;
switch (shape) std::string pwdText;
{ if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
case StellarX::ControlShape::RECTANGLE: {
fillrectangle(x,y,x+width,y+height);//有边框填充矩形 for (size_t i = 0; i < text.size(); ++i)
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str())); pwdText += '*';
break; text_width = textwidth(LPCTSTR(pwdText.c_str()));
case StellarX::ControlShape::B_RECTANGLE: text_height = textheight(LPCTSTR(pwdText.c_str()));
solidrectangle(x, y, x + width, y + height);//无边框填充矩形 }
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str())); else
break; {
case StellarX::ControlShape::ROUND_RECTANGLE: text_width = textwidth(LPCTSTR(text.c_str()));
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形 text_height = textheight(LPCTSTR(text.c_str()));
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str())); }
break;
case StellarX::ControlShape::B_ROUND_RECTANGLE: if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形 saveBackground(this->x, this->y, this->width, this->height);
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str())); // 恢复背景(清除旧内容)
break; restBackground();
} //根据形状绘制
switch (shape)
{
case StellarX::ControlShape::RECTANGLE:
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break;
case StellarX::ControlShape::B_RECTANGLE:
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
: outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break;
case StellarX::ControlShape::ROUND_RECTANGLE:
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break;
case StellarX::ControlShape::B_ROUND_RECTANGLE:
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
StellarX::TextBoxmode::PASSWORD_MODE == mode ? outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(pwdText.c_str()))
:outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
break;
}
restoreStyle(); restoreStyle();
dirty = false; //标记不需要重绘 dirty = false; //标记不需要重绘
} }
} }
bool TextBox::handleEvent(const ExMessage& msg) bool TextBox::handleEvent(const ExMessage& msg)
{ {
bool hover = false; bool hover = false;
bool oldClick = click; bool oldClick = click;
bool consume = false; bool consume = false;
switch (shape) switch (shape)
{ {
case StellarX::ControlShape::RECTANGLE: case StellarX::ControlShape::RECTANGLE:
case StellarX::ControlShape::B_RECTANGLE: case StellarX::ControlShape::B_RECTANGLE:
case StellarX::ControlShape::ROUND_RECTANGLE: case StellarX::ControlShape::ROUND_RECTANGLE:
case StellarX::ControlShape::B_ROUND_RECTANGLE: case StellarX::ControlShape::B_ROUND_RECTANGLE:
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内 hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
consume = false; consume = false;
break; break;
} }
if (hover && msg.message == WM_LBUTTONUP) if (hover && msg.message == WM_LBUTTONUP)
{ {
click = true; click = true;
if(StellarX::TextBoxmode::INPUT_MODE == mode) if (StellarX::TextBoxmode::INPUT_MODE == mode)
{ {
dirty = InputBox(LPTSTR(text.c_str()), (int)maxCharLen, "输入框", NULL, text.c_str(), NULL, NULL, false); char* temp = new char[maxCharLen + 1];
dirty = InputBox(temp, (int)maxCharLen+1, "输入框", NULL, text.c_str(), NULL, NULL, false);
if (dirty)text = temp;
delete[] temp;
temp = nullptr;
consume = true; consume = true;
} }
else if (StellarX::TextBoxmode::READONLY_MODE == mode) else if (StellarX::TextBoxmode::READONLY_MODE == mode)
{ {
dirty = false; dirty = false;
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false); InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
consume = true; consume = true;
} }
flushmessage(EX_MOUSE | EX_KEY); else if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
} {
if (dirty) char* temp = new char[maxCharLen + 1];
requestRepaint(parent); dirty = InputBox(temp, (int)maxCharLen+1, "输入框\n不可见输入,覆盖即可", NULL, NULL, NULL, NULL, false);
if (dirty)text = temp;
delete[] temp;
temp = nullptr;
consume = true;
}
flushmessage(EX_MOUSE | EX_KEY);
}
if (dirty)
requestRepaint(parent);
if (click) if (click)
click = false; click = false;
return consume; return consume;
} }
void TextBox::setMode(StellarX::TextBoxmode mode) void TextBox::setMode(StellarX::TextBoxmode mode)
{ {
this->mode = mode; this->mode = mode;
this->dirty = true; this->dirty = true;
} }
void TextBox::setMaxCharLen(size_t len) void TextBox::setMaxCharLen(size_t len)
{ {
if (len > 0) if (len > 0)
maxCharLen = len; maxCharLen = len;
this->dirty = true; this->dirty = true;
} }
void TextBox::setTextBoxshape(StellarX::ControlShape shape) void TextBox::setTextBoxshape(StellarX::ControlShape shape)
{ {
switch (shape) switch (shape)
{ {
case StellarX::ControlShape::RECTANGLE: case StellarX::ControlShape::RECTANGLE:
case StellarX::ControlShape::B_RECTANGLE: case StellarX::ControlShape::B_RECTANGLE:
case StellarX::ControlShape::ROUND_RECTANGLE: case StellarX::ControlShape::ROUND_RECTANGLE:
case StellarX::ControlShape::B_ROUND_RECTANGLE: case StellarX::ControlShape::B_ROUND_RECTANGLE:
this->shape = shape; this->shape = shape;
this->dirty = true; this->dirty = true;
break; break;
case StellarX::ControlShape::CIRCLE: case StellarX::ControlShape::CIRCLE:
case StellarX::ControlShape::B_CIRCLE: case StellarX::ControlShape::B_CIRCLE:
case StellarX::ControlShape::ELLIPSE: case StellarX::ControlShape::ELLIPSE:
case StellarX::ControlShape::B_ELLIPSE: case StellarX::ControlShape::B_ELLIPSE:
this->shape = StellarX::ControlShape::RECTANGLE; this->shape = StellarX::ControlShape::RECTANGLE;
this->dirty = true; this->dirty = true;
break; break;
} }
} }
void TextBox::setTextBoxBorder(COLORREF color) void TextBox::setTextBoxBorder(COLORREF color)
{ {
textBoxBorderClor = color; textBoxBorderClor = color;
this->dirty = true; this->dirty = true;
} }
void TextBox::setTextBoxBk(COLORREF color) void TextBox::setTextBoxBk(COLORREF color)
{ {
textBoxBkClor = color; textBoxBkClor = color;
this->dirty = true; this->dirty = true;
} }
void TextBox::setText(std::string text) void TextBox::setText(std::string text)
{ {
if(text.size() > maxCharLen) if (text.size() > maxCharLen)
text = text.substr(0, maxCharLen); text = text.substr(0, maxCharLen);
this->text = text; this->text = text;
this->dirty = true; this->dirty = true;
draw(); draw();
} }
std::string TextBox::getText() const std::string TextBox::getText() const
{ {
return this->text; return this->text;
} }

View File

@@ -1,114 +1,348 @@
#include "Window.h" #include "Window.h"
#include "Dialog.h" #include "Dialog.h"
#include <windows.h> // 确保包含 Windows API 头文件
Window::Window(int width, int height, int mode) #include <easyx.h>
#include <algorithm>
/**
* ApplyResizableStyle
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED合成双缓冲
* 关键点:
* - WS_THICKFRAME允许从四边/四角拖动改变尺寸。
* - WS_CLIPCHILDREN / WS_CLIPSIBLINGS避免子控件互相覆盖时闪烁。
* - WS_EX_COMPOSITED在一些环境更平滑但个别显卡/驱动可能带来一帧延迟感。
* - SWP_FRAMECHANGED通知窗口样式已变更强制系统重算非客户区标题栏/边框)。
*/
static void ApplyResizableStyle(HWND h, bool useComposited)
{ {
LONG style = GetWindowLong(h, GWL_STYLE);
this->pendingW = this->width = width; style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
this->pendingH = this->height = height; SetWindowLong(h, GWL_STYLE, style);
this->windowMode = mode;
LONG ex = GetWindowLong(h, GWL_EXSTYLE);
if (useComposited)
{
ex |= WS_EX_COMPOSITED;
}
else
{
ex &= ~WS_EX_COMPOSITED;
}
SetWindowLong(h, GWL_EXSTYLE, ex);
SetWindowPos(h, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
} }
Window::Window(int width, int height, int mode, COLORREF bkcloc) /**
* ApplyMinSizeOnSizing
* 作用:在 WM_SIZING 阶段执行“最小尺寸夹紧”。
* 规则:只回推“被拖动的那一侧”,另一侧当锚点(避免几何回弹/位置漂移)。
* 步骤:
* 1将“最小客户区尺寸”通过 AdjustWindowRectEx 换算为“最小窗口矩形”(含非客户区)。
* 2若当前矩形比最小还小则根据 edge哪条边/角在被拖)调整对应边,另一侧保持不动。
* 说明:仅保证不小于最小值;不做对齐/回滚等操作,把其余交给系统尺寸逻辑。
*/
static void ApplyMinSizeOnSizing(RECT* prc, WPARAM edge, HWND hWnd, int minClientW, int minClientH)
{ {
this->pendingW = this->width = width; RECT rcFrame{ 0, 0, minClientW, minClientH };
this->pendingH = this->height = height; DWORD style = GetWindowLong(hWnd, GWL_STYLE);
this->windowMode = mode; DWORD ex = GetWindowLong(hWnd, GWL_EXSTYLE);
this->wBkcolor = bkcloc; AdjustWindowRectEx(&rcFrame, style, FALSE, ex);
const int minW = rcFrame.right - rcFrame.left;
const int minH = rcFrame.bottom - rcFrame.top;
const int curW = prc->right - prc->left;
const int curH = prc->bottom - prc->top;
if (curW < minW)
{
switch (edge)
{
case WMSZ_LEFT:
case WMSZ_TOPLEFT:
case WMSZ_BOTTOMLEFT:
prc->left = prc->right - minW; // 锚定右侧,回推左侧(左边被拖)
break;
default:
prc->right = prc->left + minW; // 锚定左侧,回推右侧(右边被拖)
break;
}
}
if (curH < minH)
{
switch (edge)
{
case WMSZ_TOP:
case WMSZ_TOPLEFT:
case WMSZ_TOPRIGHT:
prc->top = prc->bottom - minH; // 锚定下侧,回推上侧(上边被拖)
break;
default:
prc->bottom = prc->top + minH; // 锚定上侧,回推下侧(下边被拖)
break;
}
}
} }
Window::Window(int width, int height, int mode, COLORREF bkcloc, std::string headline) // ---------------- 构造 / 析构 ----------------
/**
* 构造:初始化当前尺寸、待应用尺寸、最小客户区尺寸与 EasyX 模式。
* 注意:样式设置与子类化放在 draw() 内第一次绘制时完成。
*/
Window::Window(int w, int h, int mode)
{ {
this->pendingW = this->width = width; localwidth = minClientW = pendingW = width = w;
this->pendingH = this->height = height; localheight = minClientH = pendingH = height = h;
this->windowMode = mode; windowMode = mode;
this->wBkcolor = bkcloc; }
this->headline = headline;
Window::Window(int w, int h, int mode, COLORREF bk)
{
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
wBkcolor = bk;
}
Window::Window(int w, int h, int mode, COLORREF bk, std::string title)
{
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
wBkcolor = bk;
headline = std::move(title);
} }
Window::~Window() Window::~Window()
{ {
if (background) // 析构:释放背景图对象并关闭 EasyX 图形环境
delete background; if (background) delete background;
background = nullptr; background = nullptr;
closegraph(); // 确保关闭图形上下文 closegraph();
} }
// ---------------- 原生消息钩子----------------
void Window::draw() { /**
// 使用 EasyX 创建基本窗口 * WndProcThun
if (!hWnd) * 作用:替换 EasyX 的窗口过程,接管关键消息。
hWnd = initgraph(width, height, windowMode); * 关键处理:
// **启用窗口拉伸支持**:添加厚边框和最大化按钮样式 * - WM_ERASEBKGND返回 1交由自绘清屏避免系统擦背景造成闪烁。
LONG style = GetWindowLong(hWnd, GWL_STYLE); * - WM_ENTERSIZEMOVE开始拉伸 → isSizing=true 且 WM_SETREDRAW(FALSE) 冻结重绘。
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; // 可调整边框,启用最大化/最小化按钮 * - WM_SIZING拉伸中 → 仅做“最小尺寸夹紧”(按被拖边回推),不回滚、不绘制。
SetWindowLong(hWnd, GWL_STYLE, style); * - WM_EXITSIZEMOVE结束拉伸 → 读取最终客户区尺寸 → 标记 needResizeDirty解冻并刷新。
// 通知窗口样式变化生效 * - WM_GETMINMAXINFO提供系统最小轨迹限制四边一致
SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); */
LRESULT CALLBACK Window::WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)
// 设置背景色并清屏 {
setbkcolor(wBkcolor); auto* self = reinterpret_cast<Window*>(GetWindowLongPtr(h, GWLP_USERDATA));
cleardevice(); if (!self)
// 初次绘制所有控件(双缓冲)
BeginBatchDraw();
for (auto& control : controls)
{ {
control->draw(); return DefWindowProc(h, m, w, l);
} }
// (如果有初始对话框,也可绘制 dialogs
EndBatchDraw(); // 关键点①:禁止系统擦背景,避免和我们自己的清屏/双缓冲打架造成闪烁
if (m == WM_ERASEBKGND)
{
return 1;
}
// 关键点②:拉伸开始 → 冻结重绘(系统调整窗口矩形时不触发即时重绘,防止抖)
if (m == WM_ENTERSIZEMOVE)
{
self->isSizing = true;
SendMessage(h, WM_SETREDRAW, FALSE, 0);
return 0;
}
// 关键点③:拉伸中 → 仅执行“最小尺寸夹紧”,不做对齐/节流/回滚,保持系统自然流畅
if (m == WM_SIZING)
{
RECT* prc = reinterpret_cast<RECT*>(l);
// “尺寸异常值”快速过滤:仅保护极端值,不影响正常拖动
int currentWidth = prc->right - prc->left;
int currentHeight = prc->bottom - prc->top;
if (currentWidth < 0 || currentHeight < 0 || currentWidth > 10000 || currentHeight > 10000)
{
return TRUE;
}
ApplyMinSizeOnSizing(prc, w, h, self->minClientW, self->minClientH);
return TRUE;
}
// 关键点④:拉伸结束 → 解冻重绘 + 统一收口(记录最终尺寸 -> 标记 needResizeDirty
if (m == WM_EXITSIZEMOVE)
{
self->isSizing = false;
RECT rc; GetClientRect(h, &rc);
const int aw = rc.right - rc.left;
const int ah = rc.bottom - rc.top;
if (aw >= self->minClientW && ah >= self->minClientH && aw <= 10000 && ah <= 10000)
{
self->pendingW = aw;
self->pendingH = ah;
self->needResizeDirty = true;
}
// 结束拉伸后不立即执行重绘,待事件循环统一收口。
// 立即解冻重绘标志,同时标记区域为有效,避免触发额外 WM_PAINT。
SendMessage(h, WM_SETREDRAW, TRUE, 0);
ValidateRect(h, nullptr);
return 0;
}
// 关键点⑤:系统级最小轨迹限制(与 WM_SIZING 的夹紧互相配合)
if (m == WM_GETMINMAXINFO)
{
auto* mmi = reinterpret_cast<MINMAXINFO*>(l);
RECT rc{ 0, 0, self->minClientW, self->minClientH };
DWORD style = GetWindowLong(h, GWL_STYLE);
DWORD ex = GetWindowLong(h, GWL_EXSTYLE);
// 若后续添加菜单,请把第三个参数改为 HasMenu(h)
AdjustWindowRectEx(&rc, style, FALSE, ex);
mmi->ptMinTrackSize.x = rc.right - rc.left;
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
return 0;
}
// 其它消息:回落到旧过程
return self->oldWndProc ? CallWindowProc(self->oldWndProc, h, m, w, l)
: DefWindowProc(h, m, w, l);
} }
// ---------------- 绘制 ----------------
/**
* draw()
* 作用:首次初始化 EasyX 窗口与子类化过程;应用可拉伸样式;清屏并批量绘制。
* 关键步骤:
* 1initgraph 拿到 hWnd
* 2SetWindowLongPtr 子类化到 WndProcThunk只做一次
* 3ApplyResizableStyle 设置 WS_THICKFRAME/裁剪/(可选)合成双缓冲;
* 4去掉类样式 CS_HREDRAW/CS_VREDRAW避免全窗无效化引发闪屏
* 5清屏 + Begin/EndBatchDraw 批量绘制控件&对话框。
*/
void Window::draw()
{
if (!hWnd)
{
hWnd = initgraph(width, height, windowMode);
}
// 子类化:让我们的 WndProcThunk 接管窗口消息(仅执行一次)
if (!procHooked)
{
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)&Window::WndProcThunk);
procHooked = (oldWndProc != nullptr);
}
if (!headline.empty())
{
SetWindowText(hWnd, headline.c_str());
}
ApplyResizableStyle(hWnd, useComposited);
// 关闭类样式的整窗重绘标志(减少尺寸变化时的整窗 redraw
LONG_PTR cls = GetClassLongPtr(hWnd, GCL_STYLE);
cls &= ~(CS_HREDRAW | CS_VREDRAW);
SetClassLongPtr(hWnd, GCL_STYLE, cls);
setbkcolor(wBkcolor);
cleardevice();
BeginBatchDraw();
for (auto& c : controls)
{
c->draw();
}
for (auto& d : dialogs)
{
d->draw();
}
EndBatchDraw();
}
/**
* draw(imagePath)
* 作用:在 draw() 的基础上加载并绘制背景图;其它流程完全一致。
* 注意这里按当前窗口客户区大小加载背景图loadimage 的 w/h保证铺满。
*/
void Window::draw(std::string imagePath) void Window::draw(std::string imagePath)
{ {
// 使用指定图片绘制窗口背景(铺满窗口)
this->background = new IMAGE(width, height);
bkImageFile = imagePath;
if (!hWnd) if (!hWnd)
hWnd = initgraph(width, height, windowMode);
SetWindowText(hWnd, headline.c_str());
loadimage(background, imagePath.c_str(), width, height, true);
if(background)
putimage(0, 0, background);
else
{
// 设置背景色并清屏
setbkcolor(wBkcolor);
cleardevice();
}
// 同样应用可拉伸样式
LONG style = GetWindowLong(hWnd, GWL_STYLE);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
SetWindowLong(hWnd, GWL_STYLE, style);
SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
// 绘制控件(含对话框)到窗口
BeginBatchDraw();
for (auto& control : controls)
{ {
control->setDirty(true); hWnd = initgraph(width, height, windowMode);
control->draw();
} }
for (auto& dlg : dialogs) dlg->draw();
EndBatchDraw(); if (!procHooked)
{
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)&Window::WndProcThunk);
procHooked = (oldWndProc != nullptr);
}
bkImageFile = std::move(imagePath);
if (!headline.empty())
{
SetWindowText(hWnd, headline.c_str());
}
ApplyResizableStyle(hWnd, useComposited);
LONG_PTR cls = GetClassLongPtr(hWnd, GCL_STYLE);
cls &= ~(CS_HREDRAW | CS_VREDRAW);
SetClassLongPtr(hWnd, GCL_STYLE, cls);
if (background)
{
delete background;
background = nullptr;
}
background = new IMAGE;
loadimage(background, bkImageFile.c_str(), width, height, true);
putimage(0, 0, background);
BeginBatchDraw();
for (auto& c : controls)
{
c->setDirty(true);
c->draw();
}
for (auto& d : dialogs)
{
d->draw();
}
EndBatchDraw();
} }
// 运行主事件循环,处理用户输入和窗口消息 // ---------------- 事件循环 ----------------
// 此方法会阻塞直到窗口关闭
// 主消息循环优先级:对话框 > 普通控件。 /**
// 重绘策略:为保证视觉一致性,每次有对话框状态变化(打开/关闭)时, * runEventLoop()
// 会强制重绘所有控件。先绘制普通控件,再绘制对话框(确保对话框在最上层) * 作用:驱动输入/窗口消息;集中处理“统一收口重绘”
* 关键策略:
* - WM_SIZE始终更新 pendingW/H即使在拉伸中也只记录不立即绘制
* - needResizeDirty当尺寸确实变化时置位随后在循环尾进行一次性重绘
* - 非模态对话框优先消费事件(顶层从后往前);再交给普通控件。
*/
int Window::runEventLoop() int Window::runEventLoop()
{ {
ExMessage msg; ExMessage msg;
bool running = true; bool running = true;
// 说明:统一使用 needResizeDirty 作为“收口重绘”的唯一标志位
// 不再引入额外 pendingResize 等状态,避免分叉导致状态不一致。
while (running) while (running)
{ {
bool consume = false;// 是否处理了消息 bool consume = false;
// 处理所有消息
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true)) if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
{ {
if (msg.message == WM_CLOSE) if (msg.message == WM_CLOSE)
@@ -116,44 +350,60 @@ int Window::runEventLoop()
running = false; running = false;
return 0; return 0;
} }
if (msg.message == WM_SIZE)
{
if (msg.wParam != SIZE_MINIMIZED)
{
const int nw = LOWORD(msg.lParam);
const int nh = HIWORD(msg.lParam);
// 仅在尺寸真的变化时标脏 // 保险:如果 EX_WINDOW 转译了 GETMINMAXINFO同样按最小客户区折算处理
if (nw > 0 && nh > 0 || (nw != width || nh != height)) if (msg.message == WM_GETMINMAXINFO)
{
auto* mmi = reinterpret_cast<MINMAXINFO*>(msg.lParam);
RECT rc{ 0, 0, minClientW, minClientH };
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
DWORD ex = GetWindowLong(hWnd, GWL_EXSTYLE);
AdjustWindowRectEx(&rc, style, FALSE, ex);
mmi->ptMinTrackSize.x = rc.right - rc.left;
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
continue;
}
// 关键点⑥WM_SIZE 只记录新尺寸;若非拉伸阶段则立即置位 needResizeDirty
if (msg.message == WM_SIZE && msg.wParam != SIZE_MINIMIZED)
{
const int nw = LOWORD(msg.lParam);
const int nh = HIWORD(msg.lParam);
// 基本合法性校验(不小于最小值、不过大)
if (nw >= minClientW && nh >= minClientH && nw <= 10000 && nh <= 10000)
{
if (nw != width || nh != height)
{ {
pendingW = nw; pendingW = nw;
pendingH = nh; pendingH = nh;
// 在“非拉伸阶段”的 WM_SIZE例如最大化/还原/程序化调整)直接触发收口
needResizeDirty = true; needResizeDirty = true;
} }
} }
continue;//在末尾重绘制窗口 continue;
} }
// 优先处理对话框事件
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it) for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
{ {
auto& d = *it; auto& d = *it;
if (d->IsVisible() && !d->model()) if (d->IsVisible() && !d->model())
consume = d->handleEvent(msg);
if (consume)
break;
}
//普通控件
if (!consume)
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{ {
consume = (*it)->handleEvent(msg); consume = d->handleEvent(msg);
if (consume)
break;
} }
if (consume) break;
}
if (!consume)
{
for (auto& c : controls)
{
consume = c->handleEvent(msg);
if (consume) break;
}
}
} }
//如果有对话框打开或者关闭强制重绘 //如果有对话框打开或者关闭强制重绘
bool needredraw = false; bool needredraw = false;
for (auto& d : dialogs) for (auto& d : dialogs)
{ {
@@ -191,124 +441,183 @@ int Window::runEventLoop()
} }
EndBatchDraw(); EndBatchDraw();
needredraw = false; needredraw = false;
} }
//—— 统一收口”:尺寸变化后的** 一次性** 重绘 —— // —— 统一收口needResizeDirty 为真时执行一次性重绘——
if (needResizeDirty) if (needResizeDirty)
{ {
//确保窗口不会小于初始尺寸 // 以“实际客户区尺寸”为准,防止 pending 与真实尺寸出现偏差
if (pendingW >= width && pendingH >= height) RECT clientRect;
Resize(nullptr, pendingW, pendingH); GetClientRect(hWnd, &clientRect);
else int actualWidth = clientRect.right - clientRect.left;
Resize(nullptr, width, height); int actualHeight = clientRect.bottom - clientRect.top;
if (background)
const int finalW = (std::max)(minClientW, actualWidth);
const int finalH = (std::max)(minClientH, actualHeight);
// 变化过大/异常场景保护
if (finalW != width || finalH != height)
{ {
delete background; if (abs(finalW - width) > 1000 || abs(finalH - height) > 1000)
background = new IMAGE; {
loadimage(background, bkImageFile.c_str(), pendingW, pendingH); // 认为是异常帧,跳过本次(不改变任何状态)
putimage(0, 0, background); needResizeDirty = false;
continue;
}
// 再次冻结窗口更新,保证批量绘制的原子性
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
BeginBatchDraw();
// 调整底层画布尺寸
if (finalW != width || finalH != height)
{
// 批量通知控件“窗口尺寸变化”,并标记重绘
for (auto& c : controls)
adaptiveLayout(c, finalH, finalW);
for (auto& d : dialogs)
{
if (auto dd = dynamic_cast<Dialog*>(d.get()))
{
dd->setDirty(true);
dd->setInitialization(true);
}
}
//重绘窗口
Resize(nullptr, finalW, finalH);
// 重取一次实际客户区尺寸做确认
GetClientRect(hWnd, &clientRect);
int confirmedWidth = clientRect.right - clientRect.left;
int confirmedHeight = clientRect.bottom - clientRect.top;
int renderWidth = confirmedWidth;
int renderHeight = confirmedHeight;
// 背景:若设置了背景图则重载并铺满;否则清屏为纯色
if (background && !bkImageFile.empty())
{
delete background;
background = new IMAGE;
loadimage(background, bkImageFile.c_str(), renderWidth, renderHeight, true);
putimage(0, 0, background);
}
else
{
setbkcolor(wBkcolor);
cleardevice();
}
// 最终提交“当前已应用尺寸”(用于外部查询/下次比较)
width = renderWidth;
height = renderHeight;
}
// 统一批量绘制
for (auto& c : controls) c->draw();
for (auto& d : dialogs) d->draw();
EndBatchDraw();
// 解冻后标记区域有效,避免系统再次触发 WM_PAINT 覆盖自绘内容。
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
ValidateRect(hWnd, nullptr);
} }
// 标记所有控件/对话框为脏,确保都补一次背景/外观 needResizeDirty = false; // 收口完成,清标志
for (auto& c : controls)
{
c->onWindowResize();
c->draw();
}
for (auto& d : dialogs)
{
auto dd = dynamic_cast<Dialog*>(d.get());
dd->setDirty(true);
dd->setInitialization(true);
d->draw();
}
needResizeDirty = false;
} }
// 降低占用 // 轻微睡眠,削峰填谷(不阻塞拖拽体验)
Sleep(10); Sleep(10);
} }
return 1; return 1;
} }
// ---------------- 其余接口 ----------------
void Window::setBkImage(std::string pImgFile) void Window::setBkImage(std::string pImgFile)
{ {
if(nullptr == background) // 更换背景图:立即加载并绘制一次;同时将所有控件标 dirty 并重绘
this->background = new IMAGE; if (background) delete background;
else background = new IMAGE;
delete background; bkImageFile = std::move(pImgFile);
this->background = new IMAGE;
this->bkImageFile = pImgFile; loadimage(background, bkImageFile.c_str(), width, height, true);
loadimage(background, pImgFile.c_str(), width, height, true);
putimage(0, 0, background); putimage(0, 0, background);
BeginBatchDraw(); BeginBatchDraw();
for (auto& c : controls) for (auto& c : controls)
{ {
c->setDirty(true); c->setDirty(true);
c->draw(); c->draw();
} }
for (auto& c : dialogs) for (auto& d : dialogs)
{ {
c->setDirty(true); d->setDirty(true);
c->draw(); d->draw();
} }
EndBatchDraw(); EndBatchDraw();
} }
void Window::setBkcolor(COLORREF c) void Window::setBkcolor(COLORREF c)
{ {
// 更换纯色背景:立即清屏并批量重绘控件/对话框
wBkcolor = c; wBkcolor = c;
setbkcolor(wBkcolor); setbkcolor(wBkcolor);
cleardevice(); cleardevice();
// 初次绘制所有控件(双缓冲)
BeginBatchDraw(); BeginBatchDraw();
for (auto& c : controls) for (auto& c : controls)
{ {
c->setDirty(true); c->setDirty(true);
c->draw(); c->draw();
} }
for (auto& c : dialogs) for (auto& d : dialogs)
{ {
c->setDirty(true); d->setDirty(true);
c->draw(); d->draw();
} }
EndBatchDraw(); EndBatchDraw();
} }
void Window::setHeadline(std::string headline) void Window::setHeadline(std::string title)
{ {
this->headline = headline; // 设置窗口标题(仅改文本,不触发重绘)
SetWindowText(this->hWnd, headline.c_str()); headline = std::move(title);
if (hWnd)
SetWindowText(hWnd, headline.c_str());
} }
void Window::addControl(std::unique_ptr<Control> control) void Window::addControl(std::unique_ptr<Control> control)
{ {
this->controls.push_back(std::move(control)); // 新增控件:仅加入管理容器,具体绘制在 draw()/收口时统一进行
controls.push_back(std::move(control));
} }
void Window::addDialog(std::unique_ptr<Control> dialogs) void Window::addDialog(std::unique_ptr<Control> dlg)
{ {
this->dialogs.push_back(std::move(dialogs)); // 新增非模态对话框:管理顺序决定事件优先级(顶层从后往前)
dialogs.push_back(std::move(dlg));
} }
bool Window::hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const bool Window::hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const
{ {
for (const auto& dptr : dialogs) // 查询是否存在“可见且非模态”的对话框(用于避免重复弹)
for (const auto& dptr : dialogs)
{ {
if (!dptr) continue; if (!dptr) continue;
// 只检查 Dialog 类型的控件 if (auto* d = dynamic_cast<Dialog*>(dptr.get()))
Dialog* d = dynamic_cast<Dialog*>(dptr.get()); {
//检查是否有非模态对话框可见,并且消息内容一致 if (d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message) {
return true; return true;
}
}
} }
return false; return false;
} }
HWND Window::getHwnd() const HWND Window::getHwnd() const
{ {
return hWnd; return hWnd;
@@ -316,40 +625,194 @@ HWND Window::getHwnd() const
int Window::getWidth() const int Window::getWidth() const
{ {
return this->pendingW; // 注意:这里返回 pendingW
// 表示“最近一次收到的尺寸”(可能尚未应用到画布,最终以收口时的 width 为准)
return pendingW;
} }
int Window::getHeight() const int Window::getHeight() const
{ {
return this->pendingH; // 同上,返回 pendingH与 getWidth 对应)
return pendingH;
} }
std::string Window::getHeadline() const std::string Window::getHeadline() const
{ {
return this->headline; return headline;
} }
COLORREF Window::getBkcolor() const COLORREF Window::getBkcolor() const
{ {
return this->wBkcolor; return wBkcolor;
} }
IMAGE* Window::getBkImage() const IMAGE* Window::getBkImage() const
{ {
return this->background; return background;
} }
std::string Window::getBkImageFile() const std::string Window::getBkImageFile() const
{ {
return this->bkImageFile; return bkImageFile;
} }
std::vector<std::unique_ptr<Control>>& Window::getControls() std::vector<std::unique_ptr<Control>>& Window::getControls()
{ {
return this->controls; return controls;
} }
void Window::processWindowMessage(const ExMessage& msg)
{
if (msg.message == WM_SIZE && msg.wParam != SIZE_MINIMIZED)
{
const int nw = LOWORD(msg.lParam);
const int nh = HIWORD(msg.lParam);
if (nw >= minClientW && nh >= minClientH && nw <= 10000 && nh <= 10000)
{
if (nw != width || nh != height)
{
pendingW = nw;
pendingH = nh;
needResizeDirty = true; // 统一由 pumpResizeIfNeeded 来收口
}
}
}
}
void Window::pumpResizeIfNeeded()
{
if (!needResizeDirty) return;
RECT rc; GetClientRect(hWnd, &rc);
const int finalW = max(minClientW, rc.right - rc.left);
const int finalH = max(minClientH, rc.bottom - rc.top);
if (finalW == width && finalH == height) { needResizeDirty = false; return; }
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
BeginBatchDraw();
// Resize + 背景
Resize(nullptr, finalW, finalH);
GetClientRect(hWnd, &rc);
if (background && !bkImageFile.empty())
{
delete background; background = new IMAGE;
loadimage(background, bkImageFile.c_str(), rc.right - rc.left, rc.bottom - rc.top, true);
putimage(0, 0, background);
}
else
{
setbkcolor(wBkcolor);
cleardevice();
}
width = rc.right - rc.left; height = rc.bottom - rc.top;
// 通知控件/对话框
for (auto& c : controls)
{
adaptiveLayout(c, finalH, finalW);
c->onWindowResize();
}
for (auto& d : dialogs)
if (auto* dd = dynamic_cast<Dialog*>(d.get()))
dd->setInitialization(true); // 强制对话框在新尺寸下重建布局/快照
// 重绘
for (auto& c : controls) c->draw();
for (auto& d : dialogs) d->draw();
EndBatchDraw();
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
// 原实现在此调用 InvalidateRect 导致系统再次发送 WM_PAINT从而重复绘制
// 这里改为 ValidateRect直接标记区域为有效通知系统我们已完成绘制不必再触发 WM_PAINT。
// 这样可以避免收口阶段的绘制与系统重绘叠加造成顺序错乱。
ValidateRect(hWnd, nullptr);
needResizeDirty = false;
}
void Window::scheduleResizeFromModal(int w, int h)
{
if (w < minClientW) w = minClientW;
if (h < minClientH) h = minClientH;
if (w > 10000) w = 10000;
if (h > 10000) h = 10000;
if (w != width || h != height)
{
pendingW = w;
pendingH = h;
needResizeDirty = true; // 交给 pumpResizeIfNeeded 做统一收口+重绘
}
}
void Window::adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW)
{
int origParentW = this->localwidth;
int origParentH = this->localheight;
if (c->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
{
if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
{
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
int newWidth = finalW - c->getLocalX() - origRightDist;
c->setWidth(newWidth);
// 左侧距离固定ctrl->x 保持为 localx 相对窗口左侧父容器为窗口偏移0
c->setX(c->getLocalX());
}
else if ((StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2())
|| (StellarX::Anchor::Left == c->getAnchor_1() && StellarX::Anchor::Left == c->getAnchor_2()))
{
// 仅左锚定:宽度固定不变
c->setX(c->getLocalX());
c->setWidth(c->getLocalWidth());
}
else if ((StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2())
|| (StellarX::Anchor::Right == c->getAnchor_1() && StellarX::Anchor::Right == c->getAnchor_2()))
{
int origRightDist = origParentW - (c->getLocalX() + c->getLocalWidth());
c->setWidth(c->getLocalWidth()); // 宽度不变
c->setX(finalW - origRightDist - c->getWidth());
}
else if (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
{
c->setX(c->getLocalX());
c->setWidth(c->getLocalWidth());
}
if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
{
// 上下锚定:高度随窗口变化
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
int newHeight = finalH - c->getLocalY() - origBottomDist;
c->setHeight(newHeight);
c->setY(c->getLocalY());
}
else if ((StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2())
|| (StellarX::Anchor::Top == c->getAnchor_1() && StellarX::Anchor::Top == c->getAnchor_2()))
{
c->setY(c->getLocalY());
c->setHeight(c->getLocalHeight());
}
else if ((StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::NoAnchor == c->getAnchor_2())
|| (StellarX::Anchor::NoAnchor == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2())
|| (StellarX::Anchor::Bottom == c->getAnchor_1() && StellarX::Anchor::Bottom == c->getAnchor_2()))
{
int origBottomDist = origParentH - (c->getLocalY() + c->getLocalHeight());
c->setHeight(c->getLocalHeight());
c->setY(finalH - origBottomDist - c->getHeight());
}
else
{
// 垂直无锚点:默认为顶部定位,高度固定
c->setY(c->getLocalY());
c->setHeight(c->getLocalHeight());
}
}
c->onWindowResize();
}

File diff suppressed because it is too large Load Diff