This code will only show same image in the same row. How can i pass different path for image in ImageDelegate? Thanks
class testQT4(QtGui.QTableView):
def __init__(self, parent=None):
QtGui.QTableView.__init__(self, parent)
self.setItemDelegateForColumn(1, ImageDelegate(parent))
#table header
header = [ 'ID','image']
tabledata = [[1,2],[3,4]]
#create table model
self.model = MyTableModel(tabledata, header, self)
#set table model
self.setModel(self.model)
class ImageDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent):
print dir(self)
QtGui.QStyledItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
painter.fillRect(option.rect, QtGui.QColor(191,222,185))
# path = "path\to\my\image.jpg"
self.path = "image.bmp"
image = QtGui.QImage(str(self.path))
pixmap = QtGui.QPixmap.fromImage(image)
pixmap.scaled(50, 40, QtCore.Qt.KeepAspectRatio)
painter.drawPixmap(option.rect, pixmap)
In the paint method of the delegate you have access to the model via index.model(). You could then query your model for the data (image) you want to show. For example by using the Qt.UserRole for the data function of the model.
Another solution, which might even be easier, is that the data function of the model can return one of QIcon, QPixmap, QImage and QColor for the Qt.DecorationRole. In this case no Delegate is necessary.
As an example the following code will put in Icon in the (only) field of the table:
from PyQt4 import QtGui, QtCore
import PyQt4.uic
# using QtDesigner to just put a TableView in a Widget
Form, Base = PyQt4.uic.loadUiType(r'TableView.ui')
class TableModel( QtCore.QAbstractTableModel ):
def __init__(self, parent=None):
super(TableModel,self).__init__(parent)
def rowCount(self, parent=QtCore.QModelIndex()):
return 1
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if index.isValid():
if role==QtCore.Qt.DecorationRole:
return QtGui.QIcon("ChipScope.png")
return None
class TableViewUi(Form, Base ):
def __init__(self, parent=None):
Form.__init__(self)
Base.__init__(self,parent)
def setupUi(self, parent):
Form.setupUi(self,parent)
model = TableModel()
self.tableView.setModel(model)
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = TableViewUi()
ui.setupUi(ui)
MainWindow.setCentralWidget(ui)
MainWindow.show()
sys.exit(app.exec_())
Related
I am using QCompleter to implement auto-completion on a QLineEdit widget:
from PySide2 import QtGui
from PySide2.QtCore import Qt
from PySide2.QtGui import QStandardItem
from PySide2.QtWidgets import QCompleter, QWidget, QLineEdit, QFormLayout, QApplication
class SuggestionPlaceModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(SuggestionPlaceModel, self).__init__(parent)
def search(self, text):
self.clear()
data = [{'text': f"{text} {i}"} for i in range(10)]
for i, row in enumerate(data):
item = QStandardItem(row['text'])
self.appendRow(item)
class Completer(QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionPlaceModel(self)
completer = Completer(self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setModel(self._model)
lineedit = QLineEdit()
lineedit.setCompleter(completer)
lay = QFormLayout(self)
lay.addRow("Location: ", lineedit)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Here is a result:
QUESTION: How can I customize the SuggestionPlaceModel class so that the search result can include icons, horizontal separaters, different fonts, different font sizes, etc like this?
A possible solution is to use a custom delegate where the icon is set, in the case of html you can use a QLabel that supports rich text.
import random
from PySide2 import QtCore, QtGui, QtWidgets
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.text = ""
def paint(self, painter, option, index):
if isinstance(option.widget, QtWidgets.QAbstractItemView):
option.widget.openPersistentEditor(index)
super(Delegate, self).paint(painter, option, index)
def createEditor(self, parent, option, index):
editor = QtWidgets.QLabel(index.data(), parent)
editor.setContentsMargins(0, 0, 0, 0)
editor.setText(index.data(QtCore.Qt.UserRole))
return editor
class SuggestionPlaceModel(QtGui.QStandardItemModel):
def search(self, text):
self.clear()
for i in range(10):
html = f"{text}-{i} <b>Stack</b> <i>Overflow</i>"
plain = QtGui.QTextDocumentFragment.fromHtml(html).toPlainText()
pixmap = QtGui.QPixmap(128, 128)
pixmap.fill(QtGui.QColor(*random.sample(range(255), 4)))
item = QtGui.QStandardItem(plain)
item.setData(html, QtCore.Qt.UserRole)
item.setIcon(QtGui.QIcon(pixmap))
self.appendRow(item)
class Completer(QtWidgets.QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionPlaceModel(self)
completer = Completer(self)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
completer.setModel(self._model)
delegate = Delegate(completer.popup())
completer.popup().setItemDelegate(delegate)
lineedit = QtWidgets.QLineEdit()
lineedit.setCompleter(completer)
lay = QtWidgets.QFormLayout(self)
lay.addRow("Location: ", lineedit)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm trying to implement a function to move nodes by dragging in a single tree (QStandardItemModel, PyQt5, Python). My nodes are classes created by multiple inheritance like class Node(A, QStandardItem). When I drag and drop this node, only properties from QStandardItem parent class are moved, everything from the class A is lost.
Here is minimal working example:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setText("Node " + str(self.symbol))
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags```
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = QtWidgets.QTreeView(self)
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_a = Node('a')
self.root_node.appendRow(node_a)
node_b = Node('b')
self.root_node.appendRow(node_b)
node_c = Node('c')
self.root_node.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
In this example, select the node from the tree and click "Print" button - it will print to the console, 'a' for "Node a", 'b' for "Node b" and so on. Then move one node, select it and push "Print" again. The application will crash with the error AttributeError: 'QStandardItem' object has no attribute 'symbol'.
Then I tried to move a node manually by overriding methods mimeData and dropMimeData. I saved row and column indexes in mimeData and tried to get this node from the index in dropMimeData to move it. But this doesn't work because the index has changed meanwhile.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setText("Node " + str(self.symbol))
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
def mimeData(self, indexes) -> QtCore.QMimeData:
m_data = super().mimeData(indexes)
if (m_data):
r = indexes[0].row()
c = indexes[0].column()
obj = self.itemFromIndex(indexes[0])
print(f"row:{r}, column:{c}, type:{type(obj)}, ind:{indexes[0]}")
m_data.setData('row', QByteArray.number(indexes[0].row()))
m_data.setData('col', QByteArray.number(indexes[0].column()))
return m_data
def dropMimeData(self, data: QtCore.QMimeData, action: QtCore.Qt.DropAction, row: int, column: int,
parent: QtCore.QModelIndex) -> bool:
if data is None or action != QtCore.Qt.MoveAction:
return False
_row = data.data('row').toInt()[0]
_col = data.data('col').toInt()[0]
old_index = self.index(_row, _col)
current_index = parent
old_item = self.takeItem(old_index.row(), old_index.column())
parent_item = self.itemFromIndex(parent)
parent_item.appendRow(old_item)
return True
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = QtWidgets.QTreeView(self)
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_a = Node('a')
self.root_node.appendRow(node_a)
node_b = Node('b')
self.root_node.appendRow(node_b)
node_c = Node('c')
self.root_node.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
In this example the tree will break.
I wonder is there a way to move the node without destroying it. It seems to me a wrong way to recreate the object (in mimeData() and dropMimeData()) when it's only needed to change the index.
So, the questions are: how to implement this move correctly, and is it possible without destroying the node (it can be a member of some list for example)?
Since nobody replied, I post an answer to my own question (maybe it's not perfect but it works).
First, I found that my design doesn't fit Qt's drag-n-drop design. One way is not to subclass an item using multiple inheritance (like class Node(A, QStandardItem)) but rather to use composition and keep class A within QStandardItem using roles. Then no need to override any methods.
In my case nodes are using multiple inheritance so I did override dropEvent method and move row (extract it from the model and insert in the new place). It's very simple example just for demonstration, so no checks what kind of object is dropping and so on.
If somebody has better idea - you're welcome to comment or write your own solution
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setData(symbol, QtCore.Qt.UserRole)
self.setText("Node " + str(self.symbol))
def get_sym(self):
return self.data(QtCore.Qt.UserRole)
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(MyTreeView, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragEnabled(True)
def dropEvent(self, event):
index = self.indexAt(event.pos())
model = self.model()
dest_node = model.itemFromIndex(index)
if dest_node is None:
return
source_index = self.currentIndex()
source_node = model.itemFromIndex(source_index)
source_node: Node
sourse_parent = source_node.parent()
taken_row = sourse_parent.takeRow(source_index.row())
dest_parent = dest_node
if dest_node != sourse_parent:
dest_parent = dest_node.parent()
if dest_parent is None:
dest_parent = dest_node
dest_parent.insertRow(index.row(), taken_row)
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = MyTreeView(self)# QtWidgets.QTreeView(self)#
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_1 = Node('1')
self.root_node.appendRow(node_1)
node_2 = Node('2')
self.root_node.appendRow(node_2)
node_d = Node('d')
node_2.appendRow(node_d)
node_a = Node('a')
font = QtGui.QFont()
font.setBold(True)
node_a.setFont(font)
node_1.appendRow(node_a)
node_b = Node('b')
node_1.appendRow(node_b)
node_c = Node('c')
node_1.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def drop(self):
print('drop')
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
I'm trying to wrap my head around some ModelView on PyQT5.
I have a QListView, which can display data stored in an QAbstractListModel.
But i'd like to have each line of my QListView displaying a complex Widget created in QDesigner.
I've created a widget with a QLabel, a spacer, and a QPushButton.
I'd like to have each elements of my QListView using this widget to display my model data
Here is the basic code with a simple ModelView QListView
import typing
from PyQt5 import QtCore
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView
class MyListModel(QtCore.QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self.data_list = []
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role == Qt.DisplayRole:
return self.data_list[index.row()]
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self.data_list)
data = ["emotion", "unliving", "brutally", "torch", "donut", "comet"]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
layout = QWidget()
layout.setLayout(QVBoxLayout())
list_view = QListView()
model = MyListModel()
model.data_list = data
list_view.setModel(model)
layout.layout().addWidget(list_view)
layout.resize(640, 480)
layout.show()
sys.exit(app.exec_())
I've tried to create a QStyledItemDelegate, and set the delegate on the model, but i can't make it work.
class LineDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.line = Ui_Form()
self.line.setupUi(parent)
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# Not sure what to put here
The doc seems to said that a Delegate should be used to edit data inside the view, not having a complex widget view.
https://doc.qt.io/qtforpython/overviews/model-view-programming.html#a-simple-delegate
Is there anything possible to have what i want ?
I'm really not sure how to approch this.
You must use a delegate so that you create an editor for each item, and then use the openPersistentEditor method to open the editors, it is not necessary to override the paint method.
import typing
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt5.QtWidgets import (
QApplication,
QListView,
QStyledItemDelegate,
QVBoxLayout,
QWidget,
)
class Form(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = []
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
if role == Qt.DisplayRole:
return self._data_list[index.row()]
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self._data_list)
#property
def data_list(self):
return self._data_list
#data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class Delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
return Form(parent)
"""def setEditorData(self, editor, index):
editor.label.setText(index.data())"""
data = ["emotion", "unliving", "brutally", "torch", "donut", "comet"]
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
list_view = QListView()
model = MyListModel()
model.data_list = data
list_view.setModel(model)
delegate = Delegate(list_view)
list_view.setItemDelegate(delegate)
for i in range(model.rowCount()):
index = model.index(i, 0)
list_view.openPersistentEditor(index)
container = QWidget()
layout = QVBoxLayout(container)
layout.addWidget(list_view)
container.resize(640, 480)
container.show()
sys.exit(app.exec_())
I am trying to add drag and drop functionality to a small application. Getting data from a QlistWidget and Dropping the data on a QTableWidget. I should override the dropEvent of QTableWidget in order to add some other functions when dropping the data. But i have trouble, i think i can not get the text() of the object gotten from the ListWidget. here is the code:
class Table(QtWidgets.QTableWidget):
def __init__(self,r,c, parent=None):
super().__init__(r,c,parent)
self.init_ui()
def init_ui(self):
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
"""def dragMoveEvent(self, e):
e.setDropAction(QtCore.Qt.MoveAction)
e.accept()
def dragEnterEvent(self,e):
e.accept()"""
def dropEvent(self,e):
data = e.mimeData()
a=e.pos()
row = self.rowAt(a.y())
col = self.columnAt(a.x())
self.setItem(row,col,QtWidgets.QTableWidgetItem(data.text()))
print(row,col)
print(type(data.text()))
print(e.source())
x = data.text()
print(x)
e.accept()
`
The data that is transmitted from a QListWidget through the drag-and-drop is not given through text(), because an item has much more information identified by the roles, in addition you can drag several items. The data is transmitted using the MIME type application/x-qabstractitemmodeldatalist and the solution is to decode it as shown below:
from PyQt5 import QtCore, QtWidgets
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, r,c, parent=None):
super(TableWidget, self).__init__(r,c, parent)
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
def dropEvent(self, event):
md = event.mimeData()
fmt = "application/x-qabstractitemmodeldatalist"
if md.hasFormat(fmt):
encoded = md.data(fmt)
stream = QtCore.QDataStream(encoded, QtCore.QIODevice.ReadOnly)
table_items = []
while not stream.atEnd():
# row and column where it comes from
row = stream.readInt32()
column = stream.readInt32()
map_items = stream.readInt32()
it = QtWidgets.QTableWidgetItem()
for i in range(map_items):
role = stream.readInt32()
value = QtCore.QVariant()
stream >> value
it.setData(role, value)
table_items.append(it)
for it in table_items:
print(it, it.text())
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
list_widget = QtWidgets.QListWidget()
list_widget.setAcceptDrops(False)
list_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
list_widget.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
for i in range(10):
it = QtWidgets.QListWidgetItem("item-{}".format(i))
list_widget.addItem(it)
table_widget = TableWidget(5, 10)
central_widget = QtWidgets.QWidget()
hlay = QtWidgets.QHBoxLayout(central_widget)
hlay.addWidget(list_widget)
hlay.addWidget(table_widget)
self.setCentralWidget(central_widget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I struggle to drag and drop icons from a listview (ViewMode set to IconMode) to another. It's similar to what I've found in the docs.
Scenario: A user drags the QIcon 1 from ListView 1 and drops it to ListView 2. ListView 2 should add QIcon 1 to its model. Moreover I want to do some background work when a specific QIcon is added to ListView 2. How do I know that QIcon 1 was dropped to ListView 2 and not QIcon 2?
mainwindow (sets up the layout, loads the images into listview 1):
class Ui_MainWindow(object):
...
def loadImages(self):
model = QStandardItemModel()
images = Path("images").glob("*.*")
for image in images:
item = QStandardItem()
item.setIcon(QIcon(str(image)))
model.appendRow(item)
self.listView1.setModel(model)
listview 1:
class ListView1(QListView):
def __init__(self):
super().__init__()
self.setAcceptDrops(False)
self.setViewMode(QtWidgets.QListView.IconMode)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setIconSize(QSize(50, 50))
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setDragDropMode(QAbstractItemView.DragOnly)
listview 2:
class ListView2(QListView):
def __init__(self):
super().__init__()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setDragDropMode(QAbstractItemView.DropOnly)
self.setIconSize(QSize(50, 50))
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
event.accept()
def dragMoveEvent(self, event):
event.accept()
def dropEvent(self, event):
event.accept()
event.setDropAction(QtCore.Qt.MoveAction)
event.acceptProposedAction()
How can I drag and drop an icon from listview 1 to listview 2 and access its properties?
It is not necessary to overwrite dragEnterEvent, dragMoveEvent or dropEvent since those implementations already exist and work correctly, the example you point out is for other types of widgets that do not have those events implemented.
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets
class LListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(LListView, self).__init__(parent)
self.model = QtGui.QStandardItemModel(self)
self.setModel(self.model)
self.setAcceptDrops(False)
self.setViewMode(QtWidgets.QListView.IconMode)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setIconSize(QtCore.QSize(50, 50))
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
class RListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(RListView, self).__init__(parent)
self.model = QtGui.QStandardItemModel(self)
self.setModel(self.model)
self.setAcceptDrops(True)
self.setViewMode(QtWidgets.QListView.IconMode)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setIconSize(QtCore.QSize(50, 50))
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.l_view = LListView()
self.r_view = RListView()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
lay.addWidget(self.l_view)
lay.addWidget(self.r_view)
self.loadImages()
def loadImages(self):
images = Path("images").glob("*.*")
for image in images:
item = QtGui.QStandardItem()
item.setIcon(QtGui.QIcon(str(image)))
self.l_view.model.appendRow(item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Update:
If you want to add an identifier this can be done through a role that you pass when creating the item, and then in the dropEvent() get all the roles, then get the role you want and through it the identifier:
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets
class LListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(LListView, self).__init__(parent)
self.m_model = QtGui.QStandardItemModel(self)
self.setModel(self.m_model)
self.setAcceptDrops(False)
self.setViewMode(QtWidgets.QListView.IconMode)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setIconSize(QtCore.QSize(50, 50))
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
class RListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(RListView, self).__init__(parent)
self.m_model = QtGui.QStandardItemModel(self)
self.setModel(self.m_model)
self.setAcceptDrops(True)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setIconSize(QtCore.QSize(50, 50))
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
def dropEvent(self, event):
last_row_count = self.model().rowCount()
super(RListView, self).dropEvent(event)
# check if an item is added
if self.model().rowCount() > last_row_count:
md = event.mimeData()
fmt = "application/x-qabstractitemmodeldatalist"
if md.hasFormat(fmt):
encoded = md.data(fmt)
stream = QtCore.QDataStream(encoded, QtCore.QIODevice.ReadOnly)
datas = []
item = {}
while not stream.atEnd():
row = stream.readInt32()
column = stream.readInt32()
map_items = stream.readInt32()
for i in range(map_items):
key = stream.readInt32()
value = QtCore.QVariant()
stream >> value
item[QtCore.Qt.ItemDataRole(key)] = value
datas.append(item)
for data in datas:
identifier = data[QtCore.Qt.UserRole+1].value()
print("identifier: ", identifier)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.l_view = LListView()
self.r_view = RListView()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
lay.addWidget(self.l_view)
lay.addWidget(self.r_view)
self.loadImages()
def loadImages(self):
images = Path("images").glob("*.*")
for i, image in enumerate(images):
item = QtGui.QStandardItem()
identifier = "img_{:06d}".format(i+1)
item.setData(identifier, QtCore.Qt.UserRole+1)
item.setIcon(QtGui.QIcon(str(image)))
self.l_view.m_model.appendRow(item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())