簡體   English   中英

如何攔截和取消最小化窗口?

[英]How can I intercept and cancel the minimizing of a Window?

我的項目中有一個Window子類,並且在運行時創建了實例並將其完全顯示在QML端。 我知道我可以通過在flags:不包括WindowMinimizeButtonHint來防止窗口被最小化,但是我實際上需要顯示並啟用最小化按鈕,但是能夠攔截最小化按鈕單擊,取消實際的最小化,然后執行還有其他事情(僅供參考,我的客戶要求這種非標准的窗口行為,而不是我)。

到目前為止,我唯一能實現的就是處理onWindowStateChanged:事件,檢查windowState === Qt.WindowStateMinimized並從計時器中調用show() (在事件處理程序中直接調用它不執行任何操作)。 這導致窗口向下移動到系統托盤,然后突然恢復正常。

有沒有辦法做到這一點,比如可以取消的OnMinimized事件?

編輯:基於本傑明·T的回答,我至少是OSX解決方案的一部分:

#import <AppKit/AppKit.h>

bool NativeFilter::nativeEventFilter(const QByteArray &eventType, 
    void *message, long *result)
{
    if (eventType == "mac_generic_NSEvent") {
        NSEvent *event = static_cast<NSEvent *>(message);
        if ([event type] == NSKeyDown) {
            return true;
        }
    }
    return false;
}

在此示例中,我能夠攔截並取消所有NSKeyDown事件(同時使其他事件(如鼠標單擊等)仍然有效)。 剩下的問題是我仍然不知道截取一個最小事件 -NSEvent.h似乎沒有任何東西可以解決這個問題。 也許我需要進行其他類型的活動?

編輯2-工作解決方案:

我無法找到適當的方法來攔截最小事件並將其取消,因此,我的解決方法是攔截窗口上的單擊,確定單擊是否在最小化按鈕(或關閉或縮放按鈕)上並取消如果是這樣,則為事件(並將點擊發生的通知發送至我的qml窗口)。 我還處理了雙擊標題欄以縮放窗口,並使用Command-M鍵最小化窗口的情況。

第一步是實現QAbstractNativeEventFilter 在標題中:

#include <QAbstractNativeEventFilter>

class NativeFilter : public QAbstractNativeEventFilter {
public:
    bool nativeEventFilter(const QByteArray &eventType, void *message, 
        long *result);
};

實現:

#import <AppKit/AppKit.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSButton.h>

bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void 
    *message, long *result)
{
    if (eventType == "mac_generic_NSEvent") {

        NSEvent *event = static_cast<NSEvent *>(message);
        NSWindow *win = [event window];

        // TODO: determine whether or not this is a window whose
        // events you want to intercept. I did this by checking
        // [win title] but you may want to find and use the 
        // window's id instead.

        // Detect a double-click on the titlebar. If the zoom button 
        // is enabled, send the full-screen message to the window
        if ([event type] == NSLeftMouseUp) {
            if ([event clickCount] > 1) {
                NSPoint pt = [event locationInWindow];
                CGRect rect = [win frame];
                // event coordinates have y going in the opposite direction from frame coordinates, very annoying
                CGFloat yInverted = rect.size.height - pt.y;
                if (yInverted <= 20) {
                    // TODO: need the proper metrics for the height of the title bar

                    NSButton *btn = [win standardWindowButton:NSWindowZoomButton];
                    if (btn.enabled) {

                        // notify qml of zoom button click

                    }

                    return true;
                }
            }
        }

        if ([event type] == NSKeyDown) {

            // detect command-M (for minimize app)
            if ([event modifierFlags] & NSCommandKeyMask) {

                // M key
                if ([event keyCode] == 46) {
                    // notify qml of miniaturize button click
                    return true;
                }

            }

            // TODO: we may be requested to handle keyboard actions for close and zoom buttons. e.g. ctrl-cmd-F is zoom, I think,
            // and Command-H is hide.

        }


        if ([event type] == NSLeftMouseDown) {

            NSPoint pt = [event locationInWindow];
            CGRect rect = [win frame];

            // event coordinates have y going in the opposite direction from frame coordinates, very annoying
            CGFloat yInverted = rect.size.height - pt.y;

            NSButton *btn = [win standardWindowButton:NSWindowMiniaturizeButton];
            CGRect rectButton = [btn frame];
            if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
                if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {

                    // notify .qml of miniaturize button click

                    return true;
                }
            }

            btn = [win standardWindowButton:NSWindowZoomButton];
            rectButton = [btn frame];

            if (btn.enabled) {
                if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
                    if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {

                        // notify qml of zoom button click

                        return true;
                    }
                }
            }

            btn = [win standardWindowButton:NSWindowCloseButton];
            rectButton = [btn frame];
            if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
                if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {

                    // notify qml of close button click

                    return true;
                }
            }

        }

        return false;

    }

    return false;
}

然后在main.cpp中:

Application app(argc, argv);
app.installNativeEventFilter(new NativeFilter());

一般而言,您應該使用事件系統而不是信號/插槽來攔截事件和更改。

這樣做的最簡單方法是將您使用的對象子類化並重新實現適當的事件處理程序,或者使用事件過濾器。

由於您使用的是QML,因此子類化可能很困難,因為您無法訪問所有Qt內部類。

使用事件過濾時,代碼如下所示。

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);


    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    auto root = engine.rootObjects().first();
    root->installEventFilter(new EventFilter());

    return app.exec();
}

class EventFilter : public QObject
{
    Q_OBJECT
public:
    explicit EventFilter(QObject *parent = nullptr);
    bool eventFilter(QObject *watched, QEvent *event) override;
};

bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::WindowStateChange) {
        auto e = static_cast<QWindowStateChangeEvent *>(event);
        auto window = static_cast<QWindow *>(watched);

        if (window->windowStates().testFlag(Qt::WindowMinimized)
                && ! e->oldState().testFlag(Qt::WindowMinimized))
        {
            // Restore old state
            window->setWindowStates(e->oldState());
            return true;
        }
    }

    // Do not filter event
    return false;
}

但是,您將很快遇到與使用信號/插槽機制相同的問題:Qt僅在窗口已最小化時通知您。 意味着此時恢復窗口將產生隱藏/顯示效果。

因此,您需要更深入地了解本機事件過濾器。

以下代碼在Windows上有效,您應該將其適配於macOS:

class NativeFilter : public QAbstractNativeEventFilter {
public:
    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
};

bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
/* On Windows we interceot the click in the title bar. */
/* If we wait for the minimize event, it is already too late. */
#ifdef Q_OS_WIN
    auto msg = static_cast<MSG *>(message);
    // Filter out the event when the minimize button is pressed.
    if (msg->message == WM_NCLBUTTONDOWN && msg->wParam == HTREDUCE)
        return true;
#endif

/* Example macOS code from Qt doc, adapt to your need */
#ifdef Q_OS_MACOS
    if (eventType == "mac_generic_NSEvent") {
        NSEvent *event = static_cast<NSEvent *>(message);
        if ([event type] == NSKeyDown) {
            // Handle key event
            qDebug() << QString::fromNSString([event characters]);
        }
}
#endif

    return false;
}

在您的main()中:

QGuiApplication app(argc, argv);
app.installNativeEventFilter(new NativeFilter());

有關更多信息,您可以閱讀有關QAbstractNativeEventFilter的Qt文檔。

您可能需要使用QWindow::winId()來檢查本機事件針對的窗口。

由於我不是macOS開發人員,所以我不知道您可以使用NSEvent做什么。 另外,似乎NSWindowDelegate類可能對您有用: https : //developer.apple.com/documentation/appkit/nswindowdelegate如果可以從QWindow::winId()檢索NSWindow ,則應該可以使用它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM