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
上执行第一次右键单击事件时,如果我将整个 QApplication
的 MainWindow
拖动到屏幕上起始位置以外的任何位置,它将不会在正确的屏幕位置执行。除了在 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
。有没有人知道第一次调用 execing
和 QMenu
的 Titlebar'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);
关于 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
上执行第一次右键单击事件时,如果我将整个 QApplication
的 MainWindow
拖动到屏幕上起始位置以外的任何位置,它将不会在正确的屏幕位置执行。除了在 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
。有没有人知道第一次调用 execing
和 QMenu
的 Titlebar'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);