如何在 QTreeView 中的项目旁边添加红色圆圈(断点样式)
How to add red circles next to items in QTreeView (breakpoint style)
我有这样的东西:
我无法向您展示更多,但这是一个简单的 QTreeView
,其中包含 QStandardItems
。图中的项目有一个父项,父项也有一个父项。
当我在一个项目上激活 断点 时,我有这个:
没关系,但我也想像大多数 IDE 一样在其下添加一个 圆圈 (我以 PyCharm 为例):
问题是我不知道该怎么做。有人可以帮忙吗?
一个可能的解决方案是重写 QTreeView 的 drawRow
方法并使用 QModelIndex 中的信息进行绘制:
import sys
from PySide2.QtCore import Qt, QRect
from PySide2.QtGui import QColor, QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QApplication, QTreeView
IS_BREAKPOINT_ROLE = Qt.UserRole + 1
class TreeView(QTreeView):
def drawRow(self, painter, option, index):
super().drawRow(painter, option, index)
if index.column() == 0:
if not index.data(IS_BREAKPOINT_ROLE):
return
rect = self.visualRect(index)
if not rect.isNull():
margin = 4
r = QRect(0, rect.top(), rect.height(), rect.height()).adjusted(
margin, margin, -margin, -margin
)
painter.setBrush(QColor("red"))
painter.drawEllipse(r)
def main(args):
app = QApplication(args)
view = TreeView()
view.setSelectionBehavior(QAbstractItemView.SelectRows)
model = QStandardItemModel()
model.setHorizontalHeaderLabels(["col1", "col2"])
view.setModel(model)
counter = 0
for i in range(10):
item1 = QStandardItem("Child 1-{}".format(i))
item2 = QStandardItem("Child 2-{}".format(i))
for j in range(10):
child1 = QStandardItem("Child {}-1".format(counter))
child2 = QStandardItem("Child {}-2".format(counter))
child1.setData(counter % 2 == 0, IS_BREAKPOINT_ROLE)
item1.appendRow([child1, child2])
counter += 1
model.appendRow([item1, item2])
view.show()
view.resize(320, 240)
view.expandAll()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
我想提出一个基于 的替代解决方案,它会在视口中添加左边距,避免在层次结构线上绘制(这可能会隐藏父项的展开装饰箭头需要显示圆圈)。
一些重要说明:
- 左边距是使用
setViewportMargins()
创建的,但是所有项目视图在调用 updateGeometries()
时都会自动重置这些边距(几乎每次布局更改时都会发生),因此需要重写该方法;
- 在边距上绘画意味着绘画不会在视口中发生,因此我们无法实现
paintEvent()
(默认情况下会调用视口更新);这导致在 event()
中执行绘图;
- 更新必须在滚动条变化或项目为 expanded/collapsed 时显式调用,但 Qt 仅更新实际已“更改”的项目感兴趣的区域(因此可能排除其他“移位”项目) ;为了请求完整范围的更新,我们需要调用 QWidget 的基本实现(不是视图的实现,因为该方法已被覆盖);
class TreeView(QTreeView):
leftMargin = 14
def __init__(self, *args, **kwargs):
super().__init__()
self.leftMargin = self.fontMetrics().height()
self.verticalScrollBar().valueChanged.connect(self.updateLeftMargin)
self.expanded.connect(self.updateLeftMargin)
self.collapsed.connect(self.updateLeftMargin)
def updateLeftMargin(self):
QWidget.update(self,
QRect(0, 0, self.leftMargin + self.frameWidth(), self.height()))
def setModel(self, model):
if self.model() != model:
if self.model():
self.model().dataChanged.disconnect(self.updateLeftMargin)
super().setModel(model)
model.dataChanged.connect(self.updateLeftMargin)
def updateGeometries(self):
super().updateGeometries()
margins = self.viewportMargins()
if margins.left() < self.leftMargin:
margins.setLeft(margins.left() + self.leftMargin)
self.setViewportMargins(margins)
def event(self, event):
if event.type() == event.Paint:
pos = QPoint()
index = self.indexAt(pos)
qp = QPainter(self)
border = self.frameWidth()
bottom = self.height() - border * 2
qp.setClipRect(QRect(border, border, self.leftMargin, bottom))
top = .5
if self.header().isVisible():
top += self.header().height()
qp.translate(.5, top)
qp.setBrush(Qt.red)
qp.setRenderHints(qp.Antialiasing)
deltaY = self.leftMargin / 2 - border
circle = QRect(
border + 1, 0, self.leftMargin - 2, self.leftMargin - 2)
row = 0
while index.isValid():
rect = self.visualRect(index)
if index.data(IS_BREAKPOINT_ROLE):
circle.moveTop(rect.center().y() - deltaY)
qp.drawEllipse(circle)
row += 1
pos.setY(rect.bottom() + 2)
if pos.y() > bottom:
break
index = self.indexAt(pos)
return super().event(event)
我有这样的东西:
我无法向您展示更多,但这是一个简单的 QTreeView
,其中包含 QStandardItems
。图中的项目有一个父项,父项也有一个父项。
当我在一个项目上激活 断点 时,我有这个:
没关系,但我也想像大多数 IDE 一样在其下添加一个 圆圈 (我以 PyCharm 为例):
问题是我不知道该怎么做。有人可以帮忙吗?
一个可能的解决方案是重写 QTreeView 的 drawRow
方法并使用 QModelIndex 中的信息进行绘制:
import sys
from PySide2.QtCore import Qt, QRect
from PySide2.QtGui import QColor, QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QApplication, QTreeView
IS_BREAKPOINT_ROLE = Qt.UserRole + 1
class TreeView(QTreeView):
def drawRow(self, painter, option, index):
super().drawRow(painter, option, index)
if index.column() == 0:
if not index.data(IS_BREAKPOINT_ROLE):
return
rect = self.visualRect(index)
if not rect.isNull():
margin = 4
r = QRect(0, rect.top(), rect.height(), rect.height()).adjusted(
margin, margin, -margin, -margin
)
painter.setBrush(QColor("red"))
painter.drawEllipse(r)
def main(args):
app = QApplication(args)
view = TreeView()
view.setSelectionBehavior(QAbstractItemView.SelectRows)
model = QStandardItemModel()
model.setHorizontalHeaderLabels(["col1", "col2"])
view.setModel(model)
counter = 0
for i in range(10):
item1 = QStandardItem("Child 1-{}".format(i))
item2 = QStandardItem("Child 2-{}".format(i))
for j in range(10):
child1 = QStandardItem("Child {}-1".format(counter))
child2 = QStandardItem("Child {}-2".format(counter))
child1.setData(counter % 2 == 0, IS_BREAKPOINT_ROLE)
item1.appendRow([child1, child2])
counter += 1
model.appendRow([item1, item2])
view.show()
view.resize(320, 240)
view.expandAll()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
我想提出一个基于
一些重要说明:
- 左边距是使用
setViewportMargins()
创建的,但是所有项目视图在调用updateGeometries()
时都会自动重置这些边距(几乎每次布局更改时都会发生),因此需要重写该方法; - 在边距上绘画意味着绘画不会在视口中发生,因此我们无法实现
paintEvent()
(默认情况下会调用视口更新);这导致在event()
中执行绘图; - 更新必须在滚动条变化或项目为 expanded/collapsed 时显式调用,但 Qt 仅更新实际已“更改”的项目感兴趣的区域(因此可能排除其他“移位”项目) ;为了请求完整范围的更新,我们需要调用 QWidget 的基本实现(不是视图的实现,因为该方法已被覆盖);
class TreeView(QTreeView):
leftMargin = 14
def __init__(self, *args, **kwargs):
super().__init__()
self.leftMargin = self.fontMetrics().height()
self.verticalScrollBar().valueChanged.connect(self.updateLeftMargin)
self.expanded.connect(self.updateLeftMargin)
self.collapsed.connect(self.updateLeftMargin)
def updateLeftMargin(self):
QWidget.update(self,
QRect(0, 0, self.leftMargin + self.frameWidth(), self.height()))
def setModel(self, model):
if self.model() != model:
if self.model():
self.model().dataChanged.disconnect(self.updateLeftMargin)
super().setModel(model)
model.dataChanged.connect(self.updateLeftMargin)
def updateGeometries(self):
super().updateGeometries()
margins = self.viewportMargins()
if margins.left() < self.leftMargin:
margins.setLeft(margins.left() + self.leftMargin)
self.setViewportMargins(margins)
def event(self, event):
if event.type() == event.Paint:
pos = QPoint()
index = self.indexAt(pos)
qp = QPainter(self)
border = self.frameWidth()
bottom = self.height() - border * 2
qp.setClipRect(QRect(border, border, self.leftMargin, bottom))
top = .5
if self.header().isVisible():
top += self.header().height()
qp.translate(.5, top)
qp.setBrush(Qt.red)
qp.setRenderHints(qp.Antialiasing)
deltaY = self.leftMargin / 2 - border
circle = QRect(
border + 1, 0, self.leftMargin - 2, self.leftMargin - 2)
row = 0
while index.isValid():
rect = self.visualRect(index)
if index.data(IS_BREAKPOINT_ROLE):
circle.moveTop(rect.center().y() - deltaY)
qp.drawEllipse(circle)
row += 1
pos.setY(rect.bottom() + 2)
if pos.y() > bottom:
break
index = self.indexAt(pos)
return super().event(event)