Nguyen Huu Tuyen
Nguyen Huu Tuyen

Reputation: 165

How to change Icon image of QAction when press QToolButton

  1. I want to remove border of QToolButton when mouse hover. enter image description here

  2. I want to change icon image of QAction when press QToolButton. enter image description here

i changed stylesheet for QToolButton but it is not working, please help


  1. I want to remove border of QToolButton when mouse hover. I changed stylesheet for QToolButton

QToolButton:hover{ border: none; }
QToolButton:pressed{ border: none; }

but it display border bottom and button move to left like image

enter image description here

Upvotes: 3

Views: 2198

Answers (2)

Scheff's Cat
Scheff's Cat

Reputation: 20151

For a “Warm-up”, I started with a QPushButton. This class provides the signals pressed() and released() which can be used to change the icon resp.

testQPushButtonDownUp.cc:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build UI
  QIcon qIconBtn("dialog-info.svg");
  QIcon qIconBtnDown("dialog-error.svg");
  QPushButton qBtn(qIconBtn, QString::fromUtf8("Click Me."));
  qBtn.show();
  // install signal handlers
  QObject::connect(&qBtn, &QPushButton::pressed,
    [&qBtn, &qIconBtnDown]() { qBtn.setIcon(qIconBtnDown); });
  QObject::connect(&qBtn, &QPushButton::released,
    [&qBtn, &qIconBtn]() { qBtn.setIcon(qIconBtn); });
  // runtime loop
  return app.exec();
}

testQPushButtonDownUp.pro:

SOURCES = testQPushButtonDownUp.cc

QT += widgets

Compiled and tested in cygwin64 on Windows 10:

$ qmake-qt5 testQPushButtonDownUp.pro

$ make && ./testQPushButtonDownUp
Qt Version: 5.9.4

Snapshot of testQPushButtonDownUp Snapshot of testQPushButtonDownUp while button pressed

This was easy. Applying the same to QAction is a bit more complicated – QAction provides only one signal triggered(). I didn't check whether it's emitted for pressed() or released() – one of the both needed signals is surely missing.

To solve this, I used QAction::associatedWidgets() which

Returns a list of widgets this action has been added to.

This list is scanned for every occurrence of QToolButton which (is derived from QAbstractButton as well as QPushButton and) provides the same signals.

testQToolButtonDownUp.cc:

#include <QtWidgets>

void connectAction(QAction &qCmd, const QIcon &qIconDown, const QIcon &qIconUp)
{
  QList<QWidget*> pQWidgets = qCmd.associatedWidgets();
  for (QWidget *pQWidget : pQWidgets) {
    QToolButton *pQBtn = dynamic_cast<QToolButton*>(pQWidget);
    if (!pQBtn) continue;
    QObject::connect(pQBtn, &QToolButton::pressed,
      [pQBtn, qIconDown]() { pQBtn->setIcon(qIconDown); });
    QObject::connect(pQBtn, &QToolButton::released,
      [pQBtn, qIconUp]() { pQBtn->setIcon(qIconUp); });
  }
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build UI
  QToolBar qToolbar;
  QIcon qIconBtn("dialog-info.svg");
  QIcon qIconBtnDown("dialog-error.svg");
  QAction qCmd(qIconBtn, QString::fromUtf8("Click Me."));
  qToolbar.addAction(&qCmd);
  qToolbar.show();
  // install signal handlers
  connectAction(qCmd, qIconBtnDown, qIconBtn);
  // runtime loop
  return app.exec();
}

testQToolButtonDownUp.pro:

SOURCES = testQToolButtonDownUp.cc

QT += widgets

Compiled and tested again in cygwin64 on Windows 10:

$ qmake-qt5 testQToolButtonDownUp.pro

$ make && ./testQToolButtonDownUp
Qt Version: 5.9.4

Snapshot of testQToolButtonDownUp Snapshot of testQToolButtonDownUp while button pressed

This works but is a bit maintenance-unfriendly – the connectAction() function has to be called after the QAction has been added to all widgets. (Double-calling it for the same instance of QAction might need additional effort to prevent duplicated signal handlers for the same instance of QToolButton.)

It would be nice to connect new QToolButtons automatically as soon as the associated widgets of such a resp. QAction have been changed. I scrolled through the doc. up and down to find something appropriate – without luck. I tried whether the QAction::changed() signal might provide the required behavior (although the doc. gave less hope) but it didn't work.

Finally, I decided to turn it around – i.e. detecting when a QAction is added to a QToolButton. However, in my code I add the QAction to a QToolBar and the resp. QToolButton appears automatically. Thus, I made an event filter, installed to qApp. This event filter will receive any event and, hence, is good to detect any QAction added to any QWidget. All I've to do additionally, is to filter these events for QActions and QToolButtons where the down-up icons are required for.

testQActionDownUp.cc:

#include <set>
#include <QtWidgets>

class Action: public QAction {
  public:
    class FilterSingleton: public QObject {
      private:
        std::set<Action*> _pActions;
      public:
        FilterSingleton(): QObject()
        {
          qApp->installEventFilter(this);
        }
        ~FilterSingleton() { qApp->removeEventFilter(this); }
        FilterSingleton(const FilterSingleton&) = delete;
        FilterSingleton& operator=(const FilterSingleton&) = delete;

        void addAction(Action *pAction)
        {
          _pActions.insert(pAction);
        }
        bool removeAction(Action *pAction)
        {
          _pActions.erase(pAction);
          return _pActions.empty();
        }

      protected:
        virtual bool eventFilter(QObject *pQObj, QEvent *pQEvent) override;
    };

  private:
    static FilterSingleton *_pFilterSingleton;

  private:
    QIcon _qIcon, _qIconDown;

  public:
    Action(
      const QIcon &qIcon, const QIcon &qIconDown, const QString &text,
      QObject *pQParent = nullptr);  
    ~Action();
    Action(const Action&) = delete;
    Action& operator=(const Action&) = delete;

  private:
    void addToolButton(QToolButton *pQBtn)
    {
      QObject::connect(pQBtn, &QToolButton::pressed,
        [pQBtn, this]() { pQBtn->setIcon(_qIconDown); });
      QObject::connect(pQBtn, &QToolButton::released,
        [pQBtn, this]() { pQBtn->setIcon(_qIcon); });
    }
};

bool Action::FilterSingleton::eventFilter(QObject *pQObj, QEvent *pQEvent)
{
  if (QToolButton *pQBtn = dynamic_cast<QToolButton*>(pQObj)) {
    if (pQEvent->type() == QEvent::ActionAdded) {
      qDebug() << "Action::eventFilter(QEvent::ActionAdded)";
      QAction *pQAction = ((QActionEvent*)pQEvent)->action();
      if (Action *pAction = dynamic_cast<Action*>(pQAction)) {
        pAction->addToolButton(pQBtn);
      }
    }     
  }
  return QObject::eventFilter(pQObj, pQEvent);
}

Action::FilterSingleton *Action::_pFilterSingleton;

Action::Action(
  const QIcon &qIcon, const QIcon &qIconDown, const QString &text,
  QObject *pQParent):
  QAction(qIcon, text, pQParent),
  _qIcon(qIcon), _qIconDown(qIconDown)
{
  if (!_pFilterSingleton) _pFilterSingleton = new FilterSingleton();
  _pFilterSingleton->addAction(this);
}

Action::~Action()
{
  if (_pFilterSingleton->removeAction(this)) {
    delete _pFilterSingleton;
    _pFilterSingleton = nullptr;
  }
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // build UI
  QMainWindow qWin;
  QToolBar qToolbar;
  QIcon qIconBtn("dialog-info.svg");
  QIcon qIconBtnDown("dialog-error.svg");
  Action qCmd(qIconBtn, qIconBtnDown, QString::fromUtf8("Click Me."));
  qToolbar.addAction(&qCmd);
  qWin.addToolBar(&qToolbar);
  QToolBar qToolbar2;
  qWin.setCentralWidget(&qToolbar2);
  qWin.show();
  QTimer qTimer;
  qTimer.setInterval(5000); // 5000 ms = 5s
  qTimer.start();
  // install signal handlers
  int i = 0;
  QObject::connect(&qTimer, &QTimer::timeout,
    [&i, &qToolbar2, &qCmd]() {
      if (++i & 1) qToolbar2.addAction(&qCmd);
      else qToolbar2.removeAction(&qCmd);
    });
  // runtime loop
  return app.exec();
}

There is a class Action (derived from QAction) to bundle everything necessary together. The class Action uses internally a singleton (of class Action::FilterSingleton), so that one event filter is shared between all instances of Action.

A QTimer is used to add/remove the sample Action qCmd periodically to QToolBar qToolbar2 to test/demostrate whether the auto-management works properly.

testQActionDownUp.pro:

SOURCES = testQActionDownUp.cc

QT += widgets

Compiled and tested again in cygwin64 on Windows 10:

$ qmake-qt5 testQActionDownUp.pro

$ make && ./testQActionDownUp
Qt Version: 5.9.4

Snapshot of testQActionDownUp Snapshot of testQActionDownUp while button pressed

Snapshot of testQActionDownUp while 2nd button added Snapshot of testQActionDownUp while 2nd button added and pressed

Upvotes: 3

break1
break1

Reputation: 133

For remove border of QToolButton you can use style sheet like this ( i tested it is work ):

QToolButton { border: 0px;}
QToolButton:pressed {background-color: red;}

It is remove border and fill background of pressed tool button.

You can not change image of pressed button because usally we add QAction on QToolBar in QtDesignet, and need to change QAction image, but QAction have not signal from "pressed" like in QPushButton.

But if you adding QToolButton to QToolBar manually - you can do that, because QToolButton have signal "pressed" and have property "icon". In some slot connected to "pressed" signal you can change it.

Upvotes: 1

Related Questions