Programming in 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 am just his friend. ;)
    image

    Part number 2


         Last time you found out how PyQt4 works and wrote your first application!
    Text labels, even with formatting, are good, but I think you 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 reaction of the program to actions on the part of users, and, by the way, in a very elegant way. But let's move on from words to deeds and write a simple program. Last time it was said how to run 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: created an app object from the QApplication class, created a widget, configured it, showed it and launched the application. Wait, set up? But last time it wasn’t! 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 adjustment - changed the size. Qt4 widgets have a wide variety of settings, which allows you to create flexible interfaces. In this case, we used its property - size, changing it to 200x70 pixels.
         You may be happy with the result, but ... The button says “Exit”, but when we click on it, nothing happens. This is because we did not set the button click response. 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 launch, and we see that the application has not changed at all. However, try now to click on the button ... The application has closed! Here is the reaction to the user’s action, it is provided by line 8. It looks a little unusual, but in fact everything is simple. We called the static function connect (), indicating to it as a parameter the object that sends the signal, then the signal itself, then the function that should be executed when this signal arrives. What is a signal, you ask? Here perhaps we 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 in any way). QPushButton, for example, generates a clicked () signal when a button is clicked. Qt allows you to set the reaction to signals received from widgets and perform so-called slots - these are functions that should be called when signals arrive.
         “Signals and Slots” play a very important role, allowing you to connect different objects that do not know anything about each other. To connect signals to slots, the connect () function of the QObject class is used (in fact, almost all Qt objects are somehow descendants of QObject, therefore, contain its function methods). In classic Qt for C ++, the syntax for calling connect () 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 has been generated. “Recipient”, this is an object that must respond to the signal, it has the function “slot ()”, which is now executed when the 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 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 ()"), receiver_slot)
    


         The receiver slot is a function that should respond to a signal. It may not belong to any class, such as sys.exit (). This is more flexible syntax. The essence is the same: when a signal arrives, the slot function is executed. In this syntax, you need to describe the slot as follows (if the function is a member of a certain class): receiver. 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, the 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 random order, and several signals to one slot. Finally, the connection can be canceled (you can use the short form 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 take them, for example:

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


         or the same in a 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, the 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 may have a parameter, and then it will take the values ​​that the signal carries. Parameters must be specified in the same order and have the same type. However, a signal can have more parameters than a slot, then the "extra" parameters are simply ignored. If the parameters are of different types, then a corresponding warning will be issued during program execution.
         Well, let's now 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 the number changes in one of the widgets, a corresponding change will occur in the other widget, we implement this property just 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 figure it out.
         In lines 7-8, a widget window is created and configured. I already mentioned that any widget can be a window, and here I used the abstract QWidget class (which all Qt widgets inherit, by the way). Line 8 sets the window title.
         On lines 10-11, something new has appeared. This is the layout manager class. In Qt4 there are three main classes responsible for widget layout:
         QHBoxLayout - groups widgets horizontally;
         QVBoxLayout - groups widgets vertically;
         QGridLayout - groups widgets by grid cells.
         Using these managers, you can create any kind of visual combination 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 main manager (window.setLayout ()) for our window. Later, after creating the widgets, we can place them inside the manager, and he will distribute them and give them optimal sizes. In line 13-14, a horizontal slider is created and configured (the 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 to each other. Both of them have signals “valueChanged (int)”, generated when the user changes the value, and slots “setValue (int)”, which set a new value. The value change signal in one widget is connected to the slot of another widget, transmitting a new value.
         In line 24, the slider is set to 50, then line 26 makes the window visible along with all the widgets contained in it, and line 28 “launches” our application.
         It is worth describing in detail what happens when line 24 is executed. The value of the slider object changes, and it generates a signal “valueChanged (int)”, which also carries a new value. The signal comes to the spinbox object and it also sets itself the same value using the setValue (int) slot function, which takes one argument - a new value. Then, since the spinbox value has changed, it also generates the corresponding signal that comes to the slider object. But slider already contains the value that the spinbox sent (after all, slider was the first to report the new value), so it does not react to the new signal in any way, just skipping it. This does not allow the program to go into an endless cycle (in your widgets, if you create such signals and slots, you should foresee 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 spinbox would be the first to report a change in value. The same thing happens when the user interacts with these widgets: in this case, 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. The general approach is as follows: create a window, create layout managers and place them, then create widgets and immediately configure them and place them inside the layout manager, create the necessary signal and slot connections, give a default value for those widgets that affect others through this value (as line 28). Then the window can be made visible or left until better times, if your program has many windows, you can create all of them in advance, and open as needed. This approach is considered the most correct when working with Qt.
    image
    That's all for now, wait for part 3, thank you for your attention!


    Also popular now: