问题
I have a QTreeView with implemented dropEvent()-handler. If the item to be dropped is already existing under the target-root, how could I avoid the dropping at all? E.g. drag "user_b" from the "Master Data" Node on the node "Security Model"/"client_c"/"stakeholder_d" which already exists.
Full working code example:
#!/usr/bin/env python3
# coding = utf-8
from PyQt5 import QtWidgets, QtCore, QtGui
TXT_CLIENT = "Clients"
TXT_STAKEHLD = "Stakeholders"
TXT_USER = "Users"
TXT_SYSTEM = "Master Data"
TXT_SECURITY = "Security Model"
CLS_LVL_ROOT = 0
CLS_LVL_CLIENT = 1
CLS_LVL_STAKEHLD = 2
CLS_LVL_USER = 3
ICON_LVL_CLIENT = "img/icons8-bank-16.png"
ICON_LVL_STAKEHLD = "img/icons8-initiate-money-transfer-24.png"
ICON_LVL_USER = "img/icons8-checked-user-male-32.png"
DATA = [
(TXT_SYSTEM, [
(TXT_USER, [
("user_a", []),
("user_b", [])
]),
(TXT_CLIENT, [
("client_a", []),
("client_b", []),
("client_c", []),
("client_d", [])
]),
(TXT_STAKEHLD, [
("stakeholder_a", []),
("stakeholder_b", []),
("stakeholder_c", []),
("stakeholder_d", [])
])
]),
(TXT_SECURITY, [
("client_a", [
("stakeholder_b",[
("user_a",[])
])
]),
("client_c", [
("stakeholder_d",[
("user_b",[])
])
])
])
]
def create_tree_data(tree):
model = QtGui.QStandardItemModel()
addItems(tree, model, DATA)
tree.setModel(model)
def addItems(tree, parent, elements, level=0, root=0):
level += 1
for text, children in elements:
if text == TXT_SYSTEM:
root = 1
elif text == TXT_SECURITY:
root = 2
item = QtGui.QStandardItem(text)
icon = QtGui.QIcon(TXT_USER)
item.setIcon(icon)
parent.appendRow(item)
if root==1:
if children:
if text == TXT_CLIENT:
icon = QtGui.QIcon(ICON_LVL_CLIENT)
item.setIcon(icon)
elif text == TXT_STAKEHLD:
icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
item.setIcon(icon)
elif text == TXT_USER:
icon = QtGui.QIcon(ICON_LVL_USER)
item.setIcon(icon)
elif root == 2:
if level == 2:
icon = QtGui.QIcon(ICON_LVL_CLIENT)
item.setIcon(icon)
if level == 3:
icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
item.setIcon(icon)
elif level == 4:
icon = QtGui.QIcon(ICON_LVL_USER)
item.setIcon(icon)
addItems(tree, item, children, level, root)
def get_tree_selection_level(index):
level = 0
while index.parent().isValid():
index = index.parent()
level += 1
return level
class TreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
self.setHeaderHidden(True)
self.setColumnHidden(1, True)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) # InternalMove)
def dropEvent(self, event):
tree = event.source()
if self.viewport().rect().contains(event.pos()):
fake_model = QtGui.QStandardItemModel()
fake_model.dropMimeData(
event.mimeData(), event.dropAction(), 0, 0, QtCore.QModelIndex()
)
for r in range(fake_model.rowCount()):
for c in range(fake_model.columnCount()):
ix = fake_model.index(r, c)
print("item: ", ix.data())
item = QtGui.QStandardItem(ix.data())
icon = QtGui.QIcon(TXT_USER)
item.setIcon(icon)
sParent: str = ""
par_ix = tree.selectedIndexes()[0].parent()
if par_ix.isValid():
sParent = par_ix.data()
print("par. item: ", sParent)
to_index = self.indexAt(event.pos())
if to_index.isValid():
print("to:", to_index.data())
if (sParent == TXT_CLIENT and get_tree_selection_level(to_index) == CLS_LVL_ROOT) or (sParent == TXT_STAKEHLD and get_tree_selection_level(to_index) == CLS_LVL_CLIENT) or (sParent == TXT_USER and get_tree_selection_level(to_index) == CLS_LVL_STAKEHLD):
# to-do:
# 1 - check if the item is already there; if yes: omit
pass
# 2 - set the proper icon
pass
super().dropEvent(event)
self.setExpanded(to_index, True)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
centralwidget = QtWidgets.QWidget()
self.setCentralWidget(centralwidget)
hBox = QtWidgets.QHBoxLayout(centralwidget)
self.treeView = TreeView(centralwidget)
hBox.addWidget(self.treeView)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = MainWindow()
create_tree_data(window.treeView)
window.treeView.expand(window.treeView.model().index(0, 0)) # expand the System-Branch
window.setGeometry(400, 400, 500, 400)
window.show()
app.exec_()
回答1:
You can use dragMoveEvent to perform the filters:
- There can be nodes with the same text as the same father.
- The SM node can only have clients as children.
- Client nodes can only have stakeholders as children.
- The stakeholders nodes can only have users as children.
Considering the above, the solution is:
from enum import Enum
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
TXT_SYSTEM = "Master Data"
TXT_SECURITY = "Security Model"
TXT_CLIENT = "Clients"
TXT_STAKEHLD = "Stakeholders"
TXT_USER = "Users"
ICON_LVL_CLIENT = "img/icons8-bank-16.png"
ICON_LVL_STAKEHLD = "img/icons8-initiate-money-transfer-24.png"
ICON_LVL_USER = "img/icons8-checked-user-male-32.png"
TYPE_ROLE = QtCore.Qt.UserRole + 1000
class NodeRoles(Enum):
ROOT_ROLE = 0
CLIENT_ROLE = 1
STAKEHOLD_ROLE = 2
USER_ROLE = 3
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
SYSTEM_DATA = {
TXT_USER: ["user_a", "user_b"],
TXT_CLIENT: ["client_a", "client_b", "client_c", "client_d"],
TXT_STAKEHLD: ["stakeholder_a", "stakeholder_b", "stakeholder_c", "stakeholder_d"],
}
SECURITY_DATA = {
"client_a": {"stakeholder_b": ["user_a"]},
"client_c": {"stakeholder_d": ["user_b"]},
}
class CustomModel(QtGui.QStandardItemModel):
def __init__(self, system_data, security_data, parent=None):
super().__init__(parent)
self.create_system_node(system_data)
self.create_security_node(security_data)
def create_system_node(self, system_data):
root = QtGui.QStandardItem(TXT_SYSTEM)
self.appendRow(root)
for it in (root, self.invisibleRootItem()):
it.setFlags(
it.flags() & ~QtCore.Qt.ItemIsDragEnabled & ~QtCore.Qt.ItemIsDropEnabled
)
it.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)
for key, path_icon, role in zip(
(TXT_USER, TXT_CLIENT, TXT_STAKEHLD),
(ICON_LVL_USER, ICON_LVL_CLIENT, ICON_LVL_STAKEHLD),
(NodeRoles.USER_ROLE, NodeRoles.CLIENT_ROLE, NodeRoles.STAKEHOLD_ROLE),
):
it = QtGui.QStandardItem(key)
it.setFlags(
it.flags() & ~QtCore.Qt.ItemIsDragEnabled & ~QtCore.Qt.ItemIsDropEnabled
)
icon = QtGui.QIcon(os.path.join(CURRENT_DIR, path_icon))
it.setIcon(icon)
it.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)
root.appendRow(it)
for value in system_data[key]:
child = QtGui.QStandardItem(value)
child.setFlags(child.flags() & ~QtCore.Qt.ItemIsDropEnabled)
child.setData(role, TYPE_ROLE)
it.appendRow(child)
def create_security_node(self, security_data):
root = QtGui.QStandardItem(TXT_SECURITY)
root.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)
self.appendRow(root)
root.setFlags(root.flags() & ~QtCore.Qt.ItemIsDragEnabled)
self._fill_node(security_data, root, 0)
def _fill_node(self, data, root, level):
role = (NodeRoles.CLIENT_ROLE, NodeRoles.STAKEHOLD_ROLE, NodeRoles.USER_ROLE)[
level
]
icon_path = (ICON_LVL_CLIENT, ICON_LVL_STAKEHLD, ICON_LVL_USER)[level]
icon = QtGui.QIcon(os.path.join(CURRENT_DIR, icon_path))
if isinstance(data, dict):
for key, value in data.items():
it = QtGui.QStandardItem(key)
it.setFlags(it.flags() & ~QtCore.Qt.ItemIsDropEnabled)
it.setIcon(icon)
it.setData(role, TYPE_ROLE)
root.appendRow(it)
self._fill_node(value, it, level + 1)
return
else:
for d in data:
it = QtGui.QStandardItem(d)
it.setIcon(icon)
it.setData(role, TYPE_ROLE)
root.appendRow(it)
class TreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
self.setHeaderHidden(True)
self.setColumnHidden(1, True)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.resize(640, 480)
def dragMoveEvent(self, event):
if event.source() is not self:
event.ignore()
return
fake_model = QtGui.QStandardItemModel()
fake_model.dropMimeData(
event.mimeData(), event.dropAction(), 0, 0, QtCore.QModelIndex()
)
it = fake_model.item(0)
role = it.data(TYPE_ROLE)
to_index = self.indexAt(event.pos())
root = to_index
while root.parent().isValid():
root = root.parent()
if root == self.model().item(0).index():
event.ignore()
else:
to_role = to_index.data(TYPE_ROLE)
if (
(to_role == NodeRoles.ROOT_ROLE and role == NodeRoles.CLIENT_ROLE)
or (
to_role == NodeRoles.CLIENT_ROLE
and role == NodeRoles.STAKEHOLD_ROLE
)
or (to_role == NodeRoles.STAKEHOLD_ROLE and role == NodeRoles.USER_ROLE)
):
to_item = self.model().itemFromIndex(to_index)
for i in range(to_item.rowCount()):
child_it = to_item.child(i)
if child_it.text() == it.text():
event.ignore()
return
self.setExpanded(to_index, True)
super().dragMoveEvent(event)
else:
event.ignore()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = TreeView()
model = CustomModel(system_data=SYSTEM_DATA, security_data=SECURITY_DATA)
w.setModel(model)
w.show()
sys.exit(app.exec_())
来源:https://stackoverflow.com/questions/59531532/qtreeview-checking-if-item-to-be-dropped-is-already-present-and-avoid-it-on-dro