简体   繁体   English

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

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

I have a Window subclass in my project, and at runtime the instance is created and shown entirely on the QML side. 我的项目中有一个Window子类,并且在运行时创建了实例并将其完全显示在QML端。 I know that I can prevent the window from being minimized by not including the WindowMinimizeButtonHint in the flags: , but I actually need to have the minimize button present and enabled but be able to intercept the minimize button click, cancel the actual minimizing, and do something else (FYI my client is requiring this non-standard windowing behavior, not me). 我知道我可以通过在flags:不包括WindowMinimizeButtonHint来防止窗口被最小化,但是我实际上需要显示并启用最小化按钮,但是能够拦截最小化按钮单击,取消实际的最小化,然后执行还有其他事情(仅供参考,我的客户要求这种非标准的窗口行为,而不是我)。

So far, the only thing I've been able to achieve is to handle the onWindowStateChanged: event, check if windowState === Qt.WindowStateMinimized and call show() from a timer (calling it inside the event handler directly does nothing). 到目前为止,我唯一能实现的就是处理onWindowStateChanged:事件,检查windowState === Qt.WindowStateMinimized并从计时器中调用show() (在事件处理程序中直接调用它不执行任何操作)。 This results in the window moving down to the system tray and then suddenly coming back up to normal. 这导致窗口向下移动到系统托盘,然后突然恢复正常。

Is there any way to do this, something like an OnMinimized event that can be cancelled? 有没有办法做到这一点,比如可以取消的OnMinimized事件?

Edit: based on Benjamin T's answer, I'm at least part way to a solution for OSX: 编辑:基于本杰明·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;
}

In this example I'm able to intercept and cancel all NSKeyDown events (while leaving other events like mouse clicks etc. still working). 在此示例中,我能够拦截并取消所有NSKeyDown事件(同时使其他事件(如鼠标单击等)仍然有效)。 The remaining problem is that I still don't know to intercept a minimize event - NSEvent.h doesn't seem to have anything that covers that. 剩下的问题是我仍然不知道截取一个最小事件 -NSEvent.h似乎没有任何东西可以解决这个问题。 Perhaps I need to cast to a different type of event? 也许我需要进行其他类型的活动?

Edit 2 - working solution: 编辑2-工作解决方案:

I was not able to find any way to intercept the minimize event proper and cancel it, so my workaround is to instead intercept the click on the window, determine if the click is over the minimize button (or the close or zoom buttons) and cancel the event if so (and send a notification to my qml window that the click occurred). 我无法找到适当的方法来拦截最小事件并将其取消,因此,我的解决方法是拦截窗口上的单击,确定单击是否在最小化按钮(或关闭或缩放按钮)上并取消如果是这样,则为事件(并将点击发生的通知发送至我的qml窗口)。 I also handle the case of double-clicking the titlebar to zoom the window, and using the Command-M keys to minimize the window. 我还处理了双击标题栏以缩放窗口,并使用Command-M键最小化窗口的情况。

First step is to implement a QAbstractNativeEventFilter . 第一步是实现QAbstractNativeEventFilter In your header: 在标题中:

#include <QAbstractNativeEventFilter>

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

The implementation: 实现:

#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;
}

Then in main.cpp: 然后在main.cpp中:

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

Generally speaking, you should use the event system ans not signal/slots to intercept events and changes. 一般而言,您应该使用事件系统而不是信号/插槽来拦截事件和更改。

The easiest way to do so is either to subclass the object you use and reimplement the appropriate event handler, or to use an event filter. 这样做的最简单方法是将您使用的对象子类化并重新实现适当的事件处理程序,或者使用事件过滤器。

Since you are using QML, subclassing might be difficult as you don't have access to all Qt internal classes. 由于您使用的是QML,因此子类化可能很困难,因为您无法访问所有Qt内部类。

Here is what the code would look like when using event filtering. 使用事件过滤时,代码如下所示。

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;
}

However, you will quickly run into the same issue that when using the signal/slot mechanism: Qt only notify you when the window has already been minimized. 但是,您将很快遇到与使用信号/插槽机制相同的问题:Qt仅在窗口已最小化时通知您。 Meaning that restoring the window at this point will make a hide/show effect. 意味着此时恢复窗口将产生隐藏/显示效果。

So you need to go deeper and you a native event filter. 因此,您需要更深入地了解本机事件过滤器。

The following code works on Windows, you should adapt it for macOS: 以下代码在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;
}

In your main(): 在您的main()中:

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

For more info, you can read the Qt documentation about QAbstractNativeEventFilter . 有关更多信息,您可以阅读有关QAbstractNativeEventFilter的Qt文档。

You may need to use QWindow::winId() to check to which window the native events are targeted. 您可能需要使用QWindow::winId()来检查本机事件针对的窗口。

As I am not a macOS developer, I do not know what you can do with NSEvent . 由于我不是macOS开发人员,所以我不知道您可以使用NSEvent做什么。 Also it seems the NSWindowDelegate class could be useful to you: https://developer.apple.com/documentation/appkit/nswindowdelegate If you can retrieve a NSWindow from QWindow::winId() , you should be able to use it. 另外,似乎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