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.

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

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:

python
        fbtlAction = QWidgetAction(self.fbMenu)

Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!

More info Get the book

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

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.