QMenu 第一次没有在正确的位置执行

QMenu not execing at correct position first time

关于 QMenu 及其在执行时的位置,我有一个非常奇怪的问题。

这是我的子代码classed QMenu:

DockItemContextMenu::DockItemContextMenu(QWidget *parent) : QMenu(parent){
    style = qApp->style();
    QPointer<QAction> restoreAction = new QAction(QIcon(style->standardIcon(QStyle::SP_TitleBarMaxButton)), "Restore", this);
    QPointer<QAction> minimizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMinButton), "Minimize", this);
    QPointer<QAction> maximizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMaxButton), "Maximize", this);
    QPointer<QAction> stayOnTopAction = new QAction("Stay On Top", this);
    stayOnTopAction->setCheckable(true);
    QPointer<QAction> closeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarCloseButton), "Close", this);

    this->addActions({restoreAction, minimizeAction, maximizeAction, stayOnTopAction, closeAction});

    connect(restoreAction, &QAction::triggered, parent, [this](){ emit restoreTriggered();}, Qt::QueuedConnection);
    connect(minimizeAction, &QAction::triggered, parent, [this](){ emit minimizeTriggered();}, Qt::QueuedConnection);
    connect(maximizeAction, &QAction::triggered, parent, [this](){ emit maximizeTriggered();}, Qt::QueuedConnection);
    connect(stayOnTopAction, &QAction::triggered, parent, [this](){ emit stayOnTopTriggered();}, Qt::QueuedConnection);
    connect(closeAction, &QAction::triggered, parent, [this](){ emit closeTriggered();}, Qt::QueuedConnection);
}

好吧,基本上我有另一个小部件,它持有这个 DockItemContextMenu 的一个实例作为一个字段。在这个名为 Titlebar 的拥有 class 中,我做的是右击会发出 customContextMenuRequested(QPoint) 信号。

TitleBar::TitleBar(QString title, QWidget *parent){
        ...
        this->setContextMenuPolicy(Qt::CustomContextMenu);
        contextMenu = new DockItemContextMenu(this);
        connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
        ...
}

在此之后,这个小部件实际上被插入到 QGraphicsScene 中,并隐式转换为 QGraphicsItem。当我在我的 Titlebar 上执行第一次右键单击事件时,如果我将整个 QApplicationMainWindow 拖动到屏幕上起始位置以外的任何位置,它将不会在正确的屏幕位置执行。除了在 QGraphicsScene 中之外,此场景本身始终存储在 QSplitter 中。现在我会明白这是否总是有某种问题,但事实证明,每次我为该信号调用插槽时,只有第一次它会在 QGraphicsScene 中的错误位置执行。无论我如何操纵 Titlebar 小部件本身的大小,将命令移动或最大化命令到 MainWindow,甚至编辑 QGraphicsView 的拆分器大小都会影响 QGraphicsScene,之后会一直在正确的位置。这是执行函数:

void TitleBar::showContextMenu(QPoint point){
    qDebug() << point;
    contextMenu->exec(point);
    emit _parent->focusChangedIn();
}

我打印了它调用 exec 的点。最奇怪的是,我两次在同一个位置单击鼠标右键,它会为第一个 exec 和第二个 exec 打印插槽位置参数的相同值,但除了第一个之外,每次都在正确的位置。当我将上下文菜单添加到 Titlebar class 时,我是否忘记设置其他标志?它与将 QMenu's 父级设置为 Titlebar 有什么关系吗?我只是傻眼了,在给定相同值的情况下,相同的 QPoint 怎么会在两个不同的屏幕位置 exec。有没有人知道第一次调用 execingQMenuTitlebar's 插槽时可能发生或不发生什么?

编辑:问题源于在 Titlebar 构造函数中执行这行代码:

contextMenu = new DockItemContextMenu(this);

将其更改为:

contextMenu = new DockItemContextMenu;

已解决问题。有谁知道为什么,或者这可能是一个错误?我宁愿不接受这个作为答案,因为它没有解释为什么它首先发生。

这是一个具有相同效果的最小示例。

MainWindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QWidget>
#include <QGraphicsView>
#include <QSplitter>
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QPointer>
#include <QTreeWidget>
#include "titlebar.h"

class MainWindow : public QMainWindow{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:

};

#endif // MAINWINDOW_H

MainWindow.cpp:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){
    QPointer<QWidget> widgetArea = new QWidget;
    QPointer<QHBoxLayout> hLayout = new QHBoxLayout;

    widgetArea->setLayout(hLayout);

    QPointer<QSplitter> splitter = new QSplitter;
    hLayout->addWidget(splitter);

    QPointer<QTreeView> tree = new QTreeView;
    splitter->addWidget(tree);

    QPointer<QGraphicsView> view = new QGraphicsView;
    splitter->addWidget(view);
    splitter->setStretchFactor(0, 1);
    splitter->setStretchFactor(1, 4);

    QPointer<QGraphicsScene> scene = new QGraphicsScene;
    view->setScene(scene);

    QPointer<Titlebar> blue = new Titlebar;
    blue->setObjectName("blue");
    blue->setStyleSheet(QString("#blue{background-color: rgb(0,0,255)}"));
    blue->resize(250,250);
    scene->addWidget(blue);

    this->setCentralWidget(widgetArea);
    this->resize(1000,750);
}

MainWindow::~MainWindow(){

}

Titlebar.h:

#ifndef TITLEBAR_H
#define TITLEBAR_H

#include <QMenu>
#include <QWidget>
#include <QPointer>
#include <QDebug>
#include <QMouseEvent>

class Titlebar : public QWidget{
    Q_OBJECT
public:
    explicit Titlebar(QWidget *parent = nullptr);
    QPointer<QMenu> menu;
    QPoint currentPos;
protected slots:
    void mousePressEvent(QMouseEvent* event);
    void mouseMoveEvent(QMouseEvent* event);
    void showContextMenu(QPoint point);
};

#endif // TITLEBAR_H

Titlebar.cpp:

#include "titlebar.h"

Titlebar::Titlebar(QWidget *parent) : QWidget(parent){
    setContextMenuPolicy(Qt::CustomContextMenu);
    connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
    menu = new QMenu(this);
    menu->addAction("Test");
}

void Titlebar::showContextMenu(QPoint point){
    qDebug() << point;
    menu->exec(mapToGlobal(point));
}

void Titlebar::mouseMoveEvent(QMouseEvent *event){
    if (event->buttons() && Qt::LeftButton){
        QPoint diff = event->pos() - currentPos;
        move(pos() + diff);
    }
}

void Titlebar::mousePressEvent(QMouseEvent * event){
    currentPos = event->pos();
}

main.cpp:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

所以这会运行并相应地重现错误。如果您将 Titlebar.cpp 中的行从

更改为
menu = new QMenu(this);

至:

menu = new QMenu;

然后就可以正常工作了。只有第一次右键单击打开上下文菜单时,才会在屏幕上的错误位置生成。所有后续的右键单击现在都将跟随 widget/window/splitter 的任意组合。我不明白,谁能告诉我这是否真的是一个错误。

您需要添加一行代码,因为您使用的是作为 QGraphicsScene 一部分的 QGraphicsProxyWidget。场景由继承 QAbstractScrollArea 的 QGraphicsView 表示。这会导致上下文菜单通过视口显示,而不是小部件本身。因此,添加这一行代码会将标题栏覆盖为 not be embedded in the scene when it's parent was already embedded in the scene。有效地使其再次引用小部件而不是视口。

在第 26 行之后的 MainWindow.cpp 中添加

blue->setWindowFlags(Qt::BypassGraphicsProxyWidget);