diff --git a/Canvas.cpp b/Canvas.cpp index 352129d..759a6f7 100644 --- a/Canvas.cpp +++ b/Canvas.cpp @@ -159,6 +159,8 @@ bool Canvas::handleEvent(const ExMessage& msg) if (anyDirty) { + // 只要任一子控件因本次事件进入 dirty,就把这笔重绘继续向上汇报。 + // 在托管模式下,这不会立即绘制,而是登记为 Canvas 对应的重绘 root。 if (!SxIsNoisyMsg(msg.message)) SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id; requestRepaint(parent); @@ -435,8 +437,9 @@ void Canvas::requestRepaint(Control* parent) { if (shouldDeferManagedRepaint()) { + // 托管路径:由 Window 统一决定这次是否只重画本 Canvas,还是升级为补画 Dialog / 整体场景。 if (auto* host = getHostWindow()) - host->requestManagedRepaint(); + host->requestManagedRepaint(this); return; } @@ -474,3 +477,27 @@ void Canvas::requestRepaint(Control* parent) onRequestRepaintAsRoot(); } +bool Canvas::canCommitManagedPartialRepaint() const +{ + // Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时, + // 才能安全地做局部提交(即只更新内部脏子控件)。 + return show && !dirty && hasValidBackgroundSnapshot(); +} + +void Canvas::commitManagedRepaint() +{ + if (!show) + return; + + if (canCommitManagedPartialRepaint()) + { + // 快照完好:沿用 Canvas 自己已有的局部重绘逻辑。 + requestRepaint(this); + return; + } + + // 自身已经脏了,或快照失效:必须升级为整 Canvas 重画。 + this->dirty = true; + onRequestRepaintAsRoot(); +} + diff --git a/Canvas.h b/Canvas.h index c1fa4b4..6076783 100644 --- a/Canvas.h +++ b/Canvas.h @@ -4,6 +4,7 @@ * @描述: * 作为其他控件的父容器,提供统一的背景和边框样式。 * 负责将事件传递给子控件并管理它们的绘制顺序。 + * 在托管重绘模式下,Canvas 通常作为一组子控件的安全重绘 root。 * * @特性: * - 支持四种矩形形状(普通、圆角,各有边框和无边框版本) @@ -64,7 +65,12 @@ public: void setIsVisible(bool visible) override; void setDirty(bool dirty) override; void onWindowResize() override; - void requestRepaint(Control* parent)override; + // 托管模式下登记为 root;非托管模式下走局部或根级重绘 + void requestRepaint(Control* parent)override; + // 判断当前 Canvas 是否可安全做局部提交 + bool canCommitManagedPartialRepaint() const override; + // 托管收口阶段执行 Canvas 的真正重绘 + void commitManagedRepaint() override; //获取子控件列表 std::vector>& getControls() { return controls; } private: diff --git a/Control.cpp b/Control.cpp index dd71f4e..d27615b 100644 --- a/Control.cpp +++ b/Control.cpp @@ -126,8 +126,10 @@ void Control::requestRepaint(Control* parent) { if (shouldDeferManagedRepaint()) { + // 托管路径:当前正在 Window 的事件分发阶段,不能立即绘制; + // 这里只登记 source,真正的 root 选择由 Window 在 requestManagedRepaint 中完成。 if (auto* host = getHostWindow()) - host->requestManagedRepaint(); + host->requestManagedRepaint(this); return; } @@ -157,8 +159,10 @@ void Control::onRequestRepaintAsRoot() { if (shouldDeferManagedRepaint()) { + // 即使已经冒泡到 root,只要还在托管分发期,也不能直接绘制; + // 仍然回到 Window 做统一提交。 if (auto* host = getHostWindow()) - host->requestManagedRepaint(); + host->requestManagedRepaint(this); return; } @@ -178,6 +182,9 @@ bool Control::shouldDeferManagedRepaint() const return host && host->isManagedDispatchActive(); } +// 获取宿主 Window: +// - 顶层控件由 Window/addDialog 直接注入; +// - 子控件没有直接注入时,沿 parent 链向上回溯即可。 Window* Control::getHostWindow() const { if (hostWindow) @@ -185,6 +192,18 @@ Window* Control::getHostWindow() const return parent ? parent->getHostWindow() : nullptr; } +// 托管重绘 root 选择规则: +// - 对于直接挂在 Window 下的控件,root 就是它自己; +// - 对于嵌套在 Canvas/TabControl/Dialog 内的子控件,沿 parent 向上找到与宿主 Window 同属一棵树的最上层控件。 +Control* Control::getManagedRepaintRoot() +{ + Control* root = this; + Window* host = getHostWindow(); + while (root->parent && root->parent->getHostWindow() == host) + root = root->parent; + return root; +} + RECT Control::getBoundsRect() const { RECT rc{}; @@ -195,6 +214,22 @@ RECT Control::getBoundsRect() const return rc; } +bool Control::canCommitManagedPartialRepaint() const +{ + // 基类默认不承诺自己能安全做局部提交; + // 只有 Canvas / TabControl / Dialog 这类“拥有完整背景语义”的 root 才会 override 为 true。 + return false; +} + +void Control::commitManagedRepaint() +{ + if (!show) + return; + // 基类兜底:如果没有更具体的容器实现,就按根级重绘处理。 + if (dirty) + onRequestRepaintAsRoot(); +} + void Control::saveBackground(int x, int y, int w, int h) { diff --git a/Control.h b/Control.h index 3083380..3520d26 100644 --- a/Control.h +++ b/Control.h @@ -4,6 +4,7 @@ * @描述: * 提供控件的基本属性和方法,包括位置、尺寸、重绘标记等。 * 实现绘图状态保存和恢复机制,确保控件绘制不影响全局状态。 + * 同时提供“事件阶段登记、收口阶段统一提交”的托管重绘基础接口。 * * @特性: * - 定义控件基本属性(坐标、尺寸、脏标记) @@ -44,7 +45,7 @@ protected: Window* hostWindow = nullptr; // 宿主窗口(顶层由 Window 注入,子控件可沿 parent 回溯) bool dirty = true; // 是否重绘 bool show = true; // 是否显示 - bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化 + bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化(用于上层判断是否需要登记重绘) /* == 布局模式 == */ StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式 @@ -81,10 +82,11 @@ public: discardBackground(); } protected: - //向上请求重绘 + // 向上请求重绘:普通路径交给父容器,托管路径则登记到 Window virtual void requestRepaint(Control* parent); - //根控件/无父时触发重绘 + // 根控件/无父时触发重绘 virtual void onRequestRepaintAsRoot(); + // 当前是否处于 Window 托管分发阶段;若为真,则不应立即画 bool shouldDeferManagedRepaint() const; protected: //保存背景快照 @@ -127,8 +129,12 @@ public: void setParent(Control* parent) { this->parent = parent; } //设置宿主窗口(通常仅由顶层 Window/对话框注入) virtual void setHostWindow(Window* host) { this->hostWindow = host; } - Window* getHostWindow() const; - RECT getBoundsRect() const; + Window* getHostWindow() const; // 获取宿主 Window;子控件可沿 parent 向上回溯 + RECT getBoundsRect() const; // 获取当前控件外接矩形,用于覆盖/相交判断 + Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root + bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照 + virtual bool canCommitManagedPartialRepaint() const; // 当前 root 是否可安全做“局部提交”而非整 root 重画 + virtual void commitManagedRepaint(); // 托管收口阶段真正执行绘制的入口 //设置是否重绘 virtual void setDirty(bool dirty) { this->dirty = dirty; } //检查控件是否可见 @@ -137,6 +143,7 @@ public: std::string getId() const { return id; } //检查是否为脏 bool isDirty() { return dirty; } + //获取控件最近一次事件处理是否引发了视觉变化 bool didEventAffectVisual() const { return eventVisualChanged; } //用来检查对话框是否模态,其他控件不用实现 virtual bool model()const = 0; diff --git a/Dialog.cpp b/Dialog.cpp index e8446d6..878ff82 100644 --- a/Dialog.cpp +++ b/Dialog.cpp @@ -760,8 +760,10 @@ void Dialog::requestRepaint(Control* parent) { if (shouldDeferManagedRepaint()) { + // 非模态 Dialog 在 Window 主循环中也走托管提交; + // 这样底层控件和对话框的绘制顺序由 Window 统一收口控制。 if (auto* host = getHostWindow()) - host->requestManagedRepaint(); + host->requestManagedRepaint(this); return; } @@ -774,3 +776,27 @@ void Dialog::requestRepaint(Control* parent) else onRequestRepaintAsRoot(); } + +bool Dialog::canCommitManagedPartialRepaint() const +{ + // Dialog 只有在“自身底板不脏 + 仍持有有效背景快照”时, + // 才能安全地只更新内部按钮,而不重画整个对话框底板。 + return show && !dirty && hasValidBackgroundSnapshot(); +} + +void Dialog::commitManagedRepaint() +{ + if (!show) + return; + + if (canCommitManagedPartialRepaint()) + { + // 背景快照完好:沿用 Dialog 自己已有的局部重绘路径。 + requestRepaint(this); + return; + } + + // 对话框底板本身已脏,或快照失效:必须整 Dialog 重画。 + this->dirty = true; + onRequestRepaintAsRoot(); +} diff --git a/Dialog.h b/Dialog.h index 213b90c..c86ea90 100644 --- a/Dialog.h +++ b/Dialog.h @@ -4,6 +4,7 @@ * @描述: * 实现完整的对话框功能,支持多种按钮组合和异步结果回调。 * 自动处理布局、背景保存恢复和生命周期管理。 + * 在窗口托管重绘模式下,Dialog 自身也是一个独立的重绘 root。 * * @特性: * - 支持六种标准消息框类型(OK、YesNo、YesNoCancel等) @@ -103,7 +104,7 @@ public: // 关闭对话框 void Close(); //初始化 - void setInitialization(bool init); + void setInitialization(bool init); // 历史接口:当前语义更接近“请求重新布局/重建” // 宿主窗口变化时仅重新居中,不拉伸 Dialog 自身 void recenterInHostWindow(); @@ -123,10 +124,12 @@ private: // 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮 void rebuildChrome(); void addControl(std::unique_ptr control); + bool canCommitManagedPartialRepaint() const override; // 判断当前 Dialog 是否可安全做局部提交 + void commitManagedRepaint() override; // 托管收口阶段执行 Dialog 的真正重绘 // 清除所有控件 void clearControls(); //创建对话框按钮 std::unique_ptr