pythonpyqtqsplitter

Why are my QSplitter's QSplitterHandles refusing to get formatted?


I have a simple QSplitter with a few widgets thrown in:

...
actions_splitter = QSplitter(
    Qt.Horizontal,
    frameShape=QFrame.StyledPanel,
    frameShadow=QFrame.Plain
)
actions_splitter.addWidget(self.systems)
actions_splitter.addWidget(self.events)
actions_splitter.addWidget(self.compass_widget)
actions_splitter.setChildrenCollapsible(False)
...

The resulting splitters function perfectly fine, in that I can hover my mouse over their position, see the cursor change, and drag them around and change the sizes of the neighboring widgets.

The problem is that the handles themselves, the QSplitterHandles, absolutely refuse to have any visual modifications stick to them.

I have tried style sheets:

handle = actions_splitter.handle(0)
handle.setStyleSheet("{background-color: red}")

I have tried adding layouts with widgets to them:

handle = actions_splitter.handle(0)
layout = QHBoxLayout()
widget = QPushButton("HANDLE")
layout.addWidget(widget)
handle.setLayout(layout)

And absolutely nothing is doing anything to change how these handles appear. The only "visual" thing I can change is their width, and that's via QSplitter.setHandleWidth().

Someone please point out the painfully, stupidly obvious thing I'm missing here so I can get on with my life.


Solution

  • Premise

    QSplitterHandle is one of the few very specific widgets that don't seem to follow generic QWidget "rules"; the reason is pretty simple: its aim is normally limited to the usage it's intended for, which is being a QSplitter child.
    Then, you have to consider that, while pretty "liberal", the stylesheet syntax follows basic but important rules, it depends on the underlying QStyle in use and its behavior might change whenever a child widget is very dependant on its parent, as in this case.


    First of all, there's a conceptual problem in your approach: the stylesheet syntax is wrong, as you're trying to use a selector-like syntax (using {braces}) without specifying a selector. With a syntax like that, the stylesheet won't be applied anyway, even with much more "standard" widgets.

    But that's not enough. The QSplitter handle() documentation explains an important point (emphasis mine):

    Returns the handle to the left of (or above) the item in the splitter's layout at the given index, or nullptr if there is no such item. The handle at index 0 is always hidden.

    Even if your syntax were correct, it wouldn't have worked anyway since you tried to apply the stylesheet to the first (hidden) handle.

    So, theoretically, the correct syntax should've been the following:

    self.splitter.handle(1).setStyleSheet("background-color: red;")
    

    even better:

    self.splitter.handle(1).setStyleSheet(
        "QSplitterHandle {background-color: red;}")
    

    or, for all handles:

    self.splitter.setStyleSheet(
        "QSplitterHandle {background-color: red;}")
    

    On the other hand, since QSplitterHandle is a very peculiar widget, its color properties are applied in different ways according to the currently applied QStyle, even when a correct syntax is used. Remember that stylesheets are overrides to the current QStyle, which means that the QStyles states the painting rules, and applies the eventual stylesheet according to those rules.
    For instance, while the "windows" style uses the background color rule of the stylesheet to draw the actual background of the handle, the fusion style uses the background color to draw the "dots" of the splitter only.

    As with other complex widgets (like QComboBox or QAbstractItemView), you need to use specific selectors for sub-controls, as clearly explained in the QSplitter stylesheet documentation; using the selector you ensure that the color rule is applied to the handle as a whole, no matter the style in use:

    self.splitter.setStyleSheet("QSplitter::handle {background-color: red}")