首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Qt 主視窗關閉事件攔截:正確處理阻塞任務與 Esc 鍵退出

     編輯:關於C語言

場景痛點

GUI 應用中,若用戶點擊關閉按鈕或按 Esc 時,存在**耗時阻塞任務**(網路連線、資料庫事務、執行緒等待)正在運行,直接退出會導致資源洩漏、數據不一致甚至崩潰。必須攔截關閉事件,實現**優雅關閉**邏輯。

核心方案:重寫虛函數

QMainWindow(繼承自 QWidget)提供兩個關鍵虛函數:

  • void closeEvent(QCloseEvent *event):視窗關閉請求(點擊 ×、close()、系統關機)
  • void keyPressEvent(QKeyEvent *event):鍵盤按鍵(含 Esc)

完整實現代碼

// MainWindow.h
#include <QMainWindow>
#include <QCloseEvent>
#include <QKeyEvent>

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    void closeEvent(QCloseEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;

private:
    void gracefulShutdown(); // 統一關閉邏輯
    // 假設成員:WorkerThread *worker; QTimer *pollTimer; ...
};

// MainWindow.cpp
void MainWindow::closeEvent(QCloseEvent *event) {
    if (isShuttingDown) { // 防重入
        event->accept();
        return;
    }
    // 1. 忽略默認關閉,啟動優雅關閉流程
    event->ignore();
    gracefulShutdown();
    // 2. 實際關閉在 gracefulShutdown 完成後調用 close()
    // 或由 worker 完成信號觸發 this->close()
}

void MainWindow::keyPressEvent(QKeyEvent *event) {
    if (event->key() == Qt::Key_Escape) {
        // Esc 鍵觸發關閉,復用 closeEvent 邏輯
        close(); // 會調用 closeEvent,而非直接銷毀
    } else {
        QMainWindow::keyPressEvent(event); // 傳遞給父類處理其他鍵
    }
}

void MainWindow::gracefulShutdown() {
    isShuttingDown = true;
    // 1. 停止定時器/週期性任務
    if (pollTimer) pollTimer->stop();

    // 2. 通知工作執行緒退出
    if (worker) {
        worker->requestInterruption(); // Qt 5.15+ 標準方式
        // 或 worker->setStopFlag(true);
        // 不要在這裡直接 wait(),會凍結 GUI!
    }

    // 3. 關閉資料庫連線、網路 Socket、檔案句柄等
    // db.close(); socket.disconnectFromHost(); ...

    // 4. 若有異步操作,連接完成信號到真正關閉
    // connect(worker, &WorkerThread::finished, this, [this]{
    //     this->close(); // 重新發起關閉,此時 closeEvent 會 accept
    // });

    // 5. 若無異步或強制退出(超時),直接 accept
    // 這裡演示:設置 3 秒強制退出定時器
    QTimer::singleShot(3000, this, [this]{
        qWarning() << "Graceful shutdown timeout, forcing exit";
        QApplication::quit(); // 強制退出事件循環
    });
}

關鍵細節解析

  1. event->ignore() vs event->accept():
    • ignore():告訴 Qt「我暫不處理,視窗先別關」。稍後調用 close() 會再次觸發 closeEvent,此時可 accept()。
    • accept():同意關閉,視窗進入銷毀流程。
  2. 不要在 closeEvent 中阻塞等待(如 thread.wait()、sleep()),會凍結事件循環,導致界面無響應、甚至觸發系統「程序無響應」對話框。
  3. close() 是**非阻塞**請求,會投遞 QCloseEvent 到事件循環;QApplication::quit() 是**強制退出**事件循環。
  4. Esc 鍵預設行為:QDialog 按 Esc 會 reject();QMainWindow **無預設處理**,需自行實現(如本文)。
  5. 系統關機/登出:Windows WM_QUERYENDSESSION、Linux SIGTERM 也會觸發 closeEvent。同樣需快速返回,異步清理。

進階模式:關閉狀態機

enum class ShutdownState { Running, Closing, Forced };
ShutdownState shutdownState = ShutdownState::Running;

void closeEvent(QCloseEvent *e) {
    if (shutdownState == ShutdownState::Running) {
        e->ignore();
        shutdownState = ShutdownState::Closing;
        startAsyncShutdown(); // 異步啟動
    } else if (shutdownState == ShutdownState::Closing) {
        e->ignore(); // 正在關閉中,忽略重複請求
    } else { // Forced
        e->accept(); // 強制接受
    }
}

// 異步完成時
void onAsyncShutdownFinished() {
    shutdownState = ShutdownState::Forced;
    close(); // 再次觸發 closeEvent,此時 accept
}

總結

Qt 視窗關閉的正確姿勢:**攔截 closeEvent → ignore() → 啟動異步清理 → 完成後 close() 觸發 accept()**。Esc 鍵調用 close() 復用同一流程。切記:**GUI 執行緒永不阻塞**,所有耗時操作移至工作執行緒並以信號槽通知完成。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved