Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: DarkHobbit от Июль 10, 2018, 23:42



Название: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Июль 10, 2018, 23:42
Добрый вечер.
Во всех примерах по перетаскиванию в Qt, которые я видел, создаётся собственный виджет и в нём перекрывается mousePressEvent.
Допустим, у меня есть главное окно (наследник QMainWindow), в нём QTableView и другие виджеты. Я хочу, чтобы у меня работал drag'n'drop из QTableView в другие виджеты. Мне при этом обязательно отказываться от стандартного QTableView, создавать его потомка и там соответственно перекрывать mousePressEvent? Или это можно как-то разрулить в MainWindow?
Создавать потомка QTableView не хочется, поскольку окно создано в дизайнере, не хочется руками тащить в .ui нестандартные объекты.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Igors от Июль 11, 2018, 04:31
Я хочу, чтобы у меня работал drag'n'drop из QTableView в другие виджеты. Мне при этом обязательно отказываться от стандартного QTableView, создавать его потомка и там соответственно перекрывать mousePressEvent?
Необязательно, можно фильтр навесить (installEventFilter).  И если перетаскивание чисто "местное", то лучше на фильтре все и сделать, не используя DnD.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Июль 11, 2018, 11:08
1Необязательно, можно фильтр навесить (installEventFilter).
Спасибо за наводку, копну в эту сторону. Я installEventFilter раньше использовал, но только для обработки событий клавиатуры.
Цитировать
И если перетаскивание чисто "местное", то лучше на фильтре все и сделать, не используя DnD.
Да, местное, с одного QTableView на другой.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: GreatSnake от Июль 11, 2018, 11:41
Using Drag and Drop with Item Views (http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views)

И если перетаскивание чисто "местное", то лучше на фильтре все и сделать, не используя DnD.
Вот зачем вы советуете человеку, явно не искушенному в этих вопросах, заниматься сексом?


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Igors от Июль 11, 2018, 12:02
Вот зачем вы советуете человеку, явно не искушенному в этих вопросах, заниматься сексом?
По-моему так проще чем ковыряться в событиях DbD


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: GreatSnake от Июль 11, 2018, 12:06
Т.е. самому городить мышиные грабы, установку курсора, иконки и чтобы всё было портабельно - это проще?


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: zhbr от Июль 12, 2018, 06:56
имхо проще делать стандартными средствами DnD Qt. Не так давно как раз делал это с QTableView - помогли следующие доки: http://doc.qt.io/qt-5/dnd.html и http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
правда надо иметь ввиду один баг (https://bugreports.qt.io/browse/QTBUG-67155), присутствующий во всех версиях.
если док не достаточно, могу попробовать сделать выжимку


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Igors от Июль 13, 2018, 06:57
Т.е. самому городить мышиные грабы, установку курсора, иконки и чтобы всё было портабельно - это проще?
Да, и намного. И причем тут портабельность - ну фильтруем 3 события мыши вместо 1, как она может пострадать? 


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Racheengel от Июль 13, 2018, 10:32
Велосипед... Но зачем?


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Июль 13, 2018, 11:47
Using Drag and Drop with Item Views (http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views)
Спасибо за наводку, я даже не предположил, что подобные вопросы могут на уровне модели решаться (и вроде как-то это не в духе Model-View вообще...).
Буду пробовать.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Сентябрь 13, 2018, 18:40
GreatSnake: ещё раз спасибо.

Прочитал эту доку, сделал решение через mimeData/dropMimeData. Из одного table view в другой данные внутри одной программы таскаются на ура.
Сначала сделал, как и в примерах, через QDataStream.

Потом, поскольку формат у меня, в общем-то, текстовый (известен как text/vcard), решил попробовать QTextStream. Тоже заработало. Только в конце записи строк в поток обязательно надо вызвать flush(), иначе итоговый QByteArray получится пустым. Да, я знаю о наличии QMimeData::setText(), но он, к сожалению, даёт записанным данным фиксированный тип text/plain, а это не совсем мой случай.

И ещё остался вопрос, что мне делать в случае Qt::MoveAction для удаления записи в источнике. Делать специальный сигнал, который отсылается из dropMimeData, или есть стандартный механизм? Вообще, в доке есть такая фраза:
Цитировать
Although any combination of values from Qt::DropActions can be given, the model needs to be written to support them. For example, to allow Qt::MoveAction to be used properly with a list model, the model must provide an implementation of QAbstractItemModel::removeRows(), either directly or by inheriting the implementation from its base class.
Я правильно понял, что мне надо перекрыть removeRows(), и он при необходимости вызовется? У меня, правда, не list, а table...


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Сентябрь 28, 2018, 15:07
Я правильно понял, что мне надо перекрыть removeRows(), и он при необходимости вызовется?
К сожалению, на этом вопросе так и застрял. Вроде бы в кутешном примере puzzle removeRows() вызывается автоматом. У меня - нет.

Набросал простейший пример (да, код небрежный) с list model. В главном окне два list view. Класс DraggedModel - наследник QAbstractListModel, выводит строки из списка ss (QStringList).
Фрагмент конструктора главного окна:
Код
C++ (Qt)
   DraggedModel* m1 = new DraggedModel(QStringList() << "One" << "Two" << "Three");
   ui->listView->setModel(m1);
   DraggedModel* m2 = new DraggedModel();
   ui->listView_2->setModel(m2);
 
   ui->listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
   ui->listView->setDragEnabled(true);
   ui->listView->setAcceptDrops(true);
   ui->listView->setDropIndicatorShown(true);
   ui->listView_2->setSelectionMode(QAbstractItemView::ExtendedSelection);
   ui->listView_2->setDragEnabled(true);
   ui->listView_2->setAcceptDrops(true);
   ui->listView_2->setDropIndicatorShown(true);
 

Методы модели:
Код
C++ (Qt)
DraggedModel::DraggedModel(const QStringList &strings)
   :ss(strings)
{
}
 
int DraggedModel::rowCount(const QModelIndex &) const
{
   return ss.count();
}
 
QVariant DraggedModel::headerData(int section, Qt::Orientation orientation, int role) const
{
   if ((role == Qt::DisplayRole) && (orientation==Qt::Horizontal))
       return "Burkhumm";
   else
       return QAbstractItemModel::headerData(section, orientation, role);
}
 
QVariant DraggedModel::data(const QModelIndex &index, int role) const
{
   if (!index.isValid())
         return QVariant();
     if (index.row() >= ss.count())
         return QVariant();
   if (role==Qt::DisplayRole)
       return ss[index.row()];
   else return QVariant();
}
 
Qt::DropActions DraggedModel::supportedDropActions() const
{
   return Qt::CopyAction | Qt::MoveAction;
}
 
Qt::ItemFlags DraggedModel::flags(const QModelIndex &index) const
{
   Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}
 
QStringList DraggedModel::mimeTypes() const
{
   return QStringList() << "text/plain";
}
 
QMimeData *DraggedModel::mimeData(const QModelIndexList &indexes) const
{
   QMimeData *mimeData = new QMimeData();
   if (!indexes.isEmpty())
       mimeData->setText(ss[indexes[0].row()]);
   return mimeData;
}
 
bool DraggedModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
   if (action == Qt::IgnoreAction)
        return true;
    if (!data->hasFormat("text/plain"))
        return false;
    if (column > 0)
        return false;
    beginResetModel();
    ss << data->text();
    endResetModel();
    return true;
}
#include <iostream>
bool DraggedModel::removeRows(int row, int count, const QModelIndex &parent)
{
   std::cout << "DraggedModel::removeRows" << std:: endl;
   beginResetModel();
   ss.removeAt(row);
   endResetModel();
   return true;
}
 
Запускаю - removeRows() не вызывается. Чего ему может не хватать? Копирование работает, нареканий нет.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Hellraiser от Сентябрь 28, 2018, 15:28
При переопределении removeRows внутри метода надо вызывать пару beginRemoveRows/endRemoveRows, а не beginResetModel/endResetModel.


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Октябрь 01, 2018, 01:09
Э... Согласен, здесь погорячился, исправлю.
Но главную проблему это не решает. Сам-то removeRows() не вызывается, и тут уже не так важно, что я внутри него написал...


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: Авварон от Октябрь 01, 2018, 03:41
http://doc.qt.io/qt-5/qabstractitemview.html#DragDropMode-enum ?


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Октябрь 01, 2018, 12:18
Авварон, dragDropMode не особо помог. Зато рядышком у view есть свойство dragDropOverwriteMode - так вот, для list view, если его выставить в false, removeRows() начинает отрабатывать! Т.е. для списка работают и Copy, и Move.

Теперь делаю аналогичный пример для table view. Всё отличие в том, что виджеты не QListView, а QTableView, и у модели в дополнение к rowCount() переопределил ещё и columnCount(). Начинаю таскать. CopyAction по-прежнему работает, а с MoveAction проблема та же, что и раньше, т.е. removeRows() не вызывается.

Уже начинаю перечитывать доку и придираться к словам:
Цитировать
For example, to allow Qt::MoveAction to be used properly with a list model, the model must provide an implementation of QAbstractItemModel::removeRows(), either directly or by inheriting the implementation from its base class.

Может, этот приём только с list model и работает? А как же тогда с table view разруливать? Руками посылать сигнал в модель виджета-источника, чтобы она сама удаляла нужную строку? Да нет, чепуха какая-то, чем таблицы хуже списков...


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Октябрь 02, 2018, 16:45
Нашёл :)
В дополнение к dragDropOverwriteMode==false, для таблиц должно выполняться одно из двух условий:
  • количество столбцов равно 1;
  • selectionBehaviour у view выставлен в SelectRows.
Если хотя бы одно из условий выполняется, removeRows() вызывается корректно. Если ни одно не выполняется - не вызывается.
В принципе, всё оказалось просто и логично. :) Если мы выделяем отдельные ячейки, тогда непонятно, что удалять. Но пока разбирался, чуть не поседел...


Название: Re: Drag'n'drop из дочернего виджета
Отправлено: DarkHobbit от Декабрь 22, 2018, 16:30
Впрочем, совет Igors навесить фильтр тоже не пропал втуне. Хотя применять его буквально действительно не стоило.

Дело в том, что кроме "местного" драг-н-дропа строк между табличками в моей программе есть ещё и "внешний" - из файлового менеджера можно перетащить имя на панельку (в моём случае, на QTableView), и программа его открывает.

Панелек у меня одна или две. И в предыдущей реализации я перекрыл dropEvent() у главного окна... и долго ломал голову, как правильно определять, на какую именно панельку кинут URL. Сделал через определение координат события и сравнение с координатами дочерних виджетов. Способ очевидно некрасивый, и мог поломаться, если, например, мне захочется вместо горизонтального расположения панелек сделать вертикальное.

А после этого обсуждения я переписал код. Установил фильтр на события для обоих QTableView, и в фильтре просто смотрю, кто получатель. Получилось намного изящнее. :)