Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Toggle svg icon on Button click - QT C++



  • Hi,

    I am relatively new to svg + QT concepts.
    I have created an svg icon which has 2 states.
    I want to add this svg icon to a QPushButton. When the button is clicked,the icon should get toggled ( show the other state ).

    Can anybody pls give me a heads up on how to do this in QT? QT with C++!


  • Lifetime Qt Champion

    Hi,

    Take a look at QIcon.

    It's what you need to setup in order to have the various icons used automatically when the button state changes.



  • Hi @SGaist ,
    I have tried something using the QSvgRenderer and QIcon.
    I have achieved what I was looking for.

    But I am not sure if this is how it should be done.
    Please have a look and let me know if this is the preferred way or not.


    I have created a svg icon ( using 2 other svg icons ) from the https://nucleoapp.com/tool/icon-transition
    The svg icon has 2 svg icons embedded into it. My objective now is to toggle the icons when button is clicked.

    Checkout my files below

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QSvgRenderer>
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private:
        Ui::MainWindow *ui;
        bool isDefaultIconLoaded;
        QString otherIconSvgContent;
        QString defaultIconSvgContent;
    
        QSvgRenderer m_svgRenderer;
    
        void loadCurrentSvgIcon(QByteArray ba);
    
    private slots:
        void toolBtnClicked();
    };
    
    #endif // MAINWINDOW_H
    
    

    The cpp file

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QFile>
    #include <QPainter>
    #include <QtSvg/QSvgRenderer>
    #include <QPixmap>
    #include <QDomDocument>
    #include <QDomNodeList>
    #include <QTextStream>
    #include <QDebug>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow),
        isDefaultIconLoaded(true)
    {
        ui->setupUi(this);;
        connect(ui->toolButton, &QToolButton::clicked, this, &MainWindow::toolBtnClicked);
        ui->toolButton->setStyleSheet("border:none;");
    
        // open svg icon which has multiple states
        QFile file(":/icon/theSvg.svg");
        file.open(QIODevice::ReadOnly);
        const QByteArray baData = file.readAll();
        QDomDocument doc;
        doc.setContent(baData, false);
    
        // Read both the <g> elements from the .svg xml and store each 'icon content' in member var.
        QDomNodeList gList = doc.elementsByTagName("g");
        for(int i = 0; i < gList.size(); i++)
        {
            QString aContent;
            QTextStream ts(&aContent);
            gList.at(i).save(ts,0);
            qDebug("%s", qPrintable(aContent));
    
            if( isDefaultIconLoaded )
            {
                defaultIconSvgContent.insert(0, "<svg>");
                defaultIconSvgContent += aContent;;
                defaultIconSvgContent.append("</svg>");
                isDefaultIconLoaded = false;
                qDebug() <<defaultIconSvgContent;
            }
            else {
                otherIconSvgContent.insert(0, "<svg>");
                otherIconSvgContent += aContent;
                otherIconSvgContent.append("</svg>");
                qDebug() <<otherIconSvgContent;
            }
        }
    
        // Load the default svg icon first
       QByteArray ba = defaultIconSvgContent.toUtf8();
       isDefaultIconLoaded = true;
       loadCurrentSvgIcon(ba);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::loadCurrentSvgIcon(QByteArray ba)
    {
       m_svgRenderer.load(ba);
       QPixmap pix(m_svgRenderer.defaultSize());
        QPainter pixPainter(&pix);
        m_svgRenderer.render(&pixPainter);
        QIcon myicon(pix);
    
        ui->toolButton->setIcon(myicon);
    }
    
    void MainWindow::toolBtnClicked()
    {
        if ( isDefaultIconLoaded == true )
        {
            // Toggle the icon to "other"
            QByteArray ba = otherIconSvgContent.toUtf8();
            loadCurrentSvgIcon(ba);
            isDefaultIconLoaded = false;
        }
        else {
            QByteArray ba = defaultIconSvgContent.toUtf8();
            loadCurrentSvgIcon(ba);
            isDefaultIconLoaded = true;
        }
    }
    

    Content of the svg file

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="16" width="16">
        <g 
          class="nc-icon-wrapper" stroke-width="1" fill="#111111" stroke="#111111">
          <title>default</title>
        <path 
          fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" data-color="color-2" d="M1.5 1.5h2">      
        </path>
        <circle 
          fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" cx="9" cy="9" r="3.5" data-color="color-2">
        </circle>
          <path 
            fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" data-color="color-2" d="M2.5 5.5h1">        
          </path>
          <path 
            fill="none" stroke="#111111" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M14.5 3.5h-2l-1-2h-5l-1 2h-4a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1z">
          </path>
      </g>
        <g 
          class="nc-icon-wrapper" fill="#111111">
          <title>other</title>
        <path 
          fill="#111111" d="M15,3h-2.5l-1.7-2.6C10.6,0.2,10.3,0,10,0H6C5.7,0,5.4,0.2,5.2,0.4L3.5,3H1C0.4,3,0,3.4,0,4v11 c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1V4C16,3.4,15.6,3,15,3z M14,14H2V5h2c0.3,0,0.6-0.2,0.8-0.4L6.5,2h2.9l1.7,2.6 C11.4,4.8,11.7,5,12,5h2V14z">
        </path> 
        <circle 
          data-color="color-2" cx="8" cy="9" r="3">      
        </circle>
      </g>
    </svg>
    

    basically, each <g> element represents an icon.
    I am reading the <g> element contents in C'tor and storing them into member variables ( in order to avoid reading again and again ).

    Then I am "loading" the icon based on the click event using the "QSvgRenderer".

    This works as expected, but is it the best practice or is there a better way I can acheive this ?


  • Lifetime Qt Champion

    I understand your goal but what escapes me here is why you don't take advantage of QIcon's API. You should just have to assign each image of your SVG to the correct state on the icon and then it will be automatically be used by the button.



  • @SGaist
    Thanks for the reply.

    Do You mean to say something like this ?

    @
    toolButton = new QToolButton;
    toolButton->setCheckable(true);
    QIcon qIcon;
    qIcon.addFile(":/Icons/images/first.svg",QSize(32,32),QIcon::Normal,QIcon::On);
    qIcon.addFile(":/Icons/images/second.svg",QSize(32,32),QIcon::Normal,QIcon::Off);
    toolButton->setIcon(qIcon);
    @

    But here I need to have 2 seperate icons for on and off states.

    But What I am trying to do is that only single svg icon which has 2 states should be toggled on click ?
    Can that be done as per your above comment ?

    If my understanding of your above comment is wrong, would you please provide a minimal example ?


  • Lifetime Qt Champion

    @Rizwan94 said in Toggle svg icon on Button click - QT C++:

    But What I am trying to do is that only single svg icon which has 2 states should be toggled on click ?

    The Qt svg handle can not handle more than one image in a svg.



  • @Christian-Ehrlicher said in Toggle svg icon on Button click - QT C++:

    The Qt svg handle can not handle more than one image in a svg.

    Oh, fine then.
    I will go ahead with the approach suggested by @SGaist and make use of QIcon for toggling "each" svg icon.


Log in to reply