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

Custom QAction in QMenu

Scheduled Pinned Locked Moved Solved General and Desktop
13 Posts 3 Posters 6.8k 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.
  • Romain CR Offline
    Romain CR Offline
    Romain C
    wrote on last edited by Romain C
    #1

    Hi to everyone,
    Today I would like to custom a few QAction rendering in a QMenu.
    Since now, the only way I found is to go through a QWidgetAction, that take a default widget.

    What I want to have, is a standard item with a little remove action at endline

    What I get now with few line is that, it seems a little weird way, and I want your expertise to know if it's the best way to do that
    0_1483718607190_Custom.png

    Also, I lost some automatic style effects like hovered style etc...
    I would like to be the most similar to initial style, to avoid a contrast between original and my components

    My lines of code, for persons interested in:

    QMenu* p_menu = new QMenu();
    QWidgetAction* action = new QWidgetAction(p_menu);
    QWidget* widget = new QWidget(p_menu);
    QHBoxLayout* lay = new QHBoxLayout(widget);
    QPushButton* check = new QPushButton(widget);
    check->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
    QLabel* label = new QLabel(workspaces.at(i),widget);
    check->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    QPushButton* close = new QPushButton(QIcon(":/WaveView/icon/actionClose.svg"),"",widget);
    check->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
    check->setFlat(true);
    close->setFlat(true);
    lay->addWidget(check);
    lay->addWidget(label);
    lay->addWidget(close);
    widget->setLayout(lay);
    lay->setContentsMargins(0,0,0,0);
    action->setDefaultWidget(widget);
    action->setDefaultWidget(widget);
    p_menu->addAction(action);

    Thanks!
    Best regards

    1 Reply Last reply
    0
    • mrjjM Offline
      mrjjM Offline
      mrjj
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi
      If I understand what you want from the custom Action
      ( a widget in the menu with a button etc)
      then i asked the same question a year ago
      https://forum.qt.io/topic/57758/solved-qmenu-and-custom-painting-of-the-items
      and if Chris Kawa suggests QWidgetAction, i doubt there be anything smarter. :)
      Unless you have something else in mind.

      1 Reply Last reply
      2
      • 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