【问题标题】:Changing dialog's parent disables drag and drop更改对话框的父级禁用拖放
【发布时间】:2018-08-28 15:54:52
【问题描述】:

我有一个主窗口和一个对话框,通过单击按钮从该窗口打开。出于性能原因,有一个对话框缓存,它保留对话框的一个实例,并且只在应该打开对话框而不是创建新实例时显示它。在对话框中,有一个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();
}

【问题讨论】:

  • @downvoter:我真的很感兴趣你认为这个问题有什么问题......
  • 一个问题是代码可以是
  • @KubaOber:好的,我可以这样做,我考虑过,但我不明白这有什么帮助。问题不在 ui 文件中,它只会使帖子更短一些。但对我来说,它的可读性也会降低,就像 Qt 一样,你习惯于 ui 文件,而不是生成 gui 的 C++ 代码......它只会添加可能存在错误的代码,但使用 ui,你知道那里没有什么奇怪的事情发生......如果你想重现它,无论如何你都需要一个带有Qt的环境,所以它没有任何区别:)
  • “习惯于 UI 文件”?人们提出 JSON 是有原因的:XML 对人类不友好。怎么能说更多样板在某种程度上更具可读性?谁读过 UI 文件?它们适用于uic 和 Qt Designer,而不是人类——否则它们将是 JSON、YAML 或 QML,而不是 XML。没关系,您绝对不需要这么多样板文件。再次:最小化。这不是企业应用程序。这是一个问题。少即是多。而且它确实有所作为,因为你无缘无故地强迫任何回答基本上复制粘贴 5 个单独文件的人。
  • @KubaOber:好的,现在我明白你的意思了。我不知道你可以简单地显示一个小部件而不创建任何类型的 QWindow 实例......这使得 mcve 确实简单得多。

标签: c++ qt drag-and-drop qt5


【解决方案1】:

下面重现了这个问题。这确实是一个 Qt 错误。 OP 报错:https://bugreports.qt.io/browse/QTBUG-70240

问题是因为QWidgetQt::Window 标志关闭时重新创建了放置站点,调用了QWindowsWindow::updateDropSite,它做错了事情并调用了setDropSiteEnabled(false)

两种等效的解决方法是:

  1. dialog-&gt;setParent(newParent) 被替换为:

    auto flags = dialog->windowFlags();
    dialog->setParent(newParent, {});
    dialog->setWindowFlags(flags);
    
  2. dialog-&gt;setParent(nullptr) 被替换为:

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

第一个解决方法是撤消小部件的损坏状态。第二种解决方法不需要,即需要始终使用,否则必须调用第一种解决方法才能恢复可用的放置目标状态。

// 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();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-28
    • 2011-09-15
    • 2015-12-04
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多