Issue
The following somewhat long code runs after pip installing PyQt5:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel
from PyQt5.QtCore import pyqtSignal
class MyPushButton(QPushButton):
self_emit_toggle_signal = pyqtSignal(list)
def __init__(self, text):
super().__init__(text)
self.emit_self_toggle = False
self.toggled.connect(self.when_toggled)
def when_toggled(self, checked):
if self.emit_self_toggle:
self.self_emit_toggle_signal.emit([self, checked])
class ButtonWidget(QWidget):
def __init__(self):
super().__init__()
self.buttons = []
self.checked_buttons = []
self.init_ui()
def init_ui(self):
self.setWindowTitle("Widget with 10 Push Buttons")
# Create 10 push buttons
for i in range(0, 10):
button = MyPushButton(f"Button {i}")
self.buttons.append(button)
# Add buttons to the layout
layout = QVBoxLayout()
self.label = QLabel("Game not started.")
layout.addWidget(self.label)
self.start_button = QPushButton("Start")
layout.addWidget(self.start_button)
self.clear_button = QPushButton("Clear")
layout.addWidget(self.clear_button)
for button in self.buttons:
layout.addWidget(button)
self.setLayout(layout)
#Callbacks:
self.start_button.clicked.connect(self.start_game)
self.clear_button.clicked.connect(self.clear_game)
self.show()
def start_game(self):
self.label.setText("Game started.")
for button in self.buttons:
button.setCheckable(True)
button.emit_self_toggle = True
button.self_emit_toggle_signal.connect(self.upon_toggle)
def upon_toggle(self, list_button_checked):
button, checked = list_button_checked[0], list_button_checked[1]
if checked:
self.checked_buttons.append(button)
else:
self.checked_buttons.remove(button)
self.update_label()
def update_label(self):
for button in self.buttons:
if button in self.checked_buttons:
index = self.buttons.index(button)
checked_index = self.checked_buttons.index(button)
button.setText(str(checked_index + 1) + " :" + f"Button {index}") #line (1)
else:
index = self.buttons.index(button)
button.setText(f"Button {index}")
def clear_game(self):
self.label.setText("Game not started.")
self.checked_buttons.clear()
for button in self.buttons:
button.emit_self_toggle = False
button.setCheckable(False)
button.setChecked(False)
button.self_emit_toggle_signal.disconnect(self.upon_toggle)
self.update_label()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ButtonWidget()
sys.exit(app.exec_())
It gives the following:
What the above code does, in words, is the following: after the user clicks "Start", the user will be able to click buttons. The order of user click will reflect directly on the labels of buttons. If a button is clicked and the user clicks on it again, then the click is cancelled and the program treats the button as if it was never clicked before. Clicking "Clear" will initialize the program. Programatically, the code saves the list of checked buttons, updates the list upon user click, and rewrites the text on every button accordingly.
Example: if the user clicks "Start", and then in order button 1,2,3,4,5,1, it will show the following:
Questions:
For now, I reflect the order of user click by directly changing the text on the button (see line (1)). Is it possible to modify the above code such that the order is still reflected and the text on the button is unchanged at all? The most preferred outcome would be some colorful number "floating" on the left hand side of the button.
(Optional) Is there a way to avoid using the check state at all? If not, can I at least hide the check state?
Here is a demonstration of what I mean when both 1 and 2 are achieved, with the clicking being exactly the same as the example above:
Note:
The provided code is meant to serve as a demo. Feel free to change the provided code in any way, including completely rewriting it. There is no need to worry about performance. In my actual project the number of buttons will surely be smaller than 15.
Solution
Since you only want to know the order of clicked buttons, there is really no point in using the checked state of the buttons, especially since you don't event want it to be displayed. Just connect to the standard clicked
signal.
The logic is quite simple, indeed; when a button is clicked, then:
- if it is not in the list, just append it;
- if it is in the list, remove it;
If you want to show the number outside of the button, then use a "sibling" label for each of them.
In the following modification of your code, I used an inner grid layout for labels and buttons, which will be added to their respective lists.
Then, to get the button that was clicked and triggered the connected function, just use the sender()
function of all QObjects, which returns the object that emitted the signal.
class ButtonWidget(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Widget with 10 Push Buttons")
# Add buttons to the layout
layout = QVBoxLayout(self)
self.label = QLabel("Game not started.")
layout.addWidget(self.label)
self.start_button = QPushButton("Start")
layout.addWidget(self.start_button)
self.clear_button = QPushButton("Clear")
layout.addWidget(self.clear_button)
self.buttons = []
self.labels = []
self.checked_buttons = []
buttonGrid = QGridLayout()
layout.addLayout(buttonGrid)
for i in range(0, 10):
label = QLabel()
label.setMinimumWidth(20)
buttonGrid.addWidget(label, i, 0)
self.labels.append(label)
button = QPushButton("Button {}".format(i))
button.setEnabled(False)
buttonGrid.addWidget(button, i, 1)
self.buttons.append(button)
button.clicked.connect(self.update_list)
self.start_button.clicked.connect(self.start_game)
self.clear_button.clicked.connect(self.clear_game)
def start_game(self):
self.label.setText("Game started.")
for button in self.buttons:
button.setEnabled(True)
def clear_game(self):
self.label.setText("Game not started.")
for button in self.buttons:
button.setEnabled(False)
self.checked_buttons.clear()
self.update_labels()
def update_list(self):
button = self.sender()
if button in self.checked_buttons:
self.checked_buttons.remove(button)
self.update_labels()
else:
self.checked_buttons.append(button)
index = self.buttons.index(button)
# no need to update all labels, we can get its "sibling" label
# and just use the length of the checked_buttons, since this
# is obviously the last clicked button
self.labels[index].setText(
str(len(self.checked_buttons)))
def update_labels(self):
for label, button in zip(self.labels, self.buttons):
if button in self.checked_buttons:
index = self.checked_buttons.index(button)
label.setText(str(index + 1))
else:
label.clear()
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.