简体   繁体   中英

How to get instance values inside the keyPressEvent in PyQt5?

I have two instances. The first instance, active_part value is 1 and the second instance active-part value is 2. Based on this active_part value, I need to print some strings,

If we check self.active_part value , outside the method, it works fine. But I don't know how to check inside the method " keypressEvent ". Here is my code and suggest the best way to achieve it?

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Create_Instance(QWidget):
    entery = pyqtSignal()
    def __init__(self,label,dict_item,active_part):
        super().__init__()
        self.setFocusPolicy(Qt.StrongFocus)

        self.dict_items = dict_item
        self.label = label
        self.active_part = active_part

        self.lbl = QLabel()
        self.lbl.setText(self.label)

        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.lbl)
        self.setLayout(self.vbox)
        print("active_part value is ",self.active_part)

        if self.active_part == "1":
            print("active_part value is( inside the active_part = 1)",self.active_part)
        elif self.active_part == "2":
            print("active_part value is( inside the active_part = 2)",self.active_part)

    def keyPressEvent(self, event):
        if self.active_part == "1" and event.key() == Qt.Key_A:
            print(" you press A and active_part value is 1")
        elif self.active_part == "2" and event.key() == Qt.Key_B:
            print(" you press B and active_part value is 2")

class Main_Window(QWidget):
    def __init__(self):
        super(). __init__()
        self.setWindowTitle("Main Window")
        self.layout_main = QVBoxLayout()

        self.firstmenu_container = Create_Instance(label="Press A",dict_item="1",active_part = "1")
        self.secondmenu_container = Create_Instance(label="Press B",dict_item="2",active_part = "2")

        self.layout_main.addWidget(self.firstmenu_container)
        self.layout_main.addWidget(self.secondmenu_container)
        self.setLayout(self.layout_main)

def main():
    app = QApplication(sys.argv)
    ex = Main_Window()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Edited code For more clearance, here is my edited code. My aim is if I press the right or left arrow keys, then print the corresponding Label value. And if I press the Up or Down arrow keys, then the active part will change from first to second or vice versa and print Labe values. For example, If I press the down arrow, now the second group of labels will get active and respond to left or right arrow keys and print second group label values.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Create_Instance(QWidget):
    entery = pyqtSignal()
    def __init__(self,label1,label2,label3,dict_item,active_part):
        super().__init__()
        self.setFocusPolicy(Qt.StrongFocus)
        self.list = []

        self.dict_items = dict_item
        self.label1 = label1
        self.label2 = label2
        self.label3 = label3
        self.active_part = active_part

        self.lbl1 = QLabel()
        self.lbl1.setText(self.label1)
        self.lbl2 = QLabel()
        self.lbl2.setText(self.label2)
        self.lbl3 = QLabel()
        self.lbl3.setText(self.label3)

        self.hbox = QHBoxLayout()
        self.hbox.setSpacing(5)
        self.hbox.addWidget(self.lbl1)
        self.hbox.addWidget(self.lbl2)
        self.hbox.addWidget(self.lbl3)
        self.list.append(self.lbl1.text())
        self.list.append(self.lbl2.text())
        self.list.append(self.lbl3.text())


        self.vbox = QVBoxLayout()
        self.vbox.addLayout(self.hbox)
        self.setLayout(self.vbox)
        self.temp_value = 0
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Left and self.temp_value >= 2:
            self.temp_value = self.temp_value - 1
        if event.key() == Qt.Key_Right and self.temp_value <= 2 :
            self.temp_value = self.temp_value + 1
        if event.key() == Qt.Key_Down and self.active_part == "1":
            self.active_part = 2
            print("you press down arrow and now active part is second ")
        if event.key() == Qt.Key_Up and self.active_part == "2":
            self.active_part = 1
            print("you press up arrow and now active part is first ")

        if self.active_part == "1":
            if self.temp_value == 1:
                print("you select :", self.list[self.temp_value-1])
            if self.temp_value == 2:
                print("you select :", self.list[self.temp_value-1])
            if self.temp_value == 3:
                print("you select :", self.list[self.temp_value-1])

        if self.active_part == "2":
            if self.temp_value == 1:
                print("you select :", self.list[self.temp_value - 1])
            if self.temp_value == 2:
                print("you select :", self.list[self.temp_value - 1])
            if self.temp_value == 3:
                print("you select :", self.list[self.temp_value - 1])
class Main_Window(QWidget):
    def __init__(self):
        super(). __init__()
        self.setWindowTitle("Main Window")
        self.layout_main = QVBoxLayout()

        self.firstmenu_container = Create_Instance(label1="Press A",label2 = "press B", label3 = "press C",dict_item="1",active_part = "1")
        self.secondmenu_container = Create_Instance(label1="Press X",label2 = "press Y", label3 = "press Z",dict_item="2",active_part = "2")

        self.layout_main.addWidget(self.firstmenu_container)
        self.layout_main.addWidget(self.secondmenu_container)
        self.setLayout(self.layout_main)

def main():
    app = QApplication(sys.argv)
    ex = Main_Window()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

A "menu-like" widget should always consider a proper hierarchy of objects, each one only responsible of what that object can do, and without having access or control to its parent.

Considering this, a possible implementation should use 3 classes:

  • an "item" class that will only trigger its own "shortcut" when focused;
  • a "group" class that will allow switching between its items;
  • a main "menu" class that will allow switching between groups;

Note that, interestingly enough, the above hierarchical strucure is quite similar to what Qt already provides for a standard menu bar (QMenuBar > QMenu > QAction). Also, the "action range" of a "child" object is quite important from the OOP perspective: a child should not actively nor directly do anything on any of its parents, but only "ask" (signal) them to do something about it (see my slightly related post on object hierarchy ).

All these classes will override their keyPressEvent and eventually ignore it (by calling the base implementation) whenever they don't handle their assigned keys:

  • the item will only accept and handle its own "key", and ignore everything else;
  • the group will only accept and handle right and left arrows, and ignore the others;
  • the menu will only use the up and down arrows, while ignoring the rest;
class MenuItem(QLabel):
    activated = pyqtSignal()
    def __init__(self, key):
        super().__init__('Press ' + key)
        self.setFocusPolicy(Qt.StrongFocus)
        self.key = getattr(Qt, 'Key_' + key.upper())
        self.setStyleSheet('''
            QLabel::focus {
                border: 1px solid black;
            }
        ''')

    def keyPressEvent(self, event):
        if event.key() == self.key:
            self.activated.emit()
        else:
            super().keyPressEvent(event)


class MenuGroup(QWidget):
    activated = pyqtSignal(object, object)
    def __init__(self, active_part):
        super().__init__()
        self.active_part = active_part
        QHBoxLayout(self)
        self.items = []

    def index(self, widget):
        try:
            return self.items.index(widget)
        except ValueError:
            return -1

    def item(self, index):
        index = max(0, min(len(self.items) - 1, index))
        try:
            return self.items[index]
        except IndexError:
            return None

    def addItem(self, key):
        item = MenuItem(key)
        self.items.append(item)
        self.layout().addWidget(item)
        item.activated.connect(lambda: self.activated.emit(self, item))
        return item

    def keyPressEvent(self, event):
        if event.key() in (Qt.Key_Left, Qt.Key_Right):
            widget = self.focusWidget()
            if widget:
                index = self.items.index(widget)
                if event.key() == Qt.Key_Left:
                    index = max(0, index - 1)
                else:
                    index = min(len(self.items) - 1, index + 1)
                newWidget = self.items[index]
                if newWidget != widget:
                    newWidget.setFocus()
                    return
        super().keyPressEvent(event)


class MenuWidget(QWidget):
    activated = pyqtSignal(object, object)
    def __init__(self):
        super().__init__()
        QVBoxLayout(self)
        self.groups = []

    def addGroup(self, active_part):
        group = MenuGroup(active_part)
        self.groups.append(group)
        self.layout().addWidget(group)
        group.activated.connect(self.activated)
        return group

    def keyPressEvent(self, event):
        if event.key() in (Qt.Key_Up, Qt.Key_Down):
            widget = self.focusWidget()
            if widget:
                parent = widget.parent()
                groupIndex = self.groups.index(parent)
                if event.key() == Qt.Key_Up:
                    groupIndex = max(0, groupIndex - 1)
                else:
                    groupIndex = min(len(self.groups) - 1, groupIndex + 1)
                itemIndex = parent.index(widget)
                newWidget = self.groups[groupIndex].item(itemIndex)
                if newWidget and newWidget != widget:
                    newWidget.setFocus()
                    return
        super().keyPressEvent(event)


class Main_Window(QWidget):
    def __init__(self):
        super(). __init__()
        self.setWindowTitle("Main Window")
        self.layout_main = QVBoxLayout(self)

        self.topMenu = MenuWidget()
        self.layout_main.addWidget(self.topMenu)

        self.firstGroup = self.topMenu.addGroup('1')
        self.firstGroup.addItem('A')
        self.firstGroup.addItem('B')
        self.firstGroup.addItem('C')
        self.secondGroup = self.topMenu.addGroup('2')
        self.secondGroup.addItem('X')
        self.secondGroup.addItem('Y')
        self.secondGroup.addItem('Z')

        self.topMenu.activated.connect(self.itemActivated)

    def itemActivated(self, group, item):
        print('activated', group.active_part, item.text())

Solution 1

This solution is based on your codes. The main idea is that the key press is handled by the Main_window class.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Create_Instance(QWidget):
    entery = pyqtSignal()
    def __init__(self,label1,label2,label3,dict_item):
        super().__init__()
        self.setFocusPolicy(Qt.StrongFocus)
        self.list = []

        self.dict_items = dict_item
        self.label1 = label1
        self.label2 = label2
        self.label3 = label3

        self.lbl1 = QLabel()
        self.lbl1.setText(self.label1)
        self.lbl2 = QLabel()
        self.lbl2.setText(self.label2)
        self.lbl3 = QLabel()
        self.lbl3.setText(self.label3)

        self.hbox = QHBoxLayout()
        self.hbox.setSpacing(5)
        self.hbox.addWidget(self.lbl1)
        self.hbox.addWidget(self.lbl2)
        self.hbox.addWidget(self.lbl3)
        self.list.append(self.lbl1.text())
        self.list.append(self.lbl2.text())
        self.list.append(self.lbl3.text())


        self.vbox = QVBoxLayout()
        self.vbox.addLayout(self.hbox)
        self.setLayout(self.vbox)
        self.temp_value = 0

    def change_and_print_selected(self, event):
        if event.key() == Qt.Key_Left and self.temp_value >= 2:
            self.temp_value = self.temp_value - 1
        if event.key() == Qt.Key_Right and self.temp_value <= 2 :
            self.temp_value = self.temp_value + 1

        if self.temp_value == 1:
            print("you select :", self.list[self.temp_value-1])
        elif self.temp_value == 2:
            print("you select :", self.list[self.temp_value-1])
        elif self.temp_value == 3:
            print("you select :", self.list[self.temp_value-1])
        else:
            pass

        return self.temp_value


class Main_Window(QWidget):
    def __init__(self):
        super(). __init__()
        self.setWindowTitle("Main Window")
        self.layout_main = QVBoxLayout()

        self.active_part = "1"
        self.temp_value = 0
        self.firstmenu_container = Create_Instance(label1="Press A",label2 = "press B", label3 = "press C",dict_item="1")
        self.secondmenu_container = Create_Instance(label1="Press X",label2 = "press Y", label3 = "press Z",dict_item="2")

        self.layout_main.addWidget(self.firstmenu_container)
        self.layout_main.addWidget(self.secondmenu_container)
        self.setLayout(self.layout_main)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Down and self.active_part == "1":
            self.active_part = "2"
            self.secondmenu_container.temp_value = self.temp_value
            print("you press down arrow and now active part is second ")
        if event.key() == Qt.Key_Up and self.active_part == "2":
            self.active_part = "1"
            self.firstmenu_container.temp_value = self.temp_value
            print("you press up arrow and now active part is first ")
        if self.active_part == "1":
            self.temp_value = self.firstmenu_container.change_and_print_selected(event)
        elif self.active_part == "2":
            self.temp_value = self.secondmenu_container.change_and_print_selected(event)


def main():
    app = QApplication(sys.argv)
    ex = Main_Window()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

In this implement

  1. No matter when and where you press those keys, these events will be processed and change these menu_container s' states. This will be problematic if you don't want the key to take effect globally. Please refer to the comment by @musicamante.
  2. The selected items is not highlighted.
  3. I am not sure whether you want to set Press A to be selected when the user press key A on the keyboard. This function is not supported currently.

Solution 2

I also considered about using QTableWidget . By handling the currentCellChanged() signal, I get a window that may be similar to what you want.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Main_Window(QWidget):
    def __init__(self):
        super(). __init__()
        self.setWindowTitle("Main Window")
        self.layout_main = QVBoxLayout()

        self.table_widget = QTableWidget(2, 3, self)
        self.Press_A = QTableWidgetItem("Press A")
        self.Press_B = QTableWidgetItem("Press B")
        self.Press_C = QTableWidgetItem("Press C")
        self.Press_X = QTableWidgetItem("Press X")
        self.Press_Y = QTableWidgetItem("Press Y")
        self.Press_Z = QTableWidgetItem("Press Z")
        self.table_widget.setItem(0, 0, self.Press_A)
        self.table_widget.setItem(0, 1, self.Press_B)
        self.table_widget.setItem(0, 2, self.Press_C)
        self.table_widget.setItem(1, 0, self.Press_X)
        self.table_widget.setItem(1, 1, self.Press_Y)
        self.table_widget.setItem(1, 2, self.Press_Z)
        # self.layout_main.addWidget(self.firstmenu_container)
        # self.layout_main.addWidget(self.secondmenu_container)

        # make the table looks nicer
        self.table_widget.horizontalHeader().hide()
        self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table_widget.verticalHeader().hide()
        self.table_widget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # prevent mouse to change selected item by disabling the delivery of all mouse events
        self.table_widget.setAttribute(Qt.WA_TransparentForMouseEvents)

        self.layout_main.addWidget(self.table_widget)
        self.setLayout(self.layout_main)

        self.table_widget.currentCellChanged.connect(self.on_select_changed)

    def on_select_changed(self, currentRow, currentColumn, previousRow, previousColumn):
        if currentRow > previousRow != -1:  # != -1 is used to prevent printing when initializing the QTableWidget
            print("you press down arrow and now active part is second ")
        elif currentRow < previousRow != -1:
            print("you press up arrow and now active part is first ")
        else:
            pass

        current_text = self.table_widget.currentItem().text()
        print("you select :" + current_text)



def main():
    app = QApplication(sys.argv)
    ex = Main_Window()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

In this implement

  1. You may adjust the codes above to make it look nicer.
  2. However, this implementation perhaps corresponds to set self.temp_value=1 , since the Press A is selected when initialized. If you want no item to be selected until the key 'right' is pressed, I think extra efforts are needed to improve this solution.
  3. Like solution 1, Press A can not set to be selected when the user press key A on the keyboard, either.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM