Как я могу перехватить и отменить свертывание окна?
У меня есть Window
подкласс в моем проекте, и во время выполнения экземпляр создается и отображается полностью на стороне QML. Я знаю, что могу предотвратить сворачивание окна, не включая WindowMinimizeButtonHint
в flags:
, но на самом деле мне нужно, чтобы кнопка минимизации присутствовала и была включена, но я мог перехватывать нажатие кнопки минимизации, отменять фактическое свертывание и делать что-то еще (к вашему сведению, мой клиент требует такого нестандартного поведения окон, а не я).
Пока что единственное, чего я смог достичь, это справиться с 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 нет ничего, что бы покрывало это. Возможно, мне нужно привести в другой тип события?
Edit 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 ответ
Вообще говоря, вы должны использовать систему событий, а не сигнал / слоты для перехвата событий и изменений.
Самый простой способ сделать это - создать подкласс используемого вами объекта и переопределить соответствующий обработчик событий или использовать фильтр событий.
Поскольку вы используете 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());
Для получения дополнительной информации вы можете прочитать документацию Qt о QAbstractNativeEventFilter
,
Вам может понадобиться использовать QWindow::winId()
проверить, к какому окну относятся нативные события.
Поскольку я не разработчик MacOS, я не знаю, что вы можете сделать с NSEvent
, Также кажется, что NSWindowDelegate
Класс может быть полезен для вас: https://developer.apple.com/documentation/appkit/nswindowdelegate Если вы можете получить NSWindow
от QWindow::winId()
, вы должны быть в состоянии использовать его.