PyQT5 : why do direct shortcut class works whereas shortcut syntax doesn't work in QAction?

32 views Asked by At

I'm facing a weird problem where page changing shortcut works in one syntax and don't in another.

My application has 2 windows, each one on a different screen. The first window displays the main information, and the secondary displays monitoring information. Both windows are independents but should share some common pages (preferences / menu ...). Each window has different pages : some common (menu, preferences), and some of their own (home page...)

Then the structure is a little bit complex : both windows are contained in an object (config), which deals with all the objects of the application.

Both windows inherit of a commun "Windw" class, to share common methods and common pages (home page / preference...). Each window has its own class for all its dedicated variables and objects.

Everything works well for the moment and I try now to create the page's change method. Each page is referenced in a table of contents dictionary which is calling the right content. The page change method works fine as well.

The problem is related with shortcuts / actions. If I use this syntax :

menu_action = QAction ("Menu", self)
menu_action.setShortcut(QKeySequence("Ctrl+m"))
menu_action.activated.connect(self.page_change)

in the WindowPcp class, or with self.pcp when I try to put this code in the Configuration class, "page_change" is not called.

If I use this syntax :

menu_short = QShortcut(QKeySequence("Ctrl+m", self)
menu_short.activated.connect(self.page_change)

the shortcut behave as expected : the Windwpcp page is changed from home_page to menu page.

Here is a minimal working code of my application skeleton (the real application is too big to be published here (around 2500 lines for all of the windows behavior) :

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication, QKeySequence
from PyQt5.QtWidgets import (QAction,
                             QApplication,
                             QFrame,
                             QHBoxLayout,
                             QLabel,
                             QMainWindow,
                             QShortcut,
                             QStackedLayout,
                             QVBoxLayout,
                             QWidget)


class Windw(QMainWindow):
    def __init__(self):
        super().__init__()
        self.active_page = "home"

        self.menu = QLabel("Menu")

        m_body = QFrame()
        # Main common Layout
        self.central_layout = QStackedLayout()

        self.menu_page = QFrame()
        menu_page_layout = QVBoxLayout()
        self.menu_page.setLayout(menu_page_layout)

        self.menu_frame = QFrame()
        self.menu_frame_layout = QHBoxLayout()
        self.menu_frame.setLayout(self.menu_frame_layout)
        # Adding inner frame to main menu page frame
        menu_page_layout.addWidget(self.menu_frame)

        self.home_page = QFrame()
        self.home_page_layout = QVBoxLayout()
        self.home_page.setLayout(self.home_page_layout)

        self.preferences = QFrame()
        preferences_layout = QVBoxLayout()
        self.preferences.setLayout(preferences_layout)

        self.tbl_contents = {"menu": self.menu_page, "home": self.home_page, "preferences": self.preferences}

        self.central_layout.addWidget(self.menu_page)
        self.central_layout.addWidget(self.home_page)
        self.central_layout.addWidget(self.preferences)

        m_body.setLayout(self.central_layout)
        self.setCentralWidget(m_body)

    def page_change(self):
        origine = self.sender()
        print(f"page_changed activated !!!!")
        if origine.objectName() == "menu_short":
            self.active_page = "menu"
            self.central_layout.setCurrentWidget(self.tbl_contents[self.active_page])
        elif origine.objectName() == "home_short":
            self.active_page = "home"
            self.central_layout.setCurrentWidget(self.sommaire[self.page_active])
        elif origine.objectName() == "preferences_short":
            self.active_page = "preferences"
            self.central_layout.setCurrentWidget(self.sommaire[self.page_active])


class WindowPcp(Windw):
    def __init__(self):
        super().__init__()
        self.active_page = "home"
        self.setFocusPolicy(Qt.TabFocus)

        text_pcp = QLabel("Pcp Homepage")

        # Adding content to each pages
        self.menu_frame_layout.addWidget(self.menu)
        self.home_page_layout.addWidget(text_pcp)

        self.central_layout.setCurrentWidget(self.tbl_contents[self.active_page])

        """menu_act = QAction("Menu", self,)
        menu_act.setObjectName("menu_short")
        menu_act.setShortcut(QKeySequence("Ctrl+m"))                 # Never works
        menu_act.triggered.connect(self.page_change)"""

        menu_short = QShortcut(QKeySequence("Ctrl+m"), self)
        menu_short.setObjectName("menu_short")                       # works as expected
        menu_short.activated.connect(self.page_change)


class WindowScd(Windw):
    def __init__(self):
        super().__init__()
        self.active_page = "home"
        self.setFocusPolicy(Qt.NoFocus)

        text_scd = QLabel("Scd Homepage")

        # Adding content to each pages
        self.menu_frame_layout.addWidget(self.menu)
        self.home_page_layout.addWidget(text_scd)

        self.central_layout.setCurrentWidget(self.tbl_contents[self.active_page])


class Configuration(QWidget):
    def __init__(self):
        super().__init__()
        self.screen_conf = QGuiApplication.screens()
        self.prim_screen = QGuiApplication.primaryScreen()

        self.pcp = WindowPcp()
        self.scd = WindowScd()

        self.pcp.setGeometry(self.screen_conf[1].geometry())
        self.scd.setGeometry(self.prim_screen.geometry())

       # commented lines to compare working and not working syntax :
        """menu_act = QAction("Menu", self.pcp)
        menu_act.setObjectName("menu_short")                     # Never works
        menu_act.setShortcut(QKeySequence("Ctrl+m"))
        menu_act.triggered.connect(self.pcp.page_change)"""

        """menu_short = QShortcut(QKeySequence("Ctrl+m"), self.pcp)
        menu_short.setObjectName("menu_short")                   # works as expected
        menu_short.activated.connect(self.pcp.page_change)"""


# ------------- MAIN --------------------
app = QApplication.instance()
if not app:
    app = QApplication(sys.argv)

config = Configuration()  # Configuration object : contains windows and all main objects

config.scd.show()
config.pcp.show()

try:
    sys.exit(app.exec())
except SystemExit:
    print('windows closed...')

I tried to use QActions first, but the shortcut doesn't work. I tried with QShortcut and it works... but the window needs to be in focus... I then tried to put the QShortcut at the Configuration Level to have global control... But QShortcut refusse "self" as the parent object when related to Configuration and only accepts WindwPCP as parent.

Here are my questions :

  1. Why does direct QShortcut work and why the QAction shortcut (or more certainly the QAction itself) doesn't ? It's strange to me as other QActions are working inside WindwPCP when used on QPushButtons

  2. I really need these shortcuts to work as they will be used by a remote control. Then even if the QShortcut solution works, it isn't good enough for me as the window must be in focus. If focus is changed, I'll loose the remote control of my application. That's why I tried to add the QShortcut in the Configuration class, to be at the top Level of the application. But I'm facing another problem : the QShortcut doesn't work if I replace menu_short = QShortcut(QKeySequence("Ctrl+m"), self.pcp) with menu_short = QShortcut(QKeySequence("Ctrl+m"), self)

The shortcut doesn't work on the config object itself (Configuration class), it only accepts my windows as a parent. Is there a way to have a global level of shortcuts, which I'll then filter, to send them to the right objects in order to avoid to have to deal with the focus ?

  1. If nothing for 2) is possible, as I don't need remote control for the second window which will be used by a touchscreen, is there a way to force the focus on WindwPCP, without loosing control on its inside widgets ? setFocusPolicy on both windows doesn't seem ti be enough for the moment...

Thank you for your time and help : I'm really stuck at the moment...

0

There are 0 answers