Распространение пользовательского QEvent на родительский виджет в Qt / PyQt - PullRequest
11 голосов
/ 05 июля 2010

Кулак, извиняюсь за длину вопроса.

Я пытаюсь распространить пользовательское событие Qt от дочерних виджетов до верхнего родительского виджета, чтобы вызвать какое-то действие, основанное на типе события, вместо связывания сигналов.

Qt docs предполагает, что каждое событие, опубликованное с postEvent(), имеющим методы accept() и ignore(), может распространяться (имеется в виду каждый QEvent подкласс).

Я пытался переопределитьcustomEvents метод вместо events, но безрезультатно.

Python

Я пробовал это в Python, используя PyQt4 (версия Qt 4.6).

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Foo(QWidget):
    def doEvent(self):
        QApplication.postEvent(self, QEvent(12345))

    def event(self, event):
        event.ignore()
        return False

class Bar(QWidget):
    def __init__(self, *args, **kwargs):
        super(Bar, self).__init__(*args, **kwargs)
        self.foo = Foo(self)
        layout = QHBoxLayout()
        layout.addWidget(self.foo)
        self.setLayout(layout)

    def event(self, event):
        if event.type() == 12345:
            self.someEventHandler()
        return True

    def someEventHandler(self):
        print 'Handler in {0}'.format(self.__class__.__name__)

if __name__=='__main__':
    app = QApplication([''])
    bar = Bar()
    bar.show()
    bar.foo.doEvent()
    app.exec_()

В этом примере Bar.someEventHandler() будет срабатывать только в том случае, если событие было опубликовано с self.parent() в качестве первого аргумента, например так:

def doEvent(self):
    QApplication.postEvent(self.parent(), QEvent(12345))

Это понятно, поскольку событие передается непосредственно получающему объекту.

C ++

Аналогичный пример в C ++:

foobar.h

#ifndef FOOBAR_H
#define FOOBAR_H

#include <QtGui>

class Foo : public QWidget
{
    Q_OBJECT

public:
    Foo(QWidget *parent = 0);
    void doEvent();
    bool event(QEvent *);
};


class Bar : public QWidget
{
    Q_OBJECT

public:
    Bar(QWidget *parent = 0);
    Foo *foo;
    bool event(QEvent *);
};

#endif // FOOBAR_H

foobar.cpp

#include "foobar.h"

Foo::Foo(QWidget *parent)
     : QWidget(parent) {}

void Foo::doEvent() {
    QEvent *event = new QEvent(QEvent::User);
    QApplication::postEvent(this, event);
}

bool Foo::event(QEvent *event)
{
    event->ignore();
    return QWidget::event(event);
}

Bar::Bar(QWidget *parent)
     : QWidget(parent)
 {
    foo = new Foo(this);
}

bool Bar::event(QEvent *event)
{
    if (event->type() == QEvent::User) {
        qDebug() << "Handler triggered";
        return true;
    }
    return QWidget::event(event);
}

main.cpp

#include <QtGui>
#include "foobar.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Bar bar(0);
    bar.show();
    bar.foo->doEvent();
    return app.exec();
}

Так же, как в python, это работает только яСобытие f передается непосредственно объекту.

void Foo::doEvent() {
    QEvent *event = new QEvent(QEvent::User);
    QApplication::postEvent(this->parentWidget(), event);
}

Возможно, я упустил момент, возможно ли, что только события Key и Mouse распространяются вверх?

Ответы [ 3 ]

18 голосов
/ 06 июля 2010

Я потратил некоторое время на просмотр источника Qt, чтобы ответить на ваш вопрос, и в конце концов я пришел к следующему: распространение событий на родительские виджеты выполняется QApplication::notify, в основном длинным оператором switch со всеми видами событий. Например, вот как это делается для QEvent::WhatsThisClicked:

...
case QEvent::WhatsThisClicked:
        {
            QWidget *w = static_cast<QWidget *>(receiver);
            while (w) {
                res = d->notify_helper(w, e);
                if ((res && e->isAccepted()) || w->isWindow())
                    break;
                w = w->parentWidget();
            }
        }
...

Теперь важный момент: это , а не , выполняемое для пользовательских событий (и многих других явных обработок стандартных событий Qt, в том числе), поскольку предложение default:

 default:
        res = d->notify_helper(receiver, e);
        break;

И notify_helper не распространяет события. Итак, мой ответ: очевидно, пользовательские события не распространяются на родительские виджеты, вам придется сделать это самостоятельно (или лучше: переопределить QApplication::notify (это виртуальный открытый член) и добавить распространение событий для вашего события (й) ).

Надеюсь, это поможет.

5 голосов
/ 07 июля 2010

Повторная реализация QApplication.notify в Python, которая будет распространять пользовательские события.

В Qt notfy_helper проверяет, вызваны ли фильтры событий (как QApplications, так и получатели), но я пропустил это, поскольку они мне не нужны, а notfy_helper - закрытый член.

from PyQt4.QtCore import QEvent
from PyQt4.QtGui import QApplication

class MyApp(QApplication):
    def notify(self, receiver, event):
        if event.type() > QEvent.User:
            w = receiver
            while(w):
                # Note that this calls `event` method directly thus bypassing
                # calling qApplications and receivers event filters
                res = w.event(event);
                if res and event.isAccepted():
                    return res
                w = w.parent()
        return super(MyApp, self).notify(receiver, event)

И вместо использования экземпляра QApplication мы используем экземпляр нашего подкласса.

import sys
if __name__=='__main__':
    app = MyApp(sys.argv)
1 голос
/ 02 мая 2013

В качестве альтернативы ответу ребуса в этом фрагменте реализовано ручное распространение события:

def _manual_propagate(target, evt):
    app = QtGui.QApplication.instance()
    while target:
        app.sendEvent(target, evt)
        if not evt.isAccepted():
            if hasattr(target, 'parent'):
                target = target.parent()
        else:
            target = None
    return evt.isAccepted()

Обратите внимание, что здесь используется sendEvent, и, следовательно, он должен вызываться в потоке, в котором живет целевой объект.Это ограничение можно обойти с большей косвенностью.

Обратите внимание, что вам нужно будет вызывать _manual_propagate / вместо / sendEvent самостоятельно.Это «менее автоматическая» версия техники ребуса.

Кроме того, поскольку в этой версии используется sendEvent, фильтры событий на объектах правильно вызываются.


Соответствующие причины пользовательских событий:не распространяется, по крайней мере в Qt 4.8, так:

//qobject.cpp
bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    //several cases skipped
    default:
        if (e->type() >= QEvent::User) {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}

//more skips

/*!
    This event handler can be reimplemented in a subclass to receive
    custom events. Custom events are user-defined events with a type
    value at least as large as the QEvent::User item of the
    QEvent::Type enum, and is typically a QEvent subclass. The event
    is passed in the \a event parameter.

    \sa event(), QEvent
*/
void QObject::customEvent(QEvent * /* event */)
{
}

То есть QObject::event вызывает другой метод при получении пользовательского события, а затем break s из его оператора switchи выполняет return true;'.Это return true сигнализирует вызывающей стороне (обычно QCoreApplication::notify, что событие было обработано.

...