查找QGraphicsItem的屏幕位置

15

使用场景:这应该是一个相当常见的问题。在一个带有QMdiArea的普通QMainWindow中,存在一个带有QGraphicsView的mdiChild。此视图显示一个带有QGraphicsItems的QGraphicsScene。对这些项之一进行右键单击将选择(关注)该项并打开上下文菜单,方便地放置在屏幕坐标QGraphicsSceneMouseEvent::screenPos()处。这正在按预期工作。

现在我想在用户按下按键(例如Qt :: Key_Menu)时显示相同的上下文菜单。我知道所选(聚焦)的项目,我知道显示场景的视图。

所以我的问题是:
以正确的方式获取场景中QGraphicsItem的可见表示在全局屏幕坐标中的位置是什么?

以下内容无法正常工作:

QGraphicsItem *item = ...; // is the currently selected item next to which the context menu shall be opened
QGraphicsScene *scene = ...; // is the scene that hosts the item
QGraphicsView *graphicsView = ...; // is the view displaying the scene, this inherits from QWidget

// get the position relative to the scene
QPointF sp = item->scenePos();
// or use
QPointF sp = item->mapToScene(item->pos());

// find the global (screen) position of the item
QPoint global = graphicsView->mapToGlobal(graphicsView->mapFromScene(sp));

// now
myContextMenu.exec(global);
// should open the context menu at the top left corner of the QGraphicsItem item, but it goes anywhere

文档中提到: 如果你想知道一个项在视口中的位置,可以在项上调用QGraphicsItem::mapToScene(),然后在视图上调用QGraphicsView::mapFromScene()。
这正是我正在做的事情,对吗?


刚巧发现一个德国论坛的帖子指出:

QGraphicsView *view = item->scene()->views().last();

甚至更好的是:

QGraphicsView *view;
foreach (view,  this->scene()->views())
{
    if (view->underMouse() || view->hasFocus()) break;
}
// (use case in the forum thread:) // QMenu *menu = new QMenu(view);

使用这个可能会让我的问题得到更一般化的回答...

我正准备回复,但是重新阅读文档后,我认为我同意你的分析:QGraphicsView :: mapFromScene应该提供视口坐标(值得检查)。唯一的问题是在MDI子窗口内部的小部件上使用mapToGlobal是否存在潜在的错误。 - James Turner
@JamesTurner,那么你的第一个猜测会是什么(你会写出回应)? - Martin Hennings
3个回答

12

我找到了一个可行的解决方案。
QGraphicsItem必须在屏幕上可见。 (如果它因为视图显示场景的其他部分而不可见,可以将点限制在视图的视口矩形中。)

// get the screen position of a QGraphicsItem
// assumption: the scene is displayed in only one view or the first view is the one to determine the screen position for
QPoint sendMenuEventPos; // in this case: find the screen position to display a context menu at
QGraphicsItem *pFocusItem = scene()->focusItem();

if(scene() != NULL // the focus item belongs to a scene
    && !scene()->views().isEmpty() // that scene is displayed in a view...
    && scene()->views().first() != NULL // ... which is not null...
    && scene()->views().first()->viewport() != NULL // ... and has a viewport
    )
{
    QGraphicsView *v = scene()->views().first();
    QPointF sceneP = pFocusItem->mapToScene(pFocusItem->boundingRect().bottomRight());
    QPoint viewP = v->mapFromScene(sceneP);
    sendMenuEventPos = v->viewport()->mapToGlobal(viewP);
}

if(sendMenuEventPos != QPoint())
{
    // display the menu:
    QMenu m;
    m.exec(sendMenuEventPos);
}

使用视图的视口来将视图坐标映射到全局坐标非常重要。

上下文菜单键(Qt :: Key_Menu)的检测发生在“主”QGraphicsItem的keyPressEvent()中(由于我的程序结构)。


1
在事件期间,视图的视口通过QGraphicsSceneEvent::widget()暴露给场景。您可以使用QWidget::isAncestorOf(event->widget())来查找“正确”的视图,而不仅仅是取第一个视图。 - hmn
除非应用程序始终在全屏模式下运行,否则最好使用sendMenuEventPos = v->viewport()->mapTo(mainWindow, viewP);映射到QMainWindow,而不是使用mapToGlobal - vsz

1

代码似乎是正确的。但是可能存在创建上下文菜单的问题。

你是否将QContextMenu的父级设置为MainWindow(或应用程序中类似的内容)?

我认为这可能是你的问题所在。

祝你好运!


1

只是猜测,不过你可以看看这个http://www.qtcentre.org/threads/36992-Keyboard-shortcut-event-not-received

在查阅Qt文档时,似乎使用QGraphicsView可能会导致一些异常行为与快捷键有关。

看起来可能有一种规范的方法可以实现您想要的结果。

根据您如何实现上下文菜单、快捷键和QGraphicsView,您可能需要适当地设置QGraphicsView的Qt::ContextMenuPolicy,并以不同的方式构建和调用菜单。

我对这个问题很感兴趣,因为我很快就需要做类似的事情!


Qt::ActionsContextMenu 看起来很不错。我会仔细研究一下,但我并不太确信它会有所帮助。我的问题大多与定位有关,而不是键盘输入(我可以从任何地方调用该函数)。 - Martin Hennings
我猜这取决于你的实现方式,是否相关。我还发现文档中以下重载的exec函数定义很有趣:http://doc.qt.nokia.com/4.7-snapshot/qmenu.html#exec-3。它似乎是一个奇怪的静态方法,但暗示了当“...父项嵌入QGraphicsView”时,QPoint可能不够用。这有点神秘。 - Mike G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接