Изменение родителя диалога отключает перетаскивание - PullRequest
0 голосов
/ 28 августа 2018

У меня есть главное окно и диалог, который открывается из этого окна одним нажатием кнопки. Из соображений производительности имеется диалоговый кеш, который хранит экземпляр диалогового окна и показывает его только тогда, когда диалоговое окно должно быть открыто вместо создания нового экземпляра. В диалоговом окне есть QListWidget с некоторыми элементами, порядок которых можно изменить путем перетаскивания. Это работает, когда я впервые открываю диалоговое окно, но когда я закрываю его и открываю снова, я не могу бросить предметы, я получаю Qt::ForbiddenCursor.

Проблема, кажется, вызвана вызовом setParent(nullptr) при закрытии диалога (или, вероятно, простым изменением родителя). Если я удаляю эту строку, перетаскивание работает. Однако мне нужно это, чтобы предотвратить удаление родительского диалогового окна, а также диалоговое окно может иметь разных родителей в разных контекстах (это не очевидно из моего упрощенного примера). Есть идеи, что не так с этим подходом? Моя версия Qt 5.9.3. Может ли это быть ошибкой Qt?

mainwindow.h:

#include "ui_mainwindow.h"
#include "dialog.h"

#include <QPushButton>
#include <QMainWindow>
#include <memory>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget* parent = nullptr) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);

        dialog.reset(new Dialog(this));
        dialog->setAttribute(Qt::WA_DeleteOnClose, false);

        connect(ui->button, &QPushButton::pressed, [&]
        {
            dialog->setParent(this, dialog->windowFlags());
            dialog->open();
        });
    }

    ~MainWindow()
    {
        delete ui;
    }

private:
    Ui::MainWindow* ui;
    std::unique_ptr<Dialog> dialog;
};

dialog.h:

#include "ui_dialog.h"
#include <QDialog>

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget* parent) : QDialog(parent), ui(new Ui::Dialog)
    {
        ui->setupUi(this);

        ui->listWidget->addItem("first");
        ui->listWidget->addItem("second");
        ui->listWidget->addItem("third");
    }

    ~Dialog()
    {
        delete ui;
    }

public slots:
    virtual void reject() override
    {
        setParent(nullptr);
        QDialog::reject();
    }

private:
    Ui::Dialog* ui;
};

Dialog.ui - простой диалог с QListWidget и кнопкой отклонения

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>548</width>
    <height>397</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QListWidget" name="listWidget">
     <property name="dragDropMode">
      <enum>QAbstractItemView::DragDrop</enum>
     </property>
     <property name="defaultDropAction">
      <enum>Qt::MoveAction</enum>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QDialogButtonBox" name="buttonBox">
     <property name="orientation">
      <enum>Qt::Horizontal</enum>
     </property>
     <property name="standardButtons">
      <set>QDialogButtonBox::Close</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>Dialog</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>248</x>
     <y>254</y>
    </hint>
    <hint type="destinationlabel">
     <x>157</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>Dialog</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>316</x>
     <y>260</y>
    </hint>
    <hint type="destinationlabel">
     <x>286</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

MainWindow.ui - главное окно по умолчанию с одной кнопкой

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>432</width>
    <height>316</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QPushButton" name="button">
    <property name="geometry">
     <rect>
      <x>40</x>
      <y>30</y>
      <width>80</width>
      <height>21</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>432</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

1 Ответ

0 голосов
/ 29 августа 2018

Ниже воспроизводится вопрос. Это действительно ошибка Qt. ОП сообщил об ошибке: https://bugreports.qt.io/browse/QTBUG-70240

Проблема в том, что QWidget воссоздает удаленный сайт, когда флаг Qt::Window выключен, вызывая QWindowsWindow::updateDropSite, что делает не так и вызывает setDropSiteEnabled(false).

Два эквивалентных обходных пути:

  1. dialog->setParent(newParent) заменяется на:

    auto flags = dialog->windowFlags();
    dialog->setParent(newParent, {});
    dialog->setWindowFlags(flags);
    
  2. dialog->setParent(nullptr) заменяется на:

    dialog->setParent(nullptr, dialog->windowFlags());
    

Первый обходной путь отменяет поврежденное состояние виджета. Второй обходной путь не требуется, т. Е. Должен использоваться всегда, иначе первый обходной путь должен быть вызван один раз для восстановления пригодного целевого состояния удаления.

screenshot

// https://github.com/KubaO/stackoverflown/tree/master/questions/dialog-parent-dnd-52061919
#include <QtWidgets>

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);

   QWidget ui;
   QVBoxLayout layout{&ui};
   QPushButton button{"Toggle List"};
   QCheckBox workaround1{"Workaround 1"};
   QCheckBox workaround2{"Workaround 2"};
   for (auto w : QWidgetList{&button, &workaround1, &workaround2}) layout.addWidget(w);
   workaround2.setChecked(true);

   QListWidget listWidget;
   Q_ASSERT(!listWidget.testAttribute(Qt::WA_DeleteOnClose));
   listWidget.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
   listWidget.setDragDropMode(QAbstractItemView::DragDrop);
   listWidget.setDefaultDropAction(Qt::MoveAction);
   for (auto s : QStringList{"first", "second", "third"}) listWidget.addItem(s);

   QObject::connect(&button, &QPushButton::pressed, [&] {
      if (!listWidget.parent()) {
         if (!workaround1.isChecked())
            listWidget.setParent(&button, listWidget.windowFlags());
         else {
            auto flags = listWidget.windowFlags();
            listWidget.setParent(&button, {});
            listWidget.setWindowFlags(flags);
         }
         listWidget.show();
      } else {
         if (!workaround2.isChecked())
            listWidget.setParent(nullptr);
         else
            listWidget.setParent(nullptr, listWidget.windowFlags());
         listWidget.close();
      }
   });

   ui.setMinimumSize(320, 200);
   ui.show();
   return app.exec();
}
...