Как получить новую координату виджета при перемещении окна?

У меня есть маленький диалог, как это:

Когда я перемещаю диалоговое окно в другое место на рабочем столе, как я могу получить новую глобальную координату элемента в диалоговом окне (например, в этом случае верхняя левая точка кнопки Ok)? Представьте, что у меня есть подкласс MyButton для кнопки OK, и я хочу использовать 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 ответ

Qt Doc. есть хорошее введение в фильтры событий: Система событий - Фильтры событий, включая небольшой пример.

Я упускаю две важные вещи в вопросе ОП:

  1. Как QDialog построен?
  2. Где находится фильтр событий (в MyButton) установлены?

Кроме того, ОП, кажется, не знает о 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

Скомпилировано и протестировано в cygwin64 на Windows 10:

$ 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.: (кроме первых двух) появился после того, как я переместил диалог на новую позицию. Первые две строки были напечатаны вместо, когда я открыл диалог. Итак, первое, кажется, отражает промежуточное состояние, когда диалог еще не размещен на рабочем столе.

Снимок теста QButtonGlobalPos

Заметки:

  1. Основной принцип фильтров событий состоит из объекта, который обрабатывает отфильтрованные события в своем виртуальном / переопределенном eventFilter() метод. Для этого у объекта должен быть класс, производный от QObject, Подход ОП, имеющий class MyButton: public QPushButton было бы достаточно. Однако, на самом деле, любой класс, производный от QObject мог бы сделать это также (как продемонстрировано в моем примере).

  2. Чтобы заставить объект фильтра событий работать, важно установить его, вызывая QObject::installEventFilter() для объекта для просмотра. В моем случае это был QDialog qDlg к которой принадлежит кнопка в квесте.

  3. Из любопытства я попробовал альтернативу: переопределить QWidget::moveEvent() в class MyButton: public QPushButton, Это не обеспечило то, что OP/I намеревался. MyButton::moveEvent() был вызван один раз, когда QDialog qDlg был открыт. Перемещение диалога мышью не вызывало его снова. Похоже, что ход событий получает QDialog но не распространяется далее на дочерние виджеты. Это настолько разумно, что перемещение всего диалогового окна не меняет его внутреннюю планировку. Следовательно, подход OP, использующий фильтр событий для этого, был правильным путем.

Другие вопросы по тегам