Внешний сброс QListView не работает

Я пытаюсь реализовать элементы перетаскивания (обычные/текстовые) из одного QListView в другой. Перетаскивание начинается хорошо (я даже могу перетаскивать элементы в другие приложения, которые принимают перетаскивание текста), но мой второй QListView по какой-то причине не принимает перетаскивания. Вот как настроено представление списка:

ui->lessonsListView->setAcceptDrops(true);
ui->lessonsListView->setDropIndicatorShown(true);
ui->lessonsListView->setDragDropMode(QAbstractItemView::DropOnly);
ui->lessonsListView->setDragDropOverwriteMode(true);

Прокси-модель для этого listView реализует следующие методы:

Qt::ItemFlags LessonsProxyModel::flags(const QModelIndex &index) const
{
    qDebug() << __FUNCTION__;
    return Qt::ItemIsDropEnabled | QSortFilterProxyModel::flags(index);
}

Qt::DropActions LessonsProxyModel::supportedDropActions() const
{
    qDebug() << __FUNCTION__;
    return Qt::MoveAction;
}

bool LessonsProxyModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    qDebug() << __FUNCTION__;
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(column);

    if (!data->hasFormat("text/plain") || !parent.isValid())
        return false;

    return true;
}

bool LessonsProxyModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    qDebug() << __FUNCTION__;
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    emit dataDropped(data, parent);

    return true;
}

Из вывода приложения я вижу, что вызываются только supportedDropActions() и flags(). Ни canDropMimeData(), ни dropMimeData() никогда не звонили. Что я делаю неправильно? Любые подсказки будут оценены.

Спасибо!

ОТРЕДАКТИРОВАНО:

На всякий случай: ниже исходный код для listView и модели из тех, которые инициируются перетаскиванием: настройка listView:

ui->abonsListView->setDragEnabled(true);

код модели прокси:

Qt::ItemFlags AbonsProxyModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsDragEnabled | QSortFilterProxyModel::flags(index);
}

Qt::DropActions AbonsProxyModel::supportedDragActions() const
{
    qDebug() << __FUNCTION__;
    return Qt::MoveAction;
}

QStringList AbonsProxyModel::mimeTypes() const
{
    qDebug() << __FUNCTION__;
    QStringList types;
    types << "text/plain";
    return types;
}

QMimeData *AbonsProxyModel::mimeData(const QModelIndexList &indexes) const
{
    qDebug() << __FUNCTION__;
    QMimeData *mimeData = new QMimeData();

    foreach (const QModelIndex &index, indexes)
        if (index.isValid())
        {
            mimeData->setText(data(index, AbonsModel::Id).toString());
            qDebug() << __FUNCTION__;
            return mimeData;
        }

    return mimeData;
}

person Sergey Stasishin    schedule 28.03.2016    source источник
comment
Я считаю, что вы должны переопределить dragEnterEvent, dragMoveEvent и dropEvent подкласса QListView. Ознакомьтесь с документацией и примеры   -  person Marcus    schedule 29.03.2016
comment
Спасибо! Я постараюсь. Но в приведенной выше документации говорится: This document describes the basic drag and drop mechanism and outlines the approach used to enable it in custom controls. И я просто следовал инструкциям по ссылке Using drag and drop with item views. Также перетаскивание идеально подходит для другого представления списка без каких-либо подклассов: представления элементов уже поддерживают перетаскивание.   -  person Sergey Stasishin    schedule 29.03.2016
comment
Да, перетаскивание реализовано отлично. Однако падение не работает, как ожидалось. Я попытался провести простой эксперимент с перетаскиванием QListView в сочетании с QFileSystemModel и файловым браузером (дельфин). Перетаскивание с QListView на дельфин работает изумительно, а наоборот не принято. Поэтому я считаю, что вам придется настроить dragEnterEvent, dragMoveEvent и dropEvent для виджета, в котором вы собираетесь выполнить сброс.   -  person Marcus    schedule 29.03.2016
comment
Drop to QListView работает нормально, вам не нужны подклассы. Покажи код, где ты настраиваешь QListView, чтобы запустилось перетаскивание.   -  person Meefte    schedule 29.03.2016
comment
Я разместил код перетаскивания. Кстати, копаясь в исходниках Qt, я обнаружил, что такие функции, как canDropMimeData(), не объявлены virtual в QAbstractProxyModel, и из-за этого неправильная функция называется: QAbstractProxyModel::canDropMimeData() вместо canDropMimeData() моей модели. Вроде в этом причина, но как исправить или обойти не знаю... Вроде работает только с QAbstractItemModel.   -  person Sergey Stasishin    schedule 29.03.2016
comment
Кстати, моя версия Qt - 5.5.1. В документации для QAbstractProxyModel написано: virtual bool canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const но в qabstractproxymodel.h я вижу bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const Q_DECL_OVERRIDE;. Я что-то пропустил?   -  person Sergey Stasishin    schedule 29.03.2016
comment
Я считаю, что путаница из-за Q_DECL_OVERRIDE. Это C++11 специальное предложение. C++11 представил макрос препроцессора. В основном, чтобы проверить, действительно ли вы переопределяете участника или нет. Например, если вы хотите переопределить void paintEvent( QPaintEvent* ) и в итоге набираете void painterEvent( QPaintEvent ), ваша функция никогда не будет вызвана. Если вы добавите void painterEvent( QPaintEvent ) Q_DECL_OVERRIDE, будет выполнена проверка времени компиляции, и компилятор сообщит вам, что не определена такая функция, которую вы пытаетесь переопределить, тем самым экономя массу головной боли и много времени.   -  person Marcus    schedule 29.03.2016


Ответы (2)


Наконец-то я нашел ответ! Когда я начал писать этот код, я скопировал некоторые фрагменты из документации Qt Использование перетаскивания с представлениями элементов, и в этой статье они просто пропустили спецификатор const для повторной реализации canDropMimeData(). Следовательно, моя версия canDropMimeData() стала невиртуальной, и QListView просто вызвал метод из базового класса QAbstractProxyModel. Я добавил const - и все работает как шарм без каких-либо подклассов.

person Sergey Stasishin    schedule 30.03.2016

У меня нет большого опыта работы с прокси-моделями. Просматривая документы, я думаю, что имею приблизительное представление об этих двух функциях. Эти функции не вызываются автоматически. Вы должны «вызывать» их из своего представления.

Во-первых, проверка правильности данных — это работа модели. Так что это две удобные функции в модели, чтобы облегчить вашу работу. Это функции, специфичные для модели, а не общие функции, и поэтому они никогда не определяются для абстрактной модели.

Рассмотрим этот сценарий. Откуда-то в поле зрения появляется QMimeData объект. Как узнать, являются ли эти данные правильным типом? Чья работа заключается в том, чтобы решить, имеют ли данные правильный тип?

В рамках Model-View View выполняет только рисование. Model решает, как обрабатывать данные, что показывать, что приемлемо, а что нет. Поэтому, когда часть данных сбрасывается на View, вам нужно отправить ее на Model, чтобы проверить, удовлетворяют ли сброшенные данные требованию Model. Крючок для выполнения этой работы — bool Model::canDropMimeData(...).

Если это приемлемо, то обработка данных также должна быть выполнена самим Model. Опять же вам нужно отправить данные на Model. Крюк для этого bool Model::dropMimeData(...).

Как узнать, успешно ли обработаны данные? Проверьте возвращаемое значение! Если данные обработались успешно, то вам, скорее всего, нужно обновить файл View.

Таким образом, в дополнение к приведенному выше коду вы ДОЛЖНЫ переопределить dragEnterEvent, dragMoveEvent и dropEvent принимающего View, чтобы вы могли вызывать canDropMimeData(...) и dropMimeData(...).

/* You need to allow the drag to enter the widget/view. */
/* This is a must. */
void TargetView::dragEnterEvent( QDragEnterEvent *deEvent ) {

    deEvent->acceptProposedAction();
};

/* This is optional. Use this to check if the data can be */
/* dropped at the current mouse position. Example: In a */
/* FileSystem View, it makes no sense to drop a bunch of */
/* files on top of a file, but makes sense it you drop it */
/* in an empty space or on a folder */
void TargetView::dragMoveEvent( QDragMoveEvent *dmEvent ) {
    /* You can do something like this */
    if ( indexAt( dmEvent->pos() ).isValid() ) {
        /* You cannot drop it on an existing index */
        /* If you ignore it, the cursor changes to */
        /* 'don't drop' image */
        dmEvent->ignore();
    }

    else {
        /* Empty space. Tell the user to feel free */
        /* to perform the drop. We accept the event */
        /* Cursor shows the drag+drop icon */
        dmEvent->accept();
    }
};

void TargetView::dropEvent( QDropEvent *dpEvent ) {
    /* Here is where you call canDropMimeData and dropMimeData */
    QMimeData *mData = dpEvent->mimeData();
    if ( model->canDropMimeData( mData, ..., ..., ..., ... ) ) {
        /* Add the data to the model */
        bool updated = model->dropMimeData( mData, ..., ..., ..., ... );
        if ( updated ) {
            /* Intimate the view to update itself */
            update();
        }
    }
};
person Marcus    schedule 29.03.2016