代码之家  ›  专栏  ›  技术社区  ›  MusiGenesis

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

  •  14
  • MusiGenesis  · 技术社区  · 7 年前

    我有一个 Window 在我的项目中的子类,并且在运行时实例被创建并完全显示在QML端。我知道我可以通过不包括 WindowMinimizeButtonHint flags: ,但我实际上需要有最小化按钮存在并启用,但能够截获最小化按钮单击,取消实际最小化,并执行其他操作(仅供参考,我的客户机需要此非标准窗口行为,而不是我)。

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

    有什么办法可以做到这一点吗? OnMinimized 可以取消的事件?

    编辑:根据Benjamin 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事件(同时让其他事件(如鼠标单击等)仍在工作)。剩下的问题是 我还是不知道要截取一个最小化的事件 -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());
    
    1 回复  |  直到 7 年前
        1
  •  4
  •   cbuchart    7 年前

    一般来说,您应该使用事件系统而不是信号/时隙来拦截事件和更改。

    最简单的方法是对所使用的对象进行子类化并重新实现适当的事件处理程序,或者使用事件过滤器。

    因为您使用的是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;
    }
    

    在您的主体()中:

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

    有关更多信息,您可以阅读有关 QAbstractNativeEventFilter .

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

    因为我不是MacOS开发者,我不知道你能用什么 NSEvent . 而且看起来 NSWindowDelegate 类可能对您有用: https://developer.apple.com/documentation/appkit/nswindowdelegate 如果您可以检索 NSWindow QWindow::WinID()。 ,您应该能够使用它。