在上一篇文章中,实现了sql查询数据库内容,把数据填充到QtreeView上的功能,同时也实现点击父节点时,子节点复选框也会改变的效果。但是就目前而言,这样的下拉框功能是不够齐全的,还需要添加Qcombobox当前所有选中子项的列表内容,同时进行复选框三态的美化。改善后的效果如下:
(1)获取Qcombobox当前所有选择子项的列表list:函数【get_selected】
这里其实还是用递归的方法,寻找所有checkState()为选中状态的节点,然后一个一个添加到list列表中。当你需要获取Qcombobox当前所有选择子项时,只需要调用QcomboTreebox.get_selected()即可
# 获取当前选择的子项
def get_selected(self):
items = list()
for row in range(0, self.vars["listViewModel"].rowCount()):
item = self.vars["listViewModel"].item(row)
self.get_child_item(item, items)
return items
# 用于get_selected函数的递归函数
def get_child_item(self, item, items):
if item.hasChildren() == True:
total_child_count = item.rowCount()
for i in range(total_child_count):
child_item = item.child(i)
if child_item.checkState() == Qt.Checked:
items.append(child_item.text())
self.get_child_item(child_item, items)
(2)修改Qcombobox的LineEdit显示内容,如下:
def Combobox_LineEdit_showText(self):
items = self.get_selected()
l = len(items)
is_all = bool(self.vars["listViewModel"].item(0).checkState() == Qt.Checked)
self.vars["lineEdit"].setText(
"(全选)" if is_all == True else "(无选择)" if l == 0 else ";".join((item for item in items)))
(3)设置复选框的三态情况,即特殊的部分选中情况。注意,要在点击树形结构时,调用此函数,步骤(2)函数也在此调用。
# 展示下拉框
def __show_selected(self, index):
this = self.vars["currIndex"]
model = self.vars["listViewModel"]
current_standardItem = model.itemFromIndex(this)
item = self.vars["listViewModel"].item(index)
if self.vars["expandOrCollape"]:
pass
# print("Mouse_Clicked_On_ExpandOrCollape")
else:
current_standardItem.setCheckState(Qt.Unchecked if current_standardItem.checkState() == Qt.Checked else Qt.Checked)
# 开始递归寻找子节点
self.QcomboTreebox_child_node(current_standardItem)
self.checkState_set(current_standardItem) # 调用设置复选框状态
self.Combobox_LineEdit_showText() # 调用显示选择列表数据
self.vars["lock"] = True
# 设置复选框状态
def checkState_set(self, currItem):
rootItem = self.vars["listViewModel"].item(0)
currParent = currItem.parent()
count = 0
if currParent != None:
currParent_childCount = currParent.rowCount()
for i in range(currParent_childCount):
if currParent.child(i).checkState() == Qt.Checked:
count += 1
if currParent != rootItem:
if count == currParent_childCount:
currParent.setCheckState(Qt.Checked)
elif 0 < count < currParent_childCount:
currParent.setCheckState(Qt.PartiallyChecked)
else:
currParent.setCheckState(Qt.Unchecked)
l = len(self.get_selected())
if currParent != None and 0 < l < self.vars["data_length"]:
rootItem.setCheckState(Qt.PartiallyChecked)
elif l == self.vars["data_length"]:
rootItem.setCheckState(Qt.Checked)
elif l == 0:
rootItem.setCheckState(Qt.Unchecked)
完整测试代码:
from PyQt5.QtWidgets import QWidget, QComboBox, QLineEdit, QListView, QTreeView, QApplication
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QMouseEvent
from PyQt5.Qt import Qt, QRect
from collections import deque
import sys
class QComboTreeBox(QComboBox):
class MyTreeView(QTreeView):
def __init__(self, parent: QWidget = None, vars=None):
super().__init__(parent)
self.vars = vars
self.setExpandsOnDoubleClick(False)
self.setHeaderHidden(True)
def mousePressEvent(self, event):
self.vars["lock"] = False
currIndex = self.currentIndex()
index = currIndex.sibling(currIndex.row(), 0).data() # 获取同一行不同列的数据
self.vars["currIndex"] = currIndex
'''自己创建点击节点Node三角折叠/展开功能'''
rect = self.visualRect(currIndex)
expandOrCollape = QRect(rect.left() - 20, rect.top(), 20, rect.height())
if expandOrCollape.contains(event.pos()):
self.vars["expandOrCollape"] = True
if self.isExpanded(currIndex):
self.setExpanded(currIndex, False)
else:
self.setExpanded(currIndex, True)
else:
self.vars["expandOrCollape"] = False
'''自己创建点击节点Node三角折叠/展开功能'''
# super().mousePressEvent(event) # super()会出现节点打不开的情况,所以才自己创建点击节点Node三角折叠/展开功能
def mouseDoubleClickEvent(self, event):
self.vars["lock"] = False
super().mouseDoubleClickEvent(event)
def __init__(self, parent=None):
super().__init__(parent)
self.vars = dict()
self.vars["lock"] = True
self.vars["lineEdit"] = QLineEdit(self)
self.vars["lineEdit"].setReadOnly(True)
self.vars["listView"] = self.MyTreeView(self, self.vars)
self.vars["listViewModel"] = QStandardItemModel(self)
self.setModel(self.vars["listViewModel"])
self.setView(self.vars["listView"])
self.setLineEdit(self.vars["lineEdit"])
self.vars["data_length"] = None
self.view_settings()
self.activated.connect(self.__show_selected)
# self.add_item("(全选)")
# self.add_item("其他")
data = [(1, 0, 'A'), (2, 0, 'B'), (3, 0, 'C'), (4, 0, 'D'), (5, 0, 'E'), (6, 0, 'F'), (7, 0, 'G'), (8, 0, 'H'),
(9, 1, 'J'), (10, 1, 'K'), (11, 1, 'L'), (
12, 1, 'Q'), (14, 2, 'E'), (15, 2, 'R'), (13, 2, 'W')]
self.importData(data)
# QtreeView展示视图的一些设置
def view_settings(self):
self.view().setMinimumWidth(150)
self.view().setMinimumHeight(300)
self.view().setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# 往QtreeView中添加数据
def importData(self, data, root=None):
self.vars["listViewModel"].setRowCount(0)
if root is None:
root = QStandardItem()
self.vars["listViewModel"].appendRow(root)
root.setText("全选")
root.setCheckable(True)
root.setCheckState(Qt.Checked)
seen = {} # List of QStandardItem
values = deque(data)
while values:
value = values.popleft()
if value[1] == 0:
parent = root
else:
pid = value[1]
if pid not in seen:
values.append(value)
continue
parent = seen[pid]
unique_id = value[0]
child = QStandardItem()
child.setText(value[2])
child.setCheckable(True)
child.setCheckState(Qt.Checked)
parent.appendRow([
child,
])
seen[unique_id] = parent.child(parent.rowCount() - 1)
self.vars["data_length"] = len(data)
# 展示下拉框
def __show_selected(self, index):
this = self.vars["currIndex"]
model = self.vars["listViewModel"]
current_standardItem = model.itemFromIndex(this)
item = self.vars["listViewModel"].item(index)
if self.vars["expandOrCollape"]:
pass
# print("Mouse_Clicked_On_ExpandOrCollape")
else:
current_standardItem.setCheckState(Qt.Unchecked if current_standardItem.checkState() == Qt.Checked else Qt.Checked)
# 开始递归寻找子节点
self.QcomboTreebox_child_node(current_standardItem)
self.checkState_set(current_standardItem)
self.Combobox_LineEdit_showText()
self.vars["lock"] = True
# 设置复选框状态
def checkState_set(self, currItem):
rootItem = self.vars["listViewModel"].item(0)
currParent = currItem.parent()
count = 0
if currParent != None:
currParent_childCount = currParent.rowCount()
for i in range(currParent_childCount):
if currParent.child(i).checkState() == Qt.Checked:
count += 1
if currParent != rootItem:
if count == currParent_childCount:
currParent.setCheckState(Qt.Checked)
elif 0 < count < currParent_childCount:
currParent.setCheckState(Qt.PartiallyChecked)
else:
currParent.setCheckState(Qt.Unchecked)
l = len(self.get_selected())
if currParent != None and 0 < l < self.vars["data_length"]:
rootItem.setCheckState(Qt.PartiallyChecked)
elif l == self.vars["data_length"]:
rootItem.setCheckState(Qt.Checked)
elif l == 0:
rootItem.setCheckState(Qt.Unchecked)
# 递归寻找所有子节点,并改变其复选框状态
def QcomboTreebox_child_node(self, item):
if item.hasChildren() == True:
total_child_count = item.rowCount()
for i in range(total_child_count):
child_item = item.child(i)
child_item.setCheckState(Qt.Checked if item.checkState() == Qt.Checked else Qt.Unchecked)
self.QcomboTreebox_child_node(child_item)
# 递归寻找所有父节点,并展开
def QcomboTreebox_parent_node(self, item):
if item.parent() != None:
pitem = item.parent()
pitem.setExpanded(True)
self.QcomboTreebox_parent_node(pitem)
# 获取当前选择的子项
def get_selected(self):
items = list()
for row in range(0, self.vars["listViewModel"].rowCount()):
item = self.vars["listViewModel"].item(row)
self.get_child_item(item, items)
return items
# 用于get_selected函数的递归函数
def get_child_item(self, item, items):
if item.hasChildren() == True:
total_child_count = item.rowCount()
for i in range(total_child_count):
child_item = item.child(i)
if child_item.checkState() == Qt.Checked:
items.append(child_item.text())
self.get_child_item(child_item, items)
# 清空所有子项
def clear_items(self):
self.vars["listViewModel"].clear()
# 检测下拉框是否已有数据
def is_already_hasdata(self):
return True if self.vars["listViewModel"].rowCount() != 0 else False
# 下拉框LineEdit展示文本
def Combobox_LineEdit_showText(self):
items = self.get_selected()
l = len(items)
is_all = bool(self.vars["listViewModel"].item(0).checkState() == Qt.Checked)
self.vars["lineEdit"].setText(
"(全选)" if is_all == True else "(无选择)" if l == 0 else ";".join((item for item in items)))
# 重写下拉框收起事件条件
def hidePopup(self):
if self.vars["lock"]:
super().hidePopup()
def run():
app = QApplication(sys.argv)
win = QComboTreeBox()
win.show()
sys.exit(app.exec_())
if __name__ == '__main__':
run()