How to Show Qmenu Title

Heads up! You've already completed this tutorial.

tcarson4344 | 2020-05-25 18:03:30 UTC | #1

I am working on a Qdialog with an embedded Matplotlib plot. I have a RMB context menu popup but I want it to show the menu title. I can set the title (line 69) but I am not sure how to make it display.

python
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout, QLabel
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector

import numpy as np
import random

class FBPlot(QDialog):

    def __init__(self, parent=None):
        super(FBPlot, self).__init__(parent)

        # a figure instance to plot on
        self.figure = plt.figure()

        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure)
        ax = self.figure.add_subplot(111)

        # Capture button press
        self.canvas.mpl_connect('button_press_event', self.on_press)
        self.canvas.mpl_connect('pick_event', self.pick)

        # set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.canvas)
        self.setLayout(layout)

        # self.ax.grid(True)

        self.canvas.draw()
        self.span = SpanSelector(ax,
                                 self.spanzoom,
                                 'horizontal',
                                 useblit=True,
                                 rectprops=dict(alpha=0.5, facecolor='red'),
                                 button=1)

    def on_press(self, event):
        print(event)
        if event.button == 3:
            canvasSize = event.canvas.geometry()
            Qpoint_click = event.canvas.mapToGlobal(
                QtCore.QPoint(event.x, canvasSize.height()-event.y))

            if event.inaxes != None:
                self.fbMenu = QtWidgets.QMenu()
                self.fbMenu.addSection("Front/Back")
                self.fbMenu.addSeparator()
                self.fbMenu.addAction("Select All Curves")
                self.fbMenu.addAction("Remove All Curves")
                self.fbMenu.addSeparator()
                self.fbMenu.addAction("Legend")
                self.fbMenu.addAction("Cursor Legend")
                self.fbMenu.move(Qpoint_click)
                self.fbMenu.show()

            else:

                # Y Axis Front Area
                self.ylim_menu = QtWidgets.QMenu('&Limits')
                self.ylim_menu.addAction("Fixed")
                self.ylim_menu.addAction("Free")
                self.ylim_menu.addAction("Optimized")

                # y axis main menu
                self.yMenu = QtWidgets.QMenu('&Y Axis')
                self.yMenu.setTitle('sdfj')
                self.yMenu.addMenu(self.ylim_menu)
                self.yMenu.addSeparator()
                self.yMenu.addAction("Visable")
                self.yMenu.addAction("Options...")
                self.yMenu.move(Qpoint_click)
                self.yMenu.show()

                # Y Axis Back Area
                print(self.yMenu.title())

                # X Axis Area
                # Title Area
        else:
            pass

    def pick(self, event):
        print(event)

    def spanzoom(self, xmin, xmax):
        # indmin, indmax = np.searchsorted(x, (xmin, xmax))
        # indmax = min(len(x) - 1, indmax)
        # thisx = x[indmin:indmax]
        # thisy = y[indmin:indmax]
        # fb1.set_data(thisx, thisy)
        self.ax.set_xlim(xmin, xmax)

        #ax1.set_ylim(thisy.min(), thisy.max())
        self.canvas.draw()

    # def plot(self, xdata, ydata):
    #    self.ax.plot(xdata, ydata)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = FBPlot()

    # generate data
    # np.random.seed(19680801)
    # x = np.arange(0.0, 5.0, 0.01)
    # y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
    # fb1, = main.plot(x, y, Label='data')

    main.show()
    sys.exit(app.exec_())

Luca | 2020-05-25 14:27:41 UTC | #2

I think the title that you are setting is the one that would be visible if you add that QMenu to a QMenuBar, I'm not sure that internally, it is implemented to be shown as you would.


martin | 2020-05-25 18:29:23 UTC | #3

Yep, unfortunately as @Luca says the menu title is used when displaying the menu on the menubar -- i.e. it's the File, Edit, etc. title.

The menu does provide a menu.addSection(<str>) method which might be what you're looking for? But on macOS/Windows themes it is no different to a separator. The only way I can find to actually show it is using the Qt Fusion theme, e.g.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

python
app = QApplication(sys.argv)
app.setStyle("Fusion")

Then using the following

python
self.fbMenu.addSection("A menu part")

...will give the following results in the menu...

Screenshot 2020-05-25 at 20.23.57|518x428

The Fusion theme is a cross-platform toolkit, so your app will look the same on all platforms. But it won't look "native" on any.

One other option you have is to add a null QAction and disable it, e.g.

python
                a = self.fbMenu.addAction("A menu part")
                a.setDisabled(True)

This will show up how I suspect you're expecting it to...

Screenshot 2020-05-25 at 20.11.05|501x500

...but that's potentially confusing for users if it looks otherwise like a disabled entry. I've not found a way to reliably theme individual menu items (e.g. so you could highlight this dummy entry in a different colour).


tcarson4344 | 2020-05-25 18:46:29 UTC | #4

PyQt/PySide 1:1 Coaching with Martin Fitzpatrick — Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.

Book Now 60 mins ($195)

thanks,

I ended up adding a label Widget. took more lines of code that I wanted but looks ok I think I need to make it bold and/or color background to change the appearance.

It looks like this: context|323x221

My Code (in case someone wants it):

python
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout, QLabel, QWidgetAction, QSizePolicy

from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector

import numpy as np
import random

class FBPlot(QDialog):

    def __init__(self, parent=None):
        super().__init__(parent)

        # a figure instance to plot on
        self.figure = plt.figure()

        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure)

        ax = self.figure.add_subplot(111)

        # Capture button press
        self.canvas.mpl_connect('button_press_event', self.on_press)
        self.canvas.mpl_connect('pick_event', self.pick)

        # set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.canvas)
        self.setLayout(layout)

        # self.ax.grid(True)
        self.canvas.draw()

        self.span = SpanSelector(ax,
                                 self.spanzoom,
                                 'horizontal',
                                 useblit=True,
                                 rectprops=dict(alpha=0.5, facecolor='red'),
                                 button=1)

    def on_press(self, event):
        print(event)

        if event.button == 3:
            canvasSize = event.canvas.geometry()

            Qpoint_click = event.canvas.mapToGlobal(
                QtCore.QPoint(event.x, canvasSize.height()-event.y))

            if event.inaxes != None:
                self.fbMenu = QtWidgets.QMenu()

                #Add a title to context menu
                fbtl = QLabel('Front/Back')
                fbtl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
                fbtl.setAlignment(Qt.AlignCenter)
                fbtlAction = QWidgetAction(fbtl)
                fbtlAction.setDefaultWidget(fbtl)
                self.fbMenu.addAction(fbtlAction)

                # Add rest of menu items
                self.fbMenu.addSeparator()
                self.fbMenu.addAction("Select All Curves")
                self.fbMenu.addAction("Remove All Curves")
                self.fbMenu.addSeparator()
                self.fbMenu.addAction("Legend")
                self.fbMenu.addAction("Cursor Legend")
                self.fbMenu.move(Qpoint_click)
                self.fbMenu.show()

            else:

                # Y Axis Front Area
                self.ylim_menu = QtWidgets.QMenu('&Limits')
                self.ylim_menu.addAction("Fixed")
                self.ylim_menu.addAction("Free")
                self.ylim_menu.addAction("Optimized")

                # y axis main menu
                self.yMenu = QtWidgets.QMenu('&Y Axis')
                self.yMenu.setTitle('sdfj')
                self.yMenu.addMenu(self.ylim_menu)
                self.yMenu.addSeparator()
                self.yMenu.addAction("Visable")
                self.yMenu.addAction("Options...")
                self.yMenu.move(Qpoint_click)
                self.yMenu.show()

                # Y Axis Back Area
                print(self.yMenu.title())

                # X Axis Area
                # Title Area
        else:
            pass

    def pick(self, event):
        print(event)

    def spanzoom(self, xmin, xmax):
        # indmin, indmax = np.searchsorted(x, (xmin, xmax))
        # indmax = min(len(x) - 1, indmax)
        # thisx = x[indmin:indmax]
        # thisy = y[indmin:indmax]
        # fb1.set_data(thisx, thisy)
        self.ax.set_xlim(xmin, xmax)

        #ax1.set_ylim(thisy.min(), thisy.max())
        self.canvas.draw()

    # def plot(self, xdata, ydata):
    #    self.ax.plot(xdata, ydata)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = FBPlot()

    # generate data
    # np.random.seed(19680801)
    # x = np.arange(0.0, 5.0, 0.01)
    # y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
    # fb1, = main.plot(x, y, Label='data')

    main.show()
    sys.exit(app.exec_())

martin | 2020-05-25 18:46:25 UTC | #5

Nice solution @tcarson4344 hadn't occurred to me to use a widget for this. Thanks for posting! :+1:


cippall | 2021-08-04 07:37:05 UTC | #6

Thanks for posting your solution. Really helped.

Just a note that the QWidgetAction needs as a parent the menu not the label itself. To avoid my app to crash i had to modify these lines:

Over 10,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt6
Take a look

Downloadable ebook (PDF, ePub) & Complete Source code

Also available from Leanpub and Amazon Paperback

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

python
        fbtlAction = QWidgetAction(self.fbMenu)

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

How to Show Qmenu Title was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular https://www.martinfitzpatrick.com/browse/books/ on the subject.