Installing an event filter on a QScrollArea viewport
我在属于顶级
当发生鼠标事件并被安装在
顶层窗口小部件实例化自定义窗口小部件和滚动区域(让我们忘记现在是通过Qt Designer完成的,我们需要Aggregation-composition-etc等生命周期管理方面),将窗口小部件添加到滚动区域的布局中,然后实例化事件过滤器并将其设置到自定义窗口小部件上。
每当自定义小部件中拦截到鼠标事件时,事件过滤器就会发出带有全局鼠标事件位置的信号。
顶层窗口小部件对信号做出反应并调用
然后,顶层小部件在自定义小部件中调用适当的方法,即
这样,顶级窗口小部件就是实体之间的中介者。另一种方法如下:
与上面的1相同。
对事件过滤器进行编程以了解滚动区域。
每当事件过滤器在自定义窗口小部件中拦截鼠标事件时,它就会调用
定制窗口小部件订阅该信号并做出相应的反应。
这样,顶级窗口小部件仅实例化实体,并让它们自己处理业务。
编辑:现在,我已经了解了另一种方法,其中顶级小部件重新实现
我注意到,很难谈论Qt的体系结构,因为信号和插槽违反了接口的概念,因此"针对接口编程"规则几乎无效。任何东西都可以连接到它想要的地方。仍然,以上问题至少具有可能的实体布局,甚至可能还有更多。
我的方法是否正确,并且在任何方面都与使用QWidgets和C ++在Qt5中应该采取的方法类似吗?
鼠标追踪救援
好消息是:不需要显式的事件管理。在子窗口小部件上启用鼠标跟踪后,即使存在中间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // https://github.com/KubaO/stackoverflown/tree/master/questions/scrollarea-filter-40605540 #include <QtWidgets> class Tracker : public QFrame { QPoint pos; void invalidatePos() { pos.setX(-1); } bool isPosInvalid() const { return pos.x() < 0; } void mouseMoveEvent(QMouseEvent *event) override { pos = event->pos(); update(); } void paintEvent(QPaintEvent *event) override { QFrame::paintEvent(event); if (isPosInvalid()) return; QPainter p{this}; p.setPen(Qt::red); p.setBrush(Qt::red); p.drawEllipse(pos, 4, 4); } void leaveEvent(QEvent *event) { invalidatePos(); update(); QFrame::leaveEvent(event); } public: Tracker(QWidget * parent = nullptr) : QFrame{parent} { setFrameStyle(QFrame::Panel); setLineWidth(2); setMouseTracking(true); } }; class TopWidget : public QWidget { QVBoxLayout m_layout{this}; QScrollArea m_area; QWidget m_child; Tracker m_tracker{&m_child}; public: TopWidget(QWidget * parent = nullptr) : QWidget{parent} { m_layout.addWidget(&m_area); m_area.setWidget(&m_child); m_child.setMinimumSize(1024, 1024); m_tracker.setGeometry(150, 150, 300, 300); } }; int main(int argc, char ** argv) { QApplication app{argc, argv}; TopWidget ui; ui.show(); return app.exec(); } |
除了信号和插槽
首先,信号和插槽肯定提供接口:它们是接口的本质,因为它们提供了减少代码耦合的一种方法。"任何事物都可以连接到所需的位置"的观察仅部分正确:只有在信号或插槽是接口的一部分时,它才是正确的。
例如,假设您有一个用户界面小部件显示坐标。确实可以随意连接各个子控件的接口,但是这些控件是封装的,您当然不能以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class CoordinateDialog : public QDialog { Q_OBJECT Q_PROPERTY(QVector3D value READ value WRITE setValue NOTIFY coordinatesChanged) QVector3D m_value; QFormLayout m_layout{this}; QDoubleSpinBox m_x, m_y, m_z; QDialogButtonBox m_buttons; public: CoordinateDialog(QWidget *parent = nullptr) : CoordinateDialog(QVector3D(), parent) {} CoordinateDialog(const QVector3D &value, QWidget *parent = nullptr) : QDialog{parent}, m_value(value) { m_layout.addRow("X", &m_x); m_layout.addRow("Y", &m_y); m_layout.addRow("Z", &m_z); m_layout.addRow(&m_buttons); m_buttons.addButton(QDialogButtonBox::Ok); m_buttons.addButton(QDialogButtonBox::Cancel); connect(&m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(&m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(&m_x, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [=](double x){ auto v = m_value; v.setX(x); setValue(v); }); connect(&m_y, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [=](double y){ auto v = m_value; v.setY(y); setValue(v); }); connect(&m_z, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [=](double z){ auto v = m_value; v.setZ(z); setValue(v); }); } Q_SIGNAL void coordinatesChanged(const QVector3D &); Q_SIGNAL void coordinatesAccepted(const QVector3D &); void accept() override { emit coordinatesAccepted(m_value); QDialog::accept(); } QVector3D value() const { return m_value; } Q_SLOT void setValue(const QVector3D &value) { if (m_value == value) return; m_value = value; m_x.setValue(m_value.x()); m_y.setValue(m_value.y()); m_z.setValue(m_value.z()); emit coordinatesChanged(m_value); } }; |
作为此类的用户,您的接口是