📜 ⬆️ ⬇️

Programming on PyQt4. Part 2

Thanks to good people, the author of this series of articles received an invite and all subsequent articles will be published by him, so do not assign this work to me. I'm just his friend. ;)
image

Part number 2


Last time, you learned about how PyQt4 works and wrote your first application!
Text labels, even with formatting, are good, but I think you will need to write something more complex and most likely requiring user interaction. Qt4 contains many classes that provide this. It also allows you to set the program's reaction to the actions of users, and, by the way, in a very elegant way. But let's move from words to deeds and write a simple program. Last time, it was already said how to launch it, so we will not dwell on this.

 1 #! / Usr / bin / python
 2 from PyQt4 import Qt
 3 import sys
 4 if __name__ == "__main__":
 5 app = Qt.QApplication (sys.argv)
 6 button = Qt.QPushButton ("Exit")
 7 button.resize (200, 70)
 8 button.show ()
 9 app.exec_ ()


Like last time, we performed almost the same actions: we created an app object from the QApplication class, created a widget, set it up, showed it, and started the application to run. Wait, set up? But this was not the last time! Right. If you remember, I said last time that at the beginning all Qt widgets are created invisible to allow you to configure them and then display them. Now it is useful to us. In line 6 we created a button with the text “Exit”, and in line 8 we made it visible. But in line 7 we made its setting - we changed the size. Qt4 widgets have a large variety of settings that allow you to create flexible interfaces. In this case, we used its property - size, changing it to 200x70 pixels.
Perhaps you are pleased with the result, but ... The button says “Exit”, but when we click on it, nothing happens. This is because we have not established the reaction of pressing the button. Let's try to do this by adding one line:
')
 1 #! / Usr / bin / python
 2 from PyQt4 import Qt
 3 import sys
 4 if __name__ == "__main__":
 5 app = Qt.QApplication (sys.argv)
 6 button = Qt.QPushButton ("Exit")
 7 button.resize (200, 70)
 8 Qt.QObject.connect (button, Qt.SIGNAL ("clicked ()"), sys.exit)
 9 button.show ()
 10 app.exec_ ()


We start, and we see that the application has not changed at all. However, try to press the button now ... The application has closed! This is the reaction to the user’s action; it is provided by line 8. It looks a bit unusual, but in fact everything is simple. We called the connect () static function, specifying the object that sends the signal as a parameter, then the signal itself, then the function that must be executed when this signal arrives. What is the signal, you ask? Here perhaps you should make a small digression and talk about the concept of "Signals and Slots."
Qt widgets generate signals for certain events (by the way, not related to UNIX signals). QPushButton, for example, generates a clicked () signal when a button is pressed. Qt allows you to set the response to the signals coming from the widgets and perform the so-called slots - these are the functions that should be triggered when signals arrive.
“Signals and Slots” play a very important role, allowing you to connect different objects that know nothing about each other. To connect signals to the slots, the connect () function of the QObject class is used (in fact, almost all Qt objects are, in one way or another, QObject descendants, therefore, contain its function methods). In classic Qt for C ++, the syntax of the connect () call is as follows:

	 connect (sender, SIGNAL (signal ()), receiver, SLOT (slot ()))


This is slightly different from what we used, but first things first. A “sender” is an object that generated a signal: an event occurred in it. “Signal ()” is the name of the signal that was generated. “Recipient” is an object that must respond to a signal, it owns the function “slot ()”, which is now performed when a signal arrives. In C ++, the SIGNAL () and SLOT () macros are part of the syntax, but Python uses a slightly different call syntax. Since it is always necessary to indicate in Python which object the desired function belongs to when called, the developers created a shorter form for PyQt4 (I recommend using it, but the C ++ form can also be used):

	 connect (sender, SIGNAL ("signal ()"), recipient_slot)


Receiver slot is a function that should respond to a signal. It may not belong to one grade, such as sys.exit (). This is a more flexible syntax. The essence is the same: when a signal arrives, a slot function is performed. In this syntax, you need to describe the slot as follows (if the function is a member of a particular class): the recipient.function without brackets and parameters. However, there are still some possibilities.
The signal can be connected to another signal (using a recording form similar to C ++), in this case, when generating one signal, a second signal will be generated:

	 connect (sender, SIGNAL ("signal1 ()"), receiver, SIGNAL ("signal2 ()"))


One signal can be connected to several slots, and they will be executed in a random order, and several signals to one slot. Finally, the link can be canceled (you can use the short form of the entry for PyQt4):

	 disconnect (sender, SIGNAL ("signal ()"), receiver, SLOT ("slot ()"))


However, the possibilities are not limited to this. Signals can carry certain values ​​of variables, and slots can receive them, for example:

	 Qt.QObject.connect (slider, Qt.SIGNAL ("valueChanged (int)"), spinbox.setValue)


or the same in the form for C ++ (get used to the form for Python, I will use it):

	 Qt.QObject.connect (slider, Qt.SIGNAL ("valueChanged (int)"), spinbox, Qt.SLOT ("setValue (int)"))


As you can see, variables are described by a list of types in brackets. When a signal is generated, it is given all the necessary variables, and the slot can have a parameter, and then it will take the values ​​that the signal carries. Parameters must be set in the same order and have the same type. However, the signal may have more parameters than the slot, then the “extra” parameters are simply ignored. If the parameters are of different types, then a corresponding warning will be issued during the program execution.
Well, now let's try to write something interesting using our knowledge of signals and slots. This will be an application in which you can enter a number from 0 to 100 using either a slider or a dial counter. Moreover, when changing the number in one of the widgets, there will be a corresponding change in the other widget, we implement this property through signals and slots.

 1 #! / Usr / bin / python
 2 from PyQt4 import Qt
 3 import sys
 4 if __name__ == "__main__":
 5 app = Qt.QApplication (sys.argv)
 6
 7 window = Qt.QWidget ()
 8 window.setWindowTitle ("Enter a number")
 9
 10 layout = Qt.QHBoxLayout ()
 11 window.setLayout (layout)
 12
 13 slider = Qt.QSlider (Qt.Qt.Horizontal)
 14 slider.setRange (0, 100)
 15 layout.addWidget (slider)
 sixteen
 17 spinbox = Qt.QSpinBox ()
 18 spinbox.setRange (0, 100)
 19 layout.addWidget (spinbox)
 20
 21 Qt.QObject.connect (slider, Qt.SIGNAL ("valueChanged (int)"), spinbox.setValue)
 22 Qt.QObject.connect (spinbox, Qt.SIGNAL ("valueChanged (int)"), slider.setValue)
 23
 24 slider.setValue (50)
 25
 26 window.show ()
 27
 28 app.exec_ ()


Our application has become more. Let's see.
In lines 7-8, a window widget is created and configured. I already mentioned that any widget can be a window, and here I used the abstract class QWidget (which all Qt widgets inherit, by the way). In the 8th line the window title is set.
In lines 10-11, something new appeared. This is the layout manager class. Qt4 has three main classes responsible for the layout of widgets:
QHBoxLayout - groups widgets horizontally;
QVBoxLayout - groups widgets vertically;
QGridLayout - groups widgets into grid cells.
With the help of these managers, you can create any kind of visual combinations of widgets: you can not only place widgets in Layouts, but also other Layouts.
In this case, we used the horizontal layout manager, and set it as the general manager (window.setLayout ()) for our window. Afterwards, we can, after creating the widgets, place them inside the manager, and he will distribute them and give them the optimal sizes. In line 13-14, a horizontal slider is created and configured (a range of acceptable values ​​is set), and in line 15 it is placed inside the layout manager. Lines 17-19 are similar.
Lines 20-21 connect two widgets together. Both of them have “valueChanged (int)” signals that are generated when the user changes the value, and “setValue (int)” slots, which set a new value. The signal to change the value in one widget is connected to the slot of another widget, passing a new value.
In line 24, the value for slider is set to 50, then line 26 makes the window visible together with all the widgets contained in it, and line 28 “launches” our application.
It is necessary to describe in detail what happens when line 24 is executed. The value of the slider object changes, and it generates a “valueChanged (int)” signal, which also carries a new value. The signal arrives at the spinbox object and it also sets itself the same value using the slot-function “setValue (int)”, which takes one argument - a new value. Then, since the spinbox value has changed, it also generates the corresponding signal, which comes to the slider object. But the slider already contains the value that the spinbox sent (after all, the slider was the first to report the new value), so it doesn’t react to the new signal by simply passing it. This does not allow the program to go into an infinite loop (in your widgets, if you create such signals and slots, you should provide such situations and create protection against them). Line 24 could be replaced with “spinbox.setValue (50)”, this would lead to exactly the same result, both widgets would have the same value, but the spinbox would be the first to report the change in value. The same thing happens when users influence these widgets: the same signals are generated and everything happens according to the same algorithm.
As you can see, user interaction and customization of the appearance of the program are easily provided by Qt tools. A common approach looks like this: create a window, create layout managers and place them, then create widgets and immediately set them up and place them inside the layout manager, create the necessary signal and slot connections, give a default value for those widgets that affect others value (as line 28). Then the window can be made visible or left until better times, if there are many windows in your program, you can create all of them in advance and open them as needed. This approach is considered the most appropriate when working with Qt.
image
That's all for now, wait for part number 3, thank you for your attention!

Source: https://habr.com/ru/post/44712/


All Articles