[SOLVED] Drag and Drop の動作,実装方法など
-
特にQGraphicsView周りのD&Dイベント処理がややこしかったので覚書。
Qt4.7.2,Windows7Homeで確認。その他の環境は未確認。
acceptする,しないはevent->accept()をコールする,しないの意味。
実装方法は,各クラスを継承した自作クラスで(一つのクラスのみで)D&Dを実装する方法。※※※ 筆者は残念なプログラマなので信憑性もそれに順ずる。
-
QWidget:
acceptDrops既定値はfalse。acceptDropsをtrueにしないとハンドラが呼ばれない。
TopLevelWidgetかつ子WidgetにacceptDropsがtrueのWidgetを持つ場合は
Window表示時に自動でsetAcceptDrops(true)される。
dragEnterEventでacceptしないとその他のハンドラが呼ばれない。
dragMoveEventは処理しなくても良い。
* 実装方法:
dragEnterEventでaccept,
dropEventでdrop時の処理を実装して,acceptDropsをtrueにする。 -
QGraphicsView:
acceptDrops既定値はtrue。acceptDropsをfalseにすればハンドラは呼ばれない。
Sceneがセットされていない時はQWidgetと同じ。Sceneがセットされると以下の動作をする。
dragEnterEventでacceptしなくてもdragMoveEventは呼ばれる。
各ハンドラではイベントをScene用にマッピングしてSceneに渡す。
dragMoveEventではSceneの処理結果でacceptを上書き(setAccept)する。
結果,dragEnterEventでacceptしてもdragMoveEventで上書きされてしまう。
* 実装方法1(ViewのみでD&Dを受付け,Sceneにイベントを渡さない):
dragEnterEvent,dragMoveEvent,dragLeaveEvent,dropEventを,基底クラスの関数を呼ばないように上書きする。
あとはQWidgetと同じ。
* 実装方法2(Viewの全域でD&Dを受け付けるがSceneにもイベントを渡す):
dragMoveEventで基底クラスの関数を呼んだ後にacceptする。
dropEventではdrop時の処理を実装し,基底クラスの関数を呼ぶ。 -
QGraphicsScene
各ハンドラはViewを経由して呼び出される。
Sceneにポジションマッピング済みのイベントを受け取るので,Scene上の位置が必要ならこちらが便利。
dragMoveEventではItemのacceptDropsがtrueの場合のみ
ItemのdragEnterEvent,dragMoveEvent,dragLeaveEventを呼び出す。
有効なItemが無ければeventはisAcceptedはfalseかつIgnoreActionをセットされる。
* 実装方法1(SceneのみでD&Dを受付け,Itemにイベントを渡さない):
dragMoveEventでacceptし,基底クラスの関数を呼び出さない。
dropEventではdrop時の処理を実装する(基底クラスの関数は呼んでもItemにイベントは渡らない)。
* 実装方法2(Scene全域でD&Dを受付けるがItemにもイベントを渡す):
dragMoveEventで基底クラスの関数を呼んだ後に
event->accept()とevent->setDropAction(Qt::DropAction)を呼ぶ。
DropActionはCopyActionなら+付き,LinkActionなら矢印付き,MoveActionなら何もつかない四角形の
アイコン(マウスカーソル)になる(Windows7の場合)。
※ Item上ではItem固有のアイコンにする場合は,dropActionがIgnoreActionの場合のみsetDropActionする。 -
QGraphicsItem
acceptDrops既定値はfalse。
acceptDropsをtrueにしておくと,dragEnterEvent又はdragMoveEventでevent->ignoreしない限りdropEventは呼ばれる。
* 実装方法:
dropEventにdrop時の処理を実装して,acceptDropsをtrueにする。
その他:
QGraphicsViewやQTextEditなど,acceptDropsの既定値がtrueのWidgetを置くと
親WidgetでD&Dイベントを受け取れなくなる。
親WidgetでD&Dイベントを処理するならこれらのWidgetにsetAcceptDrops(false)するか,
必要に応じてevent->ignore()するように再定義した派生クラスに変える必要がある。(ignoreすると親Widgetにイベントが渡る)なんとなくまとめ:
QGraphicsView関連では,特定のItemのみD&Dを受付ける場合はItemに,
Scene上のポジションマッピングを使う場合はSceneに,
Sceneごとゴッソリ入れ替える(特にSceneの型を変える)場合はViewに実装するのがいいのかな,と思いました(まる) -
-
QWidgetで動作確認できるサンプル書いてみました。
コード行が少なくなるように書いてるので,コーディングマナーはアレですが。
.proは
SOURCES += d_d_widget.cpp
だけで動くはず。d_d_widget.cpp
@
#include <QtGui>class Widget : public QWidget
{
Q_OBJECT
public:
Widget()
{
setAcceptDrops(true);
setLayout(new QVBoxLayout);
layout()->addWidget(new QLabel(".cppファイルを受付けます。複数ファイルもOK。"));
layout()->addWidget(editor = new QTextEdit);
editor->setAcceptDrops(false);
}
protected:
void dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls())
foreach (QUrl url, event->mimeData()->urls())
if (QFileInfo(url.toLocalFile()).suffix() == "cpp")
{
event->accept();
break;
}
}
void dropEvent(QDropEvent *event)
{
editor->clear();
foreach (QUrl url, event->mimeData()->urls())
if (QFileInfo(url.toLocalFile()).suffix() == "cpp")
open(url);
else
error(url);
}
private:
void open(QUrl url) {editor->append("○ " + QFileInfo(url.toLocalFile()).fileName());}
void error(QUrl url) {editor->append("× ..." + url.path().right(10));}
QTextEdit *editor;
};int main(int argc, char **argv)
{
QApplication app(argc, argv);
QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());
Widget w;
w.show();
return app.exec();
}#include "d_d_widget.moc"
@