Qt QListWidgetのドラッグ&ドロップを極める:dropMimeData関数の徹底解説

2025-05-31

dropMimeData()関数は、ユーザーがQListWidget上に何かをドロップしたときに呼び出されます。この関数は、ドロップされたデータ(QMimeDataオブジェクトとして提供されます)を受け取り、それをQListWidgetのアイテムとして追加したり、既存のアイテムを移動したりするなどの処理を行います。

この関数は通常、QListWidgetを継承したカスタムクラスでオーバーライドして、独自のドロップ処理を実装するために使用されます。

関数のシグネチャ

virtual bool QListWidget::dropMimeData(int index, const QMimeData *data, Qt::DropAction action);

引数について説明します。

  • Qt::DropAction action: ドロップ操作の種類を示します。これは、Qt::CopyAction(コピー)、Qt::MoveAction(移動)、Qt::LinkAction(リンク)などになります。ドラッグ開始時に設定されたQt::DropActionと、ドロップ時にユーザーがModifierキー(Ctrl, Shiftなど)を押したかどうかによって決定されます。
  • const QMimeData *data: ドロップされたデータを含むQMimeDataオブジェクトへのポインタです。このオブジェクトには、テキスト、URL、カスタムデータなど、様々な形式のデータが含まれている可能性があります。
  • int index: ドロップされた場所のインデックスを示します。これは、アイテムが挿入されるべき位置、または移動されたアイテムの新しい位置を示します。

戻り値

  • bool: ドロップ操作が正常に処理された場合はtrueを返します。そうでない場合はfalseを返します。falseを返すと、ドロップ操作は無視されます。

典型的な使用例(オーバーライド)

QListWidgetにファイルをドロップして、そのファイルパスをアイテムとして追加する例を考えます。

#include <QListWidget>
#include <QMimeData>
#include <QUrl>
#include <QDebug> // デバッグ出力用

class MyListWidget : public QListWidget
{
public:
    MyListWidget(QWidget *parent = nullptr) : QListWidget(parent)
    {
        // ドロップを受け入れるように設定
        setAcceptDrops(true);
        // 内部でのドラッグ&ドロップも有効にする場合
        // setDragDropMode(QAbstractItemView::InternalMove);
    }

protected:
    // ドロップされたMIMEデータを処理する
    bool dropMimeData(int index, const QMimeData *data, Qt::DropAction action) override
    {
        // ドロップされたデータがURLリストであるかをチェック
        if (data->hasUrls()) {
            QList<QUrl> urls = data->urls();
            for (const QUrl &url : urls) {
                // ファイルスキームの場合のみ処理(例: file:///path/to/file.txt)
                if (url.scheme() == "file") {
                    addItem(url.toLocalFile()); // ファイルパスをアイテムとして追加
                    qDebug() << "Dropped file:" << url.toLocalFile();
                }
            }
            // ドロップ処理が成功したことを示す
            return true;
        }

        // 基本クラスのdropMimeDataを呼び出すことで、デフォルトの動作を維持することも可能
        // (例えば、リストアイテムの内部移動など)
        // return QListWidget::dropMimeData(index, data, action);

        // 処理できないMIMEデータの場合はfalseを返す
        return false;
    }

    // ドロップ操作を許可するMIMEタイプを定義する
    // この関数をオーバーライドしない場合、QListWidgetのデフォルトのMIMEタイプが使われる
    // この例では、URLを処理するので、hasUrls()をチェックするだけなので、
    // 明示的にオーバーライドする必要は少ないですが、より厳密に制御したい場合は実装します。
    // QStringList mimeTypes() const override
    // {
    //     QStringList types;
    //     types << "text/uri-list"; // URLリストのMIMEタイプ
    //     return types;
    // }

    // サポートするドロップアクションを定義する
    // デフォルトではQt::CopyAction | Qt::MoveAction をサポートしていることが多い
    // Qt::DropActions supportedDropActions() const override
    // {
    //     return Qt::CopyAction | Qt::MoveAction;
    // }
};
  • mimeTypes()supportedDropActions(): QListWidgetが受け入れるMIMEタイプやサポートするドロップアクションをより細かく制御したい場合は、これらの仮想関数もオーバーライドすることを検討してください。
  • デフォルトの動作: QListWidgetは、内部でドラッグ&ドロップをサポートしています。つまり、QListWidget内のアイテムをドラッグして、同じQListWidget内の別の位置にドロップすると、デフォルトでアイテムが移動します。この動作は、dropMimeData()をオーバーライドしても、適切に基底クラスのQListWidget::dropMimeData()を呼び出すことで維持できます。
  • QMimeData: QMimeDataオブジェクトは、ドラッグ&ドロップ操作で転送されるデータをカプセル化します。このオブジェクトには、様々なMIMEタイプ(text/plain, text/html, image/png, application/x-qt-windows-mime;value="FileNameW"など)でデータを含めることができます。hasUrls()hasText()などのヘルパー関数を使って、データの種類を判別できます。
  • setAcceptDrops(true): QListWidgetがドロップを受け入れるようにするには、必ずsetAcceptDrops(true)を呼び出す必要があります。これを呼び出さないと、dropMimeData()は呼び出されません。


QListWidget::dropMimeData()はドラッグ&ドロップ処理の核となる部分ですが、適切に実装されないと様々な問題が発生します。

dropMimeData()が全く呼び出されない

原因
最も一般的な原因は、ドロップを受け入れるための設定が不足していることです。

トラブルシューティング

  • MIMEタイプが一致しない
    ドラッグ元が提供するQMimeDataのMIMEタイプと、ドロップ先が受け入れるMIMEタイプが一致しない場合、ドロップは許可されません。 QMimeData *data引数を使って、data->hasUrls()data->hasText()data->hasFormat("your/custom-mime-type")などを確認し、処理したいMIMEタイプが実際に存在するかを確認してください。 また、mimeTypes()仮想関数をオーバーライドして、カスタムのMIMEタイプを受け入れるように設定することも可能です。
  • dragEnterEvent()やdragMoveEvent()での処理の不備
    dropMimeData()が呼び出される前に、dragEnterEvent()dragMoveEvent()が適切に処理され、ドロップアクションが許可されている必要があります。特にevent->acceptProposedAction()が呼び出されていない場合、ドロップは許可されません。 カスタムのドラッグ&ドロップ処理を実装する場合、これらのイベントもオーバーライドし、適切なMIMEタイプやドロップアクションをチェックし、event->acceptProposedAction()を呼び出す必要があります。
  • 適切なDragDropModeの設定
    QListWidgetで内部的なドラッグ&ドロップ(リスト内のアイテムの並べ替えなど)を有効にしたい場合は、listWidget->setDragDropMode(QAbstractItemView::InternalMove);を設定します。外部からのドロップも受け入れたい場合は、QAbstractItemView::DropOnlyQAbstractItemView::DragDropなどを適切に設定します。
  • setAcceptDrops(true)の呼び出し忘れ
    QListWidgetがドロップイベントを受け入れるようにするには、必ずlistWidget->setAcceptDrops(true);を呼び出す必要があります。これは、オーバーライドしたカスタムクラスのコンストラクタなどで設定します。

ドロップはされるが、期待する動作をしない

原因
dropMimeData()内で、ドロップされたデータの処理ロジックに問題がある場合です。

トラブルシューティング

  • アイテムの所有権の問題
    QListWidgetItemを動的に生成してQListWidgetに追加する場合、メモリリークを避けるために適切な所有権管理が必要です。QListWidget::addItem()insertItem()に渡されたアイテムは、通常QListWidgetが所有権を持つため、手動でdeleteする必要はありません。しかし、それ以外のケースでは注意が必要です。
  • 基底クラスの呼び出し忘れ/誤った呼び出し
    カスタムのdropMimeData()を実装する際、QListWidget::dropMimeData(index, data, action);を呼び出すかどうかは、その目的によって異なります。
    • 内部的なアイテム移動をサポートしつつ、外部からのドロップも処理したい場合
      外部からのドロップ(例: ファイルドロップ)を処理し、それ以外のMIMEタイプや、処理が完了しない場合に、基底クラスのdropMimeData()を呼び出すことで、QListWidgetがデフォルトで提供する内部的なアイテム移動などの機能を維持できます。
    • 完全に独自のドロップ処理を行う場合
      基底クラスのdropMimeData()を呼び出す必要はありません。この場合、全てのドロップ処理を自身で実装する必要があります。
  • ドロップアクション(action引数)の考慮不足
    Qt::DropAction action引数は、ユーザーがCtrlキーを押しながらドロップした場合はQt::CopyAction、そうでない場合はQt::MoveActionなど、ドロップの種類を示します。このactionに基づいて、コピー処理を行うか、移動処理を行うかを適切に分岐させる必要があります。
  • インデックス(index引数)の利用方法の間違い
    index引数は、ドロップされた場所のインデックスを示します。これはアイテムを挿入する位置として利用できますが、誤って使用するとアイテムが意図しない場所に追加されたり、既存のアイテムが上書きされたりする可能性があります。
  • QMimeDataの内容の確認
    dropMimeData()内でqDebug()などを使って、dataオブジェクトの内容(例えばdata->formats()で利用可能なMIMEタイプ一覧、data->urls()data->text()などの具体的なデータ)を確認してください。期待するデータが実際に含まれているかを確認することが重要です。

ドラッグ中の見た目の問題(ドロップインジケーターなど)

原因
dropMimeData()自体が直接関係する問題ではありませんが、ドラッグ&ドロップ体験全体に影響します。

トラブルシューティング

  • dragMoveEvent()の適切な実装
    dragMoveEvent()内で、ドロップが可能な場所をevent->accept()またはevent->acceptProposedAction()で示し、それ以外の場所ではevent->ignore()とすることで、ドロップインジケーターの表示を制御できます。
  • setDropIndicatorShown(true)
    ドロップが可能な位置に視覚的なインジケーターを表示するには、listWidget->setDropIndicatorShown(true);を設定します。

クラッシュや未定義動作

原因
ポインタの不正なアクセス、メモリ管理の誤り、MIMEデータの不正な解釈など。

  • シンプルなケースで動作確認
    複雑なカスタムデータを扱う前に、まずファイルパスや単純なテキストなど、QtがデフォルトでサポートするMIMEタイプでdropMimeData()が正しく動作するかを確認してください。
  • Qtのデバッグ出力の活用
    デバッグモードでビルドし、Qtの提供するデバッグ出力(qDebug()など)を確認することで、ドラッグ&ドロップ関連の警告やエラーメッセージが見つかることがあります。
  • データ型の一致
    QMimeDataからデータを取得する際(例: data->urls()data->text()など)、期待するデータ型が実際に含まれているか確認してください。例えば、data->hasUrls()falseなのにdata->urls()を呼び出すと、空のリストが返されるか、あるいは未定義動作を引き起こす可能性があります。
  • QMimeDataのポインタがnullptrでないか確認
    dropMimeDataに渡されるdataポインタは、常に有効であると仮定できますが、念のためif (data)でチェックすることは良い習慣です。


QListWidget::dropMimeData()のコード例解説

QListWidget::dropMimeData()は、ドラッグ&ドロップ操作でデータがウィジェットにドロップされた際に、そのデータをどのように処理するかを定義するためにオーバーライドする仮想関数です。

ここでは、一般的なユースケースである「外部からのファイルドロップを受け入れ、そのファイルパスをリストアイテムとして追加する」例と、「リストウィジェット内でアイテムを並べ替える」例を組み合わせたコードを示します。

基本的なセットアップ (MainWindow.h と MainWindow.cpp)

まず、QListWidgetを配置するメインウィンドウを作成します。

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget> // QListWidgetを使用
#include <QMimeData>   // ドロップされたデータ型を扱うために必要

// カスタムQListWidgetクラスの前方宣言
class MyListWidget;

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    MyListWidget *myListWidget; // カスタムQListWidgetのインスタンス
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mylistwidget.h" // カスタムQListWidgetのヘッダ

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

    // MyListWidgetのインスタンスを生成し、レイアウトに追加
    myListWidget = new MyListWidget(this);
    setCentralWidget(myListWidget); // メインウィンドウの中央ウィジェットとして設定

    // サンプルアイテムを追加
    myListWidget->addItem("Initial Item 1");
    myListWidget->addItem("Initial Item 2");
    myListWidget->addItem("Initial Item 3");
}

MainWindow::~MainWindow()
{
    delete ui;
}

カスタムMyListWidgetクラスの実装

ここでdropMimeData()をオーバーライドします。

MyListWidget.h

#ifndef MYLISTWIDGET_H
#define MYLISTWIDGET_H

#include <QListWidget>
#include <QMimeData>
#include <QUrl>
#include <QDebug> // デバッグ出力用

class MyListWidget : public QListWidget
{
    Q_OBJECT // シグナルとスロットを使用する場合に必要

public:
    MyListWidget(QWidget *parent = nullptr);

protected:
    // ドロップされたMIMEデータを処理する仮想関数
    bool dropMimeData(int index, const QMimeData *data, Qt::DropAction action) override;

    // ドロップ操作が可能かどうかを判断するイベント(任意だが推奨)
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dragMoveEvent(QDragMoveEvent *event) override;

    // ドラッグ中に提供するMIMEデータを生成する仮想関数(内部ドラッグ&ドロップ用)
    QMimeData *mimeData(const QList<QListWidgetItem *> items) const override;

    // サポートするMIMEタイプを定義する仮想関数(任意だが推奨)
    QStringList mimeTypes() const override;

    // サポートするドロップアクションを定義する仮想関数(任意だが推奨)
    Qt::DropActions supportedDropActions() const override;
};

#endif // MYLISTWIDGET_H

MyListWidget.cpp

#include "mylistwidget.h"

MyListWidget::MyListWidget(QWidget *parent) : QListWidget(parent)
{
    // ドロップを受け入れるように設定
    setAcceptDrops(true);

    // 内部でのドラッグ&ドロップを有効にする
    // これにより、アイテムの並べ替えがデフォルトで可能になります
    setDragDropMode(QAbstractItemView::InternalMove);

    // ドロップインジケーター(アイテムがどこに挿入されるかを示す線)を表示
    setDropIndicatorShown(true);
}

// ドロップされたMIMEデータを処理する
bool MyListWidget::dropMimeData(int index, const QMimeData *data, Qt::DropAction action)
{
    qDebug() << "dropMimeData called at index:" << index << "action:" << action;

    // 1. 外部からのファイルドロップを処理する
    if (data->hasUrls()) {
        QList<QUrl> urls = data->urls();
        for (const QUrl &url : urls) {
            // ローカルファイルスキームのURLのみを処理
            if (url.isLocalFile()) {
                // 指定されたインデックスに新しいアイテムを挿入
                insertItem(index, url.toLocalFile());
                qDebug() << "Dropped local file:" << url.toLocalFile();
                // 複数のファイルがドロップされた場合、次のファイルの挿入位置を調整
                // これは、挿入した分だけindexがずれるため
                if (index != -1) { // indexが-1(末尾)でない場合
                    index++;
                }
            }
        }
        // ファイルドロップが処理されたのでtrueを返す
        return true;
    }
    // 2. テキストデータのドロップを処理する
    else if (data->hasText()) {
        // ドロップされたテキストを新しいアイテムとして追加
        insertItem(index, data->text());
        qDebug() << "Dropped text:" << data->text();
        return true;
    }
    // 3. QListWidget_からドラッグされた内部データを処理する(アイテムの並べ替えなど)
    // QListWidgetのデフォルトのmimeData()は、"application/x-qabstractitemmodeldatalist" を生成します。
    // このMIMEタイプを処理することで、QListWidgetの内部的なドラッグ&ドロップ(アイテムの移動)が機能します。
    // この部分をコメントアウトすると、外部からのファイルドロップのみを処理し、内部的なアイテム移動はできなくなります。
    else if (data->hasFormat("application/x-qabstractitemmodeldatalist")) {
        qDebug() << "Internal list item drop detected.";
        // 基底クラスのdropMimeDataを呼び出すことで、QListWidgetのデフォルトのドロップ処理(アイテムの移動など)に任せる
        return QListWidget::dropMimeData(index, data, action);
    }

    // どのMIMEタイプにも一致しない場合は、ドロップを処理しない
    qDebug() << "Unsupported MIME data dropped.";
    return false;
}

// ドラッグイベントがウィジェットに入ったときに呼び出される
void MyListWidget::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "dragEnterEvent called.";
    // ドロップされたデータがURLまたはテキストを含む、あるいは内部リストデータである場合にのみ許可
    if (event->mimeData()->hasUrls() || event->mimeData()->hasText() ||
        event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) {
        event->acceptProposedAction(); // ドロップを許可
        qDebug() << "Drag enter event accepted.";
    } else {
        event->ignore(); // ドロップを無視
        qDebug() << "Drag enter event ignored.";
    }
}

// ドラッグイベントがウィジェット内を移動しているときに呼び出される
void MyListWidget::dragMoveEvent(QDragMoveEvent *event)
{
    qDebug() << "dragMoveEvent called.";
    // dragEnterEventと同様のチェックを行う
    if (event->mimeData()->hasUrls() || event->mimeData()->hasText() ||
        event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) {
        event->acceptProposedAction(); // ドロップを許可
    } else {
        event->ignore(); // ドロップを無視
    }
}

// ドラッグ中に提供するMIMEデータを生成
// QListWidgetのデフォルトの動作(テキストやアイコンなど)に加えて、
// カスタムデータを追加したい場合にオーバーライドします。
// ここでは、基底クラスの動作を呼び出し、必要に応じて追加データを設定します。
QMimeData *MyListWidget::mimeData(const QList<QListWidgetItem *> items) const
{
    // 基底クラスのmimeDataを呼び出し、デフォルトのMIMEデータを取得
    QMimeData *mime = QListWidget::mimeData(items);

    // 必要であれば、ここにカスタムデータを追加できます
    // 例: 選択されたアイテムのテキストを特定のMIMEタイプで追加
    // QStringList texts;
    // for (QListWidgetItem *item : items) {
    //     texts << item->text();
    // }
    // mime->setText(texts.join("\n")); // テキスト形式でも提供
    // mime->setData("application/x-my-custom-data", "Some custom data payload".toUtf8());

    return mime;
}

// サポートするMIMEタイプを定義
// これをオーバーライドすることで、QListWidgetがどのMIMEタイプを受け入れるか明示的に示せます。
// ここで指定されたMIMEタイプのみがdragEnterEventなどで許可されます。
QStringList MyListWidget::mimeTypes() const
{
    QStringList types = QListWidget::mimeTypes(); // デフォルトのMIMEタイプを取得
    types << "text/uri-list"; // ファイルパス(URL)用
    types << "text/plain";    // 一般的なテキスト用
    // types << "application/x-my-custom-data"; // カスタムデータ用
    return types;
}

// サポートするドロップアクションを定義
// デフォルトではQt::CopyActionとQt::MoveActionがサポートされています。
// 特定のアクションのみを許可したい場合にオーバーライドします。
Qt::DropActions MyListWidget::supportedDropActions() const
{
    // コピーと移動の両方を許可
    return Qt::CopyAction | Qt::MoveAction;
}

.proファイルの設定

プロジェクトファイル(.pro)にウィジェットと必要なモジュールを追加します。

QT += widgets

HEADERS += \
    mainwindow.h \
    mylistwidget.h

SOURCES += \
    main.cpp \
    mainwindow.cpp \
    mylistwidget.cpp

FORMS += \
    mainwindow.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. MyListWidgetクラスの定義と継承
    QListWidgetを継承したMyListWidgetクラスを定義します。これにより、既存のQListWidgetの機能を維持しつつ、特定の仮想関数をオーバーライドしてカスタマイズできます。

  2. コンストラクタ(MyListWidget::MyListWidget)での設定

    • setAcceptDrops(true);: これが最も重要で、このウィジェットがドロップイベントを受け入れることを有効にします。
    • setDragDropMode(QAbstractItemView::InternalMove);: これにより、QListWidgetがデフォルトで提供する内部的なドラッグ&ドロップ(リストアイテムの並べ替え)機能が有効になります。外部からのドロップと同時に内部ドラッグ&ドロップも使いたい場合に便利です。
    • setDropIndicatorShown(true);: ドロップ可能な位置に線が表示され、ユーザーに視覚的なフィードバックを提供します。
  3. dropMimeData()のオーバーライド
    この関数がドロップイベントの核心です。

    • data->hasUrls(): ドロップされたデータがURL(ファイルパスなど)を含んでいるかをチェックします。
      • url.isLocalFile(): URLがローカルファイルを示しているかを確認します。
      • insertItem(index, url.toLocalFile());: ファイルパスを新しいリストアイテムとして指定されたインデックスに挿入します。
    • data->hasText(): ドロップされたデータがプレーンテキストを含んでいるかをチェックします。
      • insertItem(index, data->text());: テキストを新しいリストアイテムとして挿入します。
    • data->hasFormat("application/x-qabstractitemmodeldatalist"): これは、QListWidgetや他のモデル/ビューウィジェットが内部的なドラッグ&ドロップで使うデフォルトのMIMEタイプです。この形式のデータがドロップされた場合、QListWidget::dropMimeData()(基底クラスの関数)を呼び出すことで、アイテムの並べ替えなどのデフォルト動作を実行させます。これにより、外部からのドロップと内部からのドロップの両方をうまく扱えます。
    • return true; / return false;: ドロップ処理が成功した場合はtrueを、処理できなかった場合はfalseを返します。falseを返すと、ドロップ操作は無視されます。
  4. dragEnterEvent()とdragMoveEvent()のオーバーライド (推奨)
    これらの関数は、ドロップが可能かどうかをリアルタイムでユーザーにフィードバックするために重要です。

    • event->acceptProposedAction(): ドロップを許可し、ドロップインジケーターを表示させます。
    • event->ignore(): ドロップを拒否し、ドロップインジケーターを非表示にします。
    • これらの関数では、QMimeDataオブジェクトの内容を確認し、自身のウィジェットが処理できるMIMEタイプの場合のみacceptProposedAction()を呼び出すべきです。
  5. mimeData()のオーバーライド (内部ドラッグ&ドロップのカスタマイズ)
    これは、QListWidgetからアイテムがドラッグされる際に、どのようなMIMEデータが提供されるかを定義する関数です。 上記の例では、QListWidget::mimeData(items)を呼び出してデフォルトのMIMEデータを取得しています。もし、QListWidgetItemに格納されていないカスタムデータをドラッグ&ドロップで転送したい場合は、この関数をオーバーライドしてQMimeDataオブジェクトにそのデータを設定します。

  6. mimeTypes()とsupportedDropActions()のオーバーライド (任意)

    • mimeTypes(): このウィジェットがドロップを受け入れるMIMEタイプのリストを返します。dragEnterEvent()dragMoveEvent()でのチェックと連携して機能します。
    • supportedDropActions(): このウィジェットがサポートするドロップアクション(コピー、移動、リンクなど)を返します。


QAbstractItemModel と QListView を使用する (より柔軟なアプローチ)

QListWidget は、内部的に QStandardItemModelQListView を組み合わせて作られた便利なウィジェットです。より高度なドラッグ&ドロップの制御や、大規模なデータセットを扱う場合は、QListWidget の代わりに QAbstractItemModel を継承したカスタムモデルと、QListView を直接使用する方が推奨されます。

このアプローチでは、以下の仮想関数をモデル側で実装します。

  • QStringList QAbstractItemModel::mimeTypes() const: モデルが提供または受け入れるMIMEタイプのリストを返します。
  • QMimeData *QAbstractItemModel::mimeData(const QModelIndexList &indexes) const: ドラッグ操作が開始された際に、ドラッグされるアイテムのデータを QMimeData オブジェクトとしてエンコードします。
  • Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const: アイテムのフラグを設定します。特に Qt::ItemIsDragEnabled (ドラッグ可能) と Qt::ItemIsDropEnabled (ドロップ可能) フラグを設定することで、アイテムがドラッグ&ドロップ操作の対象になることをビューに伝えます。
  • bool QAbstractItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent): QListWidget::dropMimeData() と同様に、ドロップされたMIMEデータを処理します。ただし、この関数はモデルのデータ構造を変更する責任を持ちます(行の挿入、削除、データの変更など)。
  • bool QAbstractItemModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent): ドロップ操作が可能かどうかを判断します。dropMimeData() が呼び出される前に、ドロップが許可されるべきかをモデルが判断します。

利点

  • 柔軟性
    複数のビュー間でデータをドラッグ&ドロップしたり、異なるモデルタイプ間でデータを転送したりするのに適しています。
  • パフォーマンス
    大量のアイテムを扱う場合に、QListWidget よりも優れたパフォーマンスを発揮することが多いです。
  • より高い制御性
    データの格納方法とビューの表示方法を完全に分離できるため、非常に複雑なデータ構造やカスタムドロップ処理を実装できます。

考慮点

  • 記述するコード量
    シンプルなケースでは QListWidget よりも多くのコードを記述する必要があります。
  • 学習コスト
    QListWidget を直接使用するよりも、モデル/ビューフレームワークの概念を深く理解する必要があります。

イベントフィルターを使用する

QListWidget をサブクラス化せずに、またはdropMimeData() をオーバーライドせずに、ドラッグ&ドロップイベントを処理したい場合、イベントフィルターを使用できます。

ウィジェットにイベントフィルターをインストールし、QEvent::DragEnter, QEvent::DragMove, QEvent::Drop などのイベントをフィルタリングして処理します。

// MyWidget.h (イベントフィルターをインストールする側のウィジェット)
#include <QWidget>
#include <QListWidget>
#include <QEvent>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QUrl>
#include <QDebug>

class MyListWidget; // MyListWidget を前方宣言

class MyWidget : public QWidget
{
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr);

protected:
    // イベントフィルター
    bool eventFilter(QObject *obj, QEvent *event) override;

private:
    MyListWidget *listWidget;
};

// MyListWidget.h (QListWidget を直接使用)
// dropMimeData をオーバーライドしない
#include <QListWidget>

class MyListWidget : public QListWidget
{
    Q_OBJECT
public:
    MyListWidget(QWidget *parent = nullptr) : QListWidget(parent)
    {
        setAcceptDrops(true); // ドロップを受け入れる設定は必要
        setDragDropMode(QAbstractItemView::InternalMove); // 内部移動も必要なら
    }
    // dropMimeData() はオーバーライドしない
};

// MyWidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
    listWidget = new MyListWidget(this);
    // listWidget にイベントフィルターをインストール
    listWidget->installEventFilter(this);

    // レイアウト設定など
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(listWidget);
}

bool MyWidget::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == listWidget) {
        if (event->type() == QEvent::DragEnter) {
            QDragEnterEvent *dragEnterEvent = static_cast<QDragEnterEvent*>(event);
            if (dragEnterEvent->mimeData()->hasUrls() || dragEnterEvent->mimeData()->hasText()) {
                dragEnterEvent->acceptProposedAction();
                qDebug() << "EventFilter: Drag Enter accepted.";
                return true; // イベントを処理済みとしてマーク
            } else {
                dragEnterEvent->ignore();
                qDebug() << "EventFilter: Drag Enter ignored.";
                return true; // イベントを処理済みとしてマーク
            }
        } else if (event->type() == QEvent::Drop) {
            QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
            if (dropEvent->mimeData()->hasUrls()) {
                QList<QUrl> urls = dropEvent->mimeData()->urls();
                for (const QUrl &url : urls) {
                    if (url.isLocalFile()) {
                        listWidget->addItem(url.toLocalFile());
                        qDebug() << "EventFilter: Dropped file:" << url.toLocalFile();
                    }
                }
                dropEvent->acceptProposedAction();
                return true;
            } else if (dropEvent->mimeData()->hasText()) {
                listWidget->addItem(dropEvent->mimeData()->text());
                qDebug() << "EventFilter: Dropped text:" << dropEvent->mimeData()->text();
                dropEvent->acceptProposedAction();
                return true;
            }
            // 内部ドラッグ&ドロップの処理をQListWidgetに任せる場合
            // ドロップイベントが処理されなかった場合に、QListWidgetのデフォルト処理が実行されます。
            // あるいは、ここで明示的にQListWidgetのdropMimeData()を呼び出すことも可能ですが、
            // それは結局サブクラス化と似たアプローチになります。
            // dropEvent->ignore(); // 処理しない場合は無視
            // return true;
        }
    }
    // それ以外のイベントや、他のオブジェクトの場合はデフォルトの処理に任せる
    return QObject::eventFilter(obj, event);
}

利点

  • 複数のウィジェットで共通のドラッグ&ドロップロジックを再利用できます。
  • 既存のウィジェットクラスを直接変更せずに、ドラッグ&ドロップ動作を追加できます。

考慮点

  • dropMimeData() をオーバーライドする方が、ドラッグ&ドロップに特化したインタフェースが提供されるため、コードの可読性が高まる場合があります。
  • イベント処理の順序や、イベントの伝播について理解が必要です。event->accept()event->ignore() の呼び出し方によって、その後のイベント処理が変わります。

非常に低レベルな制御が必要な場合や、QListWidget 以外のカスタムウィジェットでドラッグ&ドロップを実装する場合、QDrag オブジェクトと QDropEvent クラスのメソッドを直接使用して、ドラッグ&ドロップ操作全体を自分で管理する方法があります。

このアプローチでは、以下のイベントハンドラをオーバーライドします。

  • void QWidget::dropEvent(QDropEvent *event): ドロップ操作が完了したときに呼び出されます。event->mimeData() からデータを取得し、ウィジェットの内部状態を更新します。
  • void QWidget::dragMoveEvent(QDragMoveEvent *event): ドラッグ中のアイテムがウィジェット内を移動しているときに呼び出されます。適切なドロップアクションを event->acceptProposedAction() で設定します。
  • void QWidget::dragEnterEvent(QDragEnterEvent *event): ドラッグ中のアイテムがウィジェットに入ったときに呼び出されます。ドロップを受け入れるかどうかを判断し、event->acceptProposedAction() を呼び出します。
  • void QWidget::mouseMoveEvent(QMouseEvent *event): マウスが移動し、ドラッグが開始されるべきしきい値を超えたときに QDrag オブジェクトを生成し、ドラッグを開始します(drag->exec() を呼び出す)。
  • void QWidget::mousePressEvent(QMouseEvent *event): ドラッグ開始のきっかけを検知します(例: マウスの左ボタンが押されたとき)。

QListWidget の場合、通常はここまで低レベルな制御は不要ですが、例えばQGraphicsView のシーン内のカスタムアイテムをドラッグ&ドロップする場合など、特定の高度なユースケースでこの方法が用いられます。

利点

  • QListWidget のような標準ウィジェットの枠を超えた、完全にカスタムなドラッグ&ドロップ体験を構築できます。
  • ドラッグ&ドロップの動作を非常に細かく制御できます。
  • Qt のドラッグ&ドロップフレームワークの深い理解が必要です。
  • 最も複雑なアプローチであり、多くのコードを記述する必要があります。
  • QDrag および QDropEvent の直接操作
    QListWidget のコンテキストでは稀ですが、完全にカスタムなウィジェットやグラフィックスシーンでのドラッグ&ドロップを実装する際に用いられる、最も低レベルなアプローチです。
  • イベントフィルター
    既存のウィジェットをサブクラス化できない場合や、複数のウィジェットに共通のドラッグ&ドロップロジックを適用したい場合に便利です。
  • QAbstractItemModel と QListView
    大規模なデータや複雑なデータ構造、あるいは複数のビュー間のドラッグ&ドロップなど、より高度な柔軟性とパフォーマンスが求められる場合に適しています。
  • QListWidget::dropMimeData() をオーバーライド
    QListWidget の基本的なドラッグ&ドロップ機能を拡張する最も直接的で推奨される方法です。