基于父 QTableWidget 的 :selected 伪状态对 QTableWidget 的 cellWidget 进行样式化

Styling a cellWidget of a QTableWidget based on the :selected pseudo-state of the parent QTableWidget

我有一个带有一些行的 QTableWidget。

在每一行中,其中一个单元格通过 setCellWidget 设置了另一个小部件。

我想根据行是否被选中来设置此 cellWidget 的样式。作为参考,cellWidget 是另一个 QTableWidget,但它是 disabled/not editable 并且本质上是只读的。

我找到了访问子控件的语法(特别是访问父 QTableWidget 的项目)——即 MainTable::item https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-sub-controls

我还找到了(更标准的)css-用于访问控件伪状态的语法——即 MainTable::item:selectedhttps://doc.qt.io/qt-5/stylesheet-reference.html#list-of-pseudo-states

如果我天真地使用它来将所选项目(table行)设置为黄色,如下所示

  def add_file(self, row, element):
    """populate a new row in the table"""
    # self is the parent QTableWidget
    self.setRowHeight(row, self.ICON_SIZE.height())
    img = self.create_thumbnail(element['filepath'])

    # add an image to the first column
    item = QTableWidgetItem("",0)
    item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
    item.setData(Qt.DecorationRole, img)
    item.setData(Qt.TextAlignmentRole, Qt.AlignHCenter|Qt.AlignCenter)
    item.setData(Qt.SizeHintRole, self.ICON_SIZE)
    self.setItem(row, self.THUMBCOL, item)

    # StatsTable is another nested QTableWidget
    stats = StatsTable(element)
    # add the new nested table to the outer main tables second column
    self.setCellWidget(row, self.STATSCOL, stats)

    self.setStyleSheet("""
MainTable::item:selected 
{
  background: yellow;
  color: purple;
}
""")

除了 cellWidget 之外的整行都将具有黄色背景。

现在,如果我修改 QSS 选择器以尝试访问子小部件,我会得到意想不到的结果:

MainTable::item:selected QTableWidget
{
  background: yellow;
  color: purple;
}

这导致每一行的 cellWidget-table 都被赋予黄色背景,而与该行的选择状态无关(不像之前只有选定的行没有嵌套的 table 有一个黄色背景)。

我在这里忽略了一些简单的事情,还是我必须创建一些回调以在选择行时手动应用和取消应用样式?

这是应用了第一个 QSS 的选定行

这是应用了第二个 QSS 的选定行

如果选中该行,这两者都没有设置 cellWidget 样式。

parenthood选择器不能使用subcontrol和pseudo元素,如果选择了whole行,也无法根据选择设置特定item的背景。

项视图的背景使用 Base palette color role 绘制,通常是白色不透明的。您可以做的是覆盖它并使其透明:

def add_file(self, row, element):
    # ...
    palette = stats.palette()
    palette.setColor(palette.Base, QtCore.Qt.transparent)
    stats.setPalette(palette)

遗憾的是,这只会修复背景部分,不会改变显示文本的颜色。为此,您需要了解选择的状态并根据项目选择更新样式表。
您可以连接到 QTableWidget 的 selectionChanged of the main table's selectionModel() (or itemSelectionChanged),然后相应地设置项目样式:

        # somewhere in the __init__
        self.TableQSS = '''
            QTableWidget
            {
              background: yellow;
              color: purple;
            }
        '''
        self.itemSelectionChanged.connect(self.updateTables)

    def updateTables(self):
        selected = self.selectedIndexes()
        for row in range(self.rowCount()):
            table = self.cellWidget(row, self.STATSCOL)
            if not isinstance(table, StatsTable):
                continue
            if self.model().index(row, self.STATSCOL) in selected:
                table.setStyleSheet(self.TableQSS)
            else:
                table.setStyleSheet('')

考虑到样式表和调色板并不总能很好地协同工作,设置调色板颜色通常是首选解决方案,因为它(理论上)更安全,当前样式将使用调色板定义其他颜色,例如渐变,阴影等
所以,继续按照开头解释的方式设置调色板,仍然如上连接itemSelectionChanged信号,然后:

    def updateTables(self):
        # get the default colors for the text from the palette of the main 
        # table (we cannot rely on the child tables as they've been changed)
        basePalette = self.palette()
        colors = [basePalette.color(cg, basePalette.Text) for cg in range(3)]
        selected = self.selectedIndexes()
        for row in range(self.rowCount()):
            table = self.cellWidget(row, self.STATSCOL)
            if not isinstance(table, StatsTable):
                continue
            palette = table.palette()
            if self.model().index(row, self.STATSCOL) in selected:
                palette.setColor(palette.Text, QtGui.QColor('purple'))
            else:
                # restore default colors
                for cg, color in enumerate(colors):
                    palette.setColor(cg, palette.Text, color)
            table.setPalette(palette)

请注意,使用嵌套项目视图通常不是一个好主意,因为它会使事情变得更加复杂(尤其是选择和 keyboard/mouse 交互)并且在某些情况下可能会产生问题。

既然您似乎只需要显示数据,您应该考虑实现自己的项目委托(参见 QStyledItemDelegate) and eventually draw formatted text using a basic HTML table (see )。
或者,使用禁用滚动条的 QPlainTextEdit 并设置为只读模式(在这种情况下,您仍然需要执行上述操作)。

作为使用项目委托的替代方法,我向 itemSelectionChanged 信号添加了回调,并遍历主 table 中的行。我根据是否选择了该行,在 child-table 小部件上设置了一个 属性 值。此 属性 在样式表中访问。

不幸的是,我似乎必须通过整体设置样式表来强制重新计算它,所以看似聪明的解决方法实际上并不是那么聪明。

由于我的嵌套小部件非常受限(只读、禁用因此无法导航到...)我认为我不需要自定义项委托的灵活性,即使它可能更好解决方案。我还希望少于 100 行,因此性能可能不是问题。

  def __init__(self, ...):
    ...
    # called whenever the main table has its selected row(s) change.
    self.itemSelectionChanged.connect(self.update_selection)

  def update_selection(self):
    for row in range(self.rowCount()):
      item = self.item(row, 0)
      widg = self.cellWidget(row, 1)
      if item.isSelected():
        widg.setProperty("row_is_selected", "true")
      else:
        widg.setProperty("row_is_selected", "false")

      # it is apparently necessary to force a recalculation anyway so the
      # above property-change is a roundabout way to adjust the style
      # compared to just setting or removing it below.
      # this forces a recalculation nonetheless.
      widg.setStyleSheet(widg.styleSheet())

  def add_file(self, row, element):
    ...
    stats.setProperty("row_is_selected", "false")
    self.setStyleSheet("""
StatsTable[row_is_selected="true"]
{
  background: yellow;
  color: purple;
}
""")