Custom Qt QMenu
Asked Answered
K

3

9

Is there a way in Qt to add a layout or widgets to a QMenu to create a custom menu?

The example below (left) is what I have, and I'd like to aim for something similar to the mock-up on the right, by adding non-menu widgets. If it can't be done by QMenu, are there guides to produce similar results (perhaps by having a more standard widget act as a context menu) anywhere?

Custom QMenu

Kaput answered 13/9, 2013 at 13:21 Comment(0)
I
9

Sure there is! In Qt, if there is a a will there is a way.

You will probably need to make your own class that uses QMenu and uses a member QListWidget.

You will then need to generate the layout and overload all the correct QLayout functions for size recalculation.

And then you'll need to use this layout (think QHBoxLayout) to display both a QMenu and a QListWidget side by side.

That should be enough to point you in the right direction.

EDIT:

As a commenter pointed out, you can't inherit two QObject things so I updated the answer accordingly.

Irrelevance answered 13/9, 2013 at 13:33 Comment(5)
@Mat If you want to spend several hours writing code for this answer, be my guest. I provided enough information for the OP to get started.Irrelevance
No-one's asking you to write the code, you can remove all that meta stuff from your answer. Notice the question is "Is there a way in Qt to add a layout or widgets to a Qmenu to create a custom menu?", not "please write the code that does this for me". If the answer is "no, you need to write that yourself", then fine, and your advice looks good. (I have no idea if there are other ways, but QWidgetAction could probably be leveraged a bit.)Corvese
@Corvese I see what you mean now. I just meant to say "yes, there is a way" without writing the code. Thanks for clarifying. I removed the meta stuff from my answer.Irrelevance
I'm still getting used to the "Qt Ways" of doing things, and I've wasted several hours on 'custom' stuff only to realize there were much easier ways to do it or that I shouldn't have done it at all. Your answer is very close to my original plan, so at least I know now I'm getting started in the right direction (and maybe wrapping my head around Qt); I did not anticipate coding the size recalculation - that would have likely derailed me significantly. I'll be back after I've started this thing in case I hit any severe hangups. :DKaput
The QLayout stuff is the most difficult, in my opinion. You need to overload several virtual functions. If it were my money, I wouldn't do this because it would probably take me between 8 and 12 hours and I'm lazy.Irrelevance
S
0

To customize menu items you can use QWidgetAction class. But you want to customize menu to look like popup widget. So you may subclass QMenu and try to improve layout of menu for your needs (QMenu is QWidget). You questoin is not clear.

Schoof answered 13/9, 2013 at 14:16 Comment(0)
C
0

I wrote a script, you can try it.

but I am not subclass QMenu.


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from PySide.QtGui import *
from PySide.QtCore import *


class MenuItem(QWidget):
    """docstring for MenuItem"""
    def __init__(self, text='test', icon=None, parent=None):
        super(MenuItem, self).__init__(parent)

        hbox = QHBoxLayout(self)
        # hbox.setContentsMargins(0, 0, 0, 0)
        label = QLabel(text)
        btn = QPushButton()
        if icon:
            btn.setIcon(icon)

        hbox.addWidget(label)
        hbox.addStretch()
        hbox.addWidget(btn)
        self.setMinimumWidth(parent.width())


class MyMenu(QWidget):
    """docstring for MyMenu"""
    def __init__(self, parent=None):
        super(MyMenu, self).__init__(parent)

        self.main_width = 200
        self.main_height = 150
        self.close_menu = False

        self.parent = parent
        self.setGeometry(0, 0, 200, 150)

        self.initUI()
        self.setWindowFlags(Qt.Popup)
        # self.setWindowModality(Qt.WindowModal)

    def initUI(self):
        main_frame = QWidget(self)
        main_v_layout = QVBoxLayout(main_frame)
        main_v_layout.setContentsMargins(0, 0, 0, 0)
        item_1 = MenuItem('item 1', parent=self)
        item_2 = MenuItem('item 2', parent=self)
        item_3 = MenuItem('item 3', parent=self)
        main_v_layout.addWidget(item_1)
        main_v_layout.addWidget(item_2)
        main_v_layout.addWidget(item_3)

    def animationShow(self):
        self.close_menu = False
        self.start_close_menu = True
        self.show()

        # PyQt4.QtCore.QRect(0, 0, 400, 23)
        rect = self.parent.rect()

        # PyQt4.QtCore.QPoint(199, 11)
        center_pos = rect.center()

        # PyQt4.QtCore.QPoint(654, 465)
        global_center_pos = self.parent.mapToGlobal(center_pos)

        height = rect.height()

        show_pos = QPoint(
            global_center_pos.x() - (self.width() / 2),
            global_center_pos.y() + height)
        # print show_pos

        self.move(show_pos)
        self.inAnimation(show_pos)

    def inAnimation(self, show_pos=None):
        start_height = QSize(self.main_width, 0)
        end_height = QSize(self.main_width, self.main_height)

        size_anim = QPropertyAnimation(self, 'size')
        size_anim.setStartValue(start_height)
        size_anim.setEndValue(end_height)
        size_anim.setDuration(160)
        size_anim.setEasingCurve(QEasingCurve.OutQuad)

        opacity_anim = QPropertyAnimation(self, 'windowOpacity')
        opacity_anim.setStartValue(0.0)
        opacity_anim.setEndValue(1.0)
        opacity_anim.setDuration(260)
        opacity_anim.setEasingCurve(QEasingCurve.OutQuad)

        self.in_anim_group = QParallelAnimationGroup()
        self.in_anim_group.addAnimation(size_anim)
        self.in_anim_group.addAnimation(opacity_anim)
        self.in_anim_group.start()

    def outAnimation(self):
        try:
            end_size = QSize(self.size().width(), 0)

            pos_anim = QPropertyAnimation(self, 'size')
            pos_anim.setEndValue(end_size)
            pos_anim.setDuration(200)
            pos_anim.setEasingCurve(QEasingCurve.InQuad)

            opacity_anim = QPropertyAnimation(self, 'windowOpacity')
            opacity_anim.setStartValue(1.0)
            opacity_anim.setEndValue(0.0)
            opacity_anim.setDuration(200)
            opacity_anim.setEasingCurve(QEasingCurve.InQuad)

            self.out_anim_group = QParallelAnimationGroup()
            self.out_anim_group.addAnimation(pos_anim)
            self.out_anim_group.addAnimation(opacity_anim)
            self.out_anim_group.finished.connect(self.closeMenu)
            self.out_anim_group.start()

        except RuntimeError as e:
            pass
        except Exception as e:
            print e

    def closeMenu(self):
        self.close_menu = True
        self.setVisible(False)

    def closeEvent(self, event):
        # super(MyMenu, self).closeEvent(event)
        if self.start_close_menu:
            self.outAnimation()
            self.start_close_menu = False

    def hideEvent(self, event):
        # print 'hideEvent', event
        super(MyMenu, self).hideEvent(event)

    def setVisible(self, visible):
        if self.close_menu:
            visible = False

        elif not visible:
            visible = True

        super(MyMenu, self).setVisible(visible)


class Win(QWidget):
    """docstring for Win"""
    def __init__(self):
        super(Win, self).__init__()

        vbox = QVBoxLayout(self)
        btn = QPushButton('call menu')
        vbox.addWidget(btn)

        self.menu = MyMenu(btn)
        btn.clicked.connect(self.menu.animationShow)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Win()
    win.show()
    sys.exit(app.exec_())
Championship answered 19/9, 2018 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.