gpt4 book ai didi

python - 带有自定义按钮的 QStyledItemDelegate

转载 作者:行者123 更新时间:2023-12-01 08:55:03 25 4
gpt4 key购买 nike

我有一个 QTreeWidget,我想通过使用样式委托(delegate)来完全自定义项目的外观。

我的主要问题是我想在我的项目右侧创建一个自定义按钮,它允许我折叠和展开该项目的子项。经典的“+”按钮通常可以在大多数树的左侧找到。

我可以毫无问题地绘制按钮本身,并根据项目是否展开来更改其图标。问题是让它表现得像一个按钮(按下时激活命令,悬停时改变颜色等......)

我想到的是使用 editorEvent 来检查鼠标是否按下在与我绘制当前项目的按钮相同的位置上。

为了获得悬停效果,我编辑了树的 mouseMoveEvent 并检查鼠标是否位于该项目的按钮顶部,如果是,则在悬停时重新绘制该项目。

我的实现完成了这项工作,但我担心我做得完全错误,效率不高,而且我的树会因为这种计算而变慢。所以我想知道是否有人对如何改进下面的代码有一些建议。

代表

class styleDelegate(QtWidgets.QStyledItemDelegate):


def __init__(self, parent=None, treeWidget = None):
super(styleDelegate, self).__init__(parent)
self.tree = treeWidget

def paint(self, painter, option, index):

painter.save()
rect = option.rect

# set the pen to draw an outline around the item to divide them.
pen = QPen()
pen.setBrush(QtGui.QColor(43, 43, 43))
pen.setWidthF(1)
painter.setPen(pen)
item = self.tree.itemFromIndex(index)

# set the background color based on the item or if it is selected
if option.state & QStyle.State_Selected:
painter.setBrush(option.palette.highlight())
else:
color = item.color
painter.setBrush(QtGui.QColor(color[0] * 255, color[1] * 255, color[2] * 255))

#draw the colored background
painter.drawRect(rect)

#draw the image
imageScale = 0
margin = 4
imageScale = rect.height() - margin * 2 + 1
painter.drawPixmap(rect.x() + margin, rect.y() + margin , imageScale, imageScale, item.image.scaled(imageScale, imageScale, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))

# draw the text

painter.setPen(QtGui.QColor(255, 255, 255))
font = painter.font()
font.setPointSize(9)
painter.setFont(font)

painter.drawText(rect.x() + imageScale + margin * 3, rect.y(), 100, item.scale, Qt.AlignVCenter, item.name)

# draw the expander button only if the item has children
if item.childCount():

# choose the appropriate icon to draw depending on the state of the item.
if item.isExpanded():
path = "checked.png"
if item.hover:
path = "checked_hover.png"
else:
path = "unchecked.png"
if item.hover:
path = "unchecked_hover.png"
image = QtGui.QPixmap.fromImage(QtGui.QImage(path))
size = 20

# define the position of the expander button

positionX = rect.x() + rect.width() - 20
positionY = rect.y() + item.scale / 2 - size/2

painter.drawPixmap(positionX, positionY, size, size, image.scaled(size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))

item.expanderStart = QPoint(positionX, positionY)
item.expanderEnd = QPoint(positionX + 20, positionY + 20)

painter.restore()

def editorEvent(self, event, model, option, index):


# if an item is clicked, check if the click happened in the area whee the expander button is drawn.
if event.type() == QEvent.MouseButtonPress:
item = self.tree.itemFromIndex(index)

rect = option.rect
clickX = event.x()
clickY = event.y()

# set the expanded expanded if it was clicked
if clickX > x and clickX < x + w:
if clickY > y and clickY < y + h:
item.setExpanded(not item.isExpanded())

class myTree(QtWidgets.QTreeWidget):
def __init__(self, parent):
super(myTree, self).__init__(parent)
self.setMouseTracking(True)

def mouseMoveEvent(self, event)
item = self.itemAt(event.pos())
if item:
if item.childCount():
# get the current hovering state. if the item is already hovered, there is no need to repaint it.
hover = item.hover
if (event.pos.x() > item.expanderStart.x()
and event.pos.x() < item.expanderEnd.x()
and event.pos.y() > item.expanderStart.y()
and event.pos.y() < item.expanderEnd.y())
item.hover = True
else:
item.hover = False
if item.hover != hover:
self.viewport().update(event.pos().x(), event.pos().y(), 20, 20)

我知道这可以在不使用委托(delegate)的情况下完全实现,只需使用样式表或将小部件分配给项目即可。然而,我并没有深入研究这些方法,因为我遇到了一些问题。

我花了很多时间试图达到我想要的结果但没有成功。也许我让我的元素看起来接近我想要的,但从来没有完全像我想象的那样。

我之所以如此挑剔地想要得到我想要的委托(delegate)外观,是因为这个 QTreeWidget 曾经是一个 QListWidget,是用样式表实现的。现在我将其“升级”为一棵树,我什至不希望用户注意到差异,但我无法仅使用样式表复制相同的外观。

如果上面的代码有愚蠢的错误,请原谅我,我已经测试了完整版本并且它可以工作,我刚刚在这里发布了相关的内容。

<小时/>

编辑:

根据要求,这是一些代码(至少对我来说)产生所需的结果。但是,我想知道这是否是我正在做的事情的正确方法......

from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWidgets import *

class styleDelegate(QStyledItemDelegate):


def __init__(self, parent=None, treeWidget = None):
super(styleDelegate, self).__init__(parent)
self.tree = treeWidget

def paint(self, painter, option, index):

painter.save()
rect = option.rect

# set the pen to draw an outline around the item to divide them.
pen = QPen()
pen.setBrush(QColor(43, 43, 43))
pen.setWidthF(1)
painter.setPen(pen)
item = self.tree.itemFromIndex(index)

# set the background color based on the item or if it is selected
if option.state & QStyle.State_Selected:
painter.setBrush(option.palette.highlight())
else:
color = item.color
painter.setBrush(QColor(color[0], color[1], color[2]))

#draw the colored background
painter.drawRect(rect)

#draw the image
margin = 4
imageScale = rect.height() - margin * 2 + 1
painter.drawPixmap(rect.x() + margin, rect.y() + margin , imageScale, imageScale, item.image.scaled(imageScale, imageScale, Qt.KeepAspectRatio, Qt.SmoothTransformation))

# draw the text

painter.setPen(QColor(255, 255, 255))
font = painter.font()
font.setPointSize(9)
painter.setFont(font)

painter.drawText(rect.x() + imageScale + margin * 3, rect.y(), 300, item.scale, Qt.AlignLeft|Qt.AlignVCenter, item.name)

# draw the expander button only if the item has children
if item.childCount():

# choose the appropriate icon to draw depending on the state of the item.
if item.isExpanded():
path = "c:\\test.png"
if item.hover:
path = "c:\\test.png"
else:
path = "c:\\test.png"
if item.hover:
path = "c:\\test.png"
image = QPixmap.fromImage(QImage(path))
size = self.tree.expanderSize

# define the position of the expander button

positionX = rect.x() + rect.width() - size - 10
positionY = rect.y() + item.scale / 2 - size/2

painter.drawPixmap(positionX, positionY, size, size, image.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation))

item.expanderStart = QPoint(positionX, positionY)
item.expanderEnd = QPoint(positionX + size, positionY + size)

painter.restore()

def editorEvent(self, event, model, option, index):


# if an item is clicked, check if the click happened in the area whee the expander button is drawn.
if event.type() == QEvent.MouseButtonPress:
item = self.tree.itemFromIndex(index)
if item.childCount():
rect = option.rect
clickX = event.x()
clickY = event.y()
size = self.tree.expanderSize
# this is the rect of the expander button
x = rect.x() + rect.width() - 20
y = rect.y() + item.scale / 2 - size/2
w = size # expander width
h = size # expander height
# set the expanded expanded if it was clicked
if (clickX > item.expanderStart.x()
and clickX < item.expanderEnd.x()
and clickY > item.expanderStart.y()
and clickY < item.expanderEnd.y()):
print "expand"
item.setExpanded(not item.isExpanded())


class myTree(QTreeWidget):
def __init__(self, parent = None):
super(myTree, self).__init__(parent)
self.setMouseTracking(True)
self.setHeaderHidden(True)
self.setRootIsDecorated(False)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def mouseMoveEvent(self, event):
item = self.itemAt(event.pos())
if item:
if item.childCount():
# get the current hovering state. if the item is already hovered, there is no need to repaint it.
hover = item.hover
if (event.pos() .x() > item.expanderStart.x()
and event.pos() .x() < item.expanderEnd.x()
and event.pos() .y() > item.expanderStart.y()
and event.pos() .y() < item.expanderEnd.y()):
item.hover = True
else:
item.hover = False
if item.hover != hover:
self.viewport().update(event.pos().x(), event.pos().y(), 20, 20)
print "Hover", item.hover
def closeEvent(self, event):
self.deleteLater()

def generateTree():
tree = myTree()
tree.setGeometry(500, 500, 1000, 500)
tree.expanderSize = 50
delegate = styleDelegate(tree, treeWidget = tree)
tree.setItemDelegate(delegate)

for object in ["Aaaaaaa", "Bbbbbbb", "Ccccccc"]:
item = QTreeWidgetItem()
item.name = object
item.image = QPixmap.fromImage(QImage("c:\\test.png"))
item.color = [150, 150, 150]
item.hover = False
item.scale = 100
tree.addTopLevelItem(item)
item.setSizeHint(0, QSize(item.scale, item.scale ))
for child in ["Eeeeee", "Fffffff"]:
childItem = QTreeWidgetItem()
childItem.name = child
childItem.image = QPixmap.fromImage(QImage("c:\\test.png"))
childItem.color = [150, 150, 150]
childItem.scale = 90

item.addChild(childItem)
childItem.setSizeHint(0, QSize(childItem.scale, childItem.scale))
return tree

tree = generateTree()
tree.show()

请注意,我的显示器是 4k,并且我很快对大部分尺寸进行了硬编码,因此开箱即用的此代码将在高清显示器上生成更大的小部件。

最佳答案

您的代码有以下错误:

  • 不一定要使用QPixmap.fromImage(QImage(path)),可以直接用路径创建QPixmap:QPixmap (路径)

  • 如果它们是相同的图像,最好加载一次并重复使用它,例如在我的解决方案中,我为按钮 QPixmap 执行此操作。

  • 不要创建动态属性,因为它会生成代码耦合,对于项目,您必须使用角色。

  • 要知道某个项目是否已展开,或者您不应该使用QStyle::State_Open,这样我们就可以避免耦合,并且委托(delegate)可以由其他 View 使用,而无需进行太多更改。

  • 使用 QRect 来界定矩形,例如,您可以使用 contains 来查看某个点是否在矩形内。

以上是主要的观察结果,下面是解决方案:

from PySide2 import QtCore, QtGui, QtWidgets
from enum import Enum

ScaleRole= QtCore.Qt.UserRole + 1
expanderSize = 50


class TreeDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(TreeDelegate, self).__init__(parent)

self.pixmap_collapsed = QtGui.QPixmap("collapsed.png")
self.pixmap_collapsed_hover = QtGui.QPixmap("collapsed_hover.png")
self.pixmap_expanded = QtGui.QPixmap("expanded.png")
self.pixmap_expanded_hover = QtGui.QPixmap("expanded_hover.png")

def paint(self, painter, option, index):
image = index.data(QtCore.Qt.DecorationRole)
scale = index.data(ScaleRole)
name = index.data()

painter.save()
rect = option.rect
painter.setPen(QtGui.QPen(brush=QtGui.QColor(43, 43, 43), widthF=1))
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(option.palette.highlight())
else:
painter.setBrush(index.data(QtCore.Qt.BackgroundRole))
painter.drawRect(rect)

margin = 4
image_scale = (rect.height() - margin * 2 + 1)*QtCore.QSize(1, 1)
if image is not None and not image.isNull():
image = image.scaled(image_scale, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
painter.drawPixmap(rect.topLeft() + margin*QtCore.QPoint(1, 1), image)

painter.setPen(QtGui.QColor(255, 255, 255))
font = painter.font()
font.setPointSize(9)
painter.setFont(font)
painter.drawText(QtCore.QRect(rect.topLeft() + QtCore.QPoint(image_scale.width() + 3*margin, 0) , QtCore.QSize(300, scale)),
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, name)

if index.model().hasChildren(index):
pixmap = self.pixmap_collapsed
if option.state & QtWidgets.QStyle.State_Open:
if option.state & QtWidgets.QStyle.State_MouseOver:
pixmap = self.pixmap_expanded_hover
else:
pixmap = self.pixmap_expanded
else :
if option.state & QtWidgets.QStyle.State_MouseOver:
pixmap = self.pixmap_collapsed_hover
size = expanderSize
pixmap = pixmap.scaled(size*QtCore.QSize(1, 1), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
pos = rect.topRight() - QtCore.QPoint(size+10, (size-scale)/2)
painter.drawPixmap(pos, pixmap)
painter.restore()


class MyTreeItem(QtWidgets.QTreeWidgetItem):
def __init__(self, name, image, color, scale):
super(MyTreeItem, self).__init__([name])
self.setData(0, ScaleRole, scale)
self.setData(0, QtCore.Qt.BackgroundRole, color)
self.setData(0, QtCore.Qt.DecorationRole, image)


class MyTree(QtWidgets.QTreeWidget):
def __init__(self, parent=None):
super(MyTree, self).__init__(parent)
self.setMouseTracking(True)
self.setHeaderHidden(True)
self.setRootIsDecorated(False)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

def mousePressEvent(self, event):

if not self.itemsExpandable(): return
index = self.indexAt(event.pos())
if not index.isValid(): return
# restore state
is_expanded = self.isExpanded(index)
QtWidgets.QAbstractItemView.mousePressEvent(self, event)
self.setExpanded(index, is_expanded)

if not self.model().hasChildren(index): return
rect = self.visualRect(index)
size = expanderSize
scale = index.data(ScaleRole)
pos = rect.topRight() - QtCore.QPoint(size+10, (size-scale)/2)
r = QtCore.QRect(pos, size*QtCore.QSize(1, 1))
if r.contains(event.pos()):
self.setExpanded(index, not self.isExpanded(index))


def generate_tree():
tree = MyTree()
scale = 100
delegate = TreeDelegate(tree)
tree.setItemDelegate(delegate)

for text in ["Aaaaaaa", "Bbbbbbb", "Ccccccc"]:
item = MyTreeItem(text, QtGui.QPixmap("image.png"), QtGui.QColor(150, 150, 150), scale)
item.setSizeHint(0, QtCore.QSize(scale, scale))
tree.addTopLevelItem(item)
for child in ["Eeeeee", "Fffffff"]:
childItem = MyTreeItem(child, QtGui.QPixmap("image.png"), QtGui.QColor(150, 150, 150), scale)
childItem.setSizeHint(0, QtCore.QSize(scale, scale))
item.addChild(childItem)
return tree


if __name__ == '__main__':
import sys

app = QtWidgets.QApplication(sys.argv)
tree = generate_tree()
tree.show()
sys.exit(app.exec_())

关于python - 带有自定义按钮的 QStyledItemDelegate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52825132/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com