2012年1月29日日曜日

( Qt C++ )カスタムウィジットでドラック&ドロップ(QListWidget派生)


はいそれでは表題の件やっていきます。
今回は同アプリケーションのウィジット同士のドラッグ&ドロップの作成となります。

サンプルはC++ GUI Programming with Qt4 207ページのQListWidget派生のサンプルをほぼそのまま使用します。
そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)

※また、Qt Createrのデザイナで派生ウィジットを使いたい場合があるかと思います。その場合は「Qtをはじめよう!:独自ウィジェットを作成しデザイナで使用しよう 」の記事が役に立つかと思います。そちらを参考にしてください。

ではコードを

(custumlist.h)
#include <QListWidget>
#include <QMouseEvent>
#include <QApplication>

class CustumList : public QListWidget
{
    Q_OBJECT//マクロ

public:
    explicit CustumList(QWidget *parent = 0);//コンストラクタ

protected:
    void mousePressEvent(QMouseEvent *e);//マウス左をクリックしたら呼ばれる
    void mouseMoveEvent(QMouseEvent *e);//マウスカーソルを移動したら呼ばれる
    void dragEnterEvent(QDragEnterEvent *e);//ドラッグされたものが来たら呼ばれる
    void dragMoveEvent(QDragMoveEvent *e);//ドラッグ&ドロップが操作されている間呼ばれる
    void dropEvent(QDropEvent *e);//ドロップの動作

private:
    void startDrag();//自分のウィジットでドラッグを開始した場合
    QPoint startPos;//マウスの位置
};

(custumlist.cpp)
#include "custumlist.h"

CustumList::CustumList(QWidget *parent) :
    QListWidget(parent)//コンストラクタ
{
    //ドラッグ&ドロップを受け付ける
    //trueで許可。
    setAcceptDrops(true);
}

void CustumList::mousePressEvent(QMouseEvent *e)
{
    //マウス左ボタンをクリックしたら
    if(e->button() == Qt::LeftButton)
    {
        //そのマウスの位置を保存
        startPos = e->pos();
    }

    //デフォルトの動作へ
    QListWidget::mousePressEvent(e);
}

void CustumList::mouseMoveEvent(QMouseEvent *e)
{
    ////マウス左ボタンをクリック中なら
    if(e->buttons() & Qt::LeftButton)
    {
        //保存していたマウスの位置と現在のマウスの位置の差を求める
        //manhattanLengthは絶対値を求めるもの
        int distance = (e->pos() - startPos).manhattanLength();

        //上で求めた差分とアプリで設定してあるドラッグ&ドロップを開始する差分
        //を比べ、それ以上ならドラッグを開始する。
        //なおstartDragDistance()はデフォルトでは4が返る
        //変更はsetStartDragDistance(int)
        if(distance >= QApplication::startDragDistance())
        {
            startDrag();
        }

        QListWidget::mouseMoveEvent(e);
    }
}

void CustumList::startDrag()
{
    //現在選択中のアイテム
    QListWidgetItem *item = currentItem();

    if (item)
    {
        QMimeData *mimeData = new QMimeData;
        mimeData->setText(item->text());

        //ドラッグ中のアイコンのセット
        QDrag *drag = new QDrag(this);
        drag->setMimeData(mimeData);
        drag->setPixmap(QPixmap("//home//ubuntu001//icon.png"));

        //ドラッグ処理の開始。
        //startはドラック&ドロップが終わるまで処理が返らない
        if (drag->start(Qt::MoveAction) == Qt::MoveAction)
            delete item;
    }
}

void CustumList::dragEnterEvent(QDragEnterEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());
    
    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        e->setDropAction(Qt::MoveAction);
        e->accept();
    }
}

void CustumList::dragMoveEvent(QDragMoveEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());
    
    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        e->setDropAction(Qt::MoveAction);
        e->accept();
    }
}

void CustumList::dropEvent(QDropEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());
    
    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        //自分のアイテムへドロップされたものを追加
        addItem(e->mimeData()->text());
        e->setDropAction(Qt::MoveAction);
        e->accept();
    }
}

はいややこしいですね。ヘッダ部は見ての通りです。説明は省略します。

.cppは順に説明します。
まずコンストラクタ。ここではただドラッグ&ドロップを受け付けるよう設定しているだけです。(setAcceptDrops

次はmousePressEventここではマウスが左ボタンを押したことを確認し、もし押していたらマウスの位置を変数に保存しています。これはmouseMoveEventで使用します。
(QMouseEventリファレンス

次にmouseMoveEventです。ここでも左クリックされていることを確認し、されているなら現在のマウスの位置とmousePressEvent時に保存したときのマウスの位置との差を求め、その絶対値がデフォルトで設定されているドラック&ドロップを開始する差分以上であるかを比較し、以上ならドラッグを開始します。これは手の振るえだとかの理由でドラッグを開始させないように差分を比較しているのだそうです。
(QMouseEventリファレンス

startDragでは現在選択中のアイテムを使ってドラッグ&ドロップするMIMEデータを作成したり、ドラッグ時のアイコンの設定などを行い。drag->startでドラッグ処理を開始します。(QMimeDataリファレンス)

dragEnterEventでは自分以外からのドラッグ&ドロップなら許可するようにしています。
(QDragEnterEventリファレンス

dragMoveEventも同じく自分以外からのドラッグ&ドロップなら許可するようにしています。
(QDragMobeEventリファレンス

最後にdropEventですが、自分以外のドラッグ&ドロップならアイテムを追加しています。
(QDropEventリファレンス


このCustumListを2つメインウィンドウに配置し適当な初期値をセットして実行したものが以下のものです。


(ドラッグ&ドロップ中。独自のアイコンが表示されている)
 

(ドラッグ&ドロップを終えた時)
 

以上です。

※ちなみにこのCustumListへのアイテムの追加はQListWidgetと同じです。以下のような感じで私は追加しました。
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//uiにはGUI部品を記述(CustumListもこの中に記述)
{
    ui->setupUi(this);//uiのGUI部品の初期化

    //ui->listWidgetはCustumListです。
    ui->listWidget->addItem(new QListWidgetItem("Oak"));
    ui->listWidget->addItem(new QListWidgetItem("Banana"));
    ui->listWidget->addItem(new QListWidgetItem("Apple"));
    ui->listWidget->addItem(new QListWidgetItem("Orange"));
    ui->listWidget->addItem(new QListWidgetItem("Grapes"));
    ui->listWidget->addItem(new QListWidgetItem("Jayesh"));
}