Issue
I'm trying to do a drag and drop between two items, but I'm having trouble getting the information out of the cell I click on in the table. I have a custom table that has a custom model.
class PyTableWidget(QTableView):
def __init__(self, *args):
super().__init__()
self.setDragEnabled(True)
self.set_stylesheet(*args)
def onLeftClick(self, index):
print('onClick index.row: %s, index.col: %s' % (index.row(), index.column()))
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
drag = QDrag(self)
mime = QMimeData()
drag.setMimeData(mime)
drag.exec(Qt.MoveAction)
Here's the custom model for the table:
class CustomizedNumpyModel(QAbstractTableModel):
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = np.array(data.values)
self._cols = data.columns
self.r, self.c = np.shape(self._data)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
value = self._data[index.row(), index.column()]
if isinstance(value, float):
return "%.2f" % value
if isinstance(value, str):
return '%s' % value
return unicode(value)
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def headerData(self, p_int, orientation, role):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._cols[p_int])
if orientation == Qt.Vertical:
return p_int
return None
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def setData(self, index, value, role=Qt.EditRole):
index_value = None
based_columns = [6, 8, 10, 12]
if role == Qt.EditRole:
row = index.row()
column = index.column()
tmp = str(value)
if tmp != '':
if column in based_columns:
if column == 6 and tmp in self._cols:
index_no = np.where(self._cols == tmp)[0][0]
self._data[row, column + 1] = self._data[row, index_no]
self._data[row, column] = tmp
elif column in [8, 10, 12]:
for x in range(97, 181):
if self._data[row, x] == int(tmp):
index_value = x
break
col_name = self._cols[index_value]
col_name = col_name.removesuffix('_rank')
self._data[row, column + 1] = col_name
self._data[row, column] = tmp
self.dataChanged.emit(index, index)
return True
else:
return False
And here the code in the other widget which accept the drop
class PyCustomButton(QPushButton):
def __init__(self, *args):
super().__init__()
self.setCursor(Qt.PointingHandCursor)
self.setAcceptsDrops(True)
self._lista = []
def dragEnterEvent(self, event):
if event.mimeData().hasText():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
self._lista.append(event.mimeData().text())
print(self._lista)
The table has following aspect:
Names |
---|
First |
Second |
And I want plain text i.e. First
or Second
drag from table and drop in custom button.
Solution
The simple solution is to get the "selected" index using indexAt()
. Note that it's generally better to start a drag action when the user has moved the mouse by a certain amount of pixels, which defaults to the QApplication startDragDistance()
property.
class PyTableWidget(QTableView):
_startPos = None
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
index = self.indexAt(event.pos())
if index.isValid():
self._startPos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton and self._startPos is not None:
delta = (event.pos() - self._startPos).manhattanLength()
if delta >= QApplication.startDragDistance():
drag = QDrag(self)
mime = QMimeData()
mime.setText(self.indexAt(self._startPos).data())
drag.setMimeData(mime)
drag.exec(Qt.MoveAction)
self._startPos = None
return
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self._startPos = None
super().mouseReleaseEvent(event)
Note that Qt item views already support drag and drop: to enable that support in custom models, you need to also add Qt.ItemIsDragEnabled
in flags()
and ensure that the table supports drag either by using setDragEnabled(True)
(as you did) or by setting the dragDropMode
of the view at least to DragOnly
.
The only remaining "issue" is getting the data from outside the view from the drag mime data, which uses an internal format to serialize the content.
While this approach might seem more complex and difficult, it's also a better solution whenever you intend to support drag and drop between multiple item views, since that serialization format is common and standard to all Qt views.
class CustomizedNumpyModel(QAbstractTableModel):
# ...
def flags(self, index):
return (Qt.ItemIsEnabled
| Qt.ItemIsSelectable
| Qt.ItemIsEditable
| Qt.ItemIsDragEnabled)
class PyTableWidget(QTableView):
def __init__(self, *args):
super().__init__(*args)
self.setDragEnabled(True)
# nothing else to implement in this class
class PyCustomButton(QPushButton):
def __init__(self, *args):
super().__init__()
self.setCursor(Qt.PointingHandCursor)
self.setAcceptDrops(True)
self._lista = []
def dragEnterEvent(self, event):
if 'application/x-qabstractitemmodeldatalist' in event.mimeData().formats():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
mimeData = event.mimeData()
if mimeData.hasText():
self._lista.append(mimeData.text())
elif 'application/x-qabstractitemmodeldatalist' in mimeData.formats():
names = []
stream = QDataStream(mimeData.data(
'application/x-qabstractitemmodeldatalist'))
while not stream.atEnd():
# all fields must be read, even if not used
row = stream.readInt()
column = stream.readInt()
for _ in range(stream.readInt()):
role = stream.readInt()
value = stream.readQVariant()
if role == Qt.DisplayRole:
names.append(value)
self._lista.extend(names)
print(self._lista)
Note that setData()
must always return a bool. Remove the last else:
and push the return False
back to the main indentation level of the function.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.