qt - 移动窗口时如何获取小部件的新坐标?
问题描述
我有一个像这样的小对话框:
当我将对话框移动到桌面上的另一个位置时,如何获取对话框中元素的新全局坐标(例如,在本例中为 Ok 按钮的左上角)?想象一下,我有一个用于确定按钮的子类 MyButton,我想为这个类使用 QEvent,我正在这个类中工作,而不是在 QMainWindow 中工作。
bool MyButton::eventFilter( QObject *p_obj, QEvent *p_event )
{
if ( p_event->type() == QEvent::Move )
{
QPoint point = this->contentsRect().topLeft();
point = mapToGlobal( point );
qDebug() << point;
}
return QWidget::eventFilter( p_obj, p_event );
}
这个函数是错误的,因为按钮与对话框的相对位置永远不会改变,但我不知道如何纠正它以在移动对话框时获得按钮的新全局坐标。我需要不断的新坐标,而不是在我释放鼠标之后。
gridLayout = new QGridLayout(Form);
gridLayout->setObjectName(QStringLiteral("gridLayout"));
horizontalLayout = new QHBoxLayout();
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
lb_username = new QLabel(Form);
lb_username->setObjectName(QStringLiteral("lb_username"));
horizontalLayout->addWidget(lb_username);
le_username = new QLineEdit(Form);
le_username->setObjectName(QStringLiteral("le_username"));
horizontalLayout->addWidget(le_username);
gridLayout->addLayout(horizontalLayout, 0, 0, 1, 1);
horizontalLayout_2 = new QHBoxLayout();
horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2"));
lb_password = new QLabel(Form);
lb_password->setObjectName(QStringLiteral("lb_password"));
horizontalLayout_2->addWidget(lb_password);
le_password = new QLineEdit(Form);
le_password->setObjectName(QStringLiteral("le_password"));
horizontalLayout_2->addWidget(le_password);
gridLayout->addLayout(horizontalLayout_2, 1, 0, 1, 1);
horizontalLayout_3 = new QHBoxLayout();
horizontalLayout_3->setObjectName(QStringLiteral("horizontalLayout_3"));
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_3->addItem(horizontalSpacer);
btn_ok = new MyButton();
btn_ok->setObjectName(QStringLiteral("btn_ok"));
horizontalLayout_3->addWidget(btn_ok);
btn_cancel = new MyButton();
btn_cancel->setObjectName(QStringLiteral("btn_cancel"));
horizontalLayout_3->addWidget(btn_cancel);
gridLayout->addLayout(horizontalLayout_3, 2, 0, 1, 1);
verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout->addItem(verticalSpacer, 3, 0, 1, 1);
解决方案
Qt 文档。对事件过滤器有一个很好的介绍:事件系统 - 事件过滤器,包括一个小样本。
我在 OP 问题中遗漏了两件重要的事情:
- 是如何
QDialog
构建的? - 事件过滤器 (in
MyButton
) 安装在哪里?
此外,OP似乎不知道QWidget::mapToGlobal()
:
将小部件坐标 pos 转换为全局屏幕坐标。例如,
mapToGlobal(QPoint(0,0))
将给出小部件左上角像素的全局坐标。
关于mapToGlobal
,SO 中已经至少有一个其他 Q/A:
但是,我制作了一个MCVE来演示一个解决方案—— testQButtonGlobalPos.cc
:
#include <QtWidgets>
class WidgetPosFilter: public QObject {
private:
QWidget &qWidget;
public:
WidgetPosFilter(
QWidget &qWidget, QObject *pQParent = nullptr):
QObject(pQParent), qWidget(qWidget)
{ }
virtual ~WidgetPosFilter() = default;
WidgetPosFilter(const WidgetPosFilter&) = delete;
WidgetPosFilter& operator=(const WidgetPosFilter&) = delete;
protected:
virtual bool eventFilter(QObject *pQbj, QEvent *pQEvent) override;
};
bool WidgetPosFilter::eventFilter(
QObject *pQObj, QEvent *pQEvent)
{
if (pQEvent->type() == QEvent::Move) {
qDebug() << "QWidget Pos.:"
<< "local:" << qWidget.pos()
<< "global:" << qWidget.mapToGlobal(QPoint(0, 0));
}
return QObject::eventFilter(pQObj, pQEvent);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup UI of main window
QPushButton qBtnOpenDlg(
QString::fromUtf8("Open new dialog..."));
qBtnOpenDlg.show();
// setup UI of dialog
QDialog qDlg(&qBtnOpenDlg);
QVBoxLayout qVBox;
QDialogButtonBox qDlgBtns;
QPushButton qBtn(QString::fromUtf8("The Button"));
qDlgBtns.addButton(&qBtn, QDialogButtonBox::AcceptRole);
qVBox.addWidget(&qDlgBtns);
qDlg.setLayout(&qVBox);
WidgetPosFilter qBtnPosFilter(qBtn);
// install signal handlers
QObject::connect(&qBtnOpenDlg, &MyButton::clicked,
[&](bool) { qDlg.show(); });
qDlg.installEventFilter(&qBtnPosFilter);
// runtime loop
return app.exec();
}
要构建的 Qt 项目 – testQButtonGlobalPos.pro
:
SOURCES = testQButtonGlobalPos.cc
QT += widgets
在 Windows 10 上用cygwin64编译和测试:
$ qmake-qt5 testQButtonGlobalPos.pro
$ make && ./testQButtonGlobalPos
Qt Version: 5.9.4
QWidget Pos.: local: QPoint(0,0) global: QPoint(11,11)
QWidget Pos.: local: QPoint(83,0) global: QPoint(2690,68)
QWidget Pos.: local: QPoint(83,0) global: QPoint(98,45)
QWidget Pos.: local: QPoint(83,0) global: QPoint(2658,42)
QWidget Pos.: local: QPoint(83,0) global: QPoint(5218,46)
QWidget Pos.: local: QPoint(83,0) global: QPoint(3097,219)
QWidget Pos.: local: QPoint(83,0) global: QPoint(2251,197)
在我将对话框移动到新位置之后,以开头的每一行QWidget Pos.:
(前两行除外)都出现了。当我打开对话框时,前两行被打印出来。因此,第一个似乎反映了对话框尚未放置在桌面上时的中间状态。
笔记:
事件过滤器的基本原理由一个对象组成,该对象以其虚拟/覆盖
eventFilter()
方法处理过滤后的事件。为此,对象必须具有派生自 的类QObject
。OP的方法,有一个class MyButton: public QPushButton
就足够了。然而,实际上,任何派生自的类QObject
也可以做到这一点(如我的示例所示)。要使事件过滤器对象工作,重要的是安装它以调用
QObject::installEventFilter()
要监视的对象。就我而言,这是QDialog qDlg
quest 中的按钮所属的按钮。出于好奇,我尝试了另一种选择:覆盖
QWidget::moveEvent()
in aclass MyButton: public QPushButton
。这没有提供 OP/I 的意图。MyButton::moveEvent()
打开时调用了一次QDialog qDlg
。用鼠标移动对话框没有再次调用它。似乎移动事件被接收QDialog
但没有进一步传播到子小部件。这是合理的,因为移动整个对话窗口不会改变其内部布局。因此,OP 为此使用事件过滤器的方法是正确的。
推荐阅读
- vba - 如何在不覆盖 VBA 中的行的情况下将新行添加到另一个工作表?
- java - 如何在没有 setter 或公开变量的情况下将私有类实例变量设置为 unmarshaller 使用的 File 对象?
- java - 使用 DataFlow (Apache Beam) 将 ISO-8859-1 加载到 BigQuery 时出现问题
- c++ - c++中opencv puttext函数的问题
- json - Flutter:如何映射字典?通过字典制作下拉选项
- json - 使用 Moshi 处理可能是列表或自定义对象的 JSON 响应
- javascript - 点击时如何从表单中获取数据?
- amazon-web-services - api 网关与 aws 批处理集成
- visual-studio - 签入策略 Visual Studio - 如果在定义的模板中,则检查变更集注释
- java - 启动 Tomcat 时 JavaMelody 抛出 NullPointerException