从 QGraphicsItem 的 hover 说开来

来源:互联网 发布:linux find mmin 编辑:程序博客网 时间:2024/05/16 10:42

从 QGraphicsItem 的 hover 说开来


取个标题真难啊,就这么着吧。

起源

来自 CSDN 论坛的网友问题:

在GraphicsItem 中我设置了 setAcceptHoverEvents(true);
item加入到scene
scene加入到View,在View中mouseMoveEvent中为什么他就自动触发了
设置setMouseTracking(false)不好用,
只要鼠标移动QGraphicsView的mouseMove就执行问题重现

看原因之前,我们先用代码重现一下前面的问题:

try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui

class Widget(QtGui.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
scene = QtGui.QGraphicsScene(self)
item = QtGui.QGraphicsRectItem(0, 0, 100, 100)
item.setAcceptHoverEvents(True)
scene.addItem(item)
self.setScene(scene)

def mouseMoveEvent(self, evt):
print "mouse move", evt.pos()

if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())原因

我们知道,要接收 hover 事件,必定要接收mouseMove事件;而在graphics系统中,所有的鼠标事件又都是经由view接收的。

所以,可以想到,一旦某一个 item 设置了接收 hover 事件,相应的 viewport 必定要被设置 setMouseTracking(True)

代码

口说不足以让大家信服啊,只看上代码了。

setAcceptHoverEvents(True)

根据分析:当我们对一个item设置接收 hover 时,它要将相应的view设置为接收mouseTracking

void QGraphicsItem::setAcceptHoverEvents(bool enabled)
{
if (d_ptr->acceptsHover == quint32(enabled))
return;
d_ptr->acceptsHover = quint32(enabled);
if (d_ptr->acceptsHover && d_ptr->scene && d_ptr->scene->d_func()->allItemsIgnoreHoverEvents) {
d_ptr->scene->d_func()->allItemsIgnoreHoverEvents = false;
d_ptr->scene->d_func()->enableMouseTrackingOnViews();
}
}

代码显示:如果item在scene中,将会调用 scene的 enableMouseTrackingOnViews 函数。

而后,我们看到:scene 将对所有的 view 设置 mouseTracking

void QGraphicsScenePrivate::enableMouseTrackingOnViews()
{
foreach (QGraphicsView *view, views)
view->viewport()->setMouseTracking(true);
}addItem

前面说的是 item 在scene 中的情况,如果对item设置接收hover时,item尚未在scene中怎么办?

只能是 addItem 时生效了,代码证实了这一点。

void QGraphicsScene::addItem(QGraphicsItem *item)
{
...
// Enable mouse tracking if the item accepts hover events or has a cursor set.
if (d->allItemsIgnoreHoverEvents && d->itemAcceptsHoverEvents_helper(item)) {
d->allItemsIgnoreHoverEvents = false;
d->enableMouseTrackingOnViews();
}
...setScene

前面的代码中,是在scene已经设置到view的情况下说的,如果scene与view尚未链接时,就设置了接收hover呢?

void QGraphicsView::setScene(QGraphicsScene *scene)
{
...
// We are only interested in mouse tracking if items accept
// hover events or use non-default cursors.
if (!d->scene->d_func()->allItemsIgnoreHoverEvents
|| !d->scene->d_func()->allItemsUseDefaultCursor) {
d->viewport->setMouseTracking(true);
}
...

呵呵,到此为止,item hover和 view 的mousemove已经没什么疑问了吧?

可是:真的没有其他问题了么?

viewport

注意前面的代码:我们设置mouseTracking时,设置的对象是 viewport。然而我们的问题中:mouseMoveEvent 是 QGraphicsView 的事件。怎么回事呢?

其实也简单:QGraphicsView 截获了其viewport的事件,很容易想到是通过事件过滤器实现的。

事件过滤class QAbstractScrollAreaFilter : public QObject
{
Q_OBJECT
public:
QAbstractScrollAreaFilter(QAbstractScrollAreaPrivate *p) : d(p)
{ setObjectName(QLatin1String("qt_abstractscrollarea_filter")); }
bool eventFilter(QObject *o, QEvent *e)
{ return (o == d->viewport ? d->viewportEvent(e) : false); }
private:
QAbstractScrollAreaPrivate *d;
};

尽管是代码段,我们也可以看到,事件过滤的职责,交给了 QGraphicsViewPrivate 的 viewportEvent()

它进而调用了 QGraphicsView 的 viewportEvent() 函数:

inline bool viewportEvent(QEvent *event)
{ return q_func()->viewportEvent(event); }

这个viewportEvent 的介绍,其实看Manual就可以了(此处略)

setMouseTracking

前面的代码中,我们提到的都是 对 viewport 设置 setMouseTracking,如果我们对 QGraphicsView 直接设置 setMouseTracking 会不会有效呢?

看看代码:

bool QAbstractScrollArea::event(QEvent *e)
{
Q_D(QAbstractScrollArea);
switch (e->type()) {
...
case QEvent::MouseTrackingChange:
d->viewport->setMouseTracking(hasMouseTracking());
break;
...

看到什么没:当 MouseTrackingChange 改变时,会设置相应 viewport 的状态。但是,这并是说二者是等效的。

比如说:如果我们在前面的例子代码中“构造函数最后一行”添加下面的行:

self.viewport().setMouseTracking(False)

将会禁用掉 mouseTrack 和 hover

但如果我们用

self.setMouseTracking(False)

则不会有任何效果

self.setMouseTracking(True)
self.setMouseTracking(False)

却会有禁用的效果。(为什么呢?如果你对仔细看本节前面的代码,应该会明白的)


原创粉丝点击