5 Commits

Author SHA1 Message Date
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
26 changed files with 4258 additions and 4338 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,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[中文文档](CHANGELOG.md)
## [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
==This release is a hotfix for v2.2.0==

View File

@@ -7,6 +7,45 @@ StellarX 项目所有显著的变化都将被记录在这个文件中。
[English document](CHANGELOG.en.md)
## [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.0的修复版本==

View File

@@ -7,8 +7,8 @@
![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)
![Version](https://img.shields.io/badge/Version-2.2.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg)
![Version](https://img.shields.io/badge/Version-2.3.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.3.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -25,22 +25,28 @@ This is a **teaching-grade and tooling-grade** framework that helps developers u
------
## **🆕 v2.2.1 (Hotfix for v2.2.0)**
## 🆕 V2.3.0 - Major Update
- 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.
**This version represents a significant milestone, introducing a responsive layout system that transitions from static to dynamic layout management, and comprehensively resolves the previously encountered random rendering corruption issues caused by reentrant drawing operations.**
- **Optimized Window Resizing Mechanism**: Refactored `WndProcThunk`, `runEventLoop`, and `pumpResizeIfNeeded` to uniformly record size changes and perform centralized repainting at the end of the event loop, eliminating jitter and sequencing confusion caused by repeated redraws.
- **New Dialog Size Scheduling Interface**: Introduced the combination of `Window::scheduleResizeFromModal()` and `pumpResizeIfNeeded()`, enabling modal dialogs to notify the parent window of size updates even during resizing operations. Underlying controls are relayout during unified finalization while dialogs maintain their original dimensions.
- **Enhanced Adaptive Layout System**: Internally added the `adaptiveLayout()` function to recalculate control positions and sizes based on anchor points, allowing dual-anchored controls (left-right or top-bottom) to adaptively stretch with window resizing.
- **Fixed Modal Dialog Resizing Issues**: Resolved the problem where window resizing while modal dialogs were open prevented underlying controls from updating their positions and sizes according to anchor points; simultaneously eliminated ghosting artifacts caused by repeated dialog redraws.
- **Further Resolved Drawing Sequence Confusion**: Replaced `InvalidateRect` with `ValidateRect` during resizing operations, ensuring the window is marked as valid only after a single unified drawing pass, preventing system-triggered `WM_PAINT` messages from causing reentrancy.
- **Additional Fixes**: Corrected delayed background snapshot updates in tables and dialogs under certain edge cases.
![](image/1.png)
![](image/2.png)
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
@@ -181,6 +187,11 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
| `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` |
| Enum | Description | Common values |
| ------------ | ---------------------- | -------------------------------------------- |
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
| `Anchor` | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
### Structs
| Struct | Description |

View File

@@ -2,13 +2,18 @@
[English document](README.en.md)
> 本仓库为 **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)
[![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)
![Download](https://img.shields.io/badge/Download-2.2.0_Release-blue.svg)
![Version](https://img.shields.io/badge/Version-2.3.0-brightgreen.svg)
![Download](https://img.shields.io/badge/Download-2.3.0_Release-blue.svg)
![C++](https://img.shields.io/badge/C++-17+-00599C?logo=cplusplus&logoColor=white)
![Windows](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)
@@ -25,24 +30,27 @@
---
## 🆕v2.2.1v2.2.0修复版)
## 🆕V2.3.0——重要更新
- 解决了使用Canvas和TabControl容器时出现频闪问题
**本版本是一次重大更新,增加了响应式布局系统,由静态布局转变为动态布局,并且彻底修复了之前存在的由重入重绘导致的概率出现的渲染错乱问题**
- 修复了Dialog对话框关闭时概率出边边框残留和功能按钮残留问题
- **优化窗口尺寸调节机制**:重构 `WndProcThunk``runEventLoop``pumpResizeIfNeeded`,统一记录尺寸变化并在事件循环末尾集中重绘,避免重复重绘导致的抖动和顺序错乱。
详情参考[更新日志](CHANGELOG.md)
- **新增对话框尺寸调度接口**:引入 `Window::scheduleResizeFromModal()``pumpResizeIfNeeded()` 的组合,模态对话框在拉伸期间也可通知父窗口更新尺寸。底层控件将在统一收口时重新布局,而对话框自身保持尺寸不变。
- **自适应布局改进**:内部新增 `adaptiveLayout()` 函数,按照锚点重新计算控件位置和尺寸,使双锚定(左右或上下)控件随窗口变化自适应伸缩。
- **修复模态对话框拉伸问题**:解决模态对话框打开时,窗口拉伸导致底层控件无法根据锚点更新的位置和尺寸的问题;同时避免对话框反复重绘导致的残影。
## V2.2.0 有何变化
- **进一步解决绘制顺序错乱**:拉伸过程中采用 `ValidateRect` 替代 `InvalidateRect`,确保窗口仅在一次统一收口绘制后标记为有效,杜绝系统再次触发 `WM_PAINT` 造成重入。
- **新增 TabControl 控件,实现多页面选项卡界面:** 通过 `TabControl` 可以轻松创建选项卡式布局,支持页签在上下左右排列、点击切换显示不同内容页面。适用于设置面板、多视图切换等场景
- **控件显隐与布局响应能力增强:** 现在所有控件都可以使用统一接口动态隐藏或显示(`setIsVisible`),容器控件隐藏时其内部子控件会自动随之隐藏/显示。与此同时,引入控件对窗口尺寸变化的响应机制(`onWindowResize`),窗口拉伸后界面各元素可协调更新,杜绝拉伸过程中出现残影或错位。
- **文本样式机制完善:** Label 控件改用统一的文本样式结构 `ControlText`,开发者可方便地设置字体、颜色、大小等属性来定制 Label 的外观替代旧接口更加灵活。Button 的 Tooltip 提示也支持更丰富的定制和针对切换状态的不同提示文本。
- **其他改进:** 框架底层的对话框管理增加了防重复弹出相同提示的机制,修复了一些细节 Bug 并优化了刷新效率,进一步提升了稳定性。
- 其他修复:修正表格和对话框背景快照某些边界情况下的更新不及时问题
详见 `CHANGELOG.md / CHANGELOG.en.md` 获取完整更新列表。
![](image/1.png)
![](image/2.png)
详细变更请参阅[更新日志](CHANGELOG.md)
---
@@ -184,6 +192,13 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
| 枚举 | 描述 | 常用值 |
| ------------ | ---------------------- | -------------------------------------------- |
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
| Anchor | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
### 结构体
| 结构体 | 描述 |

View File

@@ -7,12 +7,12 @@
auto blackColor = RGB(202, 255, 255);
char initData[33] = "00000000000000000000000000000000";//初始数据
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);
std::vector<std::unique_ptr<Label>>selectionAreaButtonLabel;
std::vector<std::unique_ptr<Button>>selectionAreaButton;
@@ -22,21 +22,21 @@ void main()
selectionArea->setCanvasBkColor(blackColor);
selectionArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
for (int y = 0; y < 2; y ++)
for (int y = 0; y < 2; y++)
{
std::ostringstream os;
for (int x = 0; x <16; x++)
for (int x = 0; x < 16; x++)
{
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;
selectionAreaButtonLabel.back()->setText(os.str());
selectionAreaButtonLabel.back()->setTextdisap(true);
selectionAreaButton.push_back(
std::make_unique<Button>(x * 35 + 42 + 28 * (x / 4), 58,20,32,"0",
blackColor,RGB(171, 196, 220),StellarX::ButtonMode::TOGGLE));
std::make_unique<Button>(x * 35 + 27 + 28 * (x / 4), 58, 25, 30, "0",
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE));
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
@@ -55,19 +55,19 @@ void main()
}
else
{
selectionAreaButtonLabel.push_back(std::make_unique<Label>(x * 35 + 40 + 28 * (x / 4), 90, "", RGB(208, 208, 208)));
os << std::setw(2) << std::setfill('0') << 15-x;
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;
selectionAreaButtonLabel.back()->setText(os.str());
selectionAreaButtonLabel.back()->setTextdisap(true);
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));
selectionAreaButton.back()->textStyle.color = RGB(226, 116, 152);
selectionAreaButton.back()->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
selectionAreaButton_ptr.push_back(selectionAreaButton.back().get());
int k =15 - x;
selectionAreaButton.back()->setOnToggleOnListener([k,btn = selectionAreaButton_ptr.back()]()
int k = 15 - x;
selectionAreaButton.back()->setOnToggleOnListener([k, btn = selectionAreaButton_ptr.back()]()
{
btn->setButtonText("1");
initData[k] = '1';
@@ -90,12 +90,18 @@ void main()
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->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
function->setCanvasBkColor(blackColor);
auto bitInvert_que = std::make_unique<Canvas>(0, 0, 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();
auto bitInvert = std::make_unique<Canvas>(10,170,220,70);
auto leftShift = std::make_unique<Canvas>(240, 170, 220, 70);
auto rightShift = std::make_unique<Canvas>(470, 170, 220, 70);
bitInvert->setCanvasBkColor(blackColor);
bitInvert->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
leftShift->setCanvasBkColor(blackColor);
@@ -103,12 +109,15 @@ void main()
rightShift->setCanvasBkColor(blackColor);
rightShift->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
function->addControl(std::move(bitInvert_que));
function->addControl(std::move(leftShift_que));
function->addControl(std::move(rightShift_que));
auto bitInvertLabel = std::make_unique<Label>(18,160,"位取反");
auto bitInvertLabel = std::make_unique<Label>(13, -10, "位取反");
bitInvertLabel->setTextdisap(true);
auto leftShiftLabel = std::make_unique<Label>(248, 160, "左移位");
auto leftShiftLabel = std::make_unique<Label>(13, -10, "左移位");
leftShiftLabel->setTextdisap(true);
auto rightShiftLabel = std::make_unique<Label>(478, 160, "右移位");
auto rightShiftLabel = std::make_unique<Label>(13, -10, "右移位");
rightShiftLabel->setTextdisap(true);
// ====== 公用小工具======
@@ -139,17 +148,17 @@ void main()
//取反区控件
std::array<std::unique_ptr<Label>, 4> bitInvertFunctionLabel;
bitInvertFunctionLabel[0] = std::make_unique<Label>(35, 180, "低位");
bitInvertFunctionLabel[1] = std::make_unique<Label>(90, 180, "高位");
bitInvertFunctionLabel[2] = std::make_unique<Label>(15, 198, "");
bitInvertFunctionLabel[3] = std::make_unique<Label>(75, 198, "");
bitInvertFunctionLabel[0] = std::make_unique<Label>(30, 10, "低位");
bitInvertFunctionLabel[1] = std::make_unique<Label>(90, 10, "高位");
bitInvertFunctionLabel[2] = std::make_unique<Label>(15, 38, "");
bitInvertFunctionLabel[3] = std::make_unique<Label>(75, 38, "");
std::array<std::unique_ptr<TextBox>, 2> bitInvertFunctionTextBox;
bitInvertFunctionTextBox[0] = std::make_unique<TextBox>(35, 203, 35, 30, "0");
bitInvertFunctionTextBox[1] = std::make_unique<TextBox>(95, 203, 35, 30, "0");
bitInvertFunctionTextBox[0] = std::make_unique<TextBox>(35, 35, 35, 30, "0");
bitInvertFunctionTextBox[1] = std::make_unique<TextBox>(95, 35, 35, 30, "0");
auto invL = bitInvertFunctionTextBox[0].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));
bitInvertFunctionButton->textStyle.color = RGB(226, 116, 152);
bitInvertFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -171,16 +180,16 @@ void main()
bitInvert->addControl(std::move(b));
}
//左移控件
auto leftShiftFunctionLabel = std::make_unique<Label>(435, 198, "");
auto leftShiftFunctionLabel = std::make_unique<Label>(198, 30, "");
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->textStyle.color = RGB(226, 116, 152);
leftShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
leftShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
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));
leftShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
leftShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -194,16 +203,16 @@ void main()
leftShift->addControl(std::move(leftShiftFunctionLabel));
//右移控件
auto rightShiftFunctionLabel = std::make_unique<Label>(665, 198, "");
auto rightShiftFunctionLabel = std::make_unique<Label>(198, 30, "");
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->textStyle.color = RGB(226, 116, 152);
rightShiftFunctionTextBox->setTextBoxBk(RGB(244, 234, 142));
rightShiftFunctionTextBox->setTextBoxshape(StellarX::ControlShape::B_RECTANGLE);
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));
rightShiftFunctionButton->textStyle.color = RGB(226, 116, 152);
rightShiftFunctionButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -217,9 +226,6 @@ void main()
rightShift->addControl(std::move(rightShiftLabel));
rightShift->addControl(std::move(rightShiftFunctionLabel));
function->addControl(std::move(bitInvert));
function->addControl(std::move(leftShift));
function->addControl(std::move(rightShift));
//显示区控件
//数值显示
@@ -228,13 +234,13 @@ void main()
NumericalDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
std::array<std::unique_ptr<Label>, 3> NumericalDisplayAreaLabel;
NumericalDisplayAreaLabel[0] = std::make_unique<Label>(18, 245, "数值显示区");
NumericalDisplayAreaLabel[1] = std::make_unique<Label>(20, 278, "十六进制");
NumericalDisplayAreaLabel[2] = std::make_unique<Label>(330, 278, "十进制");
NumericalDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "数值显示区");
NumericalDisplayAreaLabel[1] = std::make_unique<Label>(20, 25, "十六进制");
NumericalDisplayAreaLabel[2] = std::make_unique<Label>(330, 25, "十进制");
std::array<std::unique_ptr<TextBox>, 2> NumericalDisplayAreaTextBox;
NumericalDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 275, 200, 30, "0");
NumericalDisplayAreaTextBox[1] = std::make_unique<TextBox>(400, 275, 200, 30, "0");
NumericalDisplayAreaTextBox[0] = std::make_unique<TextBox>(110, 25, 200, 30, "0");
NumericalDisplayAreaTextBox[1] = std::make_unique<TextBox>(400, 25, 200, 30, "0");
auto hex = NumericalDisplayAreaTextBox[0].get();
auto dec = NumericalDisplayAreaTextBox[1].get();
@@ -259,13 +265,13 @@ void main()
BinaryDisplayArea->setShape(StellarX::ControlShape::B_ROUND_RECTANGLE);
std::array<std::unique_ptr<Label>, 3> BinaryDisplayAreaLabel;
BinaryDisplayAreaLabel[0] = std::make_unique<Label>(18, 325, "二进制显示区");
BinaryDisplayAreaLabel[1] = std::make_unique<Label>(35, 353, "上次值");
BinaryDisplayAreaLabel[2] = std::make_unique<Label>(35, 400, "本次值");
BinaryDisplayAreaLabel[0] = std::make_unique<Label>(18, -10, "二进制显示区");
BinaryDisplayAreaLabel[1] = std::make_unique<Label>(35, 20, "上次值");
BinaryDisplayAreaLabel[2] = std::make_unique<Label>(35, 67, "本次值");
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[1] = std::make_unique<TextBox>(110, 400, 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, 67, 520, 30, "0000_0000_0000_0000_0000_0000_0000_0000");
auto Last = BinaryDisplayAreaTextBox[0].get();
auto This = BinaryDisplayAreaTextBox[1].get();
@@ -367,16 +373,16 @@ void main()
configuration->setCanvasBkColor(blackColor);
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);
std::array<std::unique_ptr<Button>,2> configurationButton;
configurationButton[0] = std::make_unique<Button>(450, 465, 80, 20, "一键置0",
std::array<std::unique_ptr<Button>, 2> configurationButton;
configurationButton[0] = std::make_unique<Button>(420, 10, 90, 20, "一键置0",
blackColor, RGB(171, 196, 220));
configurationButton[0]->textStyle.color = RGB(226, 116, 152);
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));
configurationButton[1]->textStyle.color = RGB(226, 116, 152);
configurationButton[1]->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -419,7 +425,7 @@ void main()
auto signedToggle = std::make_unique<Button>(
350, 465, 80, 20, "无符号",
330, 10, 80, 20, "无符号",
blackColor, RGB(171, 196, 220), StellarX::ButtonMode::TOGGLE);
signedToggle->textStyle.color = RGB(226, 116, 152);
signedToggle->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
@@ -428,7 +434,7 @@ void main()
signedTogglePtr->setOnToggleOnListener([&]() {
gSigned = true;
signedTogglePtr->setButtonText("有符号");
StellarX::MessageBox::showModal(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
// 立即刷新十进制显示:用当前位图算出新值,仅改 dec
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; }();
@@ -438,19 +444,20 @@ void main()
signedTogglePtr->setOnToggleOffListener([&]() {
gSigned = false;
signedTogglePtr->setButtonText("无符号");
StellarX::MessageBox::showAsync(mainWindow, "无符号模式下,\n所有位均为数值位。", "无符号模式");
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; }();
dec->setText(std::to_string(u));
});
signedTogglePtr->enableTooltip(true);
signedTogglePtr->setTooltipTextsForToggle("切换无符号模式", "切换有符号模式");
configuration->addControl(std::move(configurationButton[0]));
configuration->addControl(std::move(configurationButton[1]));
configuration->addControl(std::move(signedToggle));
configuration->addControl(std::move(configurationLabel));
mainWindow.addControl(std::move(selectionArea));
mainWindow.addControl(std::move(function));
mainWindow.addControl(std::move(NumericalDisplayArea));

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

@@ -18,6 +18,7 @@
#pragma once
#include "Control.h"
#include"Table.h"
class Canvas : public Control
{
@@ -39,6 +40,7 @@ public:
Canvas();
Canvas(int x, int y, int width, int height);
~Canvas() {}
//绘制容器及其子控件
void draw() override;
bool handleEvent(const ExMessage& msg) override;

View File

@@ -9,23 +9,20 @@
* - 定义控件基本属性(坐标、尺寸、脏标记)
* - 提供绘图状态管理saveStyle/restoreStyle
* - 声明纯虚接口draw、handleEvent等
* - 支持移动语义,禁止拷贝语义
* - 禁止移动语义,禁止拷贝语义
*
* @使用场景: 作为所有具体控件类的基类,不直接实例化
* @所属框架: 星垣(StellarX) GUI框架
* @作者: 我在人间做废物
******************************************************************************/
#pragma once
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#ifndef WINVER
#define WINVER _WIN32_WINNT
#endif
#include <windows.h>
#include <vector>
#include <memory>
#include <easyx.h>
@@ -45,6 +42,11 @@ protected:
bool dirty = true; // 是否重绘
bool show = true; // 是否显示
/* == 布局模式 == */
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
StellarX::Anchor anchor_1 = StellarX::Anchor::Top; // 锚点
StellarX::Anchor anchor_2 = StellarX::Anchor::Left; // 锚点
/* == 背景快照 == */
IMAGE* saveBkImage = nullptr;
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
@@ -118,8 +120,8 @@ public:
void setX(int x) { this->x = x; dirty = true; }
void setY(int y) { this->y = y; dirty = true; }
void setWidth(int width) { this->width = width; dirty = true; }
void setHeight(int height) { this->height = height; dirty = true; }
virtual void setWidth(int width) { this->width = width; dirty = true; }
virtual void setHeight(int height) { this->height = height; dirty = true; }
public:
virtual void draw() = 0;
@@ -130,7 +132,6 @@ public:
void setParent(Control* parent) { this->parent = parent; }
//设置是否重绘
virtual void setDirty(bool dirty) { this->dirty = dirty; }
//检查控件是否可见
bool IsVisible() const { return show; };
//获取控件id
@@ -139,6 +140,12 @@ public:
bool isDirty() { return dirty; }
//用来检查对话框是否模态,其他控件不用实现
virtual bool model()const = 0;
//布局
void setLayoutMode(StellarX::LayoutMode layoutMode_);
void steAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
StellarX::Anchor getAnchor_1() const;
StellarX::Anchor getAnchor_2() const;
StellarX::LayoutMode getLayoutMode() const;
protected:
void saveStyle();
void restoreStyle();

View File

@@ -343,4 +343,47 @@ namespace StellarX
Left,
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

@@ -126,7 +126,7 @@ private:
void saveBackground(int x, int y, int w, int h)override;
void restBackground()override;
void addControl(std::unique_ptr<Control> control);
// 清除所有控件
void clearControls();

View File

@@ -1,7 +1,7 @@
/*******************************************************************************
* @文件: StellarX.h
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
* @版本: v2.2.1
* @版本: v2.3.0
* @描述:
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
* 基于EasyX图形库提供简洁易用的API和丰富的控件。

View File

@@ -64,5 +64,6 @@ public:
int indexOf(const std::string& tabText) const;
void setDirty(bool dirty) override;
void requestRepaint(Control* parent)override;
};

View File

@@ -69,6 +69,7 @@ private:
bool isShowPageButton = true; // 是否显示翻页按钮
bool isNeedDrawHeaders = true; // 是否需要绘制表头
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
Button* prevButton = nullptr; // 上一页按钮
Button* nextButton = nullptr; // 下一页按钮
@@ -98,7 +99,8 @@ private:
bool model() const override { return false; };
public:
StellarX::ControlText textStyle; // 文本样式
void setWidth(int width) override;
void setHeight(int height) override;
public:
Table(int x, int y);
~Table();
@@ -127,6 +129,8 @@ public:
void setTableLineStyle(StellarX::LineStyle style);
//设置边框宽度
void setTableBorderWidth(int width);
//窗口变化丢快照+标脏
void onWindowResize() override;
//************************** 获取属性 *****************************/
@@ -152,6 +156,9 @@ public:
std::vector<std::vector<std::string>> getData() const;
//获取表格边框宽度
int getTableBorderWidth() const;
//获取表格尺寸
int getTableWidth() const;
int getTableHeight() const;
};

View File

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

@@ -166,8 +166,8 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
Button::~Button()
{
if (buttonFileIMAGE)
delete buttonFileIMAGE;
if (buttonFileIMAGE)
delete buttonFileIMAGE;
buttonFileIMAGE = nullptr;
}
@@ -327,7 +327,7 @@ bool Button::handleEvent(const ExMessage& msg)
}
}
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
{
hideTooltip(); // 隐藏悬停提示
@@ -426,12 +426,12 @@ bool Button::handleEvent(const ExMessage& msg)
void Button::setOnClickListener(const std::function<void()>&& callback)
{
this->onClickCallback = callback;
this->onClickCallback = callback;
}
void Button::setOnToggleOnListener(const std::function<void()>&& callback)
{
this->onToggleOnCallback = callback;
this->onToggleOnCallback = callback;
}
void Button::setOnToggleOffListener(const std::function<void()>&& callback)
{
@@ -443,37 +443,37 @@ void Button::setbuttonMode(StellarX::ButtonMode mode)
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
textStyle.bStrikeOut = false;
//取值范围参考 buttMode的枚举注释
this->mode = mode;
this->mode = mode;
dirty = true; // 标记需要重绘
}
void Button::setROUND_RECTANGLEwidth(int width)
{
rouRectangleSize.ROUND_RECTANGLEwidth = width;
rouRectangleSize.ROUND_RECTANGLEwidth = width;
this->dirty = true; // 标记需要重绘
}
void Button::setROUND_RECTANGLEheight(int height)
{
rouRectangleSize.ROUND_RECTANGLEheight = height;
rouRectangleSize.ROUND_RECTANGLEheight = height;
this->dirty = true; // 标记需要重绘
}
bool Button::isClicked() const
{
return this->click;
return this->click;
}
void Button::setFillMode(StellarX::FillMode mode)
{
this->buttonFillMode = mode;
this->buttonFillMode = mode;
this->dirty = true; // 标记需要重绘
}
void Button::setFillIma(StellarX::FillStyle ima)
{
buttonFillIma = ima;
buttonFillIma = ima;
this->dirty = true;
}
@@ -484,8 +484,8 @@ void Button::setFillIma(std::string imaNAme)
delete buttonFileIMAGE;
buttonFileIMAGE = nullptr;
}
buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(),width,height);
buttonFileIMAGE = new IMAGE;
loadimage(buttonFileIMAGE, imaNAme.c_str(), width, height);
this->dirty = true;
}
@@ -493,7 +493,7 @@ void Button::setFillIma(std::string imaNAme)
void Button::setButtonBorder(COLORREF Border)
{
buttonBorderColor = Border;
this->dirty = true;
this->dirty = true;
}
void Button::setButtonFalseColor(COLORREF color)
@@ -504,7 +504,7 @@ void Button::setButtonFalseColor(COLORREF color)
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_height = textheight(LPCTSTR(this->text.c_str()));
this->dirty = true;
@@ -526,7 +526,7 @@ void Button::setButtonText(std::string text)
void Button::setButtonShape(StellarX::ControlShape shape)
{
this->shape = shape;
this->shape = shape;
this->dirty = true;
this->needCutText = true;
}
@@ -561,52 +561,52 @@ void Button::setButtonClick(BOOL click)
std::string Button::getButtonText() const
{
return this->text;
return this->text;
}
const char* Button::getButtonText_c() const
{
return this->text.c_str();
return this->text.c_str();
}
StellarX::ButtonMode Button::getButtonMode() const
{
return this->mode;
return this->mode;
}
StellarX::ControlShape Button::getButtonShape() const
{
return this->shape;
return this->shape;
}
StellarX::FillMode Button::getFillMode() const
{
return this->buttonFillMode;
return this->buttonFillMode;
}
StellarX::FillStyle Button::getFillIma() const
{
return this->buttonFillIma;
return this->buttonFillIma;
}
IMAGE* Button::getFillImaImage() const
{
return this->buttonFileIMAGE;
return this->buttonFileIMAGE;
}
COLORREF Button::getButtonBorder() const
{
return this->buttonBorderColor;
return this->buttonBorderColor;
}
COLORREF Button::getButtonTextColor() const
{
return this->textStyle.color;
return this->textStyle.color;
}
StellarX::ControlText Button::getButtonTextStyle() const
{
return this->textStyle;
return this->textStyle;
}
int Button::getButtonWidth() const
@@ -623,11 +623,11 @@ int Button::getButtonHeight() const
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
{
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
if (dis <= radius)
return true;
else
return false;
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
if (dis <= radius)
return true;
else
return false;
}
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
@@ -636,15 +636,15 @@ bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, i
int centerY = (y + height) / 2;
int majorAxis = (width - x) / 2;
int minorAxis = (height - y) / 2;
double dx = mouseX - centerX;
double dy = mouseY - centerY;
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
double dx = mouseX - centerX;
double dy = mouseY - centerY;
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
// 判断鼠标是否在椭圆内
if (normalizedDistance <= 1.0)
return true;
else
return false;
// 判断鼠标是否在椭圆内
if (normalizedDistance <= 1.0)
return true;
else
return false;
}
void Button::cutButtonText()
@@ -686,9 +686,9 @@ void Button::hideTooltip()
void Button::refreshTooltipTextForState()
{
if (tipUserOverride) return; // 用户显式设置过 tipText保持不变
if(mode==StellarX::ButtonMode::NORMAL)
if (mode == StellarX::ButtonMode::NORMAL)
tipLabel.setText(tipTextClick);
else if(mode==StellarX::ButtonMode::TOGGLE)
else if (mode == StellarX::ButtonMode::TOGGLE)
tipLabel.setText(click ? tipTextOn : tipTextOff);
}

View File

@@ -17,21 +17,43 @@ void Canvas::clearAllControls()
controls.clear();
}
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();
setlinecolor(canvasBorderClor);//设置线色
setfillcolor(canvasBkClor);//设置填充色
if(StellarX::FillMode::Null != canvasFillMode)
setfillcolor(canvasBkClor);//设置填充色
setfillstyle((int)canvasFillMode);//设置填充模式
setlinestyle((int)canvasLineStyle, canvaslinewidth);
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
saveBackground(x, y, width, height);
// 恢复背景(清除旧内容)
restBackground();
// 在绘制画布之前,先恢复并更新背景快照:
// 1. 如果已有快照,则先回贴旧快照以清除之前的内容。
// 2. 当坐标或尺寸变化,或缓存图像无效时,丢弃旧快照并重新抓取新的背景。
if (hasSnap)
{
// 恢复旧快照,清除上一次绘制
restBackground();
// 如果位置或尺寸变了,或没有有效缓存,则重新抓取
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || 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);
}
// 再次恢复最新快照,确保绘制区域干净
restBackground();
//根据画布形状绘制
switch (shape)
{
@@ -76,6 +98,9 @@ bool Canvas::handleEvent(const ExMessage& msg)
void Canvas::addControl(std::unique_ptr<Control> control)
{
//坐标转化
control->setX(control->getLocalX() + this->x);
control->setY(control->getLocalY() + this->y);
control->setParent(this);
controls.push_back(std::move(control));
dirty = true;
@@ -155,9 +180,173 @@ void Canvas::setDirty(bool dirty)
void Canvas::onWindowResize()
{
Control::onWindowResize(); // 先处理自
for (auto& ch : controls) // 再转发给所有子控件
ch->onWindowResize();
// 先处理自身的快照等逻辑
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();
}
}
void Canvas::requestRepaint(Control* parent)
@@ -166,10 +355,7 @@ void Canvas::requestRepaint(Control* parent)
{
for (auto& control : controls)
if (control->isDirty() && control->IsVisible())
{
control->draw();
break;
}
}
else
onRequestRepaintAsRoot();

View File

@@ -54,6 +54,27 @@ void Control::onWindowResize()
discardBackground();
setDirty(true);
}
void Control::setLayoutMode(StellarX::LayoutMode layoutMode_)
{
this->layoutMode = layoutMode_;
}
void Control::steAnchor(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()

View File

@@ -59,7 +59,7 @@ void Dialog::draw()
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坐标
outtextxy(tx, ty, LPCTSTR(line.c_str()));
@@ -170,33 +170,71 @@ void Dialog::Show()
if (modal)
{
// 模态对话框需要阻塞当前线程直到对话框关闭
while (show && !close)
if (modal)
{
// 记录当前窗口客户区尺寸,供轮询对比
RECT rc0;
GetClientRect(hWnd.getHwnd(), &rc0);
int lastW = rc0.right - rc0.left;
int lastH = rc0.bottom - rc0.top;
// 处理消息
ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
while (show && !close)
{
handleEvent(msg);
// ① 轮询窗口尺寸(不依赖 WM_SIZE
RECT rc;
GetClientRect(hWnd.getHwnd(), &rc);
const int cw = rc.right - rc.left;
const int ch = rc.bottom - rc.top;
// 检查是否需要关闭
if (shouldClose)
if (cw != lastW || ch != lastH)
{
Close();
break;
lastW = cw;
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 (dirty)
{
requestRepaint(parent);
FlushBatchDraw();
}
// 避免CPU占用过高
Sleep(10);
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
}
else
{
// 非模态仍由主循环托管
dirty = true;
}
// 模态对话框关闭后执行清理
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
@@ -517,7 +555,7 @@ void Dialog::getTextSize()
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
for (auto text : lines)
for (auto& text : lines)
{
int w = textwidth(LPCTSTR(text.c_str()));
int h = textheight(LPCTSTR(text.c_str()));
@@ -609,6 +647,13 @@ void Dialog::restBackground()
putimage(saveBkX - BorderWidth, saveBkY - BorderWidth,saveBkImage);
}
void Dialog::addControl(std::unique_ptr<Control> control)
{
control->setParent(this);
controls.push_back(std::move(control));
dirty = true;
}
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
// 此方法在对话框不可见且被标记为待清理时由 draw() 或 handleEvent() 调用。
@@ -634,6 +679,23 @@ void Dialog::performDelayedCleanup()
FlushBatchDraw();
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;
pendingCleanup = false;
@@ -684,11 +746,8 @@ void Dialog::requestRepaint(Control* parent)
if (this == parent)
{
for (auto& control : controls)
if (control->isDirty()&&control->IsVisible())
{
if (control->isDirty() && control->IsVisible())
control->draw();
break;
}
}
else

View File

@@ -165,22 +165,37 @@ TabControl::~TabControl()
void TabControl::draw()
{
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();
// 在绘制 TabControl 之前,先恢复并更新背景快照:
if (hasSnap)
{
// 先回贴旧快照,清除之前的绘制
restBackground();
// 如位置或尺寸变化,丢弃旧快照并重新抓取
if (!saveBkImage || saveBkX != this->x || saveBkY != this->y || 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);
}
// 再次恢复最新背景,保证绘制区域干净
restBackground();
// 绘制画布背景和基本形状及其子画布控件
Canvas::draw();
for (auto& c : controls)
{
c.first->setDirty(true);
c.first->draw();
}
for (auto& c : controls)
if(c.second->IsVisible())
{
c.second->setDirty(true);
c.second->draw();
}
{
c.second->setDirty(true);
c.second->draw();
}
dirty = false;
}
@@ -216,6 +231,7 @@ void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>
controls[idx].first->setParent(this);
controls[idx].first->enableTooltip(true);
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
controls[idx].first->setOnToggleOnListener([this,idx]()
{
controls[idx].second->setIsVisible(true);
@@ -291,12 +307,19 @@ void TabControl::setIsVisible(bool visible)
void TabControl::onWindowResize()
{
Control::onWindowResize();
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
// 调用基类的窗口变化处理,丢弃快照并标记脏
Control::onWindowResize();
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
initTabBar();
initTabPage();
// 转发窗口尺寸变化给所有页签按钮和页面
for (auto& c : controls)
{
c.first->onWindowResize();
c.second->onWindowResize();
}
// 尺寸变化后需要重绘自身
dirty = true;
}
int TabControl::getActiveIndex() const
@@ -364,15 +387,9 @@ void TabControl::requestRepaint(Control* parent)
for (auto& control : controls)
{
if (control.first->isDirty() && control.first->IsVisible())
{
control.first->draw();
break;
}
else if (control.second->isDirty()&&control.second->IsVisible())
{
control.second->draw();
break;
}
}
}

View File

@@ -87,28 +87,35 @@ void Table::initTextWaH()
if (h > maxLineH)
maxLineH = h;
// 列的像素宽 = 内容宽 + 左右 padding
// 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j)
contentW += (colWidths[j] + 2 * padX) + colGap;
// 列宽包含左右 padding在计算完最大文本宽度后加上 2*padX 作为单元格内边距
for (size_t j = 0; j < colWidths.size(); ++j) {
colWidths[j] += 2 * padX;
}
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY;
const int rowH = maxLineH + 2 * padY;
const int rowsH = rowH * rowsPerPage;
// 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j)
contentW += colWidths[j] + colGap;
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY;
const int rowH = maxLineH + 2 * padY;
const int rowsH = rowH * rowsPerPage;
// 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1);
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1);
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
this->localWidth = this->width;
this->localHeight = this->height;
}
void Table::initButton()
@@ -171,6 +178,7 @@ void Table::initButton()
if (pageNum) pageNum->setDirty(true);
}
});
isNeedButtonAndPageNum = false;
}
void Table::initPageNum()
@@ -214,7 +222,7 @@ void Table::drawPageNum()
pageNumtext += "页/共";
pageNumtext += std::to_string(totalPages);
pageNumtext += "";
if (nullptr == pageNum)
if (nullptr == pageNum || isNeedButtonAndPageNum)
initPageNum();
pageNum->setText(pageNumtext);
pageNum->textStyle = this->textStyle;
@@ -226,7 +234,7 @@ void Table::drawPageNum()
void Table::drawButton()
{
if (nullptr == prevButton || nullptr == nextButton)
if ((nullptr == prevButton || nullptr == nextButton)|| isNeedButtonAndPageNum)
initButton();
this->prevButton->textStyle = this->textStyle;
@@ -242,6 +250,43 @@ void Table::drawButton()
}
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)
:Control(x, y, 0, 0)
{
@@ -317,13 +362,27 @@ void Table::draw()
setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT);
}
//确保在绘制任何表格内容之前捕获背景
// 临时恢复样式,确保捕获正确的背景
if ((!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height)||!saveBkImage)
saveBackground(this->x, this->y, this->width, this->height);
// 恢复背景清除内容
restBackground();
// 绘制表头
// 在绘制前先恢复并更新背景快照:
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
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);
}
// 恢复最新的背景,保证绘制区域干净
restBackground();
// 绘制表头
dX = x;
dY = y;
@@ -365,7 +424,7 @@ bool Table::handleEvent(const ExMessage& msg)
void Table::setHeaders(std::initializer_list<std::string> headers)
{
this->headers.clear();
for (auto lis : headers)
for (auto& lis : headers)
this->headers.push_back(lis);
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedDrawHeaders = true; // 标记需要重新绘制表头
@@ -464,6 +523,17 @@ void Table::setTableBorderWidth(int width)
this->dirty = true;
}
void Table::onWindowResize()
{
Control::onWindowResize(); // 先处理自己
if (this->prevButton && this->nextButton && this->pageNum)
{
prevButton->onWindowResize();
nextButton->onWindowResize();
pageNum->onWindowResize();
}
}
int Table::getCurrentPage() const
{
return this->currentPage;
@@ -519,4 +589,17 @@ int Table::getTableBorderWidth() const
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,159 +1,406 @@
#include "Window.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);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
SetWindowLong(h, GWL_STYLE, style);
this->pendingW = this->width = width;
this->pendingH = this->height = height;
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;
this->pendingH = this->height = height;
this->windowMode = mode;
this->wBkcolor = bkcloc;
RECT rcFrame{ 0, 0, minClientW, minClientH };
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
DWORD ex = GetWindowLong(hWnd, GWL_EXSTYLE);
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;
this->pendingH = this->height = height;
this->windowMode = mode;
this->wBkcolor = bkcloc;
this->headline = headline;
localwidth = minClientW = pendingW = width = w;
localheight = minClientH = pendingH = height = h;
windowMode = mode;
}
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()
{
if (background)
delete background;
// 析构:释放背景图对象并关闭 EasyX 图形环境
if (background) delete background;
background = nullptr;
closegraph(); // 确保关闭图形上下文
closegraph();
}
// ---------------- 原生消息钩子----------------
void Window::draw() {
// 使用 EasyX 创建基本窗口
if (!hWnd)
hWnd = initgraph(width, height, windowMode);
// **启用窗口拉伸支持**:添加厚边框和最大化按钮样式
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);
/**
* WndProcThun
* 作用:替换 EasyX 的窗口过程,接管关键消息。
* 关键处理:
* - WM_ERASEBKGND返回 1交由自绘清屏避免系统擦背景造成闪烁。
* - WM_ENTERSIZEMOVE开始拉伸 → isSizing=true 且 WM_SETREDRAW(FALSE) 冻结重绘。
* - WM_SIZING拉伸中 → 仅做“最小尺寸夹紧”(按被拖边回推),不回滚、不绘制。
* - WM_EXITSIZEMOVE结束拉伸 → 读取最终客户区尺寸 → 标记 needResizeDirty解冻并刷新。
* - WM_GETMINMAXINFO提供系统最小轨迹限制四边一致
*/
LRESULT CALLBACK Window::WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)
{
auto* self = reinterpret_cast<Window*>(GetWindowLongPtr(h, GWLP_USERDATA));
if (!self)
{
return DefWindowProc(h, m, w, l);
}
// 关键点①:禁止系统擦背景,避免和我们自己的清屏/双缓冲打架造成闪烁
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);
}
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& control : controls)
{
control->draw();
for (auto& c : controls)
{
c->draw();
}
for (auto& d : dialogs)
{
d->draw();
}
// (如果有初始对话框,也可绘制 dialogs
EndBatchDraw();
}
/**
* draw(imagePath)
* 作用:在 draw() 的基础上加载并绘制背景图;其它流程完全一致。
* 注意这里按当前窗口客户区大小加载背景图loadimage 的 w/h保证铺满。
*/
void Window::draw(std::string imagePath)
{
// 使用指定图片绘制窗口背景(铺满窗口)
this->background = new IMAGE(width, height);
bkImageFile = imagePath;
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);
if (!hWnd)
{
hWnd = initgraph(width, height, windowMode);
}
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& control : controls)
{
control->setDirty(true);
control->draw();
}
for (auto& dlg : dialogs) dlg->draw();
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()
{
ExMessage msg;
bool running = true;
ExMessage msg;
bool running = true;
while (running)
{
bool consume = false;// 是否处理了消息
// 说明:统一使用 needResizeDirty 作为“收口重绘”的唯一标志位
// 不再引入额外 pendingResize 等状态,避免分叉导致状态不一致。
while (running)
{
bool consume = false;
// 处理所有消息
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
{
if (msg.message == WM_CLOSE)
{
running = false;
return 0;
}
if (msg.message == WM_SIZE)
{
if (msg.wParam != SIZE_MINIMIZED)
{
const int nw = LOWORD(msg.lParam);
const int nh = HIWORD(msg.lParam);
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
{
if (msg.message == WM_CLOSE)
{
running = false;
return 0;
}
// 仅在尺寸真的变化时标脏
if (nw > 0 && nh > 0 || (nw != width || nh != height))
{
pendingW = nw;
pendingH = nh;
needResizeDirty = true;
}
}
continue;//在末尾重绘制窗口
}
// 优先处理对话框事件
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
{
auto& d = *it;
if (d->IsVisible() && !d->model())
consume = d->handleEvent(msg);
if (consume)
break;
}
// 保险:如果 EX_WINDOW 转译了 GETMINMAXINFO同样按最小客户区折算处理
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;
}
//普通控件
if (!consume)
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
{
consume = (*it)->handleEvent(msg);
if (consume)
break;
}
// 关键点⑥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;
pendingH = nh;
// 在“非拉伸阶段”的 WM_SIZE例如最大化/还原/程序化调整)直接触发收口
needResizeDirty = true;
}
}
continue;
}
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
{
auto& d = *it;
if (d->IsVisible() && !d->model())
{
consume = d->handleEvent(msg);
}
if (consume) break;
}
if (!consume)
{
for (auto& c : controls)
{
consume = c->handleEvent(msg);
if (consume) break;
}
}
}
//如果有对话框打开或者关闭强制重绘
bool needredraw = false;
for (auto& d : dialogs)
{
@@ -193,163 +440,380 @@ int Window::runEventLoop()
needredraw = false;
}
//—— 统一收口”:尺寸变化后的** 一次性** 重绘 ——
if (needResizeDirty)
{
//确保窗口不会小于初始尺寸
if (pendingW >= width && pendingH >= height)
Resize(nullptr, pendingW, pendingH);
else
Resize(nullptr, width, height);
if (background)
{
delete background;
background = new IMAGE;
loadimage(background, bkImageFile.c_str(), pendingW, pendingH);
putimage(0, 0, background);
}
// —— 统一收口needResizeDirty 为真时执行一次性重绘——
if (needResizeDirty)
{
// 以“实际客户区尺寸”为准,防止 pending 与真实尺寸出现偏差
RECT clientRect;
GetClientRect(hWnd, &clientRect);
int actualWidth = clientRect.right - clientRect.left;
int actualHeight = clientRect.bottom - clientRect.top;
// 标记所有控件/对话框为脏,确保都补一次背景/外观
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;
}
const int finalW = (std::max)(minClientW, actualWidth);
const int finalH = (std::max)(minClientH, actualHeight);
// 降低占用
Sleep(10);
}
return 1;
// 变化过大/异常场景保护
if (finalW != width || finalH != height)
{
if (abs(finalW - width) > 1000 || abs(finalH - height) > 1000)
{
// 认为是异常帧,跳过本次(不改变任何状态)
needResizeDirty = false;
continue;
}
// 再次冻结窗口更新,保证批量绘制的原子性
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
BeginBatchDraw();
// 调整底层画布尺寸
if (finalW != width || finalH != height)
{
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)
{
adaptiveLayout(c,finalH,finalW);
c->onWindowResize();
}
for (auto& d : dialogs)
{
if (auto dd = dynamic_cast<Dialog*>(d.get()))
{
dd->setDirty(true);
dd->setInitialization(true);
}
}
// 统一批量绘制
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; // 收口完成,清标志
}
// 轻微睡眠,削峰填谷(不阻塞拖拽体验)
Sleep(10);
}
return 1;
}
// ---------------- 其余接口 ----------------
void Window::setBkImage(std::string pImgFile)
{
if(nullptr == background)
this->background = new IMAGE;
else
delete background;
this->background = new IMAGE;
this->bkImageFile = pImgFile;
loadimage(background, pImgFile.c_str(), width, height, true);
putimage(0, 0, background);
BeginBatchDraw();
for (auto& c : controls)
{
c->setDirty(true);
c->draw();
}
for (auto& c : dialogs)
{
c->setDirty(true);
c->draw();
}
EndBatchDraw();
// 更换背景图:立即加载并绘制一次;同时将所有控件标 dirty 并重绘
if (background) delete background;
background = new IMAGE;
bkImageFile = std::move(pImgFile);
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->setDirty(true);
d->draw();
}
EndBatchDraw();
}
void Window::setBkcolor(COLORREF c)
{
wBkcolor = c;
setbkcolor(wBkcolor);
cleardevice();
// 初次绘制所有控件(双缓冲)
BeginBatchDraw();
for (auto& c : controls)
{
c->setDirty(true);
c->draw();
}
for (auto& c : dialogs)
{
c->setDirty(true);
c->draw();
}
EndBatchDraw();
// 更换纯色背景:立即清屏并批量重绘控件/对话框
wBkcolor = c;
setbkcolor(wBkcolor);
cleardevice();
BeginBatchDraw();
for (auto& c : controls)
{
c->setDirty(true);
c->draw();
}
for (auto& d : dialogs)
{
d->setDirty(true);
d->draw();
}
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)
{
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
{
for (const auto& dptr : dialogs)
{
if (!dptr) continue;
// 只检查 Dialog 类型的控件
Dialog* d = dynamic_cast<Dialog*>(dptr.get());
//检查是否有非模态对话框可见,并且消息内容一致
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
return true;
}
return false;
// 查询是否存在“可见且非模态”的对话框(用于避免重复弹)
for (const auto& dptr : dialogs)
{
if (!dptr) continue;
if (auto* d = dynamic_cast<Dialog*>(dptr.get()))
{
if (d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
{
return true;
}
}
}
return false;
}
HWND Window::getHwnd() const
{
return hWnd;
return hWnd;
}
int Window::getWidth() const
{
return this->pendingW;
// 注意:这里返回 pendingW
// 表示“最近一次收到的尺寸”(可能尚未应用到画布,最终以收口时的 width 为准)
return pendingW;
}
int Window::getHeight() const
{
return this->pendingH;
// 同上,返回 pendingH与 getWidth 对应)
return pendingH;
}
std::string Window::getHeadline() const
{
return this->headline;
return headline;
}
COLORREF Window::getBkcolor() const
{
return this->wBkcolor;
return wBkcolor;
}
IMAGE* Window::getBkImage() const
{
return this->background;
return background;
}
std::string Window::getBkImageFile() const
{
return this->bkImageFile;
return bkImageFile;
}
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());
}
}
}

File diff suppressed because it is too large Load Diff