D&DでListのItemを移動させる方法!!

takkun
どうも!たっくんです。

今回は、QListWidgetをカスタマイズして、アイテムをD&Dで自由に移動する方法のご紹介です!(`・ω・´)ゞ

クラスの骨格

QListWidgetを継承して、カスタマイズしたクラスを用意していきます!D&Dが受け付けられるように「setAcceptDrops」を設定します。また、D&Dの開始処理のためにインスタンス変数「__startPos」を用意します。

from PySide import QtCore, QtGui
 
class DraggableList(QtGui.QListWidget):
	def __init__(self, *args, **kwargs):
		super(DraggableList, self).__init__(*args, **kwargs)
		self.setAcceptDrops(True)
		self.__startPos = QtCore.QPoint()

マウスがクリックされた時の処理

mousePressEvent」をオーバーライドし、ListWidgetでマウスがクリックされた時の座標を保存します。

from PySide import QtCore, QtGui
 
class DraggableList(QtGui.QListWidget):
	def __init__(self, *args, **kwargs):
		super(DraggableList, self).__init__(*args, **kwargs)
		self.setAcceptDrops(True)
		self.__startPos = QtCore.QPoint()
		
	def mousePressEvent(self, event):
		self.__startPos = event.pos()
		super(DraggableList, self).mousePressEvent(event)

Drag中の処理

mouseMoveEvent」をオーバーライドし、ドラック中の動作をカスタマイズしていきます。詳しくは、各行のコメントをご覧ください(`・ω・´)ゞ

from PySide import QtCore, QtGui
 
class DraggableList(QtGui.QListWidget):
	def __init__(self, *args, **kwargs):
		super(DraggableList, self).__init__(*args, **kwargs)
		self.setAcceptDrops(True)
		self.__startPos = QtCore.QPoint()
		
	def mousePressEvent(self, event):
		self.__startPos = event.pos()
		super(DraggableList, self).mousePressEvent(event)
	
	def mouseMoveEvent(self, event):
		# Dragを開始した地点と、今マウスがある地点の距離を求める
		distance = (event.pos() - self.__startPos).manhattanLength()
		
		# 5px以上動いたら、ドラッグしたとみなす。
		# これをやらないと、ただ選択したいだけでもD&Dになってしまう。
		if distance >= 5:
			
			# 現在のアイテムを取得する
			item = self.currentItem()
			if item:
				# QMimeDataに、アイテムのテキストを設定する
				mimeData = QtCore.QMimeData()
				mimeData.setText(item.text())
				
				# Dragオブジェクトを作成して、QMimeDataを設定する
				drag = QtGui.QDrag(self)
				drag.setMimeData(mimeData)
				
				# Dragが終了したとき、正常にMoveActionになったか確認
				if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:
					# アイテムが移動先にできてるので、元のリストから削除する
					self.takeItem(self.currentRow())

D&Dの許可

D&Dを許可するように「dragEnterEvent」「dragMoveEvent」で、各イベントを「accept」します。D&Dに条件を設けたい場合は、if文で分岐させると良いかと思います!(`・ω・´)ゞ

from PySide import QtCore, QtGui
 
class DraggableList(QtGui.QListWidget):
	def __init__(self, *args, **kwargs):
		super(DraggableList, self).__init__(*args, **kwargs)
		self.setAcceptDrops(True)
		self.__startPos = QtCore.QPoint()
		
	def mousePressEvent(self, event):
		self.__startPos = event.pos()
		super(DraggableList, self).mousePressEvent(event)
	
	def mouseMoveEvent(self, event):
		distance = (event.pos() - self.__startPos).manhattanLength()
		if distance >= 5:
			
			item = self.currentItem()
			if item:
				mimeData = QtCore.QMimeData()
				mimeData.setText(item.text())
				
				drag = QtGui.QDrag(self)
				drag.setMimeData(mimeData)
				if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:
					self.takeItem(self.currentRow())
					
	def dragEnterEvent(self, event):
		event.accept()

	def dragMoveEvent(self, event):
		event.accept()

Dropの処理

Dropの処理では「QMimeData」にテキスト情報がある場合は、ListWidgetにアイテムを追加するようにします。

from PySide import QtCore, QtGui
 
class DraggableList(QtGui.QListWidget):
	def __init__(self, *args, **kwargs):
		super(DraggableList, self).__init__(*args, **kwargs)
		self.setAcceptDrops(True)
		self.__startPos = QtCore.QPoint()
		
	def mousePressEvent(self, event):
		self.__startPos = event.pos()
		super(DraggableList, self).mousePressEvent(event)
	
	def mouseMoveEvent(self, event):
		distance = (event.pos() - self.__startPos).manhattanLength()
		if distance >= 5:
			
			item = self.currentItem()
			if item:
				mimeData = QtCore.QMimeData()
				mimeData.setText(item.text())
				
				drag = QtGui.QDrag(self)
				drag.setMimeData(mimeData)
				if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:
					self.takeItem(self.currentRow())
					
	def dragEnterEvent(self, event):
		event.accept()

	def dragMoveEvent(self, event):
		event.accept()
		
	def dropEvent(self, event):
		if event.mimeData().hasText():
			self.addItem(event.mimeData().text())
			event.accept()

実験!

以下のコードで、実験してみます。

widget = QtGui.QWidget()
widget.setWindowFlags(QtCore.Qt.Window)
widget.resize(640, 240)
widget.setLayout(QtGui.QHBoxLayout())
layout = widget.layout()

listA = DraggableList(widget)
listA.addItem('A')
listA.addItem('B')
listA.addItem('C')
layout.addWidget(listA)
listB = DraggableList(widget)
layout.addWidget(listB)

widget.show()

左右のアイテムをD&Dすると、アイテムを自由に移動することができます!