[PyQt] QTabWidget's tab bar behavior?

Matic Kukovec kukovecmatic at hotmail.com
Sat May 25 23:18:38 BST 2019


Il giorno sab 25 mag 2019 alle ore 20:18 Matic Kukovec <kukovecmatic at hotmail.com<mailto:kukovecmatic at hotmail.com>> ha scritto:
I made a QTabWidget with a custom tabBar in which I manually add a QGroupBox with QLabel
that acts as a close button with self.setTabButton(index, QTabBar.RightSide, groupbox).

First of all, why do you use a QGroupBox? As far as I understand, you are using it as a close button, but it doesn't seem to add any major advantage or implementation. If it's for look purposes, just go with stylesheets or custom painting. If it's to extend the TabBar capabilities, extend them in the TabBar; assigning functionality to a button that doesn't seem to belong to it doesn't make much sense.


I don't exactly know what caused this? I wish to get the standard behaviour back, can anyone help?

I feel like you've being overshooting, as your code seems too complicated than it should.
For example: why do you need to constantly change the close "buttons" everytime the tab order is changed? As far as I can tell, the only thing that changes is their index, and you seem to want to hide/show them accordingly; why not just hide the button and/or change its index property?

I believe that the issue you're facing comes from the fact that you're constantly setting a new widget as the tabButton, which might interfere with widget size and positioning that occur while moving tabs (hence the odd graphical and mouse interaction behavior). As soon as I disable the _tab_moved_slot and _current_tab_changed connection slots, the QTabBar behaves as expected.

You might want to think about a different and easier approach, and possibly rethink the whole concept you use while keeping it as simple as possible.


If I may, I also have some (unrequested) suggestions.

First of all: why the unnecessary int() for every QSize declaration? You don't even need to use QSize for set[*]Size, as it also accepts numeric values. Moreover, you can even use floats, as they're automatically transformed to integers.

Then, If you need button-style behavior, just use a button :-)
You can use stylesheets to set icons, I also just discovered (thanks to you!) that you can set widgets graphical properties within the stylesheets like this:

        button.setStyleSheet('''
            QPushButton {
                border: none;
                qproperty-icon: url(up-icon.svg) off,
                url(down-icon.svg) on;
            }
        ''')

This allows you to set icons for both down and up states. Unfortunately, there are some bugs with this. First of all, a still unsolved bug prevents to set (some) properties for pseudo states, which means that you can't set an icon for the :hover state. Also, it seems that setting (some) other properties in the stylesheet (such as the border) prevents the successful update of the state icon. Nonetheless, since you only need a button with an icon and without text, you can use the simpler image syntax:

        button.setStyleSheet('''
            QPushButton {
                border: 1px solid red;
                image: url(up-icon.svg);
            }
            QPushButton:hover {
                border: 1px solid green;
                image: url(down-icon.svg);
            }
        ''')

This works as long as there's no button text: whenever you set it, you'll see that the text is overimposed on the icon, and there's no known method of setting the background alignment according to the button's text (or vice versa), since no margin or padding setting will correctly affect the result.
If you need to do more complex interaction, set an alternate stylesheet for enterEvent and set it back on leaveEvent, but at this point you could obviously use setIcon() as well.

Somehow trivial, but it's a good practice: as PEP8 suggests, it's always better to use "if cond is [not]" comparison whenever a singleton comes in place; as far as I know of, None is the most important one to keep in mind, as it's an actual singleton in both Python 2 and 3 (you can't create an instance of None), while technically you could create a new "True" variable in Python 2. While "cond == None" works, it's not a preferred pattern, not from a programming perspective, but mostly from a readability one (which also means better and easier debugging). That said, the problem also comes whenever you're using comparisons in a "light" fashion: obviously, a simple "if not cond" doesn't work as expected if you're expecting None and you get False, 0, or an empty string.

Also, remember that if you use external resources (like the button pixmaps in your case) you should attach them. While it wasn't much of an issue in your case, it might be a problem in other situations: QPixmaps that are init-ed with a non existing file are null pixmaps, so their size is invalid, which might become a problem if, for example, the issue at hand is about painting or sizing.

Finally, when attaching an example, it's always better to make it as minimal as possible. When people see hundreds of lines of code and find that it's overly complicated, they might be discouraged in trying to help you, since they'd lose much of their time trying to understand what you're really trying to achieve while being lost in dozens of unnecessary declarations, functions and methods. I know it can be annoying, but it really does help: first of all, in my experience I've learnt that recreating the example usually results in being able to find the solution on your own (see https://en.wikipedia.org/wiki/Rubber_duck_debugging ), but, anyway, it's always better to lose 15-20 minutes of your time "minimizing" your example (with the possibility of even finding the solution on your own) than losing the opportunity of having somebody who could actually help you but didn't want (or wasn't able) to find the time to "filter out" your code.

Good luck,
Maurizio


--
È difficile avere una convinzione precisa quando si parla delle ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net

Hey Mauricio,

Thanks for pinpointing that commenting out _tab_moved_slot and _current_tab_changed signals.
Now I know where to look at, excellent.

Excellent find on the button stylesheets, I did not know you could do that!

You asked why I'm using a groupbox? It's because there needs to be the ability to add as many buttons as needed to any tab.
Why the constant hiding/showing of different buttons when the index changes? That is because only the close-button is shown on non-active tab,
while all-other-buttons + close-button are shown only on the active tab. Plus there has to be an ability to hide any button on a particular tab as needed, even if it is the active tab.
So in short, I have two groupboxes:

  *   one that holds the close-button (in my complete code it can also be none if the tab is not closable)
  *   one that holds all additionally added buttons plus a close button, if the tab is closable

which I use to switch according to the activity of the tab when the current index changes. That is why there is so much code
for updating the tab index, and also for resizing when the whole application scale factor changes.
If you have any suggestions of how to make this simpler, I would be glad to hear it.

Thanks for the PEP8/None suggestion. I have been trying to hold to it, but sometimes I just forget, sorry.

As far as the example, I apologize, I forgot to attach the images. I also tried to minimize the code without breaking anything,
that is why there was stuff like int(20) in it, as before it was a more complex expression.
I am attaching the more complete example with the images. In the example code pressing the test button adds a new tab with
an additional tab-button on it, that is only visible when the tab is active, so you can see the actual behavior at runtime.

Thanks,
Matic
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190525/d7549a71/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: QTabBar_test.zip
Type: application/x-zip-compressed
Size: 53505 bytes
Desc: QTabBar_test.zip
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190525/d7549a71/attachment-0001.bin>


More information about the PyQt mailing list