ドラッグ&ドロップをもっと細かく!
以前ファイルをドラッグ&ドロップするでQTreeViewからアイテムをドラッグ&ドロップする方法をメモりましたが、今回はドラッグ時の操作をもっと細かく制御したい場合の方法です。
忘れそうになるんで、今回もメモっときます。
ファイルをドラッグ&ドロップするでは主にドロップされる側の方がメインでしたが、今回はドロップする側がメインの話になります。
まずはサンプルコードから。
# -*- coding:utf-8 -*- import sys import os from PySide import QtGui, QtCore RootDirPath = '###任意のディレクトリパス###' class DraggableView(QtGui.QListView): IconSize = QtCore.QSize(128, 128) # アイコンサイズ def __init__(self, parent=None): super(DraggableView, self).__init__(parent) self.setWindowTitle('Draggable File View') self.resize(300, 600) self.setAlternatingRowColors(True) self.setSelectionMode(self.ExtendedSelection) self.start_pos = None # modelを作成し、アイテムを追加する。================================== model = QtGui.QStandardItemModel(0, 1) # 変数RootDirPathディレクトリ下のファイルを一覧する。 for file in os.listdir(RootDirPath): item = QtGui.QStandardItem(file) # ファイルのフルパスはアイテムのdataの方に確保しておく。 item.setData(os.path.join(RootDirPath, file)) model.setItem(model.rowCount(), 0, item) self.setModel(model) # ===================================================================== # itemSelectionModelを追加する。======================================= sel_model = QtGui.QItemSelectionModel(model) self.setSelectionModel(sel_model) # ===================================================================== def mousePressEvent(self, event): u''' マウスをクリックした時の挙動をカスタマイズする。 ''' if event.buttons() == QtCore.Qt.RightButton: # マウスの右ボタンの時のみドラッグ処理の準備を行う。 # マウスクリック時の場所を覚えておく。 self.start_pos = event.pos() return # 右ボタン以外の場合は親クラスの処理を行う。 super(DraggableView, self).mousePressEvent(event) def startToDrag(self): u''' ドラッグ操作を開始する。その後ドラッグの結果を返す。 ''' # 選択アイテムからファイルパスの一覧を作成する。======================= # ついでにQFileIconProviderクラスを使用してアイコンも取得する。 provider = QtGui.QFileIconProvider() filelist = [] icons = [] for index in self.selectionModel().selectedIndexes(): # QModelIndexであるindexから24行目でsetDataしたデータを取得する。 local_filepath = index.data(QtCore.Qt.UserRole+1) icon = provider.icon(local_filepath) icons.append(icon.pixmap(self.IconSize)) # 一度URL形式に変換してからfilelistに追加する。 filelist.append(QtCore.QUrl.fromLocalFile(local_filepath)) # ===================================================================== # ファイルパスのリストをmimeDataにURLとしてセットする。 mimedata = QtCore.QMimeData() mimedata.setUrls(filelist) # ドラッグオブジェクトを生成する。===================================== drag = QtGui.QDrag(self) # 先程作成したmimeDataをセットする。 drag.setMimeData(mimedata) # 先で取得したアイコンの0番目を表示用アイコンとしてセットする。 drag.setPixmap(icons[0]) # ドラッグを開始し、その結果を返す。 return drag.exec_(QtCore.Qt.CopyAction|QtCore.Qt.MoveAction) # ===================================================================== def removeSelectedIndexes(self): ''' 選択されたアイテムをビューから削除する。 ''' model = self.model() indexes = self.selectionModel().selectedIndexes() indexes.sort() indexes.reverse() for index in indexes: model.removeRow(index.row()) def mouseMoveEvent(self, event): u''' マウスをドラッグしている際の挙動をカスタマイズする。 ''' if event.buttons() != QtCore.Qt.RightButton: # マウスの右ボタン以外の場合は親クラスの処理を行い終了する。 super(DraggableView, self).mouseMoveEvent(event) return if ( (event.pos() - self.start_pos).manhattanLength() < QtGui.qApp.startDragDistance() ): # マウスをクリックした位置から一定の距離以内の場合は何もしない。 return dragged_result = self.startToDrag() if dragged_result == QtCore.Qt.MoveAction: # ドラッグの処理が移動だった場合、リストから選択アイテムを削除する。 self.removeSelectedIndexes() if __name__ == '__main__': app = QtGui.QApplication(sys.argv) view = DraggableView() view.show() sys.exit(app.exec_())
こんな感じになります。
5行目のRootDirPathにディレクトリパスを設定しておくと、そのディレクトリ内のファイルを一覧で表示します。
一覧で表示されたアイテムを選択してどこかへドラッグすると、そのファイルがドラッグ先に移動します。
※この操作で実際に中のファイルが移動、コピーされてしまうので取扱には重々注意!!
尚、これよって起こった如何なるトラブルも当方では責任を持てませんので、自己責任にて行って下さい。
尚、これよって起こった如何なるトラブルも当方では責任を持てませんので、自己責任にて行って下さい。
今回のポイントはここ!!
■再実装するメソッド。
- 35行目 : mousePressEvent(self, event)
- 93行目 : mouseMoveEvent(self, event)
前回のD&Dではドラッグ側ではやらなかった、メソッドの再実装です。
mousePressEventはマウスをUI上でクリックされた時に呼ばれるメソッドです。
今回は右クリックした場合のみ処理を行うようにし、その際event.pos()メソッドでカーソル位置をQtCore.QPointの形で格納しておきます。
mouseMoveEventはマウスをUI上でドラッグされている時に呼ばれ続けるメソッドです。
102行目~107行目でマウスクリック時の位置とドラッグ中の位置の長さを図り、それがqAppのstartDragDistanceの値と比較して小さい場合は何もしないようになっています。
こうする事によって、ペンタブなど単に右クリックしただけのつもりでもカーソルがぶれて動いてしまうようなディバイスでも簡単にはドラッグされないようになります。
この処理がないと右クリック時に1ピクセルもカーソルを動かすとドラッグ扱いになってしまうので、右クリックとドラッグを併用するUIの場合は必ずいれておいてあげましょう。
■ドラッグをカスタマイズする!
・ 47行目 : startToDrag(self)
ここではいよいよドラッグ動作をカスタマイズします。
と言ってもやり方は超簡単で、QDragを新たに作って(72行目)そこにmimeDataをぶっ込んで(74行目)exec_する(78行目)だけ!!
他にもアイコンを設定したり(76行目)も出来ます。
exec_を実行するとドラッグ動作に入ります。
ドラッグ動作を終了すると、このexec_はどういったドロップ動作を行ったかをQt.DropActionという形で返してくれます。
定数 | 値 | 説明 |
---|---|---|
Qt.CopyAction | 0x1 | データを対象へコピー |
Qt.MoveAction | 0x2 | 元データを対象へ移動 |
Qt.LinkAction | 0x4 | 元データのリンクを対象内に作成 |
Qt.ActionMask | 0xff | |
Qt.IgnoreAction | 0x0 | アクションを無視(データにたいして何も行わない) |
Qtのサイトより一部抜粋し日本語訳化およびPython風記述に変更。
・ 109行目 : dragged_result = self.startToDrag()
・ 110行目 : if dragged_result == QtCore.Qt.MoveAction:
上のstartToDragではQDrag.exec_の戻り値を返しているので、それDropActionのいずれかに該当するかを判断し、それに応じて処理を変更しています。
今回の例では、ドラッグされたアイテムが移動処理と言う扱いだった場合、リスト内からそのアイテム削除して、ファイルブラウザからあたかもデータが移動したかのような挙動になるようにしています。
実はQTreeViewなどのデフォルトのD&Dでは、一部のブラウザアプリなどにドロップした時に移動扱いになってデータがリストから消えてしまう事があったのですが、自前のドラッグに変更する事で対処する事ができます。(なんでデフォルトでバグってんねん・・・)
■使い方は無限大!?
このカスタマイズを利用すれば色々悪巧みができますw
例えば自前のファイルブラウザを作って、それを使ってD&Dした時にファイルの移動履歴を取っておいたりなんかも出来ますね。
QDragを自前で用意することによって、D&D時にShiftキーを押すとファイルの移動になって場合も追いかける事が出来るので、このような芸当も簡単になります。
実装はちょっと面倒ですが、まぁコピペしとけばなんとかなるレベルなんで、ちょっと標準のD&Dでは困る時に如何でしょう?
スポンサーサイト