How to change button text in pyqt5 when clicked with keypress

Viewed 824

So currently I am creating Sudoku in PyQt5. So, far I have Board.py which just generates new boards. Currently I am working on Play.py, which should be able to click on empty squares and be able to change it with the press of a keyboard button(keypress). In Play.py I have finished everything - All the numbers are put into a board and I am able to click on buttons and it initiates a keypress. But, the way I coded it, all the buttons are stored into a list (I did this because I wanted the code to look clean) and it only updates the last button. Instead of it updating the last button, I would like it to update the button that was most recently clicked.

I have tried many things but what I have narrowed it down to currently is to find the position of the button in the list of all buttons(all_buttons) and do something from there but I'm not sure. So, how would I be able to take the key press and put it into the button that was recently pressed?

Here is Board.py. It just generates new boards it is unnecessary to look at.

import random
import numpy as np


# populates a row in random spaces
def populate():
    row = [0] * 9
    num_in_box = random.randint(3, 6)
    while True:
        spot = random.randint(0, 8)
        r = random.randint(1, 9)
        if r not in row:
            row[spot] = r
        if row.count(0) == (9 - num_in_box):
            break
    return row


# populates a grid in random spaces - has duplicates in column, row and box
mapped = list()
for x in range(9): mapped.append(populate())


# checks every number in column and row and returns numbers in list
def col_row_nums(array, row, col):
    check_col = [j for j in array[:, col] if j != 0]
    check_row = [i for i in array[row] if i != 0]
    if array[row][col] in check_col:
        check_col.remove(array[row][col])
    return check_col + check_row


# checks every number box and returns numbers in list
def box_nums(array, box_row):
    reshaped_box = np.reshape(array, (27, 3))
    list_boxes_in_rows = list()
    for a in range(3):
        for b in range(3):
            for c in range(3):
                p2 = list(np.reshape((reshaped_box[c::3]), (3, 9)))
                for d in range(3): list_boxes_in_rows.append(p2[a])
    array_boxes_in_rows = np.array(list_boxes_in_rows)
    return [k for k in array_boxes_in_rows[box_row] if k != 0]


# removes any duplicates so each column, row and box all have only one set of numbers 1 - 9
def finalize_board():
    box_rows_num = -1
    for x in range(9):
        for y in range(9):
            box_rows_num += 1
            arr = np.array(mapped)
            possible_nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            col_row_duplicates = list()
            box_duplicates = list()
            used_nums = set(col_row_nums(arr, x, y)) | set(box_nums(arr, box_rows_num))

            for w in used_nums:
                col_row_count = col_row_nums(arr, x, y).count(w)
                box_count = box_nums(arr, box_rows_num).count(w)
                if col_row_count > 1: col_row_duplicates.append(w)
                if box_count > 1: box_duplicates.append(w)
            if mapped[x][y] in col_row_duplicates or mapped[x][y] in box_duplicates:
                remaining_nums = list(set(possible_nums) - set(used_nums))
                if len(remaining_nums) > 0: mapped[x][y] = random.choice(remaining_nums)
    return mapped

Next is Play.Py.

import sys
from pynput import keyboard
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg

import Board


class MainWindow(qtw.QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # -------------------------------------------------------------------------------------------
        all_buttons = list()
        layout = qtw.QGridLayout()
        layout.setSpacing(0)

        board = Board.finalize_board()
        y_num = -1

        for x, rows in enumerate(board):
            for y, cell in enumerate(rows):
                y += 1
                if cell == 0: cell = ' '
                button = qtw.QPushButton(str(cell), self)
                button.setFixedHeight(100)
                button.setFixedWidth(100)
                layout.addWidget(button, x, y)
                if cell == ' ':
                    button.clicked.connect(lambda: pressed())
                    button.setStyleSheet('QPushButton {background-color: #e3efff; color: black;}')
                else:
                    button.setEnabled(False)
                    button.setStyleSheet('QPushButton {background-color: #ebebeb; color: black;}')
                all_buttons.append(button)


        def pressed():
            with keyboard.Events() as events:
                # maybe - ?? HOW TO RETURN POSITION OF BUTTON IN ALL_BUTTONS
                event = events.get(1e6)
                x = event.key
                print(str(x))
                button.setText(str(x))

        self.setLayout(layout)

        # -------------------------------------------------------------------------------------------
        self.show()


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec_())

1 Answers

You are almost there. Use keyPressEvent click here.

import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.Qt import Qt

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        
    def keyPressEvent(self, event):
        print(chr(event.key()))

    def test_method(self):
        print('Space key pressed')

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

    demo = MainWindow()
    demo.show()

    sys.exit(app.exec_())

event.key() will always return an integer. You can use the built-in chr(int) function to get the key, the problem is we can't identify keys other than alphabets and numbers. If you want other keys as well, use conditions.

   def keyPressEvent(self, event):
       if event.key() == Qt.Key_Space:
           print("Space pressed")
   # Qt.Key_Shift for shift key, Qt.Key_Control for Ctrl key.

HERE IS THE FINAL CODE: read edit2

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5.QtCore import pyqtSlot
from PyQt5 import QtGui as qtg
import Board


class MainWindow(qtw.QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # -------------------------------------------------------------------------------------------
        all_buttons = list()
        layout = qtw.QGridLayout(self)
        layout.setSpacing(0)

        board = Board.finalize_board()
        y_num = -1

        for x, rows in enumerate(board):
            for y, cell in enumerate(rows):
                y += 1
                if cell == 0: cell = ' '
                button = qtw.QPushButton(str(cell), self)
                button.setFixedHeight(100)
                button.setFixedWidth(100)
                layout.addWidget(button, x, y)
                if cell == ' ':
                    button.setCursor(qtc.Qt.PointingHandCursor)
                    button.mousePressEvent = lambda event: self.mousePressEvent(event)
                    button.setStyleSheet('QPushButton {background-color: #e3efff; color: black;}')
                else:
                    button.setEnabled(False)
                    button.setStyleSheet('QPushButton {background-color: #ebebeb; color: black;}')
                all_buttons.append(button)

        self.last_clicked = ''
        self.setLayout(layout)

        # -------------------------------------------------------------------------------------------
        self.show()
    def keyPressEvent(self, event):
        k = (chr(event.key()))
        self.last_clicked.setText(k)
        self.last_clicked.setStyleSheet("background-color: #e3efff; color: black;")


    def mousePressEvent(self, event):
        widget = self.childAt(event.pos())
        widget.setStyleSheet("background-color: #e3efff; color: black; border: 1px solid red")
        print(widget.pos())
        self.last_clicked = widget
        return qtw.QWidget.mousePressEvent(self, event)

        # -------------------------------------------------------------------------------------------


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec_())

Now when you press a key, it updates the text of the recently clicked button. You might want to add some try except to the keyPressEvent and setText function


EDIT 2: I don't exactly know why the first button gets selected, I'm guessing something's wrong with the mousePressEvent. It first returns the first widget and then the actual button that was clicked. I tweaked a few things and it now works fine.

Stuff I changed:

  • I created a new list self.available_buttons. It contains all the empty/clickable buttons.

  • Whenever an empty button is pressed, the code goes through the list and searches for last_clicked widget. If the last_clicked widget is found, it highlights the button and changes all other buttons to normal(no border color).


class MainWindow(qtw.QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # -------------------------------------------------------------------------------------------
        all_buttons = list() # contains all buttons
        self.available_buttons = list() # contains clickable buttons
        layout = qtw.QGridLayout(self)
        layout.setSpacing(0)

        board = Board.finalize_board()
        y_num = -1

        for x, rows in enumerate(board):
            for y, cell in enumerate(rows):
                y += 1
                if cell == 0: cell = ' '
                button = qtw.QPushButton(str(cell), self)
                button.setFixedHeight(100)
                button.setFixedWidth(100)
                layout.addWidget(button, x, y)
                if cell == ' ':
                    button.setCursor(qtc.Qt.PointingHandCursor)
                    button.mousePressEvent = lambda event: self.mousePressEvent(event)
                    button.setStyleSheet('QPushButton {background-color: #e3efff; color: black;}')
                    self.available_buttons.append(button) # appends empty buttons to the list
                else:
                    button.setEnabled(False)
                    button.setStyleSheet('QPushButton {background-color: #ebebeb; color: black;}')
                all_buttons.append(button)

        self.last_clicked = ''
        self.setLayout(layout)
        self.show()

        # -------------------------------------------------------------------------------------------
    def keyPressEvent(self, event):
        k = (chr(event.key()))
        self.last_clicked.setText(k)
        self.last_clicked.setStyleSheet("background-color: #e3efff; color: black;")

    def mousePressEvent(self, event):
        widget = self.childAt(event.pos())
        self.last_clicked = widget

        for button in self.available_buttons:  #searches for clicked button
            if widget == button:
                button.setStyleSheet("background-color: #e3efff; color: black; border: 1px solid red") # adds border to clicked button
            else:
                button.setStyleSheet("background-color: #e3efff; color: black;") # all the other buttons go back to their normal state

        return qtw.QWidget.mousePressEvent(self, event)
        # -------------------------------------------------------------------------------------------

Related