diff --git a/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h b/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h index 73d8b71a..64052aa0 100644 --- a/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h +++ b/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h @@ -1,4 +1,4 @@ -#ifndef NM_DIALOG_PREVIEW_GUARD_H +#ifndef NM_DIALOG_PREVIEW_GUARD_H #define NM_DIALOG_PREVIEW_GUARD_H #include @@ -20,6 +20,26 @@ public: window->installEventFilter(this); } + // 已隐藏的窗口可立即清理,供结果窗在恢复尺寸前解除原生最小化状态。 + void resetHiddenWindow() + { + cancelPendingReset(); + if(m_window.isNull() || m_window->isVisible() || !m_window->isWindow() || + !m_window->internalWinId()) return; + + m_rebuilding = true; + // 重建原生窗口会触发事件,期间本对象可能被释放,用弱指针检查是否仍然存活。 + QPointer alive(this); + const QRect geometry = m_window->geometry(); + // 在当前界面库的系统后端,即使父对象和标志不变,双参数调用也会重建原生窗口。 + // 子控件的原生父窗口由界面库同步调整,不能绕过界面库直接销毁系统句柄。 + m_window->setParent(m_window->parentWidget(), m_window->windowFlags()); + if(alive.isNull()) return; + // 恢复原有位置和尺寸,确保下次打开时窗口布局不因句柄重建而改变。 + if(!m_window.isNull()) m_window->setGeometry(geometry); + if(!alive.isNull()) m_rebuilding = false; + } + protected: // 只监听目标窗口的显式隐藏和重新显示;重建过程产生的事件不重复处理。 bool eventFilter(QObject* object, QEvent* event) @@ -52,21 +72,7 @@ protected: QObject::timerEvent(event); return; } - cancelPendingReset(); - if(m_window.isNull() || m_window->isVisible() || !m_window->isWindow() || - !m_window->internalWinId()) return; - - m_rebuilding = true; - // 重建原生窗口会触发事件,期间本对象可能被释放,用弱指针检查是否仍然存活。 - QPointer alive(this); - const QRect geometry = m_window->geometry(); - // 在当前界面库的系统后端,即使父对象和标志不变,双参数调用也会重建原生窗口。 - // 子控件的原生父窗口由界面库同步调整,不能绕过界面库直接销毁系统句柄。 - m_window->setParent(m_window->parentWidget(), m_window->windowFlags()); - if(alive.isNull()) return; - // 恢复原有位置和尺寸,确保下次打开时窗口布局不因句柄重建而改变。 - if(!m_window.isNull()) m_window->setGeometry(geometry); - if(!alive.isNull()) m_rebuilding = false; + resetHiddenWindow(); } private: diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 87cbfd21..bd355dd5 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -107,8 +107,100 @@ #include #include +#ifdef Q_OS_WIN +#include +#include +#pragma comment(lib, "dwmapi.lib") +#pragma comment(lib, "gdi32.lib") +#endif + namespace { +// 结果窗保留原生最小化小框,仅替换最小化期间的系统预览。 +class nmResultDialog : public QDialog +{ +public: + explicit nmResultDialog(QWidget* parent, Qt::WindowFlags flags) + : QDialog(parent, flags), m_previewGuard(new nmDialogPreviewGuard(this)), + m_nativeMinimized(false) + { + } + + void hideAndRestoreGeometry() + { + // 先隐藏再恢复,避免关闭小框时在原位置短暂显示大窗。 + if(!isMinimized()) return; + const QRect geometryBeforeMinimize = m_geometryBeforeMinimize; + const Qt::WindowStates restoredState = windowState() & ~Qt::WindowMinimized; + hide(); + setWindowState(restoredState); + // 复用预览清理来重建隐藏句柄,否则 Qt 4 调整原生最小化窗口尺寸时会重新显示它。 + m_previewGuard->resetHiddenWindow(); + if(!(restoredState & Qt::WindowMaximized) && geometryBeforeMinimize.isValid()) + setGeometry(geometryBeforeMinimize); + setWindowState(restoredState); + m_nativeMinimized = false; + } + +#ifdef Q_OS_WIN +protected: + bool winEvent(MSG* message, long* result) override + { + // 主窗口恢复时仅显示原来的小框,避免系统默认处理将结果窗展开。 + if(message->message == WM_SHOWWINDOW && message->wParam && + message->lParam == SW_PARENTOPENING && IsIconic(message->hwnd)) { + ShowWindow(message->hwnd, SW_SHOWMINNOACTIVE); + *result = 0; + return true; + } + + if(message->message == WM_SIZE) { + // 恢复或最大化后交还实时预览;窗口句柄重建后也由尺寸消息重新设置。 + const BOOL minimized = message->wParam == SIZE_MINIMIZED; + // Qt 此时尚未处理最小化尺寸消息,仍保留大窗的客户区位置和尺寸。 + if(minimized && !m_nativeMinimized) m_geometryBeforeMinimize = geometry(); + m_nativeMinimized = minimized; + DwmSetWindowAttribute(message->hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION, + &minimized, sizeof(minimized)); + DwmSetWindowAttribute(message->hwnd, DWMWA_HAS_ICONIC_BITMAP, + &minimized, sizeof(minimized)); + DwmInvalidateIconicBitmaps(message->hwnd); + } + + if((message->message == WM_DWMSENDICONICTHUMBNAIL || + message->message == WM_DWMSENDICONICLIVEPREVIEWBITMAP) && + IsIconic(message->hwnd)) { + // 透明位图只作用于任务栏预览,不隐藏结果窗或替换其原生标题栏。 + BITMAPINFO info = {0}; + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = 1; + info.bmiHeader.biHeight = -1; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + void* pixels = NULL; + HBITMAP bitmap = CreateDIBSection(NULL, &info, DIB_RGB_COLORS, &pixels, NULL, 0); + if(bitmap) { + *static_cast(pixels) = 0; + const HRESULT status = message->message == WM_DWMSENDICONICTHUMBNAIL + ? DwmSetIconicThumbnail(message->hwnd, bitmap, 0) + : DwmSetIconicLivePreviewBitmap(message->hwnd, bitmap, NULL, 0); + DeleteObject(bitmap); + if(SUCCEEDED(status)) { + *result = 0; + return true; + } + } + } + return QDialog::winEvent(message, result); + } +#endif + +private: + nmDialogPreviewGuard* m_previewGuard; + QRect m_geometryBeforeMinimize; + bool m_nativeMinimized; // 主窗口恢复时可能重复通知最小化,不重复记录小框尺寸。 +}; + // 保留原来求解器单例的防重入语义:全局同一时间只允许一个DLL求解任务运行。 // QPointer会在QObject销毁后自动变空,避免保留已经释放的线程指针。 QPointer s_pRunningSolverTask; @@ -135,9 +227,8 @@ void clearResultCurveDataTables(iGuiPlot* plot) void resetResultDialog(QDialog* window) { if(!window) return; - // 先恢复最小化状态再隐藏,避免隐藏后调整尺寸使系统重新显示空白窗口。 - if(window->isMinimized()) - window->setWindowState(window->windowState() & ~Qt::WindowMinimized); + // 最小化结果先在隐藏状态下恢复尺寸,再清理旧曲线。 + static_cast(window)->hideAndRestoreGeometry(); window->hide(); foreach(iGuiPlot* plot, window->findChildren()) clearResultCurveDataTables(plot); foreach(nmWxSensitivityShow* dialog, window->findChildren()) @@ -1978,12 +2069,11 @@ void nmSubWndMain::onForecastDataChanged() QDialog* nmSubWndMain::createResultDialog(const QString& name, const QString& title) { // 关闭只隐藏,窗口及绘图控件由所属分析统一销毁。 - QDialog* dialog = new QDialog(m_pResultRefreshFitting.data(), + QDialog* dialog = new nmResultDialog(m_pResultRefreshFitting.data(), Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); dialog->setObjectName(name); dialog->setWindowTitle(title); dialog->setAttribute(Qt::WA_DeleteOnClose, false); - new nmDialogPreviewGuard(dialog); dialog->resize(900, 650); QVBoxLayout* layout = new QVBoxLayout(dialog); layout->setContentsMargins(0, 0, 0, 0); @@ -2154,10 +2244,10 @@ void nmSubWndMain::onReadonlyCurveDoubleClicked( bool nmSubWndMain::eventFilter(QObject* watched, QEvent* event) { - // 最小化时先恢复窗口状态,再交给默认关闭流程隐藏,避免复用时丢失正常尺寸。 + // 最小化结果在隐藏后恢复尺寸,关闭过程中不显示大窗。 QDialog* dialog = qobject_cast(watched); - if(dialog && event->type() == QEvent::Close && dialog->isMinimized()) - dialog->setWindowState(dialog->windowState() & ~Qt::WindowMinimized); + if(dialog && event->type() == QEvent::Close) + static_cast(dialog)->hideAndRestoreGeometry(); // 关闭结果对话框时同步关闭其曲线数据表、Show 和统计设置窗口。 if((event->type() == QEvent::Close || event->type() == QEvent::Hide) && qobject_cast(watched)) {