ドラッグ&ドロップ

ドラッグ&ドロップは、ユーザーがアプリケーション間やアプリケーション内で情報を転送するために使用できる、シンプルな視覚的メカニズムを提供します。ドラッグ・アンド・ドロップの機能は、クリップボードのカット・アンド・ペーストに似ています。

このドキュメントでは、基本的なドラッグ・アンド・ドロップの仕組みと、カスタム・コントロールでドラッグ・アンド・ドロップを使用するための方法について説明します。ドラッグ&ドロップの操作は、アイテムビューやグラフィックスビューフレームワーク、QtウィジェットやQtクイックの編集コントロールなど、Qtの多くのコントロールでもサポートされています。アイテムビューとグラフィックビューの詳細については、Using drag and drop with item viewsandGraphics View Frameworkを参照してください。

ドラッグ&ドロップクラス

これらのクラスは、ドラッグ&ドロップと、必要な MIME タイプのエンコードとデコードを扱います。

QDrag

MIMEベースのドラッグ・アンド・ドロップ・データ転送のサポート

QDragEnterEvent

ドラッグ・アンド・ドロップ・アクションがウィジェットに入ったときにウィジェットに送られるイベント

QDragLeaveEvent

ドラッグ&ドロップ・アクションがウィジェットから離れるときにウィジェットに送られるイベント

QDragMoveEvent

ドラッグ&ドロップ・アクションの実行中に送信されるイベント

QDropEvent

ドラッグ&ドロップアクションが完了したときに送信されるイベント

QUtiMimeConverter

MIMEタイプとUTI(Uniform Type Identifier)フォーマットの変換

構成

QStyleHints オブジェクトは、ドラッグ&ドロップ操作に関連するいくつかのプロパティを提供します:

  • QStyleHints::startDragTime() は、ドラッグを開始する前に、ユーザがオブジェクト上でマウスボタンを押し続けなければならない時間をミリ秒単位で示します。
  • QStyleHints::startDragDistance() は、マウスボタンを押したままマウスを動かすと、その動きがドラッグと解釈されるまでの距離を表します。
  • QStyleHints::startDragVelocity() は、ドラッグを開始するためにユーザーがマウスを動かす速さ (ピクセル/秒) を示します。値が0 の場合は、そのような制限がないことを意味します。

これらの量は、コントロールでドラッグ&ドロップをサポートする場合に使用する、基本的なウィンドウシステムに準拠した賢明なデフォルト値を提供します。

Qt Quick におけるドラッグ&ドロップ

このドキュメントでは、主に C++ でのドラッグ&ドロップの実装方法について説明します。Qt Quick のシーン内でドラッグ&ドロップを使用するには、Qt Quick のDragDragEventDropArea のドキュメントと、Qt Quick Drag and Drop のサンプルを参照してください。

ドラッグ

ドラッグを開始するには、QDrag オブジェクトを作成し、その exec() 関数を呼び出します。ほとんどのアプリケーションでは、マウスボタンが押され、カーソルが一定距離移動してからドラッグ&ドロップを開始するのがよいでしょう。しかし、ウィジェットからのドラッグを有効にする最も簡単な方法は、ウィジェットのmousePressEvent() を再実装してドラッグ・アンド・ドロップ操作を開始することです:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton
        && iconLabel->geometry().contains(event->pos())) {

        QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;

        mimeData->setText(commentEdit->toPlainText());
        drag->setMimeData(mimeData);
        drag->setPixmap(iconPixmap);

        Qt::DropAction dropAction = drag->exec();
        ...
    }
}

ユーザがドラッグ操作を完了するまでに時間がかかるかもしれませんが、アプリケーションに関 する限り、exec()関数はone of several values を返すブロッキング関数です。これらは、操作がどのように終了したかを示すもので、以下で詳しく説明します。

exec() 関数はメイン・イベント・ループをブロックしないことに注意してください。

マウスクリックとドラッグを区別する必要があるウィジェットでは、ドラッグの開始位置を記録するために、ウィジェットのmousePressEvent() 関数を再実装すると便利です:

void DragWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        dragStartPosition = event->pos();
}

その後、mouseMoveEvent() で、ドラッグを開始すべきかどうかを判断し、操作を処理するドラッグ・オブジェクトを構築することができます:

void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (!(event->buttons() & Qt::LeftButton))
        return;
    if ((event->pos() - dragStartPosition).manhattanLength()
         < QApplication::startDragDistance())
        return;

    QDrag *drag = new QDrag(this);
    QMimeData *mimeData = new QMimeData;

    mimeData->setData(mimeType, data);
    drag->setMimeData(mimeData);

    Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
    ...
}

この特定のアプローチでは、QPoint::manhattanLength ()関数を使用して、マウスクリックが発生した場所と現在のカーソル位置との間の距離を大まかに推定します。この関数は正確さとスピードを引き換えにしたもので、通常はこの目的に適しています。

ドロップ

ウィジェットにドロップされたメディアを受信するには、そのウィジェットに対してsetAcceptDrops (true)を呼び出し、dragEnterEvent ()とdropEvent ()イベントハンドラ関数を再実装します。

たとえば、次のコードは、QWidget サブクラスのコンストラクタでドロップ・イベントを有効にし、ドロップ・イベント・ハンドラを有用に実装できるようにします:

Window::Window(QWidget *parent)
    : QWidget(parent)
{
    ...
    setAcceptDrops(true);
}

dragEnterEvent()関数は通常、ウィジェットが受け付けるデータのタイプをQtに通知するために使用されます。dragMoveEvent() やdropEvent() の再実装でQDragMoveEventQDropEvent を受け取りたい場合は、この関数を再実装する必要があります。

次のコードは、dragEnterEvent ()を再実装して、ドラッグ・アンド・ドロップ・システムにプレーン・テキストしか扱えないことを伝える方法を示しています:

void Window::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat("text/plain"))
        event->acceptProposedAction();
}

dropEvent ()は、ドロップされたデータを解凍し、アプリケーションに適した方法で処理するために使用されます。

以下のコードでは、イベントで指定されたテキストがQTextBrowser に渡され、QComboBox にデータの記述に使用される MIME タイプのリストが格納されます:

void Window::dropEvent(QDropEvent *event)
{
    textBrowser->setPlainText(event->mimeData()->text());
    mimeTypeCombo->clear();
    mimeTypeCombo->addItems(event->mimeData()->formats());

    event->acceptProposedAction();
}

この場合、提案されたアクションが何であるかをチェックすることなく受け入れます。実世界のアプリケーションでは、提案されたアクションを受け入れずにdropEvent()関数から戻ったり、アクションが関係ない場合はデータを処理したりする必要があるかもしれません。例えば、アプリケーションで外部ソースへのリンクをサポートしない場合、Qt::LinkAction アクションを無視することを選択するかもしれません。

提案されたアクションの上書き

提案されたアクションを無視して、データに対して他のアクションを実行することもできます。そのためには、accept ()を呼び出す前に、Qt::DropAction から優先アクションを指定して、イベントオブジェクトのsetDropAction ()を呼び出します。これにより、提案されたアクションの代わりに、置換されたドロップ・アクションが確実に使用されます。

より洗練されたアプリケーションでは、dragMoveEvent() とdragLeaveEvent() を再実装することで、ウィジェットの特定の部分をドロップ・イベントに敏感に反応させることができ、アプリケーションでドラッグ・アンド・ドロップをよりコントロールできるようになります。

複雑なウィジェットのサブクラス化

Qt の標準ウィジェットの中には、ドラッグ&ドロップを独自にサポートしているものがあります。このようなウィジェットをサブクラス化する場合、dragEnterEvent() やdropEvent() に加えて、dragMoveEvent() を再実装して、ベースクラスがデフォルトのドラッグ&ドロップ処理を提供しないようにする必要があります。

ドラッグ&ドロップ・アクション

最も単純なケースでは、ドラッグ&ドロップ・アクションのターゲットはドラッグされるデータのコピーを受け取り、ソースはオリジナルを削除するかどうかを決定します。これはCopyAction アクションで説明します。ターゲットは他のアクション、特にMoveActionLinkAction アクションを処理することもできます。ソースがQDrag::exec() を呼び出し、それがMoveAction を返す場合、元のデータを削除することを選択した場合は、ソースの責任で削除します。ソース・ウィジェットによって作成されたQMimeDataQDrag オブジェクトは削除しないでください。ドラッグ&ドロップ操作で送信されたデータの所有権はターゲットにあります。

ターゲットがLinkAction アクションを理解している場合、元の情報への参照を保存する必要があります。ドラッグ&ドロップ・アクションの最も一般的な使い方は、同じウィジェット内で移動を実行するときです。この機能の詳細については、ドロップ・アクションのセクションを参照してください。

ドラッグ・アクションのもう1つの主な用途は、text/uri-listのような参照タイプを使用するときで、ドラッグされたデータは実際にはファイルやオブジェクトへの参照です。

新しいドラッグ&ドロップタイプの追加

ドラッグ・アンド・ドロップはテキストや画像に限りません。どのようなタイプの情報でもドラッグ&ドロップで転送することができます。アプリケーション間で情報をドラッグ・アンド・ドロップするには、アプリケーション間で、どのデータ形式を受け入れ、どのデータ形式を生成できるかを示すことができなければなりません。これはMIMEタイプを使って実現される。ソースによって構築されたQDrag オブジェクトには、データを表現するために使用する MIME タイプのリストが含まれています(最も適切なものから最も適切でないものの順)。一般的なデータ型の場合、便宜関数は透過的に使用される MIME タイプを処理しますが、カスタム・データ型の場合は明示的に指定する必要があります。

QDrag の便利関数でカバーされていないタイプの情報に対してドラッグ・アンド・ドロップ・アクションを実装するために、まず最も重要なステップは、適切な既存のフォーマットを探すことです:Internet Assigned Numbers Authority(IANA)は、Information Sciences Institute(ISI)でMIMEメディアタイプの階層リストを提供している。標準的なMIMEタイプを使うことで、あなたのアプリケーションと他のソフトウェアとの相互運用性を、現在も将来も最大限に高めることができます。

追加のメディア・タイプをサポートするには、setData ()関数でQMimeData オブジェクトにデータを設定するだけです。完全なMIMEタイプと、適切なフォーマットのデータを含むQByteArray 。次のコードでは、ラベルからpixmapを取り出し、それをPortable Network Graphics(PNG)ファイルとしてQMimeData オブジェクトに格納しています:

    QByteArray output;
    QBuffer outputBuffer(&output);
    outputBuffer.open(QIODevice::WriteOnly);
    imageLabel->pixmap().toImage().save(&outputBuffer, "PNG");
    mimeData->setData("image/png", output);

もちろん、この場合、setImageData ()を代わりに使って、さまざまな形式の画像データを提供することもできます:

    mimeData->setImageData(QVariant(*imageLabel->pixmap()));

QMimeData オブジェクトに格納されるデータ量をより細かく制御できるので、この場合でもQByteArray のアプローチは有用です。

アイテム・ビューで使用されるカスタム・データ型は、meta objects として宣言する必要があり、そのためのストリーム演算子を実装する必要があることに注意してください。

ドロップアクション

クリップボードモデルでは、ユーザーはソース情報を切り取りまたはコピーし、後でそれを貼り付けることができます。ドラッグ・アンド・ドロップ・モデルでも同様に、ユーザーは情報のコピーをドラッグしたり、情報自体を新しい場所にドラッグ(移動)することができます。ドラッグ・アンド・ドロップ・モデルには、プログラマーにとってさらに複雑な問題がある:プログラムは、操作が完了するまで、ユーザーが情報をカットしたいのかコピーしたいのかわかりません。このことは、アプリケーション間で情報をドラッグする場合には違いがないことが多いのですが、アプリケーション内では、どちらのドロップ操作が使われたかをチェックすることが重要です。

ウィジェットのmouseMoveEvent()を再実装し、可能なドロップ・アクションの組み合わせでドラッグ・アンド・ドロップ操作を開始することができます。例えば、ドラッグすると常にウィジェット内のオブジェクトが移動するようにしたい場合があります:

void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (!(event->buttons() & Qt::LeftButton))
        return;
    if ((event->pos() - dragStartPosition).manhattanLength()
         < QApplication::startDragDistance())
        return;

    QDrag *drag = new QDrag(this);
    QMimeData *mimeData = new QMimeData;

    mimeData->setData(mimeType, data);
    drag->setMimeData(mimeData);

    Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
    ...
}

exec()関数によって返されるアクションは、情報が他のアプリケーションにドロップされた場合はデフォルトでCopyAction 、同じアプリケーションの他のウィジェットにドロップされた場合は、異なるドロップアクションを取得することができます。

提案されたドロップアクションは、ウィジェットのdragMoveEvent()関数でフィルタリングできます。しかし、dragEnterEvent()で提案されたアクションをすべて受け入れ、ユーザーが後でどれを受け入れるかを決めることも可能です:

void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
    event->acceptProposedAction();
}

ウィジェットでドロップが発生すると、dropEvent()ハンドラ関数が呼び出され、可能性のあるアクションを順番に処理できます。まず、同じウィジェット内でのドラッグ&ドロップ操作に対処します:

void DragWidget::dropEvent(QDropEvent *event)
{
    if (event->source() == this && event->possibleActions() & Qt::MoveAction)
        return;

この場合、移動操作は扱いません。この場合、移動の処理は拒否します。受け入れるドロップ操作の各タイプがチェックされ、それに応じて処理されます:

    if (event->proposedAction() == Qt::MoveAction) {
        event->acceptProposedAction();
        // Process the data from the event.
    } else if (event->proposedAction() == Qt::CopyAction) {
        event->acceptProposedAction();
        // Process the data from the event.
    } else {
        // Ignore the drop.
        return;
    }
    ...
}

上記のコードでは、個々のドロップ操作をチェックしていることに注意してください。上記の「提案されたアクションのオーバーライド」のセクションで述べたように、提案されたドロップアクションをオーバーライドして、可能なドロップアクションの選択から別のアクションを選択する必要がある場合があります。これを行うには、イベントのpossibleActions() が提供する値で各アクションの有無をチェックし、setDropAction() でドロップアクションを設定し、accept() を呼び出す必要があります。

矩形のドロップ

ウィジェットのdragMoveEvent()を使用して、カーソルがその領域内にあるときに提案されたドロップ・アクションのみを受け入れることによって、ウィジェットの特定の部分へのドロップを制限することができます。例えば、次のコードは、カーソルが子ウィジェット(dropFrame)の上にあるとき、提案されたドロップアクションを受け入れます:

void Window::dragMoveEvent(QDragMoveEvent *event)
{
    if (event->mimeData()->hasFormat("text/plain")
        && event->answerRect().intersects(dropFrame->geometry()))

        event->acceptProposedAction();
}

dragMoveEvent() は、ドラッグ・アンド・ドロップ操作中に視覚的なフィードバックを与えたり、ウィンドウをスクロールさせたりする必要がある場合にも使用できます。

クリップボード

アプリケーション同士は、クリップボードにデータを置くことで通信することもできます。これにアクセスするには、QApplication オブジェクトからQClipboard オブジェクトを取得する必要があります。

QMimeData クラスは、クリップボードとの間で転送されるデータを表現するために使用されます。クリップボードにデータを配置するには、一般的なデータ型用の便利関数 setText()、 setImage()、および setPixmap() を使用します。これらの関数は、QMimeData クラスにある関数と似ていますが、データの格納場所を制御する追加の引数を取る点が異なります:Clipboard が指定された場合、データはクリップボードに置かれます。Selection が指定された場合、データはマウス選択範囲に置かれます(X11のみ)。デフォルトでは、データはクリップボードに置かれます。

例えば、以下のコードでQLineEdit の内容をクリップボードにコピーできる:

QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);

異なるMIMEタイプのデータもクリップボードに置くことができる。前のセクションで説明した方法で、QMimeData オブジェクトを構築し、setData() 関数でデータを設定します。このオブジェクトは、setMimeData() 関数でクリップボードに置くことができます。

このオブジェクトは、QClipboard ()関数でクリップボードに置くことができます。 クラスは、dataChanged ()シグナルを介して、それが含むデータの変更をアプリケーションに通知することができます。例えば、このシグナルをウィジェットのスロットに接続することで、クリップボードを監視することができます:

    connect(clipboard, &QClipboard::dataChanged,
            this, &ClipWindow::updateClipboard);

このシグナルに接続されたスロットは、クリップボードのデータを表すために使用できるMIMEタイプの1つを使用して、クリップボードのデータを読み取ることができます:

void ClipWindow::updateClipboard()
{
    mimeTypeCombo->clear();

    QStringList formats = clipboard->mimeData()->formats();
    if (formats.isEmpty())
        return;

    for (const auto &format : formats) {
        QByteArray data = clipboard->mimeData()->data(format);
        // ...
    }

selectionChanged() シグナルは、X11でマウス選択を監視するために使用できます。

他のアプリケーションとの相互運用

X11 では、パブリックなXDND プロトコルが使用され、Windows では、Qt は OLE 標準を使用し、macOS では、Qt for macOS は Cocoa Drag Manager を使用します。X11では、XDNDはMIMEを使用するので、翻訳は必要ありません。Qt APIはプラットフォームに関係なく同じです。Windowsでは、MIME対応アプリケーションは、MIMEタイプであるクリップボードフォーマット名を使用して通信できます。Windowsアプリケーションの中には、すでにクリップボードフォーマットにMIME命名規則を使っているものもあります。

独自のクリップボード形式を変換するためのカスタムクラスは、Windows ではQWindowsMimeConverter を、macOS ではQUtiMimeConverter を再実装することで登録できます。

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。