QTabBar tab text painting?
Maurizio Berti
maurizio.berti at gmail.com
Sat Jun 5 02:47:58 BST 2021
Il giorno ven 4 giu 2021 alle ore 18:48 Matic Kukovec <
kukovecmatic at hotmail.com> ha scritto:
> The idea is that there are multiple QTabWidget (with their respective
> tabbar's) in the window and the one that has focus is active, the others
> are not.
> When an inactive QTabWidget becomes active, and with it, it's tabbar, it
> needs a style update:
> either an update with "*setStyleSheet*" or adding a property (for this
> example let's call the property "*active*") to the style sheet
>
The result shown in the animation is probably a "non-bug":
polishing/unpolishing and updating the stylesheet (which does the same
thing) can result in unexpected behavior, especially for complex widgets
like QTabBar *and* when the platform style is in effect.
Consider that the tabs' translation and painting is completely private and
managed by the style: there's no API to directly control the offset, and
you can only "deduce" it using QTabBar.tabRect. If deeper and advanced
control is required, you need to create your own tab widget (using
QStackedWidget, which is exactly what QTabWidget does) and create a custom
tab bar. Not easy, not painless, but feasible.
>From what I can see, though, those tabs look pretty simple, so a possible
solution could be to *completely* style the QTabBar and properly set all
the required pseudo states, which *should* theoretically work around the
issue.
The problem of the non colored active tab could come from a combination of
factors, but it's a bit tricky to find the actual source. *Theoretically*,
drawControl(CE_TabBarTabLabel) *should* use the default QCommonStyle
drawItemText using the WindowText role (so, it should work since you set
that too), but QStyle implementation is complex and there could be some bug
somewhere.
A possible fix for that could be to *force* the fusion style (which is
usually more stylesheet-compliant) for the tab bar only:
self.setStyle(QtWidgets.QStyleFactory.create('fusion'))
You would need to properly create the icons for the close button, but since
it seems that you're already setting a custom widget for that, it shouldn't
be a problem.
Alternatively, you could set a QProxyStyle for the custom tab bars, and
override the drawItemText: in this way you'll know if it gets actually
called and eventually find a way to change the color (be aware that
drawItemText doesn't receive any usable property, so you'll need to
manually set an attribute before calling drawControl).
Anyway, if you could provide a minimal reproducible example of your
situation, we might probably find alternative solutions or, at least,
verify that there's actually a bug in the windowsvista style.
Finally, but unrelated.
If you want to set the same color for all groups, you can do it more easily
(and it'll be faster too):
- setColor is actually an override of setBrush (see
https://code.woboq.org/qt5/qtbase/src/gui/kernel/qpalette.h.html );
- the setColor/setBrush overrides that don't use the group, actually set
the color for *all* groups;
- QPalette has two useful variables named "All" and "NColorRoles"; using
"All" results in calling setBrush for all color groups (similarly to
setBrush/setColor without the group), while you can cycle through all roles
to set the individual colors;
With the following code you can get the exact same result, but with much
better performance: you don't need to create tuples everytime, and most of
the job is done on the C++ side).
palette = self.palette()
brush = QtGui.QBrush(QtGui.QColor('red'))
for role in range(palette.NColorRoles):
palette.setBrush(palette.All, role, brush)
Consider that, due to the changes in PyQt6, the "role" variable should
probably be converted using the enum:
palette.setBrush(palette.All, palette.ColorRole(role), brush)
Cheers,
Maurizio.
--
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
Il giorno ven 4 giu 2021 alle ore 18:48 Matic Kukovec <
kukovecmatic at hotmail.com> ha scritto:
> Hello Joshua,
>
> Thanks for the information.
> The idea is that there are multiple QTabWidget (with their respective
> tabbar's) in the window and the one that has focus is active,
> the others are not.
> When an inactive QTabWidget becomes active, and with it, it's tabbar, it
> needs a style update:
> either an update with "*setStyleSheet*" or adding a property (for this
> example let's call the property "*active*") to the style sheet,
> setting the "*active*" property to *True* and then using "*QTabBar.unpolish();
> QTabBar.polish()"* to refresh it. If you
> don't do the *polish/unpolish*, the changing of the property does
> nothing. For example, the selected tab has this style in the stylesheet:
>
> QTabBar[indicated=false]::tab::selected {
> background-color: #ffffffff;
> color: #ff000000;
> font: normal;
> }
> QTabBar[indicated=true]::tab::selected {
> background-color: #ff0000ff;
> color: #ffffffff;
> font: bold;
> }
>
> Now, when doing this (either with *setStyleSheet *or updating the
> property) the tabs *snaps *to the right, like this:
>
> I would love it if this could be done with stylesheets and without the
> snap, but I don't know how.
>
> Any further information would be greatly appreciated!
>
> Thanks,
> Matic
> ------------------------------
> *From:* RedHuli <redhuli.comments at gmail.com>
> *Sent:* Friday, June 4, 2021 5:13 PM
> *To:* Matic Kukovec <kukovecmatic at hotmail.com>;
> pyqt at riverbankcomputing.com <pyqt at riverbankcomputing.com>
> *Subject:* Re: QTabBar tab text painting?
>
> Hello Matic,
>
> I was looking at your issue and was trying to think how to help. But I was
> confused by what you said:
>
> and the tabbar's view snaps to the right. This has been very annoying for
> users as when they switch
> to an inactive QTabWidget that becomes active, the whole tabbar view
> changes.
>
> Do you mean that the tabs actually snap to the right, like move? Would you
> care to explain just a bit more?
>
> As for inactive and active tabs,
>
> style_sheet = """QTabBar::tab {background: grey; }
> QTabBar::tab:selected { color: red; }
> QTabBar::tab:!selected {color: blue; }"""
> self.tabWidget.setStyleSheet(style_sheet)
>
> Not sure if that helps your case, I know you said stylesheets aren't
> helping. But it is possible to change the color and appearance of a tab
> depending upon whether or not it is selected with style sheets. Was trying
> to understand more how your QTabWidget is set up to better help. Perhaps
> manipulating the style sheets more may help give the tabs an appearance of
> enabled or disabled. Not sure if you have looked into that option.
>
> You can also play around with the QTabWidget::pane and QTabWidget::tab-bar
> using style sheets, perhaps changing their alignment and position depending
> upon your circumstances.
>
> I have always found it easier to manipulate the appearance of widgets with
> style sheets, rather than using QPalette.
>
> Joshua Willman
>
> On Fri, Jun 4, 2021 at 10:04 PM Matic Kukovec <kukovecmatic at hotmail.com>
> wrote:
>
> Hello Maurizio,
>
> You are right about the superfluous stuff, I copy pasted from an actual
> working example.
> The painter save/restore is still in there because I forgot to remove it
> in this example.
> And yes, *data *is the PyQt5 package with all sub-packages, all of them
> in one namespace.
>
> The reason stylesheets are not usable in my case, is that I need to switch
> between an
> active and inactive mode, and when setting a new stylesheet, the scroll
> offset is not remembered
> and the tabbar's view snaps to the right. This has been very annoying for
> users as when they switch
> to an inactive QTabWidget that becomes active, the whole tabbar view
> changes. When you have
> a lot of tabs, then the user sometimes doesn't know where the active tab
> "jumped" to.
> That is why I'm using the overridden *paintEvent*.
>
> Anyway, the main issue here is that you're probably using Windows, and the
> Windows QStyle does paint some things a bit differently, including possibly
> ignoring the palette of the style option (but, hey, that could also be a
> bug, unfortunately I cannot test it).
>
> Yes, I'm on Windows.
>
> Thanks for the help, I will investigate further.
> If anyone can shed more light on where the problem could be, please let me
> know.
>
> Regards,
> Matic
> ------------------------------
> *From:* Maurizio Berti <maurizio.berti at gmail.com>
> *Sent:* Friday, June 4, 2021 3:46 PM
> *To:* Matic Kukovec <kukovecmatic at hotmail.com>
> *Cc:* pyqt at riverbankcomputing.com <pyqt at riverbankcomputing.com>
> *Subject:* Re: QTabBar tab text painting?
>
> Hello Matic,
>
> The whole resetting the palette in the for loop is completely unnecessary
> (especially if done every time in a for loop, you can create a palette the
> first time, and then overwrite opt.palette, but that would be unnecessary
> anyway since the palette should not change between tabs).
> Also, the painter save/restore is completely pointless like that: the
> point of saving the state of the painter is that you can temporarily change
> painter settings and restore the previous state (which should also match
> the state level: it's not really clear since the formatting is not
> monospaced, but it seems that you're calling painter.restore() in the for
> loop, and if that's the case, you should not, as you'd have
> unmatched levels of saved states). Since you're not painting anything
> before or after that, there's no benefit.
>
> Anyway, the main issue here is that you're probably using Windows, and the
> Windows QStyle does paint some things a bit differently, including possibly
> ignoring the palette of the style option (but, hey, that could also be a
> bug, unfortunately I cannot test it).
>
> That said, if you only want to change the color of the tab text,
> subclassing and overriding the paintEvent is a bit too much (and also
> risky: your implementation doesn't take into account movable tabs, which is
> really tricky). I'd suggest a more simpler:
>
> self.tabWidget.setStyleSheet('QTabBar { color: red; }')
>
> Which should solve the whole problem on any platform.
>
> Be aware that it's very important that you use the class selector (unless
> you set the stylesheet on the tab bar), if you use generic/universal
> properties you'll get unexpected results, especially for a container widget
> such a QTabWidget.
> So, *never* do the following: it works, but it works very badly.
>
> self.tabWidget.setStyleSheet('color: red') # NO!
>
>
> Maurizio
>
> PS: it took me a while to understand what "data" actually was; I'd
> strongly suggest you against that choice, "data" is a very generic word and
> doesn't reflect what it refers to in your code (and it's also very
> confusing to others reading your code): what happens whenever you have a
> function that *actually* uses some "data"? Why should you then choose
> another word instead of an obvious name, because you already used that name
> for a wrong reference and you cannot overwrite it because it's required in
> the function scope?
>
> Il giorno ven 4 giu 2021 alle ore 10:26 Matic Kukovec <
> kukovecmatic at hotmail.com> ha scritto:
>
> Hi guys,
>
> I have a subclassed QTabBar which I want to manually change the color of
> each tab.
> After a lot of trial-and-error, I tried this to make all the tabs red:
>
> *def paintEvent(self, event):*
> * painter = data.QStylePainter(self)*
> * opt = data.QStyleOptionTab()*
> * painter.save()*
>
>
> * for i in range(self.count()):*
> * self.initStyleOption(opt, i)*
> * items = (*
> * data.QPalette.Window,*
> * data.QPalette.Background,*
> * data.QPalette.WindowText,*
> * data.QPalette.Foreground,*
> * data.QPalette.Base,*
> * data.QPalette.AlternateBase,*
> * data.QPalette.ToolTipBase,*
> * data.QPalette.ToolTipText,*
> * data.QPalette.PlaceholderText,*
> * data.QPalette.Text,*
> * data.QPalette.Button,*
> * data.QPalette.ButtonText,*
> * data.QPalette.BrightText,*
> * data.QPalette.Light,*
> * data.QPalette.Midlight,*
> * data.QPalette.Dark,*
> * data.QPalette.Mid,*
> * data.QPalette.Shadow,*
> * data.QPalette.Highlight,*
> * data.QPalette.HighlightedText,*
> * data.QPalette.Link,*
> * data.QPalette.LinkVisited,*
> * )*
> * groups = (*
> * data.QPalette.Disabled,*
> * data.QPalette.Active,*
> * data.QPalette.Inactive,*
> * data.QPalette.Normal,*
> * )*
> * color = data.QColor(0xffff0000)*
> * for g in groups:*
> * for p in items:*
> * opt.palette.setColor(g, p, color)*
> * opt.palette.setBrush(g, p, color)*
> * painter.setBrush(data.QBrush(data.QColor(color)))*
> * painter.setPen(data.QPen(data.QColor(color)))*
> * painter.drawControl(data.QStyle.CE_TabBarTabLabel, opt)*
>
>
> * painter.restore()*
>
> Now this works, but only for the non-currently selected tabs, the selected
> tab ALWAYS has black text and I have no idea why.
> Here is a screenshot:
> The "string.h" tab is the currently selected one.
>
> Any ideas what I'm doing wrong or missing?
>
> Thanks,
> Matic
>
>
>
> --
> È difficile avere una convinzione precisa quando si parla delle ragioni
> del cuore. - "Sostiene Pereira", Antonio Tabucchi
> http://www.jidesk.net
>
>
--
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20210605/56bfbf0b/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 4559 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20210605/56bfbf0b/attachment-0001.png>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0nwSUxGGEN.gif
Type: image/gif
Size: 68878 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20210605/56bfbf0b/attachment-0001.gif>
More information about the PyQt
mailing list