Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a41f9e03a | |||
| b765e54eff | |||
| c66cad3423 | |||
| 348cf666f5 | |||
| 572802e2ee | |||
| 10a91f9a64 | |||
| eb96e5a64e | |||
| 5cb59b3652 | |||
| 0c1cf2938f | |||
| aa0fa8d320 | |||
| 43564ef675 | |||
| 53dc237e46 | |||
| 46febdb973 | |||
| f05962954f | |||
| 58d4e8ab2f | |||
| 5420bfd644 | |||
| cc08187ced | |||
| c10e72b3fe | |||
| 6218ba54e3 | |||
| 7b087e296a | |||
| 8dee285de8 | |||
| d16b9bc226 | |||
| 270c6f5293 | |||
| 4bb0352088 | |||
| c4852d080f |
@@ -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
|
||||
@@ -1,685 +0,0 @@
|
||||
# 中文 API 文档
|
||||
|
||||
[English API Documentation](English API Documentation.md)
|
||||
|
||||
下面是 **StellarX GUI 框架** 各主要类和函数的 API 文档。每个类的用途、接口、参数和返回值等详细信息如下:
|
||||
|
||||
### Control 类 (抽象基类)
|
||||
|
||||
**描述:** `Control` 是所有控件的抽象基类,定义了通用的属性和接口,包括位置、尺寸、重绘标记等。它提供了绘图状态保存与恢复机制,确保控件绘制不影响全局状态。`Control` 本身不能直接实例化。
|
||||
|
||||
- **主要属性:**
|
||||
- `x, y`:控件左上角坐标。
|
||||
- `width, height`:控件宽度和高度。
|
||||
- `dirty`:是否需要重绘的标志。
|
||||
- `show`:控件是否可见。
|
||||
- `rouRectangleSize`:`StellarX::RouRectangle` 结构,保存圆角矩形控件圆角的宽高半径。
|
||||
- **注意:** `Control` 会在内部维护当前绘图状态(字体、颜色、线型等)的备份指针,用于绘制前保存状态、绘制后恢复。
|
||||
- **主要接口方法:**
|
||||
- `virtual void draw() = 0;`
|
||||
**描述:** 纯虚函数,绘制控件内容。具体控件类需实现自己的绘制逻辑。
|
||||
- `virtual bool handleEvent(const ExMessage& msg) = 0;`
|
||||
**描述:** 纯虚函数,处理事件消息(如鼠标、键盘事件)。返回值表示事件是否被该控件消费(`true` 则不再向其他控件传播)。
|
||||
- `void saveBackground(int x, int y, int w, int h);`
|
||||
**描述:** 保存控件区域 `(x, y, w, h)` 的背景图像快照,以便需要时恢复背景。常用于控件需要暂时覆盖背景(如弹出框)。
|
||||
- `void restBackground();`
|
||||
**描述:** 恢复最近保存的背景快照,将之前保存的背景图像绘制回控件原位置。典型用于控件隐藏或重绘前清除旧内容。
|
||||
- `void discardBackground();`
|
||||
**描述:** 丢弃当前保存的背景快照并释放相关资源。当窗口重绘或尺寸变化导致背景失效时调用,避免错误使用旧快照。
|
||||
- *属性访问和设置:*
|
||||
- `int getX() const, getY() const, getWidth() const, getHeight() const` 等:获取控件位置和尺寸各属性。
|
||||
- `int getRight() const, getBottom() const`:获取控件右边界和下边界坐标(`x + width`,`y + height`)。
|
||||
- `void setX(int nx), setY(int ny), setWidth(int w), setHeight(int h)`:设置控件位置或尺寸,对应属性改变后自动将控件标记为需要重绘 (`dirty = true`)。
|
||||
- `void setDirty(bool d)`:手动设置控件的重绘标志。
|
||||
- `void setShow(bool visible)`:设置控件可见性。如果设为 `false`,则控件将不绘制自身内容。
|
||||
- `bool isVisible() const`:返回控件当前可见状态(`show` 标志)。**注意:** 控件被标记为隐藏时,其 `draw()` 通常不会被调用。
|
||||
- *其他:*
|
||||
- `virtual bool model() const = 0;`
|
||||
**描述:** 纯虚函数,用于检查控件是否为“模态”。仅对话框控件需要实现(模态对话框返回 `true`),其他非对话框控件可忽略(返回 `false`)。窗口事件循环根据此函数区分模态对话框与普通控件的事件处理优先级。
|
||||
- `void saveStyle(), restoreStyle();` *(受保护方法)*
|
||||
**描述:** 保存当前全局绘图样式(字体、颜色、线型等)并恢复。控件在 `draw()` 中应先调用 `saveStyle()` 备份当前样式,绘制完毕后调用 `restoreStyle()` 以免影响其他绘制操作。
|
||||
|
||||
### Window 类 (应用主窗口)
|
||||
|
||||
**描述:** `Window` 类表示应用程序的主窗口,负责窗口的创建、消息循环、控件管理和整体渲染。一个应用通常创建一个 Window 实例作为 GUI 的根容器。
|
||||
|
||||
- **构造函数:**
|
||||
- `Window(int width, int height, int mode, COLORREF bkColor = ..., std::string headline = "窗口")`
|
||||
创建一个指定宽、高的主窗口。`mode` 参数指定图形模式(如是否双缓冲,EasyX 使用 NULL 或 `INIT_RENDERMANUAL` 等模式),`bkColor` 是窗口背景色,`headline` 是窗口标题文本。构造时不会立即显示窗口,需调用 `draw()`。
|
||||
- **主要方法:**
|
||||
- `void draw();`
|
||||
**描述:** 初始化并显示窗口,创建绘图界面。将使用设定的模式创建绘图窗口(调用 EasyX 的 `initgraph`),并应用窗口标题和背景色。然后绘制已添加的控件。通常在创建 Window 后立即调用一次。
|
||||
- `void draw(std::string pImgFile);`
|
||||
**描述:** 与无参版本类似,但使用指定路径的图像文件作为窗口背景图。该方法会加载图像并按照窗口尺寸绘制为背景,然后再绘制子控件。
|
||||
- `void runEventLoop();`
|
||||
**描述:** 进入窗口消息处理循环。该循环持续获取用户输入事件 (`peekmessage`) 并将其分发给子控件或对话框处理:
|
||||
- 优先将事件传递给所有已打开的非模态对话框(`Dialog`,`model() == false` 且 `isVisible() == true`)。一旦某个对话框的 `handleEvent` 返回 `true`(事件被消费),停止继续传递。
|
||||
- 若事件未被对话框消费,再按添加顺序将事件传递给普通控件列表中的各控件的 `handleEvent`(通常 Container 控件会向其子控件继续传播)。
|
||||
- 处理完事件后,如果有对话框正在显示(或刚关闭),或者 `dialogClose` 标志为真,则强制重绘一帧界面:包括所有普通控件和对话框,以确保界面更新。
|
||||
- 循环每帧暂停 10 毫秒,防止 CPU 占用过高。
|
||||
- **注意:** 该函数在调用后不会返回,直到窗口接收到关闭消息 (`WM_CLOSE`),此时函数内部会跳出循环结束。
|
||||
- `void setBkImage(IMAGE* img)` / `void setBkImage(std::string filePath);`
|
||||
**描述:** 更改窗口背景图像。可以直接传入一个已加载的 `IMAGE` 指针,或提供图像文件路径让函数内部加载。调用此函数会触发窗口重绘当前所有控件和对话框以应用新的背景。
|
||||
- `void setBkcolor(COLORREF c);`
|
||||
**描述:** 设置窗口背景颜色,并立即用该颜色清除屏幕背景(不销毁已有背景图,仅覆盖绘制)。
|
||||
- `void setHeadline(std::string title);`
|
||||
**描述:** 设置窗口标题文本。窗口创建后可随时更改标题;如果窗口已显示,则通过 WinAPI `SetWindowText` 立即更新窗口标题栏。
|
||||
- `void addControl(std::unique_ptr<Control> control);`
|
||||
**描述:** 向窗口添加一个普通控件。Window 维护一个控件列表,`draw()` 和 `runEventLoop()` 会据此绘制及派发事件。此方法接收智能指针,调用后 Window 接管控件的生命周期。
|
||||
- `void addDialog(std::unique_ptr<Control> dialog);`
|
||||
**描述:** 向窗口添加一个对话框控件(一般为 `Dialog` 类型)。对话框与普通控件分开管理,其事件和绘制有独立逻辑。
|
||||
- `bool hasNonModalDialogWithCaption(const std::string& caption) const;`
|
||||
**描述:** 检查当前是否已有**非模态**对话框,其标题(caption)与给定字符串匹配。如果存在返回 `true`。通常用于避免重复打开相同对话框。
|
||||
**重载版本:** `bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& text) const;`
|
||||
除了标题,还同时比较对话框内容文本,以更严格地判断重复。`MessageBox::showAsync` 内部使用此方法防止弹出重复的提示框。
|
||||
- *信息获取方法:*
|
||||
- `HWND getHwnd() const;` 获取底层窗口句柄(EasyX 初始化返回的 HWND)。
|
||||
- `int getWidth() const, getHeight() const;` 获取窗口宽度和高度。
|
||||
- `std::string getHeadline() const;` 获取当前窗口标题。
|
||||
- `COLORREF getBkcolor() const;` 获取窗口背景色。
|
||||
- `IMAGE* getBkImage() const;` 获取当前窗口背景图像对象指针(如果有)。
|
||||
- `std::vector<std::unique_ptr<Control>>& getControls();` 引用返回窗口维护的控件列表(可以对其遍历,但一般不手动修改)。
|
||||
|
||||
### Canvas 类 (容器控件)
|
||||
|
||||
**描述:** `Canvas` 是一种可包含子控件的画布容器,用于将其他控件分组、统一背景和边框样式,以及实现复杂布局。Canvas 自身也是一个控件,继承自 `Control`。
|
||||
|
||||
- **特性:**
|
||||
- 支持四种矩形形状背景(普通矩形/圆角矩形,各有无边框和有边框版本),可通过 `setShape` 设置。
|
||||
- 可自定义容器背景颜色 (`canvasBkColor`)、边框颜色 (`canvasBorderColor`)、边框线型 (`canvasLineStyle`)、填充模式 (`canvasFillMode`,纯色/图案/无填充) 等。
|
||||
- Canvas 能自动管理子控件的生命周期(使用 `addControl` 添加的子控件在 Canvas 销毁时自动释放)。
|
||||
- Canvas 会在绘制时遍历并绘制所有添加的子控件,并在处理事件时优先将事件传递给子控件列表中最后添加的控件(Z 顺序)。
|
||||
- **重要数据成员:**
|
||||
- `std::vector<std::unique_ptr<Control>> controls;` 子控件列表。
|
||||
- `StellarX::ControlShape shape;` 容器背景形状(默认矩形)。
|
||||
- `StellarX::FillMode canvasFillMode;` 背景填充模式(默认实色填充)。
|
||||
- `StellarX::LineStyle canvasLineStyle;` 边框线型(默认实线)。
|
||||
- `int canvaslinewidth;` 边框线宽像素值。
|
||||
- `COLORREF canvasBorderColor, canvasBkColor;` 容器边框颜色与背景色。
|
||||
- `StellarX::LayoutKind Kind;` (预留)布局管理类型,当前未深入实现,可用于标识布局策略(如绝对布局、水平/垂直布局等)。
|
||||
- **注意:** Canvas 重载了 `isVisible()` 始终返回 false,因为 Canvas 通常不作为独立可见单元(事件循环中不直接处理 Canvas,而由其子控件处理事件)。但这不影响 Canvas 作为容器的绘制和子控件事件派发。
|
||||
- **主要方法:**
|
||||
- `Canvas(); Canvas(int x, int y, int w, int h);`
|
||||
**描述:** 构造一个 Canvas 容器,可以指定位置 `(x,y)` 及初始尺寸。
|
||||
- `void addControl(std::unique_ptr<Control> control);`
|
||||
**描述:** 添加子控件到 Canvas。被添加控件将成为 Canvas 内容的一部分,其坐标相对于 Canvas 左上角(Canvas 并不主动调整子控件位置,需在添加前设置好子控件位置)。添加后 Canvas 会将自身标记为需要重绘。
|
||||
- `void setShape(StellarX::ControlShape shape);`
|
||||
**描述:** 设置 Canvas 背景形状。支持 `RECTANGLE`、`B_RECTANGLE`(无边框矩形)、`ROUND_RECTANGLE`(圆角矩形)、`B_ROUND_RECTANGLE`(无边框圆角矩形)。如果传入圆形或椭圆形(CIRCLE/ELLIPSE等),Canvas 不支持,将自动按矩形处理。
|
||||
- `void setCanvasFillMode(StellarX::FillMode mode);`
|
||||
**描述:** 设置背景填充模式,如纯色 (`Solid`)、无填充 (`Null`)、预定义图案填充 (`Hatched`) 或自定义图案/图像填充 (`Pattern/DibPattern`),需结合 `setfillstyle` 使用。默认为纯色。
|
||||
- `void setBorderColor(COLORREF color);` / `void setCanvasBkColor(COLORREF color);` / `void setCanvasLineStyle(StellarX::LineStyle style);` / `void setLinewidth(int width);`
|
||||
**描述:** 分别设置容器边框颜色、背景色、边框线型和线宽。这些改变都会使 Canvas 需要重绘。
|
||||
- `void draw() override;`
|
||||
**描述:** 绘制 Canvas 背景和其所有子控件。Canvas 绘制步骤:
|
||||
1. 根据设定的背景颜色/填充模式和形状绘制容器背景(例如填充矩形或圆角矩形区域)。
|
||||
2. 遍历子控件列表,调用每个子控件的 `draw()` 方法绘制它们。绘制前对每个子控件先设置其 `dirty = true`,确保子控件按需重绘。
|
||||
3. 恢复绘图样式,标记自身为已绘制(`dirty = false`)。
|
||||
*实现细节:* Canvas 使用 `saveStyle()/restoreStyle()` 保护全局绘图状态,并依赖 EasyX 的填充/绘制函数根据 `shape` 绘制矩形或圆角矩形背景。
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**描述:** 将事件按照子控件的添加顺序逆序传递(后添加的子控件先处理)。对 `controls` 列表从末尾向前调用每个子控件的 `handleEvent`,若某子控件返回 `true`(表示事件已被处理),则停止传递并返回 `true`;若所有子控件都未消费事件,则返回 `false` 表示未处理。
|
||||
*用途:* 使得堆叠在顶层的子控件(通常列表后面的)有更高优先权处理事件,例如多个重叠控件的情形。
|
||||
- `void clearAllControls();` *(protected 方法)*
|
||||
**描述:** 清除并移除所有子控件。会释放智能指针列表中所有控件对象。一般在容器销毁或重置时使用。普通情况下不直接调用,由容器析构自动完成。
|
||||
|
||||
### Label 类 (静态文本标签)
|
||||
|
||||
**描述:** `Label` 用于显示一段静态文本,可设置文字颜色、背景透明/不透明及样式等。Label 不处理用户输入事件(纯展示)。
|
||||
|
||||
- **构造函数:**
|
||||
- `Label(); Label(int x, int y, std::string text = "标签", COLORREF textColor = BLACK, COLORREF bkColor = WHITE);`
|
||||
**描述:** 创建一个 Label 控件。可指定初始位置 `(x,y)`、显示文本内容和文本颜色、背景色等。默认文本为“标签”,默认文字黑色、背景白色。
|
||||
- **主要属性:**
|
||||
- `std::string text;` 显示的文字内容。
|
||||
- `COLORREF textColor;` 文本颜色。
|
||||
- `COLORREF textBkColor;` 文本背景色(在背景不透明时生效)。
|
||||
- `bool textBkDisap;` 是否背景透明显示文本。若为 `true`,Label 绘制文字时使用透明背景模式。
|
||||
- `StellarX::ControlText textStyle;` 文字样式结构,包括字体、高度、粗细等属性(继承自 Control 默认值,例如 `字体:微软雅黑`)。
|
||||
- **注意:** Label 大小 (`width, height`) 初始为 0,通常 Label 大小根据文本自动适应,无需手动设置宽高。Label 绘制时会截取背景图像以覆盖文字区域刷新。
|
||||
- **主要方法:**
|
||||
- `void setTextdisap(bool transparent);`
|
||||
**描述:** 设置文本背景是否透明显示。传入 `true` 则文字背景透明,显示时不遮挡底下背景;`false` 则文字背景为 `textBkColor` 实色矩形。
|
||||
- `void setTextColor(COLORREF color);` / `void setTextBkColor(COLORREF color);`
|
||||
**描述:** 设置文字颜色或背景色。更改后会标记 Label 需要重绘。
|
||||
- `void setText(std::string text);`
|
||||
**描述:** 更改显示的文本内容,并标记需要重绘。支持中文字符串。
|
||||
- `void draw() override;`
|
||||
**描述:** 绘制文本标签。根据 `textBkDisap` 决定调用 EasyX 的 `setbkmode(TRANSPARENT)` 或 `OPAQUE`,并设置背景色 `setbkcolor(textBkColor)`。然后设置文本颜色和样式,调用 `outtextxy(x, y, text)` 输出文字。为防止文字重绘留痕迹,`Label::draw` 会先保存文字区域背景 (`saveBackground`),绘制完成后再恢复样式。首次绘制后将 `dirty` 置为 false。
|
||||
- `bool handleEvent(...) override;`
|
||||
**描述:** Label 不处理任何事件,始终返回 false。
|
||||
- `void hide();`
|
||||
**描述:** 将 Label 内容隐藏。其实现是调用 `restBackground()` 恢复先前保存的背景,然后 `discardBackground()` 释放快照,并将 `dirty` 设为 false。这通常在 Label 用作 Tooltip 提示框时,由宿主控件调用,用于移除提示文本的显示而不重绘整个界面。
|
||||
- **使用场景:** Label 常用于界面中的静态说明文字、标题、状态栏信息等。通过配合 `textStyle` 可以修改字体、大小、是否加粗等。默认情况下 Label 背景不透明白底,如需与窗口背景融合可调用 `setTextdisap(true)` 使背景透明。
|
||||
|
||||
### Button 类 (按钮控件)
|
||||
|
||||
**描述:** `Button` 提供普通按钮和切换按钮(两态)的功能,可响应点击和悬停事件。支持设置各种样式(形状、颜色)和点击回调,是交互的主要控件之一。
|
||||
|
||||
- **工作模式:** 由 `StellarX::ButtonMode` 枚举定义:
|
||||
|
||||
- `NORMAL` 普通按钮:每次点击都会触发一次动作,按钮本身不保持按下状态。
|
||||
- `TOGGLE` 切换按钮:每次点击切换自身状态(选中/未选中),并触发不同的回调。
|
||||
- `DISABLED` 禁用按钮:不响应用户点击,显示为灰色且文字带删除线。
|
||||
- 可通过 `setButtonMode(ButtonMode mode)` 改变按钮模式。
|
||||
|
||||
- **外观形状:** 由 `StellarX::ControlShape` 定义:
|
||||
|
||||
- 支持矩形 (`RECTANGLE`/`B_RECTANGLE`)、圆角矩形 (`ROUND_RECTANGLE`/`B_ROUND_RECTANGLE`)、圆形 (`CIRCLE`/`B_CIRCLE`)、椭圆 (`ELLIPSE`/`B_ELLIPSE`) 八种形状(B_ 前缀表示无边框填充)。通过 `setButtonShape(ControlShape shape)` 设置按钮形状。
|
||||
- 注意:在切换 `ControlShape` 时,某些尺寸下圆形/椭圆以控件的 `width` 和 `height` 计算,圆形半径取较小的半径,椭圆按照矩形外接框绘制。
|
||||
|
||||
- **主要可配置属性:**
|
||||
|
||||
- 颜色:`buttonTrueColor`(被点击时颜色),`buttonFalseColor`(常态颜色),`buttonHoverColor`(鼠标悬停颜色),`buttonBorderColor`(边框颜色)。
|
||||
- 填充:`buttonFillMode`(填充模式,实色/图案/图像等),`buttonFillIma`(图案填充样式,如果 `FillMode == Hatched`),`buttonFileIMAGE`(指向自定义填充图像的指针,如果 `FillMode == DibPattern`)。
|
||||
- 文本:按钮显示的文字 `text` 及其样式 `textStyle`(字体、字号、颜色等)。另外提供 `cutText` 字符串用于文本过长时显示裁剪的内容。
|
||||
- Tooltip:`tipEnabled`(是否启用悬停提示),`tipTextClick`(NORMAL模式提示文本),`tipTextOn/Off`(TOGGLE模式下两种状态的提示文本),`tipFollowCursor`(提示框是否跟随鼠标移动),`tipDelayMs`(提示延迟毫秒),`tipOffsetX/Y`(提示框相对位置偏移),`tipLabel`(内部使用的 Label 控件呈现提示文本)。
|
||||
- 圆角大小:通过继承自 `Control` 的 `rouRectangleSize` 成员控制圆角矩形圆角的半径大小。提供 `setRoundRectangleWidth(int)` 和 `setRoundRectangleHeight(int)` 接口修改。
|
||||
|
||||
- **主要方法:**
|
||||
|
||||
- `void setOnClickListener(function<void()>&& callback);`
|
||||
**描述:** 设置按钮普通点击时的回调函数。当按钮模式为 NORMAL,在每次鼠标释放点击时调用该回调;当模式为 TOGGLE,则仅在每次点击状态发生变化时调用(取决于 toggle 逻辑)。
|
||||
|
||||
- `void setOnToggleOnListener(...)` / `void setOnToggleOffListener(...);`
|
||||
**描述:** 设置按钮在 TOGGLE 模式下由未选中变为选中、以及由选中变为未选中时分别触发的回调函数。当按钮模式为 NORMAL 或 DISABLED 时,这两个回调不会被调用。
|
||||
|
||||
- `void setButtonText(const char* text)` / `void setButtonText(std::string text);`
|
||||
**描述:** 更改按钮上的显示文本。支持 C 风格字符串或 `std::string`。更改文本后会重新计算文本宽高,并标记按钮需要重绘。
|
||||
|
||||
- `void setButtonBorder(COLORREF border)` / `void setButtonFalseColor(COLORREF color)`
|
||||
**描述:** 设置按钮边框颜色、常态下背景填充颜色。
|
||||
|
||||
- `void setFillMode(FillMode mode)` / `void setFillPattern(FillStyle pattern)` / `void setFillImage(const std::string& path)`
|
||||
**描述:** 设置按钮背景填充方式:
|
||||
|
||||
- `setFillMode` 改变填充模式(纯色、图案、位图等)。
|
||||
- 若填充模式为图案 (`Pattern`),需调用 `setFillPattern` 设置具体的图案样式 (`StellarX::FillStyle`)。
|
||||
- 若填充模式为位图 (`DibPattern`),可调用 `setFillImage` 加载指定路径的图像并用作填充(会自动调整图像大小与按钮一致)。
|
||||
|
||||
- `void setButtonClick(bool click);`
|
||||
**描述:** 外部设置按钮的“点击状态”。对于 TOGGLE 模式,`setButtonClick(true)` 会将按钮设为选中状态并触发 onToggleOn 回调,`false` 则触发 onToggleOff 回调。对于 NORMAL 模式,传入 true 将临时触发一次 onClick 回调(随后状态立即复位为未点击)。无论哪种模式,调用都会引起按钮重绘。**注意:** DISABLED 模式调用此函数没有效果。
|
||||
|
||||
- `bool isClicked() const;`
|
||||
**描述:** 返回按钮当前是否处于按下状态(仅对 TOGGLE 模式有意义,表示按钮选中状态)。
|
||||
|
||||
- 其他状态获取方法如 `getButtonText()`, `getButtonMode()`, `getButtonShape()`, `getFillMode()`, `getFillPattern()`, `getFillImage()`, `getButtonBorder()`, `getButtonTextColor()`, `getButtonTextStyle()`, `getButtonWidth()`, `getButtonHeight()` 一一提供,返回对应属性值。
|
||||
|
||||
- `void draw() override;`
|
||||
**描述:** 绘制按钮外观。根据按钮当前状态选择填充颜色:若禁用则用 `DISABLEDCOLOUR` 灰色并给文字加删除线效果,否则 `click` 状态优先,其次 `hover` 状态,最后默认未点击颜色。然后设置透明背景、边框颜色和文本样式,计算文字应绘制的位置并绘制文字。
|
||||
如果按钮文本长度超出按钮宽度,`draw()` 会自动调用 `cutButtonText()` 对文本进行裁剪,并用省略号表示超出部分,以确保文字在按钮内完整显示。绘制结束后恢复样式并将 `dirty` 置为 false。
|
||||
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**描述:** 处理与按钮相关的鼠标事件,包括鼠标按下、弹起、移动等:
|
||||
|
||||
- 检测鼠标是否悬停在按钮区域内(根据按钮形状调用内部 `isMouseInCircle/Ellipse` 辅助函数)。
|
||||
- 处理 `WM_LBUTTONDOWN`(鼠标按下):若按钮未禁用且鼠标位于按钮上,在 NORMAL 模式下将按钮标记为点击状态(按下),在 TOGGLE 模式下不改变状态(等待弹起时处理)。
|
||||
- 处理 `WM_LBUTTONUP`(鼠标弹起):若鼠标在按钮上且按钮可点击:
|
||||
- NORMAL 模式:如果之前已记录按下,则认为一次有效点击,调用 `onClickCallback`,然后清除点击状态。
|
||||
- TOGGLE 模式:切换按钮状态 `click = !click`。根据切换后的状态调用 `onToggleOnCallback` 或 `onToggleOffCallback`。
|
||||
- 每次有效点击后,都会通过 `flushmessage(EX_MOUSE|EX_KEY)` 清空输入消息队列,防止此次点击产生的消息(例如弹起)重复被处理。
|
||||
- 处理鼠标移出(在 `WM_MOUSEMOVE` 中检测):如果 NORMAL 模式下拖出按钮区域则取消按下状态,任何模式下 hover 状态变化都将使按钮重绘。
|
||||
- Tooltip 提示:如果启用了 `tipEnabled`,`handleEvent` 内会管理 `tipLabel` 的显示:
|
||||
- 当鼠标首次移入按钮区域,启动一个定时(根据 `tipDelayMs`)。
|
||||
- 当鼠标离开或按钮点击后,立即隐藏提示(调用 `hideTooltip()`)。
|
||||
- 当鼠标在按钮上停留超过延迟且按钮仍显示,则设置 `tipVisible = true` 并初始化 `tipLabel` 的文本和位置准备显示提示。`tipFollowCursor` 决定提示框锚定鼠标位置还是按钮下方。
|
||||
- `refreshTooltipTextForState()` 用于当按钮为 TOGGLE 模式且未自定义提示文本时,选择显示 `tipTextOn` 或 `tipTextOff`。
|
||||
- 每帧事件处理后,如提示应显示,则调用 `tipLabel.draw()` 绘制提示文本。
|
||||
- 函数最后,如果按钮 `hover` 状态或 `click` 状态有变化,则将自身标记为 `dirty` 并立即调用 `draw()` 重绘自身,实现交互的即时反馈。
|
||||
|
||||
`handleEvent` 返回值为 `true` 当且仅当按钮按下鼠标弹起事件被处理时,表示该点击事件已被消费,其它控件不应再处理。
|
||||
|
||||
- **使用说明:**
|
||||
开发者通常通过设置按钮的各种监听器来处理点击事件。例如:
|
||||
|
||||
```
|
||||
auto btn = std::make_unique<Button>(50, 50, 80, 30, "确定");
|
||||
btn->setOnClickListener([](){ /* 点击时执行 */ });
|
||||
```
|
||||
|
||||
对于 TOGGLE 按钮:
|
||||
|
||||
```
|
||||
btn->setButtonMode(ButtonMode::TOGGLE);
|
||||
btn->setOnToggleOnListener([](){ /* 开启时执行 */ });
|
||||
btn->setOnToggleOffListener([](){ /* 取消时执行 */ });
|
||||
```
|
||||
|
||||
也可以控制按钮的外观,如:
|
||||
|
||||
```
|
||||
btn->textStyle.color = RGB(255,255,255); // 白色文字
|
||||
btn->setButtonFalseColor(RGB(100,150,200)); // 默认背景色
|
||||
btn->setButtonBorder(RGB(80,80,80)); // 边框灰色
|
||||
btn->setButtonShape(StellarX::ControlShape::RECTANGLE);
|
||||
```
|
||||
|
||||
若要提示说明按钮功能,可:
|
||||
|
||||
```
|
||||
btn->tipEnabled = true;
|
||||
btn->tipTextClick = "点击执行确定操作";
|
||||
// (TOGGLE模式则设置 tipTextOn/off)
|
||||
```
|
||||
|
||||
### TextBox 类 (文本框控件)
|
||||
|
||||
**描述:** `TextBox` 用于显示和输入单行文本。可配置为可编辑或只读模式。TextBox 提供基本的文本输入框功能,当用户点击输入模式的文本框时,会弹出一个输入对话框接受用户输入(利用 EasyX 的 `InputBox` 实现),并将结果显示在文本框中。
|
||||
|
||||
- **模式:** 由 `StellarX::TextBoxmode` 指定:
|
||||
- `INPUT_MODE` 可输入模式:用户点击文本框可输入新内容。
|
||||
- `READONLY_MODE` 只读模式:用户点击不会更改文本,典型地可用于展示不可修改的信息。
|
||||
- **外观:**
|
||||
- 支持矩形和圆角矩形四种形状(无边框或有边框),通过 `setTextBoxShape(ControlShape)` 设置。圆形/椭圆对文本框来说不常用,一律按矩形处理。
|
||||
- 文本框默认有黑色边框和白色背景,可用 `setTextBoxBorder` 和 `setTextBoxBk` 改变。
|
||||
- 文字样式通过 `textStyle` 调整,包含字体、大小、颜色等属性。
|
||||
- **主要属性:**
|
||||
- `std::string text;` 当前文本框显示的文本内容。
|
||||
- `TextBoxmode mode;` 当前模式(决定是否允许输入)。
|
||||
- `ControlShape shape;` 当前文本框边框形状(矩形或圆角矩形,默认 RECTANGLE)。
|
||||
- `bool click;` 内部使用标志,表示是否刚被点击(用于触发输入弹窗,处理完后即重置)。
|
||||
- `size_t maxCharLen;` 最大允许输入字符数。默认 255。
|
||||
- `COLORREF textBoxBorderColor, textBoxBkColor;` 边框颜色和背景填充颜色。
|
||||
- `StellarX::ControlText textStyle;` 文本样式(高度、宽度、字体、颜色等)。
|
||||
- **主要方法:**
|
||||
- `void setMode(StellarX::TextBoxmode mode);`
|
||||
**描述:** 切换文本框模式。可选 INPUT_MODE 或 READONLY_MODE。更改模式不会清除已有文本,但只读模式下用户输入将被忽略。
|
||||
- `void setMaxCharLen(size_t len);`
|
||||
**描述:** 设置文本框最大字符长度。仅当 `len > 0` 时有效。超出长度的输入会被截断。
|
||||
- `void setTextBoxShape(StellarX::ControlShape shape);`
|
||||
**描述:** 设置文本框形状。支持矩形和圆角矩形,与 Button 类似。如果传入非法形状(圆/椭圆),会自动改为矩形。设置后文本框将 `dirty`,下次绘制按新形状渲染。
|
||||
- `void setTextBoxBorder(COLORREF color);` / `void setTextBoxBk(COLORREF color);`
|
||||
**描述:** 设置边框颜色和背景颜色。
|
||||
- `void setText(std::string text);`
|
||||
**描述:** 更新文本框内容文本。如果新文本长度超过 `maxCharLen`,将自动截断。设置后标记 `dirty` 并立即调用 `draw()` 重绘,使更改即时可见。
|
||||
- `std::string getText() const;`
|
||||
**描述:** 获取当前文本框内容字符串。
|
||||
- `void draw() override;`
|
||||
**描述:** 绘制文本框背景和文字。根据 `textStyle` 设置字体,如字体高度/宽度大于控件尺寸则裁剪适应;使用透明背景模式绘制文字,以便背景色通过 `fill...` 函数填充。
|
||||
- 绘制顺序:先 `fillrectangle/roundrect` 绘制背景(按 `textBoxBkColor`),然后 `outtextxy` 绘制文本,文本在 X 方向留出 10 像素左右内边距,Y 方向垂直居中。
|
||||
- 如果文本长度超出控件宽度,不自动裁剪(与 Button 不同),而是可能显示不下或被遮盖。通常应通过设置 `maxCharLen` 控制输入长度,或设计足够宽度的文本框。
|
||||
- 绘制完成后 `dirty` 置 false。
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**描述:** 处理鼠标事件:
|
||||
- 判断鼠标是否在文本框区域 (`hover`)。
|
||||
- 当 `WM_LBUTTONUP` 且鼠标在区域内发生:若模式为 INPUT_MODE,则调用 EasyX 的 `InputBox` 弹出输入对话框,允许用户输入文本(初始值为当前文本框内容),用户提交后 `InputBox` 返回新文本,将此文本赋值并标记 `dirty` 以便重绘显示。若模式为 READONLY_MODE,则弹出一个只读提示框(不会改变文本),并不修改 `dirty` 状态。
|
||||
- 其它鼠标事件不改变文本框外观,仅根据需要返回 false(未消费事件)。
|
||||
- 当完成一次输入或点击,文本框会调用自身 `draw()` 更新显示。READONLY_MODE 下点击会简单弹出提示(使用 InputBox 显示原文本并提示输入无效)。
|
||||
- 函数返回 true 表示在 INPUT_MODE 下点击事件被消费(因为弹出了输入框),READONLY_MODE 下也返回 true(提示已显示),从而阻止事件继续传播。
|
||||
- **用法注意:**
|
||||
- 由于 EasyX 的 `InputBox` 是模态阻塞函数,`runEventLoop` 本身是非阻塞循环,**在 UI 线程直接调用 InputBox 会暂停整个消息循环** 直到输入完成。StellarX 目前采用这种简单方式处理文本输入(不是异步 UI 输入)。因此在 `TextBox::handleEvent` 中使用 `InputBox` 来得到用户输入。这简化了实现,但 UI 线程会暂时挂起。开发者应避免在非常频繁交互时滥用 InputBox。
|
||||
- 可以通过 `maxCharLen` 控制可输入最大字符数,例如对于数值输入框可以设定长度防止超出范围。
|
||||
- 使用 `textStyle` 可调整显示字体(如等宽字体用于代码输入等),`textStyle.color` 也可单独设置文字颜色。
|
||||
|
||||
### Dialog 类 (对话框控件)
|
||||
|
||||
**描述:** `Dialog` 提供模态/非模态对话框功能,可在应用中弹出提示、确认或输入等对话界面。Dialog 本质上是一个特殊的 Canvas 容器,内部包含文本消息和按钮等子控件,并与 Window 紧密配合实现模态阻塞效果。
|
||||
|
||||
- **创建与模式:**
|
||||
|
||||
- 构造函数 `Dialog(Window& parent, std::string title, std::string message = "对话框", MessageBoxType type = OK, bool modal = true)`
|
||||
**描述:** 创建一个关联到 `parent` 窗口的对话框。`title` 将作为对话框标题文本显示在对话框顶部,`message` 是正文消息内容。`type` 参数指定对话框包含的按钮组合类型(见 MessageBoxType 枚举),例如 OK 只有一个确定按钮,YesNo 包含“是”和“否”两个按钮等。`modal` 决定对话框是模态还是非模态。构造后对话框初始为隐藏状态,需要调用 `show()` 显示。
|
||||
- `MessageBoxType` 枚举定义了常见的对话框按钮组合:
|
||||
- `OK`:一个“确定”按钮。
|
||||
- `OKCancel`:“确定”和“取消”按钮。
|
||||
- `YesNo`:“是”和“否”按钮。
|
||||
- `YesNoCancel`:“是”、“否”和“取消”按钮。
|
||||
- `RetryCancel`:“重试”和“取消”。
|
||||
- `AbortRetryIgnore`:“中止”、“重试”、“忽略”按钮。
|
||||
- 模态 (`modal=true`) 对话框:调用 `show()` 时会阻塞当前线程运行,直到对话框关闭后才返回。这期间应用的其他部分暂停响应输入。适用于必须先处理用户响应的关键情景。模态对话框关闭时,`show()` 返回后可通过 `getResult()` 获取用户点击结果。
|
||||
- 非模态 (`modal=false`) 对话框:调用 `show()` 则立即返回,应用程序继续运行,用户可以在背景窗口和对话框之间自由交互。需要通过回调函数或轮询 `getResult()` 来获取对话框的结果。
|
||||
|
||||
- **组成元素:**
|
||||
|
||||
- 标题文本(使用内部的 Label 控件 `title` 实现,显示在对话框顶部)。
|
||||
- 消息正文(支持多行文本,`Dialog` 自动按行分割存储在 `lines` 向量中,并在绘制时逐行居中输出)。
|
||||
- 功能按钮(根据 `MessageBoxType`,包含1到3个 Button 控件,位置自动计算居于对话框底部)。
|
||||
- 关闭按钮(右上角的 “×” 按钮,一个小的 Button 控件,点击相当于取消/关闭)。
|
||||
- 背景样式:Dialog 采用圆角矩形背景(如 Canvas shape = ROUND_RECTANGLE)并有半透明效果(具体通过 saveBackground 恢复背景实现视觉模态效果,但在 EasyX 简化模型下实际只是截屏背景,无模糊透明实现)。
|
||||
|
||||
- **主要属性:**
|
||||
|
||||
- `bool modal;` 是否模态对话框。
|
||||
- `titleText`:标题栏文本,`message`:消息内容文本。
|
||||
- `MessageBoxType type;` 对话框类型(按钮组合)。
|
||||
- `std::vector<std::string> lines;` 分割后的多行消息文本。
|
||||
- `std::unique_ptr<Label> title;` 用于显示标题的 Label 控件。
|
||||
- `Button* closeButton;` 关闭按钮指针(“×”)。
|
||||
- `Window& hWnd;` 引用父窗口,用于操作对话框关闭后刷新背景等。
|
||||
- `MessageBoxResult result;` 用户选择结果,枚举值对应哪个按钮被按下。常用值:OK=1, Cancel=2, Yes=6, No=7 等(见 CoreTypes 定义)。
|
||||
- `std::function<void(MessageBoxResult)> resultCallback;` 非模态对话框的结果回调函数指针。如果设置,将在对话框关闭且 `modal == false` 时被调用,参数为用户最终点击结果。
|
||||
|
||||
- **主要方法:**
|
||||
|
||||
- `void show();`
|
||||
**描述:** 显示对话框。如果是模态对话框,此函数将阻塞,内部运行一个事件处理循环,使该对话框独占输入焦点,直到对话框关闭为止;如果是非模态,则此函数立即返回(但对话框窗口已可见,输入由主 Window 继续分发)。`show()` 不返回任何值,但可以在模态情况下函数返回后调用 `getResult()` 获取结果。
|
||||
|
||||
- `void closeDialog();`
|
||||
**描述:** 关闭对话框。会将对话框隐藏并清理资源。如果有设置非模态回调且对话框为非模态,则在此过程中调用 `resultCallback(result)` 通知结果。通常不直接调用,由内部逻辑根据按钮按下触发。
|
||||
|
||||
- `MessageBoxResult getResult() const;`
|
||||
**描述:** 获取对话框的返回结果(用户按下了哪个按钮)。对模态对话框,`show()` 返回后调用此函数获取结果;对非模态,可在回调或稍后某时获取。
|
||||
|
||||
- `void setTitle(const std::string& title);` / `void setMessage(const std::string& msg);`
|
||||
**描述:** 更改对话框标题或内容文本。更改内容会重新分割 `lines` 并调整文本区域大小,需要重新布局。
|
||||
|
||||
- `void setType(MessageBoxType type);`
|
||||
**描述:** 更改对话框类型(按钮组合)。将重新创建按钮布局。
|
||||
|
||||
- `void setModal(bool modal);`
|
||||
**描述:** 切换对话框模态属性(必须在 show() 前调用)。一般不会在运行中切换。
|
||||
|
||||
- `void setResult(MessageBoxResult res);`
|
||||
**描述:** 设置对话框结果代码。通常由内部在按钮按下时调用,开发者无需手动调用。
|
||||
|
||||
- `void setInitialization(bool init);`
|
||||
**描述:** 若传入 true,则提前计算和设置对话框尺寸,并保存背景快照。**用途:** 在通过 `MessageBox` 静态函数创建对话框时,会先调用此函数以准备好对话框以便正确显示。
|
||||
|
||||
- `void setResultCallback(function<void(MessageBoxResult)> cb);`
|
||||
**描述:** 为非模态对话框设置结果回调函数。用户关闭对话框时将异步调用此函数,将结果传出。
|
||||
|
||||
- *内部实现在 `Dialog` 类私有方法中:*
|
||||
|
||||
- `initButtons()`:根据 `type` 创建对应数量的 Button 控件并设置其回调,将它们添加到 Dialog 容器。
|
||||
- `initCloseButton()`:创建右上角关闭按钮(文本为 “×”),点击后触发取消逻辑。
|
||||
- `initTitle()`:创建标题 Label 控件。
|
||||
- `splitMessageLines()`:将 `message` 根据换行符分割填充到 `lines` 数组。
|
||||
- `getTextSize()`:计算 `lines` 中最长行宽度和单行高度,以确定文本区域所需尺寸。
|
||||
- `initDialogSize()`:综合计算对话框宽高(考虑文本区域和按钮区域的宽度、高度),然后居中放置对话框于窗口。最后调用上述 init** 方法创建各子控件。
|
||||
- `performDelayedCleanup()`:用于延迟销毁对话框时释放资源。Dialog 设计上为了避免背景快照不匹配,需要在对话框关闭且完全隐去后再恢复背景并释放子控件资源;此函数执行该清理流程,由 Window 的事件循环或 `handleEvent` 在适当时机调用。
|
||||
|
||||
- `void draw() override;`
|
||||
**描述:** 绘制对话框界面。Dialog 继承自 Canvas,通过调用 `Canvas::draw()` 绘制背景和子控件框架,然后另外绘制消息文本内容。绘制步骤:
|
||||
|
||||
1. 如果首次显示,对话框先保存覆盖区域的屏幕背景(便于关闭时恢复)。
|
||||
2. 设置 Canvas 的背景颜色、边框等样式为对话框特定值(通常圆角矩形)。
|
||||
3. 将所有子控件标记为 `dirty` 并调用 `Canvas::draw()` 绘制对话框背景和子控件(包括按钮、标题Label等)。
|
||||
4. 使用对话框的 `textStyle` 设置文字样式,然后将每行 `lines` 文本居中绘制在对话框内部合适的位置(计算基于对话框宽度)。
|
||||
5. 恢复样式,设置 `dirty = false`。
|
||||
|
||||
对话框作为一个整体,由 Window 管理,其绘制顺序通常在 Window::runEventLoop 强制刷新时绘制。模态对话框在自己的 loop 内绘制。
|
||||
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**描述:** 处理对话框相关事件:
|
||||
|
||||
- 如果对话框未显示 (`show == false`),则检查 `pendingCleanup` 标志,如果有延迟清理任务且当前未在清理,则调用 `performDelayedCleanup()` 执行清理,然后不消费事件直接返回 false。
|
||||
- 如果对话框正在清理或标记延迟清理,则不处理任何事件直接返回 false。
|
||||
- 若为模态对话框 (`modal == true`),并检测到用户点击了对话框区域外部(窗口背景区域)的鼠标弹起事件,则发出提示声音 (`\a`)并返回 true(**吞噬事件**),防止用户点击窗体其他部分。
|
||||
- 将事件传递给 Dialog 继承的 Canvas(即其子控件)处理:`consume = Canvas::handleEvent(msg)`,这样对话框内部的按钮可以响应事件。如果子控件处理了事件则 consume 为 true。
|
||||
- 最后,如果对话框有延迟清理任务,则调用 `performDelayedCleanup()` 处理。返回值为 whether 消费了事件。
|
||||
- **模态对话框特别说明:** Window 主循环在有模态对话框显示时,会将背景点击等事件吃掉(上面逻辑已实现),同时对话框 `show()` 方法内部启动自己的循环处理事件,使模态对话框独占用户输入。非模态对话框则依赖 Window 主循环分发事件,由 handleEvent 参与处理。
|
||||
|
||||
- **使用示例:**
|
||||
`Dialog` 通常不直接使用,而是通过 `StellarX::MessageBox` 工具调用快速创建并显示:
|
||||
|
||||
- 模态对话框:
|
||||
|
||||
```
|
||||
StellarX::MessageBoxResult res = StellarX::MessageBox::showModal(window,
|
||||
"文件已保存。是否关闭程序?", "提示",
|
||||
StellarX::MessageBoxType::YesNoCancel);
|
||||
if (res == StellarX::MessageBoxResult::Yes) { /* 用户选择是 */ }
|
||||
```
|
||||
|
||||
`showModal` 内部创建 `Dialog` 并以模态方式显示,直到用户点击“是/否/取消”其中一个按钮,函数返回对应的枚举值。
|
||||
|
||||
- 非模态对话框:
|
||||
|
||||
```
|
||||
StellarX::MessageBox::showAsync(window,
|
||||
"处理完成!", "通知",
|
||||
StellarX::MessageBoxType::OK,
|
||||
[](StellarX::MessageBoxResult){ /* 用户点了OK后的处理 */ });
|
||||
```
|
||||
|
||||
这样弹出的对话框不会阻塞程序,它包含一个“确定”按钮。用户点击后,对话框关闭并自动调用提供的 lambda 回调。Window 的事件循环会继续正常运行。
|
||||
|
||||
开发者也可自行实例化 Dialog:
|
||||
|
||||
```
|
||||
Dialog dlg(mainWindow, "标题", "内容……", StellarX::MessageBoxType::OKCancel, false);
|
||||
dlg.setResultCallback([](MessageBoxResult res) { /* 处理结果 */ });
|
||||
dlg.show();
|
||||
```
|
||||
|
||||
以上将创建一个非模态对话框。一般推荐使用 `MessageBox` 封装函数更方便。
|
||||
|
||||
## MessageBox 工具类
|
||||
|
||||
**描述:** `StellarX::MessageBox` 提供静态方法方便地创建标准对话框,无需直接操作 Dialog 类。类似 Windows API 的 MessageBox,但这里可以指定按钮组合类型,并支持非模态调用。
|
||||
|
||||
- **静态函数:**
|
||||
|
||||
- `static MessageBoxResult showModal(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK);`
|
||||
**描述:** 弹出一个模态对话框,在窗口 `wnd` 上显示,标题为 `caption`,正文为 `text`,按钮组合由 `type` 指定。此函数会阻塞当前执行直到用户在对话框上点击按钮关闭对话框。返回值为 `MessageBoxResult`,表示用户点击了哪个选项(例如 `MessageBoxResult::OK` 或 `Cancel` 等)。开发者可根据返回值执行不同逻辑。
|
||||
- `static void showAsync(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK, std::function<void(MessageBoxResult)> onResult = nullptr);`
|
||||
**描述:** 在窗口 `wnd` 弹出一个非模态对话框。参数类似 `showModal`,但 `onResult` 是可选的回调函数。如果提供了回调函数,当用户关闭对话框时将异步调用该函数并传入用户选择结果。该函数本身立即返回,不阻塞程序流程。如果在 `wnd` 中已经存在一个内容和标题都相同的非模态对话框未关闭,则此函数会发出警报音并不创建新的对话框,以防止重复(内部使用 `Window::hasNonModalDialogWithCaption(caption,text)` 检查)。
|
||||
**注意:** 如果不提供 `onResult` 回调,那么需要其他方式得知对话框结果(例如通过 Dialog 的公有接口或者全局状态),但通常建议提供回调以处理结果。
|
||||
|
||||
- **实现细节:** `showModal` 和 `showAsync` 内部都会创建一个 `Dialog` 对象,并根据 `type` 设置相应按钮逻辑:
|
||||
|
||||
- `showModal` 创建 Dialog 后,会调用 `dlg.setInitialization(true)` 准备好对话框尺寸,然后调用 `dlg.show()` 以模态方式显示,并阻塞等待结果,最后返回 `dlg.getResult()`。
|
||||
- `showAsync` 则创建 Dialog 后,同样初始化,若指定回调则通过 `dlgPtr->setResultCallback(onResult)` 注册,然后把对话框通过 `wnd.addDialog()` 加入主窗口的对话框列表,最后调用 `dlgPtr->show()` 非模态显示。非模态显示不会阻塞,因此 `showAsync` 很快返回;对话框的关闭与结果传递在回调中完成。
|
||||
|
||||
- **典型用途:** MessageBox 提供与系统 MessageBox 类似的简明接口:
|
||||
|
||||
- 显示一条提示信息并等待确认:
|
||||
`MessageBox::showModal(mainWindow, "操作成功完成!", "提示", MessageBoxType::OK);`
|
||||
|
||||
- 提示加确认选择:
|
||||
`auto res = MessageBox::showModal(mainWindow, "确定要删除记录吗?", "请确认", MessageBoxType::YesNo); if (res == MessageBoxResult::Yes) { ... }`
|
||||
|
||||
- 非模态通知:
|
||||
`MessageBox::showAsync(mainWindow, "下载已在后台进行。", "通知", MessageBoxType::OK); // 用户点OK时对话框将自行关闭`
|
||||
|
||||
- 非模态询问并通过回调获取结果:
|
||||
|
||||
```
|
||||
MessageBox::showAsync(mainWindow, "发现新版本,是否现在更新?", "更新提示", MessageBoxType::YesNo,
|
||||
[](MessageBoxResult res) {
|
||||
if (res == MessageBoxResult::Yes) startUpdate();
|
||||
});
|
||||
```
|
||||
|
||||
通过这些静态方法,可以很方便地融合对话框提示流程,无需手动管理 Dialog 对象的生命周期和事件处理逻辑。
|
||||
|
||||
## Table 类 (表格控件)
|
||||
|
||||
**描述:****Table** 类是一个高级表格控件,支持分页显示和大数据量展示[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L2-L10)。它提供完整的数据表格功能,包括表头、数据行和分页导航等,实现类似电子表格的呈现。Table 控件可自动计算列宽和行高,支持自定义边框样式、填充模式以及文本字体样式,从而适应不同的UI设计需求。通过内置的分页机制,Table 能有效展示大量数据而不影响界面性能,并使用背景缓存来优化渲染,避免重绘闪烁。
|
||||
|
||||
**主要特性:**
|
||||
|
||||
- **自动分页:** 根据数据行数和每页行数自动计算总页数和当前页显示内容[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L8-L16)。提供 **上一页/下一页** 按钮和页码指示,方便用户浏览分页数据。
|
||||
- **自定义配置:** 开发者可配置每页行数、是否显示翻页按钮,设置表格边框颜色、背景色,选择填充模式(实色填充或透明)和边框线型等,满足不同视觉风格要求。
|
||||
- **高效渲染:** Table 通过缓存背景图像来提高渲染效率。在表格内容变化或首次绘制时缓存底图,绘制时仅更新差异部分,减少重复绘制带来的闪烁[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L210-L219)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L241-L249)。
|
||||
- **典型场景:** 适用于数据列表、报表、记录浏览等需要表格式展示的场景[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L14-L17)。例如显示数据库查询结果、日志记录、统计报表等,让用户以行列形式查看信息。
|
||||
|
||||
### 公共成员函数
|
||||
|
||||
#### Table(int x, int y)
|
||||
|
||||
**函数原型:** `Table(int x, int y)`
|
||||
**参数:**
|
||||
|
||||
- `x`:表格左上角的 X 坐标位置。
|
||||
- `y`:表格左上角的 Y 坐标位置。
|
||||
**返回值:** 无(构造函数无返回值)。
|
||||
**功能描述:** Table 类的构造函数。在指定的坐标位置创建一个新的表格控件实例[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L164-L171)。初始状态下表格没有设置表头和数据,宽度和高度初始为0,会在设置数据后根据内容自动计算调整。`x` 和 `y` 参数用于将表格定位在窗口中的起始位置。
|
||||
**使用场景:** 当需要在界面上显示表格数据时,首先调用此构造函数实例化一个 Table 对象。例如,在创建主窗口后,可以创建 `Table table(50, 50);` 来放置一个表格控件,然后再设置表头和数据。
|
||||
|
||||
#### ~Table()
|
||||
|
||||
**函数原型:** `~Table()`
|
||||
**参数:** 无。
|
||||
**返回值:** 无。
|
||||
**功能描述:** Table 类的析构函数。在对象销毁时自动调用,负责释放表格内部分配的资源。如表格内部创建的翻页按钮、页码标签以及背景缓存图像都将在析构函数中删除以防止内存泄漏[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L169-L178)。
|
||||
**使用场景:** 一般不直接调用,由系统自动管理。当 Table 对象的生命周期结束(例如其所在窗口关闭或程序结束)时,析构函数确保清理内存。不需开发者手动调用,但了解其行为有助于避免重复释放资源。
|
||||
|
||||
#### draw()
|
||||
|
||||
**函数原型:** `void draw() override`
|
||||
**参数:** 无。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 重写自 `Control` 基类的绘制函数,用于将表格绘制到屏幕[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L185-L194)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L252-L260)。调用 `draw()` 时,Table 根据当前状态绘制表头、当前页的数据行、页码标签和翻页按钮(如果启用)。绘制过程中会应用先前设置的文本样式、边框颜色、背景色等属性。为避免频繁重绘,Table 内部维护一个`dirty`标志,只有当表格内容或外观需更新时(例如调用了设置函数后)`draw()` 才会实际重绘内容[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L187-L196)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L258-L266)。
|
||||
**使用场景:** 通常在设置完表头和数据、或修改了样式后调用,令表格按新内容渲染。一般框架的窗口在刷新时会自动调用各控件的 `draw()` 方法,开发者也可在需要立即刷新表格时手动调用它。例如,当表格数据更新且需要即时反映在界面上时,可以设置相应属性并调用 `draw()` 触发重绘。
|
||||
|
||||
#### handleEvent(const ExMessage& msg)
|
||||
|
||||
**函数原型:** `bool handleEvent(const ExMessage& msg) override`
|
||||
**参数:**
|
||||
|
||||
- `msg`:事件消息对象(EasyX库的 `ExMessage` 结构),包含了键盘或鼠标事件的信息。
|
||||
**返回值:** 布尔值,表示事件是否被表格控件处理。返回 `true` 表示事件已被消耗,不再需要向其他控件传播;返回 `false` 则表示表格未处理该事件。
|
||||
**功能描述:** 重写自 `Control` 基类的事件处理函数,用于处理与表格相关的用户交互事件。对于 Table 而言,`handleEvent` 主要用于处理翻页按钮的点击事件。当 `isShowPageButton` 为真(显示翻页按钮)时,该方法会将传入的事件传递给内部的 “上一页” 和 “下一页” 按钮进行处理[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L274-L283)。如果用户点击了上一页或下一页按钮,按钮的内部逻辑会更新当前页码并调用 `draw()` 重绘表格,`handleEvent` 随后返回 `true` 表示事件已处理。若翻页按钮未启用(例如只有一页数据或通过 `showPageButton(false)` 隐藏了按钮),此函数直接返回 `false`[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L276-L283),不对事件进行处理。
|
||||
**使用场景:** 框架的主事件循环会调用控件的 `handleEvent` 方法来分发用户输入。当使用 Table 控件时,无需手动调用此函数,但了解其行为有助于处理自定义事件。例如,可在Window的事件循环中将鼠标点击事件传给表格控件,使其内部的翻页按钮响应点击,从而实现分页浏览的数据切换。
|
||||
|
||||
#### setHeaders(std::initializer_liststd::string headers)
|
||||
|
||||
**函数原型:** `void setHeaders(std::initializer_list<std::string> headers)`
|
||||
**参数:**
|
||||
|
||||
- `headers`:表头文本字符串的初始化列表,每个字符串对应表格一列的列名。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格的列标题。调用此函数会清除当前已有的表头并使用提供的字符串列表作为新表头[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L288-L296)。在设置表头后,Table 会将内部状态标记为需要重新计算单元格尺寸和重新绘制表头[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L291-L295)。列标题用于在表格顶部显示,表示每列数据的含义。表头数量确定了表格的列数,后续添加的数据应与列数匹配。
|
||||
**使用场景:** 通常在创建 Table 实例后、添加数据之前调用,用来定义表格的列结构。例如:`table.setHeaders({"姓名", "年龄", "职业"});` 将表格设置为包含“姓名”、“年龄”、“职业”三列。设置表头后即可调用 `setData` 添加对应列的数据行。
|
||||
|
||||
#### setData(std::vectorstd::string data) / setData(std::initializer_list<std::vectorstd::string> data)
|
||||
|
||||
**函数原型1:** `void setData(std::vector<std::string> data)`
|
||||
**参数:**
|
||||
|
||||
- `data`:包含单行表格数据的字符串向量,每个元素对应一列的内容。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 将一个数据行添加到表格中。若传入的这行数据列数少于当前表头列数,则函数会自动在行数据末尾补空字符串,使其列数与表头一致[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L300-L308)。然后将该行数据追加到内部数据集合 `data` 中,并更新表格的总页数 (`totalPages`) 属性[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L302-L308)。更新页数时根据当前总行数和每页行数计算页数,确保至少为1页[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L303-L308)。函数调用后会标记表格需要重新计算单元格尺寸,并在下次绘制时包含新添加的数据。
|
||||
**使用场景:** 可用于逐行构建表格数据,例如从数据源读取记录并一条条加入表格。适合数据逐步到达或者需要动态添加行的情形。每次调用都会在末页附加一行,并相应更新分页显示。
|
||||
|
||||
**函数原型2:** `void setData(std::initializer_list<std::vector<std::string>> data)`
|
||||
**参数:**
|
||||
|
||||
- `data`:一个由多行数据组成的初始化列表,每个内部的 `std::vector<std::string>` 代表一行的数据。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 批量添加多行数据到表格中。对于提供的每一行数据,函数会检查其列数是否与表头列数匹配;若不足则在该行末尾补充空字符串以对齐列数[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L313-L321)。然后将处理后的整行数据依次追加到表格内部的数据集中。添加完所有行后,函数重新计算总数据行数下的总页数,并至少确保一页[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L322-L328)。与单行版本类似,此方法也会触发单元格尺寸重新计算和脏标记,使表格内容在下次绘制时更新。
|
||||
**使用场景:** 适用于一次性导入整批数据的情形,例如应用初始化时加载整个数据表。调用此函数可以方便地用初始化列表直接提供多行初始数据,而无需多次重复调用单行的 `setData`。在需要清空已有数据并填入新数据时,可以在调用此方法前手动清除旧数据(例如重新创建Table或使用返回的data容器修改)。
|
||||
|
||||
#### setRowsPerPage(int rows)
|
||||
|
||||
**函数原型:** `void setRowsPerPage(int rows)`
|
||||
**参数:**
|
||||
|
||||
- `rows`:每页显示的数据行数(正整数)。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格分页时每页包含的数据行数。调用此函数会修改内部的 `rowsPerPage` 属性,并根据当前总数据行数重新计算总页数 `totalPages`[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L332-L338)。如果重新计算后总页数低于1,则强制设为1页(避免出现0页的非法状态)。改变每页行数将影响表格的高度和分页布局,因此会标记需要重新计算单元格大小以及重新绘制。在下次 `draw()` 时,表格将按照新的分页大小重新呈现内容。
|
||||
**使用场景:** 当需要调整表格分页密度时使用。例如默认每页显示5行数据,可以通过 `table.setRowsPerPage(10);` 改为每页10行,以减少总页数。常用于根据用户偏好或窗口大小调整每页显示量。应注意,如果当前页码在调整后超出新的总页数范围,表格在下次翻页时会自动纠正到最后一页。
|
||||
|
||||
#### showPageButton(bool isShow)
|
||||
|
||||
**函数原型:** `void showPageButton(bool isShow)`
|
||||
**参数:**
|
||||
|
||||
- `isShow`:布尔值,指定是否显示翻页按钮。`true` 表示显示 “上一页” 和 “下一页” 按钮,`false` 则隐藏它们。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 控制表格分页导航按钮的可见性。调用此函数可以在界面上隐藏或显示表格底部的翻页按钮。如果设置为隐藏 (`false`),则用户将无法通过界面按钮切换页码(此时表格将始终停留在当前页);当再次显示按钮 (`true`) 时,用户可以使用按钮查看其他页的数据。该函数仅影响按钮的绘制和事件处理状态,不改变当前页码等分页数据。
|
||||
**使用场景:** 适用于当数据量较少仅一页即可显示完毕,或开发者希望以其他方式控制分页时。例如,当表格内容只有一页数据时,可以隐藏翻页按钮以简化界面。需要再次启用分页功能时再调用 `showPageButton(true)` 显示按钮。
|
||||
|
||||
#### setTableBorder(COLORREF color)
|
||||
|
||||
**函数原型:** `void setTableBorder(COLORREF color)`
|
||||
**参数:**
|
||||
|
||||
- `color`:表格边框颜色,类型为 `COLORREF`(Windows RGB 颜色值,例如 `RGB(0,0,0)` 表示黑色)。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格单元格边框线的颜色。调用该方法会将 Table 内部存储的边框颜色更新为指定的 `color`,并将表格标记为需要重绘。在随后的 `draw()` 调用中,表格的线条(包括单元格边框和外围边框)将使用新的颜色绘制[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L192-L199)。默认的边框颜色为黑色,如需与界面主题匹配可使用此函数调整。
|
||||
**使用场景:** 当需要改变表格线条颜色以配合UI配色方案时使用。例如,将表格边框设为浅灰色以获得柔和的视觉效果:`table.setTableBorder(RGB(200,200,200));`。在深色模式下,可将边框设为亮色以提高对比度。调用后下一次重绘将体现颜色变化。
|
||||
|
||||
#### setTableBk(COLORREF color)
|
||||
|
||||
**函数原型:** `void setTableBk(COLORREF color)`
|
||||
**参数:**
|
||||
|
||||
- `color`:表格背景填充颜色,类型为 `COLORREF`。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格单元格的背景颜色。调用该函数会更新 Table 内部的背景色属性,并触发重绘标记。在绘制表格时,每个单元格将使用指定的背景颜色进行填充(当填充模式为实色时)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L192-L199)。默认背景颜色为白色,如需改变单元格底色(例如间隔行着色等)可以使用不同的颜色多次调用或自定义绘制。
|
||||
**使用场景:** 用于调整表格的底色风格。例如,将表格背景设为浅黄色以突出显示内容区域:`table.setTableBk(RGB(255, 255, 224));`。结合填充模式使用,可以实现透明背景等效果。注意背景颜色变化在填充模式为Solid时可见,若填充模式为Null则背景不绘制实色。
|
||||
|
||||
#### setTableFillMode(StellarX::FillMode mode)
|
||||
|
||||
**函数原型:** `void setTableFillMode(StellarX::FillMode mode)`
|
||||
**参数:**
|
||||
|
||||
- `mode`:填充模式,枚举类型 `StellarX::FillMode`。可选值包括 Solid(实色填充)、Null(不填充)、Hatched/Pattern 等。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格的填充模式,即单元格背景的绘制方式。常用的模式有 Solid(纯色填充背景)或 Null(透明背景不填充)。当前版本中,仅对 Solid 和 Null 模式提供完全支持:如果传入其他模式值,内部会默认退回使用 Solid 模式[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L360-L369)。调用此方法会应用新的填充模式,并将此模式同步到表格的翻页按钮和页码标签控件上,使它们风格一致[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L365-L373)。例如,当设置为 Null 模式时,表格单元格背景将不被填充颜色,呈现透明效果,同时页码标签也设置为透明背景显示。改变填充模式会将表格及相关控件标记为需重绘。
|
||||
**使用场景:** 根据界面设计需要调整表格背景绘制策略。例如,为实现镂空透明效果,可使用 `table.setTableFillMode(StellarX::FillMode::Null);` 使表格仅绘制文字和边框而无底色。默认使用 Solid 模式填充背景,实现传统表格外观。当需要图案填充等高级效果时,当前版本可能不支持,仍会以纯色替代。
|
||||
|
||||
#### setTableLineStyle(StellarX::LineStyle style)
|
||||
|
||||
**函数原型:** `void setTableLineStyle(StellarX::LineStyle style)`
|
||||
**参数:**
|
||||
|
||||
- `style`:边框线型,枚举类型 `StellarX::LineStyle`,如 Solid(实线)、Dash(虚线)、Dot(点线)等。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格网格线(单元格边框线条)的样式。传入不同的线型枚举值可以改变表格绘制时边框的样式,例如使用虚线或点线来绘制单元格边框。调用该方法将更新内部存储的线型并标记表格需要重绘,下次绘制时新的线型将生效。需注意实际效果取决于绘图库对线型的支持。
|
||||
**使用场景:** 当希望改变表格外观以区分层次或提升美观时,可修改线型。例如,`table.setTableLineStyle(StellarX::LineStyle::Dash);` 将表格边框改为虚线风格,以在视觉上弱化表格线对界面的干扰。此功能在打印报表或特殊主题界面中会很有用。
|
||||
|
||||
#### setTableBorderWidth(int width)
|
||||
|
||||
**函数原型:** `void setTableBorderWidth(int width)`
|
||||
**参数:**
|
||||
|
||||
- `width`:边框线宽度(像素值,正整数)。
|
||||
**返回值:** 无。
|
||||
**功能描述:** 设置表格边框线条的粗细(线宽)。默认情况下表格边框宽度为1像素,调用此函数可使所有单元格的边框线加粗或变细。更新线宽后表格将在下次重绘时使用新的线宽绘制边框[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L198-L201)。如果设置的宽度大于1,为避免粗线覆盖过多背景,Table 在绘制时会调整背景区域的恢复以适应粗线描边[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L220-L228)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L240-L248)。
|
||||
**使用场景:** 在需要强调表格结构或适应高分辨率显示时,可增加线宽。例如,`table.setTableBorderWidth(2);` 将表格线条加粗为2像素,使表格框架更加醒目。若要获得细网格效果,可以将线宽设置为1以下(不过线宽最小为1像素)。改变线宽后请确保颜色、线型等设置搭配以获得预期视觉效果。
|
||||
|
||||
### 获取属性方法(Getters)
|
||||
|
||||
以下函数用于获取 Table 当前的状态或配置信息:
|
||||
|
||||
- **int getCurrentPage() const**:获取当前显示的页码索引。第一页为1,第二页为2,以此类推[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L390-L398)。可用于在界面上显示当前页码或在程序逻辑中判断分页位置。
|
||||
- **int getTotalPages() const**:获取根据当前数据行数和每页行数计算出的总页数[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L394-L402)。通常在设置完数据或调整分页参数后调用,以了解数据被分页成多少页。
|
||||
- **int getRowsPerPage() const**:获取当前每页显示的数据行数设置[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L400-L408)。可用于在界面上提示用户分页大小,或在逻辑中根据该值调整分页处理。
|
||||
- **bool getShowPageButton() const**:获取当前翻页按钮的显示状态(true 为显示,false 为隐藏)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L404-L412)。可根据此值判断界面上是否提供了翻页导航。
|
||||
- **COLORREF getTableBorder() const**:获取当前表格边框颜色[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L410-L418)(返回一个COLORREF值)。可以用于在需要时与其他控件颜色保持一致或者保存当前配置。
|
||||
- **COLORREF getTableBk() const**:获取当前表格背景填充颜色[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L414-L422)。
|
||||
- **StellarX::FillMode getTableFillMode() const**:获取当前表格的填充模式设置[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L420-L428)。返回值为 `StellarX::FillMode` 枚举,可判断是实色填充、透明等模式。
|
||||
- **StellarX::LineStyle getTableLineStyle() const**:获取当前表格边框线型设置[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L424-L432)。返回 `StellarX::LineStyle` 枚举值,如实线、虚线等。
|
||||
- **std::vectorstd::string getHeaders() const**:获取当前表格的表头列表副本[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L430-L438)。返回一个字符串向量,其中包含每一列的标题。可以使用此函数查看或保存表格的列标题设置。
|
||||
- **std::vector<std::vectorstd::string> getData() const**:获取当前表格的所有数据[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L434-L442)。返回一个二维字符串向量,其中每个内部向量代表表格中的一行数据(各元素对应该行每列的值)。这允许访问或遍历表格内容,例如用于导出数据。
|
||||
- **int getTableBorderWidth() const**:获取当前设置的表格边框线宽度(像素)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L440-L446)。
|
||||
|
||||
以上获取函数均为 `const` 成员函数,不会修改 Table 的状态,只返回相应属性的值。开发者可根据需要随时调用这些函数来查询表格状态,例如在界面其他区域显示统计信息或在调试时验证设置是否正确。
|
||||
|
||||
### 公共成员变量
|
||||
|
||||
- **StellarX::ControlText textStyle**:文本样式结构体,表示表格中文字内容的字体和样式设置[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/CoreTypes.h#L132-L140)。通过修改该结构体的成员,可以自定义表格文本的外观,例如字体字号、高度、宽度、字体名称 (`lpszFace`),文字颜色 (`color`),以及是否加粗 (`nWeight`)、斜体 (`bItalic`)、下划线 (`bUnderline`) 等[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/CoreTypes.h#L134-L142)。Table 默认使用框架的默认字体(例如微软雅黑)和颜色绘制文字。开发者可以直接访问并调整 `textStyle` 的属性,然后调用 `draw()` 重新绘制,使新的文本样式生效。例如,`table.textStyle.nHeight = 20; table.textStyle.color = RGB(0,0,128);` 将文字调大并改为深蓝色。值得注意的是,Table 内部的翻页按钮和页码标签也会同步使用 Table 的 `textStyle` 设置,以保持表格组件整体风格的一致。
|
||||
|
||||
### 示例
|
||||
|
||||
下面是一个使用 Table 控件的示例代码[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.md#L268-L276)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.md#L278-L286),演示如何创建表格、设置表头和数据、配置分页,并将其添加到窗口中显示:
|
||||
|
||||
```
|
||||
// 创建一个表格控件,位置在 (50, 50)
|
||||
auto myTable = std::make_unique<Table>(50, 50);
|
||||
|
||||
// 设置表头(列名称)
|
||||
myTable->setHeaders({ "ID", "姓名", "年龄", "职业" });
|
||||
|
||||
// 添加数据行
|
||||
myTable->setData({ "1", "张三", "25", "工程师" });
|
||||
myTable->setData({ "2", "李四", "30", "设计师" });
|
||||
myTable->setData({ "3", "王五", "28", "产品经理" });
|
||||
|
||||
// 设置每页显示2行数据
|
||||
myTable->setRowsPerPage(2);
|
||||
|
||||
// 设置表格文本和样式
|
||||
myTable->textStyle.nHeight = 16; // 文本字体高度
|
||||
myTable->setTableBorder(RGB(50, 50, 50)); // 表格边框颜色(深灰)
|
||||
myTable->setTableBk(RGB(240, 240, 240)); // 表格背景色(浅灰)
|
||||
|
||||
// 将表格控件添加到主窗口(假设 mainWindow 已创建)
|
||||
mainWindow.addControl(std::move(myTable));
|
||||
```
|
||||
|
||||
上述代码首先创建了一个 Table 实例,并依次设置表头和三行示例数据。接着将每页行数设为2行,这意味着数据将分页为两页(3条数据在每页2行的设置下将分两页显示)。然后调整了文本样式和颜色,将字体高度设为16像素,边框颜色设为灰色,背景色设为浅灰,以定制表格外观。最后,将配置好的表格控件添加到主窗口中,使其随着窗口一起绘制和响应用户操作。在实际应用中,添加控件后通常需要调用主窗口的 `draw()` 来绘制界面,及调用 `runEventLoop()` 进入消息循环以响应用户交互。通过上述流程,开发者即可在应用程序窗口中成功集成并显示一个功能完善的分页表格控件。
|
||||
|
||||
以上即为 StellarX 框架各主要组件的接口说明和使用方法。通过这些类和函数,开发者可以组合出完整的 GUI 应用。请注意,StellarX 基于 EasyX 实现,适用于 Windows 平台的教学和轻量工具开发场景。使用时需要确保 EasyX 图形库已初始化,并在应用结束前调用 `closegraph()` 释放图形资源。
|
||||
|
||||
+206
-1
@@ -7,12 +7,217 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
[中文文档](CHANGELOG.md)
|
||||
|
||||
## [v2.1.0] - 2025-10-05
|
||||
## [v3.0.1] - 2026 - 03 - 17
|
||||
|
||||
==Notice==
|
||||
|
||||
This update changes the **semantics of `TextBox::setText`**.
|
||||
If your old code manually called `draw()` after calling `setText()`, you should now remove that call. Otherwise, under the new version, old code may cause `TextBox` to flicker in some cases.
|
||||
|
||||
### 🙏 Acknowledgements
|
||||
|
||||
Special thanks to [Pengfei Zhu](https://github.com/zhupengfeivip) for helping improve the StellarX documentation, especially the **Include Directories and Library Directories Configuration** section, and for reporting the issue where passing `NULL` in window mode would cause a console window to pop up ([Issues#9](https://github.com/Ysm-04/StellarX/issues/9)).
|
||||
|
||||
### ⚙️ Changes
|
||||
|
||||
- **Changed semantics of `TextBox::setText`:**
|
||||
The old dirty-mark-only behavior has been replaced with a more robust workflow.
|
||||
- If the text has not changed, the function returns immediately without doing anything.
|
||||
- If the text has changed, it will decide whether to redraw immediately or request an upstream redraw depending on whether the graphics context/window has already been created. If the graphics context/window has not yet been created, it will only mark itself as dirty.
|
||||
|
||||
- **TextBox - text overflow truncation:**
|
||||
If the user types text or calls `setText()` to set text, and the text length does not exceed `maxCharLen` but its pixel width exceeds the `TextBox` width, the displayed text will be truncated by character and appended with `...` for rendering.
|
||||
The full original text is still stored internally and is not affected when retrieved through getter methods.
|
||||
|
||||
### ✅ Fixes
|
||||
|
||||
- **`TabControl::getActiveIndex() const` could return the last tab index when the tab list was not empty but no tab was active:**
|
||||
Fixed the issue where calling `getActiveIndex()` could sometimes return an incorrect index.
|
||||
Now:
|
||||
- if the tab list is empty, it returns `-1`
|
||||
- if no tab is active, it returns `-1`
|
||||
- if a tab is active, it returns the index of the active tab
|
||||
|
||||
- **Continuous full redraw while a dialog is open:**
|
||||
Added a new variable `dialogOpen` to `Window`.
|
||||
When a dialog is shown, `dialogOpen` is set to `true`. Only when `dialogOpen` is `true` will `needRedraw` be checked. After redrawing, `dialogOpen` is immediately reset to `false`.
|
||||
|
||||
- **Synthetic `WM_MOUSEMOVE` being dispatched when a dialog opens, instead of only when it closes:**
|
||||
Added a condition when dispatching synthetic `WM_MOUSEMOVE`.
|
||||
Now it is only synthesized and dispatched when `dialogClose` is `true`, meaning only after a dialog is closed.
|
||||
|
||||
- **Normal control event dispatch order in `Window::runEventLoop`:**
|
||||
Changed the traversal of `controls (vector)` from forward order
|
||||
`for (auto& c : controls)`
|
||||
to reverse order
|
||||
`for (auto it = controls.rbegin(); it != controls.rend(); ++it)`
|
||||
|
||||
## [v3.0.0] - 2026-01-09
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Logging System Demo**: Located in: examples\SXLog-LoggingSystemDemo
|
||||
- **Lightweight Logging System SxLog (SxLogger / SxLogLine / SxLogScope / Sink / TagFilter / LanguageSwitch):** A unified logging entry and macro encapsulation is introduced, supporting level and tag-based filtering, console/file output, and optional file rolling based on size thresholds. It also provides bilingual text selection capabilities (via `SX_T` / `SxT`). The logging macros have a short-circuit mechanism: logs will not be constructed or string concatenated if the level or tag condition is not met. Output is serialized with mutex to ensure thread safety. The module does not depend on WinAPI debug output channels and does not introduce third-party libraries.
|
||||
- Typical usage: `SX_LOGD/SX_LOGI/SX_LOGW/SX_LOGE/SX_LOGF/SX_LOG_TRACE` with stream concatenation; `SX_TRACE_SCOPE` for scope timing.
|
||||
- Core configuration: `setMinLevel(...)`, `setTagFilter(...)`, `enableConsole(true/false)`, `enableFile(path, append, rotateBytes)`, `setLanguage(ZhCN/EnUS)`, `setGBK()`
|
||||
|
||||
### ⚙️ Changes
|
||||
|
||||
- **TabControl Tab Switching Logic Adjustment:** The logic for tab button switching has been modified to “first close the currently opened tab, then open the triggered tab,” avoiding potential timing issues caused by the “open first, close later” sequence in complex containers/snapshot chains. The external API remains unchanged (involving the internal switching logic of `TabControl::add`).
|
||||
- **Setter Semantics Refined for Controls:** The responsibility of setters is clarified to be “updating state and marking as dirty,” with drawing now uniformly triggered by the window/container's redraw mechanism, reducing lifecycle coupling and snapshot pollution risks (see related fix entries below).
|
||||
|
||||
### ✅ Fixes
|
||||
|
||||
- **TextBox::setText Causes Interruptions Before Entering Event Loop:** Fixed the issue where calling `TextBox::setText()` before window initialization (before `initgraph()` or when the graphical context is not ready) caused access conflicts and crashes. The previous implementation coupled “state updates” with “immediate drawing,” leading to `setText()` internally triggering `draw()`, which crashed when there was no graphics context (e.g., `saveBackground()/getimage()`). Now, the setter only updates the text and marks the control as dirty, with the drawing handled by the unified redraw flow.
|
||||
- **TabControl Switching and Table Content Overlap (Ghosting):** Fixed a stable ghosting issue when switching tabs or resetting the table data. The issue was caused by partial redraws happening before the snapshot was ready, leading to snapshot contamination. This was addressed by rolling back the behavior of `setIsVisible(true)` immediately triggering `requestRepaint`, and instead marking as dirty while adding safeguards to `Canvas::requestRepaint` and `TabControl::requestRepaint`: when the container is `dirty`, `hasSnap=0`, or the snapshot cache is invalid, partial fast-path is disabled and full redraw is triggered to ensure correct snapshot chains.
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
- **Removal of Button Dimension Alias APIs:** Removed `Button::getButtonWidth()` and `Button::getButtonHeight()`, unifying the control's dimension APIs under the base class `Control::getWidth()` and `Control::getHeight()`. This change will break backward compatibility if the old methods were used, but the behavior (retrieving `width/height`) remains the same.
|
||||
- **Removal of Immediate Refresh Side Effects in Setters:** The side effect of immediate drawing in setters like `setIsVisible(true)` and `setText()` has been removed. If previous business code relied on "immediate visibility update after calling," you will now need to ensure a subsequent redraw path (via window's main loop/container redraw or explicit refresh) for visual updates to be completed.
|
||||
|
||||
### 📌 Upgrade Instructions
|
||||
|
||||
1. **Button Dimension API Migration:**
|
||||
- `getButtonWidth()` → `getWidth()`
|
||||
- `getButtonHeight()` → `getHeight()`
|
||||
2. **Adapting to Setters No Longer Triggering Immediate Drawing:**
|
||||
- It is now possible to set properties (like `TextBox::setText("default")`) during initialization, with visual updates being handled by the first `Window::draw()` or the main event loop.
|
||||
- If you need immediate visual updates in non-event-driven scenarios, you must ensure that a redraw path is triggered (avoid manually calling `draw()` inside the setter, as this would cause lifecycle coupling).
|
||||
3. **SxLog Integration Suggestions:**
|
||||
- Ensure basic configuration at the program entry (console output/minimum level/language), and use consistent tags (such as `Dirty/Resize/Table/Canvas/TabControl`) to establish traceable event chains; for high-frequency paths, control noise and I/O costs using level and tag filtering.
|
||||
|
||||
## [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
|
||||
|
||||
==This release is a hotfix for v2.2.0==
|
||||
|
||||
### ✅ Fixed
|
||||
|
||||
- The `TabControl` class overrode the base class's `setDirty` method to ensure synchronized update status between the tab and its page list.
|
||||
- The `Canvas` container, special container `TabControl`, and dialog `Dialog` overrode the `requestRepaint` method. When control bubbling propagates upward, the parent pointer is passed. Repaint requests now only bubble up one level to the parent and no longer propagate to the root. Furthermore, the entire parent container is no longer repainted; instead, the parent container repaints only the dirtied child controls, avoiding flickering caused by frequent repaints of the entire container.
|
||||
- The `saveBackground` and `restoreBackground` methods were overridden in `Dialog` to ensure no border remnants remain after the dialog is closed.
|
||||
|
||||
### ⚙️ Changed
|
||||
|
||||
- Completely disabled copy and move semantics for the `Control` class:
|
||||
`Control(const Control&) = delete;`
|
||||
`Control& operator=(const Control&) = delete;`
|
||||
`Control(Control&&) = delete;`
|
||||
`Control& operator=(Control&&) = delete;`
|
||||
|
||||
## [v2.2.0] - 2025-11-02
|
||||
|
||||
**Highlights**: Officially introduces the TabControl, enhances control show/hide and layout responsiveness, and refines the text styling mechanism; fixes several UI details to improve stability.
|
||||
|
||||
### ✨ Added
|
||||
|
||||
- **TabControl (tabbed container control)**: Added the `TabControl` class to implement a multi-page tabbed UI. The tab bar supports **top/bottom/left/right** positions via `TabControl::setTabPlacement(...)`. Provides `TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&&)` to add a “tab button + page content” pair in one go and automatically manage switching. Each page’s content area is hosted by a `Canvas`; clicking different tabs (Button, TOGGLE mode) switches the visible page **(see API)**. TabControl can automatically adjust tab layout when the window size changes and uses background snapshots to avoid ghosting under transparent themes.
|
||||
- **Control visibility**: All controls now support runtime **show/hide** toggling. The base class adds `Control::setIsVisible(bool)` to control a control’s own visibility; hidden controls no longer participate in drawing and events. Container controls (`Canvas`) override this to implement **cascading hide**: hiding a container automatically hides all its child controls, and showing it restores them. This makes it more convenient to toggle the visibility of a group of UI elements.
|
||||
- **Window resize handling**: Introduces the virtual function `Control::onWindowResize()`, called when the parent window size changes. Controls can implement this to respond to window resizing (e.g., reset background caches, adjust layout). `Canvas` implements this and **recursively notifies child controls** to call `onWindowResize()`, ensuring nested layouts update correctly. This improvement fixes possible misalignment or background issues that occurred after resizing.
|
||||
- **Label text style structure**: The `Label` control now uses a unified `ControlText` style structure to manage fonts and colors. By accessing the public member `Label::textStyle`, you can set font name, size, color, underline, etc., achieving **full text style customization** (replacing the previous `setTextColor` interface). This enables richer formatting when displaying text with Label.
|
||||
- **Dialog de-duplication mechanism**: `Window` adds an internal check to **prevent popping up duplicate dialogs with the same content**. When using `MessageBox::showAsync` for a non-modal popup, the framework checks if a dialog with the same title and message is already open; if so, it avoids creating another one. This prevents multiple identical prompts from appearing in quick succession.
|
||||
|
||||
### ⚙️ Changed
|
||||
|
||||
- **Text color interface adjustment**: `Label` removes the outdated `setTextColor` method; use the public `textStyle.color` to set text color instead. To change a Label’s text color, modify `label.textStyle.color` and redraw. This improves consistency in text property management but may be incompatible with older code and require replacement.
|
||||
- **Tooltip styling and toggle**: The `Button` Tooltip interface is adjusted to support more customization. `Button::setTooltipStyle` can now flexibly set tooltip text color, background color, and transparency; `setTooltipTextsForToggle(onText, offText)` is added so toggle buttons can display different tooltip texts in **ON/OFF** states. The original tooltip-text setting interface remains compatible, but the internal implementation is optimized, fixing the previous issue where toggle-button tooltip text didn’t update.
|
||||
- **Control coordinate system and layout**: Controls now maintain both **global coordinates** and **local coordinates**. `Control` adds members (such as `getLocalX()/getLocalY()`) to get positions relative to the parent container, and a `parent` pointer to the parent container. This makes layout calculations in nested containers more convenient and accurate. In absolute-layout scenarios, a control’s global coordinates are automatically converted and stored as local coordinates when added to a container. Note: in the new version, when moving controls or changing sizes, prefer using the control’s own setters to keep internal coordinates in sync.
|
||||
- **Window resizing defaults**: Resizing is changed from experimental to **enabled by default**. The framework always enables resizable styles for the main window (`WS_THICKFRAME | WS_MAXIMIZEBOX | ...`), so there’s no need to call a separate method to enable it. This simplifies usage and means created windows can be resized by users by default. For scenarios where resizing is not desired, pass specific mode flags at creation time to disable it.
|
||||
|
||||
### ✅ Fixed
|
||||
|
||||
- **Toggle-button tooltip updates**: Fixed an issue where tooltips for toggle-mode buttons did not update promptly when the state changed. With `setTooltipTextsForToggle`, the tooltip now correctly shows the corresponding text when switching between **ON/OFF**.
|
||||
- **Background ghosting and coordinate sync for controls**: Fixed defects where, in some cases, control backgrounds were not refreshed in time and position calculations deviated after window/container resizing. By using `onWindowResize` to uniformly discard and update background snapshots, background ghosting and misalignment during window resizing are avoided, improving overall stability.
|
||||
- **`Control` class background-snapshot memory leak**: The destructor now calls `discardBackground` to free and restore the background snapshot, preventing the memory leak caused by not releasing `*saveBkImage` in the previous version.
|
||||
- **Duplicate dialog pop-ups**: Fixed an issue where repeatedly calling a non-modal message box in quick succession could create multiple identical dialogs. The new de-duplication check ensures that only one non-modal dialog with the same content exists at a time, avoiding UI disruption.
|
||||
- **Other**: Optimized the control drawing/refresh strategy to reduce unnecessary repaints in certain scenarios and improve performance; corrected minor memory-management details to eliminate potential leaks. These enhancements further improve the framework’s performance and reliability.
|
||||
|
||||
## [v2.1.0] - 2025-10-27
|
||||
|
||||
**Focus**: Resizable/maximized window (EasyX reinforced by Win32), first-phase layout manager (HBox/VBox), early Tabs control. We also fixed black borders, maximize “ghosts”, flicker, and the issue where controls only appeared after interaction. Control-level **background snapshot/restore**, **single-line truncation**, and **tooltips** are now standardized.
|
||||
|
||||
### ✨ Added
|
||||
|
||||
- **Bilingual API Documentation (Chinese and English)**
|
||||
- The documentation provides a detailed introduction of each class, including API descriptions, functionalities, and points to note, with a comprehensive explanation of each control.
|
||||
|
||||
- **Window resize/maximize reinforcement (EasyX + Win32)**
|
||||
- `Window::enableResize(bool enable, int minW, int minH)`; toggle at runtime and set min track size.
|
||||
- Subclassed WndProc for `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT` with `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`.
|
||||
|
||||
+194
-1
@@ -3,16 +3,209 @@
|
||||
StellarX 项目所有显著的变化都将被记录在这个文件中。
|
||||
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)
|
||||
|
||||
[English document](CHANGELOG.en.md)
|
||||
|
||||
## [v3.0.1] - 2026 - 03 - 17
|
||||
|
||||
==注意==
|
||||
|
||||
此次更新变更了**TextBox::setText语义**之前如果再调用`setText`之后手动调用了`draw`方法现在应当删除,否则旧代码在新版本可能会造成`TextBox`闪烁(概率触发)【配置包含目录与库目录】
|
||||
|
||||
### 🙏 鸣谢
|
||||
|
||||
感谢用户[Pengfei Zhu](https://github.com/zhupengfeivip)帮StellarX完善文档【配置包含目录与库目录】部分,以及反馈在窗口模式传递参数NULL时会弹出命令行窗口的问题[Issues#9](https://github.com/Ysm-04/StellarX/issues/9)
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **TextBox::setText语义变化:**由原来的仅标脏改为更健全的工作原理
|
||||
- 如果文本未发生变化则立即return不做任何操作
|
||||
- 如果文本变化则根据是否已经创建图形上下文/窗口决定是否立即重绘/向上请求,如果图形上下文/窗口还为创建则仅标记为脏
|
||||
- **TextBox-文本溢出截断:**如果用户输入或者调用`setText`设置文本,文本不超过`maxCharLen`但是像素长度超过`TextBox`则按字符截断并添加...绘制,内部仍然保存完整的文本不影响get方法获取
|
||||
|
||||
### ✅ 修复
|
||||
|
||||
- **TabControl::getActiveIndex() const当页签对列表不为空时,如果所有页签都未激活则会返回最后一个页签的索引:**修复再调用`getActiveIndex()`概率获取错误的索引,如果页签对列表为空或者没有页签激活则返回-1,如果有页签激活则返回对应页签索引
|
||||
- **对话框打开时持续持续触发全量重绘:**Window新增变量`dialogOpen`,在对话框弹出时设置`dialogOpen`为`true`,只有dialogOpen为真时才判断`needredraw`,在重绘后立即设置`dialogOpen`为`flase`
|
||||
- **对话框关闭合成WM_MOUSEMOVE下发更新 可见控件hover 状态,在对话框打开时也会触发:**合成`WM_MOUSEMOVE`下发时判断是否有对话框关闭,只有`dialogClose`为真才合成并下发`WM_MOUSEMOVE`
|
||||
- **Window::runEventLoop普通控件事件分发顺序:**将`controls(vector)`的正序遍历`for (auto& c : controls)`改为逆序遍历`for (auto it = controls.rbegin(); it != controls.rend(); ++it)`
|
||||
|
||||
## [v3.0.0] - 2026 - 01 - 09
|
||||
|
||||
### ✨ 新增
|
||||
|
||||
- **日志系统使用Demo** 在:examples\SXLog-日志系统使用demo
|
||||
|
||||
- **轻量日志系统 SxLog(SxLogger / SxLogLine / SxLogScope / Sink / TagFilter / LanguageSwitch):**新增统一日志入口与宏封装,支持按级别与 Tag 筛选输出,支持控制台/文件落地与可选滚动(按大小阈值),并提供中英文双语文本选择能力(`SX_T` / `SxT`)。日志宏具备短路机制:未命中级别或 Tag 时不构造日志对象、不拼接字符串;输出侧以行级互斥保证多线程下不交错。该模块不依赖 WinAPI 调试输出通道、不引入第三方库。
|
||||
- 典型用法:`SX_LOGD/SX_LOGI/SX_LOGW/SX_LOGE/SX_LOGF/SX_LOG_TRACE` + 流式拼接;`SX_TRACE_SCOPE` 作用域耗时统计
|
||||
- 核心配置:`setMinLevel(...)`、`setTagFilter(...)`、`enableConsole(true/false)`、`enableFile(path, append, rotateBytes)`、`setLanguage(ZhCN/EnUS)`、`setGBK()`
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **TabControl 页签切换时序调整:**修改页签按钮切换逻辑为“先关闭当前已打开页,再打开目标页”,避免“先打开再关闭”在复杂容器/快照链路下引入的时序不确定性;对外 API 不变(涉及 `TabControl::add` 内部切换逻辑)。
|
||||
- **控件 Setter 语义收敛:**明确控件 Setter 的职责为“更新状态并标脏”,绘制统一由窗口/容器的重绘收口机制触发,降低生命周期耦合与快照污染风险(见下方相关修复条目)。
|
||||
|
||||
### ✅ 修复
|
||||
|
||||
- **TextBox::setText 在进入事件循环前调用触发中断:**修复在窗口尚未初始化(未 `initgraph()`、图形上下文未就绪)前调用 `TextBox::setText()` 导致访问冲突崩溃的问题。旧实现将“状态更新”和“立即绘制”耦合,`setText()` 内部直接触发 `draw()`,进而在无图形上下文时进入 `saveBackground()/getimage()` 路径崩溃;现已移除 Setter 内直接绘制,仅保留赋值与置脏,绘制交由统一重绘流程完成。
|
||||
- **TabControl 切换/关闭后 Table 新旧内容重叠(重影):**修复页签切换与表格重置数据后出现的稳定残影问题。根因是“快照未就绪阶段发生局部重绘”导致快照污染;本次回退 `setIsVisible(true)` 的“立即向上 requestRepaint”行为,改为仅置脏,并为 `Canvas::requestRepaint` 与 `TabControl::requestRepaint` 增加护栏:当容器 `dirty` 或 `hasSnap=0` 或快照缓存无效时,禁止 partial fast-path,自动降级为全量重绘以保证快照链路正确性;同时修正 `Table::setData(...)` 列补齐边界问题,降低异常噪声。
|
||||
|
||||
### ⚠️ 可能不兼容
|
||||
|
||||
- **删除 Button 尺寸别名 API(Breaking):**移除 `Button::getButtonWidth()` / `Button::getButtonHeight()`,统一使用基类 `Control::getWidth()` / `Control::getHeight()` 获取控件尺寸。该改动会导致旧代码在升级到 v3.0.0 后编译失败,但行为语义保持一致(仍读取同一 `width/height`)。
|
||||
- **可见性/文本设置的“即时刷新”副作用移除:**`setIsVisible(true)` 与 `setText()` 等 Setter 不再保证立刻触发绘制;如业务代码此前依赖“调用后立即可见”,需要确保后续存在一次重绘收口(窗口主循环/容器重绘/显式刷新)以完成视觉更新。
|
||||
|
||||
### 📌 升级指引
|
||||
|
||||
1. **Button 宽高接口迁移:**
|
||||
- `getButtonWidth()` → `getWidth()`
|
||||
- `getButtonHeight()` → `getHeight()`
|
||||
2. **Setter 不再“立即绘制”的适配:**
|
||||
- 初始化阶段可先设置属性(如 `TextBox::setText("default")`),由首次 `Window::draw()` / 主事件循环的统一绘制完成可视刷新;
|
||||
- 若在非事件驱动场景下程序化更新后需要立刻刷新,请确保触发一次统一重绘路径(避免在 Setter 内手动调用 `draw()` 造成生命周期耦合)。
|
||||
3. **SxLog 接入建议:**
|
||||
- 在程序入口完成基础配置(控制台输出/最低级别/语言),并使用统一 Tag(如 `Dirty/Resize/Table/Canvas/TabControl`)建立可回放的事件链路;高频路径建议通过级别与 Tag 控制噪声与 I/O 成本。
|
||||
|
||||
## [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.0的修复版本==
|
||||
|
||||
### ✅ 修复
|
||||
|
||||
- `TabControl`类重写了基类的`setDirty`方法保证页签+页列表同步更新
|
||||
|
||||
状态
|
||||
|
||||
- `Canvas`容器和特殊容器`TabControl`以及对话框`Dialog`重写`requestRepaint`方法,控件向上冒泡时传递父指针,请求重绘时只向上到父一级,不再传递到根。并且不再重绘整个父容器,而是由父容器重绘标脏的子控件,避免了频繁真个容器重绘导致的频闪
|
||||
|
||||
- `Dialog`中重写了`saveBackground`和`restBackground`方法,保证对话框关闭后不会有边框残留
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- 彻底禁用`Control`的移动构造`Control(const Control&) = delete;`
|
||||
`Control& operator=(const Control&) = delete;`
|
||||
`Control(Control&&) = delete;`
|
||||
`Control& operator=(Control&&) = delete;`
|
||||
|
||||
## [v2.2.0] - 2025-11-02
|
||||
|
||||
**重点**:正式引入选项卡控件(TabControl),增强控件显隐与布局响应能力,并完善文本样式机制;修复若干UI细节问题以提升稳定性。
|
||||
|
||||
### ✨ 新增
|
||||
|
||||
- **选项卡容器控件 TabControl**:新增 `TabControl` 类,实现多页面选项卡界面。支持页签栏在 **上/下/左/右** 四种位置排列,可通过 `TabControl::setTabPlacement(...)` 设置。提供 `TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&&)` 接口,一次性添加“页签按钮+页面内容”对,并自动管理切换显示。各页内容区域由 `Canvas` 承载,点击不同页签(Button,TOGGLE模式)将切换显示对应页面**【见 API】**。TabControl 在窗口大小变化时可自动调整页签布局,并使用背景快照避免透明主题下的叠影问题。
|
||||
- **控件显隐控制**:所有控件现支持运行时**显示/隐藏**切换。基类新增方法 `Control::setIsVisible(bool)` 控制自身可见性,隐藏后控件不参与绘制和事件。容器控件(`Canvas`)重写该方法,实现**级联隐藏**:隐藏容器将自动隐藏其中所有子控件,再次显示时子控件也随之恢复。这使一组界面元素的显隐切换更加方便。
|
||||
- **窗口尺寸变化响应**:引入 `Control::onWindowResize()` 虚函数,当父窗口尺寸改变时调用。各控件可通过实现此函数响应窗口大小调整,例如重置背景缓存、调整布局等。`Canvas` 已实现该函数,会在窗口变化时**递归通知子控件**调用 `onWindowResize()`,确保嵌套布局能正确更新。此改进解决了之前窗口拉伸后子控件可能位置错位或背景异常的问题。
|
||||
- **Label 文本样式结构**:`Label` 控件现在使用统一的 `ControlText` 样式结构管理字体和颜色。可通过访问公有成员 `Label::textStyle` 设置字体名称、大小、颜色、下划线等属性,实现**完整文本样式定制**(替代原先的 setTextColor 接口)。这使 Label 在展示文本时支持更丰富的格式。
|
||||
- **Dialog 防重复机制**:`Window` 新增内部检查函数,**防止重复弹出相同内容的对话框**。在使用 `MessageBox::showAsync` 非模态弹窗时,框架会判断是否已有相同标题和消息的对话框未关闭,若是则避免再次创建。此机制杜绝了短时间内弹出多个相同提示的情况。
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **文本颜色接口调整**:`Label` 移除了过时的 `setTextColor` 方法,改为通过公有的 `textStyle.color` 设置文字颜色。如需改变 Label 文本颜色,请直接修改 `label.textStyle.color` 并重绘。此改动提升了文本属性管理的一致性,但可能对旧版代码不兼容,需要做相应替换。
|
||||
- **Tooltip 样式与切换**:`Button` 的悬停提示 (Tooltip) 接口调整为支持更多自定义。`Button::setTooltipStyle` 现在可灵活设置提示文字颜色、背景色及透明模式;新增 `setTooltipTextsForToggle(onText, offText)` 用于切换按钮在 **ON/OFF** 两种状态下显示不同的提示文字。原有 Tooltip 文案设置接口仍兼容,但内部实现优化,修正了先前切换按钮提示文字不更新的问题。
|
||||
- **控件坐标系与布局**:控件现在同时维护**全局坐标**和**局部坐标**。`Control` 新增成员(如 `getLocalX()/getLocalY()` 等)用于获取控件相对父容器的位置,以及 `parent` 指针指向父容器。这一改进使得嵌套容器布局计算更加便捷和准确。在绝对布局场景下,控件的全局坐标会在添加进容器时自动转换为本地坐标保存。开发者需要注意,新版中移动控件或调整尺寸应优先使用控件自带的 setter,以确保内部坐标同步更新。
|
||||
- **默认窗口调整**:窗口拉伸功能从实验转为默认支持。框架始终为主窗口启用了可调整大小的样式(`WS_THICKFRAME|WS_MAXIMIZEBOX|...`),不再需要调用独立的方法启用。这一变更简化了使用,同时意味着创建的窗口默认可以被用户拖拽拉伸。对于不希望窗口缩放的场景,可在创建窗口时通过传递特定模式标志来禁止。
|
||||
|
||||
### ✅ 修复
|
||||
|
||||
- **切换按钮 Tooltip 更新**:修正了切换模式按钮在状态改变时悬停提示未及时更新的问题。现在使用 `setTooltipTextsForToggle` 设置不同提示后,按钮在 **ON/OFF** 状态切换时悬停提示文字会正确显示对应内容。
|
||||
- **控件背景残影与坐标同步**:解决了某些情况下窗口或容器尺寸变化后控件背景未及时刷新、位置计算偏差的缺陷。利用 `onWindowResize` 统一丢弃并更新背景快照,避免了拉伸窗口时可能出现的控件背景残影和错位现象,界面稳定性提升。
|
||||
- **`Control`类背景快照内存泄漏**:在析构函数里调用了`discardBackground`释放并恢复背景快照,避免了上一版本未释放`*saveBkImage`造成的内存泄漏
|
||||
- **重复对话框弹出**:修复了快速重复调用非模态消息框可能出现多个相同对话框的问题。新增的去重判断保证相同内容的非模态对话框同一时间只会存在一个,避免用户界面受到干扰。
|
||||
- **其他**:优化了控件绘制刷新策略,减少某些场景下的不必要重绘,提升运行效率;修正少量内存管理细节以消除潜在泄漏。上述改进进一步提高了框架的性能与可靠性。
|
||||
|
||||
## [v2.1.0] - 2025-10-27
|
||||
|
||||
**重点**:窗口可拉伸/最大化补强(EasyX + Win32)、布局管理器(HBox/VBox 第一阶段)、选项卡控件雏形;系统性修复黑边、最大化残影、频闪与“控件需交互才出现”等历史问题。并统一了**背景快照/恢复**、**按钮单行截断**、**Tooltip** 的机制。
|
||||
|
||||
### ✨ 新增
|
||||
|
||||
- **中英文双语 API文档**
|
||||
- 文档详细介绍了每个类,以及API描述、功能和需要注意的地方,详细介绍了每个控件
|
||||
|
||||
- **窗口拉伸 / 最大化补强(在 EasyX 基础上用 Win32 加固)**
|
||||
- 新增 `Window::enableResize(bool enable, int minW, int minH)`;运行时开关可拉伸并设置最小跟踪尺寸。
|
||||
- 子类化窗口过程:处理 `WM_GETMINMAXINFO / WM_SIZE / WM_EXITSIZEMOVE / WM_ERASEBKGND / WM_PAINT`,并启用 `WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS`,解决 EasyX 窗口原生不可拉伸问题。
|
||||
|
||||
+29
-1
@@ -7,13 +7,41 @@ project(StellarX VERSION 2.0.0 LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
# 为了支持 out-of-source builds,创建构建目录
|
||||
set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build)
|
||||
|
||||
# 设置生成的二进制文件输出目录
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
# 包含头文件目录(目前头文件都在根目录)
|
||||
include_directories(${CMAKE_SOURCE_DIR})
|
||||
|
||||
# 源文件收集
|
||||
# 通过选项设置是否启用调试信息
|
||||
option(USE_DEBUG "Build with debug information" OFF)
|
||||
if(USE_DEBUG)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
else()
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
# 查找源文件
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/*.cpp"
|
||||
)
|
||||
|
||||
# 生成可执行文件
|
||||
add_executable(StellarX ${SOURCES})
|
||||
|
||||
# 可以选择性地查找外部库并链接(例如 Boost,SDL2等)
|
||||
# FindPackage(Boost REQUIRED)
|
||||
# target_link_libraries(StellarX Boost::Boost)
|
||||
|
||||
# 为外部依赖配置路径
|
||||
# set(Boost_DIR "path/to/boost")
|
||||
# find_package(Boost REQUIRED)
|
||||
|
||||
# 如果有额外的库需要链接,继续在此处添加
|
||||
# target_link_libraries(StellarX Boost::Boost)
|
||||
|
||||
|
||||
@@ -1,872 +0,0 @@
|
||||
# English API Documentation
|
||||
|
||||
[API文档](API 文档.md)
|
||||
|
||||
Below is the API documentation for the **StellarX GUI framework** classes and functions, in English. It covers the usage, parameters, and behavior of each primary component:
|
||||
|
||||
### Control Class (Abstract Base Class)
|
||||
|
||||
**Description:** `Control` is the abstract base class for all GUI controls. It defines common properties and interfaces such as position, size, and dirty (redraw) flags. It also implements saving and restoring of drawing state to ensure that drawing a control does not affect global graphics state. `Control` itself is not instantiated directly.
|
||||
|
||||
- **Key Properties:**
|
||||
- `x, y`: Coordinates of the control's top-left corner.
|
||||
- `width, height`: Dimensions of the control.
|
||||
- `dirty`: A flag indicating whether the control needs to be redrawn.
|
||||
- `show`: Visibility flag (whether the control is visible).
|
||||
- `rouRectangleSize`: A `StellarX::RouRectangle` struct that stores the ellipse width and height for rounded rectangle corners (used by controls with round-rect shape).
|
||||
- **Note:** `Control` internally maintains pointers (`currentFont`, etc.) to backup current drawing state (font, colors, line style) so that it can restore the state after custom drawing.
|
||||
- **Main Interface Methods:**
|
||||
- `virtual void draw() = 0;`
|
||||
**Description:** Pure virtual function to draw the control. Each derived control class must implement its own drawing logic.
|
||||
- `virtual bool handleEvent(const ExMessage& msg) = 0;`
|
||||
**Description:** Pure virtual function to handle an input event message (mouse or keyboard). Returns true if the event is consumed by this control (meaning it should not propagate further).
|
||||
- `void saveBackground(int x, int y, int w, int h);`
|
||||
**Description:** Saves a background snapshot of the specified area `(x, y, w, h)` (in screen coordinates) where the control is drawn. Used to restore background when needed (e.g., hiding a popup).
|
||||
- `void restBackground();`
|
||||
**Description:** Restores the last saved background image by putting it back to the saved position. Typically used when a control is hidden or needs to erase its drawing.
|
||||
- `void discardBackground();`
|
||||
**Description:** Discards the currently saved background snapshot and frees its resources. Should be called when the saved background is no longer valid (e.g., after window resize) to avoid using stale snapshots.
|
||||
- *Property Accessors and Mutators:*
|
||||
- `int getX() const, getY() const, getWidth() const, getHeight() const`: Get the control's position and size.
|
||||
- `int getRight() const, getBottom() const`: Get the coordinate of the control's right boundary (`x + width`) and bottom boundary (`y + height`).
|
||||
- `void setX(int nx), setY(int ny), setWidth(int w), setHeight(int h)`: Set the control's position or size. Setting these will mark the control as dirty (need redraw).
|
||||
- `void setDirty(bool d)`: Manually mark the control as needing redraw or not.
|
||||
- `void setShow(bool visible)`: Set the control's visibility (`show` flag). If set to false, the control will not draw itself.
|
||||
- `bool isVisible() const`: Returns the current visibility state of the control (`show` flag). **Note:** If a control is hidden, its `draw()` is typically not called.
|
||||
- *Other Methods:*
|
||||
- `virtual bool model() const = 0;`
|
||||
**Description:** Pure virtual function to check if the control is "modal". Only dialog controls need to implement this (modal dialogs return true), other controls can ignore (return false). The window event loop uses this to prioritize modal dialog events.
|
||||
- `void saveStyle(); void restoreStyle();` *(protected)*
|
||||
**Description:** Saves the current global drawing style (font, colors, line style, etc.) and restores it. Controls should call `saveStyle()` at the beginning of `draw()` to backup global settings, and `restoreStyle()` at the end of `draw()` to not pollute the global state for other drawings.
|
||||
|
||||
### Window Class (Application Main Window)
|
||||
|
||||
**Description:** The `Window` class represents the main application window. It manages the window creation, message loop, and acts as the root container for all controls and dialogs. Typically, an application creates one Window instance as the main GUI container.
|
||||
|
||||
- **Constructors:**
|
||||
- `Window(int width, int height, int mode, COLORREF bkColor = ..., std::string headline = "窗口")`
|
||||
**Description:** Creates a Window with specified width and height. `mode` is the graphics mode (such as double-buffering or manual flush flags in EasyX, use NULL for default). `bkColor` is the window background color, and `headline` is the window title text. Construction does not immediately display the window; you need to call `draw()` to open it.
|
||||
- **Main Methods:**
|
||||
- `void draw();`
|
||||
**Description:** Initializes and opens the graphics window. It uses the specified mode to create the drawing window (calls EasyX `initgraph`), sets up the window's title and background color, and then draws all added controls. Usually called once right after creating the Window object.
|
||||
- `void draw(std::string pImgFile);`
|
||||
**Description:** Similar to the above, but uses an image file as the window background. This loads the image and draws it scaled to the window size, then draws all child controls on top.
|
||||
- `void runEventLoop();`
|
||||
**Description:** Enters the main event processing loop for the window. This loop continuously retrieves user input events (`peekmessage`) and dispatches them to child controls or dialogs:
|
||||
- Dialogs (non-modal ones) get first priority: for each event, it iterates over `dialogs` and if a dialog is visible (`isVisible() == true`) and not modal (`model() == false`), it calls that dialog's `handleEvent`. If any dialog consumes the event (returns true), it stops further propagation.
|
||||
- If no dialog consumed the event, it then iterates through `controls` (regular controls) in order and calls each control's `handleEvent` until one returns true (meaning the event was handled).
|
||||
- After handling an event (or if none present), it checks if any dialog is open or was just closed (`dialogClose` flag). If so, it forces a redraw of the entire interface to keep things updated:
|
||||
- It synthesizes a `WM_MOUSEMOVE` message to update hover states (ensuring controls properly update their highlight states), then calls `draw()` on all controls and dialogs, and flushes the drawing.
|
||||
- It resets `dialogClose` to false after the redraw.
|
||||
- Sleeps for 10 ms at each loop iteration to prevent high CPU usage.
|
||||
- The loop continues until a `WM_CLOSE` message is received (e.g., user closes the window), at which point it sets `running` false and breaks out.
|
||||
- `void setBkImage(IMAGE* img);` / `void setBkImage(std::string filePath);`
|
||||
**Description:** Changes the window's background image. You can pass an already loaded `IMAGE*`, or specify a file path to load. After setting, it immediately repaints the window (drawing all controls and dialogs) with the new background.
|
||||
- `void setBkcolor(COLORREF c);`
|
||||
**Description:** Sets the window background color and immediately clears the screen with that color (this does not remove an existing background image if one was set; it just overlays the color).
|
||||
- `void setHeadline(std::string title);`
|
||||
**Description:** Sets the window title text. If the window is already open, it updates the title bar via `SetWindowText` immediately.
|
||||
- `void addControl(std::unique_ptr<Control> control);`
|
||||
**Description:** Adds a regular control to the window. The Window maintains a list of such controls; `draw()` and `runEventLoop()` use this list for rendering and event dispatch. Ownership is transferred (control is managed by Window after adding).
|
||||
- `void addDialog(std::unique_ptr<Control> dialog);`
|
||||
**Description:** Adds a dialog control to the window (usually an instance of `Dialog`). Dialogs are managed separately from normal controls, with their own event and drawing handling.
|
||||
- `bool hasNonModalDialogWithCaption(const std::string& caption) const;`
|
||||
**Description:** Checks if there is a currently open **non-modal** dialog with the given title caption. Returns true if found. Typically used to avoid opening duplicate dialogs with the same purpose.
|
||||
- **Overloaded variant:** `bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& text) const;`
|
||||
Additionally compares the dialog's message text. This provides a stricter duplicate check (same caption **and** same content). It is used internally by `MessageBox::showAsync` to prevent popping up the same notification multiple times.
|
||||
- *Information Getters:*
|
||||
- `HWND getHwnd() const;` Returns the underlying window handle (HWND from EasyX).
|
||||
- `int getWidth() const, getHeight() const;` Returns the window's dimensions.
|
||||
- `std::string getHeadline() const;` Returns the current window title string.
|
||||
- `COLORREF getBkcolor() const;` Returns the background color.
|
||||
- `IMAGE* getBkImage() const;` Returns the current background image pointer (if any).
|
||||
- `std::vector<std::unique_ptr<Control>>& getControls();` Returns a reference to the list of child controls (for iteration or debugging; generally you don't modify this directly).
|
||||
|
||||
### Canvas Class (Container Control)
|
||||
|
||||
**Description:** `Canvas` is a container control that can hold child controls, grouping them with a unified background and border style, and enabling composite layouts. Canvas itself is a control (derives from `Control`), and typically used for panels or dialog surfaces.
|
||||
|
||||
- **Features:**
|
||||
- Supports four rectangular background shapes (normal rectangle or rounded rectangle, each with border or borderless version). Set via `setShape`.
|
||||
- Customizable background color (`canvasBkColor`), border color (`canvasBorderColor`), border line style (`canvasLineStyle`), and fill mode (`canvasFillMode`, e.g., solid color, hatched pattern, no fill).
|
||||
- Automatically manages the lifecycle of child controls (added via `addControl`, and destroyed when Canvas is destroyed).
|
||||
- When drawing, Canvas will draw its background and then iterate through its child controls to draw them. For event handling, it propagates events to children (with last-added getting first chance).
|
||||
- **Important Members:**
|
||||
- `std::vector<std::unique_ptr<Control>> controls;` The list of child controls.
|
||||
- `StellarX::ControlShape shape;` Background shape of the container (default is RECTANGLE).
|
||||
- `StellarX::FillMode canvasFillMode;` Background fill mode (default Solid color).
|
||||
- `StellarX::LineStyle canvasLineStyle;` Border line style (default Solid line).
|
||||
- `int canvaslinewidth;` Border line width in pixels.
|
||||
- `COLORREF canvasBorderColor, canvasBkColor;` Border and background colors of the container.
|
||||
- `StellarX::LayoutKind Kind;` (Reserved) Layout management type (Absolute/HBox/VBox/Grid). Not fully implemented but can be used to indicate layout strategy.
|
||||
- **Note:** Canvas overrides `isVisible()` to always return false because a Canvas itself is not considered a standalone visible entity for event loop prioritization (the event loop doesn't handle Canvas directly, only its children). This doesn't affect drawing or child event dispatch.
|
||||
- **Main Methods:**
|
||||
- `Canvas(); Canvas(int x, int y, int width, int height);`
|
||||
**Description:** Constructors to create a Canvas container at position `(x,y)` with given size.
|
||||
- `void addControl(std::unique_ptr<Control> control);`
|
||||
**Description:** Adds a child control to the canvas. The child control's coordinates are relative to the canvas (Canvas does not reposition children automatically; you should set the child's position accordingly before adding). After adding, the Canvas is marked dirty so it will redraw with the new content.
|
||||
- `void setShape(StellarX::ControlShape shape);`
|
||||
**Description:** Sets the canvas background shape. Supports `RECTANGLE`, `B_RECTANGLE` (borderless rectangle), `ROUND_RECTANGLE` (rounded corners with border), `B_ROUND_RECTANGLE` (rounded without border). If a circular/ellipse shape is passed, Canvas does not support it (it will treat it as RECTANGLE internally).
|
||||
- `void setCanvasFillMode(StellarX::FillMode mode);`
|
||||
**Description:** Sets the background fill mode, e.g., Solid, Null (no fill), Hatched pattern, or pattern/bitmap fill. This influences how the background is drawn (using EasyX `setfillstyle`). Default is solid fill.
|
||||
- `void setBorderColor(COLORREF color);` / `void setCanvasBkColor(COLORREF color);` / `void setCanvasLineStyle(StellarX::LineStyle style);` / `void setLinewidth(int width);`
|
||||
**Description:** Set the container's border color, background color, border line style, and line width, respectively. Changing any of these will mark the Canvas dirty (to be redrawn).
|
||||
- `void draw() override;`
|
||||
**Description:** Draws the canvas background and all its child controls. Steps:
|
||||
1. If not dirty, returns immediately (no redraw needed).
|
||||
2. Saves current style and sets line color, fill color, fill style, and line style based on canvas properties.
|
||||
3. Draws the background shape: e.g., calls `fillrectangle` or `fillroundrect` depending on `shape`. If an unsupported shape (Circle/Ellipse) was set, it defaults to rectangle.
|
||||
4. Iterates over `controls` and calls each child's `setDirty(true)` then `draw()`, ensuring each child is redrawn on this canvas.
|
||||
5. Restores the style and marks itself clean (`dirty = false`).
|
||||
*Note:* Canvas uses `saveStyle()`/`restoreStyle()` to preserve global drawing state, and uses EasyX drawing functions to fill the background according to its shape.
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**Description:** Propagates the event to its children in reverse order (so the last added child gets the event first). It iterates `controls` from end to start and calls each child's `handleEvent(msg)`. If any child returns true (meaning it consumed the event), Canvas stops and returns true. If none consumed it, returns false.
|
||||
*Use case:* This ensures that in overlapping or layered controls, the topmost (last added) gets first crack at the event, implementing a basic Z-order.
|
||||
- `void clearAllControls();` *(protected)*
|
||||
**Description:** Clears all child controls from the canvas, deleting them. This releases all smart pointers in the `controls` list. Typically used in destructor or when resetting the UI. In normal use, you don't call this directly (the Canvas destructor will automatically free children).
|
||||
|
||||
### Label Class (Static Text Label)
|
||||
|
||||
**Description:** `Label` displays a static text string on the UI. It supports transparent background and custom text style, but does not handle user input (no interactive events). It is lightweight and intended for captions, status messages, etc.
|
||||
|
||||
- **Constructors:**
|
||||
|
||||
- `Label(); Label(int x, int y, std::string text = "标签", COLORREF textColor = BLACK, COLORREF bkColor = WHITE);`
|
||||
**Description:** Creates a Label control at `(x,y)`. You can specify initial text, text color, and background color. The default text is "标签" (label in Chinese), default text color is black, background is white.
|
||||
|
||||
- **Key Properties:**
|
||||
|
||||
- `std::string text;` The text content displayed.
|
||||
- `COLORREF textColor;` The text color.
|
||||
- `COLORREF textBkColor;` The background color behind the text when not transparent.
|
||||
- `bool textBkDisap;` Flag indicating if the background is transparent. If true, the label is drawn with a transparent background (so whatever is behind it shows through); if false, it draws an opaque rectangle behind the text using `textBkColor`.
|
||||
- `StellarX::ControlText textStyle;` The text style struct (includes font face, size, weight, etc. as well as text color).
|
||||
- **Note:** Label's width and height are initially 0; usually a Label's size is determined by its text content automatically when drawn. You normally don't need to set width/height for labels.
|
||||
|
||||
- **Main Methods:**
|
||||
|
||||
- `void setTextdisap(bool transparent);`
|
||||
**Description:** Sets whether the label's background is transparent. If true, when drawing, the text is rendered with `TRANSPARENT` background mode (not overwriting the background behind it). If false, the text is drawn on an opaque background colored `textBkColor`.
|
||||
- `void setTextColor(COLORREF color);` / `void setTextBkColor(COLORREF color);`
|
||||
**Description:** Sets the text color and background color, respectively. After setting, the label is marked dirty for redraw.
|
||||
- `void setText(std::string text);`
|
||||
**Description:** Changes the label's displayed text content. Marks the label dirty (so it will redraw with the new text).
|
||||
- `void draw() override;`
|
||||
**Description:** Draws the label's text (and background if not transparent). It:
|
||||
- Calls `saveStyle()`.
|
||||
- If `textBkDisap` is true, calls `setbkmode(TRANSPARENT)`; otherwise `setbkmode(OPAQUE)` and sets `bkcolor` to `textBkColor`.
|
||||
- Sets the text color via `settextcolor(textColor)` and font via `settextstyle` based on `textStyle`.
|
||||
- Saves the area behind where the text will be drawn using `saveBackground` (so it can restore it later if needed).
|
||||
- Writes the text at `(x,y)` using `outtextxy`.
|
||||
- Restores the drawing style and marks `dirty = false`.
|
||||
This approach ensures that if the label text is redrawn, the background behind it is handled properly.
|
||||
- `bool handleEvent(...) override;`
|
||||
**Description:** Label does not handle any events; it always returns false (meaning it never consumes events).
|
||||
- `void hide();`
|
||||
**Description:** Hides the label. Specifically, it uses `restBackground()` to put back the saved background (erasing the text from the screen) and then `discardBackground()` to free the snapshot, and sets `dirty = false`. This is typically used when a Label is serving as a transient tooltip or overlay and needs to be removed without causing a full screen redraw.
|
||||
|
||||
- **Usage Scenarios:**
|
||||
Label is used for static text like descriptions, titles, or status information. You can adjust `textStyle` to change the font and size (by default, the font might be "微软雅黑" with height 0 meaning default height). For example:
|
||||
|
||||
```
|
||||
Label *status = new Label(10, 10, "Ready", RGB(0,128,0));
|
||||
status->textStyle.nHeight = 20; // set font size if needed
|
||||
```
|
||||
|
||||
If you want the label to blend into a custom background, you can do:
|
||||
|
||||
```
|
||||
status->setTextdisap(true);
|
||||
```
|
||||
|
||||
to make the background transparent.
|
||||
|
||||
### Button Class (Button Control)
|
||||
|
||||
**Description:** `Button` provides a clickable button control, supporting both standard push-button behavior and toggle (on/off) behavior. It handles click and hover events, and allows setting various styles (shape, colors). Buttons are one of the primary interactive controls.
|
||||
|
||||
- **Operating Modes:** Determined by `StellarX::ButtonMode`:
|
||||
|
||||
- `NORMAL` – Standard push-button. Each click triggers an action but does not maintain a pressed state.
|
||||
- `TOGGLE` – Toggle button. Each click changes the button's state (pressed vs not pressed) and triggers different callbacks for each state.
|
||||
- `DISABLED` – Disabled button. It does not respond to user clicks and typically displays in a grayed-out style with strikeout text.
|
||||
- The mode can be changed via `setButtonMode(ButtonMode mode)`.
|
||||
|
||||
- **Appearance Shape:** Determined by `StellarX::ControlShape`:
|
||||
|
||||
- Supports rectangle (`RECTANGLE`/`B_RECTANGLE`), rounded rectangle (`ROUND_RECTANGLE`/`B_ROUND_RECTANGLE`), circle (`CIRCLE`/`B_CIRCLE`), and ellipse (`ELLIPSE`/`B_ELLIPSE`) – eight shape options in total (`B_` prefix indicates borderless). Use `setButtonShape(ControlShape shape)` to set.
|
||||
- Note: When switching shapes, ensure the button's width/height are appropriate (for circle/ellipse shapes, the drawing will use width and height differently; circle uses min(width,height)/2 as radius, ellipse uses width,height as bounding box).
|
||||
- The button's border will be drawn for shapes without the `B_` prefix; borderless shapes omit the border.
|
||||
|
||||
- **Key Configurable Properties:**
|
||||
|
||||
- **Colors:**
|
||||
- `buttonTrueColor` – color when button is in pressed state (for toggle or momentary press in normal mode).
|
||||
- `buttonFalseColor` – color when button is not pressed (normal default state).
|
||||
- `buttonHoverColor` – color when mouse is hovering over the button.
|
||||
- `buttonBorderColor` – border outline color.
|
||||
- **Fill:**
|
||||
- `buttonFillMode` – fill mode for the button background (solid, hatched pattern, custom pattern, custom image).
|
||||
- `buttonFillIma` – pattern style for hatched fills (if `FillMode == Hatched`).
|
||||
- `buttonFileIMAGE` – pointer to an `IMAGE` for custom image fill (if `FillMode == DibPattern`).
|
||||
- **Text:**
|
||||
- `text` – the text label displayed on the button.
|
||||
- `textStyle` – text style (font face, size, weight, etc., including text color).
|
||||
- Additionally, `cutText` is used internally if the text is too long to fit; the button can automatically truncate and add "..." or a Chinese ellipsis to fit.
|
||||
- **Tooltip (hover hint):**
|
||||
- `tipEnabled` – whether a tooltip is enabled on hover.
|
||||
- `tipTextClick` – tooltip text for the NORMAL mode (single-state).
|
||||
- `tipTextOn` / `tipTextOff` – tooltip texts for toggle mode when the button is ON or OFF.
|
||||
- `tipFollowCursor` – if true, tooltip appears near the cursor; if false, tooltip appears at a fixed offset below the button.
|
||||
- `tipDelayMs` – delay in milliseconds before tooltip appears when hovering.
|
||||
- `tipOffsetX, tipOffsetY` – offset of tooltip position relative to cursor or button (depending on `tipFollowCursor`).
|
||||
- `tipLabel` – an internal `Label` object used to display the tooltip text.
|
||||
- **Rounded Corner Size:** In round-rectangle shapes, the corner radii come from the inherited `rouRectangleSize` (`ROUND_RECTANGLEwidth` and `height`). Set via `setRoundRectangleWidth(int)` and `setRoundRectangleHeight(int)`.
|
||||
|
||||
- **Main Methods:**
|
||||
|
||||
- `void setOnClickListener(const std::function<void()>&& callback);`
|
||||
**Description:** Sets the callback function to be invoked when the button is clicked in NORMAL mode. For a toggle button, this callback is called every time the button is clicked (but typically you might use toggle-specific callbacks instead). The callback is executed when the user releases the mouse button over the button.
|
||||
- `void setOnToggleOnListener(const std::function<void()>&& callback);`
|
||||
**Description:** Sets the callback for when a toggle button is switched to the "on/pressed" state.
|
||||
- `void setOnToggleOffListener(const std::function<void()>&& callback);`
|
||||
**Description:** Sets the callback for when a toggle button is switched to the "off/released" state.
|
||||
- `void setButtonText(const char* text)` / `void setButtonText(std::string text);`
|
||||
**Description:** Changes the button's label text. Accepts a C-string or an std::string. After changing, it recalculates the text width/height (for centering and truncation logic) and marks the button dirty to redraw.
|
||||
- `void setButtonBorder(COLORREF border);` / `void setButtonFalseColor(COLORREF color);`
|
||||
**Description:** Sets the border color and the default (false) state fill color, respectively.
|
||||
- `void setFillMode(FillMode mode);` / `void setFillPattern(FillStyle pattern);` / `void setFillImage(const std::string& path);`
|
||||
**Description:** Configures the background fill of the button:
|
||||
- `setFillMode` changes the fill mode (solid, hatched pattern, custom bitmap, etc).
|
||||
- If setting to a hatched pattern fill, call `setFillPattern` to choose the hatch style (`StellarX::FillStyle`).
|
||||
- If setting to a custom image fill, call `setFillImage` with the image file path; it will load the image (resizing to button size) and store it in `buttonFileIMAGE`.
|
||||
- `void setButtonClick(bool click);`
|
||||
**Description:** Programmatically sets the button's "clicked" state.
|
||||
- For NORMAL mode: setting true will simulate a click – it triggers the onClick callback (if any) and then immediately resets to false (not pressed).
|
||||
- For TOGGLE mode: setting true or false will force the button into that state and trigger the corresponding onToggle callback if provided.
|
||||
- In any case, it marks the button dirty and redraws it (ensuring visual state update).
|
||||
- **Note:** If the button is disabled, this method has no effect.
|
||||
- `bool isClicked() const;`
|
||||
**Description:** Returns whether the button is currently in the pressed state. This is primarily meaningful for toggle buttons (true if toggled on, false if off). For a normal button, this is typically false except during the brief moment of click handling.
|
||||
- Other getters such as `getButtonText()`, `getButtonMode()`, `getButtonShape()`, `getFillMode()`, `getFillPattern()`, `getFillImage()`, `getButtonBorder()`, `getButtonTextColor()`, `getButtonTextStyle()`, `getButtonWidth()`, `getButtonHeight()` provide read access to the button's corresponding properties.
|
||||
- `void draw() override;`
|
||||
**Description:** Draws the button's background and text according to its current state and properties:
|
||||
- Chooses the fill color based on state: if disabled, uses `DISABLEDCOLOUR` (gray) and applies a strike-out to the text; otherwise, if `click` is true (pressed or toggled on) uses `buttonTrueColor`, else if `hover` is true uses `buttonHoverColor`, otherwise `buttonFalseColor`.
|
||||
- Sets transparent background mode for text, sets border color and text style (color, font).
|
||||
- If `needCutText` is true, calls `cutButtonText()` to possibly truncate the text with ellipsis if it doesn't fit in the button width.
|
||||
- Recalculates `text_width` and `text_height` if the text content or style changed (to ensure text is centered correctly).
|
||||
- Sets the fill style for background (using `buttonFillMode`, pattern or image as needed).
|
||||
- Draws the shape:
|
||||
- For rectangle shapes: uses `fillrectangle` or `solidrectangle`.
|
||||
- For round-rectangle: uses `fillroundrect` or `solidroundrect` with `rouRectangleSize` radii.
|
||||
- For circle: uses `fillcircle` or `solidcircle` with radius = min(width,height)/2.
|
||||
- For ellipse: uses `fillellipse` or `solidellipse` with bounding box corners.
|
||||
- Draws the text:
|
||||
- It calculates the position to center the text/cutText horizontally and vertically within the button.
|
||||
- If `isUseCutText` is true (meaning the original text was truncated), it draws `cutText` (with "..." or Chinese ellipsis).
|
||||
- Otherwise, draws the full `text`.
|
||||
- Restores style and marks dirty false after drawing.
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**Description:** Handles mouse events for the button:
|
||||
- If the button is hidden (`show == false`), returns false immediately.
|
||||
- Tracks the previous `hover` and `click` states to detect changes.
|
||||
- If the message is `WM_MOUSEMOVE`, updates `lastMouseX/Y` for tooltip positioning.
|
||||
- Determines `hover` state by checking if the mouse `(msg.x, msg.y)` is within the button's shape (calls `isMouseInCircle` or `isMouseInEllipse` for those shapes, otherwise simple rectangle bounds check).
|
||||
- Handling `WM_LBUTTONDOWN`: if button is enabled and mouse is over it:
|
||||
- In NORMAL mode: set `click = true` (button appears pressed), mark dirty, and mark event consumed.
|
||||
- In TOGGLE mode: do nothing on down (toggle action is deferred to release).
|
||||
- Handling `WM_LBUTTONUP`: if button is enabled and mouse is currently over it:
|
||||
- In NORMAL mode: if it was pressed (`click` was true), trigger the onClick callback (if set), then set `click = false` (release it). Mark dirty, consume event, and hide tooltip (if any). Also flush the message queue of any pending mouse/keyboard events using `flushmessage` to prevent processing a duplicate click message.
|
||||
- In TOGGLE mode: flip the `click` state (`click = !click`). If it becomes true, trigger onToggleOn callback; if false, trigger onToggleOff callback. Mark dirty, consume event, update tooltip text via `refreshTooltipTextForState()`, hide tooltip, and flush message queue similarly.
|
||||
- Handling `WM_MOUSEMOVE`:
|
||||
- If moving outside (not hover anymore) while in NORMAL mode and the button was pressed (`click == true`), then user is dragging out: set `click = false` (cancel the press) and mark dirty (so it returns to unpressed visual).
|
||||
- If hover state changed (entered or exited), mark dirty.
|
||||
- Tooltip management:
|
||||
- If tooltip enabled (`tipEnabled`):
|
||||
- If just hovered (hover true and oldHover false): record the timestamp via `tipHoverTick` and set `tipVisible = false` (starting hover timer).
|
||||
- If just exited (hover false and oldHover true): immediately call `hideTooltip()` (which hides the tooltip label if it was visible).
|
||||
- If still hovering and tooltip not yet visible:
|
||||
- Check if current time minus `tipHoverTick` >= `tipDelayMs`; if so:
|
||||
- Set `tipVisible = true`.
|
||||
- Determine tooltip position: if `tipFollowCursor` is true, `tipX = lastMouseX + tipOffsetX`, `tipY = lastMouseY + tipOffsetY`. If false, perhaps `tipX = lastMouseX` (or button center) and `tipY = y + height` (so it appears below the button).
|
||||
- Set the tooltip text: if `tipUserOverride` is true (meaning user explicitly set tooltip text via `setTooltipText` or `setTooltipTextsForToggle`), then:
|
||||
- For NORMAL mode: always use `tipTextClick` (explicitly set text or maybe button text by default).
|
||||
- For TOGGLE mode: use `click ? tipTextOn : tipTextOff`.
|
||||
- If `tipUserOverride` is false (no explicit text provided):
|
||||
- If mode is TOGGLE: default behavior might be to use `tipTextOn/Off` (which could be set to default values like "On"/"Off" or left empty).
|
||||
- If mode is NORMAL: possibly do nothing (the code currently only sets text in else for toggle, leaving normal with no dynamic text unless overridden).
|
||||
- Position the internal `tipLabel` at (tipX, tipY), mark it dirty.
|
||||
- After updating tooltip state,
|
||||
- If `hover` or `click` state changed compared to old states, mark the button dirty so it will redraw to reflect highlight or press/unpress.
|
||||
- If the button is dirty, call `draw()` immediately for real-time feedback.
|
||||
- If tooltip is enabled and now visible (`tipVisible`), call `tipLabel.draw()` to draw the tooltip label.
|
||||
- Return true if the event was handled (for example, a click or certain modal conditions), else false.
|
||||
- (In summary, the event is considered consumed if a click occurred or if the modal logic swallowed it. Hover alone doesn’t consume the event.)
|
||||
|
||||
- **Usage Notes:**
|
||||
Typically, you create a Button and set its callback like:
|
||||
|
||||
```
|
||||
auto btn = std::make_unique<Button>(50, 50, 80, 30, "OK");
|
||||
btn->setOnClickListener([](){ /* handle click */ });
|
||||
```
|
||||
|
||||
For toggle functionality:
|
||||
|
||||
```
|
||||
btn->setButtonMode(StellarX::ButtonMode::TOGGLE);
|
||||
btn->setOnToggleOnListener([](){ /* handle toggle on */ });
|
||||
btn->setOnToggleOffListener([](){ /* handle toggle off */ });
|
||||
```
|
||||
|
||||
Adjusting appearance:
|
||||
|
||||
```
|
||||
btn->textStyle.color = RGB(255,255,255); // white text
|
||||
btn->setButtonFalseColor(RGB(100,150,200)); // default background
|
||||
btn->setButtonBorder(RGB(80,80,80)); // dark gray border
|
||||
btn->setButtonShape(StellarX::ControlShape::RECTANGLE);
|
||||
```
|
||||
|
||||
To provide a tooltip on hover:
|
||||
|
||||
```
|
||||
btn->tipEnabled = true;
|
||||
btn->tipTextClick = "Click to confirm"; // for normal mode
|
||||
// for toggle mode:
|
||||
// btn->setTooltipTextsForToggle("On state hint", "Off state hint");
|
||||
```
|
||||
|
||||
The Button control will handle highlighting itself when hovered, pressing down on click, toggling, etc. Ensure to keep the `runEventLoop` running so these UI feedbacks happen in real time.
|
||||
|
||||
### TextBox Class (Single-line Text Box)
|
||||
|
||||
**Description:** `TextBox` is a single-line text input box, which can either allow user input or be read-only. It uses an EasyX input box for user text entry (which is a modal popup), then displays the entered text in the control. It's a simple text field primarily for small inputs like numbers or short strings.
|
||||
|
||||
- **Modes:** Controlled by `StellarX::TextBoxmode`:
|
||||
|
||||
- `INPUT_MODE` – the user can click and enter text. On click, a modal input dialog appears where the user can type.
|
||||
- `READONLY_MODE` – the text box is for display only. Clicking it will not change the text; it might just show an alert that input is not allowed (the current implementation pops an InputBox with a message).
|
||||
- Set via `setMode(TextBoxmode mode)`.
|
||||
|
||||
- **Appearance:**
|
||||
|
||||
- Supports rectangular shapes (normal or rounded, with or without border). Set with `setTextBoxShape(ControlShape)`. Circular/ellipse shapes are not logically typical for text entry and are treated as rectangle in the implementation.
|
||||
- By default, a TextBox has a black border and white background. Use `setTextBoxBorder` and `setTextBoxBk` to change those.
|
||||
- Text style is adjustable via `textStyle` (including font face, size, color).
|
||||
|
||||
- **Key Properties:**
|
||||
|
||||
- `std::string text;` The content string displayed in the text box.
|
||||
- `TextBoxmode mode;` Current mode (input or read-only).
|
||||
- `ControlShape shape;` Current border shape (default RECTANGLE or B_RECTANGLE).
|
||||
- `bool click;` (Internal flag) Indicates if the text box was clicked (used to trigger the InputBox; it's set true on LButtonUp and then immediately reset after processing).
|
||||
- `size_t maxCharLen;` Maximum number of characters allowed. Default is 255.
|
||||
- `COLORREF textBoxBorderColor, textBoxBkColor;` Border color and background color of the text box.
|
||||
- `StellarX::ControlText textStyle;` Text style for the content (e.g., can use monospace font for numeric input if desired, or change the color).
|
||||
|
||||
- **Main Methods:**
|
||||
|
||||
- `void setMode(StellarX::TextBoxmode mode);`
|
||||
**Description:** Switches the text box between input mode and read-only mode. Changing mode does not clear the current text content.
|
||||
- `void setMaxCharLen(size_t len);`
|
||||
**Description:** Sets the maximum number of characters that can be input. If len > 0 (non-zero positive), it will enforce that limit. Input beyond this length will be truncated.
|
||||
- `void setTextBoxShape(StellarX::ControlShape shape);`
|
||||
**Description:** Sets the shape of the text box. Supports rectangle or rounded rectangle (with or without border). If a shape like circle/ellipse is passed, it's internally treated as rectangle (the implementation falls back to rectangle for unsupported shapes). Changing shape marks the text box dirty for redraw.
|
||||
- `void setTextBoxBorder(COLORREF color);` / `void setTextBoxBk(COLORREF color);`
|
||||
**Description:** Sets the border color and background color of the text box.
|
||||
- `void setText(std::string text);`
|
||||
**Description:** Updates the displayed text content of the text box. If the new text exceeds `maxCharLen`, it is automatically truncated to that length. Marks the text box dirty and immediately calls `draw()` to update the displayed text.
|
||||
- `std::string getText() const;`
|
||||
**Description:** Returns the current text content of the text box.
|
||||
- `void draw() override;`
|
||||
**Description:** Draws the text box background and the text:
|
||||
- Saves style and sets `fillcolor` to `textBoxBkColor`, `linecolor` to `textBoxBorderColor`.
|
||||
- Ensures the font height/width do not exceed the control's dimensions (if `textStyle.nHeight` is larger than `height`, it sets it equal to height; similarly for width).
|
||||
- Sets the font and text color via `settextstyle` and `settextcolor` according to `textStyle`.
|
||||
- Transparent background mode (`setbkmode(TRANSPARENT)` is used to avoid drawing a separate background behind text, since the background is already filled by fillrectangle).
|
||||
- Calculates the pixel width and height of the text (`textwidth` and `textheight`).
|
||||
- Draws the background shape:
|
||||
- If shape is RECTANGLE (with border): calls `fillrectangle` (the border is then drawn by outline because linecolor is set).
|
||||
- If shape is B_RECTANGLE: calls `solidrectangle` (no border outline).
|
||||
- If shape is ROUND_RECTANGLE: calls `fillroundrect` using `rouRectangleSize` for corners.
|
||||
- If shape is B_ROUND_RECTANGLE: calls `solidroundrect`.
|
||||
- (Other shapes default to rectangle).
|
||||
- Draws the text: It positions the text 10 pixels from the left inside the box (`x + 10`) and vertically centers it (`y + (height - text_h) / 2`).
|
||||
- Restores the style and sets dirty false.
|
||||
- **Note:** If text content is larger than the control width, it will overflow/clipped; there's no automatic horizontal scrolling or ellipsis for text box implemented. The developer should ensure the width is sufficient or the `maxCharLen` is set appropriately.
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**Description:** Handles the mouse events for the text box:
|
||||
- Determines if the mouse is over the text box (`hover`) using the shape logic (similar to button, but generally rectangular).
|
||||
- For `WM_LBUTTONUP` when the cursor is over the text box:
|
||||
- If in `INPUT_MODE`: It sets `click = true` and calls `InputBox` (EasyX's modal input dialog). The `InputBox` parameters include:
|
||||
- The output buffer (here passing `LPTSTR(text.c_str())` to supply initial text).
|
||||
- Max characters (`maxCharLen`).
|
||||
- A title like "输入框" (Input Box).
|
||||
- It passes `text.c_str()` as default content, etc.
|
||||
- The returned value (the function returns nonzero if text changed, 0 if canceled, but here they capture it as `dirty`).
|
||||
- After `InputBox` returns, if any input was done, the `text` variable will contain the new text (because the first parameter was the buffer referencing `text` content).
|
||||
- They mark `dirty` based on InputBox result (the code sets `dirty` to whatever InputBox returned, which in EasyX indicates whether text was changed).
|
||||
- They mark `consume = true` to indicate the click was handled.
|
||||
- If in `READONLY_MODE`: They do not change the text. Instead, they possibly call `InputBox(NULL, maxCharLen, "输出框(输入无效!)", ... text ...)` which essentially shows the text in an output box stating input is invalid (effectively a message box).
|
||||
- They keep `dirty = false` (since no visual change), set `consume = true`.
|
||||
- In both cases, after handling, they flush the input messages with `flushmessage(EX_MOUSE | EX_KEY)` to clear any leftover events (similar reason to button: to avoid re-triggering).
|
||||
- For other messages (like `WM_LBUTTONDOWN` or `WM_MOUSEMOVE`):
|
||||
- `WM_LBUTTONDOWN` is not explicitly handled (the code simply sets `hover` and sets `consume = false` in the switch).
|
||||
- `WM_MOUSEMOVE`: The code sets `hover` accordingly (and uses `consume = false`).
|
||||
- After event processing:
|
||||
- If `dirty` is true (text changed), it calls `draw()` to refresh the display with new text.
|
||||
- If `click` was set (meaning an input happened), it resets `click = false` after handling to ensure state is consistent.
|
||||
- Returns `consume` (which would be true if a click caused an InputBox or a read-only alert, false otherwise).
|
||||
- Essentially, clicking the text box in input mode opens an input dialog for user to type, and clicking it in read-only mode just shows a dummy output box (so user knows it's not editable).
|
||||
|
||||
- **Usage Considerations:**
|
||||
|
||||
- Because `InputBox` is modal, it will pause the entire program's event loop until the user closes the input dialog. This simplifies input handling but means the UI doesn't update during text entry (the `runEventLoop` is blocked). For small apps and short inputs, this is acceptable, but for more advanced usage you might want to implement a custom non-blocking input field.
|
||||
|
||||
- Always set an appropriate `maxCharLen` if expecting certain input sizes (like numeric values of certain length) to avoid overflow of the display region.
|
||||
|
||||
- Use `textStyle` to set a suitable font for the input. For instance, for numeric input, you might choose a monospace font for alignment.
|
||||
|
||||
- Example:
|
||||
|
||||
```
|
||||
auto field = std::make_unique<TextBox>(100, 100, 200, 30, "0");
|
||||
field->setMaxCharLen(10);
|
||||
field->textStyle.color = RGB(255,69,0); // orange text
|
||||
field->textStyle.nHeight = 18;
|
||||
// field is input mode by default. Optionally:
|
||||
// field->setMode(StellarX::TextBoxmode::READONLY_MODE);
|
||||
```
|
||||
|
||||
- After user input (in input mode), you can retrieve the text via `field->getText()`. In read-only mode, you may set the text programmatically via `setText` and it will just display.
|
||||
|
||||
### Dialog Class (Dialog Control)
|
||||
|
||||
**Description:** `Dialog` implements modal and modeless dialog windows with rich content and multiple buttons (like message boxes). It inherits from `Canvas` (so it is a container), and includes a caption, message text, and standard buttons combination. It integrates with `Window` to manage blocking input for modal dialogs.
|
||||
|
||||
- **Creation & Modes:**
|
||||
|
||||
- Constructor `Dialog(Window& parent, std::string title, std::string message = "对话框", MessageBoxType type = OK, bool modal = true)`
|
||||
**Description:** Creates a dialog associated with the `parent` window. `title` is the dialog title text (often shown at top of dialog), `message` is the body text content. `type` specifies which buttons are included (see `MessageBoxType` enum for combinations, e.g., OK, YesNo, etc.). `modal` determines if this is a modal dialog (true) or modeless (false). The dialog is initially not visible (you must call `show()` to display it).
|
||||
- `MessageBoxType` enumeration defines common button sets:
|
||||
- `OK` – only an "OK" button.
|
||||
- `OKCancel` – "OK" and "Cancel" buttons.
|
||||
- `YesNo` – "Yes" and "No" buttons.
|
||||
- `YesNoCancel` – "Yes", "No", and "Cancel".
|
||||
- `RetryCancel` – "Retry" and "Cancel".
|
||||
- `AbortRetryIgnore` – "Abort", "Retry", "Ignore".
|
||||
- Modal dialogs (`modal == true`): When `show()` is called, it will block the current thread until the dialog is closed (the internal `show()` implementation runs its own loop). This is useful for confirmations or critical inputs that need immediate resolution. After `show()` returns, you can get the result via `getResult()`.
|
||||
- Modeless dialogs (`modal == false`): When `show()` is called, it returns immediately (the dialog remains open, but the program flow continues and the main event loop will handle the dialog’s interactions concurrently). To get the result, you typically provide a callback or check `getResult()` after the dialog closes (e.g., via a callback or a flag).
|
||||
|
||||
- **Component Elements of Dialog:**
|
||||
|
||||
- **Title label:** A `Label` (stored in `title`) showing the dialog title at top.
|
||||
- **Message text:** The main content text, which can be multi-line. It's internally split into `lines` and drawn in the dialog client area.
|
||||
- **Buttons:** One to three `Button` controls at the bottom of the dialog, depending on `MessageBoxType`. They are usually centered or evenly spaced.
|
||||
- **Close (X) button:** A small `Button` in the top-right corner to close the dialog (especially for modeless dialogs, acts like a Cancel).
|
||||
- **Background style:** Typically a rounded rectangle background with a slight border. There is no actual transparency or blur (the background might just capture what's behind for restoration when closed).
|
||||
|
||||
- **Key Properties:**
|
||||
|
||||
- `bool modal;` – Whether the dialog is modal.
|
||||
- `titleText` – The dialog's title text string.
|
||||
- `message` – The dialog's message content string.
|
||||
- `MessageBoxType type;` – The type of dialog (determines buttons).
|
||||
- `std::vector<std::string> lines;` – The message content split into lines (populated internally).
|
||||
- `std::unique_ptr<Label> title;` – Label control for title.
|
||||
- `Button* closeButton;` – Pointer to the "X" close button.
|
||||
- `Window& hWnd;` – Reference to the parent window (for sending refresh signals, etc.).
|
||||
- `StellarX::MessageBoxResult result;` – The result of the dialog (which button was clicked by the user). Common values: OK=1, Cancel=2, Yes=6, No=7, etc. (Matches typical message box results).
|
||||
- `std::function<void(MessageBoxResult)> resultCallback;` – If set (for modeless use), a callback function to be called when the dialog is closed with the final result.
|
||||
- (Other internal flags track if initialization is needed, if a cleanup is pending, etc.)
|
||||
|
||||
- **Main Methods:**
|
||||
|
||||
- `void show();`
|
||||
**Description:** Displays the dialog.
|
||||
- If `modal == true`, this function enters a loop that blocks the rest of the application until the dialog is closed. During this period, it intercepts events in its own loop (preventing main window events).
|
||||
- If `modal == false`, this function simply makes the dialog visible and returns immediately; the dialog will be handled by the main window's event loop.
|
||||
- Note: The implementation sets `dirty` and possibly runs its own flush for modals, and uses `shouldClose` to break out of the modal loop if needed.
|
||||
- `void closeDialog();`
|
||||
**Description:** Closes the dialog.
|
||||
- It hides the dialog (`show = false`), marks it for cleanup, and signals the parent window to refresh other controls (by marking them dirty).
|
||||
- If a `resultCallback` is set and the dialog is modeless, it invokes the callback with the result.
|
||||
- This is typically called internally when a button is clicked (like Cancel or the X button).
|
||||
- `StellarX::MessageBoxResult getResult() const;`
|
||||
**Description:** Returns the `MessageBoxResult` code indicating which button the user pressed. For modal dialogs, you call this after `show()` returns to get the user's choice. For modeless, you'll typically get the result from the callback rather than polling this function.
|
||||
- `void setTitle(const std::string& title);` / `void setMessage(const std::string& message);`
|
||||
**Description:** Changes the dialog's title or message text. Changing the message triggers re-splitting into lines and recalculating sizes (the code marks dirty and reinitializes layout).
|
||||
- `void setType(StellarX::MessageBoxType newType);`
|
||||
**Description:** Changes the dialog's button configuration. This will recreate the buttons layout (calls `initButtons()` internally) and mark the dialog dirty.
|
||||
- `void setModal(bool modal);`
|
||||
**Description:** Sets the modal property (should be done before showing). Typically you decide at creation whether a dialog is modal or not; toggling at runtime is rare.
|
||||
- `void setResult(StellarX::MessageBoxResult res);`
|
||||
**Description:** Sets the dialog's result code. This is called internally when a button is clicked (for example, clicking "Yes" calls `setResult(MessageBoxResult::Yes)`).
|
||||
- `void setInitialization(bool init);`
|
||||
**Description:** If `init` is true, it performs an initialization of dialog size and captures the background. This method is used before showing the dialog (particularly by `MessageBox::showModal` and `showAsync`) to prepare the dialog geometry off-screen. Essentially, it calls `initDialogSize()` and saves the background behind where the dialog will appear (so that it can restore it on close).
|
||||
- `void setResultCallback(std::function<void(MessageBoxResult)> cb);`
|
||||
**Description:** Sets a callback function to be called with the dialog result when a modeless dialog is closed. Use this for asynchronous dialog handling.
|
||||
- *Private internal methods (for implementation):*
|
||||
- `initButtons()` – Creates the appropriate `Button` objects for the dialog's `type`, positions them, and sets their onClick listeners to call `setResult` and `closeDialog`. For example, in a YesNo dialog, it creates "Yes" and "No" buttons and sets their listeners to `SetResult(Yes)` / `...No` and `closeDialog()` accordingly.
|
||||
- `initCloseButton()` – Creates the "X" close button at top-right of dialog (small size). Its onClick calls `SetResult(Cancel)` (or similarly appropriate default) and triggers `hWnd.dialogClose = true` and `closeDialog()`.
|
||||
- `initTitle()` – Creates the title Label at the top inside the dialog.
|
||||
- `splitMessageLines()` – Splits the `message` string by newline characters into the `lines` vector for drawing.
|
||||
- `getTextSize()` – Calculates the maximum text width and single line height among the `lines`. It does this by setting the text style and measuring each line (`textwidth` & `textheight` calls). It stores results in `textWidth` and `textHeight`.
|
||||
- `initDialogSize()` – Computes the dialog's required width and height based on:
|
||||
- The greater of text area width (textWidth + margins) and button area width (depending on number of buttons).
|
||||
- For height: sums title bar height, spacing to text, total text block height (line count * textHeight + line spacing), and button area height.
|
||||
- Enforces a minimum width (in code, 200 px).
|
||||
- Centers the dialog on the parent window by setting `x,y` such that the dialog is at window center.
|
||||
- Also sets a default text style height (e.g., 20 px).
|
||||
- Then calls `initButtons()`, `initTitle()`, and `initCloseButton()` to create all child controls.
|
||||
- `performDelayedCleanup()` – Handles the delayed destruction of the dialog's content after it’s closed. It:
|
||||
- Marks `isCleaning = true` to avoid reentry.
|
||||
- Clears all child controls (`controls.clear()`).
|
||||
- Resets pointers (closeButton, title).
|
||||
- If a background image was saved and still present (`hasSnap` is true), it restores the screen background (`restBackground()`) and discards the snapshot (`discardBackground()`) to remove any ghost image of dialog.
|
||||
- Resets flags (needsInitialization, pendingCleanup, isCleaning, shouldClose).
|
||||
- This is called by the event loop after the dialog is hidden, to safely remove the dialog from UI and restore background.
|
||||
- `void draw() override;`
|
||||
**Description:** Draws the dialog. It combines container drawing with additional text drawing:
|
||||
- If dialog is not visible (`show == false`), it does nothing (except if `pendingCleanup` and not cleaning, it calls `performDelayedCleanup()`, but that's in handleEvent rather than draw).
|
||||
- If `needsInitialization` is true and dialog is now visible, it calls `initDialogSize()` to compute layout and then sets `needsInitialization = false`. This ensures the dialog's size and children are all properly set up on first draw.
|
||||
- It then checks `dirty` and `show`, and if both are true:
|
||||
- Saves drawing style.
|
||||
- If it's the first draw and `saveBkImage` is null, it calls `saveBackground(...)` to capture what's behind the dialog.
|
||||
- Sets the border color, border width, background color, and shape on the base Canvas part (calls `Canvas::setBorderColor`, etc.) to round rectangle and appropriate style.
|
||||
- Marks all child controls dirty (so they will be redrawn).
|
||||
- Calls `Canvas::draw()` to draw the rounded rectangle background and all the child controls (like buttons, title).
|
||||
- Then sets text color and style (for the message text) and calculates a starting y coordinate `ty` just below the title area (using `closeButtonHeight` and a margin).
|
||||
- For each line in `lines`, it computes an x coordinate `tx` to center that line in the dialog (x + (width - textwidth(line))/2), and then outputs the text with `outtextxy(tx, ty, line)`.
|
||||
- Moves `ty` down by textheight + 5 for the next line (5 px line spacing).
|
||||
- Restores style, sets `dirty = false`.
|
||||
- The base Canvas::draw already drew the background and the title label and buttons. So these text drawing steps are to overlay the message text content (which is not a separate control, but drawn directly).
|
||||
- `bool handleEvent(const ExMessage& msg) override;`
|
||||
**Description:** Handles events for the dialog:
|
||||
- If the dialog is not visible (`!show`):
|
||||
- If there's a `pendingCleanup` and not already cleaning, it calls `performDelayedCleanup()`.
|
||||
- Returns false (no event consumed).
|
||||
- If a cleanup is pending or in progress (`pendingCleanup || isCleaning`), it immediately returns false (dialog isn't interactive at this moment).
|
||||
- If modal:
|
||||
- If the user clicked outside the dialog (LButtonUp at a point outside `[x, y, x+width, y+height]`):
|
||||
- It prints a bell (`std::cout << "\a"`) to alert (like system beep).
|
||||
- Returns true (consumes the event) to prevent underlying controls from receiving the click.
|
||||
- Then it attempts to dispatch the event to dialog's child controls:
|
||||
- `consume = Canvas::handleEvent(msg)` calls the base container logic to let any button inside the dialog handle the event (e.g., if user clicked "OK", the button's handleEvent will consume it).
|
||||
- If a child consumed it, `consume` becomes true.
|
||||
- After dispatching, if `pendingCleanup && !isCleaning`, it calls `performDelayedCleanup()` (maybe if a button triggered immediate closure).
|
||||
- Returns `consume` (so if a dialog button or modal logic handled the event, it returns true, otherwise false).
|
||||
- The effect is that normal controls behind won't receive events while a modal dialog is open (due to the modal check swallowing outside clicks and because the Window event loop prioritizes dialogs).
|
||||
- For modeless dialogs, events outside are not swallowed by Dialog (so user can interact with background controls as well, which is typical for modeless).
|
||||
|
||||
- **Usage Examples:**
|
||||
It's more common to use `MessageBox` static methods to show dialogs (documented next), but you can use `Dialog` directly:
|
||||
|
||||
- **Modal example:**
|
||||
|
||||
```
|
||||
Dialog confirm(window, "Confirm Delete", "Are you sure you want to delete?", StellarX::MessageBoxType::YesNo, true);
|
||||
confirm.show();
|
||||
if (confirm.getResult() == StellarX::MessageBoxResult::Yes) {
|
||||
// proceed with delete
|
||||
}
|
||||
```
|
||||
|
||||
- **Modeless example:**
|
||||
|
||||
```
|
||||
Dialog *notify = new Dialog(window, "Notice", "Background task started.", StellarX::MessageBoxType::OK, false);
|
||||
notify->setResultCallback([](StellarX::MessageBoxResult res) {
|
||||
// maybe log that user acknowledged
|
||||
});
|
||||
notify->show();
|
||||
```
|
||||
|
||||
Here we allocate on heap (since for modeless we need it to persist beyond function scope), set a callback to handle result asynchronously, and show it. The main loop will handle its events. The callback will be invoked on close (OK click).
|
||||
|
||||
- Typically, prefer using `MessageBox::showModal` and `showAsync` which handle these patterns for you, including cleanup of the `Dialog` object when done.
|
||||
|
||||
### MessageBox Utility Class
|
||||
|
||||
**Description:** `StellarX::MessageBox` provides convenient static methods to display standard dialogs (message boxes) without manually managing `Dialog` objects. It mimics the idea of a system message box where you specify a message and get a user response.
|
||||
|
||||
- **Static Functions:**
|
||||
|
||||
- `static MessageBoxResult showModal(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK);`
|
||||
**Description:** Displays a modal message box on window `wnd` with given content. `caption` is the title of the dialog (e.g., "Error", "Confirmation"), `text` is the message to display, and `type` is the combination of buttons to include. This function blocks until the user closes the dialog. It returns a `MessageBoxResult` indicating which button the user clicked (e.g., `MessageBoxResult::OK` or `Cancel`, etc.). The caller can then branch logic based on this result.
|
||||
- `static void showAsync(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK, std::function<void(MessageBoxResult)> onResult = nullptr);`
|
||||
**Description:** Displays a modeless (asynchronous) message box. It returns immediately, leaving the dialog on screen. If `onResult` callback is provided, it will be called once the user closes the dialog, with the `MessageBoxResult` of what they clicked. If a dialog with the same caption **and text** is already open on `wnd`, this function will emit a beep (`\a`) and not open another (to prevent duplicates).
|
||||
- This duplicate check uses `wnd.hasNonModalDialogWithCaption(caption, text)` internally.
|
||||
- If no callback is provided, the function simply opens the dialog and returns; you'll have to find another way to know when it's closed (perhaps by checking the `Dialog` via some global or so, but ideally you provide a callback).
|
||||
|
||||
- **Implementation Details:**
|
||||
|
||||
- `showModal`:
|
||||
- Creates a `Dialog` (with modal = true) on the stack.
|
||||
- Calls `dlg.setInitialization(true)` to prepare its layout and capture background before showing (to minimize flicker).
|
||||
- Calls `dlg.show()`, which will block until the dialog is closed.
|
||||
- When `show()` returns, it retrieves the result with `dlg.getResult()` and returns that.
|
||||
- All memory is cleaned up on function exit since `dlg` was on stack.
|
||||
- `showAsync`:
|
||||
- First checks for duplicates; if found, prints a bell and returns without doing anything.
|
||||
- Otherwise, creates a `Dialog` on the heap (with modal = false) using a unique_ptr. It then:
|
||||
- Calls `dlgPtr->setInitialization(true)` to layout and capture background.
|
||||
- If an `onResult` callback is provided, calls `dlgPtr->setResultCallback(std::move(onResult))` to register it.
|
||||
- Uses `wnd.addDialog(std::move(dlg))` to add the dialog to the window's management list (transferring ownership to the window).
|
||||
- Calls `dlgPtr->show()` to display the dialog modelessly.
|
||||
- Then returns immediately. The dialog will operate asynchronously; when closed, it triggers `resultCallback` if set.
|
||||
|
||||
- **Typical Uses:**
|
||||
|
||||
- To display an information message and wait for user acknowledgment:
|
||||
|
||||
```
|
||||
StellarX::MessageBox::showModal(mainWindow, "Operation completed successfully.", "Info", MessageBoxType::OK);
|
||||
```
|
||||
|
||||
This will show an OK dialog and block until user presses OK.
|
||||
|
||||
- To ask a yes/no question:
|
||||
|
||||
```
|
||||
auto res = StellarX::MessageBox::showModal(mainWindow, "Delete this file?", "Confirm", MessageBoxType::YesNo);
|
||||
if (res == MessageBoxResult::Yes) {
|
||||
// delete the file
|
||||
}
|
||||
```
|
||||
|
||||
- To show a non-blocking notification:
|
||||
|
||||
```
|
||||
StellarX::MessageBox::showAsync(mainWindow, "The update is downloading in the background.", "Update", MessageBoxType::OK);
|
||||
// user can continue using app; the dialog will close when they press OK.
|
||||
```
|
||||
|
||||
- To ask something in the background and get result via callback:
|
||||
|
||||
```
|
||||
StellarX::MessageBox::showAsync(mainWindow, "New version available. Download now?", "Update Available", MessageBoxType::YesNo,
|
||||
[](StellarX::MessageBoxResult res) {
|
||||
if (res == StellarX::MessageBoxResult::Yes) {
|
||||
startDownload();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Here, the lambda will execute once the user clicks Yes or No (Yes triggers `startDownload()` in this example).
|
||||
|
||||
Using these static methods is recommended because they handle the creation and cleanup of `Dialog` internally, and provide a simple synchronous interface for modal dialogs and an event-driven interface for modeless dialogs.
|
||||
|
||||
## Table Class
|
||||
|
||||
### Class Overview and Purpose
|
||||
|
||||
The **Table** class is an advanced table UI control that supports pagination and displaying large datasets[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L2-L10). It provides a comprehensive data grid functionality, including column headers, data rows, and pagination navigation, similar to a spreadsheet. The Table control automatically computes appropriate column widths and row heights, and it allows customization of border style, fill mode, and text font style to fit various UI design requirements. With its built-in paging mechanism, Table can efficiently handle and present large amounts of data without overwhelming the interface, utilizing background buffering to optimize rendering and prevent flicker during redraws.
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- **Pagination Support:** Automatically handles pagination by computing total pages based on data size and configurable rows-per-page, and provides **Previous/Next** navigation buttons with a page indicator[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.md#L274-L282). This enables users to browse through data that spans multiple pages easily.
|
||||
- **Customization:** Developers can configure the number of rows per page, toggle the visibility of page navigation buttons, and adjust visual aspects such as border color, background color, fill mode (solid fill or transparent), and line style to match the application's theme.
|
||||
- **Efficient Rendering:** The Table control uses double-buffering techniques by capturing the background under the table and only redrawing changed areas[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L210-L219)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L241-L249). This minimizes redraw artifacts and improves performance when updating the table content or appearance.
|
||||
- **Typical Use Cases:** Ideal for displaying tabular data like lists, reports, and records in a GUI application[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/table.h#L14-L17). For example, it can be used to show database query results, log entries, or statistical reports in a scrollable, paginated view, allowing users to easily read and navigate the information.
|
||||
|
||||
### Public Member Functions
|
||||
|
||||
#### Table(int x, int y)
|
||||
|
||||
**Prototype:** `Table(int x, int y)`
|
||||
**Parameters:**
|
||||
|
||||
- `x` – The X coordinate for the table’s top-left corner position.
|
||||
- `y` – The Y coordinate for the table’s top-left corner position.
|
||||
**Return Value:** None (constructor).
|
||||
**Description:** Constructs a new Table control at the specified position on the screen[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L164-L171). Initially, the table has no headers or data; its width and height are set to 0 and will be calculated once data is provided and headers are set. The `x` and `y` coordinates position the table within the parent window or container. Internally, Table inherits from the base `Control` class, so it is created with an initial area that will expand based on content.
|
||||
**Usage Scenario:** Use this constructor when you need to create a table in your application’s UI. For example, after creating a main window, you might call `auto table = std::make_unique<Table>(50, 50);` to place a Table at coordinates (50,50). You would then set up its headers and data before rendering it. The Table can be added to a Window or Canvas as a sub-control for display.
|
||||
|
||||
#### ~Table()
|
||||
|
||||
**Prototype:** `~Table()`
|
||||
**Parameters:** None.
|
||||
**Return Value:** None.
|
||||
**Description:** Destructor for the Table class. It is called automatically when a Table object is destroyed, and it ensures that all internally allocated resources are freed. This includes deleting any internal controls such as the pagination buttons, page number label, and the background image buffer that were created during the Table’s operation[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L169-L178). By cleaning up these resources, the destructor prevents memory leaks.
|
||||
**Usage Scenario:** Generally, you do not need to call this explicitly; it will be invoked when the Table goes out of scope or the program terminates. Understanding the destructor’s behavior is useful for developers to ensure that if a Table is dynamically allocated (e.g., via `new` or `make_unique`), it should be deleted or allowed to go out of scope to trigger cleanup of internal components.
|
||||
|
||||
#### draw()
|
||||
|
||||
**Prototype:** `void draw() override`
|
||||
**Parameters:** None.
|
||||
**Return Value:** None.
|
||||
**Description:** Overrides the `Control::draw()` method to render the table onto the screen[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L185-L194)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L252-L260). When `draw()` is called, the Table will draw its column headers, the data rows for the current page, the page number label, and the navigation buttons (if they are enabled). The drawing routine uses the current text style, border color, background color, fill mode, and line style settings that have been configured. The Table employs a “dirty flag” mechanism to optimize rendering: it will only redraw its content if something has changed (such as data or style) that marks it as needing an update[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L187-L196)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L258-L266). If `dirty` is false, calling `draw()` has no effect. Internally, the function also takes care of restoring any saved background and drawing with minimal flicker.
|
||||
**Usage Scenario:** In most cases, you do not need to call `table.draw()` manually, as the framework’s window or event loop will handle drawing. Typically, after adding the Table to a Window and calling the window’s `draw()`, the Table will be drawn. However, if you update the Table’s content or appearance at runtime (for example, in response to an event) and want to refresh immediately, you may call `table.draw()` to force a redraw. This will ensure the table’s display is updated with any new data or style changes.
|
||||
|
||||
#### handleEvent(const ExMessage& msg)
|
||||
|
||||
**Prototype:** `bool handleEvent(const ExMessage& msg) override`
|
||||
**Parameters:**
|
||||
|
||||
- `msg` – An event message (`ExMessage` from EasyX library) containing details of a user input event (mouse click, key press, etc.).
|
||||
**Return Value:** Boolean, indicating whether the event was handled by the Table. Returns `true` if the event was consumed (handled) by this Table control, or `false` if the Table did not handle the event.
|
||||
**Description:** Overrides the `Control::handleEvent()` method to process user input events relevant to the Table. For the Table control, `handleEvent` primarily deals with events on the pagination buttons. If page navigation buttons are enabled (`isShowPageButton` is true), this function will forward the incoming event to the internal "Previous Page" and "Next Page" Button controls for handling[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L274-L283). For example, if the user clicks one of these buttons, the respective internal Button will capture the event, update the Table’s `currentPage` (decrement or increment it), and trigger a redraw of the table to show the new page. In such a case, `handleEvent` returns `true` to indicate the event was handled (and no further processing is needed). If the page buttons are hidden (`isShowPageButton == false`), the Table will ignore all events and always return `false`[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L276-L283), as there is no interactive element for the user to manipulate directly in the Table.
|
||||
**Usage Scenario:** In normal usage, you do not call this method directly. The framework’s main loop will call `handleEvent` on each control (including Table) to propagate user inputs. However, understanding this function is useful if you plan to extend the Table or integrate it into a custom event handling flow. For instance, when integrating Table into a Window, the Window’s `runEventLoop()` will pass events to `table.handleEvent(msg)` automatically. If you have disabled the default page buttons, you might implement a custom paging control elsewhere; in that case, Table’s `handleEvent` would not consume events, and you could handle them in your custom controls.
|
||||
|
||||
#### setHeaders(std::initializer_liststd::string headers)
|
||||
|
||||
**Prototype:** `void setHeaders(std::initializer_list<std::string> headers)`
|
||||
**Parameters:**
|
||||
|
||||
- `headers` – An initializer list of strings, each representing a column header title.
|
||||
**Return Value:** None.
|
||||
**Description:** Defines the column headers of the table. This function clears any existing headers and sets up new headers as provided by the list[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L288-L296). Each string in the list becomes the title of a column in the table header. After calling this, the Table marks itself as needing to recalculate cell dimensions and to redraw the headers[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L291-L295) on the next draw. The number of headers determines the number of columns in the table; any subsequent data rows added should match this column count.
|
||||
**Usage Scenario:** Call this after creating a Table and before adding data. For example: `table.setHeaders({"Name", "Age", "Occupation"});` will configure the table to have three columns titled "Name", "Age", and "Occupation". Defining headers is typically the first step in preparing the Table for use. Once headers are set, you can proceed to populate data rows with `setData`. Changing headers after data has been added is possible but not recommended, as the existing data might not align with the new columns (if the counts differ); if you must do so, you should reset or adjust the data accordingly.
|
||||
|
||||
#### setData(std::vectorstd::string data) / setData(std::initializer_list<std::vectorstd::string> data)
|
||||
|
||||
**Prototype 1:** `void setData(std::vector<std::string> data)`
|
||||
**Parameters:**
|
||||
|
||||
- `data` – A vector of strings representing a single row of data, where each element corresponds to a cell under the respective column.
|
||||
**Return Value:** None.
|
||||
**Description:** Adds one row of data to the table. If the number of elements in the provided vector is less than the number of headers (columns), the function will append empty strings to the vector until it matches the header count[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L300-L308). This ensures that every row has a value (or placeholder) for each column. The row is then appended to the Table’s internal data storage, and the total page count (`totalPages`) is recalculated based on the new total number of rows and the current `rowsPerPage` setting[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L302-L308). The calculation rounds up to ensure at least one page[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L303-L308). After adding the row, the Table is marked as needing a layout recompute and redraw.
|
||||
**Usage Scenario:** Use this function to build the table row by row. For example, in a loop reading records from a file or database, you might call `table.setData({val1, val2, val3});` for each record to append it as a new row. This is especially useful for dynamically updating the table with new entries (e.g., real-time log updates). Each call adds the row at the end of the data and the Table’s display will include it on the appropriate page (immediately if it falls on the current page and you redraw, or after navigation).
|
||||
|
||||
**Prototype 2:** `void setData(std::initializer_list<std::vector<std::string>> data)`
|
||||
**Parameters:**
|
||||
|
||||
- `data` – An initializer list of rows, where each row is represented by a `std::vector<std::string>` (as described above).
|
||||
**Return Value:** None.
|
||||
**Description:** Adds multiple rows of data to the table in one call. For each row provided in the initializer list, the function checks if its number of elements matches the number of table headers; if a row is shorter, empty strings will be appended until its length equals the header count[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L313-L321). Each row (adjusted as needed) is then appended to the table’s data. After inserting all the rows, the total page count is recalculated accordingly, ensuring it is at least 1[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L322-L328). Like the single-row version, this overload marks the table as needing re-layout and redraw. Note that this function does **not** clear existing data; new rows are appended to any data already present.
|
||||
**Usage Scenario:** This is convenient for initializing the table with a batch of data. For example, if you already have a collection of rows ready (perhaps from a data structure or file), you can call `table.setData({row1, row2, row3});` to add them all at once instead of calling the single-row version repeatedly. It simplifies populating the table with initial data. If you intend to replace all existing data, you may want to clear the old data (by resetting the Table or using the returned data vector) before calling this overload. Use this when you have a static set of data to display, especially during initialization or when loading a new dataset into the table.
|
||||
|
||||
#### setRowsPerPage(int rows)
|
||||
|
||||
**Prototype:** `void setRowsPerPage(int rows)`
|
||||
**Parameters:**
|
||||
|
||||
- `rows` – The number of data rows to display per page.
|
||||
**Return Value:** None.
|
||||
**Description:** Sets the pagination size of the table by specifying how many rows are shown on each page. This function updates the internal `rowsPerPage` setting and recalculates the total number of pages (`totalPages`) based on the current total number of data rows[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L332-L338). If the recalculation results in less than 1 page, it defaults to 1 to maintain a valid state. Changing this value will affect the layout (particularly the vertical size of the table and the distribution of data across pages), so the Table marks itself for resizing of cells and redraw. The current page (`currentPage`) is not explicitly adjusted in this function, so if the new `rowsPerPage` significantly reduces the total pages, the table may internally handle out-of-range page indices on the next draw by capping the current page to the last page.
|
||||
**Usage Scenario:** Use this to adjust how dense the table appears in terms of data per page. For instance, if a table initially shows 5 rows per page by default but you want to show more data on a larger screen, calling `table.setRowsPerPage(10);` will double the number of rows shown at once, reducing the total page count roughly by half. This is useful for user preferences (show more or fewer items per page) or responsive design considerations. It can be called before or after adding data. If called after data is loaded and the current page is beyond the new total pages, the next redraw will naturally show the last page of data.
|
||||
|
||||
#### showPageButton(bool isShow)
|
||||
|
||||
**Prototype:** `void showPageButton(bool isShow)`
|
||||
**Parameters:**
|
||||
|
||||
- `isShow` – A boolean flag indicating whether the page navigation buttons (Previous/Next) should be displayed. `true` to show the buttons; `false` to hide them.
|
||||
**Return Value:** None.
|
||||
**Description:** Toggles the visibility of the pagination control buttons of the table. When set to `false`, the “Previous Page” and “Next Page” buttons will not be drawn, and the user will not have a UI control to navigate between pages (the table will effectively remain on whatever the current page is, unless changed programmatically). When set to `true`, the buttons are visible, allowing the user to click them to change pages. Hiding the buttons does not alter the data or current page internally; it purely affects the UI and event handling (when hidden, `handleEvent` will ignore navigation clicks as described above). Any call to this function will mark the table as dirty so that on the next draw the buttons will be shown/hidden accordingly.
|
||||
**Usage Scenario:** Useful when the dataset fits on one page or when you want to control pagination through a different means. For example, if your table only has a few rows that all fit on a single page, you might call `table.showPageButton(false);` to simplify the interface by removing unnecessary controls. Conversely, if you later load more data that requires paging, you can call `showPageButton(true)` to reveal the navigation. It can also be toggled in response to user actions (like a "show all data" toggle that disables manual paging).
|
||||
|
||||
#### setTableBorder(COLORREF color)
|
||||
|
||||
**Prototype:** `void setTableBorder(COLORREF color)`
|
||||
**Parameters:**
|
||||
|
||||
- `color` – A COLORREF value specifying the new color for the table’s border lines (grid lines). This is typically created by the `RGB(r,g,b)` macro on Windows (e.g., `RGB(0,0,0)` for black).
|
||||
**Return Value:** None.
|
||||
**Description:** Sets the color used to draw the table’s cell borders and outline. After calling this, the Table updates its internal border color and marks itself for redraw. The next time the table is drawn, all grid lines and borders will be rendered in the specified color[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L192-L199). By default, the table’s border color is black. Changing the border color can be useful to match the overall UI theme or to highlight the table. This affects the lines around each cell and the table perimeter.
|
||||
**Usage Scenario:** Use this to customize the appearance of the table’s grid. For example, in a dark-themed application you might use a lighter border color for contrast, or in a light theme, a subtle gray to avoid a heavy look: `table.setTableBorder(RGB(200, 200, 200));`. This is purely a cosmetic setting and can be changed anytime (even at runtime) to alter the table’s look. Remember to refresh the display (via `draw()` or window redraw) to see the effect immediately.
|
||||
|
||||
#### setTableBk(COLORREF color)
|
||||
|
||||
**Prototype:** `void setTableBk(COLORREF color)`
|
||||
**Parameters:**
|
||||
|
||||
- `color` – A COLORREF specifying the background fill color for the table’s cells.
|
||||
**Return Value:** None.
|
||||
**Description:** Sets the background color used to fill each cell of the table. This updates the internal background color property and marks the Table as needing redraw. During the next `draw()`, each cell’s background will be filled with the given color (provided the fill mode is solid). By default, table cell backgrounds are white. Changing this can provide alternating row colors or highlight the table area against the window background. If the fill mode is set to Null (transparent), this color will not be visible since cells won’t be filled with a solid color.
|
||||
**Usage Scenario:** To change the background color of the table for styling or readability. For instance, `table.setTableBk(RGB(240, 240, 240));` would set a light gray background for all cells, which can reduce glare compared to bright white. This can be paired with custom text colors for better contrast. If you want to implement alternating row colors (like “zebra striping”), the current Table API does not support per-row color via this function directly – you would need to draw custom backgrounds or modify the data drawing logic.
|
||||
|
||||
#### setTableFillMode(StellarX::FillMode mode)
|
||||
|
||||
**Prototype:** `void setTableFillMode(StellarX::FillMode mode)`
|
||||
**Parameters:**
|
||||
|
||||
- `mode` – A `StellarX::FillMode` value indicating the fill pattern mode for the cell backgrounds. Common values are `FillMode::Solid` (solid color fill) or `FillMode::Null` (no fill/transparent background). Other modes like `Hatched`, `Pattern`, etc., are also defined but may fall back to Solid if not supported.
|
||||
**Return Value:** None.
|
||||
**Description:** Configures how the table’s cell background is filled when drawn. In practice, this toggles between solid fill and no fill for the Table (the current implementation treats any mode other than Solid or Null as Solid)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L360-L369). Setting `FillMode::Solid` means cells will be drawn with the current background color (`tableBk`), whereas `FillMode::Null` means cell backgrounds will not be drawn (making them transparent, showing whatever was behind the table). This function also propagates the fill mode and text style to the internal navigation buttons and page number label to ensure visual consistency[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L365-L373). For example, if you choose Null mode (transparent), the page number label’s background will be made transparent as well. Any change here marks the Table (and its sub-controls) for redraw.
|
||||
**Usage Scenario:** Use this when you need to adjust whether the table’s background is visible. A typical use is to set `table.setTableFillMode(StellarX::FillMode::Null);` if you want the table to overlay on a custom background (so only text and borders render, letting the underlying image or color show through). Otherwise, keep it as Solid to use a uniform background color for cells. The default is Solid fill. Be aware that patterns or hatched fills are not fully supported in this version of the framework; even if set, they will revert to a solid fill for simplicity[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L360-L368).
|
||||
|
||||
#### setTableLineStyle(StellarX::LineStyle style)
|
||||
|
||||
**Prototype:** `void setTableLineStyle(StellarX::LineStyle style)`
|
||||
**Parameters:**
|
||||
|
||||
- `style` – A `StellarX::LineStyle` value indicating the style of the grid lines (e.g., `LineStyle::Solid` for solid lines, `LineStyle::Dash` for dashed lines, `LineStyle::Dot` for dotted lines, etc.).
|
||||
**Return Value:** None.
|
||||
**Description:** Sets the style used for drawing the table’s border and grid lines. Changing this will cause the table to use the specified line style (if supported by the underlying graphics library) for all cell borders and the outline on the next redraw. Examples include dashed or dotted lines for a lighter look compared to solid lines. After calling this, the line style property is updated and the table is marked dirty for redrawing. Note that the actual rendering of different line styles depends on the graphics library capabilities.
|
||||
**Usage Scenario:** This is a visual customization for the table’s grid. For instance, calling `table.setTableLineStyle(StellarX::LineStyle::Dash);` will make the table draw its cell borders with dashed lines, which might be desirable in a print preview or a specialized UI theme. Use it to differentiate sections or to achieve a stylistic effect. Combine it with appropriate border color and width for best results. If a particular line style is not obvious on screen, ensure that line width is 1 and the color contrasts with the background.
|
||||
|
||||
#### setTableBorderWidth(int width)
|
||||
|
||||
**Prototype:** `void setTableBorderWidth(int width)`
|
||||
**Parameters:**
|
||||
|
||||
- `width` – The thickness of the border lines in pixels. Must be a positive integer.
|
||||
**Return Value:** None.
|
||||
**Description:** Sets the width (thickness) of the lines used for the table’s cell borders and outline. By default, grid lines are 1 pixel thick. Increasing this value will make the table’s grid lines thicker/bolder, while a value of 1 keeps them thin. When this property is changed, the table marks itself for redraw and will use the new line width for drawing borders on the next `draw()`. If a thicker border is set (greater than 1), the Table’s drawing logic takes into account the larger stroke when restoring the background to avoid artifacts (adjusting the area it refreshes)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L220-L228)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L240-L248).
|
||||
**Usage Scenario:** Use a larger border width when you want the table grid to stand out more, such as in a presentation or when displaying on a high-DPI screen where a 1-pixel line may appear too thin. For example, `table.setTableBorderWidth(2);` will draw grid lines at 2 pixels thickness, making the table look more pronounced. Be cautious with very thick lines as they can clutter the appearance and might overlap cell content if too large relative to cell size. Always test the appearance after changing this setting.
|
||||
|
||||
#### Accessor Methods (Getters)
|
||||
|
||||
The following methods allow you to query the Table’s current state or configuration:
|
||||
|
||||
- **int getCurrentPage() const** – Returns the current page index (1-based) that the table is displaying[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L390-L398). For example, if the table is showing the first page of data, this returns 1; if the user has navigated to the second page, it returns 2, and so on. This can be used to display or log the current page number.
|
||||
- **int getTotalPages() const** – Returns the total number of pages of data in the table given the current dataset and rows-per-page setting[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L394-L402). This is calculated as ceil(total_rows / rowsPerPage). It’s useful for informing the user (e.g., “Page 2 of 5”) or for logic that might depend on whether more pages are available.
|
||||
- **int getRowsPerPage() const** – Returns the number of data rows the table is set to display on each page[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L400-L408). This reflects the value last set by `setRowsPerPage`. It can be used to verify the current pagination setting or to adjust external controls accordingly.
|
||||
- **bool getShowPageButton() const** – Returns the current setting of whether the pagination buttons are visible[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L404-L412) (`true` if they are shown, `false` if hidden). This could be checked to decide if manual navigation UI should be enabled or if an alternate navigation method is needed.
|
||||
- **COLORREF getTableBorder() const** – Returns the current color (COLORREF) used for the table’s border/grid lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L410-L418). For instance, `RGB(0,0,0)` for black. This allows you to retrieve the color if you need to apply the same color elsewhere or for saving the configuration.
|
||||
- **COLORREF getTableBk() const** – Returns the current background fill color (COLORREF) of the table’s cells[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L414-L422).
|
||||
- **StellarX::FillMode getTableFillMode() const** – Returns the current fill mode for the table’s cells[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L420-L428), as a `StellarX::FillMode` enum (e.g., Solid or Null). This lets you check whether the table is drawing solid backgrounds or is in transparent mode.
|
||||
- **StellarX::LineStyle getTableLineStyle() const** – Returns the current line style used for the table’s grid lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L424-L432) (as a `StellarX::LineStyle` enum). You can use this to inspect the style (solid, dashed, etc.) programmatically.
|
||||
- **std::vectorstd::string getHeaders() const** – Returns a copy of the current list of header strings[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L430-L438). The returned vector contains each column’s header text in order. This can be used to retrieve the headers for display elsewhere or for serialization.
|
||||
- **std::vector<std::vectorstd::string> getData() const** – Returns a copy of all the table’s data[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L434-L442). The returned value is a 2D vector of strings, where each inner vector represents one row of the table (with elements corresponding to each column’s value in that row). This is useful for exporting the table data or performing computations on the data outside of the table (like searching or sorting, which would typically be done on the data and then reapplied to the table).
|
||||
- **int getTableBorderWidth() const** – Returns the current width (in pixels) of the table’s border lines[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/src/table.cpp#L440-L446).
|
||||
|
||||
All these getter functions are `const` and do not modify the Table’s state. They are intended for retrieving the table’s configuration and content at any moment. For example, after populating the table, you might call `getTotalPages()` to update a UI element that shows the total page count, or use `getData()` to verify the data loaded into the table.
|
||||
|
||||
### Public Member Variables
|
||||
|
||||
- **StellarX::ControlText textStyle** – A struct that defines the font and style used for text rendered in the table (including header text, cell content, and the page number label). By modifying this public member, you can customize the appearance of the table’s text. For instance, you can set `textStyle.nHeight` to change the font height (size), `textStyle.lpszFace` to change the font family name, `textStyle.color` to change the text color, and other attributes such as `nWeight` for boldness, `bItalic` for italics, `bUnderline` for underline, etc.[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/include/StellarX/CoreTypes.h#L134-L142). The Table uses these settings when drawing text via EasyX functions. The default `ControlText` values are initialized to a standard font (e.g., a default Chinese font "微软雅黑" as in the code, or "SimSun") at a default size and black color. Developers can adjust `textStyle` any time before calling `draw()` to apply new text styles. Notably, the Table’s internal prev/next Buttons and page number Label also use the Table’s `textStyle` to ensure consistency in typography across the entire control. For example, to make the table text larger and blue:
|
||||
|
||||
```
|
||||
table.textStyle.nHeight = 20; // set font height
|
||||
table.textStyle.color = RGB(0, 0, 128); // set text color to navy blue
|
||||
```
|
||||
|
||||
After adjusting, calling `table.draw()` (or a window redraw) will render the table with the new font settings.
|
||||
|
||||
### Example
|
||||
|
||||
Below is an example demonstrating how to create and use a Table control in a StellarX application[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.en.md#L275-L283)[GitHub](https://github.com/Ysm-04/StellarX/blob/95149238e221b19a15bfa750bcf937620e23fd47/README.en.md#L284-L290):
|
||||
|
||||
```c++
|
||||
// Create a table control at position (50, 50)
|
||||
auto myTable = std::make_unique<Table>(50, 50);
|
||||
|
||||
// Set column headers
|
||||
myTable->setHeaders({ "ID", "Name", "Age", "Occupation" });
|
||||
|
||||
// Add data rows
|
||||
myTable->setData({ "1", "Zhang San", "25", "Engineer" });
|
||||
myTable->setData({ "2", "Li Si", "30", "Designer" });
|
||||
myTable->setData({ "3", "Wang Wu", "28", "Product Manager" });
|
||||
|
||||
// Set to display 2 rows per page
|
||||
myTable->setRowsPerPage(2);
|
||||
|
||||
// Customize text style and table appearance
|
||||
myTable->textStyle.nHeight = 16; // set font height
|
||||
myTable->setTableBorder(RGB(50, 50, 50)); // set border color (dark gray)
|
||||
myTable->setTableBk(RGB(240, 240, 240)); // set background color (light gray)
|
||||
|
||||
// Add the table to the main window (assume mainWindow is a Window instance)
|
||||
mainWindow.addControl(std::move(myTable));
|
||||
```
|
||||
|
||||
In this code, we create a `Table` at coordinates (50, 50) on the window. We then define four column headers: "ID", "Name", "Age", "Occupation". Next, three rows of sample data are added to the table. The `setRowsPerPage(2)` call configures the table to show only 2 rows per page; since we added 3 rows of data, the table will have 2 pages (the first page showing the first 2 rows, and the second page showing the remaining 1 row). We then adjust the table’s appearance by setting the font size (making text slightly larger), changing the border color to a dark gray, and the background color of cells to a light gray for better contrast. Finally, we add the table to a `mainWindow` (which is an instance of `Window`) so that it becomes part of the GUI. Once added, the window’s drawing routine will render the table, and the event loop will ensure the table’s pagination buttons respond to user clicks. Typically, after adding controls to a window, you would call `mainWindow.draw()` to render the initial UI, and then `mainWindow.runEventLoop()` to start processing events, allowing interactions like clicking the "Next" button to flip pages. This example illustrates the basic steps to integrate the Table control into an application and configure its data and appearance.
|
||||
|
||||
|
||||
|
||||
The above documentation covers all key classes in the StellarX framework. With these, developers can construct fully-featured GUI applications with windows, interactive buttons, text fields, and pop-up dialogs. Keep in mind StellarX is built on EasyX and thus inherits its platform limitation (Windows only) and certain modal behaviors (like `InputBox` use). It is ideal for educational purposes and lightweight tools requiring a simple GUI.
|
||||
+194
-333
@@ -1,15 +1,19 @@
|
||||
# StellarX GUI Framework
|
||||
# StellarX GUI Framework README
|
||||
|
||||
[中文文档](README.md)
|
||||
[中文README](README.md)
|
||||
|
||||
official website:https://stellarx-gui.top
|
||||
blog: https://blog.stellarx-gui.top
|
||||
|
||||
> For framework information and quick start instructions, please visit the official website. For detailed usage tutorials, please refer to the StellarX Xingyuan page on my personal blog.
|
||||
|
||||
------
|
||||
|
||||

|
||||
|
||||
[](https://github.com/Ysm-04/StellarX)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -18,434 +22,291 @@
|
||||

|
||||

|
||||
|
||||
> **"Bound by Stars, Light as Dust"** — A native C++ GUI framework for Windows platform, featuring extreme lightweight and high modularity.
|
||||
> **“Bounded by the stars, light as dust.”** — An ultra-lightweight, highly modular, native C++ GUI framework for Windows.
|
||||
|
||||
`StellarX` was born from resistance against "overly bloated" modern GUI frameworks. It rejects dependencies that often reach hundreds of MB, long compilation times, and steep learning curves, choosing to return to the essence: solving core desktop application development needs with the most concise code, clearest architecture, and highest efficiency.
|
||||
`StellarX` rejects bloat: no hundreds-of-MB dependencies, no marathon builds, and no steep learning curve. Back to the essence—clean code, clear architecture, and high efficiency to solve the core needs of desktop app development.
|
||||
|
||||
It is a **pure teaching-level, tool-level framework** designed to help developers deeply understand GUI principles and quickly build lightweight Windows tools.
|
||||
This is a **teaching-grade and tooling-grade** framework that helps developers understand GUI fundamentals and quickly build lightweight utilities.
|
||||
|
||||
---
|
||||
------
|
||||
|
||||
### 🆕V3.0.1 - Major Update
|
||||
|
||||
[CHANGELOG.en.md](CHANGELOG.en.md)
|
||||
|
||||
==Notice==
|
||||
|
||||
This update changes the **semantics of `TextBox::setText`**.
|
||||
If your previous code manually called `draw()` after calling `setText()`, that call should now be removed. Otherwise, under the new version, old code may cause `TextBox` flickering in some cases.
|
||||
|
||||
### 🙏 Acknowledgements
|
||||
|
||||
Special thanks to [Pengfei Zhu](https://github.com/zhupengfeivip) for helping improve the StellarX documentation, especially the **Include Directories and Library Directories Configuration** section, and for reporting the issue where passing `NULL` in window mode would cause a console window to appear ([Issues#9](https://github.com/Ysm-04/StellarX/issues/9)).
|
||||
|
||||
### ⚙️ Changes
|
||||
|
||||
- **Changed semantics of `TextBox::setText`:** from the previous dirty-mark-only behavior to a more robust workflow
|
||||
- If the text has not changed, it returns immediately without doing anything.
|
||||
- If the text has changed, it decides whether to redraw immediately or request an upstream redraw depending on whether the graphics context/window has already been created. If the graphics context/window has not yet been created, it only marks itself as dirty.
|
||||
- **TextBox - text overflow truncation:** when the user enters text or calls `setText` to set text, if the text length does not exceed `maxCharLen` but its pixel width exceeds the `TextBox` width, the displayed text will be truncated by character and appended with `...` when rendered. The full original text is still stored internally and is not affected when retrieved through getter methods.
|
||||
|
||||
**For other fixes and changes, please refer to the [Changelog](CHANGELOG.md).**
|
||||
|
||||
## 📦 Project Structure & Design Philosophy
|
||||
|
||||
StellarX framework adopts classic **Object-Oriented** and **modular** design with a clear and standardized project structure:
|
||||
|
||||
```markdown
|
||||
StellarX adopts classic **OOP** and **modular** design with a clear structure:
|
||||
|
||||
```
|
||||
StellarX/
|
||||
├── include/ # Header files directory
|
||||
│ └── StellarX/ # Framework header files
|
||||
│ ├── StellarX.h # Main include header - one-click import of entire framework
|
||||
│ ├── CoreTypes.h # ★ Core ★ - Single source of truth for all enums and structs
|
||||
│ ├── Control.h # Abstract base class - defines unified interface for all controls
|
||||
│ ├── Button.h # Button control
|
||||
│ ├── Window.h # Window management
|
||||
│ ├── Label.h # Label control
|
||||
│ ├── TextBox.h # Text box control
|
||||
│ ├── Canvas.h # Canvas container
|
||||
│ ├── Dialog.h # Dialog control (new in v2.0.0)
|
||||
│ ├── MessageBox.h # Message box factory (new in v2.0.0)
|
||||
│ └── Table.h # Table control
|
||||
├── src/ # Source files directory
|
||||
├── include/
|
||||
│ └── StellarX/
|
||||
│ ├── StellarX.h
|
||||
│ ├── CoreTypes.h # single source of truth (enums/structs)
|
||||
│ ├── Control.h
|
||||
│ ├── Button.h
|
||||
│ ├── Window.h
|
||||
│ ├── Label.h
|
||||
│ ├── TextBox.h
|
||||
│ ├── TabControl.h #v2.2.0
|
||||
│ ├── Canvas.h
|
||||
│ ├── Dialog.h
|
||||
│ ├── MessageBox.h
|
||||
│ └── Table.h
|
||||
├── src/
|
||||
│ ├── Control.cpp
|
||||
│ ├── Button.cpp
|
||||
│ ├── Window.cpp
|
||||
│ ├── Label.cpp
|
||||
│ ├── TextBox.cpp
|
||||
│ ├── Canvas.cpp
|
||||
│ ├── TabControl.cpp #v2.2.0
|
||||
│ ├── Table.cpp
|
||||
│ ├── Dialog.cpp # v2.0.0 new
|
||||
│ └── MessageBox.cpp # v2.0.0 new
|
||||
├── examples/ # Example code directory
|
||||
│ └── demo.cpp # Basic demonstration
|
||||
├── docs/ # Documentation directory
|
||||
│ └── CODE_OF_CONDUCT.md # Code of Conduct
|
||||
├── CMakeLists.txt # CMake build configuration
|
||||
├── CONTRIBUTING.md # Contribution guide
|
||||
├── CHANGELOG.md # Changelog
|
||||
├── Doxyfile # Doxygen configuration
|
||||
├── LICENSE # MIT License
|
||||
└── README.md # Project description
|
||||
│ ├── Dialog.cpp
|
||||
│ └── MessageBox.cpp
|
||||
├── examples/
|
||||
│ └── demo.cpp
|
||||
├── docs/
|
||||
│ └── CODE_OF_CONDUCT.md
|
||||
├── CMakeLists.txt
|
||||
├── CONTRIBUTING.md
|
||||
├── CHANGELOG.md
|
||||
├── CHANGELOG.en.md
|
||||
├── Doxyfile
|
||||
├── LICENSE
|
||||
├──API 文档.md
|
||||
├──API Documentation.en.md
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### **Design Philosophy:**
|
||||
**Design Philosophy:**
|
||||
|
||||
1. **Single Responsibility Principle (SRP)**: Each class/file is responsible for one thing only.
|
||||
2. **Dependency Inversion Principle (DIP)**: High-level modules (like `Window`) don't depend on low-level modules (like `Button`), both depend on abstractions (`Control`).
|
||||
3. **Open/Closed Principle (OCP)**: New controls can be easily extended by inheriting the `Control` base class without modifying existing code.
|
||||
4. **Consistency**: All controls share unified `draw()` and `handleEvent()` interfaces.
|
||||
1. **Single Responsibility (SRP):** each class/file does exactly one thing.
|
||||
2. **Dependency Inversion (DIP):** high-level modules depend on abstractions (`Control`), not concrete controls.
|
||||
3. **Open/Closed (OCP):** extend by inheriting from `Control` without modifying existing code.
|
||||
4. **Consistency:** unified `draw()` / `handleEvent()` across all controls.
|
||||
|
||||
## 🚀 Core Features
|
||||
|
||||
- **Extreme Lightweight**: Core library compiles to only ~12MB, with zero external dependencies. Generated applications are compact.
|
||||
- **Clear Modular Architecture**: Uses `CoreTypes.h` to uniformly manage all types, eliminating duplicate definitions and greatly improving maintainability.
|
||||
- **Native C++ Performance**: Built directly on EasyX and Win32 API, providing near-native execution efficiency with very low memory footprint (typically <10MB).
|
||||
- **Complete Control System**: Button, Label, TextBox, Canvas, Table, Dialog, and MessageBox factory.
|
||||
- **Highly Customizable**: From control colors, shapes (rectangle, rounded, circle, ellipse) to fill modes and font styles, all have detailed enum support for easy customization.
|
||||
- **Simple & Intuitive API**: Uses classic object-oriented design, code as documentation, low learning curve.
|
||||
- **Standard Project Structure**: Adopts standard include/src separation structure, supports CMake build, easy to integrate and use.
|
||||
- **Enhanced Event System**: v2.0.0 introduces event consumption mechanism, all `handleEvent` methods return `bool` indicating whether event was consumed, supporting finer-grained event propagation control.
|
||||
- **Dialog System**: New complete dialog support, including modal and non-modal dialogs, automatically handling background saving and restoration.
|
||||
- **Ultra-lightweight:** no heavyweight external dependencies besides EasyX.
|
||||
- **Clear modules:** `CoreTypes.h` unifies types and enums.
|
||||
- **Native performance:** EasyX + Win32 for efficient execution and low memory (often <10 MB).
|
||||
- **Complete control set:** Button, Label, TextBox, Canvas, Table, Dialog, MessageBox, **TabControl**.
|
||||
- **Highly customizable:** colors; shapes (rectangle/rounded/circle/ellipse); fills; fonts—switchable via enums.
|
||||
- **Simple, intuitive API:** OOP design with clear semantics—code as documentation.
|
||||
- **Standard project layout:** split `include/src`, CMake-friendly, easy to integrate or use out of the box.
|
||||
|
||||
## ⚡ Quick Start (5 Minutes to Get Started)
|
||||
------
|
||||
|
||||
> **🎯 Latest Version Download**
|
||||
> Download pre-compiled library files and header files from [GitHub Releases](https://github.com/Ysm-04/StellarX/releases/latest) for quick integration into your project.
|
||||
## ⚡ Quick Start (5 minutes)
|
||||
|
||||
### Environment Requirements
|
||||
> Get the prebuilt package from [Releases](https://github.com/Ysm-04/StellarX/releases/latest).
|
||||
|
||||
- **OS**: Windows 10 or higher
|
||||
- **Compiler**: C++17 supported compiler (e.g., **Visual Studio 2019+**)
|
||||
- **Graphics Library**: [EasyX](https://easyx.cn/) (2022 version or higher, please select the version matching your compiler during installation)
|
||||
- **Build Tool**: CMake 3.12+ (optional, recommended)
|
||||
### Requirements
|
||||
|
||||
- **OS:** Windows 10+
|
||||
- **Compiler:** C++17 (e.g., VS 2019+)
|
||||
- **Graphics:** [EasyX](https://easyx.cn/) 2022+ (matching your compiler)
|
||||
- **Build:** CMake 3.12+ (optional)
|
||||
|
||||
### Install EasyX
|
||||
|
||||
1. Visit [EasyX official website](https://easyx.cn/) to download the latest version
|
||||
2. Run the installer, select the version matching your Visual Studio version
|
||||
3. After installation, no additional configuration is needed, StellarX framework will automatically link EasyX
|
||||
1. Download the latest EasyX
|
||||
2. Install components matching your Visual Studio version
|
||||
3. The framework links automatically—no extra config needed
|
||||
|
||||
### Method 1: Using CMake Build (Recommended)
|
||||
### Build with CMake (recommended)
|
||||
|
||||
1. **Clone the project**:
|
||||
|
||||
```bash
|
||||
```
|
||||
git clone https://github.com/Ysm-04/StellarX.git
|
||||
cd StellarX
|
||||
```
|
||||
|
||||
2. **Generate build system**:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
```
|
||||
|
||||
3. **Compile the project**:
|
||||
|
||||
```bash
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
4. **Run the example**:
|
||||
|
||||
```bash
|
||||
./examples/Demo
|
||||
```
|
||||
|
||||
### Method 2: Manual Integration into Existing Project
|
||||
### Manual Integration
|
||||
|
||||
1. **Copy the include and src directories** to your project
|
||||
2. **Configure include paths** to ensure the compiler can find the `include/StellarX/` directory
|
||||
3. **Add all .cpp files** to your project for compilation
|
||||
- Copy `include` and `src`
|
||||
- Add header search path: `include/StellarX/`
|
||||
- Add all `.cpp` files to your project
|
||||
|
||||
### Create Your First StellarX Application
|
||||
### First Resizable Window
|
||||
|
||||
```cpp
|
||||
// Just include this one header to use all features
|
||||
```
|
||||
#include "StellarX.h"
|
||||
|
||||
// Program entry point (use WinMain for better compatibility)
|
||||
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
|
||||
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
// 1. Create a 640x480 window with white background, titled "My App"
|
||||
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "My First StellarX App");
|
||||
|
||||
// 2. Create a button (managed with smart pointer)
|
||||
auto myButton = std::make_unique<Button>(
|
||||
250, 200, 140, 40, // x, y, width, height
|
||||
"Click Me", // button text
|
||||
StellarX::ButtonMode::NORMAL,
|
||||
StellarX::ControlShape::ROUND_RECTANGLE
|
||||
);
|
||||
|
||||
// 3. Set click event for the button (using Lambda expression)
|
||||
myButton->setOnClickListener([&mainWindow]() {
|
||||
// Use message box factory to create modal dialog
|
||||
auto result = StellarX::MessageBox::ShowModal(
|
||||
mainWindow,
|
||||
"Welcome to StellarX GUI\r\nAuthor: Ysm-04",
|
||||
"Greeting",
|
||||
StellarX::MessageBoxType::OKCancel
|
||||
);
|
||||
// Handle dialog result
|
||||
if (result == StellarX::MessageBoxResult::OK) {
|
||||
// User clicked OK button
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 4. (Optional) Set button style
|
||||
myButton->textStyle.nHeight = 20;
|
||||
myButton->textStyle.color = RGB(0, 0, 128); // Dark blue text
|
||||
myButton->setButtonBorder(RGB(0, 128, 255)); // Blue border
|
||||
|
||||
// 5. Add button to window
|
||||
mainWindow.addControl(std::move(myButton));
|
||||
|
||||
// 6. Draw the window
|
||||
// Resizing enabled by default; current size is the minimum size
|
||||
Window mainWindow(800, 600, 0, RGB(255,255,255), "My StellarX App");
|
||||
mainWindow.draw();
|
||||
|
||||
// 7. Enter message loop, wait for user interaction
|
||||
mainWindow.runEventLoop();
|
||||
// Add your controls...
|
||||
// mainWindow.addControl(std::move(btn));
|
||||
|
||||
mainWindow.runEventLoop();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
1. **Compile and run!** You'll see a window with a blue rounded button, clicking it will pop up a message box.
|
||||
> Implementation note: perform **full-window background drawing** (solid/image) during `WM_PAINT`, and combine with EasyX batch drawing to suppress flicker and black edges.
|
||||
|
||||
## 📚 Core Types Detailed Explanation (`CoreTypes.h`)
|
||||
------
|
||||
|
||||
All visual and behavioral properties of the StellarX framework are controlled through the elegant enums and structs defined in `CoreTypes.h`.
|
||||
## 📚 Core Types (excerpt from `CoreTypes.h`)
|
||||
|
||||
### Enum Types (Enums)
|
||||
### Enums
|
||||
|
||||
| Enum Type | Description | Common Values |
|
||||
| :--------------------- | :---------------------- | :----------------------------------------------------------- |
|
||||
| **`ControlShape`** | Control geometric shape | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`, etc. |
|
||||
| **`ButtonMode`** | Button behavior mode | `NORMAL`, `TOGGLE`, `DISABLED` |
|
||||
| **`TextBoxMode`** | Text box mode | `INPUT_MODE`, `READONLY_MODE` |
|
||||
| **`FillMode`** | Graphics fill mode | `SOLID`, `NULL`, `HATCHED`, etc. |
|
||||
| **`FillStyle`** | Pattern fill style | `HORIZONTAL`, `CROSS`, etc. |
|
||||
| **`LineStyle`** | Border line style | `SOLID`, `DASH`, `DOT`, etc. |
|
||||
| **`MessageBoxType`** | Message box type | `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore` |
|
||||
| **`MessageBoxResult`** | Message box result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
|
||||
| Enum | Description | Common values |
|
||||
| ------------------ | ---------------- | ------------------------------------------------------------ |
|
||||
| `ControlShape` | Geometric shape | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE` |
|
||||
| `ButtonMode` | Button behavior | `NORMAL`, `TOGGLE`, `DISABLED` |
|
||||
| `TextBoxMode` | TextBox mode | `INPUT_MODE`, `READONLY_MODE` |
|
||||
| `FillMode` | Fill mode | `SOLID`, `NULL`, `HATCHED` |
|
||||
| `FillStyle` | Pattern style | `HORIZONTAL`, `CROSS` |
|
||||
| `LineStyle` | Line style | `SOLID`, `DASH`, `DOT` |
|
||||
| `MessageBoxType` | Message box type | `OK`, `OKCancel`, `YesNo`, ... |
|
||||
| `MessageBoxResult` | Result | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
|
||||
| `TabPlacement` | Tab position | `Top`, `Bottom`, `Left`, `Right` |
|
||||
|
||||
### Structs (Structs)
|
||||
| Enum | Description | Common values |
|
||||
| ------------ | ---------------------- | -------------------------------------------- |
|
||||
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
|
||||
| `Anchor` | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
|
||||
|
||||
### Structs
|
||||
|
||||
| Struct | Description |
|
||||
| :----------------- | :----------------------------------------------------------- |
|
||||
| **`ControlText`** | Encapsulates all text style attributes, including font, size, color, bold, italic, underline, strikethrough, etc. |
|
||||
| **`RouRectangle`** | Defines rounded rectangle corner ellipse dimensions, contains width and height properties. |
|
||||
| -------------- | ---------------------------------------------------- |
|
||||
| `ControlText` | Font/size/color/bold/italic/underline/strike-through |
|
||||
| `RouRectangle` | Corner ellipse size for rounded rectangles |
|
||||
|
||||
**Usage Example:**
|
||||
------
|
||||
|
||||
```c++
|
||||
// Create a complex text style
|
||||
StellarX::ControlText myStyle;
|
||||
myStyle.nHeight = 25; // Font height
|
||||
myStyle.lpszFace = _T("Microsoft YaHei"); // Font
|
||||
myStyle.color = RGB(255, 0, 0); // Red color
|
||||
myStyle.nWeight = FW_BOLD; // Bold
|
||||
myStyle.bUnderline = true; // Underline
|
||||
## 🧩 Controls Library
|
||||
|
||||
// Apply to controls
|
||||
myLabel->textStyle = myStyle;
|
||||
myButton->textStyle = myStyle;
|
||||
```
|
||||
### 1) Basic Controls
|
||||
|
||||
## 🧩 Complete Control Library
|
||||
| Control | Header | Description | Key Points |
|
||||
| ------- | ----------- | ----------------- | ------------------------------------------------------------ |
|
||||
| Button | `Button.h` | Versatile button | Shapes/modes; hover/pressed colors; callbacks; **single-line truncation** + **Tooltip** (v2.1.0) |
|
||||
| Label | `Label.h` | Text label | Transparent/opaque background; custom fonts |
|
||||
| TextBox | `TextBox.h` | Input/display box | Input/readonly; integrates EasyX `InputBox` |
|
||||
|
||||
### 1. Basic Controls
|
||||
### 2) Container Controls
|
||||
|
||||
| Control | Header File | Description | Key Features |
|
||||
| :---------- | :---------- | :-------------------- | :----------------------------------------------------------- |
|
||||
| **Button** | `Button.h` | Multi-function button | Supports multiple modes/shapes/states, hover/click colors, custom callbacks |
|
||||
| **Label** | `Label.h` | Text label | Supports transparent/opaque background, custom font styles |
|
||||
| **TextBox** | `TextBox.h` | Input box/Display box | Supports input and read-only modes, integrates EasyX's `InputBox` |
|
||||
| Control | Header | Description |
|
||||
| ------- | ---------- | ------------------------------------------------------------ |
|
||||
| Canvas | `Canvas.h` | Parent container with custom border/background; **built-in HBox/VBox auto layout** (v2.1.0) |
|
||||
| Window | `Window.h` | Top-level container with message loop and dispatch; **resizable** (v2.1.0) |
|
||||
|
||||
### 2. Container Controls
|
||||
### 3) Advanced Controls
|
||||
|
||||
| Control | Header File | Description |
|
||||
| :--------- | :---------- | :----------------------------------------------------------- |
|
||||
| **Canvas** | `Canvas.h` | Container control, can serve as parent container for other controls, supports custom borders and background. |
|
||||
| **Window** | `Window.h` | Top-level window, ultimate container for all controls, responsible for message loop and dispatching. |
|
||||
| Control | Header | Description | Key Points |
|
||||
| ---------- | -------------- | ----------- | ------------------------------------------------------------ |
|
||||
| Table | `Table.h` | Data grid | Paging/header/auto column width; fixed page-control overlap/ghosting (v2.1.0) |
|
||||
| Dialog | `Dialog.h` | Dialog | Modal/non-modal; auto layout; background save/restore |
|
||||
| TabControl | `TabControl.h` | Tabs | One-click add of “tab + page” pair (pair), or add child controls to a page; uses relative coordinates |
|
||||
|
||||
### 3. Advanced Controls
|
||||
### 4) Static Factory
|
||||
|
||||
| Control | Header File | Description | Key Features |
|
||||
| :--------- | :---------- | :----------- | :----------------------------------------------------------- |
|
||||
| **Table** | `Table.h` | Data table | **Framework highlight feature**, supports paginated display, custom headers and data, automatic column width calculation, page turn buttons. |
|
||||
| **Dialog** | `Dialog.h` | Dialog class | Implements complete dialog functionality, supports multiple button combinations and asynchronous result callbacks. Automatically handles layout, background save/restore and lifecycle management. |
|
||||
| Control | Header | Description | Key Points |
|
||||
| ---------- | -------------- | ------------------- | -------------------------------------------- |
|
||||
| MessageBox | `MessageBox.h` | Message-box factory | Static API; modal/non-modal; de-dup built in |
|
||||
|
||||
**Table Control Example:**
|
||||
------
|
||||
|
||||
```c++
|
||||
// Create a table
|
||||
auto myTable = std::make_unique<Table>(50, 50);
|
||||
## 📐 Layout Management (HBox/VBox)
|
||||
|
||||
// Set headers
|
||||
myTable->setHeaders({ "ID", "Name", "Age", "Occupation" });
|
||||
==Reserved, to be implemented==
|
||||
|
||||
// Add data rows
|
||||
myTable->setData({ "1", "Zhang San", "25", "Engineer" });
|
||||
myTable->setData({ "2", "Li Si", "30", "Designer" });
|
||||
myTable->setData({ "3", "Wang Wu", "28", "Product Manager" });
|
||||
------
|
||||
|
||||
// Set rows per page
|
||||
myTable->setRowsPerPage(2);
|
||||
## 🗂 Tabs (TabControl)
|
||||
|
||||
// Set table style
|
||||
myTable->textStyle.nHeight = 16;
|
||||
myTable->setTableBorder(RGB(50, 50, 50));
|
||||
myTable->setTableBk(RGB(240, 240, 240));
|
||||
- Tab strip (button group) + page container (`Canvas`)
|
||||
- For transparent themes: **background snapshot** switching in the page area to avoid ghosting
|
||||
- API: **see the API documentation**
|
||||
|
||||
// Add to window
|
||||
mainWindow.addControl(std::move(myTable));
|
||||
```
|
||||
------
|
||||
|
||||
### 4. Static Factory Class
|
||||
## ✂️ Single-line Text Truncation & Button Tooltip
|
||||
|
||||
| Control | Header File | Description | Key Features |
|
||||
| :------------- | :------------- | :------------------- | :----------------------------------------------------------- |
|
||||
| **MessageBox** | `MessageBox.h` | Dialog factory calls | Static method calls, no instantiation needed, automatically handles modal and non-modal logic differences, integrated into window's dialog management system, provides deduplication mechanism to prevent duplicate dialogs |
|
||||
- **Button truncation:** separate handling for CJK/Latin under MBCS; append `...` based on pixel-width threshold
|
||||
- **Tooltip:** delayed show and auto-hide; default text = button text; customizable; uses control-level **background snapshot/restore**
|
||||
|
||||
**MessageBox Usage Example:**
|
||||
------
|
||||
|
||||
```c++
|
||||
// Modal message box (blocks until closed)
|
||||
auto result = StellarX::MessageBox::ShowModal(
|
||||
mainWindow,
|
||||
"Confirm to perform this operation?",
|
||||
"Confirmation",
|
||||
StellarX::MessageBoxType::YesNo
|
||||
);
|
||||
## 🧊 Transparent Background & Background Snapshots
|
||||
|
||||
if (result == StellarX::MessageBoxResult::Yes)
|
||||
{
|
||||
// User selected "Yes"
|
||||
}
|
||||
- **General convention:** `captureBackground(rect)` before the first draw; `restoreBackground()` before hiding/covering
|
||||
- **Table:** snapshot region **includes the header**; after page switch, restore immediately + redraw; paging controls centered
|
||||
|
||||
// Non-modal message box (asynchronous callback)
|
||||
StellarX::MessageBox::ShowAsync(
|
||||
mainWindow,
|
||||
"Operation completed",
|
||||
"Notification",
|
||||
StellarX::MessageBoxType::OK,
|
||||
[](StellarX::MessageBoxResult result) {
|
||||
// Asynchronously handle result
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- **Register Viewer (≈450 lines)** — An interactive 32-bit register visualization tool implemented based on StellarX (supports bit inversion, left/right shift, hex/decimal conversion, signed/unsigned toggle, binary grouping display).
|
||||
Path: `examples/register-viewer/`
|
||||
------
|
||||
|
||||
## 🔧 Advanced Topics & Best Practices
|
||||
|
||||
### 1. Custom Controls
|
||||
- Custom controls: inherit from `Control`, implement `draw()` / `handleEvent()`
|
||||
- Performance:
|
||||
- **Dirty rectangles:** set `dirty=true` on state changes for on-demand redraw
|
||||
- **Avoid extra `cleardevice()`**: background is centrally handled in `WM_PAINT`
|
||||
- Ensure `SetWorkingImage(nullptr)` before drawing so output goes to the screen
|
||||
- Event consumption: return `true` after handling to stop propagation
|
||||
|
||||
You can create custom controls by inheriting from the `Control` base class. Just implement the two pure virtual functions `draw()` and `handleEvent()`.
|
||||
------
|
||||
|
||||
```c++
|
||||
class MyCustomControl : public Control {
|
||||
public:
|
||||
MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
|
||||
## ⚠️ Applicability & Limits
|
||||
|
||||
void draw() override {
|
||||
saveStyle();
|
||||
// Your custom drawing logic
|
||||
setfillcolor(RGB(255, 100, 100));
|
||||
fillrectangle(x, y, x + width, y + height);
|
||||
restoreStyle();
|
||||
}
|
||||
- Not suitable for high-performance games or complex animation; re-verify metrics under extreme DPI
|
||||
- No accessibility support yet
|
||||
- Windows-only, not cross-platform
|
||||
- For complex commercial front-ends, consider Qt / wxWidgets / ImGui / Electron
|
||||
|
||||
bool handleEvent(const ExMessage& msg) override {
|
||||
// Your custom event handling logic
|
||||
if (msg.message == WM_LBUTTONDOWN &&
|
||||
msg.x > x && msg.x < x + width &&
|
||||
msg.y > y && msg.y < y + height) {
|
||||
// Handle click
|
||||
return true; // Event consumed
|
||||
}
|
||||
return false; // Event not consumed
|
||||
}
|
||||
|
||||
bool IsVisible() const override { return true; }
|
||||
bool model() const override { return false; }
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Layout Management
|
||||
|
||||
Current version of StellarX primarily uses **absolute positioning**. For simple layouts, you can achieve this by calculating coordinates. For complex layouts, consider:
|
||||
|
||||
- Nesting controls in `Canvas` to achieve relative positioning.
|
||||
- Implementing simple flow layout or grid layout managers yourself.
|
||||
|
||||
### 3. Performance Optimization
|
||||
|
||||
- **Dirty Rectangle Rendering**: Implemented internally in the framework, controls set `dirty=true` when state changes, only redraw when necessary.
|
||||
- **Image Resources**: Use `IMAGE` objects to load images, then reuse them to avoid multiple loads.
|
||||
- **Reduce Operations in Loops**: Avoid heavy calculations in `draw()` and `handleEvent()`.
|
||||
- **Event Consumption Mechanism**: Use event consumption return values appropriately to avoid unnecessary event propagation.
|
||||
|
||||
### 4. Dialog Usage Tips
|
||||
|
||||
- **Modal Dialogs**: Use `ShowModal` method, blocks current thread until dialog closes, suitable for important operations requiring user confirmation.
|
||||
- **Non-modal Dialogs**: Use `ShowAsync` method, doesn't block main thread, suitable for informational prompts or background tasks.
|
||||
- **Dialog Deduplication**: Framework has built-in non-modal dialog deduplication mechanism to prevent multiple dialogs for the same message from appearing simultaneously.
|
||||
|
||||
## ⚠️ Important Limitations & Suitable Scenarios
|
||||
|
||||
**StellarX framework's design goals are lightness, clarity, and teaching value, therefore it explicitly is NOT suitable for the following scenarios:**
|
||||
|
||||
- **High-performance games or complex animations**: Rendering is based on EasyX's CPU software rendering, performance is limited.
|
||||
- **Applications requiring high DPI scaling**: Limited support for high DPI displays, interface may not display correctly.
|
||||
- **Applications requiring accessibility features**: No support for screen readers and other assistive technologies.
|
||||
- **Cross-platform applications**: Deeply dependent on Windows API and EasyX, cannot run directly on Linux/macOS.
|
||||
- **Complex commercial software frontends**: Lacks advanced controls (like tree views, rich text boxes, tabs, advanced lists) and mature automatic layout managers.
|
||||
|
||||
**If you need to develop the aforementioned types of applications, please consider the following mature solutions:**
|
||||
|
||||
- **Qt**: Extremely powerful, cross-platform, suitable for large commercial applications.
|
||||
- **wxWidgets**: Native appearance, cross-platform.
|
||||
- **Dear ImGui**: Immediate mode GUI, very suitable for tools and debug interfaces.
|
||||
- **Web Technology Stack (Electron/CEF)**: Suitable for scenarios requiring web technologies.
|
||||
------
|
||||
|
||||
## 📜 License
|
||||
|
||||
This project uses the **MIT License**.
|
||||
MIT (see `LICENSE`).
|
||||
|
||||
You are free to:
|
||||
## 👥 Contributing Guidelines
|
||||
|
||||
- Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the framework.
|
||||
- Use it for private or commercial projects.
|
||||
|
||||
The only requirement is:
|
||||
|
||||
- Please retain the original copyright notice in your projects.
|
||||
|
||||
See the [LICENSE](https://license/) file in the project root directory for details.
|
||||
|
||||
## 👥 Contribution Guide
|
||||
|
||||
We welcome all forms of contributions! If you want to contribute to the StellarX framework, please read the following guidelines:
|
||||
|
||||
1. **Code Style**: Please follow the existing Google C++ style guide (use spaces for indentation, braces on new lines, etc.).
|
||||
2. **New Features**: Must provide **example code** and update relevant parts of this README document.
|
||||
3. **Submitting PRs**: Please ensure your code is tested before submission and clearly describe your changes and motivation.
|
||||
4. **Issue Reporting**: If you find a bug or have new ideas, welcome to submit Issues on GitHub.
|
||||
|
||||
Detailed contribution guide please refer to [CONTRIBUTING.md](CONTRIBUTING.en.md).
|
||||
- Follow the existing C++ style
|
||||
- New features should include examples and README updates
|
||||
- Self-test before submitting and explain the motivation for changes
|
||||
- For bugs/ideas, please open an Issue
|
||||
|
||||
## 🙏 Acknowledgements
|
||||
|
||||
- Thanks to [EasyX Graphics Library](https://easyx.cn/) for providing the simple and easy-to-use graphics foundation for this project, making C++ GUI programming education possible.
|
||||
- Thanks to all developers pursuing the **concise, efficient, clear** coding philosophy, you are the inspiration for StellarX's birth.
|
||||
- Thanks to [EasyX](https://easyx.cn/)
|
||||
- Thanks to developers who value **simplicity/efficiency/clarity**
|
||||
|
||||
------
|
||||
|
||||
**Star Ocean Vast, Code as Boat.**
|
||||
|
||||
May `StellarX` become a reliable cornerstone for your exploration of the GUI world, whether for learning, teaching, or creating practical tools.
|
||||
**Stars and seas, code as the vessel.**
|
||||
|
||||
## 📞 Support & Feedback
|
||||
|
||||
If you encounter problems during use or have any suggestions:
|
||||
|
||||
1. Check [Example Code](examples/) for usage reference
|
||||
2. Check [Changelog](CHANGELOG.en.md) to learn about latest changes
|
||||
3. Submit Issues on GitHub repository to report problems
|
||||
|
||||
------
|
||||
|
||||
*StellarX Framework - Light as Dust, Bound by Stars*
|
||||
|
||||
- See [examples/](examples/)
|
||||
- Read the [CHANGELOG](CHANGELOG.md / CHANGELOG.en.md)
|
||||
- Submit an Issue on GitHub
|
||||
@@ -2,14 +2,23 @@
|
||||
|
||||
[English document](README.en.md)
|
||||
|
||||
官网地址:https://stellarx-gui.top
|
||||
博客:https://blog.stellarx-gui.top
|
||||
|
||||
> 框架信息以及快速开始可前往官网查看,详细的使用教程可前往个人博客的StellarX星垣页面查看
|
||||
|
||||
> 本仓库为 **StellarX** 主仓:开发与 Issue/PR 均在 GitHub 进行。
|
||||
> GitCode 仅为只读镜像:如需反馈请到 GitHub:https://github.com/Ysm-04/StellarX
|
||||
|
||||
[](https://gitcode.com/Ysm-04/StellarX)
|
||||
|
||||
------
|
||||
|
||||

|
||||
|
||||
[](https://github.com/Ysm-04/StellarX)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -20,436 +29,296 @@
|
||||
|
||||
> **「繁星为界,轻若尘埃」** —— 一个为 Windows 平台打造的、极致轻量级、高度模块化的 C++ 原生 GUI 框架。
|
||||
|
||||
`星垣 (StellarX)` 诞生于对现代GUI框架"过度臃肿"的反抗。它拒绝动辄数百MB的依赖、漫长的编译时间和复杂的学习曲线,选择回归本质:用最精简的代码、最清晰的架构和最高的效率,解决桌面应用开发的核心需求。
|
||||
`星垣 (StellarX)` 反对臃肿,拒绝动辄数百 MB 的依赖、漫长编译与高门槛学习曲线,回归本质:以精简代码、清晰架构与高效率,解决桌面应用开发的核心需求。
|
||||
|
||||
它是一个**纯粹的教学级、工具级框架**,旨在让开发者深入理解GUI原理,并快速构建轻量级Windows工具。
|
||||
这是一个**教学级、工具级**框架,帮助开发者深入理解 GUI 原理,并快速构建轻量工具。
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### 🆕V3.0.1 - 重要更新
|
||||
|
||||
完整版建议查看[更新日志](CHANGELOG.md)
|
||||
|
||||
==注意==
|
||||
|
||||
此次更新变更了**TextBox::setText语义**之前如果再调用`setText`之后手动调用了`draw`方法现在应当删除,否则旧代码在新版本可能会造成`TextBox`闪烁(概率触发)【配置包含目录与库目录】
|
||||
|
||||
### 🙏 鸣谢
|
||||
|
||||
感谢用户[Pengfei Zhu](https://github.com/zhupengfeivip)帮StellarX完善文档【配置包含目录与库目录】部分,以及反馈在窗口模式传递参数NULL时会弹出命令行窗口的问题[Issues#9](https://github.com/Ysm-04/StellarX/issues/9)
|
||||
|
||||
### ⚙️ 变更
|
||||
|
||||
- **TextBox::setText语义变化:**由原来的仅标脏改为更健全的工作原理
|
||||
- 如果文本未发生变化则立即return不做任何操作
|
||||
- 如果文本变化则根据是否已经创建图形上下文/窗口决定是否立即重绘/向上请求,如果图形上下文/窗口还为创建则仅标记为脏
|
||||
- **TextBox-文本溢出截断:**如果用户输入或者调用`setText`设置文本,文本不超过`maxCharLen`但是像素长度超过`TextBox`则按字符截断并添加...绘制,内部仍然保存完整的文本不影响get方法获取
|
||||
|
||||
**其余修复及变更请前往[更新日志](CHANGELOG.md)**
|
||||
|
||||
---
|
||||
|
||||
## 📦 项目结构与设计哲学
|
||||
|
||||
星垣框架采用经典的**面向对象**和**模块化**设计,项目结构清晰规范:
|
||||
星垣采用经典 **OOP** 与 **模块化** 设计,结构清晰:
|
||||
|
||||
```markdown
|
||||
StellarX/
|
||||
├── include/ # 头文件目录
|
||||
│ └── StellarX/ # 框架头文件
|
||||
│ ├── StellarX.h # 主包含头文件 - 一键引入整个框架
|
||||
│ ├── CoreTypes.h # ★ 核心 ★ - 所有枚举、结构体的唯一定义源
|
||||
│ ├── Control.h # 抽象基类 - 定义所有控件的统一接口
|
||||
│ ├── Button.h # 按钮控件
|
||||
│ ├── Window.h # 窗口管理
|
||||
│ ├── Label.h # 标签控件
|
||||
│ ├── TextBox.h # 文本框控件
|
||||
│ ├── Canvas.h # 画布容器
|
||||
│ ├── Dialog.h # 对话框控件(v2.0.0新增)
|
||||
│ ├── MessageBox.h # 消息框工厂(v2.0.0新增)
|
||||
│ └── Table.h # 表格控件
|
||||
├── src/ # 源文件目录
|
||||
├── include/
|
||||
│ └── StellarX/
|
||||
│ ├── StellarX.h
|
||||
│ ├── CoreTypes.h # 唯一定义源(枚举/结构体)
|
||||
│ ├── Control.h
|
||||
│ ├── Button.h
|
||||
│ ├── Window.h
|
||||
│ ├── Label.h
|
||||
│ ├── TextBox.h
|
||||
│ ├── TabControl.h #v2.2.0
|
||||
│ ├── Canvas.h
|
||||
│ ├── Dialog.h
|
||||
│ ├── MessageBox.h
|
||||
│ └── Table.h
|
||||
├── src/
|
||||
│ ├── Control.cpp
|
||||
│ ├── Button.cpp
|
||||
│ ├── Window.cpp
|
||||
│ ├── Label.cpp
|
||||
│ ├── TextBox.cpp
|
||||
│ ├── Canvas.cpp
|
||||
│ ├── TabControl.cpp #v2.2.0
|
||||
│ ├── Table.cpp
|
||||
│ ├── Dialog.cpp # v2.0.0新增
|
||||
│ └── MessageBox.cpp # v2.0.0新增
|
||||
├── examples/ # 示例代码目录
|
||||
│ └── demo.cpp # 基础演示
|
||||
├── docs/ # 文档目录
|
||||
│ └── CODE_OF_CONDUCT.md # 行为准则
|
||||
├── CMakeLists.txt # CMake 构建配置
|
||||
├── CONTRIBUTING.md # 贡献指南
|
||||
├── CHANGELOG.md # 更新日志
|
||||
├── Doxyfile # Doxygen 配置
|
||||
├── LICENSE # MIT 许可证
|
||||
└── README.md # 项目说明
|
||||
│ ├── Dialog.cpp
|
||||
│ └── MessageBox.cpp
|
||||
├── examples/
|
||||
│ └── demo.cpp
|
||||
├── docs/
|
||||
│ └── CODE_OF_CONDUCT.md
|
||||
├── CMakeLists.txt
|
||||
├── CONTRIBUTING.md
|
||||
├── CHANGELOG.md
|
||||
├── CHANGELOG.en.md
|
||||
├── Doxyfile
|
||||
├── LICENSE
|
||||
├──API 文档.md
|
||||
├──API Documentation.en.md
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**设计理念:**
|
||||
|
||||
1. **单一职责原则 (SRP)**: 每个类/文件只负责一件事。
|
||||
2. **依赖倒置原则 (DIP)**: 高层模块(如`Window`)不依赖低层模块(如`Button`),二者都依赖其抽象(`Control`)。
|
||||
3. **开闭原则 (OCP)**: 通过继承`Control`基类,可以轻松扩展新的控件,而无需修改现有代码。
|
||||
4. **一致性**: 所有控件共享统一的`draw()`和`handleEvent()`接口。
|
||||
1. **单一职责(SRP)**:每个类/文件只做一件事。
|
||||
2. **依赖倒置(DIP)**:高层模块依赖抽象(`Control`),而非具体控件。
|
||||
3. **开闭原则(OCP)**:继承 `Control` 可扩展新控件,无需修改既有代码。
|
||||
4. **一致性**:所有控件统一 `draw()` / `handleEvent()` 接口。
|
||||
|
||||
## 🚀 核心特性
|
||||
|
||||
- **极致的轻量级**: 核心库编译后仅 ~12MB,无任何外部依赖。生成的应用程序小巧玲珑。
|
||||
- **清晰的模块化架构**: 使用`CoreTypes.h`统一管理所有类型,消除重复定义,极大提升可维护性。
|
||||
- **原生C++性能**: 直接基于EasyX和Win32 API,提供接近原生的执行效率,内存占用极低(通常<10MB)。
|
||||
- **完整的控件体系**: 按钮(Button)、标签(Label)、文本框(TextBox)、画布(Canvas)、表格(Table)、对话框(Dialog)与消息框工厂(MessageBox)。
|
||||
- **高度可定制化**: 从控件颜色、形状(矩形、圆角、圆形、椭圆)到填充模式、字体样式,均有详尽枚举支持,可轻松定制。
|
||||
- **简洁直观的API**: 采用经典的面向对象设计,代码即文档,学习成本极低。
|
||||
- **标准项目结构**: 采用标准的include/src分离结构,支持CMake构建,易于集成和使用。
|
||||
- **增强的事件系统**: v2.0.0引入事件消费机制,所有`handleEvent`方法返回`bool`表示是否消费事件,支持更精细的事件传播控制。
|
||||
- **对话框系统**: 新增完整的对话框支持,包括模态和非模态对话框,自动处理背景保存和恢复。
|
||||
- **极致轻量**:除 EasyX 外无外部重量级依赖。
|
||||
- **模块清晰**:`CoreTypes.h` 统一类型与枚举。
|
||||
- **原生性能**:EasyX + Win32,执行高效、内存低占用(常见 <10MB)。
|
||||
- **控件齐全**:Button、Label、TextBox、Canvas、Table、Dialog、MessageBox、**TabControl**。
|
||||
- **高度自定义**:颜色、形状(矩形/圆角/圆/椭圆)、填充、字体等皆有枚举配置,易于切换。
|
||||
- **简单直观 API**:OOP 设计,接口语义明确、调用友好,代码即文档。
|
||||
- **标准工程结构**:include/src 分离,支持 CMake 构建,方便集成到现有项目或开箱即用。
|
||||
|
||||
------
|
||||
|
||||
## ⚡ 快速开始(5 分钟上手)
|
||||
|
||||
> **🎯 最新版本下载**
|
||||
> 从 [GitHub Releases](https://github.com/Ysm-04/StellarX/releases/latest) 下载预编译的库文件和头文件,即可快速集成到你的项目中。
|
||||
> 从 [Releases](https://github.com/Ysm-04/StellarX/releases/latest) 获取预编译包。
|
||||
|
||||
### 环境要求
|
||||
|
||||
- **操作系统**: Windows 10 或更高版本
|
||||
- **编译器**: 支持C++17的编译器 (如: **Visual Studio 2019+**)
|
||||
- **图形库**: [EasyX](https://easyx.cn/) (2022版本或更高,安装时请选择与您编译器匹配的版本)
|
||||
- **构建工具**: CMake 3.12+ (可选,推荐使用)
|
||||
- **系统**:Windows 10+
|
||||
- **编译器**:C++17(如 VS 2019+)
|
||||
- **图形库**: [EasyX](https://easyx.cn/) 2022+(与编译器匹配)
|
||||
- **构建**:CMake 3.12+(可选)
|
||||
|
||||
### 安装 EasyX
|
||||
1. 访问 [EasyX 官网](https://easyx.cn/) 下载最新版本
|
||||
2. 运行安装程序,选择与您的 Visual Studio 版本匹配的版本
|
||||
3. 安装完成后,无需额外配置,星垣框架会自动链接 EasyX
|
||||
|
||||
### 方法一:使用CMake构建(推荐)
|
||||
1. 下载 EasyX 最新版
|
||||
2. 按 VS 版本安装匹配组件
|
||||
3. 框架会自动链接,无需额外配置
|
||||
|
||||
### CMake 构建(推荐)
|
||||
|
||||
1. **克隆项目**:
|
||||
```bash
|
||||
git clone https://github.com/Ysm-04/StellarX.git
|
||||
cd StellarX
|
||||
```
|
||||
|
||||
2. **生成构建系统**:
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
```
|
||||
|
||||
3. **编译项目**:
|
||||
```bash
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
4. **运行示例**:
|
||||
```bash
|
||||
./examples/Demo
|
||||
```
|
||||
|
||||
### 方法二:手动集成到现有项目
|
||||
### 手动集成
|
||||
|
||||
1. **将include和src目录复制**到您的项目中
|
||||
2. **配置包含路径**,确保编译器可以找到`include/StellarX/`目录
|
||||
3. **将所有.cpp文件**添加到您的项目中编译
|
||||
- 拷贝 `include` 与 `src`
|
||||
- 配置头文件搜索路径:`include/StellarX/`
|
||||
- 将全部 `.cpp` 加入工程
|
||||
|
||||
### 创建你的第一个星垣应用
|
||||
### 第一个可拉伸窗口
|
||||
|
||||
```cpp
|
||||
// 只需包含这一个头文件即可使用所有功能
|
||||
```c++
|
||||
#include "StellarX.h"
|
||||
|
||||
// 程序入口点(请使用WinMain以获得更好的兼容性)
|
||||
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
|
||||
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
// 1. 创建一个640x480的窗口,背景为白色,标题为"我的应用"
|
||||
Window mainWindow(640, 480, 0, RGB(255, 255, 255), "我的第一个星垣应用");
|
||||
|
||||
// 2. 创建一个按钮 (使用智能指针管理)
|
||||
auto myButton = std::make_unique<Button>(
|
||||
250, 200, 140, 40, // x, y, 宽度, 高度
|
||||
"点击我", // 按钮文本
|
||||
StellarX::ButtonMode::NORMAL,
|
||||
StellarX::ControlShape::ROUND_RECTANGLE
|
||||
);
|
||||
|
||||
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
||||
myButton->setOnClickListener([&mainWindow]() {
|
||||
// 使用消息框工厂创建模态对话框
|
||||
auto result = StellarX::MessageBox::ShowModal(
|
||||
mainWindow,
|
||||
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
|
||||
"问候",
|
||||
StellarX::MessageBoxType::OKCancel
|
||||
);
|
||||
// 处理对话框结果
|
||||
if (result == StellarX::MessageBoxResult::OK) {
|
||||
// 用户点击了确定按钮
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 4. (可选)设置按钮样式
|
||||
myButton->textStyle.nHeight = 20;
|
||||
myButton->textStyle.color = RGB(0, 0, 128); // 深蓝色文字
|
||||
myButton->setButtonBorder(RGB(0, 128, 255)); // 蓝色边框
|
||||
|
||||
// 5. 将按钮添加到窗口
|
||||
mainWindow.addControl(std::move(myButton));
|
||||
|
||||
// 6. 绘制窗口
|
||||
//默认启动拉伸,当前尺寸为最小尺寸
|
||||
Window mainWindow(800, 600, 0, RGB(255,255,255), "我的星垣应用");
|
||||
mainWindow.draw();
|
||||
|
||||
// 7. 进入消息循环,等待用户交互
|
||||
mainWindow.runEventLoop();
|
||||
// 添加你的控件...
|
||||
// mainWindow.addControl(std::move(btn));
|
||||
|
||||
mainWindow.runEventLoop();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
1. **编译并运行!** 您将看到一个带有蓝色圆角按钮的窗口,点击它将会弹出消息框。
|
||||
> 实现要点:在 `WM_PAINT` 期进行**整窗背景绘制**(纯色/图片),并配合 EasyX 批量绘制抑制频闪与黑边。
|
||||
|
||||
## 📚 核心类型详解 (`CoreTypes.h`)
|
||||
------
|
||||
|
||||
星垣框架的所有视觉和行为属性都通过`CoreTypes.h`中定义的精美枚举和结构体来控制。
|
||||
## 📚 核心类型(`CoreTypes.h` 摘要)
|
||||
|
||||
### 枚举类型 (Enums)
|
||||
### 枚举
|
||||
|
||||
| 枚举类型 | 描述 | 常用值 |
|
||||
| :--------------------- | :----------- | :----------------------------------------------------------- |
|
||||
| **`ControlShape`** | 控件几何形状 | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE`等 |
|
||||
| **`ButtonMode`** | 按钮行为模式 | `NORMAL`(普通), `TOGGLE`(切换), `DISABLED`(禁用) |
|
||||
| **`TextBoxMode`** | 文本框模式 | `INPUT_MODE`(输入), `READONLY_MODE`(只读) |
|
||||
| **`FillMode`** | 图形填充模式 | `SOLID`(实心), `NULL`(空心), `HATCHED`(图案)等 |
|
||||
| **`FillStyle`** | 图案填充样式 | `HORIZONTAL`(水平线), `CROSS`(十字线)等 |
|
||||
| **`LineStyle`** | 边框线型 | `SOLID`(实线), `DASH`(虚线), `DOT`(点线)等 |
|
||||
| **`MessageBoxType`** | 消息框类型 | `OK`, `OKCancel`, `YesNo`, `YesNoCancel`, `RetryCancel`, `AbortRetryIgnore` |
|
||||
| **`MessageBoxResult`** | 消息框结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
|
||||
| 枚举 | 描述 | 常用值 |
|
||||
| ------------------ | ---------- | ------------------------------------------------------------ |
|
||||
| `ControlShape` | 几何形状 | `RECTANGLE`, `B_RECTANGLE`, `ROUND_RECTANGLE`, `CIRCLE`, `ELLIPSE` |
|
||||
| `ButtonMode` | 按钮行为 | `NORMAL`, `TOGGLE`, `DISABLED` |
|
||||
| `TextBoxMode` | 文本框模式 | `INPUT_MODE`, `READONLY_MODE` |
|
||||
| `FillMode` | 填充模式 | `SOLID`, `NULL`, `HATCHED` |
|
||||
| `FillStyle` | 图案样式 | `HORIZONTAL`, `CROSS` |
|
||||
| `LineStyle` | 线型 | `SOLID`, `DASH`, `DOT` |
|
||||
| `MessageBoxType` | 消息框类型 | `OK`, `OKCancel`, `YesNo`, ... |
|
||||
| `MessageBoxResult` | 结果 | `OK`, `Cancel`, `Yes`, `No`, `Abort`, `Retry`, `Ignore` |
|
||||
| `TabPlacement` | 页签位置 | `Top`,`Bottom`,`Left`,`Right` |
|
||||
|
||||
### 结构体 (Structs)
|
||||
| 枚举 | 描述 | 常用值 |
|
||||
| ------------ | ---------------------- | -------------------------------------------- |
|
||||
| `LayoutMode` | 窗口布局模式 | `Fixed`, `AnchorToEdges` |
|
||||
| Anchor | 控件相对于父容器的锚点 | `NoAnchor` ,`Left` , `Right`, `Top`,`Bottom` |
|
||||
|
||||
|
||||
|
||||
### 结构体
|
||||
|
||||
| 结构体 | 描述 |
|
||||
| :----------------- | :----------------------------------------------------------- |
|
||||
| **`ControlText`** | 封装了所有文本样式属性,包括字体、大小、颜色、粗体、斜体、下划线、删除线等。 |
|
||||
| **`RouRectangle`** | 定义圆角矩形的圆角椭圆尺寸,包含宽度和高度属性。 |
|
||||
| -------------- | -------------------------------------- |
|
||||
| `ControlText` | 字体/字号/颜色/粗体/斜体/下划线/删除线 |
|
||||
| `RouRectangle` | 圆角矩形的角部椭圆尺寸 |
|
||||
|
||||
**使用示例:**
|
||||
------
|
||||
|
||||
cpp
|
||||
## 🧩 控件库
|
||||
|
||||
```c++
|
||||
// 创建一个复杂的文本样式
|
||||
StellarX::ControlText myStyle;
|
||||
myStyle.nHeight = 25; // 字体高度
|
||||
myStyle.lpszFace = _T("微软雅黑"); // 字体
|
||||
myStyle.color = RGB(255, 0, 0); // 红色
|
||||
myStyle.nWeight = FW_BOLD; // 粗体
|
||||
myStyle.bUnderline = true; // 下划线
|
||||
### 1) 基础控件
|
||||
|
||||
// 应用于控件
|
||||
myLabel->textStyle = myStyle;
|
||||
myButton->textStyle = myStyle;
|
||||
```
|
||||
| 控件 | 头文件 | 描述 | 关键点 |
|
||||
| ------- | ----------- | ----------- | ------------------------------------------------------------ |
|
||||
| Button | `Button.h` | 多功能按钮 | 形状/模式、悬停/点击色、回调,**单行截断** + **Tooltip**(v2.1.0) |
|
||||
| Label | `Label.h` | 文本标签 | 透明/不透明背景,自定义字体 |
|
||||
| TextBox | `TextBox.h` | 输入/显示框 | 输入/只读,整合 EasyX `InputBox` |
|
||||
|
||||
|
||||
|
||||
## 🧩 控件库大全
|
||||
|
||||
### 1. 基础控件
|
||||
|
||||
| 控件 | 头文件 | 描述 | 关键特性 |
|
||||
| :---------- | :---------- | :------------ | :------------------------------------------------------ |
|
||||
| **Button** | `Button.h` | 多功能按钮 | 支持多种模式/形状/状态,可设置悬停/点击颜色,自定义回调 |
|
||||
| **Label** | `Label.h` | 文本标签 | 支持背景透明/不透明,自定义字体样式 |
|
||||
| **TextBox** | `TextBox.h` | 输入框/显示框 | 支持输入和只读模式,集成EasyX的`InputBox` |
|
||||
|
||||
### 2. 容器控件
|
||||
### 2) 容器控件
|
||||
|
||||
| 控件 | 头文件 | 描述 |
|
||||
| :--------- | :--------- | :------------------------------------------------------- |
|
||||
| **Canvas** | `Canvas.h` | 容器控件,可作为其他控件的父容器,支持自定义边框和背景。 |
|
||||
| **Window** | `Window.h` | 顶级窗口,所有控件的最终容器,负责消息循环和调度。 |
|
||||
| ------ | ---------- | ------------------------------------------------------------ |
|
||||
| Canvas | `Canvas.h` | 父容器,自定义边框/背景,**内置 HBox/VBox 自动布局**(v2.1.0) |
|
||||
| Window | `Window.h` | 顶层容器,消息循环与分发,**可拉伸**(v2.1.0) |
|
||||
|
||||
### 3. 高级控件
|
||||
### 3) 高级控件
|
||||
|
||||
| 控件 | 头文件 | 描述 | 关键特性 |
|
||||
| :--------- | :--------- | :------- | :----------------------------------------------------------- |
|
||||
| **Table** | `Table.h` | 数据表格 | **框架功能亮点**,支持分页显示、自定义表头和数据、自动计算列宽、翻页按钮。 |
|
||||
| **Dialog** | `Dialog.h` | 对话框类 | 实现完整的对话框功能,支持多种按钮组合和异步结果回调。自动处理布局、背景保存恢复和生命周期管理。 |
|
||||
| 控件 | 头文件 | 描述 | 关键点 |
|
||||
| ---------- | -------------- | -------- | ------------------------------------------------------------ |
|
||||
| Table | `Table.h` | 数据表格 | 分页/表头/列宽自动、翻页控件重叠/透明叠影已修复(v2.1.0) |
|
||||
| Dialog | `Dialog.h` | 对话框 | 支持模态/非模态,自动布局与背景保存/恢复 |
|
||||
| TabControl | `TabControl.h` | 选项卡 | 支持一键添加“页签+页”对(pair)也可以单独对某页添加子控件,采用相对坐标 |
|
||||
|
||||
**表格控件示例:**
|
||||
### 4) 静态工厂
|
||||
|
||||
cpp
|
||||
| 控件 | 头文件 | 描述 | 关键点 |
|
||||
| ---------- | -------------- | ---------- | ----------------------------------- |
|
||||
| MessageBox | `MessageBox.h` | 消息框工厂 | 静态 API;支持模态/非模态;内置去重 |
|
||||
|
||||
```c++
|
||||
// 创建一个表格
|
||||
auto myTable = std::make_unique<Table>(50, 50);
|
||||
------
|
||||
|
||||
// 设置表头
|
||||
myTable->setHeaders({ "ID", "姓名", "年龄", "职业" });
|
||||
## 📐 布局管理(HBox/VBox)
|
||||
|
||||
// 添加数据行
|
||||
myTable->setData({ "1", "张三", "25", "工程师" });
|
||||
myTable->setData({ "2", "李四", "30", "设计师" });
|
||||
myTable->setData({ "3", "王五", "28", "产品经理" });
|
||||
|
||||
// 设置每页显示2行
|
||||
myTable->setRowsPerPage(2);
|
||||
|
||||
// 设置表格样式
|
||||
myTable->textStyle.nHeight = 16;
|
||||
myTable->setTableBorder(RGB(50, 50, 50));
|
||||
myTable->setTableBk(RGB(240, 240, 240));
|
||||
|
||||
// 添加到窗口
|
||||
mainWindow.addControl(std::move(myTable));
|
||||
```
|
||||
==预留,待实现==
|
||||
|
||||
|
||||
|
||||
### 4. 静态工厂类
|
||||
------
|
||||
|
||||
| 控件 | 头文件 | 描述 | 关键特性 |
|
||||
| :------------- | :------------- | :--------------- | :----------------------------------------------------------- |
|
||||
| **MessageBox** | `MessageBox.h` | 对话框的工厂调用 | 静态方法调用,无需实例化,自动处理模态和非模态的逻辑差异集成到窗口的对话框管理系统中提供去重机制防止重复对话框 |
|
||||
## 🗂 选项卡(TabControl)
|
||||
|
||||
**消息框使用示例:**
|
||||
- 页签条(按钮组) + 页面容器(`Canvas`)
|
||||
- 透明主题:页面区域**背景快照**切换,避免叠影
|
||||
- API :**查看API文档**
|
||||
|
||||
cpp
|
||||
------
|
||||
|
||||
```c++
|
||||
// 模态消息框(阻塞直到关闭)
|
||||
auto result = StellarX::MessageBox::ShowModal(
|
||||
mainWindow,
|
||||
"确认要执行此操作吗?",
|
||||
"确认",
|
||||
StellarX::MessageBoxType::YesNo
|
||||
);
|
||||
## ✂️ 文本单行截断 & Button Tooltip
|
||||
|
||||
if (result == StellarX::MessageBoxResult::Yes)
|
||||
{
|
||||
// 用户选择了"是"
|
||||
}
|
||||
- **按钮截断**:多字节字符集下**中/英分治**,基于像素宽度阈值追加 `...`
|
||||
- **Tooltip**:延时出现、自动隐藏;默认文字=按钮文本,可自定义;使用控件级**背景快照/恢复**
|
||||
|
||||
// 非模态消息框(异步回调)
|
||||
StellarX::MessageBox::ShowAsync(
|
||||
mainWindow,
|
||||
"操作已完成",
|
||||
"提示",
|
||||
StellarX::MessageBoxType::OK,
|
||||
[](StellarX::MessageBoxResult result) {
|
||||
// 异步处理结果
|
||||
}
|
||||
);
|
||||
```
|
||||
------
|
||||
|
||||
## 示例
|
||||
- **寄存器查看器 (≈450 行)** — 一个基于 StellarX 实现的交互式 32 位寄存器可视化工具(支持位取反、左/右移、十六进制/十进制转换、带有符号/无符号切换、二进制分组显示)。
|
||||
路径:`examples/register-viewer/`
|
||||
## 🧊 透明背景与背景快照
|
||||
|
||||
- **通用约定**:首绘前 `captureBackground(rect)`,隐藏/覆盖前 `restoreBackground()`
|
||||
- **Table**:快照区域**包含表头**;翻页后立即恢复 + 重绘,分页控件整体居中
|
||||
|
||||
------
|
||||
|
||||
## 🔧 高级主题与最佳实践
|
||||
|
||||
### 1. 自定义控件
|
||||
- 自定义控件:继承 `Control`,实现 `draw()` / `handleEvent()`
|
||||
- 性能:
|
||||
- **脏矩形**:状态改变时置 `dirty=true`,按需重绘
|
||||
- **避免额外 `cleardevice()`**:背景已由 `WM_PAINT` 统一处理
|
||||
- 绘制前确保 `SetWorkingImage(nullptr)` 将输出落到屏幕
|
||||
- 事件消费:处理后返回 `true` 终止传播
|
||||
|
||||
您可以通过继承`Control`基类来创建自定义控件。只需实现`draw()`和`handleEvent()`两个纯虚函数即可。
|
||||
------
|
||||
|
||||
cpp
|
||||
## ⚠️ 适用与限制
|
||||
|
||||
```c++
|
||||
class MyCustomControl : public Control {
|
||||
public:
|
||||
MyCustomControl(int x, int y) : Control(x, y, 100, 100) {}
|
||||
- 不适合高性能游戏/复杂动画;极端 DPI 需复核度量
|
||||
- 暂无无障碍能力
|
||||
- Windows 专用,不跨平台
|
||||
- 复杂商业前端建议用 Qt / wxWidgets / ImGui / Electron
|
||||
|
||||
void draw() override {
|
||||
saveStyle();
|
||||
// 您的自定义绘制逻辑
|
||||
setfillcolor(RGB(255, 100, 100));
|
||||
fillrectangle(x, y, x + width, y + height);
|
||||
restoreStyle();
|
||||
}
|
||||
------
|
||||
|
||||
bool handleEvent(const ExMessage& msg) override {
|
||||
// 您的自定义事件处理逻辑
|
||||
if (msg.message == WM_LBUTTONDOWN &&
|
||||
msg.x > x && msg.x < x + width &&
|
||||
msg.y > y && msg.y < y + height) {
|
||||
// 处理点击
|
||||
return true; // 事件已消费
|
||||
}
|
||||
return false; // 事件未消费
|
||||
}
|
||||
## 📜 许可证
|
||||
|
||||
bool IsVisible() const override { return true; }
|
||||
bool model() const override { return false; }
|
||||
};
|
||||
```
|
||||
MIT(见 `LICENSE`)。
|
||||
|
||||
## 👥 贡献指南
|
||||
|
||||
- 遵循现有 C++ 风格
|
||||
- 新特性需附示例与 README 更新
|
||||
- 提交前请自测,并说明变更动机
|
||||
- Bug/想法请提 Issue
|
||||
|
||||
### 2. 布局管理
|
||||
## 🙏 致谢
|
||||
|
||||
当前版本星垣主要采用**绝对定位**。对于简单布局,您可以通过计算坐标来实现。对于复杂布局,可以考虑:
|
||||
|
||||
- 在`Canvas`中嵌套控件,实现相对定位。
|
||||
- 自行实现简单的流式布局或网格布局管理器。
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
- **脏矩形渲染**: 框架内部已实现,控件状态改变时`dirty=true`,仅在需要时重绘。
|
||||
- **图像资源**: 使用`IMAGE`对象加载图片后,可重复使用,避免多次加载。
|
||||
- **减少循环内操作**: 在`draw()`和`handleEvent()`中避免进行重型计算。
|
||||
- **事件消费机制**: 合理使用事件消费返回值,避免不必要的事件传播。
|
||||
|
||||
### 4. 对话框使用技巧
|
||||
|
||||
- **模态对话框**: 使用`ShowModal`方法,会阻塞当前线程直到对话框关闭,适合需要用户确认的重要操作。
|
||||
- **非模态对话框**: 使用`ShowAsync`方法,不会阻塞主线程,适合提示性信息或后台任务。
|
||||
- **对话框去重**: 框架内置了非模态对话框去重机制,防止同一消息的多个对话框同时出现。
|
||||
|
||||
## ⚠️ 重要限制与适用场景
|
||||
|
||||
**星垣框架的设计目标是轻便、清晰和教学价值,因此它明确** **不适用于** **以下场景:**
|
||||
|
||||
- **高性能游戏或复杂动画**: 渲染基于EasyX的CPU软件渲染,性能有限。
|
||||
- **需要高DPI缩放的应用**: 对高DPI显示器的支持有限,界面可能显示不正确。
|
||||
- **需要无障碍功能的应用**: 未提供对屏幕阅读器等辅助技术的支持。
|
||||
- **跨平台应用**: 深度依赖Windows API和EasyX,无法直接在Linux/macOS上运行。
|
||||
- **复杂的商业软件前端**: 缺乏高级控件(如树形图、富文本框、选项卡、高级列表等)和成熟的自动布局管理器。
|
||||
|
||||
**如果您需要开发上述类型的应用,请考虑使用以下成熟方案:**
|
||||
|
||||
- **Qt**: 功能极其强大,跨平台,适合大型商业应用。
|
||||
- **wxWidgets**: 原生外观,跨平台。
|
||||
- **Dear ImGui**: 即时模式GUI,非常适合工具和调试界面。
|
||||
- **Web技术栈 (Electron/CEF)**: 适合需要Web技术的场景。
|
||||
|
||||
## 📜 许可证 (License)
|
||||
|
||||
本项目采用 **MIT 许可证**。
|
||||
|
||||
您可以自由地:
|
||||
|
||||
- 使用、复制、修改、合并、出版发行、散布、再授权及销售本框架的副本。
|
||||
- 将其用于私人或商业项目。
|
||||
|
||||
唯一要求是:
|
||||
|
||||
- 请在您的项目中保留原始的版权声明。
|
||||
|
||||
详见项目根目录的 [LICENSE](LICENSE) 文件。
|
||||
|
||||
## 👥 贡献指南 (Contributing)
|
||||
|
||||
我们欢迎任何形式的贡献!如果您想为星垣框架添砖加瓦,请阅读以下指南:
|
||||
|
||||
1. **代码风格**: 请遵循现有的Google C++规范风格(使用空格缩进,大括号换行等)。
|
||||
2. **新增功能**: 必须提供**示例代码**,并更新本README文档的相关部分。
|
||||
3. **提交PR**: 请确保您的代码在提交前已经过测试,并描述清楚您的更改内容和动机。
|
||||
4. **问题反馈**: 如果您发现了Bug或有新的想法,欢迎在GitHub提交Issue。
|
||||
|
||||
详细贡献指南请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
## 🙏 致谢 (Acknowledgements)
|
||||
|
||||
- 感谢 [EasyX Graphics Library](https://easyx.cn/) 为这个项目提供了简单易用的图形基础,使得用C++教学GUI编程成为可能。
|
||||
- 感谢所有追求**简洁、高效、清晰**编码理念的开发者,你们是"星垣"诞生的灵感源泉。
|
||||
- 感谢 [EasyX](https://easyx.cn/)
|
||||
- 感谢推崇**简洁/高效/清晰**的开发者
|
||||
|
||||
------
|
||||
|
||||
**星辰大海,代码为舟。**
|
||||
|
||||
愿 `星垣 (StellarX)` 能成为您探索GUI世界的一颗可靠基石,无论是用于学习、教学还是创造实用的工具。
|
||||
|
||||
## 📞 支持与反馈
|
||||
|
||||
如果您在使用过程中遇到问题或有任何建议:
|
||||
|
||||
1. 查看 [示例代码](examples/) 获取使用参考
|
||||
2. 查阅 [更新日志](CHANGELOG.md) 了解最新变化
|
||||
3. 在GitHub仓库提交Issue反馈问题
|
||||
|
||||
------
|
||||
|
||||
*星垣框架 - 轻若尘埃,繁星为界*
|
||||
- 查看 [examples/](examples/)
|
||||
- 查阅 [更新日志](CHANGELOG.md)[CHANGELOG](CHANGELOG.en.md)
|
||||
- 在 GitHub 提交 Issue
|
||||
@@ -0,0 +1,58 @@
|
||||
#include"StellarX.h"//包含SXLog头文件
|
||||
|
||||
#if 1
|
||||
// 业务场景只需用日志系统时的最简用法示例
|
||||
/*
|
||||
//设置编码 按需开启
|
||||
StellarX::SxLogger::setGBK();
|
||||
|
||||
//启用日志文件和控制台输出 按需开启
|
||||
StellarX::SxLogger::Get().enableFile("stellarx.log", false, 1024);
|
||||
StellarX::SxLogger::Get().enableConsole(true);
|
||||
|
||||
//设置最低日志级别和语言 按需设置
|
||||
StellarX::SxLogger::Get().setMinLevel(StellarX::SxLogLevel::Debug); // Info/Debug/Trace 自己切
|
||||
StellarX::SxLogger::Get().setLanguage(StellarX::SxLogLanguage::ZhCN); // ZhCN / EnUS
|
||||
*/
|
||||
using namespace StellarX;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
// 1) 可选:把 Windows 控制台切到 GBK,避免中文乱码(内部用 system("chcp 936"))
|
||||
SxLogger::setGBK();
|
||||
|
||||
// 2) 获取全局 logger 并开启控制台输出
|
||||
SxLogger& log = SxLogger::Get();
|
||||
log.enableConsole(true);
|
||||
|
||||
// 3) 最低输出级别(Debug 及以上都会输出)
|
||||
log.setMinLevel(SxLogLevel::Debug);
|
||||
|
||||
// 4) 语言选择(影响 SX_T 的文本选择,不做转码)
|
||||
log.setLanguage(SxLogLanguage::ZhCN);
|
||||
|
||||
// 5) 打几条日志(流式拼接)
|
||||
SX_LOGI("Init") << SX_T("日志系统已启用", "logging enabled");
|
||||
SX_LOGD("Init") << "minLevel=" << (int)log.getMinLevel();
|
||||
|
||||
// 6) 作用域耗时统计(只有 Trace 级别打开且 shouldLog 命中才会输出)
|
||||
{
|
||||
SX_TRACE_SCOPE("Perf", "demo-scope");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
// 7) 切换语言演示
|
||||
log.setLanguage(SxLogLanguage::EnUS);
|
||||
SX_LOGI("Init") << SX_T("这条不会显示中文", "this line is English");
|
||||
|
||||
// 8) Tag 过滤演示(只允许 Init)
|
||||
log.setTagFilter(SxTagFilterMode::Whitelist, { "Init" });
|
||||
SX_LOGI("Init") << "allowed";
|
||||
SX_LOGI("Table") << "blocked (should not appear)";
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -24,7 +24,7 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _
|
||||
// 3. 为按钮设置点击事件(使用Lambda表达式)
|
||||
myButton->setOnClickListener([&mainWindow]() {
|
||||
// 使用消息框工厂创建模态对话框
|
||||
auto result = StellarX::MessageBox::ShowModal(
|
||||
auto result = StellarX::MessageBox::showModal(
|
||||
mainWindow,
|
||||
"欢迎使用星垣GUI\r\n作者:我在人间做废物",
|
||||
"问候",
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
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位选择区");
|
||||
@@ -29,13 +29,13 @@ void main()
|
||||
{
|
||||
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",
|
||||
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);
|
||||
@@ -55,13 +55,13 @@ void main()
|
||||
}
|
||||
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;
|
||||
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);
|
||||
@@ -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",
|
||||
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));
|
||||
|
||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
@@ -21,14 +21,14 @@
|
||||
#include "Control.h"
|
||||
#include"label.h"
|
||||
|
||||
|
||||
#define DISABLEDCOLOUR RGB(96, 96, 96) //禁用状态颜色
|
||||
#define TEXTMARGINS_X 6
|
||||
#define TEXTMARGINS_Y 4
|
||||
constexpr int bordWith = 1; //边框宽度,用于快照恢复时的偏移计算
|
||||
constexpr int bordHeight = 1; //边框高度,用于快照恢复时的偏移计算
|
||||
|
||||
class Button : public Control
|
||||
{
|
||||
|
||||
std::string text; // 按钮上的文字
|
||||
bool click; // 是否被点击
|
||||
bool hover; // 是否被悬停
|
||||
@@ -51,8 +51,6 @@ class Button : public Control
|
||||
StellarX::FillStyle buttonFillIma = StellarX::FillStyle::BDiagonal; //按钮填充图案
|
||||
IMAGE* buttonFileIMAGE = nullptr; //按钮填充图像
|
||||
|
||||
|
||||
|
||||
std::function<void()> onClickCallback; //回调函数
|
||||
std::function<void()> onToggleOnCallback; //TOGGLE模式下的回调函数
|
||||
std::function<void()> onToggleOffCallback; //TOGGLE模式下的回调函数
|
||||
@@ -156,10 +154,6 @@ public:
|
||||
COLORREF getButtonTextColor() const;
|
||||
//获取按钮文字样式
|
||||
StellarX::ControlText getButtonTextStyle() const;
|
||||
//获取按钮宽度
|
||||
int getButtonWidth() const;
|
||||
//获取按钮高度
|
||||
int getButtonHeight() const;
|
||||
public:
|
||||
// === Tooltip API===
|
||||
//设置是否启用提示框
|
||||
@@ -171,20 +165,10 @@ public:
|
||||
//设置提示框位置偏移
|
||||
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
||||
//设置提示框样式
|
||||
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent)
|
||||
{
|
||||
tipLabel.setTextColor(text);
|
||||
tipLabel.setTextBkColor(bk);
|
||||
tipLabel.setTextdisap(transparent);
|
||||
}
|
||||
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
||||
//设置提示框文本
|
||||
void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
|
||||
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText)
|
||||
{
|
||||
tipTextOn = onText;
|
||||
tipTextOff = offText;
|
||||
tipUserOverride = true;
|
||||
}
|
||||
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
|
||||
private:
|
||||
//初始化按钮
|
||||
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
|
||||
@@ -194,11 +178,10 @@ private:
|
||||
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
|
||||
//获取对话框类型
|
||||
bool model() const override { return false; }
|
||||
//文本截断
|
||||
void cutButtonText();
|
||||
// 统一隐藏&恢复背景
|
||||
void hideTooltip();
|
||||
// 根据当前 click 状态选择文案
|
||||
void refreshTooltipTextForState();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "Control.h"
|
||||
#include"Table.h"
|
||||
|
||||
class Canvas : public Control
|
||||
{
|
||||
@@ -32,13 +33,16 @@ protected:
|
||||
COLORREF canvasBorderClor = RGB(0, 0, 0); //边框颜色
|
||||
COLORREF canvasBkClor = RGB(255, 255, 255); //背景颜色
|
||||
|
||||
|
||||
// 清除所有子控件
|
||||
void clearAllControls();
|
||||
public:
|
||||
Canvas();
|
||||
Canvas(int x, int y, int width, int height);
|
||||
~Canvas() {}
|
||||
|
||||
void setX(int x)override;
|
||||
void setY(int y)override;
|
||||
|
||||
//绘制容器及其子控件
|
||||
void draw() override;
|
||||
bool handleEvent(const ExMessage& msg) override;
|
||||
@@ -56,9 +60,14 @@ public:
|
||||
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; };
|
||||
};
|
||||
|
||||
|
||||
+56
-23
@@ -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>
|
||||
@@ -34,14 +31,22 @@
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "CoreTypes.h"
|
||||
|
||||
class Control
|
||||
{
|
||||
protected:
|
||||
int x, y; // 左上角坐标
|
||||
int width, height; // 控件尺寸
|
||||
std::string id; // 控件ID
|
||||
int localx, x, localy, y; // 左上角坐标
|
||||
int localWidth, width, localHeight, height; // 控件尺寸
|
||||
Control* parent = nullptr; // 父控件
|
||||
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::Right; // 锚点
|
||||
|
||||
/* == 背景快照 == */
|
||||
IMAGE* saveBkImage = nullptr;
|
||||
int saveBkX = 0, saveBkY = 0; // 快照保存起始坐标
|
||||
@@ -58,12 +63,13 @@ protected:
|
||||
|
||||
Control(const Control&) = delete;
|
||||
Control& operator=(const Control&) = delete;
|
||||
Control(Control&&) = default;
|
||||
Control& operator=(Control&&) = default;
|
||||
Control(Control&&) = delete;
|
||||
Control& operator=(Control&&) = delete;
|
||||
|
||||
Control() : x(0), y(0), width(100), height(100) {}
|
||||
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)
|
||||
: x(x), y(y), width(width), height(height) {}
|
||||
: localx(x), x(x), localy(y), y(y), localWidth(width), width(width), height(height), localHeight(height) {
|
||||
}
|
||||
public:
|
||||
|
||||
virtual ~Control()
|
||||
@@ -79,15 +85,25 @@ public:
|
||||
currentBkColor = nullptr;
|
||||
currentBorderColor = nullptr;
|
||||
currentLineStyle = nullptr;
|
||||
discardBackground();
|
||||
}
|
||||
|
||||
protected:
|
||||
void saveBackground(int x, int y, int w, int h);
|
||||
void restBackground(); // putimage 回屏
|
||||
void discardBackground(); // 释放快照(窗口重绘/尺寸变化后必须作废)
|
||||
//向上请求重绘
|
||||
virtual void requestRepaint(Control* parent);
|
||||
//根控件/无父时触发重绘
|
||||
virtual void onRequestRepaintAsRoot();
|
||||
protected:
|
||||
//保存背景快照
|
||||
virtual void saveBackground(int x, int y, int w, int h);
|
||||
// putimage 回屏
|
||||
virtual void restBackground();
|
||||
// 释放快照(窗口重绘/尺寸变化后必须作废)
|
||||
void discardBackground();
|
||||
public:
|
||||
//释放快照重新保存,在尺寸变化时更新背景快照避免尺寸变化导致显示错位
|
||||
void updateBackground();
|
||||
//窗口变化丢快照
|
||||
virtual void onWindowResize();
|
||||
// 获取位置和尺寸
|
||||
int getX() const { return x; }
|
||||
int getY() const { return y; }
|
||||
@@ -96,24 +112,41 @@ public:
|
||||
int getRight() const { return x + width; }
|
||||
int getBottom() const { return y + height; }
|
||||
|
||||
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; }
|
||||
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; }
|
||||
|
||||
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:
|
||||
//设置是否重绘
|
||||
void setDirty(bool dirty) { this->dirty = dirty; }
|
||||
|
||||
virtual void draw() = 0;
|
||||
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
||||
|
||||
//设置是否显示
|
||||
void setShow(bool show) { this->show = show; }
|
||||
virtual void setIsVisible(bool show);
|
||||
//设置父容器指针
|
||||
void setParent(Control* parent) { this->parent = parent; }
|
||||
//设置是否重绘
|
||||
virtual void setDirty(bool dirty) { this->dirty = dirty; }
|
||||
//检查控件是否可见
|
||||
bool IsVisible() const { return show; };
|
||||
|
||||
//获取控件id
|
||||
std::string getId() const { return id; }
|
||||
//检查是否为脏
|
||||
bool isDirty() { return dirty; }
|
||||
//用来检查对话框是否模态,其他控件不用实现
|
||||
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:
|
||||
void saveStyle();
|
||||
void restoreStyle();
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace StellarX
|
||||
|
||||
/**
|
||||
* @枚举类名称: TextBoxmode
|
||||
* @功能描述: 定义了文本框的两种模式
|
||||
* @功能描述: 定义了文本框的三种模式
|
||||
*
|
||||
* @详细说明:
|
||||
* 需要限制文本框是否接受用户输入时使用
|
||||
@@ -203,11 +203,13 @@ namespace StellarX
|
||||
* @取值说明:
|
||||
* INPUT_MODE, // 用户可输入模式
|
||||
* READONLY_MODE // 只读模式
|
||||
* PASSWORD_MODE // 密码模式
|
||||
*/
|
||||
enum class TextBoxmode
|
||||
{
|
||||
INPUT_MODE, // 用户可输入模式
|
||||
READONLY_MODE // 只读模式
|
||||
READONLY_MODE, // 只读模式
|
||||
PASSWORD_MODE // 密码模式
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -319,4 +321,71 @@ namespace StellarX
|
||||
int flowBreak = 0; // 1=强制换行
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
* @枚举名称: TabPlacement
|
||||
* @功能描述: 定义了选项卡页签的不同位置
|
||||
*
|
||||
* @详细说明:
|
||||
* 根据选项卡页签的位置,选项卡页签可以有不同的布局方式。
|
||||
*
|
||||
* @成员说明:
|
||||
* Top, - 选项卡页签位于顶部
|
||||
* Bottom, - 选项卡页签位于底部
|
||||
* Left, - 选项卡页签位于左侧
|
||||
* Right - 选项卡页签位于右侧
|
||||
*
|
||||
* @使用示例:
|
||||
* TabPlacement placement = TabPlacement::Top;
|
||||
*/
|
||||
enum class TabPlacement
|
||||
{
|
||||
Top,
|
||||
Bottom,
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
#pragma once
|
||||
#include"StellarX.h"
|
||||
|
||||
#define closeButtonWidth 23 //关闭按钮宽度
|
||||
#define closeButtonWidth 30 //关闭按钮宽度
|
||||
#define closeButtonHeight 20 //关闭按钮高度 同时作为对话框标题栏高度
|
||||
#define functionButtonWidth 70 //按钮宽度
|
||||
#define functionButtonHeight 30 //按钮高度
|
||||
@@ -29,7 +29,7 @@
|
||||
#define buttonAreaHeight 50 //按钮区域高度
|
||||
#define titleToTextMargin 10 //标题到文本的距离
|
||||
#define textToBorderMargin 10 //文本到边框的距离
|
||||
|
||||
#define BorderWidth 3 //边框宽度
|
||||
class Dialog : public Canvas
|
||||
{
|
||||
Window& hWnd; //窗口引用
|
||||
@@ -37,7 +37,6 @@ class Dialog : public Canvas
|
||||
int textWidth = 0; //文本宽度
|
||||
int textHeight = 0; //文本高度
|
||||
int buttonNum = 0; // 按钮数量
|
||||
int BorderWidth = 2; //边框宽度
|
||||
|
||||
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
|
||||
std::string titleText = "提示"; //标题文本
|
||||
@@ -46,7 +45,6 @@ class Dialog : public Canvas
|
||||
std::string message; //提示信息
|
||||
std::vector<std::string> lines; //消息内容按行分割
|
||||
|
||||
|
||||
bool needsInitialization = true; //是否需要初始化
|
||||
bool close = false; //是否关闭
|
||||
bool modal = true; //是否模态
|
||||
@@ -58,7 +56,6 @@ class Dialog : public Canvas
|
||||
COLORREF buttonFalseColor = RGB(215, 215, 215); //按钮未被点击颜色
|
||||
COLORREF buttonHoverColor = RGB(224, 224, 224); //按钮悬浮颜色
|
||||
|
||||
|
||||
Button* closeButton = nullptr; //关闭按钮
|
||||
|
||||
StellarX::MessageBoxResult result = StellarX::MessageBoxResult::Cancel; // 对话框结果
|
||||
@@ -79,7 +76,6 @@ public:
|
||||
//获取对话框消息,用以去重
|
||||
std::string GetText() const;
|
||||
|
||||
|
||||
public:
|
||||
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
||||
~Dialog();
|
||||
@@ -110,7 +106,6 @@ public:
|
||||
//初始化
|
||||
void setInitialization(bool init);
|
||||
|
||||
|
||||
private:
|
||||
// 初始化按钮
|
||||
void initButtons();
|
||||
@@ -124,11 +119,11 @@ private:
|
||||
void getTextSize();
|
||||
//初始化对话框尺寸
|
||||
void initDialogSize();
|
||||
|
||||
|
||||
void addControl(std::unique_ptr<Control> control);
|
||||
|
||||
// 清除所有控件
|
||||
void clearControls();
|
||||
//创建对话框按钮
|
||||
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
|
||||
void requestRepaint(Control* parent) override;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* @文件: StellarX.h
|
||||
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
||||
* @版本: v2.0.0
|
||||
* @版本: v3.0.1
|
||||
* @描述:
|
||||
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
||||
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
||||
@@ -11,7 +11,9 @@
|
||||
*
|
||||
* @作者: 我在人间做废物
|
||||
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
||||
* @仓库: [https://github.com/Ysm-04/StellarX]
|
||||
* @官网:https://stellarx-gui.top/
|
||||
* @仓库: https://github.com/Ysm-04/StellarX
|
||||
* @博客:https://blog.stellarx-gui.top/
|
||||
*
|
||||
* @许可证: MIT License
|
||||
* @版权: Copyright (c) 2025 我在人间做废物
|
||||
@@ -30,6 +32,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "SxLog.h"
|
||||
#include "Control.h"
|
||||
#include"Canvas.h"
|
||||
#include"Window.h"
|
||||
@@ -39,3 +42,6 @@
|
||||
#include"Table.h"
|
||||
#include"Dialog.h"
|
||||
#include"MessageBox.h"
|
||||
#include"TabControl.h"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
#pragma once
|
||||
|
||||
/********************************************************************************
|
||||
* @文件: SxLog.h
|
||||
* @摘要: StellarX 日志系统对外接口定义(控制台/文件输出 + 级别过滤 + Tag过滤 + 中英文选择)
|
||||
* @描述:
|
||||
* 该日志系统采用“宏 + RAII(析构提交)”的方式实现:
|
||||
* - 调用端通过 SX_LOGD/SX_LOGI... 写日志
|
||||
* - 宏内部先 shouldLog 短路过滤,未命中时不构造对象、不拼接字符串
|
||||
* - 命中时构造 SxLogLine,使用 operator<< 拼接内容
|
||||
* - 语句结束时 SxLogLine 析构,统一提交到 SxLogger::logLine 输出
|
||||
*
|
||||
* 输出通道(Sink)目前提供:
|
||||
* - ConsoleSink: 写入 std::cout(不走 WinAPI 调试输出通道)
|
||||
* - FileSink: 写入文件,支持按字节阈值滚动
|
||||
*
|
||||
* @特性:
|
||||
* - 日志级别:Trace/Debug/Info/Warn/Error/Fatal/Off
|
||||
* - Tag 过滤:None/Whitelist/Blacklist
|
||||
* - 可选前缀:时间戳/级别/Tag/线程ID/源码位置
|
||||
* - 中英文选择:SX_T(zh, en) / setLanguage
|
||||
* - 文件滚动:rotateBytes > 0 时按阈值滚动
|
||||
*
|
||||
* @使用场景:
|
||||
* - 排查重绘链路、脏标记传播、Tab 切换、Table 数据刷新等时序问题
|
||||
* - 输出可复现日志,配合回归验证
|
||||
*
|
||||
* @注意:
|
||||
* - SX_T 仅做“字符串选择”,不做编码转换
|
||||
* - 控制台显示是否乱码由“终端 codepage/字体/环境”决定
|
||||
* - 该头文件只声明接口,实现位于 SxLog.cpp
|
||||
*
|
||||
* @所属框架: 星垣(StellarX) GUI框架
|
||||
* @作者: 我在人间做废物
|
||||
********************************************************************************/
|
||||
|
||||
// SxLog.h - header-only interface (implementation in SxLog.cpp)
|
||||
// Pure standard library: std::cout and optional file sink.
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifndef SX_LOG_ENABLE
|
||||
#define SX_LOG_ENABLE 1
|
||||
#endif
|
||||
|
||||
namespace StellarX
|
||||
{
|
||||
/* ========================= 日志级别 ========================= */
|
||||
// 说明:
|
||||
// - minLevel 表示最低输出级别,小于 minLevel 的日志会被 shouldLog 直接过滤
|
||||
// - Off 表示全局关闭
|
||||
enum class SxLogLevel : int
|
||||
{
|
||||
Trace = 0, // 最细粒度:高频路径追踪(谨慎开启)
|
||||
Debug = 1, // 调试信息:状态变化/关键分支
|
||||
Info = 2, // 业务信息:关键流程节点
|
||||
Warn = 3, // 警告:非致命但异常的情况
|
||||
Error = 4, // 错误:功能失败、需要关注
|
||||
Fatal = 5, // 致命:通常意味着无法继续运行
|
||||
Off = 6 // 关闭全部日志
|
||||
};
|
||||
|
||||
/* ========================= 语言选择 ========================= */
|
||||
// 说明:仅用于 SX_T 选择输出哪一段文本,不做编码转换
|
||||
enum class SxLogLanguage : int
|
||||
{
|
||||
ZhCN = 0, // 中文
|
||||
EnUS = 1 // 英文
|
||||
};
|
||||
|
||||
/* ========================= Tag 过滤模式 ========================= */
|
||||
// None : 不过滤,全部输出
|
||||
// Whitelist : 只输出 tagList 中包含的 tag
|
||||
// Blacklist : 输出除 tagList 以外的 tag
|
||||
enum class SxTagFilterMode : int
|
||||
{
|
||||
None = 0,
|
||||
Whitelist = 1,
|
||||
Blacklist = 2
|
||||
};
|
||||
|
||||
/* ========================= 日志配置 ========================= */
|
||||
// 说明:SxLogger 内部持有该配置,shouldLog 与 logLine 都依赖它
|
||||
struct SxLogConfig
|
||||
{
|
||||
SxLogLevel minLevel = SxLogLevel::Info; // 最低输出级别
|
||||
|
||||
bool showTimestamp = true; // 是否输出时间戳前缀
|
||||
bool showLevel = true; // 是否输出级别前缀
|
||||
bool showTag = true; // 是否输出 tag 前缀
|
||||
bool showThreadId = false; // 是否输出线程ID(排查并发时开启)
|
||||
bool showSource = false; // 是否输出源码位置(file:line func)
|
||||
bool autoFlush = true; // 每行写完是否 flush(排查问题更稳,性能略差)
|
||||
|
||||
SxTagFilterMode tagFilterMode = SxTagFilterMode::None; // Tag 过滤模式
|
||||
std::vector<std::string> tagList; // Tag 列表(白名单/黑名单)
|
||||
|
||||
bool fileEnabled = false; // 文件输出是否启用(enableFile 成功才为 true)
|
||||
std::string filePath; // 文件路径
|
||||
bool fileAppend = true; // 是否追加写入
|
||||
std::size_t rotateBytes = 0; // 滚动阈值(0 表示不滚动)
|
||||
};
|
||||
|
||||
/* ========================= Sink 接口 ========================= */
|
||||
// 说明:
|
||||
// - Sink 负责“把完整的一行日志写到某个地方”
|
||||
// - SxLogger 负责过滤/格式化/分发
|
||||
class ILogSink
|
||||
{
|
||||
public:
|
||||
virtual ~ILogSink() = default;
|
||||
|
||||
// 返回 Sink 名称,用于调试识别(例如 "console"/"file")
|
||||
virtual const char* name() const = 0;
|
||||
|
||||
// 写入一整行(调用方保证 line 已包含换行或按约定追加换行)
|
||||
virtual void writeLine(const std::string& line) = 0;
|
||||
|
||||
// 刷新缓冲(可选实现)
|
||||
virtual void flush() {}
|
||||
};
|
||||
|
||||
/* ========================= 控制台输出 Sink ========================= */
|
||||
// 作用:把日志写入指定输出流(默认用 std::cout)
|
||||
class ConsoleSink : public ILogSink
|
||||
{
|
||||
public:
|
||||
// 绑定一个输出流引用(常见用法:std::cout)
|
||||
explicit ConsoleSink(std::ostream& os) : out(os) {}
|
||||
|
||||
const char* name() const override { return "console"; }
|
||||
|
||||
// 写入一行(不自动追加换行,换行由上层统一拼接)
|
||||
void writeLine(const std::string& line) override { out << line; }
|
||||
|
||||
// 立即 flush(当 autoFlush=true 时由 SxLogger 调用)
|
||||
void flush() override { out.flush(); }
|
||||
|
||||
private:
|
||||
std::ostream& out; // 输出流引用(不负责生命周期)
|
||||
};
|
||||
|
||||
/* ========================= 文件输出 Sink ========================= */
|
||||
// 作用:把日志写入文件,支持按字节阈值滚动
|
||||
class FileSink : public ILogSink
|
||||
{
|
||||
public:
|
||||
FileSink() = default;
|
||||
|
||||
const char* name() const override { return "file"; }
|
||||
|
||||
// 打开文件
|
||||
// path : 文件路径
|
||||
// append : true 追加写;false 清空重写
|
||||
bool open(const std::string& path, bool append);
|
||||
|
||||
// 关闭文件(安全可重复调用)
|
||||
void close();
|
||||
|
||||
// 查询文件是否处于打开状态
|
||||
bool isOpen() const;
|
||||
|
||||
// 设置滚动阈值(字节)
|
||||
// bytes = 0 表示不滚动
|
||||
void setRotateBytes(std::size_t bytes) { rotateBytes = bytes; }
|
||||
|
||||
// 写入一行,并在需要时触发滚动
|
||||
void writeLine(const std::string& line) override;
|
||||
|
||||
// flush 文件缓冲
|
||||
void flush() override;
|
||||
|
||||
private:
|
||||
// 检查并执行滚动
|
||||
// 返回值:是否发生滚动(或是否重新打开)
|
||||
bool rotateIfNeeded();
|
||||
|
||||
std::ofstream ofs; // 文件输出流
|
||||
std::string filePath; // 当前文件路径
|
||||
bool appendMode = true; // 是否追加模式(用于 reopen)
|
||||
std::size_t rotateBytes = 0; // 滚动阈值
|
||||
};
|
||||
|
||||
/* ========================= 日志中心 SxLogger ========================= */
|
||||
// 作用:
|
||||
// - 保存配置(SxLogConfig)
|
||||
// - 过滤(level/tag/sink enabled)
|
||||
// - 格式化前缀(时间/级别/tag/线程/源码位置)
|
||||
// - 分发到 console/file 等 sink
|
||||
class SxLogger
|
||||
{
|
||||
public:
|
||||
// 仅用于 Windows 控制台:把 codepage 切到 GBK,解决中文乱码。
|
||||
// 不使用 WinAPI:内部通过 system("chcp 936") 实现
|
||||
// 注意:这只影响终端解释输出字节的方式,不影响源码文件编码
|
||||
static void setGBK();
|
||||
|
||||
// 获取全局单例
|
||||
// 说明:函数内静态对象,C++11 起保证线程安全初始化
|
||||
static SxLogger& Get();
|
||||
|
||||
// 设置最低输出级别
|
||||
void setMinLevel(SxLogLevel level);
|
||||
|
||||
// 获取最低输出级别
|
||||
SxLogLevel getMinLevel() const;
|
||||
|
||||
// 设置语言(用于 SX_T 选择)
|
||||
void setLanguage(SxLogLanguage lang);
|
||||
|
||||
// 获取当前语言
|
||||
SxLogLanguage getLanguage() const;
|
||||
|
||||
// 设置 Tag 过滤
|
||||
// mode: None/Whitelist/Blacklist
|
||||
// tags: 过滤列表(精确匹配)
|
||||
void setTagFilter(SxTagFilterMode mode, const std::vector<std::string>& tags);
|
||||
|
||||
// 清空 Tag 过滤(恢复 None)
|
||||
void clearTagFilter();
|
||||
|
||||
// 开关控制台输出
|
||||
void enableConsole(bool enable);
|
||||
|
||||
// 开启文件输出
|
||||
// path : 文件路径
|
||||
// append : 追加写/清空写
|
||||
// rotateBytes: 滚动阈值(0 不滚动)
|
||||
// 返回值:是否打开成功
|
||||
bool enableFile(const std::string& path, bool append = true, std::size_t rotateBytes = 0);
|
||||
|
||||
// 关闭文件输出(不影响控制台输出)
|
||||
void disableFile();
|
||||
|
||||
// 快速判定是否需要输出(宏层面的短路依赖它)
|
||||
// 说明:
|
||||
// - shouldLog 一定要“副作用为 0”
|
||||
// - 若返回 false,调用端不会创建 SxLogLine,也不会拼接字符串
|
||||
bool shouldLog(SxLogLevel level, const char* tag) const;
|
||||
|
||||
// 输出一条完整日志
|
||||
// 说明:这是统一出口,SxLogLine 析构最终会走到这里
|
||||
void logLine(
|
||||
SxLogLevel level,
|
||||
const char* tag,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* func,
|
||||
const std::string& msg);
|
||||
|
||||
// 获取配置副本(避免外部直接改内部 cfg)
|
||||
SxLogConfig getConfigCopy() const;
|
||||
|
||||
// 批量设置配置(整体替换)
|
||||
void setConfig(const SxLogConfig& cfg);
|
||||
|
||||
// 工具:把级别转为字符串(用于前缀)
|
||||
static const char* levelToString(SxLogLevel level);
|
||||
|
||||
// 工具:生成本地时间戳字符串(用于前缀与文件滚动名)
|
||||
static std::string makeTimestampLocal();
|
||||
|
||||
private:
|
||||
SxLogger();
|
||||
|
||||
// 判断 tag 是否允许输出(根据 Tag 过滤模式与 tagList)
|
||||
static bool tagAllowed(const SxLogConfig& cfg, const char* tag);
|
||||
|
||||
// 生成前缀(调用方需已持有锁)
|
||||
std::string formatPrefixUnlocked(
|
||||
const SxLogConfig& cfg,
|
||||
SxLogLevel level,
|
||||
const char* tag,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* func) const;
|
||||
|
||||
mutable std::mutex mtx; // 保护 cfg 与 sink 写入,确保多线程行级一致性
|
||||
SxLogConfig cfg; // 当前配置
|
||||
std::atomic<SxLogLanguage> lang; // 语言开关(仅影响 SX_T 选择)
|
||||
|
||||
std::unique_ptr<ConsoleSink> consoleSink; // 控制台 sink(enableConsole 控制)
|
||||
std::unique_ptr<FileSink> fileSink; // 文件 sink(enableFile 控制)
|
||||
};
|
||||
|
||||
/* ========================= 双语选择辅助 ========================= */
|
||||
// 说明:
|
||||
// - 只做“选择 zhCN 或 enUS”,不做编码转换
|
||||
// - 输出显示是否正常由终端环境决定
|
||||
inline const char* SxT(const char* zhCN, const char* enUS)
|
||||
{
|
||||
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN) ? zhCN : enUS;
|
||||
}
|
||||
|
||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||
// 说明:
|
||||
// - C++20 的 u8"xxx" 是 char8_t*,为了兼容调用端,这里提供重载
|
||||
// - reinterpret_cast 只是改指针类型,不做 UTF-8 -> GBK 转码
|
||||
inline const char* SxT(const char8_t* zhCN, const char* enUS)
|
||||
{
|
||||
return (SxLogger::Get().getLanguage() == SxLogLanguage::ZhCN)
|
||||
? reinterpret_cast<const char*>(zhCN)
|
||||
: enUS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ========================= RAII 日志行对象 ========================= */
|
||||
// 作用:
|
||||
// - 构造时记录 level/tag/源码位置
|
||||
// - operator<< 拼接内容
|
||||
// - 析构时统一提交给 SxLogger::logLine 输出
|
||||
//
|
||||
// 设计意义:
|
||||
// - 避免调用端忘记写换行
|
||||
// - 保证一行日志作为整体写出
|
||||
class SxLogLine
|
||||
{
|
||||
public:
|
||||
// 构造:记录元信息(不输出)
|
||||
SxLogLine(SxLogLevel level, const char* tag, const char* file, int line, const char* func);
|
||||
|
||||
// 析构:提交输出(真正写出发生在这里)
|
||||
~SxLogLine();
|
||||
|
||||
// 拼接内容(流式写法)
|
||||
template<typename T>
|
||||
SxLogLine& operator<<(const T& v)
|
||||
{
|
||||
ss << v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SxLogLevel lvl; // 日志级别
|
||||
const char* tg; // Tag(不拥有内存)
|
||||
const char* srcFile; // 源文件名(来自 __FILE__)
|
||||
int srcLine; // 行号(来自 __LINE__)
|
||||
const char* srcFunc; // 函数名(来自 __func__)
|
||||
std::ostringstream ss; // 内容拼接缓冲
|
||||
};
|
||||
|
||||
/* ========================= RAII 作用域计时对象 ========================= */
|
||||
// 作用:
|
||||
// - 仅在 shouldLog(Trace, tag) 为 true 时启用计时
|
||||
// - 析构时输出耗时(微秒)
|
||||
//
|
||||
// 使用建议:
|
||||
// - 只在需要定位性能瓶颈时开启 Trace
|
||||
// - name 建议传入常量字符串,便于检索
|
||||
class SxLogScope
|
||||
{
|
||||
public:
|
||||
// 构造:根据 shouldLog 决定是否启用计时
|
||||
SxLogScope(SxLogLevel level, const char* tag, const char* file, int line, const char* func, const char* name);
|
||||
|
||||
// 析构:若启用则输出耗时
|
||||
~SxLogScope();
|
||||
|
||||
private:
|
||||
bool enabled = false; // 是否启用(未启用则析构无输出)
|
||||
SxLogLevel lvl = SxLogLevel::Trace; // 级别(通常用 Trace)
|
||||
const char* tg = nullptr; // Tag
|
||||
const char* srcFile = nullptr; // 源文件
|
||||
int srcLine = 0; // 行号
|
||||
const char* srcFunc = nullptr; // 函数
|
||||
const char* scopeName = nullptr; // 作用域名
|
||||
std::chrono::steady_clock::time_point t0; // 起始时间点
|
||||
};
|
||||
|
||||
} // namespace StellarX
|
||||
|
||||
#if SX_LOG_ENABLE
|
||||
|
||||
// SX_T:双语选择宏,调用 SxT 根据当前语言选择输出
|
||||
#define SX_T(zh, en) ::StellarX::SxT(zh, en)
|
||||
|
||||
// 日志宏说明:
|
||||
// 1) 先 shouldLog 短路过滤,未命中则不会构造 SxLogLine,也不会执行 else 分支的表达式
|
||||
// 2) 命中则构造临时 SxLogLine,并允许继续使用 operator<< 拼接
|
||||
// 3) 语句结束时临时对象析构,触发真正输出
|
||||
#define SX_LOG_TRACE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Trace, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__)
|
||||
#define SX_LOGD(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Debug, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Debug, tag, __FILE__, __LINE__, __func__)
|
||||
#define SX_LOGI(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Info, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Info, tag, __FILE__, __LINE__, __func__)
|
||||
#define SX_LOGW(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Warn, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Warn, tag, __FILE__, __LINE__, __func__)
|
||||
#define SX_LOGE(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Error, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Error, tag, __FILE__, __LINE__, __func__)
|
||||
#define SX_LOGF(tag) if(!::StellarX::SxLogger::Get().shouldLog(::StellarX::SxLogLevel::Fatal, tag)) ; else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Fatal, tag, __FILE__, __LINE__, __func__)
|
||||
|
||||
// 作用域耗时统计宏:默认用 Trace 级别
|
||||
#define SX_TRACE_SCOPE(tag, nameLiteral) ::StellarX::SxLogScope sx_scope_##__LINE__(::StellarX::SxLogLevel::Trace, tag, __FILE__, __LINE__, __func__, nameLiteral)
|
||||
|
||||
#else
|
||||
|
||||
// 关闭日志时的兼容宏:保证调用端代码不需要改动
|
||||
#define SX_T(zh, en) (en)
|
||||
#define SX_LOG_TRACE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_LOGD(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_LOGI(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_LOGW(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_LOGE(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_LOGF(tag) if(true) {} else ::StellarX::SxLogLine(::StellarX::SxLogLevel::Off, tag, "", 0, "")
|
||||
#define SX_TRACE_SCOPE(tag, nameLiteral) do {} while(0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* @类: TabControl
|
||||
* @摘要: 选项卡容器控件,管理“页签按钮 + 对应页面(Canvas)”
|
||||
* @描述:
|
||||
* 提供页签栏布局(上/下/左/右)、选中切换、页内容区域定位;
|
||||
* 与 Button 一起工作,支持窗口大小变化、可见性联动与脏区重绘。
|
||||
*
|
||||
* @特性:
|
||||
* - 页签栏四向排列(Top / Bottom / Left / Right)
|
||||
* - 一键添加“页签+页”或为指定页添加子控件
|
||||
* - 获取/设置当前激活页签索引
|
||||
* - 自适应窗口变化,重算页签与页面区域
|
||||
* - 与 Button 的 TOGGLE 模式联动显示/隐藏页面
|
||||
*
|
||||
* @使用场景: 在同一区域内承载多张页面,使用页签进行快速切换
|
||||
* @所属框架: 星垣(StellarX) GUI框架
|
||||
* @作者: 我在人间做废物
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "CoreTypes.h"
|
||||
#include "Button.h"
|
||||
#include "Canvas.h"
|
||||
#define BUTMINHEIGHT 15 //页签按钮最小尺寸,过小会导致显示问题
|
||||
#define BUTMINWIDTH 30 //页签按钮最小尺寸,过小会导致显示问题
|
||||
class TabControl :public Canvas
|
||||
{
|
||||
int tabBarHeight = BUTMINWIDTH; //页签栏高度
|
||||
bool IsFirstDraw = true; //首次绘制标记
|
||||
int defaultActivation = -1; //默认激活页签索引
|
||||
StellarX::TabPlacement tabPlacement = StellarX::TabPlacement::Top; //页签排列方式
|
||||
std::vector<std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>> controls; //页签/页列表
|
||||
|
||||
private:
|
||||
using Canvas::addControl; // 禁止外部误用
|
||||
void addControl(std::unique_ptr<Control>) = delete; // 精准禁用该重载
|
||||
private:
|
||||
// 初始化页签按钮位置和尺寸
|
||||
inline void initTabBar();
|
||||
inline void initTabPage();
|
||||
public:
|
||||
TabControl();
|
||||
TabControl(int x, int y, int width, int height);
|
||||
~TabControl();
|
||||
|
||||
//重写位置设置以适应页签和页面布局
|
||||
void setX(int x)override;
|
||||
void setY(int y)override;
|
||||
|
||||
void draw() 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;
|
||||
};
|
||||
@@ -22,12 +22,9 @@
|
||||
class Label : public Control
|
||||
{
|
||||
std::string text; //标签文本
|
||||
COLORREF textColor; //标签文本颜色
|
||||
COLORREF textBkColor; //标签背景颜色
|
||||
bool textBkDisap = false; //标签背景是否透明
|
||||
|
||||
|
||||
|
||||
//标签事件处理(标签无事件)不实现具体代码
|
||||
bool handleEvent(const ExMessage& msg) override { return false; }
|
||||
//用来检查对话框是否模态,此控件不做实现
|
||||
@@ -42,12 +39,8 @@ public:
|
||||
void hide();
|
||||
//设置标签背景是否透明
|
||||
void setTextdisap(bool key);
|
||||
//设置标签文本颜色
|
||||
void setTextColor(COLORREF color);
|
||||
//设置标签背景颜色
|
||||
void setTextBkColor(COLORREF color);
|
||||
//设置标签文本
|
||||
void setText(std::string text);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,33 @@
|
||||
#include "Button.h"
|
||||
#include "Label.h"
|
||||
|
||||
// === Table metrics (layout) ===
|
||||
#define TABLE_PAD_X 10 // 单元格左右内边距
|
||||
#define TABLE_PAD_Y 5 // 单元格上下内边距
|
||||
#define TABLE_COL_GAP 20 // 列间距(列与列之间)
|
||||
#define TABLE_HEADER_EXTRA 10 // 表头额外高度(若不想复用 pad 计算)
|
||||
#define TABLE_ROW_EXTRA 10 // 行额外高度(同上;或直接用 2*TABLE_PAD_Y)
|
||||
#define TABLE_BTN_GAP 12 // 页码与按钮的水平间距
|
||||
#define TABLE_BTN_PAD_H 12 // 按钮水平 padding
|
||||
#define TABLE_BTN_PAD_V 0 // 按钮垂直 padding(initButton)
|
||||
#define TABLE_BTN_TEXT_PAD_V 8 // 计算页脚高度时的按钮文字垂直 padding(initTextWaH)
|
||||
#define TABLE_FOOTER_PAD 16 // 页脚额外高度(底部留白)
|
||||
#define TABLE_FOOTER_BLANK 8 // 页脚顶部留白
|
||||
#define TABLE_PAGE_TEXT_OFFSET_X (-40) // 页码文本的临时水平修正
|
||||
|
||||
// === Table defaults (theme) ===
|
||||
#define TABLE_DEFAULT_ROWS_PER_PAGE 5
|
||||
#define TABLE_DEFAULT_BORDER_WIDTH 1
|
||||
#define TABLE_DEFAULT_BORDER_COLOR RGB(0,0,0)
|
||||
#define TABLE_DEFAULT_BG_COLOR RGB(255,255,255)
|
||||
|
||||
// === Strings (i18n ready) ===
|
||||
#define TABLE_STR_PREV "上一页"
|
||||
#define TABLE_STR_NEXT "下一页"
|
||||
#define TABLE_STR_PAGE_PREFIX "第"
|
||||
#define TABLE_STR_PAGE_MID "页/共"
|
||||
#define TABLE_STR_PAGE_SUFFIX "页"
|
||||
|
||||
class Table :public Control
|
||||
{
|
||||
private:
|
||||
@@ -29,18 +56,19 @@ private:
|
||||
std::vector<std::string> headers; // 表格表头
|
||||
std::string pageNumtext = "页码标签"; // 页码标签文本
|
||||
|
||||
int tableBorderWidth = 1; // 边框宽度
|
||||
int tableBorderWidth = TABLE_DEFAULT_BORDER_WIDTH; // 边框宽度
|
||||
|
||||
std::vector<int> colWidths; // 每列的宽度
|
||||
std::vector<int> lineHeights; // 每行的高度
|
||||
|
||||
int rowsPerPage = 5; // 每页显示的行数
|
||||
int rowsPerPage = TABLE_DEFAULT_ROWS_PER_PAGE; // 每页显示的行数
|
||||
int currentPage = 1; // 当前页码
|
||||
int totalPages = 1; // 总页数
|
||||
|
||||
bool isShowPageButton = true; // 是否显示翻页按钮
|
||||
bool isNeedDrawHeaders = true; // 是否需要绘制表头
|
||||
bool isNeedDrawHeaders = true; // 是否需要绘制表头(暂时废弃,单做保留,后期优化可能用到)
|
||||
bool isNeedCellSize = true; // 是否需要计算单元格尺寸
|
||||
bool isNeedButtonAndPageNum = true; // 是否需要计算翻页按钮和页码信息
|
||||
|
||||
Button* prevButton = nullptr; // 上一页按钮
|
||||
Button* nextButton = nullptr; // 下一页按钮
|
||||
@@ -54,8 +82,8 @@ private:
|
||||
|
||||
StellarX::FillMode tableFillMode = StellarX::FillMode::Solid; //填充模式
|
||||
StellarX::LineStyle tableLineStyle = StellarX::LineStyle::Solid; // 线型
|
||||
COLORREF tableBorderClor = RGB(0, 0, 0); // 表格边框颜色
|
||||
COLORREF tableBkClor = RGB(255, 255, 255); // 表格背景颜色
|
||||
COLORREF tableBorderClor = TABLE_DEFAULT_BORDER_COLOR; // 表格边框颜色
|
||||
COLORREF tableBkClor = TABLE_DEFAULT_BG_COLOR; // 表格背景颜色
|
||||
|
||||
void initTextWaH(); //初始化文本像素宽度和高度
|
||||
void initButton(); //初始化翻页按钮
|
||||
@@ -70,7 +98,10 @@ private:
|
||||
bool model() const override { return false; };
|
||||
public:
|
||||
StellarX::ControlText textStyle; // 文本样式
|
||||
|
||||
void setX(int x) override;
|
||||
void setY(int y) override;
|
||||
void setWidth(int width) override;
|
||||
void setHeight(int height) override;
|
||||
public:
|
||||
Table(int x, int y);
|
||||
~Table();
|
||||
@@ -99,6 +130,14 @@ public:
|
||||
void setTableLineStyle(StellarX::LineStyle style);
|
||||
//设置边框宽度
|
||||
void setTableBorderWidth(int width);
|
||||
//清空表头
|
||||
void clearHeaders();
|
||||
//清空表格数据
|
||||
void clearData();
|
||||
//清空表头和数据
|
||||
void resetTable();
|
||||
//窗口变化丢快照+标脏
|
||||
void onWindowResize() override;
|
||||
|
||||
//************************** 获取属性 *****************************/
|
||||
|
||||
@@ -124,7 +163,7 @@ public:
|
||||
std::vector<std::vector<std::string>> getData() const;
|
||||
//获取表格边框宽度
|
||||
int getTableBorderWidth() const;
|
||||
|
||||
|
||||
//获取表格尺寸
|
||||
int getTableWidth() const;
|
||||
int getTableHeight() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#pragma once
|
||||
#include "Control.h"
|
||||
|
||||
|
||||
class TextBox : public Control
|
||||
{
|
||||
std::string text; //文本
|
||||
@@ -55,5 +54,3 @@ private:
|
||||
//用来检查对话框是否模态,此控件不做实现
|
||||
bool model() const override { return false; };
|
||||
};
|
||||
|
||||
|
||||
|
||||
+69
-55
@@ -1,84 +1,98 @@
|
||||
|
||||
/**
|
||||
* Window(头文件)
|
||||
*
|
||||
* 设计目标:
|
||||
* - 提供一个基于 Win32 + EasyX 的“可拉伸且稳定不抖”的窗口容器。
|
||||
* - 通过消息过程子类化(WndProcThunk)接管关键消息(WM_SIZING/WM_SIZE/...)。
|
||||
* - 将“几何变化记录(pendingW/H)”与“统一收口重绘(needResizeDirty)”解耦。
|
||||
*
|
||||
* 关键点(与 .cpp 中实现对应):
|
||||
* - isSizing:处于交互拉伸阶段时,冻结重绘;松手后统一重绘,防止抖动。
|
||||
* - WM_SIZING:只做“最小尺寸夹紧”,不回滚矩形、不做对齐;把其余交给系统。
|
||||
* - WM_GETMINMAXINFO:按最小“客户区”换算到“窗口矩形”,提供系统层最小轨迹值。
|
||||
* - runEventLoop:只记录 WM_SIZE 的新尺寸;真正绘制放在 needResizeDirty 时集中处理。
|
||||
*/
|
||||
//fuck windows
|
||||
//fuck win32
|
||||
//fuck xiaomi
|
||||
#pragma once
|
||||
|
||||
#include "Control.h"
|
||||
/*******************************************************************************
|
||||
* @类: Window
|
||||
* @摘要: 应用程序主窗口类,管理窗口生命周期和消息循环
|
||||
* @描述:
|
||||
* 创建和管理应用程序主窗口,作为所有控件的根容器。
|
||||
* 处理消息分发、事件循环和渲染调度。
|
||||
*
|
||||
* @特性:
|
||||
* - 多种窗口模式配置(双缓冲、控制台等)
|
||||
* - 背景图片和颜色支持
|
||||
* - 集成的对话框管理系统
|
||||
* - 完整的消息处理循环
|
||||
* - 控件和对话框的生命周期管理
|
||||
*
|
||||
* @使用场景: 应用程序主窗口,GUI程序的入口和核心
|
||||
* @所属框架: 星垣(StellarX) GUI框架
|
||||
* @作者: 我在人间做废物
|
||||
******************************************************************************/
|
||||
#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; // 项目内使用的状态位,对话框关闭标志
|
||||
mutable bool dialogOpen = false; // 项目内使用的状态位,对话框打开标志
|
||||
|
||||
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 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(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
||||
~Window();
|
||||
//绘制窗口
|
||||
void draw();
|
||||
void draw(std::string pImgFile);
|
||||
//事件循环
|
||||
int runEventLoop();
|
||||
//设置窗口背景图片
|
||||
|
||||
// —— 绘制与事件循环 ——(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();
|
||||
|
||||
// —— 尺寸调整 ——(供内部与外部调用的尺寸变化处理)
|
||||
void pumpResizeIfNeeded(); // 执行一次统一收口重绘
|
||||
void scheduleResizeFromModal(int w, int h);
|
||||
private:
|
||||
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+49
-32
@@ -1,4 +1,5 @@
|
||||
#include "Button.h"
|
||||
#include "SxLog.h"
|
||||
|
||||
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
|
||||
: Control(x, y, width, height)
|
||||
@@ -128,8 +129,23 @@ static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const c
|
||||
return head;
|
||||
}
|
||||
|
||||
void Button::setTooltipStyle(COLORREF text, COLORREF bk, bool transparent)
|
||||
{
|
||||
tipLabel.textStyle.color = text;
|
||||
tipLabel.setTextBkColor(bk);
|
||||
tipLabel.setTextdisap(transparent);
|
||||
}
|
||||
|
||||
void Button::setTooltipTextsForToggle(const std::string& onText, const std::string& offText)
|
||||
{
|
||||
tipTextOn = onText;
|
||||
tipTextOff = offText;
|
||||
tipUserOverride = true;
|
||||
}
|
||||
|
||||
void Button::initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch)
|
||||
{
|
||||
this->id = "Button";
|
||||
this->text = text;
|
||||
this->mode = mode;
|
||||
this->shape = shape;
|
||||
@@ -142,13 +158,12 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
|
||||
// === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 ===
|
||||
tipTextClick = tipTextOn = tipTextOff = this->text;
|
||||
tipLabel.setText(tipTextClick);
|
||||
tipLabel.setTextColor(RGB(167, 170, 172));
|
||||
tipLabel.textStyle.color = (RGB(167, 170, 172));
|
||||
tipLabel.setTextBkColor(RGB(255, 255, 255));
|
||||
tipLabel.setTextdisap(false);
|
||||
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
||||
}
|
||||
|
||||
|
||||
Button::~Button()
|
||||
{
|
||||
if (buttonFileIMAGE)
|
||||
@@ -158,8 +173,8 @@ Button::~Button()
|
||||
|
||||
void Button::draw()
|
||||
{
|
||||
if (dirty && show)
|
||||
{
|
||||
if (!dirty || !show)return;
|
||||
|
||||
//保存当前样式和颜色
|
||||
saveStyle();
|
||||
|
||||
@@ -208,6 +223,10 @@ void Button::draw()
|
||||
|
||||
//设置按钮填充模式
|
||||
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
||||
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
||||
saveBackground(this->x, this->y, (this->width + bordWith), (this->height + bordHeight));
|
||||
// 恢复背景(清除旧内容)
|
||||
restBackground();
|
||||
//根据按钮形状绘制
|
||||
switch (shape)
|
||||
{
|
||||
@@ -255,8 +274,6 @@ void Button::draw()
|
||||
|
||||
restoreStyle();//恢复默认字体样式和颜色
|
||||
dirty = false; //标记按钮不需要重绘
|
||||
|
||||
}
|
||||
}
|
||||
// 处理鼠标事件,检测点击和悬停状态
|
||||
// 根据按钮模式和形状进行不同的处理
|
||||
@@ -265,8 +282,9 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
if (!show)
|
||||
return false;
|
||||
|
||||
bool oldHover = hover;
|
||||
bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏
|
||||
bool oldClick = click;
|
||||
|
||||
bool consume = false;//是否消耗事件
|
||||
// 记录鼠标位置(用于tip定位)
|
||||
if (msg.message == WM_MOUSEMOVE)
|
||||
@@ -292,14 +310,20 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
hover = isMouseInEllipse(msg.x, msg.y, x, y, x + width, y + height);
|
||||
break;
|
||||
}
|
||||
|
||||
if (hover != oldHover)
|
||||
{
|
||||
SX_LOGD("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id
|
||||
<< " text= " << text
|
||||
<< " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0);
|
||||
}
|
||||
// 处理鼠标点击事件
|
||||
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
|
||||
if (mode == StellarX::ButtonMode::NORMAL)
|
||||
{
|
||||
click = true;
|
||||
SX_LOGD("Button") << SX_T("被点击: ","lbtn - down:")<< "id = " << id <<" text = "<<text << " mode = " << (int)mode;
|
||||
|
||||
dirty = true;
|
||||
consume = true;
|
||||
}
|
||||
@@ -316,6 +340,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||
{
|
||||
if (onClickCallback) onClickCallback();
|
||||
SX_LOGI("Button") << "click: id=" << id << " (NORMAL) callback=" << (onClickCallback ? "Y" : "N");
|
||||
|
||||
click = false;
|
||||
dirty = true;
|
||||
consume = true;
|
||||
@@ -328,6 +354,11 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
click = !click;
|
||||
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||
SX_LOGI("Button") << "toggle: id=" << id
|
||||
<< " " << (oldClick ? 1 : 0) << "->" << (click ? 1 : 0)
|
||||
<< " onCb=" << (onToggleOnCallback ? "Y" : "N")
|
||||
<< " offCb=" << (onToggleOffCallback ? "Y" : "N");
|
||||
|
||||
dirty = true;
|
||||
consume = true;
|
||||
refreshTooltipTextForState();
|
||||
@@ -367,6 +398,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
// 到点就显示
|
||||
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
||||
{
|
||||
SX_LOGD("Button") << SX_T("提示信息显示: ","tooltip show:")<<" id = " << id <<SX_T("延时时间: ", " delayMs = ") << tipDelayMs;
|
||||
|
||||
tipVisible = true;
|
||||
|
||||
// 定位(跟随鼠标 or 相对按钮)
|
||||
@@ -398,9 +431,11 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
|
||||
// 如果需要重绘,立即执行
|
||||
if (dirty)
|
||||
draw();
|
||||
requestRepaint(parent);
|
||||
|
||||
if (tipEnabled && tipVisible)
|
||||
tipLabel.draw();
|
||||
|
||||
return consume;
|
||||
}
|
||||
|
||||
@@ -420,15 +455,17 @@ void Button::setOnToggleOffListener(const std::function<void()>&& callback)
|
||||
|
||||
void Button::setbuttonMode(StellarX::ButtonMode mode)
|
||||
{
|
||||
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
|
||||
textStyle.bStrikeOut = false;
|
||||
//取值范围参考 buttMode的枚举注释
|
||||
this->mode = mode;
|
||||
dirty = true; // 标记需要重绘
|
||||
}
|
||||
|
||||
void Button::setROUND_RECTANGLEwidth(int width)
|
||||
{
|
||||
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||
this->dirty = true; // 标记需要重绘
|
||||
|
||||
}
|
||||
|
||||
void Button::setROUND_RECTANGLEheight(int height)
|
||||
@@ -466,7 +503,6 @@ void Button::setFillIma(std::string imaNAme)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Button::setButtonBorder(COLORREF Border)
|
||||
{
|
||||
buttonBorderColor = Border;
|
||||
@@ -532,10 +568,9 @@ void Button::setButtonClick(BOOL click)
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
if (dirty)
|
||||
draw();
|
||||
requestRepaint(parent);
|
||||
}
|
||||
|
||||
|
||||
std::string Button::getButtonText() const
|
||||
{
|
||||
return this->text;
|
||||
@@ -586,18 +621,6 @@ StellarX::ControlText Button::getButtonTextStyle() const
|
||||
return this->textStyle;
|
||||
}
|
||||
|
||||
int Button::getButtonWidth() const
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int Button::getButtonHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
||||
{
|
||||
double dis = sqrt(pow(mouseX - x, 2) + pow(mouseY - y, 2));
|
||||
@@ -643,11 +666,9 @@ void Button::cutButtonText()
|
||||
else
|
||||
{
|
||||
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
||||
|
||||
}
|
||||
isUseCutText = true;
|
||||
needCutText = false;
|
||||
|
||||
}
|
||||
|
||||
void Button::hideTooltip()
|
||||
@@ -662,13 +683,9 @@ void Button::hideTooltip()
|
||||
|
||||
void Button::refreshTooltipTextForState()
|
||||
{
|
||||
if (!tipUserOverride) return;
|
||||
if (tipUserOverride) return; // 用户显式设置过 tipText,保持不变
|
||||
if (mode == StellarX::ButtonMode::NORMAL)
|
||||
tipLabel.setText(tipTextClick);
|
||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+334
-9
@@ -1,27 +1,88 @@
|
||||
#include "Canvas.h"
|
||||
#include "SxLog.h"
|
||||
|
||||
static bool SxIsNoisyMsg(UINT m)
|
||||
{
|
||||
return m == WM_MOUSEMOVE;
|
||||
}
|
||||
|
||||
Canvas::Canvas()
|
||||
:Control(0, 0, 100, 100) {}
|
||||
:Control(0, 0, 100, 100)
|
||||
{
|
||||
this->id = "Canvas";
|
||||
}
|
||||
|
||||
Canvas::Canvas(int x, int y, int width, int height)
|
||||
:Control(x, y, width, height) {}
|
||||
:Control(x, y, width, height)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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);//设置线色
|
||||
if (StellarX::FillMode::Null != canvasFillMode)
|
||||
setfillcolor(canvasBkClor);//设置填充色
|
||||
setfillstyle((int)canvasFillMode);//设置填充模式
|
||||
setlinestyle((int)canvasLineStyle, canvaslinewidth);
|
||||
|
||||
// 在绘制画布之前,先恢复并更新背景快照:
|
||||
// 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();
|
||||
//根据画布形状绘制
|
||||
switch (shape)
|
||||
{
|
||||
@@ -53,14 +114,55 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show) return false;
|
||||
|
||||
bool consumed = false;
|
||||
bool anyDirty = false;
|
||||
Control* firstConsumer = nullptr;
|
||||
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
if ((*it)->handleEvent(msg))
|
||||
return true; // 事件被消费短路传递,立即返回true 否则返回false
|
||||
return false;
|
||||
{
|
||||
Control* c = it->get();
|
||||
bool cConsumed = c->handleEvent(msg);
|
||||
|
||||
if (cConsumed && !firstConsumer) firstConsumer = c;
|
||||
consumed |= cConsumed;
|
||||
|
||||
if (c->isDirty()) anyDirty = true;
|
||||
}
|
||||
|
||||
if (firstConsumer && !SxIsNoisyMsg(msg.message))
|
||||
{
|
||||
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message
|
||||
<< SX_T("子控件"," by child")<<" id=" << firstConsumer->getId();
|
||||
}
|
||||
|
||||
if (anyDirty)
|
||||
{
|
||||
if (!SxIsNoisyMsg(msg.message))
|
||||
SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id;
|
||||
requestRepaint(parent);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||
{
|
||||
|
||||
//坐标转化
|
||||
control->setX(control->getLocalX() + this->x);
|
||||
control->setY(control->getLocalY() + this->y);
|
||||
control->setParent(this);
|
||||
SX_LOGI("Canvas")
|
||||
<< SX_T("添加子控件:父=Canvas 子id=", "addControl: parent=Canvas childId=")
|
||||
<< control->getId()
|
||||
<< SX_T(" 相对坐标=(", " local=(")
|
||||
<< control->getLocalX() << "," << control->getLocalY()
|
||||
<< SX_T(") 绝对坐标=(", ") abs=(")
|
||||
<< control->getX() << "," << control->getY()
|
||||
<< ")";
|
||||
|
||||
|
||||
controls.push_back(std::move(control));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -110,12 +212,235 @@ void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Canvas::setLinewidth(int width)
|
||||
{
|
||||
this->canvaslinewidth = width;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Canvas::setIsVisible(bool visible)
|
||||
{
|
||||
this->show = visible;
|
||||
dirty = true;
|
||||
for (auto& control : controls)
|
||||
{
|
||||
control->setIsVisible(visible);
|
||||
}
|
||||
if (!visible)
|
||||
this->updateBackground();
|
||||
}
|
||||
|
||||
void Canvas::setDirty(bool dirty)
|
||||
{
|
||||
this->dirty = dirty;
|
||||
for (auto& control : controls)
|
||||
control->setDirty(dirty);
|
||||
}
|
||||
|
||||
void Canvas::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)
|
||||
{
|
||||
if (this == parent)
|
||||
{
|
||||
if (!show)
|
||||
return;
|
||||
|
||||
// 关键护栏:
|
||||
// - Canvas 自己是脏的 / 没有快照 / 缓存图为空
|
||||
// => 禁止局部重绘,直接升级为一次完整 draw(先把 dirty 置真,避免 draw() 早退)
|
||||
if (dirty || !hasSnap || !saveBkImage)
|
||||
{
|
||||
SX_LOGD("Dirty")
|
||||
<< SX_T("Canvas 局部重绘降级为全量重绘: id=", "Canvas partial->full draw: id=")
|
||||
<< id
|
||||
<< " dirty=" << (dirty ? 1 : 0)
|
||||
<< " hasSnap=" << (hasSnap ? 1 : 0);
|
||||
|
||||
this->dirty = true;
|
||||
this->draw();
|
||||
return;
|
||||
}
|
||||
|
||||
SX_LOGD("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
|
||||
|
||||
for (auto& control : controls)
|
||||
if (control->isDirty() && control->IsVisible())
|
||||
control->draw();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SX_LOGD("Dirty") << SX_T("Canvas 请求根级重绘:id=", "Canvas::requestRepaint(root): id=") << id;
|
||||
onRequestRepaintAsRoot();
|
||||
}
|
||||
|
||||
|
||||
+101
-7
@@ -1,4 +1,5 @@
|
||||
#include "Control.h"
|
||||
#include "SxLog.h"
|
||||
#include<assert.h>
|
||||
|
||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||
@@ -42,6 +43,61 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
void Control::setIsVisible(bool show)
|
||||
{
|
||||
SX_LOGD("Control") << SX_T("重置可见状态: id=", "setIsVisible: id=")
|
||||
<< id
|
||||
<< " show=" << (show ? 1 : 0);
|
||||
|
||||
if (this->show == show)
|
||||
return;
|
||||
|
||||
this->show = show;
|
||||
this->dirty = true;
|
||||
|
||||
if (!show)
|
||||
{
|
||||
// 隐藏:擦除自己在屏幕上的内容,并释放快照
|
||||
this->updateBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示:不在这里 requestRepaint(避免父容器快照未就绪时子控件抢跑 draw,污染快照)
|
||||
// 仅向上标脏,让事件收口阶段由容器统一重绘。
|
||||
if (parent)
|
||||
parent->setDirty(true);
|
||||
}
|
||||
|
||||
void Control::onWindowResize()
|
||||
{
|
||||
SX_LOGD("Layout") << SX_T("尺寸变化:id=", "onWindowResize: id=") << id
|
||||
<< SX_T(" -> 丢背景快照 + 标脏", " -> discardSnap + dirty");
|
||||
|
||||
// 自己:丢快照 + 标脏
|
||||
discardBackground();
|
||||
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()
|
||||
@@ -65,19 +121,59 @@ void Control::restoreStyle()
|
||||
setfillstyle(BS_SOLID);//恢复填充
|
||||
}
|
||||
|
||||
void Control::requestRepaint(Control* parent)
|
||||
{
|
||||
// 说明:
|
||||
// - 常规路径:子控件调用 requestRepaint(this->parent),然后 parent 负责局部重绘(Canvas/TabControl override)
|
||||
// - 兜底路径:如果某个“容器控件”没 override requestRepaint,就会出现 parent==this 的递归风险
|
||||
// 此时我们改为向更上层冒泡,直到根重绘。
|
||||
if (parent == this)
|
||||
{
|
||||
SX_LOGW("Dirty")
|
||||
<< SX_T("requestRepaint(默认容器兜底):id=", "requestRepaint(default-container-fallback): id=")
|
||||
<< id
|
||||
<< SX_T(",parent==this,向上层 parent 继续冒泡", " parent==this, bubble to upper parent");
|
||||
|
||||
if (this->parent) this->parent->requestRepaint(this->parent);
|
||||
else onRequestRepaintAsRoot();
|
||||
return;
|
||||
}
|
||||
|
||||
SX_LOGD("Dirty") << SX_T("请求重绘:id=","requestRepaint: id=") << id << " parent=" << (parent ? parent->getId() : "null");
|
||||
|
||||
if (parent) parent->requestRepaint(parent); // 交给容器处理(容器可局部重绘)
|
||||
else onRequestRepaintAsRoot(); // 根兜底
|
||||
}
|
||||
|
||||
void Control::onRequestRepaintAsRoot()
|
||||
{
|
||||
SX_LOGI("Dirty")
|
||||
<< SX_T("触发根重绘:id=", "onRequestRepaintAsRoot: id=") << id
|
||||
<< SX_T("(从根节点开始重画)", " (root repaint)");
|
||||
|
||||
|
||||
discardBackground();
|
||||
setDirty(true);
|
||||
draw(); // 只有“无父”时才允许立即画,不会被谁覆盖
|
||||
}
|
||||
|
||||
void Control::saveBackground(int x, int y, int w, int h)
|
||||
{
|
||||
|
||||
if (w <= 0 || h <= 0) return;
|
||||
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
|
||||
|
||||
if (saveBkImage)
|
||||
{
|
||||
//尺寸变了才重建,避免反复 new/delete
|
||||
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||||
{
|
||||
SX_LOGD("Snap") <<SX_T("重新保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||||
|
||||
delete saveBkImage; saveBkImage = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
SX_LOGD("Snap") << SX_T("保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||||
if (!saveBkImage) saveBkImage = new IMAGE(w, h);
|
||||
|
||||
SetWorkingImage(nullptr); // ★抓屏幕
|
||||
@@ -97,6 +193,8 @@ void Control::discardBackground()
|
||||
{
|
||||
if (saveBkImage)
|
||||
{
|
||||
restBackground();
|
||||
SX_LOGD("Snap") << SX_T("丢弃背景快照:id=","discardBackground: id=") << id << " hasSnap=" << (hasSnap ? 1 : 0);
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
}
|
||||
@@ -105,10 +203,6 @@ void Control::discardBackground()
|
||||
|
||||
void Control::updateBackground()
|
||||
{
|
||||
if (saveBkImage)
|
||||
{
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
}
|
||||
hasSnap = false; saveWidth = saveHeight = 0;
|
||||
restBackground();
|
||||
discardBackground();
|
||||
}
|
||||
|
||||
+106
-46
@@ -1,13 +1,13 @@
|
||||
#include "Dialog.h"
|
||||
#include "SxLog.h"
|
||||
|
||||
Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::MessageBoxType type, bool modal)
|
||||
: Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
||||
{
|
||||
this->id = "Dialog";
|
||||
show = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
}
|
||||
@@ -35,20 +35,11 @@ void Dialog::draw()
|
||||
// 保存当前绘图状态
|
||||
saveStyle();
|
||||
|
||||
|
||||
// 保存背景(仅在第一次绘制时)
|
||||
if (saveBkImage == nullptr)
|
||||
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
|
||||
|
||||
Canvas::setBorderColor(this->borderColor);
|
||||
Canvas::setLinewidth(this->BorderWidth);
|
||||
Canvas::setLinewidth(BorderWidth);
|
||||
Canvas::setCanvasBkColor(this->backgroundColor);
|
||||
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
|
||||
//设置所有控件为脏状态
|
||||
for(auto& c :this->controls)
|
||||
c->setDirty(true);
|
||||
|
||||
Canvas::draw();
|
||||
|
||||
//绘制消息文本
|
||||
@@ -59,9 +50,8 @@ void Dialog::draw()
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
|
||||
|
||||
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()));
|
||||
@@ -75,8 +65,6 @@ void Dialog::draw()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Dialog::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
@@ -92,12 +80,12 @@ bool Dialog::handleEvent(const ExMessage& msg)
|
||||
// 如果正在清理或标记为待清理,则不处理事件
|
||||
if (pendingCleanup || isCleaning)
|
||||
return false;
|
||||
// 模态对话框不允许点击外部区域
|
||||
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
|
||||
if (modal && msg.message == WM_LBUTTONUP &&
|
||||
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
|
||||
{
|
||||
std::cout << "\a" << std::endl;
|
||||
// 模态对话框不允许点击外部区域
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -142,7 +130,6 @@ void Dialog::SetModal(bool modal)
|
||||
this->modal = modal;
|
||||
}
|
||||
|
||||
|
||||
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
||||
{
|
||||
this->result = result;
|
||||
@@ -162,6 +149,7 @@ void Dialog::Show()
|
||||
{
|
||||
if (pendingCleanup)
|
||||
performDelayedCleanup();
|
||||
SX_LOGI("Dialog") << SX_T("对话框弹出:是否模态=","Dialog::Show: modal=") << (modal ? 1 : 0);
|
||||
|
||||
show = true;
|
||||
dirty = true;
|
||||
@@ -169,18 +157,49 @@ void Dialog::Show()
|
||||
close = false;
|
||||
shouldClose = false;
|
||||
|
||||
hWnd.dialogOpen = true;// 通知窗口有对话框打开
|
||||
|
||||
if (modal)
|
||||
{
|
||||
// 模态对话框需要阻塞当前线程直到对话框关闭
|
||||
if (modal)
|
||||
{
|
||||
// 记录当前窗口客户区尺寸,供轮询对比
|
||||
RECT rc0;
|
||||
GetClientRect(hWnd.getHwnd(), &rc0);
|
||||
int lastW = rc0.right - rc0.left;
|
||||
int lastH = rc0.bottom - rc0.top;
|
||||
|
||||
while (show && !close)
|
||||
{
|
||||
// 处理消息
|
||||
// ① 轮询窗口尺寸(不依赖 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)
|
||||
{
|
||||
lastW = cw;
|
||||
lastH = ch;
|
||||
SX_LOGD("Resize") <<SX_T("模态对话框检测到窗口大小变化:(", "Modal dialog detected window size change: (") << cw << "x" << 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();
|
||||
@@ -188,17 +207,27 @@ void Dialog::Show()
|
||||
}
|
||||
}
|
||||
|
||||
// 重绘
|
||||
// ③ 最后一笔:只画这只模态,保证永远在最上层
|
||||
if (dirty)
|
||||
{
|
||||
draw();
|
||||
FlushBatchDraw();
|
||||
BeginBatchDraw();
|
||||
this->draw(); // 注意:不要 requestRepaint(parent),只画自己
|
||||
EndBatchDraw();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
// 避免CPU占用过高
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
if (pendingCleanup && !isCleaning)
|
||||
performDelayedCleanup();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非模态仍由主循环托管
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// 模态对话框关闭后执行清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
performDelayedCleanup();
|
||||
@@ -208,8 +237,6 @@ void Dialog::Show()
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dialog::Close()
|
||||
{
|
||||
if (!show) return;
|
||||
@@ -218,15 +245,10 @@ void Dialog::Close()
|
||||
close = true;
|
||||
dirty = true;
|
||||
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||
auto& c = hWnd.getControls();
|
||||
for(auto& control:c)
|
||||
control->setDirty(true);
|
||||
|
||||
// 工厂模式下非模态触发回调 返回结果
|
||||
if (resultCallback && !modal)
|
||||
resultCallback(this->result);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Dialog::setInitialization(bool init)
|
||||
@@ -235,10 +257,10 @@ void Dialog::setInitialization(bool init)
|
||||
{
|
||||
initDialogSize();
|
||||
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
|
||||
this->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Dialog::initButtons()
|
||||
{
|
||||
controls.clear();
|
||||
@@ -254,6 +276,7 @@ void Dialog::initButtons()
|
||||
okbutton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
okbutton->textStyle = this->textStyle;
|
||||
@@ -275,7 +298,7 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||
(okButton.get()->getX() + okButton.get()->getWidth() + buttonMargin),
|
||||
okButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
@@ -306,7 +329,7 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||
(yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin),
|
||||
yesButton.get()->getY(),
|
||||
"否"
|
||||
);
|
||||
@@ -337,7 +360,7 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||
yesButton.get()->getX() + yesButton.get()->getWidth() + buttonMargin,
|
||||
yesButton.get()->getY(),
|
||||
"否"
|
||||
);
|
||||
@@ -348,7 +371,7 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||
noButton.get()->getX() + noButton.get()->getWidth() + buttonMargin,
|
||||
noButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
@@ -362,7 +385,6 @@ void Dialog::initButtons()
|
||||
noButton->textStyle = this->textStyle;
|
||||
cancelButton->textStyle = this->textStyle;
|
||||
|
||||
|
||||
this->addControl(std::move(yesButton));
|
||||
this->addControl(std::move(noButton));
|
||||
this->addControl(std::move(cancelButton));
|
||||
@@ -382,7 +404,7 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin,
|
||||
retryButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
@@ -413,7 +435,7 @@ void Dialog::initButtons()
|
||||
this->Close();
|
||||
});
|
||||
auto retryButton = createDialogButton(
|
||||
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||
abortButton.get()->getX() + abortButton.get()->getWidth() + buttonMargin,
|
||||
abortButton.get()->getY(),
|
||||
"重试"
|
||||
);
|
||||
@@ -424,7 +446,7 @@ void Dialog::initButtons()
|
||||
this->Close();
|
||||
});
|
||||
auto ignoreButton = createDialogButton(
|
||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getX() + retryButton.get()->getWidth() + buttonMargin,
|
||||
retryButton.get()->getY(),
|
||||
"忽略"
|
||||
);
|
||||
@@ -461,6 +483,7 @@ void Dialog::initCloseButton()
|
||||
StellarX::ControlShape::B_RECTANGLE
|
||||
);
|
||||
but.get()->setButtonFalseColor(this->backgroundColor);
|
||||
but.get()->enableTooltip(false);
|
||||
but->setOnClickListener([this]() {
|
||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||
this->hWnd.dialogClose = true;
|
||||
@@ -520,7 +543,9 @@ 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)
|
||||
int tempHeight = 0;
|
||||
int tempWidth = 0;
|
||||
for (auto& text : lines)
|
||||
{
|
||||
int w = textwidth(LPCTSTR(text.c_str()));
|
||||
int h = textheight(LPCTSTR(text.c_str()));
|
||||
@@ -529,6 +554,7 @@ void Dialog::getTextSize()
|
||||
if (this->textWidth < w)
|
||||
this->textWidth = w;
|
||||
}
|
||||
|
||||
restoreStyle();
|
||||
}
|
||||
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
|
||||
@@ -586,6 +612,12 @@ void Dialog::initDialogSize()
|
||||
initCloseButton(); // 初始化关闭按钮
|
||||
}
|
||||
|
||||
void Dialog::addControl(std::unique_ptr<Control> control)
|
||||
{
|
||||
control->setParent(this);
|
||||
controls.push_back(std::move(control));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
|
||||
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
|
||||
@@ -596,20 +628,38 @@ void Dialog::performDelayedCleanup()
|
||||
|
||||
isCleaning = true;
|
||||
|
||||
// 清除所有控件
|
||||
auto& c = hWnd.getControls();
|
||||
for (auto& control : c)
|
||||
control->setDirty(true);
|
||||
|
||||
controls.clear();
|
||||
|
||||
// 重置指针
|
||||
closeButton = nullptr;
|
||||
title.reset();
|
||||
|
||||
// 释放背景图像资源
|
||||
if (saveBkImage && hasSnap)
|
||||
{
|
||||
restBackground();
|
||||
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,会早退不绘)
|
||||
EndBatchDraw();
|
||||
FlushBatchDraw();
|
||||
}
|
||||
// 重置状态
|
||||
needsInitialization = true;
|
||||
pendingCleanup = false;
|
||||
@@ -655,4 +705,14 @@ std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::stri
|
||||
return btn;
|
||||
}
|
||||
|
||||
|
||||
void Dialog::requestRepaint(Control* parent)
|
||||
{
|
||||
if (this == parent)
|
||||
{
|
||||
for (auto& control : controls)
|
||||
if (control->isDirty() && control->IsVisible())
|
||||
control->draw();
|
||||
}
|
||||
else
|
||||
onRequestRepaintAsRoot();
|
||||
}
|
||||
+6
-1
@@ -1,11 +1,14 @@
|
||||
#include "MessageBox.h"
|
||||
|
||||
#include "SxLog.h"
|
||||
namespace StellarX
|
||||
{
|
||||
MessageBoxResult MessageBox::showModal(Window& wnd, const std::string& text, const std::string& caption,
|
||||
MessageBoxType type)
|
||||
{
|
||||
Dialog dlg(wnd, caption, text, type, true); // 模态
|
||||
SX_LOGI("MessageBox") << "show: Message=" << dlg.GetText()
|
||||
<< " modal=" << (dlg.model() ? 1 : 0);
|
||||
|
||||
dlg.setInitialization(true);
|
||||
dlg.Show();
|
||||
return dlg.GetResult();
|
||||
@@ -22,6 +25,8 @@ namespace StellarX
|
||||
}
|
||||
auto dlg = std::make_unique<Dialog>(wnd, caption, text,
|
||||
type, false); // 非模态
|
||||
SX_LOGI("MessageBox") << "show: Message=" << dlg->GetText()
|
||||
<< " modal=" << (dlg->model() ? 1 : 0);
|
||||
Dialog* dlgPtr = dlg.get();
|
||||
dlgPtr->setInitialization(true);
|
||||
// 设置回调
|
||||
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
#include "SxLog.h"
|
||||
#include <cstdlib>
|
||||
#include <clocale>
|
||||
|
||||
/********************************************************************************
|
||||
* @文件: SxLog.cpp
|
||||
* @摘要: StellarX 日志系统实现(过滤/格式化/输出/文件滚动/RAII提交/作用域计时)
|
||||
* @描述:
|
||||
* 该实现文件主要包含 4 个关键点:
|
||||
* 1) FileSink: 文件打开、写入、flush 与按阈值滚动
|
||||
* 2) SxLogger: shouldLog 过滤、formatPrefix 前缀拼接、logLine 统一输出出口
|
||||
* 3) SxLogLine: 析构提交(RAII)确保“一条语句输出一整行”
|
||||
* 4) SxLogScope: 按需启用计时,析构输出耗时
|
||||
*
|
||||
* @实现难点提示:
|
||||
* - shouldLog 必须“零副作用”,否则宏短路会带来不可预测行为
|
||||
* - logLine 是统一出口,必须保证行级一致性,且避免在持锁状态下递归打日志
|
||||
* - 文件滚动要处理文件名安全性与跨平台 rename 行为差异
|
||||
* - 时间戳生成需要兼容 Windows 与 POSIX(localtime_s/localtime_r)
|
||||
********************************************************************************/
|
||||
|
||||
namespace StellarX
|
||||
{
|
||||
// -------- FileSink --------
|
||||
|
||||
// 打开文件输出
|
||||
// 难点:
|
||||
// - 需要支持追加与清空两种模式
|
||||
// - open 前先 close,避免重复打开导致句柄泄漏
|
||||
bool FileSink::open(const std::string& path, bool append)
|
||||
{
|
||||
close();
|
||||
filePath = path;
|
||||
appendMode = append;
|
||||
|
||||
std::ios::openmode mode = std::ios::out;
|
||||
mode |= (append ? std::ios::app : std::ios::trunc);
|
||||
|
||||
ofs.open(path.c_str(), mode);
|
||||
return ofs.is_open();
|
||||
}
|
||||
|
||||
// 关闭文件输出(可重复调用)
|
||||
void FileSink::close()
|
||||
{
|
||||
if (ofs.is_open()) ofs.close();
|
||||
}
|
||||
|
||||
// 查询是否已打开
|
||||
bool FileSink::isOpen() const
|
||||
{
|
||||
return ofs.is_open();
|
||||
}
|
||||
|
||||
// 写入一整行
|
||||
// 难点:
|
||||
// - 写入后若启用 rotateBytes,需要及时检测文件大小是否到阈值
|
||||
void FileSink::writeLine(const std::string& line)
|
||||
{
|
||||
if (!ofs.is_open()) return;
|
||||
ofs << line;
|
||||
if (rotateBytes > 0) rotateIfNeeded();
|
||||
}
|
||||
|
||||
// flush 文件缓冲
|
||||
void FileSink::flush()
|
||||
{
|
||||
if (ofs.is_open()) ofs.flush();
|
||||
}
|
||||
|
||||
// 滚动文件
|
||||
// 难点:
|
||||
// 1) tellp() 返回的是当前写指针位置,通常可近似视为文件大小
|
||||
// 2) 时间戳用于文件名时需要做字符清洗,避免出现不友好字符
|
||||
// 3) rename 行为与权限/占用有关,失败时需要保证不崩溃(此处选择“尽力而为”)
|
||||
bool FileSink::rotateIfNeeded()
|
||||
{
|
||||
if (!ofs.is_open() || rotateBytes == 0) return false;
|
||||
|
||||
const std::streampos pos = ofs.tellp();
|
||||
if (pos < 0) return false;
|
||||
|
||||
const std::size_t size = static_cast<std::size_t>(pos);
|
||||
if (size < rotateBytes) return false;
|
||||
|
||||
ofs.flush();
|
||||
ofs.close();
|
||||
|
||||
// xxx.log -> xxx.log.YYYYmmdd_HHMMSS
|
||||
// 说明:
|
||||
// - makeTimestampLocal 形如 "2026-01-09 12:34:56"
|
||||
// - 文件名中把 '-' ' ' ':' 替换为 '_',只保留数字与 '_',降低环境差异
|
||||
const std::string ts = SxLogger::makeTimestampLocal();
|
||||
std::string safeTs;
|
||||
safeTs.reserve(ts.size());
|
||||
for (char ch : ts)
|
||||
{
|
||||
if (ch >= '0' && ch <= '9') safeTs.push_back(ch);
|
||||
else if (ch == '-' || ch == ' ' || ch == ':') safeTs.push_back('_');
|
||||
}
|
||||
if (safeTs.empty()) safeTs = "rotated";
|
||||
|
||||
const std::string rotated = filePath + "." + safeTs;
|
||||
std::rename(filePath.c_str(), rotated.c_str());
|
||||
|
||||
// 重新打开新文件
|
||||
// 注意: 这里用 append=false,确保新文件从空开始
|
||||
return open(filePath, false);
|
||||
}
|
||||
|
||||
// -------- SxLogger --------
|
||||
|
||||
|
||||
|
||||
// 设置 Windows 控制台 codepage(只执行一次)
|
||||
// 难点:
|
||||
// - 只影响终端解释输出字节的方式,不影响源码文件编码
|
||||
// - 使用 once_flag 避免重复 system 调用造成噪声与性能浪费
|
||||
//
|
||||
void SxLogger::setGBK()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
// 切到chcp 936(GBK),避免中文日志在 CP936 控制台下乱码
|
||||
// 说明:这不是 WinAPI;是执行系统命令
|
||||
std::system("chcp 936 >nul");
|
||||
|
||||
// 补充说明:
|
||||
// - chcp 936 实际是设置为 CP936(GBK)
|
||||
// - 如果你的终端本身是 UTF-8 环境,调用它可能反而改变显示行为
|
||||
// - 该函数建议只在“明确需要 GBK 控制台输出”的场景调用
|
||||
|
||||
// 尝试让 C/C++ 运行库按 UTF-8 工作(对部分流输出有帮助)
|
||||
// std::setlocale(LC_ALL, ".UTF8");
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// 获取单例
|
||||
// 难点:
|
||||
// - 作为全局入口,初始化必须线程安全
|
||||
// - C++11 起函数内静态对象初始化由标准保证线程安全
|
||||
SxLogger& SxLogger::Get()
|
||||
{
|
||||
static SxLogger inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
// 构造:设置默认语言
|
||||
SxLogger::SxLogger()
|
||||
: lang(SxLogLanguage::ZhCN)
|
||||
{
|
||||
}
|
||||
|
||||
// 设置最低输出级别
|
||||
void SxLogger::setMinLevel(SxLogLevel level)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
cfg.minLevel = level;
|
||||
}
|
||||
|
||||
// 获取最低输出级别
|
||||
SxLogLevel SxLogger::getMinLevel() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return cfg.minLevel;
|
||||
}
|
||||
|
||||
// 设置语言
|
||||
// 难点:
|
||||
// - 语言只影响 SX_T 的字符串选择
|
||||
// - 这里用 atomic relaxed,避免频繁加锁
|
||||
void SxLogger::setLanguage(SxLogLanguage l)
|
||||
{
|
||||
lang.store(l, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// 获取语言
|
||||
SxLogLanguage SxLogger::getLanguage() const
|
||||
{
|
||||
return lang.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// 设置 Tag 过滤
|
||||
// 难点:
|
||||
// - 当前实现是 vector<string> 线性匹配,适合 tag 数量不大
|
||||
// - 若未来 tag 很多,可考虑 unordered_set 优化(但会增加依赖与复杂度)
|
||||
void SxLogger::setTagFilter(SxTagFilterMode mode, const std::vector<std::string>& tags)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
cfg.tagFilterMode = mode;
|
||||
cfg.tagList = tags;
|
||||
}
|
||||
|
||||
// 清空 Tag 过滤
|
||||
void SxLogger::clearTagFilter()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
cfg.tagFilterMode = SxTagFilterMode::None;
|
||||
cfg.tagList.clear();
|
||||
}
|
||||
|
||||
// 开关控制台输出
|
||||
// 难点:
|
||||
// - ConsoleSink 持有 ostream 引用,不管理其生命周期
|
||||
void SxLogger::enableConsole(bool enable)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (enable)
|
||||
{
|
||||
if (!consoleSink) consoleSink.reset(new ConsoleSink(std::cout));
|
||||
}
|
||||
else
|
||||
{
|
||||
consoleSink.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// 开启文件输出
|
||||
// 难点:
|
||||
// - enableFile 成功与否决定 cfg.fileEnabled
|
||||
// - 需要把 rotateBytes 同步到 FileSink
|
||||
bool SxLogger::enableFile(const std::string& path, bool append, std::size_t rotateBytes_)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
if (!fileSink) fileSink.reset(new FileSink());
|
||||
fileSink->setRotateBytes(rotateBytes_);
|
||||
|
||||
const bool ok = fileSink->open(path, append);
|
||||
cfg.fileEnabled = ok;
|
||||
cfg.filePath = path;
|
||||
cfg.fileAppend = append;
|
||||
cfg.rotateBytes = rotateBytes_;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// 关闭文件输出
|
||||
void SxLogger::disableFile()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (fileSink) fileSink->close();
|
||||
cfg.fileEnabled = false;
|
||||
}
|
||||
|
||||
// 获取配置副本
|
||||
// 难点:
|
||||
// - 返回副本避免外部拿到内部引用后绕过锁修改
|
||||
SxLogConfig SxLogger::getConfigCopy() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// 设置配置(整体替换)
|
||||
void SxLogger::setConfig(const SxLogConfig& c)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
cfg = c;
|
||||
}
|
||||
|
||||
// 级别转字符串
|
||||
const char* SxLogger::levelToString(SxLogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case SxLogLevel::Trace: return "TRACE";
|
||||
case SxLogLevel::Debug: return "DEBUG";
|
||||
case SxLogLevel::Info: return "INFO ";
|
||||
case SxLogLevel::Warn: return "WARN ";
|
||||
case SxLogLevel::Error: return "ERROR";
|
||||
case SxLogLevel::Fatal: return "FATAL";
|
||||
default: return "OFF ";
|
||||
}
|
||||
}
|
||||
|
||||
// 判断 tag 是否允许输出
|
||||
// 难点:
|
||||
// - 精确匹配 tag 字符串
|
||||
// - tag==nullptr 时默认允许,避免“无 tag 日志被误杀”
|
||||
bool SxLogger::tagAllowed(const SxLogConfig& c, const char* tag)
|
||||
{
|
||||
if (c.tagFilterMode == SxTagFilterMode::None) return true;
|
||||
if (!tag) return true;
|
||||
|
||||
bool found = false;
|
||||
for (const auto& t : c.tagList)
|
||||
{
|
||||
if (t == tag) { found = true; break; }
|
||||
}
|
||||
|
||||
if (c.tagFilterMode == SxTagFilterMode::Whitelist) return found;
|
||||
if (c.tagFilterMode == SxTagFilterMode::Blacklist) return !found;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 快速判定是否需要输出(宏短路依赖)
|
||||
// 难点:
|
||||
// 1) 必须无副作用:返回 false 时调用端不会构造对象也不会拼接
|
||||
// 2) 过滤维度要完整:级别、tag、sink 是否启用
|
||||
// 3) 当前实现加锁保证 cfg 与 sink 状态一致;代价是高频路径会有锁开销
|
||||
bool SxLogger::shouldLog(SxLogLevel level, const char* tag) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (cfg.minLevel == SxLogLevel::Off) return false;
|
||||
if (level < cfg.minLevel) return false;
|
||||
if (!tagAllowed(cfg, tag)) return false;
|
||||
if (!consoleSink && !cfg.fileEnabled) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 生成本地时间戳字符串
|
||||
// 难点:
|
||||
// - Windows 与 POSIX 的线程安全 localtime API 不同
|
||||
std::string SxLogger::makeTimestampLocal()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
const auto now = system_clock::now();
|
||||
const std::time_t t = system_clock::to_time_t(now);
|
||||
|
||||
std::tm tmv{};
|
||||
#if defined(_WIN32)
|
||||
localtime_s(&tmv, &t);
|
||||
#else
|
||||
localtime_r(&t, &tmv);
|
||||
#endif
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&tmv, "%Y-%m-%d %H:%M:%S");
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// 拼接日志前缀(调用方已持锁)
|
||||
// 难点:
|
||||
// - 前缀拼接必须与配置项严格对应,且尽量避免多余开销
|
||||
// - showSource 会输出 (file:line func),对定位时序问题很有价值
|
||||
std::string SxLogger::formatPrefixUnlocked(
|
||||
const SxLogConfig& c,
|
||||
SxLogLevel level,
|
||||
const char* tag,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* func) const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
if (c.showTimestamp) oss << "[" << makeTimestampLocal() << "] ";
|
||||
if (c.showLevel) oss << "[" << levelToString(level) << "] ";
|
||||
if (c.showTag && tag) oss << "[" << tag << "] ";
|
||||
|
||||
if (c.showThreadId)
|
||||
{
|
||||
oss << "[T:" << std::this_thread::get_id() << "] ";
|
||||
}
|
||||
|
||||
if (c.showSource && file && func)
|
||||
{
|
||||
oss << "(" << file << ":" << line << " " << func << ") ";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// 统一输出出口
|
||||
// 难点:
|
||||
// 1) 行级一致性:必须把 prefix + msg + "\n" 当作整体写入
|
||||
// 2) 线程安全:持锁写入可避免不同线程日志互相穿插
|
||||
// 3) 避免重入:在持锁期间不要再调用 SX_LOG...(会导致死锁)
|
||||
void SxLogger::logLine(
|
||||
SxLogLevel level,
|
||||
const char* tag,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* func,
|
||||
const std::string& msg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
if (cfg.minLevel == SxLogLevel::Off) return;
|
||||
if (level < cfg.minLevel) return;
|
||||
if (!tagAllowed(cfg, tag)) return;
|
||||
|
||||
const std::string prefix = formatPrefixUnlocked(cfg, level, tag, file, line, func);
|
||||
const std::string lineText = prefix + msg + "\n";
|
||||
|
||||
if (consoleSink) consoleSink->writeLine(lineText);
|
||||
|
||||
if (cfg.fileEnabled && fileSink && fileSink->isOpen())
|
||||
{
|
||||
fileSink->writeLine(lineText);
|
||||
}
|
||||
|
||||
if (cfg.autoFlush)
|
||||
{
|
||||
if (consoleSink) consoleSink->flush();
|
||||
if (cfg.fileEnabled && fileSink) fileSink->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// -------- SxLogLine --------
|
||||
|
||||
// 构造:只记录元信息
|
||||
SxLogLine::SxLogLine(SxLogLevel level, const char* tag, const char* file, int line, const char* func)
|
||||
: lvl(level), tg(tag), srcFile(file), srcLine(line), srcFunc(func)
|
||||
{
|
||||
}
|
||||
|
||||
// 析构:提交输出
|
||||
// 难点:
|
||||
// - 这是 RAII 设计的核心:保证语句结束时日志自动落地
|
||||
// - 也要求调用端不要把临时对象跨语句保存(宏用法本身也不支持那样做)
|
||||
SxLogLine::~SxLogLine()
|
||||
{
|
||||
SxLogger::Get().logLine(lvl, tg, srcFile, srcLine, srcFunc, ss.str());
|
||||
}
|
||||
|
||||
// -------- SxLogScope --------
|
||||
|
||||
// 构造:按需启用计时
|
||||
// 难点:
|
||||
// - 只有 shouldLog 为 true 才记录起点,避免在未输出场景做无意义计时
|
||||
SxLogScope::SxLogScope(SxLogLevel level, const char* tag, const char* file, int line, const char* func, const char* name)
|
||||
: lvl(level), tg(tag), srcFile(file), srcLine(line), srcFunc(func), scopeName(name)
|
||||
{
|
||||
enabled = SxLogger::Get().shouldLog(lvl, tg);
|
||||
if (enabled) t0 = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
// 析构:输出耗时
|
||||
// 难点:
|
||||
// - steady_clock 用于衡量耗时,避免系统时间调整造成跳变
|
||||
SxLogScope::~SxLogScope()
|
||||
{
|
||||
if (!enabled) return;
|
||||
const auto t1 = std::chrono::steady_clock::now();
|
||||
const auto us = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "SCOPE " << (scopeName ? scopeName : "") << " cost=" << us << "us";
|
||||
SxLogger::Get().logLine(lvl, tg, srcFile, srcLine, srcFunc, oss.str());
|
||||
}
|
||||
|
||||
} // namespace StellarX
|
||||
@@ -0,0 +1,428 @@
|
||||
#include "TabControl.h"
|
||||
#include "SxLog.h"
|
||||
inline void TabControl::initTabBar()
|
||||
{
|
||||
if (controls.empty())return;
|
||||
int butW = max(this->width / (int)controls.size(), BUTMINWIDTH);
|
||||
int butH = max(this->height / (int)controls.size(), BUTMINHEIGHT);
|
||||
|
||||
if (this->tabPlacement == StellarX::TabPlacement::Top || this->tabPlacement == StellarX::TabPlacement::Bottom)
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setHeight(tabBarHeight);
|
||||
c.first->setWidth(butW);
|
||||
}
|
||||
else if (this->tabPlacement == StellarX::TabPlacement::Left || this->tabPlacement == StellarX::TabPlacement::Right)
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setHeight(butH);
|
||||
c.first->setWidth(tabBarHeight);
|
||||
}
|
||||
int i = 0;
|
||||
switch (this->tabPlacement)
|
||||
{
|
||||
case StellarX::TabPlacement::Top:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setX(this->x + i * butW);
|
||||
c.first->setY(this->y);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Bottom:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setX(this->x + i * butW);
|
||||
c.first->setY(this->y + this->height - tabBarHeight);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Left:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setX(this->x);
|
||||
c.first->setY(this->y + i * butH);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Right:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setX(this->x + this->width - tabBarHeight);
|
||||
c.first->setY(this->y + i * butH);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void TabControl::initTabPage()
|
||||
{
|
||||
if (controls.empty())return;
|
||||
//子控件坐标原点
|
||||
int nX = 0;
|
||||
int nY = 0;
|
||||
switch (this->tabPlacement)
|
||||
{
|
||||
case StellarX::TabPlacement::Top:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setX(this->x);
|
||||
c.second->setY(this->y + tabBarHeight);
|
||||
c.second->setWidth(this->width);
|
||||
c.second->setHeight(this->height - tabBarHeight);
|
||||
}
|
||||
nX = this->x;
|
||||
nY = this->y + tabBarHeight;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
for (auto& v : c.second->getControls())
|
||||
{
|
||||
v->setX(v->getLocalX() + nX);
|
||||
v->setY(v->getLocalY() + nY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Bottom:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setX(this->x);
|
||||
c.second->setY(this->y);
|
||||
c.second->setWidth(this->width);
|
||||
c.second->setHeight(this->height - tabBarHeight);
|
||||
}
|
||||
nX = this->x;
|
||||
nY = this->y;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
for (auto& v : c.second->getControls())
|
||||
{
|
||||
v->setX(v->getLocalX() + nX);
|
||||
v->setY(v->getLocalY() + nY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Left:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setX(this->x + tabBarHeight);
|
||||
c.second->setY(this->y);
|
||||
c.second->setWidth(this->width - tabBarHeight);
|
||||
c.second->setHeight(this->height);
|
||||
}
|
||||
nX = this->x + tabBarHeight;
|
||||
nY = this->y;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
for (auto& v : c.second->getControls())
|
||||
{
|
||||
v->setX(v->getLocalX() + nX);
|
||||
v->setY(v->getLocalY() + nY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StellarX::TabPlacement::Right:
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setX(this->x);
|
||||
c.second->setY(this->y);
|
||||
c.second->setWidth(this->width - tabBarHeight);
|
||||
c.second->setHeight(this->height);
|
||||
}
|
||||
nX = this->x;
|
||||
nY = this->y;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
for (auto& v : c.second->getControls())
|
||||
{
|
||||
v->setX(v->getLocalX() + nX);
|
||||
v->setY(v->getLocalY() + nY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TabControl::TabControl() :Canvas()
|
||||
{
|
||||
this->id = "TabControl";
|
||||
}
|
||||
|
||||
TabControl::TabControl(int x, int y, int width, int height)
|
||||
: Canvas(x, y, width, height)
|
||||
{
|
||||
this->id = "TabControl";
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (!dirty || !show)return;
|
||||
// 绘制画布背景和基本形状及其子画布控件
|
||||
Canvas::draw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setDirty(true);
|
||||
c.first->draw();
|
||||
}
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setDirty(true);
|
||||
c.second->draw();
|
||||
}
|
||||
|
||||
// 首次绘制时处理默认激活页签
|
||||
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)
|
||||
{
|
||||
if (!show)return false;
|
||||
bool consume = false;
|
||||
for (auto& c : controls)
|
||||
if (c.first->handleEvent(msg))
|
||||
{
|
||||
consume = true;
|
||||
break;
|
||||
}
|
||||
for (auto& c : controls)
|
||||
if (c.second->IsVisible())
|
||||
if (c.second->handleEvent(msg))
|
||||
{
|
||||
consume = true;
|
||||
break;
|
||||
}
|
||||
if (dirty)
|
||||
requestRepaint(parent);
|
||||
return consume;
|
||||
}
|
||||
|
||||
void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control)
|
||||
{
|
||||
controls.push_back(std::move(control));
|
||||
initTabBar();
|
||||
initTabPage();
|
||||
size_t idx = controls.size() - 1;
|
||||
controls[idx].first->setParent(this);
|
||||
controls[idx].first->enableTooltip(true);
|
||||
controls[idx].first->setbuttonMode(StellarX::ButtonMode::TOGGLE);
|
||||
|
||||
controls[idx].first->setOnToggleOnListener([this, idx]()
|
||||
{
|
||||
int prevIdx = -1;
|
||||
for (size_t i = 0; i < controls.size(); ++i)
|
||||
{
|
||||
if (controls[i].second->IsVisible())
|
||||
{
|
||||
prevIdx = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto& tab : controls)
|
||||
{
|
||||
if (tab.first->getButtonText() != controls[idx].first->getButtonText() && tab.first->isClicked())
|
||||
tab.first->setButtonClick(false);
|
||||
}
|
||||
|
||||
|
||||
SX_LOGI("Tab") << SX_T("激活选项卡:","activate tab: ") << prevIdx << "->" << (int)idx
|
||||
<< " text=" << controls[idx].first->getButtonText();
|
||||
controls[idx].second->onWindowResize();
|
||||
controls[idx].second->setIsVisible(true);
|
||||
dirty = true;
|
||||
|
||||
|
||||
});
|
||||
controls[idx].first->setOnToggleOffListener([this, idx]()
|
||||
{
|
||||
SX_LOGI("Tab") << SX_T("关闭选项卡:id=","deactivate tab: idx=") << (int)idx
|
||||
<< " text=" << controls[idx].first->getButtonText();
|
||||
|
||||
controls[idx].second->setIsVisible(false);
|
||||
dirty = true;
|
||||
});
|
||||
controls[idx].second->setParent(this);
|
||||
controls[idx].second->setLinewidth(canvaslinewidth);
|
||||
controls[idx].second->setIsVisible(false);
|
||||
}
|
||||
|
||||
void TabControl::add(std::string tabText, std::unique_ptr<Control> control)
|
||||
{
|
||||
control->setDirty(true);
|
||||
for (auto& tab : controls)
|
||||
{
|
||||
if (tab.first->getButtonText() == tabText)
|
||||
{
|
||||
control->setParent(tab.second.get());
|
||||
control->setIsVisible(tab.second->IsVisible());
|
||||
tab.second->addControl(std::move(control));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabControl::setTabPlacement(StellarX::TabPlacement placement)
|
||||
{
|
||||
this->tabPlacement = placement;
|
||||
setDirty(true);
|
||||
initTabBar();
|
||||
initTabPage();
|
||||
}
|
||||
|
||||
void TabControl::setTabBarHeight(int height)
|
||||
{
|
||||
tabBarHeight = height;
|
||||
setDirty(true);
|
||||
initTabBar();
|
||||
initTabPage();
|
||||
}
|
||||
|
||||
void TabControl::setIsVisible(bool visible)
|
||||
{
|
||||
// 先让基类 Canvas 处理自己的回贴/丢快照逻辑
|
||||
Canvas::setIsVisible(visible);
|
||||
for (auto& tab : controls)
|
||||
{
|
||||
if(true == visible)
|
||||
{
|
||||
tab.first->setIsVisible(visible);
|
||||
//页也要跟着关/开,否则它们会保留旧的 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()
|
||||
{
|
||||
// 调用基类的窗口变化处理,丢弃快照并标记脏
|
||||
Control::onWindowResize();
|
||||
// 根据当前 TabControl 的新尺寸重新计算页签栏和页面区域
|
||||
initTabBar();
|
||||
initTabPage();
|
||||
// 转发窗口尺寸变化给所有页签按钮和页面
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->onWindowResize();
|
||||
c.second->onWindowResize();
|
||||
}
|
||||
// 尺寸变化后需要重绘自身
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
int TabControl::getActiveIndex() const
|
||||
{
|
||||
int idx = -1;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
idx++;
|
||||
if (c.first->isClicked())
|
||||
return idx;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TabControl::setActiveIndex(int idx)
|
||||
{
|
||||
if (IsFirstDraw)
|
||||
defaultActivation = idx;
|
||||
else
|
||||
{
|
||||
if (idx >= 0 && idx < controls.size())
|
||||
controls[idx].first->setButtonClick(true);
|
||||
}
|
||||
}
|
||||
|
||||
int TabControl::count() const
|
||||
{
|
||||
return (int)controls.size();
|
||||
}
|
||||
|
||||
int TabControl::indexOf(const std::string& tabText) const
|
||||
{
|
||||
int idx = -1;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
idx++;
|
||||
if (c.first->getButtonText() == tabText)
|
||||
return idx;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void TabControl::setDirty(bool dirty)
|
||||
{
|
||||
this->dirty = dirty;
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setDirty(dirty);
|
||||
c.second->setDirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
void TabControl::requestRepaint(Control* parent)
|
||||
{
|
||||
if (this == parent)
|
||||
{
|
||||
for (auto& control : controls)
|
||||
{
|
||||
if (control.first->isDirty() && control.first->IsVisible())
|
||||
control.first->draw();
|
||||
if (control.second->isDirty() && control.second->IsVisible())
|
||||
control.second->draw();
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
onRequestRepaintAsRoot();
|
||||
}
|
||||
+15
-12
@@ -3,16 +3,18 @@
|
||||
Label::Label()
|
||||
:Control(0, 0, 0, 0)
|
||||
{
|
||||
this->id = "Label";
|
||||
this->text = "默认标签";
|
||||
textColor = RGB(0,0,0);
|
||||
textStyle.color = RGB(0, 0, 0);
|
||||
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
||||
}
|
||||
|
||||
Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColor)
|
||||
:Control(x, y, 0, 0)
|
||||
{
|
||||
this->id = "Label";
|
||||
this->text = text;
|
||||
textColor = textcolor;
|
||||
textStyle.color = textcolor;
|
||||
textBkColor = bkColor; //默认白色背景
|
||||
}
|
||||
|
||||
@@ -28,14 +30,21 @@ void Label::draw()
|
||||
setbkmode(OPAQUE); //设置背景不透明
|
||||
setbkcolor(textBkColor); //设置背景颜色
|
||||
}
|
||||
settextcolor(textColor);
|
||||
settextcolor(textStyle.color);
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
this->saveBackground(x, y,textwidth(text.c_str()),textheight(text.c_str()));
|
||||
this-> restBackground();
|
||||
if (0 == this->width || 0 == this->height)
|
||||
{
|
||||
this->width = textwidth(text.c_str());
|
||||
this->height = textheight(text.c_str());
|
||||
}
|
||||
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();
|
||||
outtextxy(x, y, LPCTSTR(text.c_str()));
|
||||
this->restoreStyle();
|
||||
restoreStyle();
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
@@ -52,12 +61,6 @@ void Label::setTextdisap(bool key)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Label::setTextColor(COLORREF color)
|
||||
{
|
||||
textColor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Label::setTextBkColor(COLORREF color)
|
||||
{
|
||||
textBkColor = color;
|
||||
|
||||
+224
-59
@@ -1,4 +1,5 @@
|
||||
#include "Table.h"
|
||||
#include "SxLog.h"
|
||||
// 绘制表格的当前页
|
||||
// 使用双循环绘制行和列,考虑分页偏移
|
||||
void Table::drawTable()
|
||||
@@ -7,8 +8,8 @@ void Table::drawTable()
|
||||
|
||||
// 表体从“表头之下”开始
|
||||
dX = x + border;
|
||||
dY = y + border + lineHeights.at(0) + 10; // 表头高度
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
dY = y + border + lineHeights.at(0) + TABLE_HEADER_EXTRA; // 表头高度
|
||||
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
|
||||
|
||||
size_t startRow = (currentPage - 1) * rowsPerPage;
|
||||
size_t endRow = startRow + (size_t)rowsPerPage < data.size() ? startRow + (size_t)rowsPerPage : data.size();
|
||||
@@ -17,33 +18,31 @@ void Table::drawTable()
|
||||
{
|
||||
for (size_t j = 0; j < data[i].size(); ++j)
|
||||
{
|
||||
uX = dX + colWidths.at(j) + 20; // 列宽 + 20
|
||||
uX = dX + colWidths.at(j) + TABLE_COL_GAP;
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
outtextxy(dX + 10, dY + 5, LPCTSTR(data[i][j].c_str()));
|
||||
dX += colWidths.at(j) + 20;
|
||||
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(data[i][j].c_str()));
|
||||
dX += colWidths.at(j) + TABLE_COL_GAP;
|
||||
}
|
||||
dX = x + border;
|
||||
dY = uY;
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Table::drawHeader()
|
||||
{
|
||||
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
// 内容区原点 = x+border, y+border
|
||||
dX = x + border;
|
||||
dY = y + border;
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
uY = dY + lineHeights.at(0) + TABLE_HEADER_EXTRA;
|
||||
|
||||
for (size_t i = 0; i < headers.size(); i++)
|
||||
{
|
||||
uX = dX + colWidths.at(i) + 20; // 注意这里是 +20,和表体一致
|
||||
uX = dX + colWidths.at(i) + TABLE_COL_GAP; // 注意这里是 +20,和表体一致
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
outtextxy(dX + 10, dY + 5, LPCTSTR(headers[i].c_str()));
|
||||
dX += colWidths.at(i) + 20; // 列间距 20
|
||||
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(headers[i].c_str()));
|
||||
dX += colWidths.at(i) + TABLE_COL_GAP; // 列间距 20
|
||||
}
|
||||
}
|
||||
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
|
||||
@@ -51,9 +50,9 @@ void Table::drawHeader()
|
||||
void Table::initTextWaH()
|
||||
{
|
||||
// 和绘制一致的单元内边距
|
||||
const int padX = 10; // 左右 padding
|
||||
const int padY = 5; // 上下 padding
|
||||
const int colGap = 20; // 列间距
|
||||
const int padX = TABLE_PAD_X; // 左右 padding
|
||||
const int padY = TABLE_PAD_Y; // 上下 padding
|
||||
const int colGap = TABLE_COL_GAP; // 列间距
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
|
||||
// 统计每列最大文本宽 & 每列最大行高(包含数据 + 表头)
|
||||
@@ -87,11 +86,15 @@ void Table::initTextWaH()
|
||||
if (h > maxLineH)
|
||||
maxLineH = h;
|
||||
|
||||
// 列的像素宽 = 内容宽 + 左右 padding
|
||||
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
|
||||
for (size_t j = 0; j < colWidths.size(); ++j) {
|
||||
colWidths[j] += 2 * padX;
|
||||
}
|
||||
|
||||
// 表内容总宽 = Σ(列宽 + 列间距)
|
||||
int contentW = 0;
|
||||
for (size_t j = 0; j < colWidths.size(); ++j)
|
||||
contentW += (colWidths[j] + 2 * padX) + colGap;
|
||||
contentW += colWidths[j] + colGap;
|
||||
|
||||
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding)
|
||||
const int headerH = maxLineH + 2 * padY;
|
||||
@@ -101,31 +104,33 @@ void Table::initTextWaH()
|
||||
// 页脚:
|
||||
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||
const int btnTextH = textheight(LPCTSTR("上一页"));
|
||||
const int btnPadV = 8;
|
||||
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
|
||||
const int btnH = btnTextH + 2 * btnPadV;
|
||||
const int footerPad = 16;
|
||||
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()
|
||||
{
|
||||
const int gap = 12; // 页码与按钮之间的固定间距
|
||||
const int padH = 12; // 按钮水平内边距
|
||||
const int padV = 0; // 按钮垂直内边距
|
||||
const int gap = TABLE_BTN_GAP;
|
||||
const int padH = TABLE_BTN_PAD_H;
|
||||
const int padV = TABLE_BTN_PAD_V; // 按钮垂直内边距
|
||||
|
||||
int pageW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||
int lblH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||
|
||||
// 统一按钮尺寸(用按钮文字自身宽高 + padding)
|
||||
int prevW = textwidth(LPCTSTR("上一页")) + padH * 2;
|
||||
int nextW = textwidth(LPCTSTR("下一页")) + padH * 2;
|
||||
int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2;
|
||||
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
|
||||
int btnH = lblH + padV * 2;
|
||||
|
||||
|
||||
// 基于“页码标签”的矩形来摆放:
|
||||
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
|
||||
int prevX = pX - gap - prevW;
|
||||
@@ -133,7 +138,7 @@ void Table::initButton()
|
||||
int btnY = pY; // 和页码同一基线
|
||||
|
||||
if (!prevButton)
|
||||
prevButton = new Button(prevX, btnY, prevW, btnH, "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
prevButton = new Button(prevX, btnY, prevW, btnH, TABLE_STR_PREV, RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
else
|
||||
{
|
||||
prevButton->setX(prevX);
|
||||
@@ -141,7 +146,7 @@ void Table::initButton()
|
||||
}
|
||||
|
||||
if (!nextButton)
|
||||
nextButton = new Button(nextX, btnY, nextW, btnH, "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
nextButton = new Button(nextX, btnY, nextW, btnH, TABLE_STR_NEXT, RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
else
|
||||
{
|
||||
nextButton->setX(nextX);
|
||||
@@ -155,22 +160,38 @@ void Table::initButton()
|
||||
|
||||
prevButton->setOnClickListener([this]()
|
||||
{
|
||||
int oldPage = currentPage;
|
||||
if (currentPage > 1)
|
||||
{
|
||||
--currentPage;
|
||||
SX_LOGI("Table")
|
||||
<< SX_T("翻页:id=", "page change: id=") << id
|
||||
<< " " << oldPage << "->" << currentPage
|
||||
<< SX_T(" 总页数=", " total=") << totalPages
|
||||
<< SX_T(" 行数=", " rows=") << (int)data.size();
|
||||
|
||||
|
||||
dirty = true;
|
||||
if (pageNum) pageNum->setDirty(true);
|
||||
}
|
||||
});
|
||||
nextButton->setOnClickListener([this]()
|
||||
{
|
||||
int oldPage = currentPage;
|
||||
if (currentPage < totalPages)
|
||||
{
|
||||
++currentPage;
|
||||
SX_LOGI("Table")
|
||||
<< SX_T("翻页:id=", "page change: id=") << id
|
||||
<< " " << oldPage << "->" << currentPage
|
||||
<< SX_T(" 总页数=", " total=") << totalPages
|
||||
<< SX_T(" 行数=", " rows=") << (int)data.size();
|
||||
|
||||
dirty = true;
|
||||
if (pageNum) pageNum->setDirty(true);
|
||||
}
|
||||
});
|
||||
isNeedButtonAndPageNum = false;
|
||||
}
|
||||
|
||||
void Table::initPageNum()
|
||||
@@ -178,20 +199,20 @@ void Table::initPageNum()
|
||||
// 统一坐标系
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
const int baseH = lineHeights.empty() ? 0 : lineHeights.at(0);
|
||||
const int headerH = baseH + 10;
|
||||
const int rowsH = baseH * rowsPerPage + rowsPerPage * 10;
|
||||
const int headerH = baseH + TABLE_HEADER_EXTRA;
|
||||
const int rowsH = baseH * rowsPerPage + rowsPerPage * TABLE_ROW_EXTRA;
|
||||
|
||||
// 内容宽度 = sum(colWidths + 20);initTextWaH() 已把 this->width += 2*border
|
||||
// 因此 contentW = this->width - 2*border 更稳妥
|
||||
const int contentW = this->width - (border << 1);
|
||||
|
||||
// 页脚顶部位置(表头 + 可视数据区 之后)
|
||||
pY = y + border + headerH + rowsH + 8; // +8 顶部留白
|
||||
pY = y + border + headerH + rowsH + TABLE_FOOTER_BLANK; // +8 顶部留白
|
||||
|
||||
// 按理来说 x + (this->width - textW) / 2;就可以
|
||||
// 但是在绘制时,发现控件偏右,因此减去40
|
||||
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||
pX = x - 40 +(this->width - textW) / 2;
|
||||
pX = x + TABLE_PAGE_TEXT_OFFSET_X + (this->width - textW) / 2;
|
||||
|
||||
if (!pageNum)
|
||||
pageNum = new Label(pX, pY, pageNumtext);
|
||||
@@ -208,24 +229,23 @@ void Table::initPageNum()
|
||||
|
||||
void Table::drawPageNum()
|
||||
{
|
||||
|
||||
pageNumtext = "第";
|
||||
pageNumtext += std::to_string(currentPage);
|
||||
pageNumtext += "页/共";
|
||||
pageNumtext += std::to_string(totalPages);
|
||||
pageNumtext += "页";
|
||||
if (nullptr == pageNum)
|
||||
if (nullptr == pageNum || isNeedButtonAndPageNum)
|
||||
initPageNum();
|
||||
pageNum->setText(pageNumtext);
|
||||
pageNum->textStyle = this->textStyle;
|
||||
if (StellarX::FillMode::Null == tableFillMode)
|
||||
pageNum->setTextdisap(true);
|
||||
pageNum->draw();
|
||||
|
||||
}
|
||||
|
||||
void Table::drawButton()
|
||||
{
|
||||
if (nullptr == prevButton || nullptr == nextButton)
|
||||
if ((nullptr == prevButton || nullptr == nextButton) || isNeedButtonAndPageNum)
|
||||
initButton();
|
||||
|
||||
this->prevButton->textStyle = this->textStyle;
|
||||
@@ -238,12 +258,65 @@ void Table::drawButton()
|
||||
this->nextButton->setDirty(true);
|
||||
prevButton->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)
|
||||
:Control(x, y, 0, 0)
|
||||
{
|
||||
this->id = "Table";
|
||||
}
|
||||
|
||||
Table::~Table()
|
||||
@@ -264,6 +337,28 @@ Table::~Table()
|
||||
|
||||
void Table::draw()
|
||||
{
|
||||
//在这里先初始化保证翻页按钮不为空
|
||||
// 在一些容器中,Table不会被立即绘制可能导致事件事件传递时触发空指针警报
|
||||
// 由于单元格初始化依赖字体数据所以先设置一次字体样式
|
||||
// 先保存当前绘图状态
|
||||
saveStyle();
|
||||
|
||||
// 设置表格样式
|
||||
setfillcolor(tableBkClor);
|
||||
setlinecolor(tableBorderClor);
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
settextcolor(textStyle.color);
|
||||
setlinestyle((int)tableLineStyle, tableBorderWidth);
|
||||
setfillstyle((int)tableFillMode);
|
||||
// 是否需要计算单元格尺寸
|
||||
if (isNeedCellSize)
|
||||
{
|
||||
initTextWaH();
|
||||
isNeedCellSize = false;
|
||||
}
|
||||
restoreStyle();
|
||||
if (this->dirty && this->show)
|
||||
{
|
||||
// 先保存当前绘图状态
|
||||
@@ -280,13 +375,6 @@ void Table::draw()
|
||||
setfillstyle((int)tableFillMode);
|
||||
setbkmode(TRANSPARENT);
|
||||
|
||||
// 是否需要计算单元格尺寸
|
||||
if (isNeedCellSize)
|
||||
{
|
||||
initTextWaH();
|
||||
isNeedCellSize = false;
|
||||
}
|
||||
|
||||
if (isNeedDrawHeaders)
|
||||
{
|
||||
// 重新设置表格样式
|
||||
@@ -300,23 +388,32 @@ void Table::draw()
|
||||
setfillstyle((int)tableFillMode);
|
||||
setbkmode(TRANSPARENT);
|
||||
}
|
||||
//确保在绘制任何表格内容之前捕获背景
|
||||
// 临时恢复样式,确保捕获正确的背景
|
||||
if (!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);
|
||||
}
|
||||
// 恢复最新的背景,保证绘制区域干净
|
||||
restBackground();
|
||||
// 绘制表头
|
||||
|
||||
dX = x;
|
||||
dY = y;
|
||||
//if (!headers.empty())
|
||||
drawHeader();
|
||||
this->isNeedDrawHeaders = false;
|
||||
|
||||
|
||||
// 绘制当前页
|
||||
drawTable();
|
||||
|
||||
// 绘制页码标签
|
||||
drawPageNum();
|
||||
|
||||
@@ -338,38 +435,49 @@ bool Table::handleEvent(const ExMessage& msg)
|
||||
return consume;
|
||||
else
|
||||
{
|
||||
consume = prevButton->handleEvent(msg);
|
||||
if (!consume)
|
||||
if (prevButton)consume = prevButton->handleEvent(msg);
|
||||
if (nextButton && !consume)
|
||||
consume = nextButton->handleEvent(msg);
|
||||
}
|
||||
if (dirty)
|
||||
draw();
|
||||
requestRepaint(parent);
|
||||
return consume;
|
||||
}
|
||||
|
||||
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);
|
||||
SX_LOGI("Table") << SX_T("设置表头:id=","setHeaders: id=") << id << SX_T("总数="," count=") << (int)this->headers.size();
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||
dirty = true;
|
||||
|
||||
}
|
||||
|
||||
void Table::setData(std::vector<std::string> data)
|
||||
{
|
||||
if (data.size() < headers.size())
|
||||
for (int i = 0; data.size() <= headers.size(); i++)
|
||||
while (data.size() < headers.size())
|
||||
data.push_back("");
|
||||
|
||||
this->data.push_back(data);
|
||||
|
||||
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
if (totalPages < 1)
|
||||
totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
|
||||
isNeedCellSize = true;
|
||||
dirty = true;
|
||||
|
||||
SX_LOGI("Table")
|
||||
<< SX_T("新增Data:id=", "appendRow: id=") << id
|
||||
<< SX_T(" 本行列数=", " cols=") << (int)data.size()
|
||||
<< SX_T(" 数据总行数=", " totalRows=") << (int)this->data.size()
|
||||
<< SX_T(" 总页数=", " totalPages=") << totalPages;
|
||||
}
|
||||
|
||||
|
||||
void Table::setData(std::initializer_list<std::vector<std::string>> data)
|
||||
{
|
||||
for (auto lis : data)
|
||||
@@ -387,6 +495,13 @@ void Table::setData( std::initializer_list<std::vector<std::string>> data)
|
||||
totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
dirty = true;
|
||||
SX_LOGI("Table")
|
||||
<< SX_T("新增Data:id=", "appendRow: id=") << id
|
||||
<< SX_T(" 本行列数=", " cols=") << (int)data.size()
|
||||
<< SX_T(" 数据总行数=", " totalRows=") << (int)this->data.size()
|
||||
<< SX_T(" 总页数=", " totalPages=") << totalPages;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Table::setRowsPerPage(int rows)
|
||||
@@ -449,6 +564,45 @@ void Table::setTableBorderWidth(int width)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::clearHeaders()
|
||||
{
|
||||
this->headers.clear();
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
isNeedDrawHeaders = true; // 标记需要重新绘制表头
|
||||
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||
dirty = true;
|
||||
SX_LOGI("Table") << SX_T("清除表头:id=","clearHeaders: id=" )<< id;
|
||||
|
||||
}
|
||||
|
||||
void Table::clearData()
|
||||
{
|
||||
this->data.clear();
|
||||
this->currentPage = 1;
|
||||
this->totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
isNeedButtonAndPageNum = true;// 标记需要重新计算翻页按钮和页码信息
|
||||
dirty = true;
|
||||
SX_LOGI("Table") << SX_T("清除表格数据:id=","clearData: id=") << id;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return this->currentPage;
|
||||
@@ -504,4 +658,15 @@ 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;
|
||||
}
|
||||
+102
-15
@@ -1,10 +1,11 @@
|
||||
// TextBox.cpp
|
||||
#include "TextBox.h"
|
||||
|
||||
#include "SxLog.h"
|
||||
|
||||
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)
|
||||
{
|
||||
this->id = "TextBox";
|
||||
}
|
||||
|
||||
void TextBox::draw()
|
||||
@@ -24,38 +25,81 @@ void TextBox::draw()
|
||||
|
||||
settextcolor(textStyle.color);
|
||||
setbkmode(TRANSPARENT);
|
||||
int text_width = textwidth(LPCTSTR(text.c_str()));
|
||||
int text_height = textheight(LPCTSTR(text.c_str()));
|
||||
|
||||
int text_width = 0;
|
||||
int text_height = 0;
|
||||
std::string pwdText;
|
||||
std::string displayText; // 用于显示的文本(可能被截断)
|
||||
bool isTextTruncated = false; // 标记文本是否被截断
|
||||
|
||||
if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||
{
|
||||
for (size_t i = 0; i < text.size(); ++i)
|
||||
pwdText += '*';
|
||||
displayText = pwdText;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayText = text;
|
||||
}
|
||||
|
||||
// 计算可用宽度(留出左右边距)
|
||||
int availableWidth = width - 20; // 左右各10像素边距
|
||||
|
||||
// 截断文本以适应可用宽度
|
||||
int currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
||||
if (currentWidth > availableWidth && availableWidth > 0)
|
||||
{
|
||||
// 需要截断文本,预留空间放置省略号
|
||||
int ellipsisWidth = textwidth("...");
|
||||
int truncatedWidth = availableWidth - ellipsisWidth;
|
||||
|
||||
std::string truncatedText = displayText;
|
||||
while (truncatedText.size() > 0 && textwidth(LPCTSTR(truncatedText.c_str())) > truncatedWidth)
|
||||
{
|
||||
truncatedText.pop_back();
|
||||
}
|
||||
displayText = truncatedText + "...";
|
||||
isTextTruncated = true;
|
||||
currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
||||
}
|
||||
|
||||
text_width = currentWidth;
|
||||
text_height = textheight(LPCTSTR(displayText.c_str()));
|
||||
|
||||
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();
|
||||
//根据形状绘制
|
||||
switch (shape)
|
||||
{
|
||||
case StellarX::ControlShape::RECTANGLE:
|
||||
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(displayText.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_RECTANGLE:
|
||||
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(displayText.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(displayText.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(displayText.c_str()));
|
||||
break;
|
||||
}
|
||||
restoreStyle();
|
||||
dirty = false; //标记不需要重绘
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TextBox::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show) return false;
|
||||
|
||||
bool hover = false;
|
||||
bool oldClick = click;
|
||||
bool consume = false;
|
||||
@@ -66,16 +110,25 @@ bool TextBox::handleEvent(const ExMessage& msg)
|
||||
case StellarX::ControlShape::B_RECTANGLE:
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
|
||||
consume = false;
|
||||
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (hover && msg.message == WM_LBUTTONUP)
|
||||
{
|
||||
click = true;
|
||||
|
||||
const size_t oldLen = text.size();
|
||||
SX_LOGI("TextBox") << SX_T("激活:id=","activate: id=") << id << " mode=" << (int)mode << " oldLen=" << oldLen;
|
||||
|
||||
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;
|
||||
consume = true;
|
||||
}
|
||||
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
||||
@@ -84,16 +137,39 @@ bool TextBox::handleEvent(const ExMessage& msg)
|
||||
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
||||
consume = true;
|
||||
}
|
||||
else if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||
{
|
||||
char* temp = new char[maxCharLen + 1];
|
||||
// 不记录明文,只记录长度变化
|
||||
dirty = InputBox(temp, (int)maxCharLen + 1, "输入框\n不可见输入,覆盖即可", NULL, NULL, NULL, NULL, false);
|
||||
if (dirty) text = temp;
|
||||
delete[] temp;
|
||||
consume = true;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
SX_LOGI("TextBox") << SX_T("文本已更改: id=","text changed: id=") << id
|
||||
<< " oldLen=" << oldLen << " newLen=" << text.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
SX_LOGD("TextBox") << SX_T("文本无变化:id=","no change: id=") << id;
|
||||
}
|
||||
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
draw();
|
||||
requestRepaint(parent);
|
||||
|
||||
if (click)
|
||||
click = false;
|
||||
|
||||
return consume;
|
||||
}
|
||||
|
||||
|
||||
void TextBox::setMode(StellarX::TextBoxmode mode)
|
||||
{
|
||||
this->mode = mode;
|
||||
@@ -142,16 +218,27 @@ void TextBox::setTextBoxBk(COLORREF color)
|
||||
|
||||
void TextBox::setText(std::string text)
|
||||
{
|
||||
if(text == this->text)
|
||||
return; // 文本未改变,无需更新和重绘
|
||||
if (text.size() > maxCharLen)
|
||||
text = text.substr(0, maxCharLen);
|
||||
this->text = text;
|
||||
this->dirty = true;
|
||||
this->dirty = true; // 标记需要重绘,不论是否窗口图形上下文是否已初始化,等第一次绘制时由窗口真正调用 draw() 来重绘显示文本
|
||||
|
||||
//有父控件时请求父控件重绘,无父控件时直接重绘,确保文本更新后界面正确刷新显示
|
||||
if (nullptr != parent)
|
||||
{
|
||||
//通过hasSnap是否持有有效快照,判断控件是否已经绘制过,避免在控件未绘制前/窗口图形上下文未初始化调用draw()导致的错误
|
||||
if (hasSnap)
|
||||
requestRepaint(parent);
|
||||
}
|
||||
else
|
||||
if (hasSnap)
|
||||
draw();
|
||||
|
||||
}
|
||||
|
||||
std::string TextBox::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+679
-134
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,687 @@
|
||||
# StellarX GUI Framework API Documentation (中英双语)
|
||||
|
||||
## CoreTypes(基础类型)
|
||||
|
||||
**CoreTypes** 模块定义了 StellarX 框架中使用的所有基础枚举和结构体类型,以确保类型一致性(Core types module defines all fundamental enums and structs used in the StellarX framework to ensure type consistency)。主要包括以下内容:
|
||||
|
||||
- **FillStyle(填充图案样式)**:定义控件填充图案的枚举类型。例如 Horizontal 表示水平线填充,Vertical 表示垂直线,FDiagonal 表示反斜线,BDiagonal 表示正斜线,Cross 表示十字,DiagCross 表示网格。(Defines patterns for filling control backgrounds. For example, Horizontal for horizontal lines, Vertical for vertical lines, FDiagonal for forward diagonal lines, BDiagonal for backward diagonal lines, Cross for crosshatch, DiagCross for grid pattern.)默认填充图案为水平线 (The default pattern is horizontal line)。
|
||||
|
||||
- **FillMode(填充模式)**:定义控件背景的填充模式,包括纯色、无填充、图案填充、自定义图案、自定义图片填充等。(Defines how control backgrounds are filled: solid color, no fill, hatched pattern, custom pattern, or custom image fill, etc.)例如 Solid(固实填充),Null(不填充),Hatched(图案填充),Pattern(自定义图案),DibPattern(自定义图像)。默认填充模式为 Solid 固实填充 (default is solid fill)。
|
||||
|
||||
- **LineStyle(线型样式)**:定义控件边框的线型风格枚举。例如 Solid 表示实线,Dash 表示虚线,Dot 表示点线,DashDot 表示点划线,DashDotDot 表示双点划线,Null 表示无边框线。(Defines line style for control borders. For example, Solid for solid line, Dash for dashed line, Dot for dotted line, DashDot for dash-dot line, DashDotDot for double dash-dot, Null for no border line.)默认边框线型为实线 (default line style is solid)。
|
||||
|
||||
- **ControlShape(控件形状)**:定义控件的几何形状类型,共提供矩形、圆角矩形、圆形、椭圆,每种形状分别有“有边框”和“无边框”两种版本。(Enumerates geometric shape types for controls: rectangle, rounded rectangle, circle, and ellipse – each in bordered and borderless variants.)例如 RECTANGLE(有边框矩形)、B_RECTANGLE(无边框矩形)、ROUND_RECTANGLE(有边框圆角矩形)、B_ROUND_RECTANGLE(无边框圆角矩形)、CIRCLE(有边框圆形)、B_CIRCLE(无边框圆形)、ELLIPSE(有边框椭圆)、B_ELLIPSE(无边框椭圆)。按钮类支持所有形状,而某些控件可能只支持部分形状(Button supports all shapes, while some controls only support a subset of shapes)。
|
||||
|
||||
- **TextBoxmode(文本框模式)**:定义文本框控件的工作模式枚举,包括 `INPUT_MODE`(用户可输入模式)、`READONLY_MODE`(只读模式)和 `PASSWORD_MODE`(密码模式)。这允许设置文本框是否可输入、仅显示或以密码形式显示。(Defines the operating mode of a TextBox control: `INPUT_MODE` for editable text input, `READONLY_MODE` for read-only display, and `PASSWORD_MODE` for password input mode, allowing configuration of whether the text box accepts input or is display-only or masked as password input.)
|
||||
|
||||
- **ButtonMode(按钮模式)**:定义按钮控件的工作模式枚举,包括 `NORMAL`(普通模式)、`TOGGLE`(切换模式)和 `DISABLED`(禁用模式)。普通模式下按钮每次点击触发回调但不保持状态;切换模式下按钮在选中/未选中之间切换,并触发不同回调;禁用模式下按钮不可点击,显示灰化且文本带删除线。(Defines the working mode of a Button: `NORMAL` mode triggers a callback on each click without maintaining state; `TOGGLE` mode toggles between pressed and unpressed states and triggers different callbacks for each; `DISABLED` mode makes the button non-clickable, typically grayed out with strikethrough text to indicate disabled state.)
|
||||
|
||||
- **MessageBoxType(消息框类型)**:枚举标准消息框的按钮组合类型,包括 OK(只有“确定”按钮)、OKCancel(“确定”和“取消”)、YesNo(“是”和“否”)、YesNoCancel(“是”“否”“取消”)、RetryCancel(“重试”和“取消”)、AbortRetryIgnore(“中止”“重试”“忽略”)等类型。(Enumerates the standard combinations of buttons for a message box: e.g., OK (OK button only), OKCancel (OK and Cancel), YesNo, YesNoCancel, RetryCancel, AbortRetryIgnore, etc.)开发者可根据需要选择消息框包含的按钮组合 (Developers can select the appropriate set of buttons for the message box as needed)。
|
||||
|
||||
- **MessageBoxResult(消息框结果)**:枚举消息框的返回结果类型,与 MessageBoxType 相对应,包括 OK(确定)、Cancel(取消)、Yes(是)、No(否)、Abort(中止)、Retry(重试)、Ignore(忽略)等。(Enumerates the possible results of a message box, corresponding to which button was pressed: OK, Cancel, Yes, No, Abort, Retry, Ignore, etc.)模态消息框会返回这些枚举值表示用户选择 (Modal message boxes return one of these values to indicate the user's choice)。
|
||||
|
||||
- **LayoutMode(布局模式)**:定义窗口拉伸时控件的布局策略,包括 `Fixed`(固定布局)和 `AnchorToEdges`(锚定布局)。(Defines how controls behave when the window is resized: `Fixed` for no resizing (controls maintain their size/position), and `AnchorToEdges` for anchoring controls to container edges so they stretch or move accordingly.)通过 **Control** 的接口可以设置控件的布局模式 (The layout mode for a control can be set via the **Control** class’s interface)。
|
||||
|
||||
- **Anchor(锚点位置)**:定义控件相对于父窗口的锚定边缘位置,用于 AnchorToEdges 布局模式。包括 Left(左锚定)、Right(右锚定)、Top(上锚定)、Bottom(下锚定)以及 NoAnchor(不锚定)。可以组合两个锚点来固定控件的两个方向。(Defines which edges of the parent container a control is anchored to (used when layout mode is AnchorToEdges). Options include Left, Right, Top, Bottom, plus NoAnchor for no anchoring. Two anchors (horizontal and vertical) are typically used together to lock the control’s position in both directions.)
|
||||
|
||||
- **TabPlacement(选项卡位置)**:定义 TabControl 选项卡标签栏的位置,包括 Top(页签在顶部)、Bottom(在底部)、Left(在左侧)、Right(在右侧)。选择不同值会将选项卡的标签(按钮)放置在容器的不同边缘。(Defines the placement of the tab headers in a TabControl: Top, Bottom, Left, or Right. The chosen value determines on which edge of the TabControl the tab buttons are displayed.)
|
||||
|
||||
- **RouRectangle(圆角矩形参数)**:用于定义控件圆角矩形形状时圆角的椭圆尺寸的结构体。包含两个整数成员 ROUND_RECTANGLEwidth 和 ROUND_RECTANGLEheight,默认值均为 20,表示圆角矩形拐角处椭圆的宽度和高度。(A struct defining the ellipse size of the corners for rounded rectangle shapes. It has two int members, ROUND_RECTANGLEwidth and ROUND_RECTANGLEheight (both default to 20), which represent the width and height of the ellipse used for the rounded corners of a rectangle.)通过修改这个结构体,可以调整控件圆角的弧度 (By modifying these values, one can adjust the curvature of a control’s rounded corners).
|
||||
|
||||
- **ControlText(控件文本样式)**:定义控件字体和文本颜色样式的结构体。它包含多个字段用于描述文本外观:
|
||||
|
||||
- `nHeight`:字体高度(像素值)(font height in pixels)
|
||||
- `nWidth`:字体宽度(像素值,如果为0则自动适配高度)(font width in pixels; 0 means auto-adjust to height)
|
||||
- `lpszFace`:字体名称,默认“微软雅黑” (font face name, default is "微软雅黑" font)
|
||||
- `color`:文字颜色,默认黑色 (text color, default RGB(0,0,0) black)
|
||||
- `nEscapement`:字符串整体旋转角度(单位0.1度,如900表示90度)(text escapement angle for the entire string, in tenths of degrees, e.g., 900 means 90°)
|
||||
- `nOrientation`:单个字符旋转角度(单位0.1度)(orientation angle of individual characters, also in tenths of degrees)
|
||||
- `nWeight`:字体粗细(字重),0表示默认,范围0~1000 (font weight/thickness, 0 means default; range 0~1000)
|
||||
- `bItalic`:是否斜体 (italic flag)
|
||||
- `bUnderline`:是否下划线 (underline flag)
|
||||
- `bStrikeOut`:是否删除线 (strikethrough flag)
|
||||
|
||||
此结构可用于控件的文本样式自定义,如 **Button**、**Label** 等控件都有 `textStyle` 成员使用该结构。(This struct is used to customize text appearance for controls. For example, **Button**, **Label**, etc., include a `textStyle` member of this type to specify font and text style.)
|
||||
|
||||
## Control(控件基类)
|
||||
|
||||
**类名**:`Control`
|
||||
**中文简要说明**:所有控件的抽象基类,定义通用接口和基础功能,不直接实例化。(*Abstract base class for all UI controls, defining common interfaces and fundamental functionality. It is not meant to be instantiated directly*。)
|
||||
|
||||
**继承关系**:无(Control 为顶级基类)(None. Control is the top-level base class for all controls).
|
||||
|
||||
**功能摘要**: Control 提供控件的基本属性(如位置、尺寸)和通用方法,并实现绘图状态保存/恢复机制,确保控件绘制不影响全局状态。它还声明了一些纯虚方法供子类实现,例如 `draw()` 和 `handleEvent()`。(*The Control class provides basic properties (position, size, visibility) and common methods for controls. It implements mechanisms for saving and restoring drawing state to ensure control rendering does not disturb global graphics state. It also declares abstract methods like `draw()` and `handleEvent()` that subclasses must implement.*)
|
||||
|
||||
**公共方法 (Public Methods)**:
|
||||
|
||||
- `draw()` – **纯虚函数**,由子类实现,用于绘制控件内容 (pure virtual; draws the control’s appearance, implemented by each concrete control subclass).
|
||||
- `bool handleEvent(const ExMessage& msg)` – **纯虚函数**,由子类实现,处理输入事件消息,返回事件是否被控件消费 (pure virtual; handles input events for the control and returns true if the event is consumed)。
|
||||
- `void setIsVisible(bool show)` – 设置控件的可见性。传入 `false` 将隐藏控件,`true` 将显示控件。默认实现改变内部 `show` 状态并将控件标记为需要重绘 (Sets the control’s visibility. False to hide the control, true to show it. The base implementation updates the internal `show` flag and marks the control as needing redraw)。**(说明**:某些复合控件会重载此方法以同步子控件,例如 **TabControl** 对该方法进行了重载,实现显示/隐藏所有选项卡页) (Note: Some composite controls override this to also affect their children – e.g. **TabControl** overrides setIsVisible to show/hide all tab pages).
|
||||
- `void setDirty(bool dirty)` – 将控件标记为“脏”,表示需要重绘 (Marks the control as dirty, i.e., requiring a redraw). 默认实现设置内部标志,并**对容器控件**会将所有子控件一起标记 (Base implementation sets the internal flag; container controls like Canvas also mark child controls dirty).
|
||||
- `void setParent(Control* parent)` – 设置控件的父容器指针 (Assigns a parent control container to this control). 用于建立控件层次关系 (Used to establish parent-child relationship for nested controls).
|
||||
- `bool IsVisible() const` – 返回控件当前是否处于可见状态 (Checks if the control is currently set to be visible).
|
||||
- `std::string getId() const` – 获取控件的标识字符串ID (Returns the string identifier of the control).
|
||||
- `bool isDirty() const` – 判断控件是否被标记为需要重绘 (Returns whether the control is marked as dirty (needing redraw)).
|
||||
- `void setX(int x), setY(int y)` – 设置控件左上角的位置坐标 (Sets the control’s top-left position). 基类实现更新位置并将控件标记为脏,需要重绘 (Base implementation updates the coordinate and marks the control dirty)。某些容器控件(如 **Canvas**)会重载这些方法,以便在移动自身时同步更新子控件位置 (Some container controls like **Canvas** override these to also adjust children’s positions when the container moves).
|
||||
- `void setWidth(int w), setHeight(int h)` – 设置控件尺寸宽度和高度 (Sets the control’s width and height). 基类实现更新尺寸并将控件标记为需要重绘 (Base implementation updates the size and marks the control dirty)。
|
||||
- `int getX() const, getY() const, getWidth() const, getHeight() const` – 获取控件当前的全局坐标位置及宽高 (Returns the control’s current absolute X, Y coordinates and its width and height)。
|
||||
- `int getRight() const, getBottom() const` – 获取控件右边缘和下边缘的全局坐标 (Returns the coordinate of the control’s right edge (x+width) and bottom edge (y+height)).
|
||||
- `int getLocalX() const, getLocalY() const, getLocalWidth() const, getLocalHeight() const` – 获取控件相对于父容器的坐标和尺寸 (Returns the control’s position and size in its parent’s local coordinate space).
|
||||
- `int getLocalRight() const, getLocalBottom() const` – 获取控件右边缘和下边缘相对于父容器坐标系的位置 (Returns the local-space coordinates of the control’s right and bottom edges).
|
||||
- **布局相关方法 (Layout-related)**:
|
||||
- `void setLayoutMode(StellarX::LayoutMode mode)` – 设置控件的布局模式,如固定或锚定布局 (Sets the control’s layout mode (e.g., Fixed or AnchorToEdges)). 基类存储所选模式,窗口大小变化时会参考此设置调整控件 (The base class stores the mode; on window resize, this informs how the control should adjust, if at all).
|
||||
- `void setAnchor(StellarX::Anchor anchor1, StellarX::Anchor anchor2)` – 设置控件在锚定布局模式下的水平和垂直锚点 (Sets the horizontal and vertical anchor positions for AnchorToEdges layout mode). 例如可以将控件锚定在左上(Top+Left)以保持与窗口左上角的距离不变 (For example, anchoring Top+Left keeps the control’s top-left distance to the container constant).
|
||||
- `StellarX::Anchor getAnchor_1() const, getAnchor_2() const` – 获取当前设置的水平和垂直锚点值 (Returns the currently configured primary and secondary anchor).
|
||||
- `StellarX::LayoutMode getLayoutMode() const` – 获取当前布局模式 (Gets the current layout mode).
|
||||
- `void onWindowResize()` – 虚函数,窗口尺寸变化时由系统或父容器调用,用于让控件丢弃过期的背景缓存并适应新尺寸。基类实现默认丢弃背景快照并标记控件为脏,需要重绘 (Virtual function called when the window or parent container is resized. The base implementation discards any saved background snapshot and marks the control dirty for redraw)。大多数控件使用基类行为,但容器控件会在重载中调整子控件布局 (Most controls use the base behavior, but container controls override this to adjust child layouts).
|
||||
- **其他方法 (Other methods)**:
|
||||
- `void updateBackground()` – 主动释放旧背景快照并重新保存当前背景,用于在控件尺寸变化后更新缓存以防止显示错位。调用该方法会抓取控件当前位置的新背景 (This forces the control to discard any old background image and capture a new background snapshot, typically used after resizing to avoid misalignment of cached content).
|
||||
- `virtual bool model() const = 0` – **纯虚函数**,用于对话框控件检查自身是否为模态。非对话框类可实现为简单返回 false。(*Pure virtual function to indicate if the control is a modal dialog. Non-dialog controls implement this to always return false; only dialog-related classes override it to reflect modal state*).
|
||||
|
||||
**保护方法 (Protected Methods)**:*(通常供控件内部或子类使用)*
|
||||
|
||||
- `void requestRepaint(Control* parent)` – 向上请求父容器重绘。本控件或其子控件需要重绘时调用,遍历到最顶层容器触发实际重绘。基础实现为:如果存在父容器则递归通知父容器重绘,否则(无父,已到窗口根)调用 `onRequestRepaintAsRoot()` (Requests the parent to schedule a repaint of this control. Called when this control (or one of its children) becomes dirty and needs redraw. The base implementation propagates the request up the chain: if a parent exists, call parent’s requestRepaint, otherwise if at the root, call `onRequestRepaintAsRoot()` to trigger a top-level redraw).
|
||||
- `void onRequestRepaintAsRoot()` – 当控件是最顶层且需要重绘时,由框架调用以执行实际的刷新动作。典型实现是在窗口事件循环中检测到需要重绘时调用 (Called when the control is the root (no parent) and a repaint has been requested, to perform the actual redraw. In practice, the framework calls this during the window’s event loop when it detects a repaint is needed).
|
||||
- `void saveBackground(int x, int y, int w, int h)` – 保存控件区域在屏幕上的背景图像快照,用于实现擦除重绘时不留下残影。在控件首次绘制或移动/隐藏时抓取其背景 (Saves a snapshot of the background under the control’s area (at position x,y with size w×h) to facilitate restoring the background when the control is moved or removed, preventing ghosting artifacts).
|
||||
- `void restBackground()` – 恢复先前保存的背景快照,将其绘制回控件区域,从而擦除控件上一次绘制的内容。(Restores the previously saved background image to the control’s area, effectively erasing the control’s last drawn appearance. Typically used at the start of a redraw to clear old content.)
|
||||
- `void discardBackground()` – 丢弃并释放当前保存的背景快照。当控件或窗口尺寸变化、控件销毁时需要调用以防止使用失效的背景缓存。(Discards the stored background snapshot (if any). Called when the control or window size changes, or when the control is destroyed, to avoid using an outdated background cache.)
|
||||
- `void saveStyle()` / `void restoreStyle()` – 保存当前全局绘图状态(颜色、线型、填充等)并在绘制完成后恢复,以防止控件绘制改变全局状态。Control 基类实现了对 EasyX 图形状态的保存和恢复,每个控件在 `draw()` 开始时应调用 `saveStyle()`,结束时调用 `restoreStyle()` (Saves the current global drawing state (colors, line style, fill style, etc.) and restores it after drawing, to ensure the control’s drawing doesn’t alter the global state. The Control base provides these to wrap drawing code: call `saveStyle()` at the start of `draw()`, and `restoreStyle()` at the end).
|
||||
|
||||
**成员变量 (Member Variables)** *(除特殊说明外,均为 `protected` 访问权限 — Protected unless otherwise noted)*:
|
||||
|
||||
- `std::string id` – 控件的字符串ID标识符 (String identifier for the control). 可能用于调试或控件查找 (Often used for debugging or identifying controls).
|
||||
|
||||
- `int x, y` – 控件左上角的**全局**坐标 (X and Y coordinates of the control’s top-left corner in the **global (window) coordinate space**).
|
||||
|
||||
- `int width, height` – 控件当前的宽度和高度 (Current width and height of the control).
|
||||
|
||||
- `int localx, localy` – 控件左上角相对于父控件的局部坐标 (Local X, Y position relative to its parent control’s origin). 对于顶级控件(如窗口直接子控件),local 坐标通常与全局相同 (For top-level controls (children of the window), local coordinates are typically the same as global).
|
||||
|
||||
- `int localWidth, localHeight` – 控件在父容器坐标系下的尺寸大小 (Width and height of the control in the parent’s coordinate system). 初始化时等于控件自身宽高,但在父容器缩放或布局时可能用于计算 (Initialized to the control’s own width/height; used in layout calculations if needed).
|
||||
|
||||
- `Control* parent` – 父控件指针 (Pointer to the parent control). 为 `nullptr` 则表示本控件无父(可能是顶层元素)(If `nullptr`, this control has no parent, i.e., it’s a root element).
|
||||
|
||||
- `bool dirty` – 控件重绘标记。如果为 true 表示控件内容已改变或无效,需要在下一个周期重新绘制 (Dirty flag indicating the control needs to be redrawn. True means the control’s content has changed or invalidated and should be redrawn on the next cycle). 绘制完成后通常将 dirty 重置为 false (After drawing, this is reset to false until something changes again).
|
||||
|
||||
- `bool show` – 控件可见性标志。true 表示控件应显示,false 则控件被隐藏,绘制时应跳过 (Visibility flag: true if the control should be visible, false if hidden. Hidden controls are skipped during rendering).
|
||||
|
||||
- **布局属性 (Layout-related)**:
|
||||
|
||||
- `StellarX::LayoutMode layoutMode` – 控件的布局模式(Fixed 或 AnchorToEdges),决定窗口大小变化时的位置调整策略 (Specifies the control’s layout mode (Fixed or AnchorToEdges), determining how it behaves when the window is resized). 默认为 Fixed 固定布局。
|
||||
- `StellarX::Anchor anchor_1, anchor_2` – 控件相对父容器的锚点设置(如 Top/Bottom/Left/Right)。当 layoutMode 为 AnchorToEdges 时使用,表示控件的哪个边固定在父容器的哪个边上 (Anchor positions (primary and secondary) relative to parent container, used when layoutMode is AnchorToEdges. Determines which side(s) of the control remain fixed to which side of the parent). 例如 anchor_1=Top, anchor_2=Right 表示控件上边和右边相对父窗口距离保持不变 (e.g., Top + Right means the control’s top and right edges maintain constant distance from the parent’s top and right edges).
|
||||
|
||||
- **背景快照属性 (Background snapshot)**:
|
||||
|
||||
- `IMAGE* saveBkImage` – 保存的背景图像指针,用于在控件重绘前恢复覆盖区域背景 (Pointer to an IMAGE storing the snapshot of the background behind the control). 如果不为空,表示当前持有一份有效的背景缓存 (If not null, a valid background image is stored).
|
||||
- `int saveBkX, saveBkY` – 背景快照在屏幕上的保存起始坐标 (The X, Y coordinates of where the background snapshot was taken).
|
||||
- `int saveWidth, saveHeight` – 背景快照区域的宽度和高度 (The width and height of the saved background area).
|
||||
- `bool hasSnap` – 标记当前是否有有效的背景快照 (Flag indicating whether a valid background snapshot is currently stored).
|
||||
|
||||
- `StellarX::RouRectangle rouRectangleSize` – 控件圆角矩形参数。当控件形状为圆角矩形时,使用该结构决定圆角的大小 (Stores the control’s rounded rectangle corner sizes. Used when the control’s shape is a rounded rectangle to determine the curvature radii)。默认圆角宽高为20 (Defaults to 20 for both width and height, as defined in RouRectangle).
|
||||
|
||||
- **绘图状态缓存 (Drawing state caches)**: 为了在控件绘制时保存并恢复全局绘图状态,每个控件维护以下当前状态指针:
|
||||
|
||||
- `LOGFONT* currentFont` – 当前字体样式的备份指针 (Pointer to a LOGFONT storing the current font style before control drawing).
|
||||
- `COLORREF* currentColor` – 当前文本绘制颜色的备份 (Pointer storing current text color).
|
||||
- `COLORREF* currentBkColor` – 当前背景填充颜色的备份 (Pointer storing current background fill color).
|
||||
- `COLORREF* currentBorderColor` – 当前边框颜色的备份 (Pointer storing current border color).
|
||||
- `LINESTYLE* currentLineStyle` – 当前线型样式的备份 (Pointer storing current line style settings).
|
||||
|
||||
以上指针在控件销毁时会删除以释放资源。调用 `saveStyle()` 时,这些指针会指向保存的全局状态;`restoreStyle()` 则将全局绘图状态恢复并重置这些指针 (These are allocated to hold copies of global drawing settings; they are deleted in the Control destructor to free resources. When `saveStyle()` is called, the current global drawing settings are stored in these objects, and `restoreStyle()` reapplies them and resets the pointers).
|
||||
|
||||
- **禁用复制和移动 (Deleted copy/move)**: 为防止误用,Control 明确删除了复制构造和赋值运算符,以及移动构造和赋值 (Copy constructor and assignment operator, as well as move constructor and move assignment, are deleted to prevent copying or moving of controls)。控件对象不可被复制,只能以指针或智能指针方式管理 (Control objects cannot be copied; they should be managed via pointers or smart pointers).
|
||||
|
||||
- **构造函数与析构函数 (Constructors & Destructor)**:
|
||||
|
||||
- `Control(int x, int y, int width, int height)` – **受保护构造函数**,使用指定的位置和尺寸初始化控件基本属性。被派生类调用,用于设置控件初始的 local/global 坐标和宽高 (Protected constructor used by subclasses to initialize the control’s position (both local and global) and size).
|
||||
- `Control()` – **受保护默认构造函数**,初始化控件位置为 (0,0),宽高为 (100,100)。通常不会显式使用,除非子类需要默认大小的控件 (Initializes the control at origin (0,0) with a default size of 100×100. Typically invoked by subclass default constructors if needed).
|
||||
- `virtual ~Control()` – 析构函数,基类析构确保清理分配的资源。它删除并释放上述 `currentFont` 等绘图状态缓存指针,并调用 `discardBackground()` 释放背景快照 (Base destructor cleans up allocated resources, deleting the stored font, color, and line style objects, setting them to null, and calling `discardBackground()` to release any saved background image)。
|
||||
|
||||
**依赖关系 (Dependencies)**:
|
||||
|
||||
- **操作系统/图形库**:Control 使用 Win32 API 数据类型(如 `HWND`、`COLORREF` 等)以及 EasyX 图形库进行绘图管理。因此需要 Windows 平台支持 (The Control class includes Windows headers and depends on EasyX graphics library for drawing operations, hence it runs on Windows platform).
|
||||
- **框架核心类型**:Control 依赖 `CoreTypes.h` 中定义的 StellarX 命名空间枚举和结构,如 LayoutMode、Anchor、ControlShape、RouRectangle、ControlText 等,用于自身属性 (It relies on types defined in CoreTypes, such as LayoutMode, Anchor, ControlShape, RouRectangle, and ControlText for its properties and behavior).
|
||||
- **子类关系**:Control 是所有具体控件(按钮、标签、文本框、表格等)的基类。比如 **Button**, **Label**, **TextBox**, **Table** 等类都公开继承自 Control (All concrete control classes like Button, Label, TextBox, Table, etc., publicly inherit from Control). Control 提供的接口(draw、handleEvent等)由子类实现,实现各自的外观和行为 (The interfaces provided by Control (draw, handleEvent, etc.) are overridden by these subclasses to implement their specific appearance and behavior).
|
||||
- **容器关系**:尽管 Control 本身不包含子控件列表,但其派生的容器类(如 **Canvas**、**Window** 等)利用继承的接口管理子控件集合 (Control itself does not store child controls, but derived container classes like Canvas or Window use Control’s interface to manage child controls).
|
||||
|
||||
## 派生控件(Derived Controls)
|
||||
|
||||
以下类均继承自 **Control** 基类,表示常见的可视控件组件。这些控件实现了 Control 定义的接口,并根据需要添加自己的属性和方法。每个控件的继承关系和特定功能如下: *(The following classes all derive from the **Control** base class, representing common UI components. They implement the interfaces defined by Control and add their own properties and methods as needed. Each control’s inheritance and specific features are detailed below:)*
|
||||
|
||||
### Button(按钮控件)
|
||||
|
||||
**类名**:`Button`
|
||||
**继承**:继承自 Control (`class Button : public Control`)
|
||||
|
||||
**简要说明**:多功能按钮控件,支持多种状态和样式。提供完整的按钮交互,包括普通点击、切换开关、禁用等模式,并支持自定义外观(颜色、形状、填充)和鼠标悬停提示等。(*A versatile button control supporting multiple states and styles. It provides full button functionality including normal click, toggle (on/off) mode, and disabled state. The Button supports extensive customization of appearance (colors, shape, fill patterns) and features such as hover tooltips.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Button(int x, int y, int width, int height, const std::string& text, StellarX::ButtonMode mode = NORMAL, StellarX::ControlShape shape = RECTANGLE)` – **构造函数**,创建按钮,指定位置尺寸、显示文本,以及可选的按钮模式和形状。将按钮初始化为默认颜色配置 (Constructs a button at given position and size with the specified label text, and optional ButtonMode and shape. Initializes the button with default color settings for its states).
|
||||
- `Button(int x, int y, int width, int height, const std::string& text, COLORREF ct, COLORREF cf, StellarX::ButtonMode mode = NORMAL, StellarX::ControlShape shape = RECTANGLE)` – **构造函数**,创建具有自定义“按下/未按”颜色的按钮。参数 `ct` 为按钮被点击时颜色,`cf` 为按钮未点击时颜色 (Constructor to create a button with custom colors for the pressed (`ct`) and unpressed (`cf`) states).
|
||||
- `Button(int x, int y, int width, int height, const std::string& text, COLORREF ct, COLORREF cf, COLORREF ch, StellarX::ButtonMode mode = NORMAL, StellarX::ControlShape shape = RECTANGLE)` – **构造函数**,创建具有自定义“按下/未按/悬停”颜色的按钮。额外参数 `ch` 指定鼠标悬停时的按钮背景颜色 (Constructor to create a button with custom pressed (`ct`), unpressed (`cf`), and hover (`ch`) colors).
|
||||
- `~Button()` – **析构函数**,销毁按钮时释放可能加载的图像资源。例如如果按钮使用了自定义填充图像,将在析构时清理 (Releases any resources such as loaded images when the button is destroyed).
|
||||
- `void draw() override` – 绘制按钮外观。实现包括根据按钮状态(正常、悬停、按下、禁用)设置颜色和填充,并绘制按钮边框和文本等。按钮绘制支持多种形状,如果是圆形或椭圆按钮,会进行鼠标区域判定的特殊处理 (Renders the button. The implementation sets the appropriate colors/fill based on button state (normal, hover, pressed, disabled), draws the button’s border and filled shape, and then draws the text centered on the button. Different shapes (rectangle, rounded rect, circle, ellipse) are handled, including special hit-testing for circular/elliptical shapes).
|
||||
- `bool handleEvent(const ExMessage& msg) override` – 处理按钮的鼠标事件。包括检测鼠标按下、释放、移入、移出等,以更新按钮的 `click`(按下状态)和 `hover`(悬停状态),并触发相应回调 (Handles mouse events for the button. This includes detecting mouse down/up to update the `click` state (pressed or not), tracking mouse enter/leave to set the `hover` state, and triggering the appropriate callbacks when clicked or toggled).
|
||||
- 回调设置方法 (Callback setters):
|
||||
- `void setOnClickListener(std::function<void()>&& callback)` – 设置按钮在 NORMAL 模式下点击时执行的回调函数。当按钮每次被点击(鼠标按下然后松开)且模式为 NORMAL 时调用此回调 (Assigns a callback to be invoked when the button is clicked in NORMAL mode).
|
||||
- `void setOnToggleOnListener(std::function<void()>&& callback)` – 设置按钮在 TOGGLE 模式下从未选中切换为选中状态时的回调。当按钮切换到“按下/选中”状态时调用 (Sets the callback for when a toggle-mode button is toggled on (pressed state)).
|
||||
- `void setOnToggleOffListener(std::function<void()>&& callback)` – 设置按钮在 TOGGLE 模式下从选中切换为未选中状态时的回调。当按钮从按下恢复为弹起状态时调用 (Sets the callback for when a toggle-mode button is toggled off (released state)).
|
||||
- 模式和形状设置 (Mode/Shape settings):
|
||||
- `void setbuttonMode(StellarX::ButtonMode mode)` – 设置按钮的工作模式(NORMAL/TOGGLE/DISABLED)。更改模式会影响按钮行为:设置 TOGGLE 会使按钮保持按下状态,设置 DISABLED 会使按钮不可点击 (Changes the button’s operating mode; e.g., setting TOGGLE makes the button stay pressed when clicked, setting DISABLED grays it out and disables interaction).
|
||||
- `void setButtonShape(StellarX::ControlShape shape)` – 设置按钮形状(矩形、圆角矩形、圆形、椭圆等)。修改形状会影响按钮绘制的轮廓和鼠标命中区域 (Sets the geometric shape of the button. This changes how the button is drawn (e.g., with rounded corners or as a circle) and how hover/click hit-testing is calculated).
|
||||
- 外观属性设置 (Appearance setters):
|
||||
- `void setROUND_RECTANGLEwidth(int width)` / `void setROUND_RECTANGLEheight(int height)` – 若按钮形状为圆角矩形,设置其圆角椭圆的宽度或高度。用于调整圆角大小 (If the button’s shape is a rounded rectangle, these adjust the horizontal/vertical radius of the corner’s ellipse to change the corner roundness).
|
||||
- `void setFillMode(StellarX::FillMode mode)` – 设置按钮背景的填充模式。例如纯色填充、图案填充等 (Sets the fill mode for the button’s background, e.g., solid color, hatched pattern, image pattern, etc.).
|
||||
- `void setFillIma(StellarX::FillStyle style)` – 设置按钮背景填充的图案样式。只有当 FillMode 为 Hatched 有效,用于选择具体的填充图案 (Sets the hatched fill pattern for the button’s background, effective if FillMode is set to Hatched. Chooses which hatch style to use).
|
||||
- `void setFillIma(std::string imageName)` – 设置按钮背景填充为指定文件的图像。这会加载给定路径的图像用于填充按钮背景(FillMode 应设为 Pattern/DibPattern)(Uses an external image file to fill the button background. This will load the image from the given file path; the FillMode should be set to Pattern or DibPattern to use the image fill).
|
||||
- `void setButtonBorder(COLORREF color)` – 设置按钮边框颜色。更改边框绘制使用的颜色 (Sets the border color of the button’s outline).
|
||||
- `void setButtonFalseColor(COLORREF color)` – 设置按钮未被按下时的背景颜色。即按钮处于弹起状态的填充色 (Sets the background color for the button’s unpressed (false) state).
|
||||
- `void setButtonText(const char* text)` / `void setButtonText(std::string text)` – 设置按钮显示的文本标签。更改按钮上显示的文字内容 (Sets the label text displayed on the button).
|
||||
- `void setButtonClick(BOOL clicked)` – 强制设置按钮按下状态。传 true 则使按钮显示为按下状态,false 则恢复未按状态;通常用于程序控制 TOGGLE 模式按钮的状态 (Forces the button’s pressed state. True makes the button appear pressed (selected), false makes it unpressed. This is mainly useful for programmatically controlling the state of a TOGGLE mode button).
|
||||
- 查询方法 (Getters):
|
||||
- `bool isClicked() const` – 返回按钮当前是否处于按下(选中)状态。TOGGLE 模式下按下一次后该状态会保持,NORMAL 模式下每次点击会短暂为 true (Indicates whether the button is currently in the pressed state. In TOGGLE mode, this stays true after being clicked until toggled off; in NORMAL mode it is true only during the click).
|
||||
- `std::string getButtonText() const` / `const char* getButtonText_c() const` – 获取按钮的文本标签内容。提供 std::string 和 C 字符串两种形式 (Returns the text label of the button, as a std::string or C-string).
|
||||
- `StellarX::ButtonMode getButtonMode() const` – 获取按钮当前的模式(NORMAL/TOGGLE/DISABLED).
|
||||
- `StellarX::ControlShape getButtonShape() const` – 获取按钮当前形状类型.
|
||||
- `StellarX::FillMode getFillMode() const` – 获取按钮当前填充模式.
|
||||
- `StellarX::FillStyle getFillIma() const` – 获取按钮当前填充图案样式.
|
||||
- `IMAGE* getFillImaImage() const` – 如果按钮使用了图像填充,获取当前填充所用的 IMAGE 对象指针.
|
||||
- `COLORREF getButtonBorder() const` – 获取按钮边框颜色.
|
||||
- `COLORREF getButtonTextColor() const` – 获取按钮文字颜色 (Returns the text color used for the button’s label). *(说明:文字颜色存储在 `ControlText` 的 color 字段中,此方法便于直接获取)*(Note: The text color is actually stored in the `ControlText` struct as part of textStyle; this getter provides convenient access to it.)*.
|
||||
- `StellarX::ControlText getButtonTextStyle() const` – 获取按钮的文字样式结构副本。包含字体、字号、颜色等信息 (Returns a copy of the ControlText struct representing the button’s current text style, including font face, size, color, etc.).
|
||||
- **Tooltip 提示功能方法** (Button 提示工具条 API):
|
||||
- `void enableTooltip(bool on)` – 启用或禁用鼠标悬停提示功能。设为 true 则当鼠标悬停在按钮上时显示提示文本 (Enable or disable a tooltip that appears when the mouse hovers over the button). 关闭提示时会立即隐藏当前提示 (Disabling tooltips will hide any currently visible tooltip immediately).
|
||||
- `void setTooltipDelay(int ms)` – 设置提示出现的延迟时间(毫秒)。例如 1000 表示鼠标悬停1秒后显示提示 (Sets the delay in milliseconds before the tooltip is shown on hover. e.g., 1000 means the tooltip appears after 1 second of hovering).
|
||||
- `void setTooltipFollowCursor(bool on)` – 设置提示框是否跟随鼠标移动。如果为 true,则提示将出现在鼠标附近位置,并随鼠标移动更新 (Determines if the tooltip should follow the cursor. If true, the tooltip will appear near the cursor and move along with it).
|
||||
- `void setTooltipOffset(int dx, int dy)` – 设置提示框相对于鼠标位置的偏移。可调整提示框出现的位置,例如 (12, 18) 表示在鼠标坐标基础上右移12,下移18 像素 (Sets an offset for the tooltip’s position relative to the cursor. For example, (12, 18) will position the tooltip 12px to the right and 18px below the cursor).
|
||||
- `void setTooltipStyle(COLORREF textColor, COLORREF backgroundColor, bool transparent)` – 设置提示框文本颜色、背景颜色及是否背景透明。若 transparent 为 true,则提示背景不绘制矩形底色 (Configures the tooltip’s text color, background color, and transparency. If `transparent` is true, the tooltip has no opaque background, allowing underlying graphics to show through behind the text).
|
||||
- `void setTooltipText(const std::string& text)` – 设置按钮在 NORMAL 模式下的提示文本。当按钮未切换或不在TOGGLE模式时,将显示此提示内容 (Sets the tooltip text for the button (used in NORMAL mode or when the button is not toggling between two states)). 调用该函数会将 tipTextClick 设置为给定内容,并标记用户自定义标志以避免覆盖 (This updates the internal `tipTextClick` and flags that a custom tooltip is set by the user).
|
||||
- `void setTooltipTextsForToggle(const std::string& onText, const std::string& offText)` – 设置按钮在 TOGGLE 模式下不同状态的提示文本。第一个参数用于按钮选中(Pressed)时的提示,第二个用于未选中(Released)时的提示 (Defines two tooltip strings for a toggle-mode button: one when the button is toggled on (pressed) and one when toggled off).
|
||||
|
||||
**成员变量** *(Button的大部分成员为私有 private)*:
|
||||
|
||||
- `std::string text` – 按钮当前显示的文本 (The text label displayed on the button).
|
||||
- `bool click` – 按钮按下状态标志。true 表示按钮当前呈现为按下(TOGGLE模式下为选中),false 表示未按下。(Indicates if the button is in a pressed state. True when the button is currently pressed (or toggled on), false when not pressed.)
|
||||
- `bool hover` – 按钮悬停状态标志。true 表示鼠标当前在按钮区域内 (Indicates if the mouse cursor is currently hovering over the button).
|
||||
- `std::string cutText` – 裁剪后的文本。如果按钮文本过长需要裁剪显示,则存储裁剪结果 (A possibly truncated version of the text used for rendering if the full text does not fit in the button).
|
||||
- `bool needCutText` – 标志是否需要裁剪文本。当文本长度超出按钮宽度时设为 true (Flag indicating if text needs to be truncated to fit the button width).
|
||||
- `bool isUseCutText` – 标志当前是否使用裁剪后的文本显示。为 true 则表示 draw 时使用 `cutText` 而非完整 text (Indicates whether the button is currently using the truncated `cutText` for display instead of the full `text`).
|
||||
- `int padX, padY` – 文本绘制的内边距 (padding)。padX 是左右最小内边距像素,padY 是上下内边距 (Minimum horizontal (padX) and vertical (padY) padding in pixels around the button’s text). 默认 padX=6, padY=4.
|
||||
- **颜色设置 (Color settings)**:
|
||||
- `COLORREF buttonTrueColor` – 按钮被点击(选中)时的填充背景颜色 (Background color when the button is in pressed/true state).
|
||||
- `COLORREF buttonFalseColor` – 按钮未被点击时的填充背景颜色 (Background color when the button is in unpressed/false state).
|
||||
- `COLORREF buttonHoverColor` – 鼠标悬停在按钮上时的背景颜色 (Background color when the mouse is hovering over the button).
|
||||
- `COLORREF buttonBorderColor` – 按钮边框颜色,默认黑色 (The color of the button’s border; default is black).
|
||||
- `StellarX::ButtonMode mode` – 当前按钮模式(NORMAL/TOGGLE/DISABLED).
|
||||
- `StellarX::ControlShape shape` – 当前按钮形状(矩形、圆角矩形、圆形或椭圆等).
|
||||
- `StellarX::FillMode buttonFillMode` – 按钮填充模式(纯色、图案等),默认 Solid 纯色填充.
|
||||
- `StellarX::FillStyle buttonFillIma` – 按钮填充图案样式,默认 BDiagonal(右斜线网格),当 FillMode=Hatched 时使用.
|
||||
- `IMAGE* buttonFileIMAGE` – 填充按钮背景的图像指针(如使用自定义图片填充),默认为 nullptr.
|
||||
- **回调函数**:
|
||||
- `std::function<void()> onClickCallback` – 按钮点击回调函数指针 (Function to call on button click in normal mode).
|
||||
- `std::function<void()> onToggleOnCallback` – 按钮 TOGGLE 模式下切换为选中状态时的回调 (Function to call when a toggle button is toggled on).
|
||||
- `std::function<void()> onToggleOffCallback` – 按钮 TOGGLE 模式下切换为未选中状态时的回调 (Function to call when a toggle button is toggled off).
|
||||
- `StellarX::ControlText oldStyle` – 按钮文本原样式的备份 (Backup of the button’s text style before certain changes). 一些操作如禁用按钮时可能暂时修改文本样式(如加删除线),oldStyle 保存原始样式以便恢复 (For example, when disabling the button, the text might be styled with a strikethrough; `oldStyle` stores the original style so it can be restored when re-enabled).
|
||||
- `int oldtext_width, oldtext_height` – 记录上一次绘制时文本的宽度和高度 (Stores the width and height of the text from the last draw, used to detect if recalculation or re-cutting is needed).
|
||||
- `int text_width, text_height` – 当前文本的像素宽度和高度 (The measured pixel width and height of the current text string, used for centering and for truncation logic).
|
||||
- **Tooltip 提示相关**:
|
||||
- `bool tipEnabled` – 是否启用了鼠标悬停提示 (Whether the tooltip feature is enabled for this button).
|
||||
- `bool tipVisible` – 当前提示是否正显示 (Whether the tooltip is currently visible on screen).
|
||||
- `bool tipFollowCursor` – 提示框是否跟随鼠标移动 (Whether the tooltip should move with the cursor).
|
||||
- `bool tipUserOverride` – 用户是否自定义了提示文本 (Flag indicating if the user has overridden the tooltip text via setTooltipText; if false, default texts might be used for toggle states).
|
||||
- `int tipDelayMs` – 提示显示延迟毫秒数,默认1000ms (Delay in milliseconds before tooltip appears; default is 1000 ms).
|
||||
- `int tipOffsetX, tipOffsetY` – 提示框相对鼠标的偏移 (Tooltip offset from the cursor in X and Y).
|
||||
- `ULONGLONG tipHoverTick` – 记录鼠标开始悬停的时间戳 (Timestamp (in GetTickCount units) when the mouse started hovering over the button).
|
||||
- `int lastMouseX, lastMouseY` – 记录最近一次鼠标所在的位置,用于确定 tooltip 出现位置 (The last known mouse coordinates over the button, used for positioning the tooltip).
|
||||
- `std::string tipTextClick` – NORMAL 模式或 Toggle 未选中状态下使用的提示文本 (Tooltip text used in normal mode or when toggle button is in off state).
|
||||
- `std::string tipTextOn` – Toggle 按钮选中状态下使用的提示文本 (Tooltip text when the toggle button is in the on state).
|
||||
- `std::string tipTextOff` – Toggle 按钮未选中状态下使用的提示文本 (Tooltip text when the toggle button is in the off state).
|
||||
- `Label tipLabel` – 用于显示 tooltip 的 Label 控件对象。内部直接复用一个 Label 来呈现提示文本。tooltip 逻辑通过操作 tipLabel 的显示和内容实现 (A Label object used internally to render the tooltip text. The button’s tooltip functionality is implemented by configuring and showing this label near the cursor when needed).
|
||||
|
||||
**依赖**:Button 依赖 **Label** 控件来显示其 Tooltip 提示 (The Button class internally uses a **Label** control to display tooltips). 它还使用框架的 **ControlShape**、**FillMode**、**FillStyle** 等枚举定义不同的外观选项 (It uses framework enums like ControlShape, FillMode, FillStyle, etc., for its appearance settings). Button 是常用控件,通常作为对话框按钮或触发某动作的 UI 元素使用 (Button is a fundamental interactive control, commonly used in dialogs or as a trigger for actions in the UI).
|
||||
|
||||
### Label(标签控件)
|
||||
|
||||
**类名**:`Label`
|
||||
**继承**:继承自 Control (`class Label : public Control`)
|
||||
|
||||
**简要说明**:静态文本标签控件,用于显示只读文本内容。Label 不处理用户输入,主要作用是呈现文本,可支持背景透明。(*A static text label control for displaying read-only text. The Label does not handle user input and is mainly for presenting text, with support for transparent background.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Label()` – 默认构造函数,创建一个空文本的标签,使用默认字体颜色黑色、背景白色,默认大小适配文本 (Default constructor that creates a label with default text “标签” (Chinese for “Label”), black text color, white background. The size will be determined based on text content once set).
|
||||
- `Label(int x, int y, std::string text = "标签", COLORREF textColor = BLACK, COLORREF bkColor = RGB(255,255,255))` – 重载构造函数,在指定位置创建带初始文本的标签,可指定文字颜色和背景色。背景默认白色,text默认“标签” (Overloaded constructor to create a label at given position with initial text and optional text color and background color. The default text is “标签” (label), default text color black, default background white).
|
||||
- `void draw() override` – 绘制标签内容。实现为:如果 `textBkDisap` 为 false,则先绘制背景矩形(填充 `textBkColor`),然后设置文字颜色和字体,将 `text` 绘制在控件位置。如果 `textBkDisap` 为 true,则不绘制背景使其透明叠加 (Renders the label. If `textBkDisap` (background disappear flag) is false, it fills a rectangle with `textBkColor` as background; then draws the `text` at the label’s position using the current font and text color. If `textBkDisap` is true, no background rectangle is drawn, allowing whatever is behind the label to show through, achieving a transparent background effect). Label 根据其文本和样式自动适应大小 (The label’s drawn size adapts to the text content and style).
|
||||
- `void hide()` – 将标签隐藏。实现上相当于 `setIsVisible(false)`,并(可能)清除或不再绘制标签内容 (Hides the label, equivalent to calling setIsVisible(false) so it will no longer be drawn. This is a convenience method to quickly make the label invisible).
|
||||
- `void setTextdisap(bool transparent)` – 设置标签背景是否透明。传入 true 则开启背景透明模式(不绘制背景颜色),false 则有背景色填充 (Enables or disables transparent background for the label. True means the label will not draw any background (transparent), false means it will fill a background with its background color).
|
||||
- `void setTextBkColor(COLORREF color)` – 设置标签背景颜色。仅当背景不透明时有效,改变 Label 绘制时填充的背景矩形颜色 (Sets the background color of the label. Has effect only if the label is not set to transparent; changes the color used to fill behind the text).
|
||||
- `void setText(std::string newText)` – 设置标签显示的文本内容。更新文本后会自动调整控件大小以适应新文本长度,并将控件标记为需要重绘 (Changes the text displayed by the label. After setting, the label may resize itself to fit the new text (depending on implementation) and will be marked dirty for redraw).
|
||||
|
||||
**事件处理**:Label 重写了 `handleEvent` 但不做任何处理,总是返回 false。这意味着标签不拦截或消费任何事件,事件会传递给底层控件 (The Label overrides `handleEvent` to do nothing and always return false, meaning it does not consume any events and they will pass through it).
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- `std::string text` – 标签显示的文本内容 (The text string displayed by the label).
|
||||
- `COLORREF textBkColor` – 文本背景色 (Background color behind the text). 默认情况下在构造时设为白色或指定颜色 (Set in constructor, default white unless specified).
|
||||
- `bool textBkDisap` – 文本背景是否透明的标志。true 表示背景透明(不绘制背景色),false 表示绘制不透明背景 (Flag indicating if the label’s background is transparent. True means do not draw the background (transparent), false means fill background with `textBkColor`). 默认 false (Default is false, meaning background is drawn).
|
||||
- `StellarX::ControlText textStyle` – 标签文本的样式 (字体、字号、颜色等)。默认使用黑色字体、默认大小 (Defaults to a standard font (e.g., 12pt “微软雅黑”) and black color, can be modified to change font style or color). 开发者可以直接修改该结构体的字段(如 textStyle.color 等)然后调用 `draw()` 刷新,以改变标签文字外观 (Developers can modify this struct’s fields (e.g., textStyle.color for text color) and then redraw to change the label’s text appearance).
|
||||
|
||||
**依赖**:Label 主要依赖 **ControlText** 结构来存储字体和颜色信息,使用 Windows GDI(通过 EasyX)绘制文本。它无子控件且不处理事件,常作为静态显示用途 (The Label uses **ControlText** for font info and EasyX (GDI) to draw text. It has no children and performs no event logic, typically used for static display).
|
||||
|
||||
### TextBox(文本框控件)
|
||||
|
||||
**类名**:`TextBox`
|
||||
**继承**:继承自 Control (`class TextBox : public Control`)
|
||||
|
||||
**简要说明**:文本输入框控件,支持用户输入和只读显示两种模式,并提供可选的密码模式。内部集成 EasyX 的输入框功能来实现用户输入。(*A text box control that supports both user input mode and read-only display mode, as well as an optional password mode. It internally leverages EasyX’s input box capabilities for text input.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = INPUT_MODE, StellarX::ControlShape shape = RECTANGLE)` – 构造函数,在指定位置创建文本框,初始化显示文本、模式和形状。默认模式为可输入,默认形状为有边框矩形 (Constructs a TextBox at the given position and size, with an initial text (which defaults to empty), a mode (defaults to input mode), and shape (defaults to a bordered rectangle)).
|
||||
- `void draw() override` – 绘制文本框外观。根据当前形状绘制文本框的边框和背景,然后绘制文本内容。若模式为密码模式,则绘制时会隐藏实际文字(例如用星号替代)(Renders the text box. It draws the rectangle (or chosen shape) for the text box border and background, then draws the text. If the mode is password, the actual characters are hidden (likely rendered as asterisks or bullets)).
|
||||
- `bool handleEvent(const ExMessage& msg) override` – 处理文本框的鼠标和键盘事件。当检测到鼠标点击文本框时,若模式为可输入,则调用 EasyX 提供的 `InputBox` 弹出文本输入对话框获取用户输入。获取输入后更新内部文本并标记重绘 (Handles events for the text box. If a mouse click is detected within the text box and the mode is INPUT_MODE, it uses EasyX’s InputBox (a system modal input dialog) to get user input. After the user enters text and closes the InputBox, the TextBox updates its internal text with the new value and marks itself dirty for redraw). 在 READONLY_MODE 下,点击不会触发输入框 (In READONLY_MODE, clicking does nothing).
|
||||
- `void setMode(StellarX::TextBoxmode mode)` – 设置文本框的工作模式(输入或只读或密码)。更改模式会影响交互行为:只读模式下禁止输入,密码模式下输入的文字以特殊符号显示 (Sets the TextBox’s mode. Changing the mode alters its behavior: in READONLY_MODE, user input is disabled; in PASSWORD_MODE, the text is masked when displayed).
|
||||
- `void setMaxCharLen(size_t len)` – 设置文本框允许输入的最大字符长度。限制用户通过 InputBox 输入的字符数量 (Sets the maximum number of characters that can be input in the text box. This limits the length of text accepted by the InputBox dialog).
|
||||
- `void setTextBoxshape(StellarX::ControlShape shape)` – 设置文本框的形状类型。可选矩形或圆角矩形等,但一般文本框使用矩形外观 (Sets the shape of the text box (rectangle, rounded rectangle, etc.). Typically text boxes use a rectangular shape).
|
||||
- `void setTextBoxBorder(COLORREF color)` – 设置文本框边框颜色。改变文本框四周边框的绘制颜色 (Changes the border color of the text box’s outline).
|
||||
- `void setTextBoxBk(COLORREF color)` – 设置文本框背景填充颜色。 (Sets the background fill color of the text box).
|
||||
- `void setText(std::string text)` – 设置文本框显示的文本内容。如果在 INPUT_MODE 下,可以程序matically预填内容;在 READONLY_MODE 下用于显示只读文本 (Sets the text content of the text box. In input mode, can be used to pre-fill text; in read-only mode, use it to display static text). 设置后标记控件需要重绘 (After setting, the control is marked dirty for redraw).
|
||||
- `std::string getText() const` – 获取文本框当前的文本内容。如果处于密码模式,返回的仍是实际文本而非掩码字符 (Returns the current text content of the text box. In password mode, this returns the actual stored text, not the masked display).
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- `std::string text` – 文本框当前包含的文本内容 (The text currently stored/displayed in the text box).
|
||||
- `StellarX::TextBoxmode mode` – 文本框当前模式(INPUT_MODE 可输入、READONLY_MODE 只读、PASSWORD_MODE 密码).
|
||||
- `StellarX::ControlShape shape` – 文本框外观形状(矩形、圆角矩形等).
|
||||
- `bool click` – 文本框点击状态标志。用于指示当前控件是否处于选中/激活(点击后正在输入)的状态。例如,click 为 true 时可能表示输入框正在获取输入 (A flag possibly indicating if the textbox is “active” or clicked. True might mean the text box was clicked and is awaiting input, though the exact usage depends on implementation details).
|
||||
- `size_t maxCharLen` – 文本框允许输入的最大字符数,默认10。超过此长度的输入会被截断 (Maximum number of characters allowed in the text box. Defaults to 10. Input beyond this length will be truncated or disallowed).
|
||||
- `COLORREF textBoxBkClor` – 文本框背景颜色,默认白色 (Background color of the text box, default is white).
|
||||
- `COLORREF textBoxBorderClor` – 文本框边框颜色,默认黑色 (Border color of the text box, default is black).
|
||||
- `StellarX::ControlText textStyle` – 文本框文本样式。决定文本绘制的字体、颜色等 (The font and text appearance settings for the text content of the text box). 可以调整其中字体大小以改变文本框中文字的显示大小 (Modifying this (e.g., font height) will change how the text appears inside the box).
|
||||
|
||||
**依赖**:TextBox 依赖 **CoreTypes** 中的 **TextBoxmode** 枚举来区分模式,还使用 EasyX 的 `InputBox` 函数进行实际文本输入对话框(在 Windows 环境下弹出模态输入窗口)。在图形上,它类似 **Button** 使用 **ControlShape** 控制边框形状。TextBox 提供了简单的文本输入能力,对于复杂文本编辑需要另行扩展 (Internally, the TextBox calls EasyX’s InputBox to get user input in a modal dialog, which is a limitation to Windows platform. Graphically, it uses ControlShape for its outline similar to Button. It is intended for basic text input; more complex text editing would require additional functionality beyond this control).
|
||||
|
||||
### Table(表格控件)
|
||||
|
||||
**类名**:`Table`
|
||||
**继承**:继承自 Control (`class Table : public Control`)
|
||||
|
||||
**简要说明**:高级表格控件,用于显示二维表格数据,支持分页显示和大量数据高效渲染。Table 提供表头、数据行分页、翻页按钮等完整功能,适合用于数据报告、记录浏览等场景。(*An advanced table/grid control for displaying tabular data. It supports pagination and efficient rendering of large data sets. The Table provides features such as headers, paginated data rows, navigation buttons for page flips, etc., making it suitable for reports, record browsers, and similar use cases.*)
|
||||
|
||||
**主要特性**: 自动分页计算、每页行数可配置、自适应列宽行高、翻页导航按钮、背景缓存优化绘制性能等。(*Key features include automatic pagination and page index calculation, configurable rows per page, auto-calculated column widths and row heights, navigation buttons for paging, and background buffering to optimize rendering performance.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Table(int x, int y)` – 构造函数,在指定位置创建一个空的表格控件。默认表格初始宽高由内容决定(通常在设置数据后计算)。默认每页显示行数为 5。创建后需调用 `setHeaders` 和 `setData` 来填充表头和数据 (Constructs a Table at the given position (top-left). The initial size may be determined after data is provided. By default, it shows 5 rows per page. After construction, one should call `setHeaders` and `setData` to populate the table).
|
||||
- `~Table()` – 析构函数,清理表格资源 (Destructor to clean up any resources. It will delete any dynamically allocated components, like navigation buttons or labels created internally).
|
||||
- `void draw() override` – 绘制表格当前页的内容。绘制流程包括表头、数据行以及页脚(页码和翻页按钮)。Table 内部通过辅助的私有 `drawHeader()`, `drawTable()`, `drawPageNum()`, `drawButton()` 方法分别绘制不同部分。(Draws the table for the current page. The drawing routine includes rendering the header row, the data rows for the current page, and the footer (page number display and navigation buttons). Internally it likely uses helper functions like `drawHeader()`, `drawTable()` (for data cells), `drawPageNum()`, and `drawButton()` for different sections.) Table 会根据内容自动计算列宽和行高,并支持隔行变色、高亮当前行等(如果实现了的话)(The Table calculates column widths and row heights based on content, and may support features like alternating row colors or highlighting if implemented).
|
||||
- `bool handleEvent(const ExMessage& msg) override` – 处理表格的事件。包括翻页按钮的点击事件,可能还有对行的点击选择等 (Handles events for the table, such as detecting clicks on the "Previous" or "Next" page buttons, and potentially row click events for selection if such feature exists). 当点击翻页按钮时,会更新 `currentPage` 并触发重绘显示新页数据 (On clicking navigation buttons, it updates `currentPage` and triggers a redraw to display the new page of data).
|
||||
- 设置数据与外观的方法 (Data and appearance setters):
|
||||
- `void setHeaders(std::initializer_list<std::string> headers)` – 设置表格表头列名。传入一个字符串列表,每个元素作为一列的标题。调用后会自动计算列宽,并标记需要重绘表头 (Sets the table’s header labels. The initializer_list of strings provides a title for each column. This triggers a recalculation of column widths and marks the table for redraw).
|
||||
- `void setData(std::vector<std::string> data)` – 设置表格数据。**注意**: 这里参数类型可能是错误,应为 `std::vector<std::vector<std::string>>` 或初始化列表(见下)(Note: The signature in code appears to be `setData(std::vector<std::string>)` which is likely a typo; actual implementation probably expects a 2D structure. Use the initializer_list overload for clarity).
|
||||
- `void setData(std::initializer_list<std::vector<std::string>> data)` – 设置表格数据。传入一个列表,每个元素是代表一行的字符串向量。Table 将保存数据并自动分页计算 `totalPages` 和 `currentPage` 等 (Sets the table data by providing an initializer_list of rows, where each row is a vector of strings for the columns. The table stores this data and computes `totalPages` based on rowsPerPage, resetting currentPage to 1).
|
||||
- `void setRowsPerPage(int rows)` – 设置每页显示的行数。改变此值会重新计算总页数并调整当前显示 (Sets how many rows are displayed per page. Changing this will recalculate `totalPages` and adjust the current page if needed).
|
||||
- `void showPageButton(bool show)` – 设置是否显示翻页按钮。如果数据量小仅一页,开发者可选择隐藏翻页控制 (Determines whether the page navigation buttons (prev/next) are visible. If the data fits on one page, one might hide the navigation controls).
|
||||
- `void setTableBorder(COLORREF color)` – 设置表格边框颜色(单元格网格线颜色)。改变表格绘制时单元格边框和外框的颜色 (Sets the color used for the table grid lines and border).
|
||||
- `void setTableBk(COLORREF color)` – 设置表格背景颜色。作为单元格背景的底色 (Sets the background color used for the table’s cells).
|
||||
- `void setTableFillMode(StellarX::FillMode mode)` – 设置表格单元格背景填充模式。可选择纯色或图案填充 (Sets the fill mode for the table cells background, e.g., solid fill or hatched pattern).
|
||||
- `void setTableLineStyle(StellarX::LineStyle style)` – 设置表格线型样式。用于单元格边框线,可选实线、虚线等 (Sets the line style for the table grid lines (cells borders), e.g., solid, dashed).
|
||||
- `void setTableBorderWidth(int width)` – 设置表格网格线和外边框的宽度(像素)(Sets the width (thickness) of the table’s grid lines and border in pixels).
|
||||
- `void clearHeaders()` – 清空表头。将 headers 列表置空,并标记需要重绘 (Clears all header labels. After calling, the table will have no columns defined until new headers are set).
|
||||
- `void clearData()` – 清空表格数据。移除所有数据行,重置当前页等 (Clears all table data rows and resets pagination state).
|
||||
- `void resetTable()` – 清空表头和数据。相当于依次调用 clearHeaders() 和 clearData(), 将表格恢复初始空状态 (Clears both headers and data, resetting the table to an initial empty state).
|
||||
- `void onWindowResize() override` – 当容器窗口大小变化时调用。实现为丢弃背景快照并标记表格为脏,需要在新尺寸下重绘 (Called when the window is resized. The Table’s implementation likely discards any saved background (if using caching) and marks itself dirty to redraw according to the new size).
|
||||
- 查询方法 (Getters):
|
||||
- `int getCurrentPage() const` – 获取当前页码(从1开始计数).
|
||||
- `int getTotalPages() const` – 获取总页数.
|
||||
- `int getRowsPerPage() const` – 获取每页显示的行数设置.
|
||||
- `bool getShowPageButton() const` – 获取当前是否设置为显示翻页按钮.
|
||||
- `COLORREF getTableBorder() const` – 获取表格边框(网格线)颜色.
|
||||
- `COLORREF getTableBk() const` – 获取表格背景颜色.
|
||||
- `StellarX::FillMode getTableFillMode() const` – 获取表格当前填充模式.
|
||||
- `StellarX::LineStyle getTableLineStyle() const` – 获取表格当前线型样式.
|
||||
- `std::vector<std::string> getHeaders() const` – 获取当前表头列表的副本.
|
||||
- `std::vector<std::vector<std::string>> getData() const` – 获取当前表格数据的副本.
|
||||
- `int getTableBorderWidth() const` – 获取表格边框/网格线宽度.
|
||||
- `int getTableWidth() const, int getTableHeight() const` – 获取表格当前总宽度和总高度。这通常对应绘制时计算出的整个表格占用尺寸,包括表头和页脚 (Returns the overall width and height of the table as currently rendered, including header and footer areas. These are typically calculated during the last draw).
|
||||
|
||||
**成员变量** *(主要为私有属性)*:
|
||||
|
||||
- `std::vector<std::vector<std::string>> data` – 表格数据存储,按行列组织的字符串二维数组。每个内部 `std::vector<std::string>` 代表一行的数据,所有行长度应与 headers 列数匹配 (Stores the table’s data in a 2D vector of strings (rows × columns). Each inner vector represents one row. The number of elements in each row should match the number of header columns).
|
||||
- `std::vector<std::string> headers` – 表格列标题列表。长度即列数 (List of column headers. The size of this vector defines the number of columns in the table).
|
||||
- `std::string pageNumtext` – 页码标签文本模版。默认值 "页码标签",可能用于显示当前页/总页的信息 (A template or base text for the page number label, default "页码标签". This might be combined with current page info to display pagination status like “第 X 页/共 Y 页”).
|
||||
- `int tableBorderWidth` – 表格边框和网格线宽度,默认值来自 `TABLE_DEFAULT_BORDER_WIDTH` 宏,通常为1 (Thickness of table grid lines and border. Default comes from TABLE_DEFAULT_BORDER_WIDTH, typically 1 pixel).
|
||||
- `std::vector<int> colWidths` – 每列宽度像素值的数组。绘制时每列的起始位置和单元格矩形基于此计算 (Array of pixel widths for each column. These are calculated from header/text lengths and used to layout the table cells).
|
||||
- `std::vector<int> lineHeights` – 每行高度像素值的数组。通常各行高度相同,由字体高度和 padding 决定;也可能表头行和数据行区分 (Array of heights for each row. Often uniform for all data rows and possibly a different height for header; determined by text height and vertical padding).
|
||||
- `int rowsPerPage` – 每页显示行数,默认 5.
|
||||
- `int currentPage` – 当前页码,默认 1(第一页).
|
||||
- `int totalPages` – 总页数,默认 1。根据数据行数和 rowsPerPage 计算得到 (Total number of pages, computed based on number of data rows and rowsPerPage).
|
||||
- **特性开关**:
|
||||
- `bool isShowPageButton` – 是否显示翻页按钮,默认 true.
|
||||
- `bool isNeedDrawHeaders` – 是否需要绘制表头标志,暂未使用(代码注释为“暂时废弃”).
|
||||
- `bool isNeedCellSize` – 是否需要计算单元格尺寸标志。可能用于延迟计算优化 (Flag indicating if cell sizes need recalculation. Possibly used to optimize and avoid recalculating cell metrics on every draw).
|
||||
- `bool isNeedButtonAndPageNum` – 是否需要计算翻页按钮和页码信息标志。类似地用于控制是否更新页脚 (Flag indicating if navigation buttons and page number label need to be recalculated/positioned. Could be used to skip re-layout of footer if not needed).
|
||||
- **导航控件**:
|
||||
- `Button* prevButton` – “上一页”按钮指针。指向 Table 内部创建的 Button 对象,用于翻页到前一页。nullptr 表示未创建或隐藏 (Pointer to the "Previous Page" button control. Created internally by the Table for pagination. If nullptr, the button is not present or not needed).
|
||||
- `Button* nextButton` – “下一页”按钮指针。用于翻到下一页 (Pointer to the "Next Page" button).
|
||||
- `Label* pageNum` – 页码显示的标签指针。用于显示当前页/总页信息 (Pointer to a Label that displays the page number text, e.g., "第 X 页/共 Y 页").
|
||||
- **内部坐标缓存**(用于绘制过程的临时值):
|
||||
- `int dX, dY` – 当前单元格绘制的起始坐标 (likely "draw X/Y": the starting x,y for drawing cells). 初始化为表格区域左上角 (Initialized to x,y of the table). 绘制过程中更新以逐行逐列推进 (These are updated as the table draws each cell to track the current drawing position).
|
||||
- `int uX, uY` – 当前单元格绘制的结束坐标 (likely "end X/Y": maybe unused or similar to dX,dY as drawing coordinates).
|
||||
- `int pX, pY` – 页码标签 Label 的左上角坐标 (Coordinates for where the page number label is placed).
|
||||
- **颜色和样式**:
|
||||
- `StellarX::FillMode tableFillMode` – 表格单元格填充模式, 默认 Solid。
|
||||
- `StellarX::LineStyle tableLineStyle` – 表格网格线线型, 默认 Solid 实线。
|
||||
- `COLORREF tableBorderClor` – 表格边框和网格线颜色, 默认黑色 (RGB(0,0,0))。
|
||||
- `COLORREF tableBkClor` – 表格背景底色, 默认白色 (RGB(255,255,255))。
|
||||
- `StellarX::ControlText textStyle` – 表格内容文本样式。用于设置表格单元格中文字的字体和颜色,默认继承自 ControlText 默认值 (Determines font and color for text drawn in the table’s cells). 可以修改例如字体大小以调整表格内容字号 (Can be adjusted to change text size of table content).
|
||||
|
||||
**依赖**:Table 控件内部组合使用 **Button**(翻页按钮)和 **Label**(页码显示)控件来实现完整功能,因此它依赖这些子控件的接口来处理事件和绘制文字。Table 作为容器包含这些控件并在翻页时对它们进行布局和更新 (The Table internally composes **Button** controls for navigation and a **Label** for page number, thus it relies on their functionality for event handling and text rendering. The Table acts as a container for these controls, positioning and updating them as pages change). Table 还会使用 GDI 绘制单元格边框和背景,对性能要求高时利用了背景缓存等优化 (The Table uses GDI (EasyX) to draw cell grids and background, and likely employs background buffering optimizations to handle large data efficiently).
|
||||
|
||||
## 复合控件(Composite Controls)
|
||||
|
||||
复合控件是指包含其他控件作为子元素,实现更复杂界面功能的控件。例如 TabControl 包含多个页面 Canvas 和对应的按钮,Dialog 包含文本、按钮等控件。下面是这些复合控件的接口说明: *(Composite controls contain other controls as child elements to provide more complex UI functionality. For example, TabControl contains multiple Canvas pages and their respective tab buttons; Dialog contains text and button controls. Below are the interface details of these composite controls:)*
|
||||
|
||||
### TabControl(选项卡控件)
|
||||
|
||||
**类名**:`TabControl`
|
||||
**继承**:继承自 Canvas (`class TabControl : public Canvas`)
|
||||
|
||||
**简要说明**:选项卡容器控件,可在单一界面区域内承载多个子页面,通过页签(标签按钮)进行切换。TabControl 提供类似选项卡对话框的界面,每个选项卡有一个标题按钮和对应的内容区域。(*A tabbed container control that can hold multiple child “pages” in the same area and switch between them using tab header buttons. It provides a tabbed dialog-like interface where each tab has a header button and an associated content area.*)
|
||||
|
||||
**功能特点**:TabControl 支持将页签栏放置在容器的不同位置(上、下、左、右)。每个选项卡由一个按钮(作为页签)和一个 Canvas 容器(作为页面内容)组成。一次仅显示一个选项卡的内容,其余页面隐藏。TabControl 自动管理页签按钮的布局和当前激活页内容的显示切换。(*The TabControl allows the tab bar to be placed at the top, bottom, left, or right of the control. Each tab consists of a Button (tab header) and a Canvas (page content). Only one page is visible at a time, with others hidden. The TabControl automatically handles laying out the tab header buttons and toggling the visibility of the page content for the active tab.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `TabControl()` – 默认构造函数,创建一个默认大小的选项卡控件。内部调用 Canvas 基类构造将位置设为 (0,0), 宽高默认100×100,并设置控件ID为 "TabControl" (Calls the Canvas constructor to initialize a TabControl at (0,0) of default size, and sets its id to "TabControl").
|
||||
- `TabControl(int x, int y, int width, int height)` – 构造函数,在指定位置和尺寸创建一个选项卡控件。初始化后无选项卡,需要通过 `add` 方法添加页面 (Creates a TabControl at specified position with given width and height. Initially it contains no tabs; use `add()` to add tab pages).
|
||||
- `void add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control)` – **添加选项卡页面**,将一个按钮-页面对添加为新的选项卡。`pair.first` 是页签按钮,`pair.second` 是对应内容 Canvas。调用该方法后,TabControl 会负责管理该按钮和页面的布局与显示 (Adds a new tab to the TabControl given a pair of a Button (tab header) and a Canvas (tab page content). After adding, the TabControl takes ownership and will manage the layout and display of the tab’s button and page). 调用后自动调整所有页签按钮大小并初始化新页面坐标,将新选项卡默认设置为隐藏内容。(The method will recalc the size of all tab buttons, position the new page, and by default hide the new page’s content so that it’s not shown until activated.)
|
||||
- `void add(std::string tabText, std::unique_ptr<Control> control)` – **向已有选项卡添加子控件**。此重载用于将一个新的控件添加到已有的某个选项卡页面内。`tabText` 指定目标选项卡的标题文本,`control` 是要添加的控件指针。调用时会在匹配标题的选项卡页面 Canvas 内添加该控件,并保持其相对坐标 (Adds a control to one of the existing tab pages. The `tabText` identifies which tab’s page to add to (by matching the tab’s title text), and `control` is the new child control to add. The method sets the new control’s parent to the target page’s Canvas and adjusts its coordinates to be relative to that page). 如果 `tabText` 没找到对应页签,则不执行操作 (If no tab with the given title is found, the call does nothing).
|
||||
- `void setTabPlacement(StellarX::TabPlacement placement)` – 设置选项卡页签栏的位置。可以在 TabControl 的顶部、底部、左侧或右侧显示页签。调用后立即重新布局页签和页面区域 (Sets the placement of the tabs (Top, Bottom, Left, or Right). Recomputes the layout of tab header buttons and page content areas accordingly).
|
||||
- `void setTabBarHeight(int height)` – 设置页签栏的高度(或宽度,如果在左/右侧)。调整这个值可以改变页签按钮的大小。默认高度有最小值限制 (Sets the height of the tab bar (if tabs are top/bottom) or width (if tabs are left/right)). This effectively changes the size of each tab button on that dimension. There is an internal minimum enforced for tab button size to ensure usability (e.g., `BUTMINHEIGHT`, `BUTMINWIDTH`).
|
||||
- `void setActiveIndex(int index)` – 显式设置当前激活的选项卡索引。索引从0开始。若指定索引有效且不等于当前激活,则将对应页签按钮设为点击状态,切换显示该页内容。如果在首次绘制前调用,则会将此索引存为默认激活页 (Sets the active tab by index (0-based). If called after the control is shown and the index is valid (and different from current), it programmatically “clicks” that tab’s button to activate the page. If called before the first draw (initialization phase), the index is stored as the default to activate on first render rather than switching immediately).
|
||||
- `int getActiveIndex() const` – 获取当前激活的选项卡索引。如果没有页签被选中则可能返回 -1 (Returns the index of the currently active tab. If no tab is active, it returns -1).
|
||||
- `int count() const` – 获取当前选项卡总数量.
|
||||
- `int indexOf(const std::string& tabText) const` – 根据页签标题文本查找对应的索引。如果存在多个同名页签,返回第一个匹配的索引;如未找到则返回 -1 (Returns the index of the tab with the given title text. If multiple tabs have the same title, returns the first match. Returns -1 if not found).
|
||||
- `void setIsVisible(bool visible) override` – 重载自 Canvas:设置整个 TabControl(以及其中所有页签按钮和页面)的可见性。如果设置为可见,则只显示当前激活的页面,但**所有**页签按钮都会显示;如果设置为不可见,则页签和页面全部隐藏。此外,该实现会确保隐藏状态下子页面不会遗留背景快照 (Overrides Canvas::setIsVisible to show/hide the TabControl along with all its tabs and pages. When made visible, all tab header buttons are shown and only the currently active page’s Canvas is shown (others remain hidden); when invisible, all tabs and pages are hidden. It also handles clearing out saved snapshots for hidden pages to prevent stale visuals).
|
||||
- `void onWindowResize() override` – 当父窗口尺寸改变时调用。实现为:调用 Control 基类的 onWindowResize() 丢弃自身背景,然后重新计算页签栏和页面区域布局。之后对每个页签按钮和页面调用各自 onWindowResize() 使其内容也调整 (On parent window resize, the TabControl’s override first calls Control::onWindowResize() to discard its background and mark dirty. It then recalculates the tab bar and page layout (size and position) for the new control size. Finally it forwards the onWindowResize call to each tab’s Button and Canvas so they can adjust their internals if needed).
|
||||
- `void setDirty(bool dirty) override` – 重载自 Control:将 TabControl 及其所有子页签按钮和页面的脏标记设为给定值。通常在需要整组重绘时调用 (Overrides Control::setDirty to set the dirty flag for the TabControl itself and all its child tab buttons and page Canvases to the given value (true/false). This is useful to invalidate or validate the entire tab set at once).
|
||||
- `void requestRepaint(Control* parent) override` – 重载自 Control:处理 TabControl 自身或子控件请求重绘的逻辑。如果 `parent` 参数就是当前 TabControl 本身,则表示一次针对整个选项卡控件的重绘请求;实现中会检查每个子控件(按钮和页面)的 dirty 状态,将需要重绘的子控件各自绘制一遍。否则(parent 不是本控件),调用基类 Canvas 的 onRequestRepaintAsRoot() 让更高层容器处理。(Overrides Control::requestRepaint. If the `parent` passed in is this TabControl itself (meaning a repaint of the whole tab control is requested), the implementation iterates through all tab pairs and if a tab’s Button or Canvas is dirty and visible, it calls their draw() to repaint them. If `parent` is not this (meaning the request is bubbled further up), it defers to the base behavior by calling onRequestRepaintAsRoot().) 这一特殊实现确保当 TabControl 本身重绘时,其子页面内容也会刷新 (This ensures that when the TabControl is repainted, all visible child content is also redrawn).
|
||||
|
||||
**事件处理**:TabControl 实现自己的 `handleEvent` 逻辑,将事件分发给当前页的子控件或页签按钮。具体而言,当鼠标点击一个页签按钮时,相应按钮会触发回调使该选项卡切换为激活状态;TabControl::handleEvent 会优先将事件传给所有页签按钮(通常是排列在最上层)检查是否有按钮点击。如果没有页签按钮消费事件,则再把事件传递给当前激活的页面 Canvas 里的子控件。这样实现当用户点击不同区域时由不同元素响应 (The TabControl’s event handling sends incoming messages first to the tab header buttons (which are typically drawn above the page content) to see if any button consumes the event (e.g., a click on a tab). If none of the buttons handle the event, it then forwards the event to the currently visible page’s Canvas (which in turn dispatches to its child controls). This ensures the correct element responds depending on where the user clicked).
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- `std::vector< std::pair< std::unique_ptr<Button>, std::unique_ptr<Canvas> > > controls` – 存储选项卡的容器,包含多个 (按钮, 页面Canvas) 对。每个 pair 的 first 是页签按钮,second 是对应内容页面 (Vector holding the tab definitions: each element is a pair where first is the tab header Button and second is the page Canvas for that tab). 这个容器是 TabControl 最重要的结构,用于迭代布局和事件处理 (This is central to TabControl’s operation for iterating through tabs for layout, drawing, and event dispatch).
|
||||
- `int tabBarHeight` – 页签栏高度(或宽度),用于决定页签按钮尺寸。默认值可能为某个常量,例如30像素 (Height of the tab bar. Determines the size of tab header buttons in the minor axis. Default might be around 30px, ensuring a minimum usable size). 可以通过 `setTabBarHeight` 修改。
|
||||
- `StellarX::TabPlacement tabPlacement` – 当前页签栏位置(Top/Bottom/Left/Right)。默认应该为 Top (Default placement likely Top). 改变该值会在下一次布局时把页签移动到相应边 (Changing this will re-position the tabs on the specified side on next layout recalculation).
|
||||
- `bool IsFirstDraw` – 标记是否尚未绘制过(首次绘制标志)。初始为 true,第一次调用 draw() 后置 false (A flag indicating whether the TabControl has been drawn for the first time or not. Initialized to true, set to false after the first draw). 用于处理 `defaultActivation` 逻辑,在首次绘制时激活预设的选项卡.
|
||||
- `int defaultActivation` – 默认激活选项卡索引。初始可能为 -1,表示未指定。如果在控件显示前通过 `setActiveIndex` 调用设置了一个索引,则该值保存那个索引,在首次绘制时相应页签会被激活。(Stores an index of a tab to activate on first draw. Default -1 meaning none specified. If `setActiveIndex` is called before the control is drawn, this captures the desired active tab, and during the first `draw()` call the TabControl will activate that tab).
|
||||
- **常量**:内部定义的最小按钮宽高常量,如 `BUTMINWIDTH`, `BUTMINHEIGHT`,用于确保无论 TabControl 尺寸多小,每个页签按钮都有合理可点击大小。这些常量可能定义为例如 50 像素宽、20 像素高 (There are internal constants for minimum tab button dimensions (e.g., `BUTMINWIDTH`, `BUTMINHEIGHT`) to guarantee each tab remains a clickable size even if the control is very small).
|
||||
- *(其余布局相关变量和 Canvas 基类的成员,如 x, y, width, height, 以及 Canvas 提供的 children 列表,都通过继承获得)*(Other layout and inherited members: since TabControl extends Canvas, it inherits all Canvas members like x, y, width, height, and the vector of child controls from Canvas. However, in TabControl, instead of using the inherited `controls` vector from Canvas, it manages its own vector of pair for tabs. The Canvas’s own child list might remain empty or unused in this context.)*
|
||||
|
||||
**继承和依赖**:TabControl 直接继承自 **Canvas**(即它本身就是一个可包含控件的画布),这意味着 TabControl 拥有 Canvas 的所有能力(背景绘制、子控件容器等),并进一步组合了多个 Button 和 Canvas 子控件来形成选项卡结构。**Button** 用于页签,**Canvas** 用于页内容,因此 TabControl 强依赖 Button 和 Canvas 的接口 (TabControl inherits from **Canvas**, giving it container capabilities. It composes **Button** controls as tab headers and **Canvas** controls as pages. It heavily relies on the Button interface (for toggling and event callbacks) and Canvas for holding page content). TabControl 本身不依赖外部系统资源,除了通过父 Window 获取尺寸变化事件。作为复杂控件,TabControl 提供了一个在桌面 GUI 中组织多页面内容的便捷组件 (As a composite control, TabControl provides an easy way to organize multi-page content in a desktop GUI, without relying on OS-level tabs).
|
||||
|
||||
### Dialog(对话框控件)
|
||||
|
||||
**类名**:`Dialog`
|
||||
**继承**:继承自 Canvas (`class Dialog : public Canvas`)
|
||||
|
||||
**简要说明**:模态/非模态对话框控件,提供标准消息框的界面和功能。Dialog 可以独立弹出,包含标题、消息文本和一组按钮(如确定、取消等),并支持阻塞主窗口(模态)或非阻塞回调两种模式。(*A dialog box control supporting both modal and modeless operation, providing a standard message box UI with a title, message text, and a set of buttons (e.g., OK, Cancel). The Dialog can be shown modally (blocking the main window) or non-modally with a callback for results.*)
|
||||
|
||||
**主要特性**: 支持多个预定义类型(MessageBoxType 枚举的六种组合按钮),可以根据需要显示相应按钮组合;支持自动根据消息文本长度换行及调整对话框尺寸;在模态模式下弹出会阻塞调用线程直到对话框关闭,在非模态模式下则通过回调通知结果;对话框绘制时会保存背景并在关闭时恢复,以实现半透明效果或防止闪烁。(*Key features include support for several predefined configurations of buttons (according to the MessageBoxType enum), automatic text wrapping and dialog resizing based on content, modal display (blocking until closed) vs. modeless with callbacks, and background saving/restoring to prevent flicker or allow transparency effects.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = OK, bool modal = true)` – 构造函数,创建一个对话框实例。参数:`hWnd` 父窗口引用,用于在该窗口中央显示对话框;`text` 对话框显示的消息内容;`message` 对话框标题文本(默认为“对话框”);`type` 对话框类型(决定按钮组合,默认为 OK 单按钮);`modal` 指定是否模态 (true=模态)。构造函数内会根据类型创建对应的按钮集合、关闭按钮等,并计算对话框初始尺寸 (The constructor sets up the dialog: it stores a reference to the parent Window, saves the provided message text and title, creates the appropriate buttons according to the specified type, adds a close (“X”) button, and computes an initial size based on text length and number of buttons. If `modal` is true, the Dialog is configured to block input to the parent window when shown).
|
||||
- `~Dialog()` – 析构函数,负责销毁对话框内创建的控件(按钮、标签等)并释放背景缓存等资源。若对话框在非模态显示且尚未关闭,析构时应通知父窗口清理引用 (Destroys all child controls created inside the dialog (buttons, labels) and releases any saved background images. If the dialog was modeless and still open, the destructor ensures any references in the parent are cleared).
|
||||
- `void draw() override` – 绘制对话框。Dialog 继承自 Canvas,draw() 实现包含:绘制对话框背景矩形(使用背景颜色 backgroundColor 和边框颜色 borderColor,通常为一个带边框的圆角矩形或矩形);绘制标题栏(包括标题文本Label和关闭按钮);绘制消息文本内容(自动换行后的多行);绘制底部功能按钮区域(按照 type 创建的确定/取消等按钮)。绘制过程中处理背景快照以在出现对话框时保存被遮挡的内容 (The Dialog’s draw method draws the dialog’s background and border (often a rounded rectangle with borderColor), draws the title bar (title text label and close button at the top), renders the message text (splitting it into lines to fit width), and draws the bottom area with the action buttons as specified by the dialog type. It uses background snapshot routines to save what's behind the dialog when it appears and restore upon closing to prevent artifacts).
|
||||
- `bool handleEvent(const ExMessage& msg) override` – 处理对话框的事件。包括:点击关闭按钮时将关闭对话框;点击功能按钮(如确定、取消等)时,将根据按钮设定设置结果枚举,并关闭对话框(模态则结束阻塞返回结果,非模态则通过回调); 以及拖动窗口(如果实现了窗口拖动)(Handles events such as clicks on the close button (triggers Close()), clicks on any of the action buttons (sets the result accordingly and closes the dialog), and possibly window dragging if the title bar is draggable). 在模态情况下,handleEvent 内部可能会自己运行消息循环直到对话框关闭 (In modal mode, handleEvent might not be used in the typical way since control is in a separate loop; but for modeless, it ensures events are passed to child controls).
|
||||
- **设置和获取**:
|
||||
- `void SetTitle(const std::string& title)` – 设置对话框标题文本。更新内部 `titleText` 并刷新标题 Label (Changes the dialog’s title. Updates the internal `titleText` and the title Label control accordingly).
|
||||
- `void SetMessage(const std::string& message)` – 设置对话框主消息文本。更新内部 `message` 并重新拆分行、调整对话框大小 (Sets the main message text of the dialog. Updates the internal `message` string, re-splits it into lines, and recalculates dialog size as needed to accommodate the new text).
|
||||
- `void SetType(StellarX::MessageBoxType type)` – 设置对话框类型。更改需要显示的按钮组合,例如从 OK 切换为 YesNo 等。调用将移除原有按钮并根据新类型创建新按钮,重新布局底部区域 (Changes which buttons are displayed by altering the MessageBoxType. This will remove any existing action buttons and create new ones for the specified type, then reposition them in the footer).
|
||||
- `void SetModal(bool modal)` – 设置对话框的模态属性。true 表示在 Show() 时以模态方式运行(阻塞),false 表示以非模态方式运行 (Configures whether the dialog is modal or not. True means it will run modally (blocking the caller until closed), false means it will be modeless).
|
||||
- `void SetResult(StellarX::MessageBoxResult result)` – 人为设置对话框结果。这通常在对话框逻辑内部使用,例如用户点击按钮后调用,以记录选择的结果 (Manually sets the dialog’s result. Typically used internally when a button is clicked to store the user’s choice prior to closing the dialog).
|
||||
- `StellarX::MessageBoxResult GetResult() const` – 获取对话框结果。仅在对话框关闭后有意义:模态对话框可以通过此函数获取用户选择的结果枚举值 (Returns the dialog’s result (which button was pressed). This is useful after a modal dialog closes to know what the user selected).
|
||||
- `bool model() const override` – 返回对话框是否为模态。Dialog 实现此函数,用于区分模态对话框(返回 true)和非模态(返回 false)。父窗口在处理窗口事件时会据此判断是否需要暂停交互 (Indicates if the dialog is modal. The Dialog overrides `model()` to return the value of its modal flag. This can be used by the parent Window to determine whether user interaction with other controls should be blocked).
|
||||
- `void Show()` – 显示对话框。对于模态对话框,该函数会使对话框在屏幕中央出现并阻塞,直到对话框关闭。对于非模态,则在屏幕中央显示并立即返回 (Displays the dialog. If modal, this will position the dialog (typically centered on parent window) and enter a local event loop, blocking further execution until the dialog is closed by the user. If modeless, it simply makes the dialog visible (likely centered) and returns immediately). Show() 方法内部会将对话框加入父窗口的对话框列表 `Window::dialogs` 并标记窗口存在对话框 (Internally, Show() likely adds this dialog to the parent Window’s `dialogs` vector (for tracking open dialogs) and sets flags like `dialogClose = false` in the window).
|
||||
- `void Close()` – 关闭对话框。将对话框隐藏并从父窗口管理列表中移除,销毁内部控件。模态情况下Close会结束内部阻塞循环,恢复主窗口事件处理;非模态情况下Close则调用已注册的 resultCallback (Closes the dialog. It hides/removes the dialog from view, removes it from the parent window’s tracking list, and triggers cleanup of its child controls. In modal mode, Close breaks out of the modal loop allowing execution to continue in the caller; in modeless mode, Close will invoke the `resultCallback` (if set) to inform of the result asynchronously).
|
||||
- `void setInitialization(bool init)` – 设置是否需要初始化标志。这个内部标志 `needsInitialization` 控制对话框仅在首次 Show 时创建按钮等 (Sets the internal `needsInitialization` flag. If true, indicates that the dialog hasn’t yet fully initialized its UI (buttons/labels) and should do so on next show. Once initialized, this can be set false to avoid reinitializing).
|
||||
- **结果回调**:
|
||||
- `std::function<void(StellarX::MessageBoxResult)> resultCallback` – 非模态对话框结果回调函数。在 Dialog 显示为非模态时可以设置此回调,当用户点击某个按钮关闭对话框后将异步调用该函数并传入选择结果 (Callback function that will be called with the dialog’s result when a modeless dialog is closed. This allows handling the user’s choice without blocking).
|
||||
- `void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb)` – 设置结果回调函数。用于非模态对话框,传入一个函数,当对话框关闭时框架会调用它并将 MessageBoxResult 结果传递进去 (Assigns the callback to call when the dialog is closed in modeless mode, receiving the MessageBoxResult).
|
||||
- **其他方法**:
|
||||
- `void performDelayedCleanup()` – 执行延迟清理。当对话框以非模态显示并关闭时,由 MessageBox 工厂调用,用于实际删除对话框对象 (Performs a delayed cleanup of the dialog. This is used by the MessageBox factory to delete the dialog object some time after closing if it was modeless, ensuring the callback is called before destruction).
|
||||
- `std::string GetCaption() const` – 获取对话框标题文本,用于去重检测。MessageBox 工厂在创建非模态对话框时可调用此函数检查是否已有相同标题和消息的对话框未关闭,从而避免重复弹出 (Returns the dialog’s title text (caption). The MessageBox factory uses this to check if a dialog with the same caption/text is already open, to prevent duplicates).
|
||||
- `std::string GetText() const` – 获取对话框消息文本,用于去重检测。
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- `Window& hWnd` – 对话框所属的父窗口引用。Dialog 会在该窗口的客户区显示,并对其事件进行拦截(模态时阻止 hWnd 处理事件)。通过引用可以在需要时调用父窗口的方法,例如 addDialog/removeDialog (Reference to the parent Window on which this dialog is displayed. The dialog is drawn within the parent window’s context. In modal mode, it uses this reference to block input to the window. The reference is also used to register/unregister the dialog in the window’s internal dialog list).
|
||||
- `int textWidth, textHeight` – 计算得到的对话框消息文本总尺寸(像素)。根据 `message` 内容和预设最大宽度自动换行得出 (The width and height (in pixels) of the message text area after word-wrapping. This is used to size the dialog appropriately).
|
||||
- `int buttonNum` – 底部功能按钮数量。由 MessageBoxType 决定,例如 OKCancel 类型时为2 (Number of action buttons in the dialog (excluding the close 'X' button). Determined by the MessageBoxType; e.g., OKCancel yields 2).
|
||||
- `StellarX::MessageBoxType type` – 当前对话框类型。决定对话框包含哪些功能按钮组合 (The type of the dialog, determining which action buttons are present, e.g., OK, YesNo, etc.).
|
||||
- `std::string titleText` – 对话框标题栏文本。默认 "提示" (Default is "提示", meaning "Prompt" or "Notice").
|
||||
- `std::unique_ptr<Label> title` – 标题文本的 Label 控件指针。对话框使用一个 Label 来显示标题文字,通常放置在标题栏中央 (Pointer to a Label control that displays the dialog’s title in the title bar area).
|
||||
- `std::string message` – 对话框主要消息内容文本。由构造函数传入 (The main message text of the dialog to be displayed in the body).
|
||||
- `std::vector<std::string> lines` – 分割后的消息文本行列表。根据对话框宽度将 message 拆分成若干行存储,用于在 draw 时逐行绘制 (The message text split into multiple lines to fit within the dialog’s width. This is prepared during initialization so that drawing can simply iterate over lines).
|
||||
- `bool needsInitialization` – 是否需要初始化的标志。初始为 true,表示需要调用 initButtons等初始化UI元素。在第一次 Show 时完成初始化后设为 false (Flag indicating if the dialog’s UI elements (buttons, etc.) still need to be initialized. True initially; after the first show (where `initButtons()`, etc., are called), set to false so as not to duplicate initialization).
|
||||
- `bool close` – 对话框关闭状态标志。为 true 时表示对话框应关闭,事件循环会检查此值退出 (Likely used to signal that the dialog should close. When set to true (e.g., after a button click), the modal loop will break and the dialog will begin teardown).
|
||||
- `bool modal` – 是否模态的标志。构造时根据传入参数设置,影响 Show 和 handleEvent 行为 (Indicates if the dialog is modal. Set in constructor based on parameter, determines how Show/handleEvent operate).
|
||||
- `COLORREF backgroundColor` – 对话框背景颜色,默认 RGB(240,240,240) 浅灰.
|
||||
- `COLORREF borderColor` – 对话框边框颜色,默认 RGB(100,100,100) 深灰.
|
||||
- `COLORREF buttonTrueColor, buttonFalseColor, buttonHoverColor` – 对话框底部功能按钮的配色方案。这些颜色用于创建对话框按钮时指定其不同状态颜色:buttonTrueColor(按钮按下时颜色,默认为略深的灰粉色),buttonFalseColor(按钮正常状态颜色,默认为浅灰),buttonHoverColor(按钮悬停颜色,默认为稍亮灰) (Color scheme for the dialog’s action buttons. These are used when creating the Button controls for OK/Cancel/etc: buttonTrueColor for pressed state (default a muted red/pinkish tone, RGB(211,190,190)), buttonFalseColor for unpressed state (default light gray, RGB(215,215,215)), buttonHoverColor for hover state (default slightly brighter gray, RGB(224,224,224))).
|
||||
- `Button* closeButton` – 对话框右上角的关闭按钮(“X”)指针。点击该按钮相当于取消/关闭对话框 (Pointer to the top-right close Button (with an “X”). Clicking it will close the dialog, typically equivalent to Cancel).
|
||||
- `StellarX::MessageBoxResult result` – 对话框的结果枚举值。初始默认为 Cancel(假定取消),当用户点击某个功能按钮时被设为对应的结果 (Stores the outcome of the dialog (which button was pressed). Initialized to Cancel by default, and set to the corresponding value when a button is clicked).
|
||||
- `bool shouldClose` – 标志对话框是否即将关闭。可能与 close 类似,但用于区分真正销毁时机 (Flag indicating the dialog should be closed. This might be used to initiate the shutdown sequence slightly before actual removal).
|
||||
- `bool isCleaning` – 标志对话框是否正在清理过程中。防止重复清理 (Flag indicating the dialog is currently in the cleanup process, to avoid re-entrance).
|
||||
- `bool pendingCleanup` – 标志是否需要延迟清理(如等待回调完成)。非模态对话框关闭时可能先不立即销毁对象而设置此标志,稍后由 performDelayedCleanup 真正删除 (Flag indicating that cleanup (object deletion) is pending. For modeless dialogs, after close, the object might not be destroyed immediately to allow the result callback to execute; instead pendingCleanup is set and actual deletion is done via performDelayedCleanup when safe).
|
||||
- `StellarX::ControlText textStyle` – 对话框文本样式。用于对话框消息文本的字体和颜色 (Font and style for the dialog’s message text). Dialog 通常使用系统默认字体、黑色文本 (Likely default to a standard font and black color).
|
||||
- **内部方法** *(private, for initialization and layout)*:
|
||||
- `void initButtons()` – 根据当前 `type` 创建底部功能按钮(如确定、取消等)并布局它们。同时计算 buttonNum 等 (Creates the action buttons (OK, Cancel, etc.) based on the dialog’s type and positions them appropriately at the bottom. Updates `buttonNum` and likely sizes).
|
||||
- `void initCloseButton()` – 创建并设置关闭按钮 (Creates the close “X” button and positions it at the top-right of the dialog).
|
||||
- `void initTitle()` – 创建并初始化标题 Label 控件 (Initializes the title Label with the current titleText and positions it in the title bar).
|
||||
- `void splitMessageLines()` – 将 `message` 按一定宽度拆分成多行填充到 `lines` 向量中 (Performs word wrapping: splits the message string into multiple lines that fit within the dialog’s width and stores them in `lines`).
|
||||
- `void getTextSize()` – 计算消息文本内容在当前字体下需要的宽高 (Calculates the pixel width and height required for the message text given the current font and line breaks).
|
||||
- `void initDialogSize()` – 根据内容(文本尺寸和按钮区域)计算对话框的总尺寸并调整自身宽高 (Sets the overall dialog width and height based on the text dimensions and space needed for buttons and title, including margins).
|
||||
- `void addControl(std::unique_ptr<Control> control)` – 将给定控件加入对话框的子控件列表 (Adds a new child control (Button/Label) to the Dialog’s Canvas children list and position relative to dialog).
|
||||
- `void clearControls()` – 清除并删除对话框内的所有子控件 (Removes all child controls from the dialog, deleting the buttons and labels. Used when resetting or destroying).
|
||||
- `std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text)` – 工具函数,创建一个在对话框底部指定位置的标准按钮。用于 OK/Cancel 等按钮的批量创建 (Utility to create a standard-sized dialog action button at given coordinates with given label text. Used by initButtons to create each required button).
|
||||
- `void requestRepaint(Control* parent) override` – 重载自 Control:Dialog 特殊实现覆盖 Canvas 默认行为。如果 parent == this,则仅执行 Canvas::requestRepaint 逻辑;否则调用父类 onRequestRepaintAsRoot() (The Dialog’s override for requestRepaint likely ensures that if a repaint is requested, it properly handles redrawing of itself and perhaps background. It might also intercept calls to avoid propagating beyond the dialog if not needed. The snippet suggests it overrides but possibly just uses default Canvas behavior except in special cases).
|
||||
|
||||
**依赖**:Dialog 依赖 **Window** 类来实现模态效果:模态对话框弹出时,会通过 Window 拦截消息循环或标记 `dialogClose` 等使主窗口等待。Dialog 内部组合了 **Label**(标题和消息文本)和 **Button**(操作按钮和关闭按钮)等多个控件,因此其正确运行依赖这些子控件的行为。Dialog 还使用 **MessageBoxType**/**MessageBoxResult** 枚举定义按钮方案和结果值。作为 Canvas 子类,Dialog 继承了绘制和背景处理机制,并在此基础上扩展 (The Dialog works closely with the **Window** class to implement modality: when shown modally, it likely uses Window’s event loop control (like Window::runEventLoop or flags) to block input to other controls. It composes multiple **Label** and **Button** controls and therefore relies on them for display and click handling. It uses **MessageBoxType** to decide what Buttons to create and **MessageBoxResult** to report outcomes. Being a subclass of Canvas, it benefits from Canvas's drawing logic and snapshot management to handle covering the underlying window content smoothly).
|
||||
|
||||
### MessageBox(消息框工厂类)
|
||||
|
||||
**类名**:`StellarX::MessageBox`
|
||||
**继承**:无,所有成员为静态方法 (This is a static utility class within namespace StellarX; it is not a Control and cannot be instantiated).
|
||||
|
||||
**简要说明**:消息框对话框的工厂类,提供简化的静态方法来创建和显示 **Dialog** 对象。开发者无需直接操作 Dialog 类,通过 MessageBox 提供的接口即可快捷地显示模态或非模态对话框。(*A factory class for message boxes that provides convenient static methods to create and display **Dialog** instances. It spares developers from dealing with the Dialog class directly by offering easy functions to show modal or modeless dialogs.*)
|
||||
|
||||
**公共静态方法**:
|
||||
|
||||
- `static MessageBoxResult showModal(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK)` – 显示一个模态消息框。在窗口 `wnd` 中央弹出一个对话框,显示内容 `text` 和标题 `caption`(默认“提示”),按钮组合由 `type` 指定(默认单个 OK)。该调用会阻塞当前线程,直到用户点击任意按钮关闭对话框。返回值为用户选择的结果(MessageBoxResult 枚举,例如 MessageBoxResult::OK 或 MessageBoxResult::Cancel 等) (Displays a modal message box on the given window `wnd`. It creates a Dialog with the provided message text and caption (default "提示"), with buttons as specified by `type` (default OK only). This call blocks execution until the user closes the dialog by clicking a button. It returns the result as a MessageBoxResult value corresponding to which button was pressed). 典型用法如:`MessageBox::showModal(mainWindow, "操作完成", "通知", MessageBoxType::OKCancel)` 阻塞等待用户点“确定”或“取消” (Example: calling showModal to present an "Operation completed" message with OK and Cancel buttons, and wait for the user to choose).
|
||||
|
||||
- `static void showAsync(Window& wnd, const std::string& text, const std::string& caption = "提示", MessageBoxType type = MessageBoxType::OK, std::function<void(MessageBoxResult)> onResult = nullptr)` – 显示一个非模态(异步)消息框。参数同上,但该函数**不会阻塞**。它立即返回,消息框以非模态方式显示在窗口上,用户可以继续与主窗口交互。当用户点击按钮关闭消息框时,如果提供了 `onResult` 回调函数,则异步调用之并传入结果枚举值 (Displays a modeless (non-blocking) message box. Parameters are similar to showModal, but this function returns immediately, allowing the main window to continue running. The message box dialog is shown and will close when the user clicks a button, at which point if an `onResult` callback is provided, it will be invoked with the MessageBoxResult of the user’s choice). 这种用法适合不想打断用户操作的提示,例如:
|
||||
|
||||
```
|
||||
MessageBox::showAsync(mainWindow, "下载已在后台进行", "信息", MessageBoxType::OK,
|
||||
[](MessageBoxResult res){ /* 回调处理 */ });
|
||||
```
|
||||
|
||||
(This is useful for notifications that shouldn’t halt the program flow. For example, showing a "Download started in background" info box with just an OK button and using the callback to perform any follow-up if needed).
|
||||
|
||||
**实现说明**: MessageBox 的实现会根据提供的参数创建一个 **Dialog** 对象并显示。对于 showModal,它创建 Dialog(模态=true)然后调用 Dialog::Show(),等待获取结果后返回 (In implementation, showModal likely constructs a Dialog with modal=true, then calls its Show() method, and finally returns the Dialog’s result). 对于 showAsync,它创建 Dialog(模态=false),设置 Dialog::resultCallback 为传入的 onResult,然后调用 Dialog::Show() 后直接返回 (For showAsync, it constructs a Dialog with modal=false, sets its resultCallback to the provided function (if any), calls Show(), and returns immediately without waiting). MessageBox 工厂可能维护一个记录以避免同时弹出两个内容相同的非模态对话框(如通过 Dialog::GetCaption()/GetText() 检查去重) (The MessageBox might also ensure no duplicate dialogs are opened for the same text/caption pair, possibly by checking existing open dialogs in Window’s list using Dialog::GetCaption()/GetText()).
|
||||
|
||||
**依赖**:MessageBox 工厂严重依赖 **Dialog** 类实现实际对话框,以及 **Window** 提供的容器支持。它只是封装了 Dialog 的构造和显示逻辑 (The MessageBox relies on the **Dialog** class to do the actual work of showing a message box, and on **Window** for placement. It is essentially a thin wrapper that simplifies using Dialog).
|
||||
|
||||
## 顶层容器(Top-Level Containers)
|
||||
|
||||
顶层容器类在框架中充当应用程序窗口或主要界面容器,能够容纳其他控件。这里包括 Window 和 Canvas 两个类:Window 代表应用主窗口,负责和操作系统交互;Canvas 则是一个通用的控件容器,可以放置在窗口内或其他容器内用于分组子控件。 *(The top-level container classes serve as the application window or primary interface containers that hold other controls. This includes the Window class (representing the main application window, handling OS-level interactions) and the Canvas class (a general-purpose container control that can be placed in windows or other containers to group child controls).)*
|
||||
|
||||
### Window(窗口容器)
|
||||
|
||||
**类名**:`Window`
|
||||
**继承**:无直接继承自 Control,但 Window 包含并管理 Control 子控件的列表 (Window is not a Control itself, but it aggregates controls. It does not inherit from Control, it's an independent class that manages a collection of Control objects).
|
||||
|
||||
**简要说明**:应用程序主窗口类,封装 Win32 窗口并提供容器功能,能够添加控件并处理窗口事件循环。Window 结合 EasyX 图形库,实现一个可调整大小、不闪烁的窗口,支持锚定布局和双缓冲绘图,以容纳 GUI 控件。(*The main application window class. It wraps a Win32 window (via EasyX) and provides a container for controls. The Window class is designed to be resizable and minimize flicker, supporting anchored layouts and double-buffered drawing, and serves as the root container for GUI controls.*)
|
||||
|
||||
**关键实现**: Window 通过子类化窗口过程(WndProcThunk)拦截窗口消息,如大小调整(WM_SIZING/WM_SIZE)、最小尺寸约束等。它将窗口客户区大小变化解耦为 pending 尺寸记录和统一重绘信号,从而在拖动调整窗口大小时避免频繁重绘抖动。(*Key points: The Window subclasses the window procedure (using a thunk) to intercept important messages like WM_SIZING/WM_SIZE. It clamps minimum client size, defers resize handling by recording pending width/height and uses a flag (needResizeDirty) to trigger a one-time redraw at the end of a resize, preventing flicker during continuous resizing.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Window(int width, int height, int mode)` – 构造函数,创建指定客户区宽高的窗口。`mode` 为 EasyX 初始化模式,如 EX_SHOWCONSOLE(显示控制台)或 EX_TOPMOST(置顶)等。构造函数仅初始化成员,实际创建窗口和子类化窗口过程在 draw() 中完成 (Initializes a Window with given client area width and height, and an EasyX mode flag. It does not immediately open the OS window; it just sets up internal state. Actual window creation and subclassing of WndProc happen in draw()).
|
||||
- `Window(int width, int height, int mode, COLORREF bkColor)` – 重载构造,额外指定背景颜色。
|
||||
- `Window(int width, int height, int mode, COLORREF bkColor, std::string headline)` – 重载构造,额外指定窗口标题文本。
|
||||
- `~Window()` – 析构函数,关闭窗口并清理 (Closes the window (likely via EasyX closegraph or similar) and destroys child controls).
|
||||
- **绘制与事件循环**:
|
||||
- `void draw()` – 绘制窗口界面(无参数版本)。执行 EasyX 初始化(initgraph)创建窗口,设置必要的窗口扩展样式 WS_EX_COMPOSITED(useComposited 控制)减少闪烁。然后填充背景纯色(wBkcolor)或背景图(如果设置了 background image),并绘制所有已添加的控件 (This sets up the actual Win32 window if not done, possibly by calling EasyX’s initgraph using stored `width`, `height` and `windowMode`. It then draws the background: if a background image is loaded (background != nullptr), it draws that, otherwise fills a solid color (wBkcolor). After that, it iterates through the `controls` vector to draw each child control. It also likely draws any non-modal dialogs that are open (from `dialogs` vector) on top).
|
||||
- `void draw(std::string pImgFile)` – 绘制窗口界面(带背景图)。该重载加载指定路径的图像作为窗口背景(赋值给 background 指针),然后调用 draw() 主函数 (This variant sets the background image by loading from the provided file path (stores it in `background` image pointer and records `bkImageFile` path), then likely calls the regular draw() routine to render with this image).
|
||||
- `int runEventLoop()` – 运行窗口的主事件循环。实现为循环调用 EasyX 的 PeekMessage 获取消息,然后对所有控件调用其 handleEvent() 进行分发,并在必要时调用 draw 进行重绘 (Runs the main GUI loop. Likely uses a loop around `PeekMessage()` (non-blocking message fetch) to continuously handle events. For each message, it passes it to each control’s handleEvent (in reverse order perhaps for proper z-order), tracks if any control became dirty (needResizeDirty usage), and if so, triggers a redraw of all controls at the end of the loop iteration). 特殊处理 WM_SIZING 等消息:当用户拖动调整窗口大小时,临时冻结重绘(isSizing=true),只在拖动结束 WM_EXITSIZEMOVE 时统一更新 (It has logic to handle WM_SIZING by not immediately redrawing (set isSizing true), and only upon WM_SIZE at end (or WM_EXITSIZEMOVE) does it schedule one combined redraw using `needResizeDirty`). runEventLoop 返回值可能是窗口关闭时的退出代码 (The function returns an exit code when the window is closed, possibly 0).
|
||||
- **背景与标题设置**:
|
||||
- `void setBkImage(std::string pImgFile)` – 更换窗口背景图像。加载指定文件为背景 (Loads the specified image file and sets it as the window’s background. After calling, it triggers a redraw of the window to display the new background).
|
||||
- `void setBkcolor(COLORREF c)` – 设置窗口背景纯色。将背景色改为 c,并立即触发一次重绘 (Sets the window background color to `c` and immediately triggers a full redraw of the window).
|
||||
- `void setHeadline(std::string headline)` – 设置窗口标题文本。更新内部 `headline` 字符串,并调用 Win32 API 更新窗口标题栏显示 (Changes the window’s title text. Updates the internal `headline` and likely calls SetWindowText on the underlying HWND to reflect the new title).
|
||||
- **控件和对话框管理**:
|
||||
- `void addControl(std::unique_ptr<Control> control)` – 将一个新的控件添加到窗口。此控件将被纳入 Window 的 `controls` 列表,在下次 draw 时绘制,并在事件循环中接收事件 (Adds a Control to the window’s control list. The Window takes ownership via the unique_ptr. The new control’s coordinates are typically relative to the window’s client area. It will be drawn on next repaint and will participate in event handling). 对于某些控件,Window 在添加时可能根据当前窗口大小调用其 onWindowResize 以初始化布局 (Window might call the new control’s onWindowResize upon adding to adjust layout if anchors are used).
|
||||
- `void addDialog(std::unique_ptr<Control> dialog)` – 将一个非模态对话框控件添加到窗口。Window 将其存入 `dialogs` 列表,该列表专门用于对话框( Adds a non-modal dialog (which is a Control) to the window. It is stored in a separate `dialogs` vector. The window will draw these on top of other controls and give them priority in event handling). 这通常由 MessageBox::showAsync 内部调用,当创建 Dialog 后将其托管给 Window (This is typically called internally by MessageBox::showAsync after creating a Dialog, to ensure the window knows about the open dialog).
|
||||
- `bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const` – 检查是否存在某个标题和消息匹配的非模态对话框正在窗口中显示。用于避免重复弹出内容相同的对话框 (Checks the `dialogs` list to see if a dialog with the given caption and message text is currently open. This is used to prevent opening duplicate dialogs with identical content).
|
||||
- **状态访问器**:
|
||||
- `HWND getHwnd() const` – 获取窗口的 Windows 窗口句柄。这允许调用底层 Win32 API,如需要 (Returns the Win32 window handle (HWND) of the EasyX-created window, in case direct Win32 calls are needed).
|
||||
- `int getWidth() const` / `int getHeight() const` – 获取窗口当前客户区宽度和高度。注意 Window 自身维护的 width/height 表示当前**有效**客户区尺寸,可能在交互拉伸过程中暂时保持旧值直到松开鼠标统一更新 (Returns the current effective client area width and height of the window. Note that the Window maintains `width`/`height` as the last fully applied size; during interactive resizing, it uses pendingW/H to store the changing values).
|
||||
- `std::string getHeadline() const` – 获取窗口标题文本.
|
||||
- `COLORREF getBkcolor() const` – 获取窗口背景色.
|
||||
- `IMAGE* getBkImage() const` – 获取窗口背景 IMAGE 对象指针(如果有). 如果未设置背景图则返回 nullptr (Returns the pointer to the background image if one is set, otherwise nullptr).
|
||||
- `std::string getBkImageFile() const` – 获取当前背景图片文件路径(如果有).
|
||||
- `std::vector<std::unique_ptr<Control>>& getControls()` – 获取对窗口普通控件列表的非 const 引用。允许遍历或操作已添加的控件 (Provides a reference to the vector of controls (not including dialogs), allowing iteration or manipulation of child controls if needed).
|
||||
- **尺寸调整**:
|
||||
- `void pumpResizeIfNeeded()` – 检查并执行一次延迟的重绘以响应窗口尺寸变化。Window 内部在事件循环每次迭代结尾调用此函数:如果 `needResizeDirty` 标志为 true,则调用一次 draw() 完成所有控件的调整绘制,然后将 `needResizeDirty` 复位 (Executes a deferred full redraw if a resize event has occurred. Inside the event loop, after processing all messages, Window calls this: if `needResizeDirty` is true (meaning a resize has happened and controls might need repositioning), it calls draw() to redraw everything and resets `needResizeDirty`). 这样可以把连续的 WM_SIZE 处理压缩为单次绘制 (This condenses potentially many WM_SIZE events into a single redraw, improving efficiency and reducing flicker).
|
||||
- `void scheduleResizeFromModal(int w, int h)` – 当模态对话框关闭时调用,将 pendingW/H 更新为给定值并标记 `needResizeDirty`。这用于在模态对话框可能改变父窗口大小(比如隐藏滚动条之类)后,通知窗口稍后调整布局 (Schedules a resize handling after a modal dialog closes and potentially affected the window size or layout. It sets the pendingW, pendingH (maybe to w,h) and marks needResizeDirty so that pumpResizeIfNeeded will redraw with updated layout).
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- **尺寸状态**:
|
||||
- `int width, height` – 当前有效客户区宽度和高度。这是应用到控件布局的实际尺寸 (The current effective width and height of the client area, which have been applied to control layout).
|
||||
- `int localwidth, localheight` – 窗口创建时的初始基准宽度和高度。作为锚定布局计算参考 (The baseline width/height at window creation, used as reference for anchor layout scaling calculations).
|
||||
- `int pendingW, pendingH` – 待应用的新宽高。在窗口被拉伸 (WM_SIZING) 过程中,Window 会记录不断变化的尺寸到 pendingW/H,但暂不应用到控件;等到用户释放鼠标(最终 WM_SIZE)时,再将 pendingW/H 赋给 width/height 并触发统一重绘。(The width and height that are pending application. During interactive resizing, as WM_SIZING events provide new sizes, the Window stores them in pendingW/H without immediately adjusting controls. Only when resizing is done (WM_SIZE final) do these get applied to actual width/height with one unified redraw).
|
||||
- `int minClientW, minClientH` – 业务设定的最小客户区宽高。Window 初始化时可指定一个最小大小,用于在 WM_GETMINMAXINFO 消息处理中限制窗口不能缩得太小 (Minimum client area width and height set for the window (if any). The Window uses these to enforce a minimum window size by handling WM_GETMINMAXINFO or clamping WM_SIZING).
|
||||
- `int windowMode` – EasyX 窗口初始化模式标志。例如 EX_SHOWCONSOLE, EX_TOPMOST 等,通过 EasyX initgraph 用 (The EasyX window mode flags used on creation, such as whether to show a console or make the window topmost, etc.).
|
||||
- `bool needResizeDirty` – 统一收口重绘标志。为 true 表示窗口尺寸变动引起的重绘尚未处理,需要在事件循环空闲时统一执行 (Flag indicating that a resize occurred and a full redraw is needed. When true, the event loop will call draw once and then clear this flag. It's set during resizing events to avoid multiple intermediate draws).
|
||||
- `bool isSizing` – 是否处于拖拽调整大小的状态。当收到 WM_ENTERSIZEMOVE 消息(用户开始拖拽窗口边框)时设 true,在 WM_EXITSIZEMOVE(拖拽结束)时设 false。(Flag indicating if the user is currently resizing the window (dragging the window border). Set to true on WM_ENTERSIZEMOVE, false on WM_EXITSIZEMOVE. When true, Window may suppress immediate redraws to avoid jitter).
|
||||
- **原生窗口和子类化**:
|
||||
- `HWND hWnd` – EasyX 创建的窗口句柄。Window 类在 draw() 中通过 EasyX 初始化拿到窗口句柄,并安装自定义窗口过程 (The Win32 window handle obtained after EasyX initialization. The Window uses it to subclass the window procedure).
|
||||
- `WNDPROC oldWndProc` – 保存的旧窗口过程指针。子类化时,用于在 Window 过程内调用原窗口过程处理未拦截的消息 (The original window procedure pointer saved when subclassing. The custom WndProcThunk will call this (via CallWindowProc) for messages it doesn’t handle).
|
||||
- `bool procHooked` – 标志窗口过程是否已子类化,避免重复钩挂.
|
||||
- `static LRESULT CALLBACK WndProcThunk(HWND h, UINT m, WPARAM w, LPARAM l)` – **静态窗口过程**,将 Win32 消息分发到特定 Window 实例的成员函数 (A static window procedure that acts as a trampoline to the Window instance’s message handler. It likely retrieves the Window* from a static map or from `GetWindowLongPtr(h,GWLP_USERDATA)` and calls an instance method). Window 在初始化时把该 Thunk 安装到 hWnd (During Window initialization, this function is set as the new window procedure for hWnd).
|
||||
- **绘制相关**:
|
||||
- `bool useComposited` – 是否使用组合窗口扩展风格(WS_EX_COMPOSITED)。默认为 true。打开该选项可减少某些机器上的绘制闪烁(但可能引入一帧延迟)(Whether to use the WS_EX_COMPOSITED extended style for the window, which can reduce flicker by double buffering the entire window. Default true, though on some systems this might introduce a slight frame delay).
|
||||
- `std::string headline` – 窗口标题文本。将在窗口标题栏显示 (The text for the window’s title bar).
|
||||
- `COLORREF wBkcolor` – 窗口背景纯色。绘制窗口时作为填充底色 (The solid background color used if no background image is set. Default BLACK (0x000000)).
|
||||
- `IMAGE* background` – 背景图像指针。如果加载了背景图,则绘制窗口时优先绘制该图像覆盖整个客户区 (Pointer to an IMAGE used as the window’s background. If not null, draw() will tile or stretch/draw this image to cover the client area).
|
||||
- `std::string bkImageFile` – 当前背景图像文件路径。记录所加载背景图的位置 (Stores the filename of the currently loaded background image, if any, mainly for reference or reloading purposes).
|
||||
- **控件/对话框容器**:
|
||||
- `std::vector<std::unique_ptr<Control>> controls` – 存放窗口上的普通控件列表。Window 的主要子控件容器。加入此列表的控件会在 draw() 时绘制、在事件循环中接收事件 (Holds all non-dialog child controls added to the window. These are drawn in the window’s draw() and have their events processed in the main loop).
|
||||
- `std::vector<std::unique_ptr<Control>> dialogs` – 存放窗口上当前打开的非模态对话框控件列表。这些对话框(Dialog 类实例)在 Window::draw() 中应绘制在普通控件之上,在事件处理时应优先处理 (Holds any modeless dialog controls currently open on this window. In draw(), these are drawn on top of regular controls, and in event handling, they get first chance to consume input, effectively overlaying modal-like behavior for their portion of the window).
|
||||
- `bool dialogClose` – 项目内使用的标志,指示对话框关闭状态。当窗口存在模态对话框时,该标志或许用于中断主事件循环 (A flag used to indicate a dialog has closed; possibly used internally to break out of a modal loop or signal something related to dialogs. It’s usage is not fully clear but likely interacts with the modal logic).
|
||||
|
||||
**依赖**:Window 直接使用 Win32 API(通过 EasyX)来创建窗口和处理消息。因此,它需要 Windows 平台支持。Window 包含框架内所有控件,是 GUI 的根容器:它依赖 **Control** 及其子类(Button 等)的存在,以将它们加入显示列表并调用其绘制和事件处理。Window 也依赖 **Dialog** 用于处理模态对话框逻辑、依赖 **MessageBoxType/Result** 枚举来处理最小化尺寸、结果返回等 (The Window class wraps the OS window and heavily interacts with the Win32 API via EasyX. It serves as the integration point where Control objects meet OS events. It depends on all control classes for populating its interface, and on **Dialog** for handling modal scenarios. It uses enumerated types from CoreTypes (LayoutMode, Anchor) for resizing logic as well). Window 与操作系统的交互如消息循环、最小化最大化等,均在其内部实现,对于开发者而言主要使用其 addControl、draw、runEventLoop 等接口 (All OS-level interactions (minimize, close, etc.) are handled internally by Window’s subclassed WndProc. For the developer, the main usage is adding controls, calling draw to initialize, and runEventLoop to start the GUI loop).
|
||||
|
||||
### Canvas(画布容器)
|
||||
|
||||
**类名**:`Canvas`
|
||||
**继承**:继承自 Control (`class Canvas : public Control`)。Canvas 是 Control 的一个具体实现,主要用于作为容器承载其他控件。
|
||||
|
||||
**简要说明**:通用画布控件,可充当容器容纳子控件,并支持绘制背景形状和处理子控件布局。Canvas 通常用作复合控件的基础(如 Dialog 和 TabControl 都继承自 Canvas),也可独立用于在窗口中分组一组控件。(*A general-purpose canvas control that can contain child controls and draw a background shape. Canvas often serves as a base for composite controls (e.g., Dialog and TabControl inherit Canvas), and can also be used on its own to group a set of controls in a window.*)
|
||||
|
||||
**功能**: Canvas 继承自 Control,提供除基本属性外的重要扩展:维护一个子控件列表(controls),并在绘制时能够绘制自身背景(可设置形状、填充模式、颜色等)然后递归绘制其包含的子控件。它也重载了部分 Control 方法以在自身移动或可见性变化时调整子控件。例如,当 Canvas 移动时,会相应偏移所有子控件的位置;当 Canvas 被隐藏时,自动隐藏子控件。(*Canvas extends Control by maintaining a list of child controls and by drawing a background (with configurable shape, fill mode, colors) before drawing its children. It overrides certain Control methods to handle child elements: for instance, moving a Canvas shifts all its children’s positions accordingly; hiding a Canvas also hides its children.*)
|
||||
|
||||
**公共方法**:
|
||||
|
||||
- `Canvas()` – 默认构造函数,将 Canvas 初始放置在 (0,0) 且尺寸为 100×100。调用 Control 基类构造,并设置 id 为 "Canvas" (Initializes a Canvas at origin with a default size of 100 by 100. Sets the control’s id to "Canvas").
|
||||
- `Canvas(int x, int y, int width, int height)` – 构造函数,在指定坐标创建给定尺寸的 Canvas。设置 id 为 "Canvas"。初始时背景形状默认矩形,填充模式、颜色等使用默认值 (Creates a Canvas at (x,y) with specified width and height. Id set to "Canvas". By default, its shape is rectangle with default fill mode and colors).
|
||||
- `void setX(int x) override` / `void setY(int y) override` – 重载移动 Canvas 的方法。Canvas 实现除更新自身位置外,会遍历所有子控件,将每个子控件的绝对坐标随之偏移(新的 X = 子控件原 localX + Canvas 新的 x 等)并调用子控件的 `onWindowResize()` 让其自行处理 (When moving the Canvas, it not only updates its own x (or y) but also adjusts all child controls: it calls each child’s `onWindowResize()` and sets the child’s X coordinate to its stored localX plus the new Canvas X. This effectively keeps children at the same relative position inside the Canvas even as the Canvas moves. Similarly for Y. Also marks itself dirty for redraw).
|
||||
- `void setWidth(int width) override` / `void setHeight(int height) override` – 默认继承 Control 的行为,只调整自身尺寸并标记 dirty。Canvas 没有特别重载这两个,但在 Canvas 内部,调整尺寸通常伴随需要重新布局子控件,此时 Canvas 的 onWindowResize 可能会被显式调用 (Canvas uses the base implementation for setWidth/Height: update and mark dirty. However, resizing a Canvas usually would imply repositioning children if anchors are set; such logic might rely on explicit call to onWindowResize to recalculation local positions if needed).
|
||||
- `void clearAllControls()` – 清空 Canvas 容器中的所有子控件。实现为简单调用 `controls.clear()` 删除子控件智能指针列表 (Removes all child controls from the Canvas’s vector, destroying them. After calling this, the Canvas will have no child controls).
|
||||
- `void draw() override` – 绘制 Canvas 及其子控件。实现逻辑:
|
||||
1. 若 Canvas 自身不是 dirty 或处于隐藏状态(show=false),则跳过自身重绘,仅检查并绘制特殊控件子类如 Table 等需要持续绘制 (If the Canvas itself is not marked dirty or is not visible, it will skip redrawing its background. However, it still iterates children: if a child is of a special type (like Table) that manages its own content and may need continuous drawing, it calls that child’s draw anyway to update dynamic content).
|
||||
2. 如果需要重绘:先调用 `saveStyle()` 保存当前全局绘图状态。然后设置绘图颜色和样式:边框色 = `canvasBorderClor`,填充色 = `canvasBkClor`(前提 FillMode 不是 Null),填充模式 = `canvasFillMode`,线型 = `canvasLineStyle`,线宽 = `canvaslinewidth`。
|
||||
3. 绘制 Canvas 背景形状:根据 `shape` 不同,调用 EasyX 不同函数绘制矩形、圆角矩形、圆形或椭圆等。如果 shape 带 B_ 前缀表示无边框版本,则使用 solidrectangle/solidroundrect 等无边框绘制,否则用 fillrectangle/fillroundrect 绘制带边框。圆角尺寸由 rouRectangleSize 提供 (Draw the canvas background shape: depending on `shape`, it chooses the appropriate function: e.g., fillrectangle for a filled rectangle with border, solidrectangle for borderless, fillroundrect/solidroundrect for rounded corners with/without border, etc. The rouRectangle struct provides the corner ellipse size for roundrect shapes).
|
||||
4. 依次绘制所有子控件:遍历 `controls` 列表,对每个子控件设置 dirty=true(确保其自身 draw 刷新),然后调用其 `draw()`。这样保证当 Canvas 重绘时,所有子控件都重绘 (Iterates through each child control, marks it dirty (setDirty(true)), and calls its draw() to render it).
|
||||
5. 调用 `restoreStyle()` 恢复全局绘图状态,标记 Canvas 自身 dirty=false 表示绘制已完成 (Finally restores the saved drawing state and marks itself not dirty).
|
||||
- `bool handleEvent(const ExMessage& msg) override` – 处理事件,将事件分发给子控件。实现为从后向前遍历子控件列表(Z序从顶开始)调用每个子控件的 handleEvent。如有子控件消费事件则记录 firstConsumer 并设置 consumed=true。遍历完后,如果有控件消费且该事件不属于“噪音”消息(如 WM_MOUSEMOVE),则打印调试日志 (It iterates over children in reverse order (so the topmost drawn control gets event first) calling each child’s handleEvent(msg). If a child returns true (event consumed) and firstConsumer is not yet set, it stores that pointer. After checking all, if an event was consumed and is not a “noisy” event like WM_MOUSEMOVE, it logs a debug message indicating Canvas consumed the event via a child).
|
||||
然后,如果发现任意子控件 `isDirty()==true` 则设 anyDirty=true。若 anyDirty 且消息非噪音,则打印日志并调用 `requestRepaint(parent)` 通知父容器需要重绘 (It also checks if any child became dirty during event handling; if yes, for non-mouse-move events it logs and calls `requestRepaint(parent)` to ask the parent to repaint this Canvas). 最后返回 consumed (Finally returns whether any child consumed the event).
|
||||
- 管理子控件:
|
||||
- `void addControl(std::unique_ptr<Control> control)` – 将一个子控件添加到 Canvas。实现:把 control 的 local 坐标 (getLocalX/Y) 转换为全局坐标,即加上 Canvas 自身的 x,y;将 control.parent 设为 this;记录日志说明添加 (Logs the addition with both local and computed global coords)。然后将 control 转移到 Canvas.controls 列表末尾,并将 Canvas 标记 dirty=true (Then moves the control into the Canvas’s controls list and marks the Canvas dirty). 这样下次 Canvas 重绘时会绘制该子控件 (The new control will be rendered on the next draw call of the Canvas).
|
||||
- `void setShape(StellarX::ControlShape shape)` – 设置 Canvas 背景形状。允许在矩形、圆角矩形、圆形、椭圆几种外观之间切换。只接受枚举定义的四边形/圆形形状 (Sets the shape used to draw the Canvas background (rectangle, round-rectangle, circle, ellipse with or without border). Only the shapes defined in ControlShape are accepted). 实现中,对传入 shape 进行 switch,但实际上任何受支持值都会执行 (The implementation likely just switches to the provided shape if valid and sets dirty true).
|
||||
- `void setCanvasfillMode(StellarX::FillMode mode)` – 设置 Canvas 背景填充模式(注意代码拼写大小写,此应为 setCanvasFillMode)。(Sets the fill mode for the Canvas background). 例如可将 Canvas 背景设为图案填充等。实现:赋值 canvasFillMode = mode 并标记 dirty (It assigns the new fill mode and marks the Canvas dirty).
|
||||
- `void setBorderColor(COLORREF color)` – 设置 Canvas 边框颜色。实现:canvasBorderClor = color,dirty=true.
|
||||
- `void setCanvasBkColor(COLORREF color)` – 设置 Canvas 背景填充颜色。实现:canvasBkClor = color,dirty=true.
|
||||
- `void setCanvasLineStyle(StellarX::LineStyle style)` – 设置 Canvas 边框线型样式。实现:canvasLineStyle = style,dirty=true.
|
||||
- `void setLinewidth(int width)` – 设置 Canvas 边框线宽。实现:canvaslinewidth = width,dirty=true.
|
||||
- `void setIsVisible(bool visible) override` – 重载 Control::setIsVisible。Canvas 将自身 show 标志设为 visible 并置 dirty=true。与 Control 默认实现相比没有额外逻辑 (Canvas does not add extra logic here beyond base: it sets its own `show` to the given value and marks itself dirty. It does not explicitly hide children here – however, when Canvas’s draw runs and show=false, children won’t be drawn and events won’t be passed if IsVisible returns false). 注:TabControl 等子类会进一步重载实现子控件可见性同步 (Note: As seen, TabControl overrides setIsVisible to handle children, but base Canvas doesn’t).
|
||||
- `void setDirty(bool dirty) override` – 重载 Control::setDirty。实现:设置自身 dirty 标志,并对所有子控件也递归设置 dirty= true。这确保当 Canvas 需要重绘时,其包含的子控件也会重绘 (Marks the canvas and all its children as dirty when dirty=true is set. This ensures a full redraw of all content. If dirty=false, it similarly clears the dirty flag on children).
|
||||
|
||||
**成员变量**:
|
||||
|
||||
- `std::vector< std::unique_ptr<Control> > controls` – Canvas 的子控件列表 (The container of child controls within this Canvas). 这些控件的坐标是**全局**坐标(已经加上 Canvas.x,y),Canvas 同时可能存储子控件的局部坐标用于布局 (Children are stored with their absolute positions already offset by Canvas’s position. The Canvas itself may not separately store local coords for them, as each Control already knows its local coordinates relative to parent from when it was added).
|
||||
- `StellarX::ControlShape shape` – Canvas 背景形状类型 (The shape used for this Canvas’s background. Default likely RECTANGLE).
|
||||
- `StellarX::FillMode canvasFillMode` – Canvas 背景填充模式 (Fill mode for the background; default Solid).
|
||||
- `StellarX::LineStyle canvasLineStyle` – Canvas 边框线型 (Border line style; default Solid).
|
||||
- `int canvaslinewidth` – Canvas 边框线宽度(像素)(Border line width; default maybe 1).
|
||||
- `COLORREF canvasBorderClor` – Canvas 边框颜色 (Border color; default black RGB(0,0,0)).
|
||||
- `COLORREF canvasBkClor` – Canvas 背景填充颜色 (Background fill color; default white RGB(255,255,255)).
|
||||
|
||||
*(默认值参考 TABLE_DEFAULT_BG_COLOR/DEFAULT_BORDER_COLOR 等相似定义推测)*
|
||||
|
||||
**依赖**:Canvas 作为 Control 子类,不直接依赖 OS 资源,但它依赖 **ControlShape**、**FillMode**、**LineStyle** 等 CoreTypes 定义来渲染外观。Canvas 常被其他控件继承或组合使用,例如 **TabControl** 和 **Dialog** 就利用 Canvas 来实现多控件布局和背景绘制功能 (Canvas relies on the core enums for shapes and styles. It is often used as a base or a component in composite controls: e.g., **TabControl** inherits Canvas to manage pages, **Dialog** inherits Canvas to manage dialog content. Thus, Canvas forms a foundational block for any container-like control in the framework). 开发者可直接使用 Canvas 来划分界面区域或自定义绘制形状和放置子控件 (A developer might use a Canvas directly in a Window to group controls or draw a colored panel background). Canvas 无窗口句柄,不处理独立消息循环,其事件处理和重绘完全依赖父 Window 传递 (Canvas doesn’t have its own window handle; it relies on the parent Window to dispatch events and refresh calls to it, just like any other Control).
|
||||
|
||||
## 工具类(Utility Classes)
|
||||
|
||||
工具类提供辅助功能,非 GUI 控件但为框架的开发和调试提供支持。例如日志记录类 SxLog 用于输出调试信息。下面对框架的日志系统做简要说明: *(Utility classes offer supporting functionality outside of the UI controls, such as logging or debug output. For instance, the SxLog logging system is used for debug and information logging. Below is an overview of the framework’s logging utility:)*
|
||||
|
||||
### 日志系统 SxLog(Logging Utility)
|
||||
|
||||
**类名**:`StellarX::SxLog` *(实际上由多个类和宏组成,包括 SxLogger, FileSink, SxLogLine, SxLogScope 等;对外主要以宏接口使用)* (Implemented by several classes like SxLogger, FileSink, SxLogLine, SxLogScope, but primarily exposed via macros).
|
||||
|
||||
**简要说明**:StellarX 框架的日志记录工具,支持中英双语日志输出、日志分级过滤、文件输出及滚动等功能。通过一组宏(如 `SX_LOGD`, `SX_LOGI`, `SX_LOGW`, `SX_LOGE` 分别用于调试、信息、警告、错误级别)和辅助宏 `SX_T`(将中英文字符串选择输出)实现简洁的日志记录接口。(*The logging system for the StellarX framework, providing bilingual log output, log level filtering, and file output with rotation. It is accessed via a set of macros (e.g., `SX_LOGD`, `SX_LOGI`, `SX_LOGW`, `SX_LOGE` for debug, info, warning, error levels respectively) and a helper macro `SX_T` (for bilingual text selection) to offer a simple logging interface.*)
|
||||
|
||||
**使用方法**:
|
||||
在代码中,可通过例如:
|
||||
|
||||
```
|
||||
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message;
|
||||
```
|
||||
|
||||
将会打印一条调试日志。在日志输出中,`"Event"` 为日志标签,SX_T 宏包含了中文和英文两段字符串,日志系统会根据当前语言设置选取相应语言输出。以上示例最终可能输出:“Canvas 消耗消息: msg=0x0201 子控件 id=Button1”(当语言设为中文)或 “Canvas consumed: msg=0x0201 by child id=Button1”(设为英文)(In practice, using the macros as shown will produce log lines with a tag and bilingual text. The system chooses Chinese or English text in SX_T based on the current language setting. For example, the above might log “Canvas 消耗消息: msg=...” in Chinese mode, or “Canvas consumed: msg=...” in English mode).
|
||||
|
||||
**日志级别**: SxLog 定义了日志级别枚举 SxLogLevel(例如 Debug, Info, Warn, Error 等)。通过全局日志器 SxLogger,可以设置最低输出级别。当使用 `SX_LOGx` 宏记录时,低于当前最低级别的日志不会输出 (The SxLog system has log levels (likely Debug=0, Info=1, etc.). The global logger SxLogger has a `setMinLevel(SxLogLevel level)` function to set the minimum level of messages to actually output. Logging calls via `SX_LOGx` macros with a level below this threshold are no-ops).
|
||||
|
||||
**语言控制**: 默认情况下日志语言为中文(ZhCN)。可以通过 `SxLogger::setLanguage(SxLogLanguage lang)` 切换语言,例如设为 EnUS 输出英文日志 (By default, the log language is Chinese (ZhCN). The language can be changed by calling `SxLogger::setLanguage(SxLogLanguage)` to switch to English (EnUS) or other supported languages. Only the text provided via SX_T macro is affected by this setting).
|
||||
|
||||
**输出方式**: 日志既可以输出到调试控制台,也可以输出到文件。通过 `SxLogger::enableFile(const std::string& path, bool append, size_t rotateBytes)` 可以启用日志文件输出。参数 path 为文件路径,append 指定是否追加模式写入,rotateBytes 则如果>0表示启用文件滚动,当文件达到指定大小字节时自动改名备份并开启新文件 (The log can be configured to write to a file by calling `SxLogger::enableFile(path, append, rotateBytes)`. If `rotateBytes` is set (non-zero), the logger will automatically rotate (rename and start a new log file) when the current file exceeds that size).
|
||||
|
||||
**实现细节**:
|
||||
|
||||
- 日志系统由以下组件构成:
|
||||
- **FileSink**:负责文件输出的类,封装文件打开、写入、关闭及文件滚动逻辑。
|
||||
- **SxLogger**:日志核心单例类(Get() 获取实例),管理日志级别、语言和输出目的地。SxLogger 提供 `shouldLog(level)` 判断是否应输出某级别日志,`logLine(level, tag, message)` 统一将一行日志送往所有目的(控制台、文件)。
|
||||
- **SxLogLine**:使用 RAII 的技巧实现流式日志,将 `SX_LOGx` 宏展开后生成的对象在析构时自动调用 SxLogger 提交日志。这保证了一条日志完整输出在一行结束。
|
||||
- **SxLogScope**:用于计算一段作用域执行时间的辅助类。在构造时记录时间戳,析构时计算耗时并输出日志,可用于性能分析。
|
||||
- **日志宏**:
|
||||
- `SX_LOGD(tag)`, `SX_LOGI(tag)`, `SX_LOGW(tag)`, `SX_LOGE(tag)` 分别创建一个 SxLogLine 对象用于调试(D)、信息(I)、警告(W)、错误(E)级别日志。参数 `tag` 是短字符串标签,用于标识日志分类。后续可以用流插入操作符 `<<` 将消息内容流入日志对象。每条日志宏调用最终在一行输出,并以换行结束 (These macros create an SxLogLine internally for the specified level with the given tag, then the code uses `<<` to append message parts. When the statement ends, the SxLogLine’s destructor is invoked, which sends the accumulated text to SxLogger to output with a newline).
|
||||
- `SX_T(chinese, english)` 宏用于处理内嵌在日志中的中英文双语字符串。运行时根据当前语言,只选择其中一个串输出。例如 `SX_T("失败","Failure")` 若语言为中文则产出 "失败",英文则产出 "Failure" (The SX_T macro is a translation helper: it takes a Chinese string and an English string, and at runtime includes only the one matching the current language setting in the log. It allows log messages to be bilingual without duplicating code).
|
||||
|
||||
**使用示例**:
|
||||
|
||||
```
|
||||
SX_LOGI("Init") << SX_T("窗口创建成功,大小=","Window created, size=")
|
||||
<< wnd.getWidth() << "x" << wnd.getHeight();
|
||||
```
|
||||
|
||||
如果当前日志级别 <= Info,则将输出类似:“窗口创建成功,大小=800x600” 或 “Window created, size=800x600”。否则不会输出任何信息 (If the current log level is Info or lower, this will output the message in the appropriate language. If the level is higher (e.g., Warning), nothing is output).
|
||||
|
||||
**依赖**:日志系统与 GUI 框架其他部分松耦合。它使用 C++11 线程安全特性(如 `std::call_once` 设置控制台 CodePage, `std::mutex` 来序列化日志输出)。日志宏在 GUI 控件代码中被调用以记录调试信息,但它本身不依赖 GUI 类。**EasyX** 控制台输出在 Windows 默认是 GBK,所以 SxLogger 在初始化时尝试设置控制台 CodePage 为 936 (GBK) 以正确显示中文日志(The logging system is mostly self-contained, using standard C++ for file and console operations. In the context of GUI, it’s used by controls for debug output but does not interact with GUI components. Notably, it sets the Windows console code page to 936 (GBK) once to ensure Chinese characters are properly displayed on the console, as noted in the comments).
|
||||
|
||||
通过 SxLog 工具类,开发者可以方便地输出框架内部运行状况信息,这对调试 GUI 行为、事件处理流程等非常有帮助。(*The SxLog utility enables developers to conveniently output internal state and flow information from the framework, which is invaluable for debugging GUI behavior and event handling.*)
|
||||
Reference in New Issue
Block a user