当窗口移动时,如何获取小部件的新坐标?

时间:2018-10-22 15:36:00

标签: qt qevent

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

enter image description here

将对话框移到桌面上的另一个位置时,如何获取对话框中元素的新全局坐标(例如,在这种情况下,单击确定按钮的左上角)? 想象一下,我有一个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);

1 个答案:

答案 0 :(得分:2)

Qt文档很好地介绍了事件过滤器:The Event System – Event Filters,其中包括一个小样本。

在OP问题中,我缺少了两个基本要点:

  1. QDialog的结构如何?
  2. MyButton中的事件过滤器安装在哪里?

此外,OP似乎不了解QWidget::mapToGlobal()

  

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

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

SO: Qt - Determine absolute widget and cursor position

但是,我做了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.:开头的每一行(前两行除外)出现在 之后。我将对话框移到了新位置。当我打开对话框时,打印的是前两行。因此,当对话框尚未放置在桌面上时,第一个似乎反映了一种中间状态。

Snapshot of testQButtonGlobalPos

注释:

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

  2. 要使事件过滤器对象正常工作,重要的是安装它以调用QObject::installEventFilter()进行观看。就我而言,这是任务中的按钮所属的QDialog qDlg

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