need help on tristate toolbutton creation
-
Hi,
thank you for your attention.
May be, we don't understand each other.
I cannot find any code for tristate-support. Not in QAction nor in QPushButton.
Also QIcon has a boolean as state (QIcon::State).I did not use QIcon::addFile, but QIcon::addPixmap - which I suppose, does the same.
QAction looks like this:
if(d->checkable) { . . . setChecked(!d->checked);
To me, that does not look like tristate-support out of the box.
I haven't highlighted my main question. Maybe you missed it:
I was looking for the code, that selects the pixmap from QIcon@django-Reinhard QIcon operates on states. You can addFile() for each state.
QAction offers tri-state support out of the box via checkable property.
QIcon documentation, first link from my previous post, offers snippet for the custom icon selection depending on state:void MyWidget::drawIcon(QPainter *painter, QPoint pos) { QPixmap pixmap = icon.pixmap(QSize(22, 22), isEnabled() ? QIcon::Normal : QIcon::Disabled, isChecked() ? QIcon::On : QIcon::Off); painter->drawPixmap(pos, pixmap); }
-
Sorry Artur,
but your postings made me feel like you are shitting on me.
I could not find any tristate-support out of the box.The code sample is the only a user function, which does not get called. So you're up on your own :(
Anyway - I was able to solve the problem, but the solution is absolutely ugly and I hate it. But as I don't know any better, I have to live with it.
What turned me mad is the fact, that its not possible to add a subclass of QToolButton to a toolbar (or at least I was not able to solve it).
QToolBar::addAction creates a QToolButton hidden in layout labyrinth and if you add a Subclass instance of QToolButton, toolbar creates a QActionWidget with the subclass attached, but the possibly attached action of the toolbutton subclass gets lost :(
Same is true for any already established action connections.... anyway - here's my solution:
my icon subclass declaration:
class MIcon : public QIcon { public: MIcon(const QString& stdFileName, const QString& selFileName); MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile); static void setDisabledFileName(const QString& fileName); protected: static QString disabledFileName; };
... with this constructor:
MIcon::MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile) : QIcon(normalFile) { addPixmap(QPixmap(activeFile), QIcon::Mode::Active); addPixmap(QPixmap(selectedFile), QIcon::Mode::Selected); }
... the action definition (its an extended Action, so just ignore the conditions):
power = new DynaAction(MIcon(":/res/SK_PowerOff.png" , ":/res/SK_PowerOff_1.png" , ":/res/SK_PowerOn.png") , tr("Poweroff") , new TrueCondition() , new EqualCondition(vm.getModel("taskState"), EMC_TASK_STATE_ENUM::EMC_TASK_STATE_ON) , this); power->setCheckable(true);
... and finally the toolbar creation and handling of tristate:
powerTB = new QToolBar(tr("Power"), this); powerTB->setObjectName("PowerTB"); powerTB->setIconSize(s); powerTB->addAction(power); connect(power, &QAction::triggered, this, [=](bool){ int state = power->property("state").toInt() + 1; QToolButton* btPow = static_cast<QToolButton*>(powerTB->widgetForAction(power)); if (state > 2) state = 0; power->setProperty("state", state); switch (state) { case 1: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Active)); break; case 2: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Selected)); break; default: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Normal)); break; } qDebug() << "power - state == " << state; });
Why I hate this solution?
Well, in my opinion a QAction::triggered callback should be used for functionality only. Not for styling or painting.
So here two worlds are mixed, that should not be mixed ...My conclusion so far: there's absolutely no support for tristate buttons in Qt, but a workaround is possible.
-
@django-Reinhard said in need help on tristate toolbutton creation:
My conclusion so far: there's absolutely no support for tristate buttons in Qt
QCheckbox has tristates, so this is not true.
-
Sorry Artur,
but your postings made me feel like you are shitting on me.
I could not find any tristate-support out of the box.The code sample is the only a user function, which does not get called. So you're up on your own :(
Anyway - I was able to solve the problem, but the solution is absolutely ugly and I hate it. But as I don't know any better, I have to live with it.
What turned me mad is the fact, that its not possible to add a subclass of QToolButton to a toolbar (or at least I was not able to solve it).
QToolBar::addAction creates a QToolButton hidden in layout labyrinth and if you add a Subclass instance of QToolButton, toolbar creates a QActionWidget with the subclass attached, but the possibly attached action of the toolbutton subclass gets lost :(
Same is true for any already established action connections.... anyway - here's my solution:
my icon subclass declaration:
class MIcon : public QIcon { public: MIcon(const QString& stdFileName, const QString& selFileName); MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile); static void setDisabledFileName(const QString& fileName); protected: static QString disabledFileName; };
... with this constructor:
MIcon::MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile) : QIcon(normalFile) { addPixmap(QPixmap(activeFile), QIcon::Mode::Active); addPixmap(QPixmap(selectedFile), QIcon::Mode::Selected); }
... the action definition (its an extended Action, so just ignore the conditions):
power = new DynaAction(MIcon(":/res/SK_PowerOff.png" , ":/res/SK_PowerOff_1.png" , ":/res/SK_PowerOn.png") , tr("Poweroff") , new TrueCondition() , new EqualCondition(vm.getModel("taskState"), EMC_TASK_STATE_ENUM::EMC_TASK_STATE_ON) , this); power->setCheckable(true);
... and finally the toolbar creation and handling of tristate:
powerTB = new QToolBar(tr("Power"), this); powerTB->setObjectName("PowerTB"); powerTB->setIconSize(s); powerTB->addAction(power); connect(power, &QAction::triggered, this, [=](bool){ int state = power->property("state").toInt() + 1; QToolButton* btPow = static_cast<QToolButton*>(powerTB->widgetForAction(power)); if (state > 2) state = 0; power->setProperty("state", state); switch (state) { case 1: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Active)); break; case 2: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Selected)); break; default: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Normal)); break; } qDebug() << "power - state == " << state; });
Why I hate this solution?
Well, in my opinion a QAction::triggered callback should be used for functionality only. Not for styling or painting.
So here two worlds are mixed, that should not be mixed ...My conclusion so far: there's absolutely no support for tristate buttons in Qt, but a workaround is possible.
-
Sorry Artur,
but your postings made me feel like you are shitting on me.
I could not find any tristate-support out of the box.The code sample is the only a user function, which does not get called. So you're up on your own :(
Anyway - I was able to solve the problem, but the solution is absolutely ugly and I hate it. But as I don't know any better, I have to live with it.
What turned me mad is the fact, that its not possible to add a subclass of QToolButton to a toolbar (or at least I was not able to solve it).
QToolBar::addAction creates a QToolButton hidden in layout labyrinth and if you add a Subclass instance of QToolButton, toolbar creates a QActionWidget with the subclass attached, but the possibly attached action of the toolbutton subclass gets lost :(
Same is true for any already established action connections.... anyway - here's my solution:
my icon subclass declaration:
class MIcon : public QIcon { public: MIcon(const QString& stdFileName, const QString& selFileName); MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile); static void setDisabledFileName(const QString& fileName); protected: static QString disabledFileName; };
... with this constructor:
MIcon::MIcon(const QString& normalFile, const QString& activeFile, const QString& selectedFile) : QIcon(normalFile) { addPixmap(QPixmap(activeFile), QIcon::Mode::Active); addPixmap(QPixmap(selectedFile), QIcon::Mode::Selected); }
... the action definition (its an extended Action, so just ignore the conditions):
power = new DynaAction(MIcon(":/res/SK_PowerOff.png" , ":/res/SK_PowerOff_1.png" , ":/res/SK_PowerOn.png") , tr("Poweroff") , new TrueCondition() , new EqualCondition(vm.getModel("taskState"), EMC_TASK_STATE_ENUM::EMC_TASK_STATE_ON) , this); power->setCheckable(true);
... and finally the toolbar creation and handling of tristate:
powerTB = new QToolBar(tr("Power"), this); powerTB->setObjectName("PowerTB"); powerTB->setIconSize(s); powerTB->addAction(power); connect(power, &QAction::triggered, this, [=](bool){ int state = power->property("state").toInt() + 1; QToolButton* btPow = static_cast<QToolButton*>(powerTB->widgetForAction(power)); if (state > 2) state = 0; power->setProperty("state", state); switch (state) { case 1: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Active)); break; case 2: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Selected)); break; default: btPow->setIcon(power->icon().pixmap(powerTB->iconSize() , QIcon::Mode::Normal)); break; } qDebug() << "power - state == " << state; });
Why I hate this solution?
Well, in my opinion a QAction::triggered callback should be used for functionality only. Not for styling or painting.
So here two worlds are mixed, that should not be mixed ...My conclusion so far: there's absolutely no support for tristate buttons in Qt, but a workaround is possible.
@django-Reinhard said in need help on tristate toolbutton creation:
What turned me mad is the fact, that its not possible to add a subclass of QToolButton to a toolbar (or at least I was not able to solve it).
Why do you think so? Should be doable (maybe not the standard way)
Have your tried reimplementing
createWidget
?and then use your custom toolButton as widget.
Haven't done this, but this is how I would try it. -
@django-Reinhard said in need help on tristate toolbutton creation:
My conclusion so far: there's absolutely no support for tristate buttons in Qt
QCheckbox has tristates, so this is not true.
@mchinand said in need help on tristate toolbutton creation:
QCheckbox has tristates, so this is not true.
Ok, although QCheckbox is a descendant of QAbstractButton - for me a CheckBox is not a button.
@Pl45m4 said in need help on tristate toolbutton creation:
Why do you think so? Should be doable (maybe not the standard way)
Thanks for the hint. I'll check it out.
My standard way is reading docs to understand the interface of a class. It that does not work like expected, I'll try to find the sources and follow the function calls.
This way I found that QToolBar::addWidget does not respect the action assotiated with the parameter widget.
Following QToolBar::addAction I found out, that QToolButton is created by layout (QToolBarLayout::createItem).As I don't use QWidgetAction I found no trace of
createWidget
... so for me, this path will only be visible after a professional hint like yours.
So - thank you for the pointer. -
@django-Reinhard said in need help on tristate toolbutton creation:
What turned me mad is the fact, that its not possible to add a subclass of QToolButton to a toolbar (or at least I was not able to solve it).
Why do you think so? Should be doable (maybe not the standard way)
Have your tried reimplementing
createWidget
?and then use your custom toolButton as widget.
Haven't done this, but this is how I would try it.@Pl45m4 said in need help on tristate toolbutton creation:
Have your tried reimplementing createWidget?
https://doc.qt.io/qt-5/qwidgetaction.html#createWidget
and then use your custom toolButton as widget.
I had a quick research at Qt. If that way is to be promising, then the function must be used or called within Qt.
However, I could not find any evidence for this.There is this comment at QToolBar::actionEvent
// reparent the action to this toolbar if it has been created // using the addAction(text) etc. convenience functions, to // preserve Qt 4.1.x behavior. The widget is already // reparented to us due to the createWidget call inside // createItem()
but QToolBar does not have a createItem function. So I guess, the createItem of toolbarlayout is meant. But that function does not use createWidget. Instead creation of QToolButton is hardcoded:
if (!widget) { QToolButton *button = new QToolButton(tb);
So if it really is possible to add a subclass of QToolButton to QToolBar, I need some help to find the way to do it.
-
@mchinand said in need help on tristate toolbutton creation:
QCheckbox has tristates, so this is not true.
Ok, although QCheckbox is a descendant of QAbstractButton - for me a CheckBox is not a button.
@Pl45m4 said in need help on tristate toolbutton creation:
Why do you think so? Should be doable (maybe not the standard way)
Thanks for the hint. I'll check it out.
My standard way is reading docs to understand the interface of a class. It that does not work like expected, I'll try to find the sources and follow the function calls.
This way I found that QToolBar::addWidget does not respect the action assotiated with the parameter widget.
Following QToolBar::addAction I found out, that QToolButton is created by layout (QToolBarLayout::createItem).As I don't use QWidgetAction I found no trace of
createWidget
... so for me, this path will only be visible after a professional hint like yours.
So - thank you for the pointer.@django-Reinhard said in need help on tristate toolbutton creation:
As I don't use QWidgetAction I found no trace of createWidget
You don't use it directly, but it's used internally when you add
QActions
to your ToolBar.
Might be interesting to read
(Not exactly what you are doing, but somehow related)Since
QToolBar
is a container forQWidget
which representsQActions
in most cases (-> "widgetForAction"), a custom widget is drawn/created internally for everyQAction
you add to your standardQToolBar
. This is why you can't just add a customQWidget
to a toolBar without breaking a lot of stuff (resize behavior, toolBar layout etc.).
This is also the case, if you don't have aQMainWindow
with its "standard"QToolBar
. Like it is the case in my linked topic above. As you've said before, every additionalQToolBar
is somewhere added to your layout, which you don't want.You want your custom tristate ToolButton in your toolBar:
This function is called whenever the action is added to a container widget that supports custom widgets.
(from: https://doc.qt.io/qt-5/qwidgetaction.html#createWidget)
So maybe your understanding of how
QToolButtons
work in aQToolBar
, is not quite correct.The problem is, that
createWidget
has to create the widget. So it's not possible to pass any existing widget to your customQWidgetAction
and add it to yourQToolBar
. See the linked topic, where @Alexey-Serebryakov tried to pass custom widgets toQWidgetAction
. The issue was thatQToolBar
creates this so calledExtension menu
when you resize your main widget where the toolBar is located, and thereforecreateWidget
is called again to populate this menu (re-draw/ -create allactionWidgets
again).So from my understanding, you could create custom
widgetActions
which will create yourTristateButtons
. Then you can add them to any standardQToolBar
withaddAction
without losing any functionality.A, B, C, D, E are regular
QToolButtons
created by Qt withaddAction("A")
.
MyTristateToolButton
is a custom widgetAction subclass which creates a customQToolButton
. There you can do anything you want."Extension menu" -> widgets redrawn, still works.
-
@django-Reinhard said in need help on tristate toolbutton creation:
As I don't use QWidgetAction I found no trace of createWidget
You don't use it directly, but it's used internally when you add
QActions
to your ToolBar.
Might be interesting to read
(Not exactly what you are doing, but somehow related)Since
QToolBar
is a container forQWidget
which representsQActions
in most cases (-> "widgetForAction"), a custom widget is drawn/created internally for everyQAction
you add to your standardQToolBar
. This is why you can't just add a customQWidget
to a toolBar without breaking a lot of stuff (resize behavior, toolBar layout etc.).
This is also the case, if you don't have aQMainWindow
with its "standard"QToolBar
. Like it is the case in my linked topic above. As you've said before, every additionalQToolBar
is somewhere added to your layout, which you don't want.You want your custom tristate ToolButton in your toolBar:
This function is called whenever the action is added to a container widget that supports custom widgets.
(from: https://doc.qt.io/qt-5/qwidgetaction.html#createWidget)
So maybe your understanding of how
QToolButtons
work in aQToolBar
, is not quite correct.The problem is, that
createWidget
has to create the widget. So it's not possible to pass any existing widget to your customQWidgetAction
and add it to yourQToolBar
. See the linked topic, where @Alexey-Serebryakov tried to pass custom widgets toQWidgetAction
. The issue was thatQToolBar
creates this so calledExtension menu
when you resize your main widget where the toolBar is located, and thereforecreateWidget
is called again to populate this menu (re-draw/ -create allactionWidgets
again).So from my understanding, you could create custom
widgetActions
which will create yourTristateButtons
. Then you can add them to any standardQToolBar
withaddAction
without losing any functionality.A, B, C, D, E are regular
QToolButtons
created by Qt withaddAction("A")
.
MyTristateToolButton
is a custom widgetAction subclass which creates a customQToolButton
. There you can do anything you want."Extension menu" -> widgets redrawn, still works.
Hi,
@Pl45m4 said in need help on tristate toolbutton creation:
(Not exactly what you are doing, but somehow related)
Thank you for the pointer!
Very interesting.
I like the post, where Alexey wrote, that he got mad.
Same happened to me, when I looked at the sources of QToolBar and QToolBarLayout :(@Pl45m4 said in need help on tristate toolbutton creation:
As you've said before, every additional QToolBar is somewhere added to your layout, which you don't want.
No! That's a misunderstanding. Of cause I want toolbars with their layout.
You see 5 toolbars, all dockable and floating allowed.
The Notebook is my central widget, which is a widget stack. Displayed widget is decided by user action and application flow.
All other widgets are dockable widgets with dynamic form loading.For the toolbar buttons I use empty Icon for disabled state. So a disabled action can not be identified.
What I don't like, on the other hand, is the abuse of responsibility and competence.
Look:
If I want some money from you, the most natural and commonly accepted way is, that I ask you for money (and you decide what to do).
Only in programming business many devs find it ok, to grab your moneybox and take your money out without asking.So - for me - a layout should care for arrangement of child widgets of the widget it controls - in that context a layout may change size and position of a child-widget. But under no conditions a layout should be allowed to create widgets on its own, like QToolBarLayout is doing.
QToolBarLayout could use a factory method of QToolBar or QToolBar could create the toolbuttons on adding the action, what ever ...Same rule applies to my code. I don't like, that icon of a toolbutton is changed by application code. Therefore I wanted to subclass QToolButton.
The most preferred way would be, to extend an Action to be able to handle all tristate logic. But for that to work, the QIcon needs tristate-support and whatever code is responsible for extracting pixmap by status from qicon should be extended for tristate support too.Therefore: I don't know enuf to subclass a QToolButton to support tristate icons.
... but I'll try QWidgetAction::createWidget anyway.
-
Hi Pl45m4,
toolbar seems to be a crazy environment. I shouted the 'F'-word several times. Had lot of crashes, but finally I think I got it.
Not perfect, but a solution, I can live with ;)Every posibility, I thought I should work with is blocked/prohibited. Either function not virtual, or no function used for access ...
QWidgetAction
is the only posibility to get individual widgets into a toolbar. But that widget will be reduced to a stupid painter subject. The factory action is still used as an action (well - I thought it would be just a factory action, but it is not. Stil a complete QAction!).After I got that point, it was straightforward to implement -
MultiStateButton - declaration:class MultiStateToolButton : public QToolButton { Q_OBJECT public: MultiStateToolButton(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent = nullptr); protected slots: void stateChanged(const QVariant& state); private: ValueModel* model; const QIcon& s0Icon; const QIcon& s1Icon; const QIcon& s2Icon; };
I implemented it for 3 States, but it could be adapted to any number of states.
ValueModel is used to hold the buttons state - as in my application the buttons state can be changed from outside of my application ...
model
could be a Qt-property as well.MultiStateButton - definition:
MultiStateToolButton::MultiStateToolButton(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent) : QToolButton(parent) , model(vm) , s0Icon(s0Icon) , s1Icon(s1Icon) , s2Icon(s2Icon) { setIcon(this->s0Icon); connect(model, &ValueModel::valueChanged, this, &MultiStateToolButton::stateChanged); } void MultiStateToolButton::stateChanged(const QVariant& state) { qDebug() << "MultiStateButton::stateChanged ... " << state; switch (state.toInt()) { case 2: setIcon(s1Icon); break; case 3: setIcon(s2Icon); break; default: setIcon(s0Icon); break; } }
The hardest point for me to get rid of was, that the toolbutton works fine with const references to icons, but the QWidgetAction will crash with icons as references.
As I could not use one QIcon with multiple pixmaps, I decided to use one QIcon for each picture.So the subclass of
QWidgetAction
is straight forward:class MultiStateAction : public QWidgetAction { Q_OBJECT public: MultiStateAction(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget* parent = nullptr); QWidget* createWidget(QWidget* parent) override; private: ValueModel* model; QIcon s0Icon; QIcon s1Icon; QIcon s2Icon; };
I started with const references in the widget action, as I thought, I only have to store the parameters for widget creation later on ...
MultiStateAction::MultiStateAction(ValueModel* vm, const QIcon& s0Icon, const QIcon& s1Icon, const QIcon& s2Icon, QWidget *parent) : QWidgetAction(parent) , model(vm) , s0Icon(s0Icon) , s1Icon(s1Icon) , s2Icon(s2Icon) { setIcon(s0Icon); setText("MultiStateFactoryAction"); } QWidget *MultiStateAction::createWidget(QWidget *parent) { QToolButton* tb = new MultiStateToolButton(model, s0Icon, s1Icon, s2Icon, parent); tb->setDefaultAction(this); return tb; }
QToolButton::setDefaultAction()
is the most important step. Without that, the button does not emit a triggered signal. With that function call, button and action emit a triggered signal.Creation of toolbar (in mainwindow or similar) looks like:
Window::Window() : model(new ValueModel("state", 0)) , tb(new QToolBar) , a1(new QAction("A")) , a2(new QAction("B")) , a3(new QAction("C")) , a4(new QAction("D")) , msa(new MultiStateAction(model , QIcon(":/res/doc.png") , QIcon(":/res/hand.png") , QIcon(":/res/ok.png"))) { setLayout(new QVBoxLayout); setupActions(); connectActions(); } void Window::setupActions() { tb->setIconSize(QSize(100, 100)); tb->addAction(a1); tb->addAction(a2); tb->addAction(a3); tb->addAction(msa); tb->addAction(a4); layout()->addWidget(tb); }
First I had no call of setDefaultAction used and got no signal from the multistatebutton. So I tried several ways to get the button-press-event ...
As said - button state can be changed outside of my app, so the plain actions simulate external status change ...
void Window::connectActions() { connect(a1, &QAction::triggered, this, [=]() { qDebug() << "A"; model->setValue(0); }); connect(a2, &QAction::triggered, this, [=]() { qDebug() << "B"; model->setValue(1); }); connect(a3, &QAction::triggered, this, [=]() { qDebug() << "C"; model->setValue(2); }); connect(a4, &QAction::triggered, this, [=]() { qDebug() << "D"; model->setValue(3); }); connect(msa, &QAction::triggered, this, [=]() { qDebug() << " - MultiStateAction triggered ... !"; }); QToolButton* multiStateButton = static_cast<QToolButton*>(tb->widgetForAction(msa)); if (multiStateButton) { connect(multiStateButton, &QToolButton::triggered, this, [=]() { qDebug() << "MultiStateToolButton clicked ..."; }); } else { throw QString("OUPS - toolbar has no MultiStateToolButton button!"); } }
@Pl45m4 thank you very much for your help!