How to stop a checkable button from automatically rendering differently when checked?
-
I have an app with several checkable QToolButtons. By default, when a button becomes checked, the button will darken to indicate that it is checked.
I want one of the buttons to change its icon instead of darkening when checked. I managed to get the icon to change depending on state. However, the button still darkens when checked. Is there a way to turn off that default behavior for that one button specifically?
-
@RickyRister
I have not done this, but here is my understanding. Although you do not say, I presume you have used aQIcon
for the icon of theQToolButton
.A tool button's icon is set as QIcon. This makes it possible to specify different pixmaps for the disabled and active state. The disabled pixmap is used when the button's functionality is not available. The active pixmap is displayed when the button is auto-raised because the mouse pointer is hovering over it.
QIcon
s have a number of different modes/states. Look at https://doc.qt.io/qt-6/qicon.html#making-classes-that-use-qicon where these are shown. I think your case is the bottom right one, On and Selected?So you want to alter that. I guess either you call void QIcon::addPixmap(const QPixmap &pixmap, QIcon::Mode mode = Normal, QIcon::State state = Off) to explicitly add your desired pixmap without the "darkening" for the mode/state you want to change or you cause it to draw/paint as
QIcon::Off
when asked to draw itQIcon::On
. -
@JonB
Yes, I am using a QIcon. This is my code currentlyplayButton = new QToolButton; QIcon icon = QIcon(); icon.addPixmap(QPixmap("theme:replay/start"), QIcon::Normal, QIcon::Off); icon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On); playButton->setIcon(icon); playButton->setCheckable(true);
The icon displayed on the button correctly changes to match the state. However, when the button is checked, it is darkened in addition to changing the icon. I would like to make it so that only the icon changes when the button is checked, without the button also darkening.
-
@RickyRister
I can only guess. Did you look at https://doc.qt.io/qt-6/qicon.html#making-classes-that-use-qicon as I suggested? The only ones there I can see "darkened", excluding Disabled, is Selected. Is your checked icon darkened because/only while it is selected? Did you tryaddPixmap(..., QIcon::Selected, QIcon::On)
? -
Try a stylesheet, where you specify the
QToolButton:checked { ..... }
style -
Is there something I can put in the
QToolButton:checked { ..... }
that would make it inherit the color from the unchecked button?
The app supports multiple color themes, so I don't want to hardcode the color on that one button. -
This post is deleted!
-
Using a stylesheet is also the first thing that comes to mind. But, I don't think there is a way to inherit the unchecked style.
The next solution will be a little bit more involved (and you have to do some research because I'm fuzzy on the details). QWidget has a function
setStyle()
. The QStyle is responsible for actually drawing GUI elements (usually forwarding to system drawing functions). You could inherit from QStyle and override drawControl(). Just handle the one specific case of a checked tool button (just draw it unchecked). Everything else you can then forward to QApplication::style() for rendering. Then, set this style on the tool button(s). -
I tried to using
QProxyStyle
. It didn't work. My code is as follows// in the .h file class UnchangingCheckedButtonStyle : public QProxyStyle { public: void drawControl(ControlElement, const QStyleOption *, QPainter *, const QWidget * = nullptr) const; }; // in the .cpp file void UnchangingCheckedButtonStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (option->state & State_On){ QStyleOption *newOption = new QStyleOption(*option); newOption->state.setFlag(State_On, false); QProxyStyle::drawControl(element, newOption, painter, widget); } else { QProxyStyle::drawControl(element, option, painter, widget); } }
// in the function for creating the button playButton = new QToolButton; QIcon icon = QIcon(); icon.addPixmap(QPixmap("theme:replay/start"), QIcon::Normal, QIcon::Off); icon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On); playButton->setIcon(icon); playButton->setCheckable(true); auto style = new UnchangingCheckedButtonStyle(); style->setParent(playButton); playButton->setStyle(style);
The result is that the icon now disappears if the button is checked, but the button still darkens if checked! So seems like proxying the
drawControl
is in fact doing something, but whatever controls the color of the button when checked is not going throughdrawControl
. -
@RickyRister said in How to stop a checkable button from automatically rendering differently when checked?:
but whatever controls the color of the button when checked is not going through drawControl.
You could check the source code of
QAbstractButton
/QToolButton
yourself and see how it's done -
@Pl45m4 said in How to stop a checkable button from automatically rendering differently when checked?:
You could check the source code of QAbstractButton / QToolButton yourself and see how it's done
Better the style he's using to see what triggers the darkening. Otoh when the style does this I don't understand why a QToolButton should behave differently only for his application.
-
First thing to check is to have drawControl just always forward to QProxyStyle. My guess is that this works.
What immediately caught my eye (but it is not your current problem) is that you leak the pointer to newOption. I would suggest that you just change the option you are given without making a copy.
Here is my best guess what's wrong: You do delete the QStyle::State_On flag. However, you didn't turn on the QStyle::State_Off flag. This might be the problem.
-
First thing to check is to have drawControl just always forward to QProxyStyle. My guess is that this works.
Yes, this indeed works
What immediately caught my eye (but it is not your current problem) is that you leak the pointer to newOption. I would suggest that you just change the option you are given without making a copy.
How would I go about doing this? I can't modify option directly because it's
const
.Here is my best guess what's wrong: You do delete the QStyle::State_On flag. However, you didn't turn on the QStyle::State_Off flag. This might be the problem.
Tried toggling both flags. Same behavior (icon disappears but button still darkens)
void UnchangingCheckedButtonStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (option->state & State_On){ QStyleOption *newOption = new QStyleOption(*option); newOption->state.setFlag(State_On, false); newOption->state.setFlag(State_Off, true); QProxyStyle::drawControl(element, newOption, painter, widget); } else { QProxyStyle::drawControl(element, option, painter, widget); } }
-
@RickyRister said in How to stop a checkable button from automatically rendering differently when checked?:
How would I go about doing this? I can't modify option directly because it's const.
Alright, I didn't see that it is const. Still, you can just use a local variable:
QStyleOption newOption = *option; ... QProxyStyle::drawControl(element, &newOption, painter, widget);
@RickyRister said in How to stop a checkable button from automatically rendering differently when checked?:
Tried toggling both flags. Same behavior (icon disappears but button still darkens)
I did some more reading of the documentation for
QStyle::DrawControl
. It says the following:The option parameter is a pointer to a QStyleOption object that can be cast to the correct subclass using the qstyleoption_cast() function.
Further down, it says in the table that the correct style option seems to be a QStyleOptionToolButton. So, just copying the
option
variable will splice the object, i.e. it will only copy the information that is contained in the QStyleOption class, but will ommit everything that has been inherited. My guess (I haven't tried it myself) is to do the following instead:QStyleOptionToolButton newOption = *qstyleoption_cast<const QStyleOptionToolButton *>(option);
-
Alright, I didn't see that it is const. Still, you can just use a local variable
Good catch
Further down, it says in the table that the correct style option seems to be a QStyleOptionToolButton. So, just copying the
option
variable will splice the object, i.e. it will only copy the information that is contained in the QStyleOption class, but will ommit everything that has been inherited. My guess (I haven't tried it myself) is to do the following instead:QStyleOptionToolButton newOption = *qstyleoption_cast<const QStyleOptionToolButton *>(option);
Making this change did cause something to change! The icon no longer disappears when checked; it will stay the same instead. ...The button still darkens though.
At this point, I'm starting to suspect that the button color is solely controlled through the stylesheet, and that the stylesheet bypasses
drawControl
. -
@RickyRister said in How to stop a checkable button from automatically rendering differently when checked?:
the stylesheet
You are funny - giving such an important information after five days... and not even showing us the stylesheet you're using.
As I already wrote - directly look in the style code instead guessing five days. -
The app doesn't set a stylesheet by default, so it's just using the default style for whatever OS it's on. Which apparently just gives me an empty string if I call
app.stylesheet()
. -
@RickyRister said in How to stop a checkable button from automatically rendering differently when checked?:
The icon no longer disappears when checked; it will stay the same instead.
This is not totally unexpected: You say that the style should draw the tool button in the on state, so it does (meaning it uses the icon for the on state). You also get handed in a QWidget pointer which should be the QToolButton. You could make a copy of this and change the icon for the on state to that of the off state and render that.
I'm out of ideas why it is still drawn dark. Maybe you can check which style options are actually set. Have a look at the options for the regular on state and the off state (and also post them). There might be yet another difference besides State_On/State_Off.