我是Python和Qt的初学者,这是我在该论坛中的第一个问题,对我的学习过程有很大帮助。
我将一些层次结构数据存储在QAbstractItemModel中,并且与QTreeView一起使用时效果很好。我也成功地使用了QDataWidgetMapper和selectionModel.currentChanged将选定节点的数据映射到lineEdit。
对于某些节点,我有4列要绘制为条形图中的条形图。
我已经制作了一个适用于硬编码集的ChartView,但是当我添加QHBarModelMapper将所选行的第1-4列映射到条形系列时,我得到了一个空图表。
import sys
from PySide2.QtCore import (QModelIndex, Qt, QObject, QAbstractItemModel)
from PySide2.QtWidgets import (QWidget, QTreeView, QLineEdit, QVBoxLayout, QApplication, QDataWidgetMapper)
from PySide2.QtCharts import QtCharts
class CustomTreeModel(QAbstractItemModel):
def __init__(self, root, parent=None):
super(CustomTreeModel, self).__init__(parent)
self._root_node = root
def rowCount(self, parent):
if not parent.isValid():
parent_node = self._root_node
else:
parent_node = parent.internalPointer()
return parent_node.childCount()
def columnCount(self, parent):
return 5
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer()
if role == Qt.DisplayRole or role == Qt.EditRole:
if not isinstance(node, QModelIndex):
if index.column() == 0:
return node.name
if index.column() == 1:
return node.value1
if index.column() == 2:
return node.value2
if index.column() == 3:
return node.value3
if index.column() == 4:
return node.value4
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole:
if section == 0:
return self._root_node.name
if section == 1:
return "value1"
if section == 2:
return "value2"
if section == 3:
return "value3"
if section == 4:
return "value4"
def parent(self, index):
node = self.getNode(index)
parent_node = node.parent
if parent_node == self._root_node:
return QModelIndex()
return self.createIndex(parent_node.row(), 0, parent_node)
def index(self, row, column, parent):
parent_node = self.getNode(parent)
child_item = parent_node.child(row)
if child_item:
return self.createIndex(row, column, child_item)
else:
return QModelIndex()
def getNode(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
return self._root_node
class Nodes(QObject):
def __init__(self, name, value1, value2, value3, value4, parent = QModelIndex):
self.parent = parent
self.name = name
self.value1 = value1
self.value2 = value2
self.value3 = value3
self.value4 = value4
self.children = []
def child(self, row):
return self.children[row]
def row(self):
if self.parent is not None:
return self.parent.children.index(self)
def addChild(self, child):
self.children.append(child)
def removeChild(self, position):
if position > 0 or position > len(self.children):
return False
node = self.children.pop(position)
node.parent = None
def childCount(self):
return len(self.children)
class MyWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.line_edit = QLineEdit()
self.tree_view = QTreeView()
self.bar_chart = QtCharts.QChart()
self.bar_series = QtCharts.QBarSeries()
self.bar_chart.addSeries(self.bar_series)
categories = ["value 1", "value 2", "value 3", "value 4"]
axis_x = QtCharts.QBarCategoryAxis()
axis_x.append(categories)
self.bar_chart.addAxis(axis_x, Qt.AlignBottom)
self.bar_series.attachAxis(axis_x)
axis_y = QtCharts.QValueAxis()
self.bar_chart.addAxis(axis_y, Qt.AlignLeft)
self.bar_series.attachAxis(axis_y)
self.chart_view = QtCharts.QChartView(self.bar_chart)
self.chart_view.setMinimumSize(640, 480)
self.v_layout = QVBoxLayout(self)
self.v_layout.addWidget(self.line_edit)
self.v_layout.addWidget(self.tree_view)
self.v_layout.addWidget(self.chart_view)
self.mapper = None
self.bar_mapper = None
def set_selection(self, current, old):
parent = current.parent()
self.mapper.setRootIndex(parent)
self.mapper.setCurrentModelIndex(current)
self.bar_mapper.setFirstBarSetRow(current.row())
def set_model(self, model):
self.tree_view.setModel(model)
self.tree_view.selectionModel().currentChanged.connect(self.set_selection)
self.mapper = QDataWidgetMapper()
self.mapper.setModel(model)
self.mapper.addMapping(self.line_edit, 0)
self.bar_mapper = QtCharts.QHBarModelMapper()
self.bar_mapper.setModel(model)
self.bar_mapper.setFirstColumn(1)
self.bar_mapper.setColumnCount(4)
self.bar_mapper.setSeries(self.bar_series)
if __name__ == "__main__":
# Qt Application
app = QApplication(sys.argv)
root_node = Nodes('Data Tree', 0,0,0,0)
node1 = Nodes('node 1', 23, 54, 35 ,35, root_node)
node1_1 = Nodes('node 1.1', 23 , 24 , 25 , 26, node1)
node1_2 = Nodes('node 1.2', 24, 25, 26, 23, node1)
node2 = Nodes('node 2', 26, 27, 22, 24, root_node)
node2_1 = Nodes('node 2.1', 25, 26, 23, 24, node2)
node2_2 = Nodes('node 2.2', 26, 23, 24, 25, node2)
node1.addChild(node1_1)
node1.addChild(node1_2)
node2.addChild(node2_1)
node2.addChild(node2_2)
root_node.addChild(node1)
root_node.addChild(node2)
data_model = CustomTreeModel(root_node)
widget = MyWidget()
widget.set_model(data_model)
widget.show()
sys.exit(app.exec_())
我意识到QAbstractItemModel树模型的行号在树中的每个父级之后重新启动。这是否意味着我不能像这样在模型上直接使用QHBarModelMapper:
self.bar_mapper.setFirstBarSetRow(current.row()).
我应该怎么用呢?
最佳答案
不幸的是,尽管Q{X}ModelMapper
类的文档表明它们支持QAbstractItemModel作为模型,但是它们只能处理列表或表类型模型或树型模型的第一层。与QListView,QDataWidgetMapper等不同,您可以通过rootIndex属性设置迭代点,但Q{X}ModelMapper
并非如此。
一个可能的解决方案是实现代理以提取子树,但是在您的特殊情况下,我认为它是不必要的,因为它足以遍历该行:
def set_selection(self, current, old):
parent = current.parent()
self.mapper.setRootIndex(parent)
self.mapper.setCurrentModelIndex(current)
model = self.tree_view.model()
if isinstance(model, CustomTreeModel):
node = model.getNode(current)
self.bar_series.clear()
bar_set = QtCharts.QBarSet(node.name)
bar_set << node.value1 << node.value2 << node.value3 << node.value4
self.bar_series.append(bar_set)
axs = self.bar_chart.axes(Qt.Vertical, self.bar_series)
if axs:
axis_y = axs[0]
axis_y.setMax(max([bar_set.at(i) for i in range(bar_set.count())]) + 10)
def set_model(self, model):
self.tree_view.setModel(model)
self.tree_view.selectionModel().currentChanged.connect(self.set_selection)
self.mapper = QDataWidgetMapper()
self.mapper.setModel(model)
self.mapper.addMapping(self.line_edit, 0)