Reputation: 83
I'm new to PyQt5 and pretty new to Python in general. I needed a keyboard layout, and in stead of manually creating a QPushButton for every letter and setting the text, coordinates and size for each i tried to automate it. My idea was to iterate through a dictionary to create a new name for each QPushButton. I then had to use something else than self.dict[x]
as text for the QPushButton, because self.dict[x]
was a QPushButton itself. I created a list with all the characters and used list[x]
in stead. I would use the coords
list to tweak the coordinates for each QPushButton through the iterations. My attempt looked like this:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
class Window(QMainWindow):
coords = [0, 200]
size = 50
count = 0
list = [chr(x) for x in range(65, 91)]
dict = {}
for x in range(len(list)):
dict[x] = list[x]
def __init__(self):
super(Window, self).__init__()
self.setGeometry(100, 100, 800, 500)
self.setWindowTitle('')
self.makeButtons()
def makeButtons(self):
for x in range(len(self.dict)):
self.dict[x] = QtWidgets.QPushButton(self)
self.dict[x].resize(self.size, self.size)
self.dict[x].move(self.coords[0], self.coords[1])
self.dict[x].setText(self.list[x])
self.coords[0] += self.size
self.count += 1
if self.count == 9:
self.coords = [0, 250]
if self.count == 18:
self.coords = [25, 300]
def window():
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
window()
which does create the keyboard layout i want. However, I got stuck trying to create a method for each QPushButton, so I can't add any functionalty to the buttons. Is it possible to implement the automation of creating other metods in the makeButton method like I did with the QPushButtons themselves, or do i need another strategy to automate it?
Upvotes: 0
Views: 251
Reputation: 48335
Use lambda and emit signals manually:
class Window(QMainWindow):
keyClicked = QtCore.pyqtSignal(object)
# ...
def makeButtons(self):
# ...
for x in range(len(self.dict)):
self.dict[x] = button = QtWidgets.QPushButton(self)
# ...
button.clicked.connect(lambda _, key=self.list[x]: self.keyClicked.emit(key))
# ...
Whenever you have lots of widgets that behave in the same way, you should provide a common interface for their behavior. lambda
functions can help you with that, but you have to be aware about the scope the variables belong to (remember, as soon as a function exits/returns, any "anonymous" variable that has no persistent relation to other objects will be garbage collected by python, which practically means "deleted").
As you can see in the example above, I'm using two arguments.
The first is the one provided whenever the signal is emitted (any QAbstractButton descendant emits a clicked
signal with a bool arguments that reports the new "checked" status, so if the button is checkable the argument will be the button checked state, otherwise it will always be False
). We can just ignore that, but it has to be added as an argument of the lambda function.
The latter keyword argument is provided to keep consistence with the scope of the current cycle. In this way we can ensure that the key
argument of the lambda function will always be that of the current cycle (otherwise it will always be the last for
value assigned to that for
cycle).
This part of the answer is totally unrequested and unnecessary for the actual purpose of this question; nonetheless, I'm assuming you're new not only to Python and PyQt in general, but to the UX side that programming almost always has to relate to. As programmers, we have to be aware about that, since the majority of users that will finally use our products are not programmers.
Creating a keyboard GUI is not an easy task.
People are accustomed to the keyboards they use everyday (guess what, smartphones). Choosig an alphabet-order based layout for a keyboard that uses a standard "three rows" layout is not only an unpopular choice, but also a wrong one. We're (luckily) far away from that gap-time that was before the 2010's, where almost nobody knew how to type at all, but we also have to consider the results of this aspect.
To add even more mess to that, consider that even if QWERTY layout is almost standard, there are other layouts that differ partially (or completely) from it. For example, French people use AZERTY, while some Central Europe countries use QWERTZ, not to mention non-latin based writings.
And all of this only accounts for the standard alphabet letters.
from string import ascii_uppercase
from PyQt5 import QtCore, QtWidgets
class Keyboard(QtWidgets.QWidget):
ASCII, QWERTY, DVORAK = 0, 1, 2
KeyLayoutLetters = {
ASCII: [ascii_uppercase[r*9:r*9+9] for r in range(3)],
QWERTY: ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'],
DVORAK: ['PYFGCRL', 'AOEUIDHTNS ', 'QJKXBMWVZ'],
}
# some default special stretch set for specific keyboard layouts
KeyStretch = {
QWERTY: [(1, 1), (1, 1), (1, 2)],
DVORAK: [(2, 1), (1, 2), (3, 1)],
}
keySize = 50
spacing = 2
letterClicked = QtCore.pyqtSignal(object)
def __init__(self):
super(Keyboard, self).__init__()
self.setKeyboardLayout()
def setKeyboardLayout(self, keyLayout=None):
keyLayout = keyLayout if keyLayout is not None else self.ASCII
if self.layout() is not None:
QtWidgets.QWidget().setLayout(self.layout())
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(self.spacing)
stretches = self.KeyStretch.get(keyLayout, [(1, 1)] * 3)
for row, rowChars in enumerate(self.KeyLayoutLetters.get(keyLayout, self.KeyLayoutLetters[self.ASCII])):
rowLayout = QtWidgets.QHBoxLayout()
rowLayout.setSpacing(self.spacing)
layout.addLayout(rowLayout)
stretchLeft, stretchRight = stretches[row]
rowLayout.addStretch(stretchLeft)
for letter in rowChars:
if not letter.strip():
spacer = QtWidgets.QWidget()
rowLayout.addWidget(spacer)
spacer.setFixedSize(self.keySize * .5, self.keySize)
continue
letterButton = QtWidgets.QPushButton(letter)
rowLayout.addWidget(letterButton, stretch=0)
letterButton.setFixedSize(self.keySize, self.keySize)
letterButton.clicked.connect(lambda _, key=letter: self.letterClicked.emit(key))
rowLayout.addStretch(stretchRight)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QVBoxLayout(central)
# just a QLineEdit widget to test the text intertion
self.lineEdit = QtWidgets.QLineEdit()
layout.addWidget(self.lineEdit)
self.keyboard = Keyboard()
layout.addWidget(self.keyboard)
self.keyboard.setKeyboardLayout(Keyboard.DVORAK)
self.keyboard.letterClicked.connect(self.lineEdit.insert)
self.keySelector = QtWidgets.QComboBox()
layout.addWidget(self.keySelector)
self.keySelector.addItems(['ASCII', 'QWERTY', 'DVORAK'])
self.keySelector.currentIndexChanged.connect(self.keyboard.setKeyboardLayout)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
keyboard = MainWindow()
keyboard.show()
sys.exit(app.exec_())
Keep in mind that this is a really simple and raw implementation of what you're probably looking for. There is a moltitude of aspects you'd need to take care of (mostly related to special characters that are typical of the user's language).
Upvotes: 1