如何拦截和取消窗口的最小化? [英] How can I intercept and cancel the minimizing of a Window?

查看:14
本文介绍了如何拦截和取消窗口的最小化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的项目中有一个 Window 子类,在运行时该实例被创建并完全显示在 QML 端.我知道我可以通过在 flags: 中不包含 WindowMinimizeButtonHint 来防止窗口最小化,但我实际上需要存在并启用最小化按钮但能够拦截最小化按钮单击,取消实际最小化,并做其他事情(仅供参考,我的客户需要这种非标准的窗口行为,而不是我).

I have a Window subclass in my project, and at runtime the instance is created and shown entirely on the QML side. 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).

到目前为止,我唯一能够实现的是处理 onWindowStateChanged: 事件,检查 windowState === Qt.WindowStateMinimized 是否并调用 show() 来自计时器(在事件处理程序中直接调用它什么都不做).这会导致窗口向下移动到系统托盘,然后突然恢复正常.

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). This results in the window moving down to the system tray and then suddenly coming back up to normal.

有没有办法做到这一点,比如可以取消的 OnMinimized 事件?

Is there any way to do this, something like an OnMinimized event that can be cancelled?

根据 Benjamin T 的回答,我至少是 OSX 解决方案的一部分:

based on Benjamin T's answer, I'm at least part way to a solution for 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 似乎没有任何内容.也许我需要转换为不同类型的事件?

In this example I'm able to intercept and cancel all NSKeyDown events (while leaving other events like mouse clicks etc. still working). 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. Perhaps I need to cast to a different type of event?

编辑 2 - 工作解决方案:

我无法找到任何方法来正确拦截最小化事件并取消它,因此我的解决方法是拦截窗口上的点击,确定点击是否在最小化按钮(或关闭或缩放按钮) 并取消事件(如果发生了点击,并向我的 qml 窗口发送通知).我还处理了双击标题栏以缩放窗口,并使用 Command-M 键最小化窗口的情况.

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). I also handle the case of double-clicking the titlebar to zoom the window, and using the Command-M keys to minimize the window.

第一步是实现一个QAbstractNativeEventFilter.在您的标题中:

First step is to implement a QAbstractNativeEventFilter. In your header:

#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中:

Then in 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.

由于您使用的是 QML,子类化可能会很困难,因为您无法访问所有 Qt 内部类.

Since you are using QML, subclassing might be difficult as you don't have access to all Qt internal classes.

这是使用事件过滤时代码的样子.

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

但是,您很快就会遇到与使用信号/槽机制时相同的问题:Qt 仅在窗口已经最小化时才通知您.这意味着此时恢复窗口将产生隐藏/显示效果.

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. 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.

以下代码适用于 Windows,您应该针对 macOS 进行调整:

The following code works on Windows, you should adapt it for 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() 中:

In your main():

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

有关更多信息,您可以阅读有关 QAbstractNativeEventFilter 的 Qt 文档.

For more info, you can read the Qt documentation about QAbstractNativeEventFilter.

您可能需要使用 QWindow::winId() 检查本机事件的目标窗口.

You may need to use QWindow::winId() to check to which window the native events are targeted.

由于我不是 macOS 开发人员,我不知道您可以使用 NSEvent 做什么.此外,似乎 NSWindowDelegate 类可能对您有用:https://developer.apple.com/documentation/appkit/nswindowdelegate如果你可以从 QWindow::winId() 中检索到一个 NSWindow,你应该可以使用它.

As I am not a macOS developer, I do not know what you can do with 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.

这篇关于如何拦截和取消窗口的最小化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆