首页 > 解决方案 > 移动窗口时如何获取小部件的新坐标?

问题描述

我有一个像这样的小对话框:

在此处输入图像描述

当我将对话框移动到桌面上的另一个位置时,如何获取对话框中元素的新全局坐标(例如,在本例中为 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);

标签: qtqevent

解决方案


Qt 文档。对事件过滤器有一个很好的介绍:事件系统 - 事件过滤器,包括一个小样本。

我在 OP 问题中遗漏了两件重要的事情:

  1. 是如何QDialog构建的?
  2. 事件过滤器 (in MyButton) 安装在哪里?

此外,OP似乎不知道QWidget::mapToGlobal()

将小部件坐标 pos 转换为全局屏幕坐标。例如,mapToGlobal(QPoint(0,0))将给出小部件左上角像素的全局坐标。

关于mapToGlobal,SO 中已经至少有一个其他 Q/A:

SO:Qt - 确定绝对小部件和光标位置

但是,我制作了一个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.:(前两行除外)都出现了。当我打开对话框时,前两行被打印出来。因此,第一个似乎反映了对话框尚未放置在桌面上时的中间状态。

testQButtonGlobalPos 的快照

笔记:

  1. 事件过滤器的基本原理由一个对象组成,该对象以其虚拟/覆盖eventFilter()方法处理过滤后的事件。为此,对象必须具有派生自 的类QObject。OP的方法,有一个class MyButton: public QPushButton就足够了。然而,实际上,任何派生自的类QObject也可以做到这一点(如我的示例所示)。

  2. 要使事件过滤器对象工作,重要的是安装它以调用QObject::installEventFilter()要监视的对象。就我而言,这是QDialog qDlgquest 中的按钮所属的按钮。

  3. 出于好奇,我尝试了另一种选择:覆盖QWidget::moveEvent()in a class MyButton: public QPushButton。这没有提供 OP/I 的意图。MyButton::moveEvent()打开时调用了一次QDialog qDlg。用鼠标移动对话框没有再次调用它。似乎移动事件被接收QDialog但没有进一步传播到子小部件。这是合理的,因为移动整个对话窗口不会改变其内部布局。因此,OP 为此使用事件过滤器的方法是正确的。


推荐阅读