Normen_Zocch | 2020-08-18 20:24:22 UTC | #1
Hello everybody, after studying the tutorials and the book (really helpful) I'm stuck how do i return something back to the widget. The demos print a lot on the console. Well thats easy. What i want is when i push a button, the outcome of the function behind the pushed button should display its values in the widget or qlabel. What i have so far: a class A and functions that generates random values. a class B and functions where i keep the button1_clicked function which calls the functions from the first class a main class C with an init function and a InitUI functions in which all the widgets are created and displayed. So what i want is, click on button1 from the main class C, it calls the button_clicked function in class B which calls the random functions in class A and returns back to the widget/qlabel InitUi in class C.
Is that the way how it is done in Pyqt? How do i get my values back in the widget (not console) and how would i properly structurre such a program in terms of classes and functions? Any help and insight is highly appreciated! Thanks very much, best.
martin | 2020-08-20 19:12:14 UTC | #2
Hi @Normen_Zocch welcome to the forum!
To be able to get values into your widgets, you need to have kept a reference to the widget object somewhere accessible. If you have a class, and define your widgets in that class, storing them on that class object you can then access them from any method using self
, e.g. self.my_label.setText(...)
In your example where you have 3 separate classes you have two options --
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.
- Make sure your classes have a reference to the classes whos' widgets they are updating. For example, if you want to update class C from class A, class A would need to have a reference to the class C object. This works for small examples, but loses you the isolation of your classes (see below).
- Define custom signals to pass this data around. As well as Qt's built-in signals, you can also define your own signals and use them to hook parts of your application together. So for example, you could define a custom signal on class A and connect it directly to the widget on class C you want to update.
If you have some example code I can show how to achieve it with signals.
Lastly, just be careful you're not introducing classes for their own sake, where they don't give any benefits -- it's a judgement call, and depends on the app. The random number generators might be better suited to a submodule for example (do you need the instance?)
If you do need a class for handling state, "a random number generator" sounds more like a "service" which should be to be used by class B, rather than interacting with class C directly. That is B should request a number from A, then pass it to C. A number generator shouldn't need to know about the UI (if that makes sense?) Signals give you this isolation, but you can also achieve it in other ways.
Normen_Zocch | 2020-09-07 11:46:47 UTC | #3
Hello Martin, thanks for this. This is really helpful. I have to mention, that im - just - learning Python and rerad about the concept of classes and functions. These three classes are my first dive into OOP. So i got the principle but im not there yet to play around in freestyle mode. To have a Project wich interests me and helps me learning Python, im writing a NPC Generator for a popular Pen & Paper Roleplay game. While studying the Internet for help, i now dropped Class B and put B and Mainclass C together. I got several functions in my NPCGen Class. That is class "A" where the real data is generated. Example Class A
class NPCGen:
def generate_name(self):
with open('swnames.csv') as f:
swnames_reader = csv.reader(f)
swnames = random.choice(list(swnames_reader))
global swname
firstname = (swnames[0])
surename = (swnames[1])
fullname = (firstname + " " + surename )
return fullname
Then i created an Object
# Creating NPCGen Class Object
npcbuild = NPCGen()
Now in Class C, i create the Widgets:
def initUI(self, birth):
widget = QWidget()
layout = QGridLayout()
self.setWindowTitle("That famous Space Opera D6 NPC Generator") # self.title
self.setGeometry(self.left, self.top, self.width, self.height)
# Generate Button to initiate new random values
self.button1 = QPushButton('Generate', self)
self.button1.move(10,20)
self.button1.clicked.connect(self.button1_clicked)
# Displaying Labels
self.npcswname = QLabel(self)
self.swname = npcbuild.generate_name()
self.npcswname.update()
self.npcswname.setFixedWidth(100)
self.npcswname.setText(self.swname)
self.npcswname.move(260,80)
self.npcswname.adjustSize()
```
So im not there yet to understand how a reference should work correctly, but here I have 15 different labels , then I activate them via:
```python
layout.addWidget(self.npcswname)
Now comes the interesting part, in here in my main class in have my button1_clicked function where i update all the values like:
def button1_clicked(self):
# Update Random Values
# Char Name
self.swname = npcbuild.generate_name() # <- i call here again a new random name
self.npcswname.setText(self.swname)
So this scenario works, but it looks "ugly" coz i would rather separate it and have more readable beautiful code. So here i would be interested how custom signals could do the job more professionally. In the end all my 15-20 QLabels should be upgraded by this, when i click on the Generate Button. I didn't choose the QMainWindow coz i did not fully comprehend where and what and why all the widgets should go. But i could imagine, this owuld be the way to go towards a "proper" PyQT Application. So, thanks vor any Wisdom you can share :slight_smile: Normen
martin | 2020-09-07 12:07:24 UTC | #4
Hi @Normen_Zocch ...sorry for the delay in replying this time, I just had a baby :)
[quote="Normen_Zocch, post:3, topic:426"]
def initUI(self, birth):
widget = QWidget()
layout = QGridLayout()
self.setWindowTitle("That famous Space Opera D6 NPC Generator") # self.title
self.setGeometry(self.left, self.top, self.width, self.height)
# Generate Button to initiate new random values
self.button1 = QPushButton('Generate', self)
self.button1.move(10,20)
self.button1.clicked.connect(self.button1_clicked)
# Displaying Labels
self.npcswname = QLabel(self)
self.swname = npcbuild.generate_name()
self.npcswname.update()
self.npcswname.setFixedWidth(100)
self.npcswname.setText(self.swname)
self.npcswname.move(260,80)
self.npcswname.adjustSize()
[/quote]
So there are a couple of things you can do here to make things simpler for yourself.
- Use Qt layouts, rather than positioning the widgets using width, move + adjustSize. Then you can just plop them into your layout.
- Storing the reference to the widgets on
self
is fine, but as you notice when you have multiple widgets it can get a bit out of hand.
So, first for the layouts. Since we just have a list of QLabels
to display, we can stack them up using a QVBoxLayout
(vertical box layout), e.g.
def initUI(self, birth):
# ... your other code
player_layout = QVBoxLayout()
self.npcswname = QLabel(self)
player_layout.addWidget(self.npcswname)
# etc.
# Set the layout on the window.
self.setLayout(player_layout)
If you have another layout (e.g. the grid) you can add one layout to another to nest them.
Don't worry about using QMainWindow
, this is only necessary if you want access to toolbars, etc. in your application.
For updating the widgets with the new values, you can do a few things. One option is to store your player stats in a dictionary and store a dictionary that maps from each state name to a widget, you can iterate and copy the values over.
def initUI(self, birth):
# ... your other code
player_layout = QVBoxLayout()
npcswname = QLabel(self)
player_layout.addWidget(npcswname)
# etc.
stat_widgets = {
'name': npcswname,
'hp': hitpoints
}
# Set the layout on the window.
self.setLayout(player_layout)
Here we've stored references to the npcswname
widget in a dictionary, another named hitpoints
. The dictionary maps from the statistic name, to the widget which displays that statistic.
If we store our statistics in a similar dictionary, e.g.
player_stats = {
'name': 'bilbo',
'hitpoints': 45
}
...we can then iterate the statistics, and update the widgets, with something like ...
for k, v in player_stats.items():
widget = self.stat_widgets[k]
widget.setText(str(v))
````
As we iterate `player_stats`, `k` will get the key (the statistic name) and `v` will get the value of that statistic. We look up the k (statistic name) n the `stat_widgets` dictionary, to get the widget, and then call `setText` on that widget. We wrap it in `str` since `hitpoints` is a number, and `QLabel.setText` only accepts strings.
Note that using a dictionary like this doesn't have to complicate your creation. If you have a method that returns a random name, you can populate the dictionary directly like follows.
```python
player_stats = {
'name': npcbuild.generate_name()
'hitpoints': npcbuild.generate_hitpoints()
}
... the resulting player_stats
dictionary will contain the generated values.
If that's all a bit much, you can keep the pattern of storing data in a dictionary + just assign to the widgets in turn. Still, I would create a separate update method to keep it nice and tidy.
def update(self, player_stats):
self.npcswname.setText(player_stats['name'])
self.npcswhitpoints.setText(player_stats['hitpoints'])
Having your statistics in a dictionary (or any other structure) will make it a lot nicer when you want to do anything else with the values.