Как я могу перехватить и отменить минимизацию окна? - программирование
Подтвердить что ты не робот

Как я могу перехватить и отменить минимизацию окна?

У меня есть подкласс Window в моем проекте, и во время выполнения экземпляр создается и полностью отображается на стороне QML. Я знаю, что я могу предотвратить минимизацию окна, не включая WindowMinimizeButtonHint во flags: но мне действительно нужно, чтобы кнопка минимизации была включена и включена, но можно перехватить кнопку "Свернуть", отменить фактическое минимизацию и сделать что-то еще (FYI мой клиент требует этого нестандартного поведения окон, а не меня).

Пока что единственное, что мне удалось достичь, - это onWindowStateChanged: событие onWindowStateChanged: проверить, является ли windowState === Qt.WindowStateMinimized и вызывать show() из таймера (вызов его внутри обработчика событий напрямую ничего не делает). Это приводит к тому, что окно переходит к системному лотку, а затем неожиданно возвращается в нормальное состояние.

Есть ли способ сделать это, что-то вроде события OnMinimized которое можно отменить?

Изменение: на основе ответа Бенджамина Т, я по крайней мере частично способ решения 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 - рабочее решение:

Я не смог найти способ перехватить событие минимизации надлежащим образом и отменить его, поэтому мой обходной путь заключается в том, чтобы вместо этого перехватить щелчок по окну, определить, находится ли щелчок по кнопке минимизации (или кнопкам закрытия или масштабирования) и отменить событие if so (и отправить уведомление в мое окно 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 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());
4b9b3361

Ответ 1

Вообще говоря, вы должны использовать систему событий, а не сигнал/слоты, чтобы перехватывать события и изменения.

Самый простой способ сделать это - либо подклассифицировать объект, который вы используете, либо переопределить соответствующий обработчик событий, либо использовать фильтр событий.

Поскольку вы используете 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 уведомляет вас только о том, что окно уже свернуто. Это означает, что восстановление окна в этот момент сделает эффект hide/show.

Поэтому вам нужно идти глубже, и вы являетесь родным фильтром событий.

Следующий код работает в 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());

Для получения дополнительной информации вы можете прочитать документацию Qt о QAbstractNativeEventFilter.

Возможно, вам нужно будет использовать QWindow::winId() чтобы проверить, в какое окно нацелены нативные события.

Поскольку я не разработчик macOS, я не знаю, что вы можете сделать с NSEvent. Также кажется, что класс NSWindowDelegate может быть вам полезен: https://developer.apple.com/documentation/appkit/nswindowdelegate. Если вы можете получить NSWindow из QWindow::winId(), вы сможете использовать его.