Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Custom QAction in QMenu
Qt 6.11 is out! See what's new in the release blog

Custom QAction in QMenu

Scheduled Pinned Locked Moved Solved General and Desktop
13 Posts 3 Posters 8.4k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Chris KawaC Offline
    Chris KawaC Offline
    Chris Kawa
    Lifetime Qt Champion
    wrote on last edited by
    #3

    I'd use a QActionWidget, but if you want to make the widget look like normal menu item use the same method to draw it.
    It's always good to practice, so I made a little sample that you can hopefully expand on:

    class CustomItem : public QWidget
    {
        Q_OBJECT
    
        QString text;
        bool checked;
        QPushButton* btnRemove;
    
    signals:
        void toggled(bool checked) const;
        void activated() const;
        void removeClicked() const;
    
    public:
        CustomItem(const QString& text, QWidget* parent = nullptr)
            : QWidget(parent), text(text), checked(false)
        {
            setMouseTracking(true);  //so we get paint updates
    
            btnRemove = new QPushButton(QIcon(":/WaveView/icon/actionClose.svg"), QString());
            btnRemove->setFlat(true);
            connect(btnRemove, &QPushButton::clicked, this, &CustomItem::removeClicked);
    
            auto lay = new QHBoxLayout();
            lay->setContentsMargins(0,0,0,0);
            lay->addStretch();
            lay->addWidget(btnRemove);
            setLayout(lay);
        }
    
        QSize minimumSizeHint() const override
        {
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
            opt.menuHasCheckableItems = true;
            QSize contentSize = fontMetrics().size(Qt::TextSingleLine | Qt::TextShowMnemonic, text);
            return style()->sizeFromContents(QStyle::CT_MenuItem, &opt, contentSize, this)
                   + QSize(btnRemove->minimumSizeHint().width(), 0);
        }
    
        void paintEvent(QPaintEvent* e) override
        {
            QPainter p(this);
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
            opt.text = text;
            opt.menuHasCheckableItems = true;
            opt.checked = checked;
            opt.checkType = QStyleOptionMenuItem::NonExclusive;
            opt.menuItemType = QStyleOptionMenuItem::Normal;
    
            if (rect().contains(mapFromGlobal(QCursor::pos())))
                opt.state |= QStyle::State_Selected;
    
            style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
        }
    
        void mouseReleaseEvent(QMouseEvent* evt) override
        {
            QWidget::mouseReleaseEvent(evt);
            QRect checkboxRect(0,0,25,height()); //the value 25 seems to be hardcoded in the style :/
            if (isEnabled())
            {
                if (checkboxRect.contains(mapFromGlobal(QCursor::pos())))
                {
                    checked = !checked;
                    emit toggled(checked);
                }
                else
                    emit activated();
            }
        }
    };
    

    You can probably make tons of adjustments, like add any number of buttons dynamically, but it's a starting point.

    1 Reply Last reply
    5
    • Romain CR Offline
      Romain CR Offline
      Romain C
      wrote on last edited by
      #4

      Thanks to both of you!
      @Chris-Kawa I try your lines of code and it fits perfectly to my needs !
      Before your answered,I was trying an approach like the one you made, after reading QMenu lines of code. But without success!
      Again, thank you very much!

      1 Reply Last reply
      1
      • Romain CR Offline
        Romain CR Offline
        Romain C
        wrote on last edited by Chris Kawa
        #5

        Some little changes to avoid redondant informations :

        #ifndef QCUSTOMITEM
        #define QCUSTOMITEM
        
        //Other location includes
        #include <QWidget>
        #include <QString>
        
        //Forward
        class QPushButton;
        
        class QCustomItem : public QWidget
        {
            Q_OBJECT
        	//////////////////////////////////////////////
        	//// ----------------- MEMBERS ------------
        	private:
        		/**
        		 * @brief 
        		 */
        		QAction*		m_action;
        		/**
        		 * @brief 
        		 */
        		QPushButton*	m_button;
        	//////////////////////////////////////////////
        	//// ----------------- CONSTRUCTORS ------------
        	public:
        		/**
        		 * @brief
        		 */
        		QCustomItem(QAction* p_action, const QIcon& p_icon,QWidget* p_parent = nullptr);
        		/**
        		 * @brief
        		 */
        		virtual ~QCustomItem();
        		
        	//////////////////////////////////////////////
        	//// ----------------- METHODS ------------
        
        	public:
        		/**
        		 * @brief 
        		 */
        		virtual QSize minimumSizeHint() const;
        		/**
        		 * @brief 
        		 */
        		virtual void paintEvent(QPaintEvent* e);
        		/**
        		 * @brief 
        		 */
        		virtual void mouseReleaseEvent(QMouseEvent* evt);
        
        	signals:
        		/**
        		 * @brief 
        		 */
        		void toggled(bool checked) const;
        		/**
        		 * @brief 
        		 */
        		void activated() const;
        		/**
        		 * @brief 
        		 */
        		void action() const;
        };
        
        #endif
        
        
        #include "QCustomItem.h"
        
        ///// QT
        #include <QAction>
        #include <QHBoxLayout>
        #include <QPainter>
        #include <QPushButton>
        #include <QStyleOptionMenuItem>
        
        //////////////////////////////////////////////
        //// ----------------- CONSTRUCTORS ------------
        QCustomItem::QCustomItem(QAction* p_action, const QIcon& p_icon, QWidget* p_parent)
            : QWidget(p_parent), m_action(p_action)
        {
            setMouseTracking(true);  //so we get paint updates
        
            m_button = new QPushButton(p_icon, QString());
            m_button->setFlat(true);
            connect(m_button, &QPushButton::clicked, this, &QCustomItem::action);
        
            QHBoxLayout* layout = new QHBoxLayout();
            layout->setContentsMargins(0,0,0,0);
            layout->addStretch();
            layout->addWidget(m_button);
            setLayout(layout);
        }
        
        QCustomItem::~QCustomItem()
        {
        }
        
        //////////////////////////////////////////////
        //// ----------------- METHODS ------------
        QSize QCustomItem::minimumSizeHint() const
        {
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
            opt.menuHasCheckableItems = true;
            QSize contentSize = fontMetrics().size(Qt::TextSingleLine | Qt::TextShowMnemonic, m_action->text());
            return style()->sizeFromContents(QStyle::CT_MenuItem, &opt, contentSize, this)
                    + QSize(m_button->minimumSizeHint().width(), 0);
        }
        
        void QCustomItem::paintEvent(QPaintEvent* e)
        {
            QPainter p(this);
            QStyleOptionMenuItem opt;
            opt.initFrom(this);
        	opt.text = m_action->text();
            opt.menuHasCheckableItems = true;
        	opt.checked = m_action->isChecked();
            opt.checkType = QStyleOptionMenuItem::NonExclusive;
            opt.menuItemType = QStyleOptionMenuItem::Normal;
        
            if (rect().contains(mapFromGlobal(QCursor::pos())))
                opt.state |= QStyle::State_Selected;
        
            style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
        }
        
        void QCustomItem::mouseReleaseEvent(QMouseEvent* evt)
        {
            QWidget::mouseReleaseEvent(evt);
            QRect checkboxRect(0,0,25,height()); //the value 25 seems to be hardcoded in the style :/
            if (isEnabled())
            {
                if (checkboxRect.contains(mapFromGlobal(QCursor::pos())))
                {
        			m_action->toggle();
                }
                else
                    emit activated();
            }
        }
        

        It seems to work nice!
        Many thanks!

        1 Reply Last reply
        0
        • Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #6

          @Romain-C I added code tags to your last post. Please use them in the future.
          Using QAction to hold a string and a bool is kinda overkill. I'm not sure what redundancies you mean but now you made the code really redundant. The widget is meant to be used as an action widget, which inherits a QAction on its own, so now you have a QAction inside a QWidget inside a QAction.

          Romain CR 1 Reply Last reply
          0
          • Chris KawaC Chris Kawa

            @Romain-C I added code tags to your last post. Please use them in the future.
            Using QAction to hold a string and a bool is kinda overkill. I'm not sure what redundancies you mean but now you made the code really redundant. The widget is meant to be used as an action widget, which inherits a QAction on its own, so now you have a QAction inside a QWidget inside a QAction.

            Romain CR Offline
            Romain CR Offline
            Romain C
            wrote on last edited by
            #7

            @Chris-Kawa Sorry, didn't see that </> was for code insertion!
            I my idea, I would like that the QWidget was the view of the QAction (and the QAction the model).
            In this situation I hope if my QAction is check in other part of the code, the QWidget will take the modifications.
            Not sure I'm right.

            1 Reply Last reply
            0
            • Chris KawaC Offline
              Chris KawaC Offline
              Chris Kawa
              Lifetime Qt Champion
              wrote on last edited by
              #8

              It's a widget. It shouldn't really be an action. A QWidgetAction is the adapter for these.
              If you want to use QAction anyway you're making the task a lot harder for yourself, because with it, to make the solution complete, you would need to sync the inner and outer actions i.e. if someone sets text on the QWidget Action, changes icon, description etc. This goes the other way too.
              Of course you can ignore these issues but it makes the solution half baked.

              Romain CR 1 Reply Last reply
              0
              • Romain CR Offline
                Romain CR Offline
                Romain C
                wrote on last edited by
                #9

                Ok I understand what you mean, and i forgot that QWidget have some defined methods like setText() that need to be managed.
                In my first idea, the only properties that needs to be set was in relationship with the button part, all the rest would be taken from the QAction. So I admit it's a little weird. The Qaction field was just a way for my widget to know the QWidgetAction "parent" of my widget and to synchronize it.

                1 Reply Last reply
                0
                • Chris KawaC Chris Kawa

                  It's a widget. It shouldn't really be an action. A QWidgetAction is the adapter for these.
                  If you want to use QAction anyway you're making the task a lot harder for yourself, because with it, to make the solution complete, you would need to sync the inner and outer actions i.e. if someone sets text on the QWidget Action, changes icon, description etc. This goes the other way too.
                  Of course you can ignore these issues but it makes the solution half baked.

                  Romain CR Offline
                  Romain CR Offline
                  Romain C
                  wrote on last edited by Romain C
                  #10

                  @Chris-Kawa
                  Hi again, and a question more specific and perhaps more for you,
                  I get 2 main issues with that solution:

                  • First one, when I open the QMenu the first time, my elements are truncated, but the second they are sized properly.
                    0_1484149212857_Sans titre.png
                    I put a QDebug on paintEvent, the first time width equal to 135, and the second time it's 123.
                    Does QMenu compute max element size, and does it exist a way to refresh it before show?

                  • Second one, is one highlight of items:

                  void QItemWithButton::paintEvent(QPaintEvent* e)
                  {
                      QPainter p(this);
                      QStyleOptionMenuItem opt;
                      opt.initFrom(this);
                      opt.text = m_action->text();
                      opt.menuHasCheckableItems = true;
                      opt.checked = m_action->isChecked();
                      opt.checkType = QStyleOptionMenuItem::NonExclusive;
                      opt.menuItemType = QStyleOptionMenuItem::Normal;
                  
                      if (rect().contains(mapFromGlobal(QCursor::pos())))
                      {
                          opt.state |= QStyle::State_Selected;
                      }
                      style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
                  }
                  
                  
                  

                  This ssems to work properly, when item is under the mouse, but the element is never showed in highlight way?
                  I try to force Palette from QMenu to item, thinking that there a missing color, but nothing change.
                  Do you have an idea?

                  Many thanks

                  1 Reply Last reply
                  0
                  • Chris KawaC Offline
                    Chris KawaC Offline
                    Chris Kawa
                    Lifetime Qt Champion
                    wrote on last edited by Chris Kawa
                    #11

                    The first issue is related to the fact that the size of a widget is first calculated when it is shown, not before. First show uses the size hint to calculate the size and adjusts it if needed. Have you modified the size hint in any way? In particular have you used something like width() in the size hint? Or maybe you're setting the text too late and the size hint uses empty text to calculate the needed width?

                    As for the second issue I'm not sure what you mean. Could you explain a bit more or show a picture of what happens? One thing missing in the code snippet you posted is the initialization of the style options object:

                    QStyleOptionMenuItem opt;
                    opt.initFrom(this); //<- that part is important
                    

                    I try to force Palette from QMenu to item

                    That sounds like something you shouldn't do. What code is that exactly?

                    Romain CR 2 Replies Last reply
                    2
                    • Chris KawaC Chris Kawa

                      The first issue is related to the fact that the size of a widget is first calculated when it is shown, not before. First show uses the size hint to calculate the size and adjusts it if needed. Have you modified the size hint in any way? In particular have you used something like width() in the size hint? Or maybe you're setting the text too late and the size hint uses empty text to calculate the needed width?

                      As for the second issue I'm not sure what you mean. Could you explain a bit more or show a picture of what happens? One thing missing in the code snippet you posted is the initialization of the style options object:

                      QStyleOptionMenuItem opt;
                      opt.initFrom(this); //<- that part is important
                      

                      I try to force Palette from QMenu to item

                      That sounds like something you shouldn't do. What code is that exactly?

                      Romain CR Offline
                      Romain CR Offline
                      Romain C
                      wrote on last edited by
                      #12

                      @Chris-Kawa Hi Chris,

                      For the first one, I didn't change sizeHint(). My code remain quite similar with the code I uploaded with the QAction field.
                      To explain perhaps a part of the problem, my QMenu is generated dynamically. I connect &QMenu::aboutToShow with a code of mine to refresh it each time user ask for it.

                      For the second one, it was just a concise snippet, I edit my last post and I put the entire method. It's the same thing as you did. I have a lot of problems to explain what happens, I cannot donwload sources of Qt at this time to make a debug...

                      1 Reply Last reply
                      0
                      • Chris KawaC Chris Kawa

                        The first issue is related to the fact that the size of a widget is first calculated when it is shown, not before. First show uses the size hint to calculate the size and adjusts it if needed. Have you modified the size hint in any way? In particular have you used something like width() in the size hint? Or maybe you're setting the text too late and the size hint uses empty text to calculate the needed width?

                        As for the second issue I'm not sure what you mean. Could you explain a bit more or show a picture of what happens? One thing missing in the code snippet you posted is the initialization of the style options object:

                        QStyleOptionMenuItem opt;
                        opt.initFrom(this); //<- that part is important
                        

                        I try to force Palette from QMenu to item

                        That sounds like something you shouldn't do. What code is that exactly?

                        Romain CR Offline
                        Romain CR Offline
                        Romain C
                        wrote on last edited by Romain C
                        #13

                        @Chris-Kawa Hi Chris, ok so I reviewed my code today, and to solve the problem of size I just removed the mimimumSizeHint() and it works perfectly. Thks!
                        I only need to fix highlight color now.

                        1 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved