-
Notifications
You must be signed in to change notification settings - Fork 2
/
kapitel_02.tex
3117 lines (2673 loc) · 146 KB
/
kapitel_02.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\ospchapter{Einführung in die Programmierung mit PyQt und PySide}{Einführung in die Programmierung mit PyQt\osplinebreak{} und PySide}{chap:einfuehrung}
\ospsection{Vorbereitungen}{Vorbereitungen}{sec:vorbereitungen}
Mit PyQt steht dem Entwickler ein ausgereiftes GUI-Framework unter
Python zur Verfügung. Um in einer komfortablen Umgebung mit der
Entwicklungsarbeit beginnen zu können, muss man zunächst die nötigen
Pakete zum Editieren und Ausführen des Skript-Codes auf seinem Rechner
einrichten. Python und Qt sind grundsätzlich plattformunabhängig, so
dass sich eine Reihe von Betriebssystemen für die Entwicklung und als
Zielsystem für PyQt-Anwendungen eignen. Es soll hier hauptsächlich auf
Windows und Linux eingegangen werden, außerdem wird gegebenenfalls auf
Besonderheiten unter Mac OS eingegangen. Zudem lassen sich
PyQt-Anwendung beispielsweise aber mittlerweile auch auf
Mobiltelefonen ausführen (und entwickeln!), seitdem Nokia das
Qt-Framework als Entwicklungsplattform und GUI-Framework für seine
Betriebssysteme Symbian und Maemo bzw. MeeGo eingekauft hat. Es gibt
mit dem Nokia N900 und dem N9 immer noch zwei aktuelle Smartphones,
auf denen Sie Ihre Python-Anwendungen laufen lassen können, auch wenn
Nokia mittlerweile voll auf Windows Phone 7 setzt. Zu den
Zielplattformen und der Paketerstellung aber später in Kapitel
\ref{chap:projektsstruktur} mehr. Zunächst wollen wir die
Entwicklungsumgebung auf dem Entwicklerrechner einrichten.
\ospsubsection{Entwicklungsumgebung}{Entwicklungsumgebung}{sec:entwicklungsumgebung}
Wer schon Skripte und Anwendungen mit Python entwickelt hat, der kann
für die PyQt-Programmierung getrost weiter auf seine gewohnte Umgebung
mit Editor, IDE usw. vertrauen. Die Arbeit mit PyQt unterscheidet sich
nicht grundsätzlich von derjenigen mit anderen Python-Modulen und
-Frameworks.
\index{Editor}%
Wer jedoch mit Python bisher nicht viel zu tun hatte, kann
grundsätzlich alle Beispiele dieses Buches mit einem einfachen
(Code-)Editor ausprobieren. Viele Editoren unterstützen mindestens
Syntax-Highlighting für Python, einige weitere außerdem Dinge wie
Code-Vervollständigung, Anzeige von erwarteten Parametern bei der
Eingabe von Funktionsnamen, Aufruf des Interpreters per Hot-Key usw.
Als recht vernünftige Basis hat sich für den Autor der Komodo-Editor
von
ActiveState\ospfootnote{fn:komodoedit}{\ospurl{http://www.activestate.com/komodo-edit}}
bewährt: Er ist frei verfügbar, plattformunabhängig und unterstützt
neben Python auch andere Programmiersprachen. In einem
fortgeschrittenen Stadium kann man dann auf die Komodo IDE umsteigen,
die Active State mit kommerzieller Lizenz für professionelle
Entwickler anbietet.
\index{IDE}%
\index{KDevelop}%
\index{Eric}%
\index{PyDev}%
\index{PyCharm}%
Aber auch für eine komplette IDE gibt es freie Alternativen: Unter
Linux unterstützt
KDevelop\ospfootnote{fn:kdevelop}{\ospurl{http://www.kdevelop.org}}
PyQt- und PySide-Projekte; mit
Eric\ospfootnote{fn:eric}{\ospurl{http://eric-ide.python-projects.org}}
existiert außerdem eine selbst auf PyQt basierende, ausgewachsene IDE
mit allem, was das PyQt-Entwickler-Herz begehrt. Wenn man größere
Projekte mit PyQt plant, ist diese wohl das Mittel der Wahl. Es lohnt
sich dann auch, mit Eric zu starten, da man anhand der Beispiele in
diesem Buch auch gleich die Funktionen der IDE ausprobieren kann. Auf
der Eric-Webseite finden Sie eine Reihe von Tutorials, die Ihnen einen
einfachen Einstieg ermöglichen. Eclipse-Anwendern steht mit
PyDev\ospfootnote{fn:pydev}{\ospurl{http://pydev.org/}} ein
umfangreiches Plugin zur Python-Entwicklung zur Verfügung. Letztlich
ist auch
PyCharm\ospfootnote{fn:pycharm}{\ospurl{http://www.jetbrains.com/pycharm/} --
Open-Source-Projekte können sich um eine kostenlose Lizenz
bewerben.} eine bei Python-Entwicklern beliebte,
plattformunabhängige (aber kostenpflichtige) IDE, die sich
hervorragend für die Entwicklung mit PyQt und PySide eignet.
\ospsubsection{Python und PyQt bzw. PySide installieren}{Python und PyQt bzw. PySide installieren}{sec:installieren}
\index{Installation}%
\index{Python-Versionen}%
Neben einem Editor müssen auf dem Entwicklersystem natürlich
Python\ospfootnote{fn:python}{\ospurl{http://www.python.org}} sowie
PyQt\ospfootnote{fn:pyqt}{\ospurl{http://www.riverbankcomputing.co.uk/software/pyqt}}
bzw. PySide\ospfootnote{fn:pyside}{\ospurl{http://www.pyside.org}}
installiert werden. Sowohl PyQt als auch PySide liegen in einer
Version für Python 2 als auch Python 3 vor. Die Python-3-Version wird
im Allgemeinen dann empfohlen, wenn man von vornherein weiß, dass man
keine Python-Module verwenden möchte, die Python 2 voraussetzen.
Leider ist es jedoch immer noch so, dass nicht alle Module in einer
Python-3-Version existieren, so dass man mit einer
Python-2-Installation häufig besser fährt. Ich setze auf meinem
Entwicklungsrechner für die meisten Zwecke Python 2.7 ein. Falls man
außer PyQt auch andere Module verwenden möchte und unter Windows
arbeitet, dann findet man für diese Version häufig auch vorkompilierte
Versionen. Andernfalls müsste man solche Zusatzmodule mit einem
C++-Compiler kompilieren, wobei der Einrichtungsaufwand nicht
unerheblich ist. Ich empfehle also Python-Neulingen, Python 2.7 sowie
die passende PyQt-Installationsdatei zu verwenden. Der Quellcode
dieses Buches wurde immer mit Python 2 entwickelt, in den seltensten
Fällen sollte es jedoch einen größeren Portierungsaufwand für Python 3
geben. Die Syntax der Beispiele benutzt keine besonderen
Konstruktionen.
\index{PyQt vs. PySide}%
Ob man nun zunächst auf PyQt oder PySide setzt, ist unerheblich.
PySide hat einerseits den Vorteil, dass es in einer LGPL-Version
angeboten wird, so dass sich damit auch kommerzielle Anwendungen
entwickeln lassen, ohne dass der Code dafür veröffentlicht werden
muss. Wenn Sie PyQt verwenden, müssten Sie in diesem Fall eine
kommerzielle Lizenz vom Anbieter Riverbank Computing Limited erwerben.
Andererseits ist PyQt ein etabliertes Produkt, das seit Jahren
gepflegt wird. PySide wurde demgegenüber erst nach der Übernahme von
Trolltech und damit dem Kauf von Qt durch Nokia aus der Taufe gehoben
und wird derzeit allein von Nokia finanziert. Wie sich der
Strategiewechsel auf die weitere PySide-Entwicklung auswirkt, ist im
Moment kaum abzusehen. Eine Gruppe am Instituto Nokia de Tecnologia
in Brasilien, wo bislang die Entwicklung maßgeblich stattfand, wird
zunächst das Projekts fortführen. Zudem gibt es eine nicht
unerhebliche Anzahl von Entwicklern in der Community rund um das
Projekt. Verlässlicher ist im Moment sicher die weitere Entwicklung
bei PyQt. Dennoch: Die Konvertierung einer PyQt- in eine
PySide-Anwendung beschränkt sich meist auf das Ändern einer
\ospcmd{import}-Anweisung im Header des Quelltextes. Es gibt aus
Entwicklersicht einige kleinere Unterschiede zwischen PyQt und PySide,
die aber gerade beim Einstieg kaum ins Gewicht fallen. Sie können sich
also für eine Variante entscheiden oder einfach beide installieren.
Die Module koexistieren dann friedlich in einer Python-Installation,
und Sie können so jederzeit von einem auf das andere Modul wechseln.
Der Quelltext der Beispiele bezieht sich in diesem Buch immer zuerst
auf PyQt. Würde man bestimmte Stellen in PySide anders programmieren,
wird im Text immer darauf eingegangen. Über den Index finden Sie alle
diese Fälle unter dem Eintrag \dqo{}PyQt vs. PySide\dqc{}. Meist sind
die Anpassungsarbeiten aber marginal.
\index{Shiboken}%
Intern ist der Unterschied zwischen PyQt und PySide allerdings
gewaltig: Nokia hat eine eigene Bibliothek namens \emph{Shiboken} zur
Erzeugung von Python"=Modulen aus Qt-basierten APIs entwickeln lassen.
Ziel war es einerseits, die API gegenüber PyQt schneller zu machen,
andererseits ein eigenes Verfahren zur Erstellung von Python-Modulen
aus Qt-Bibliotheken anbieten zu können. Entwickler von Bibliotheken
rund um Qt sollten so die Möglichkeit bekommen, mit Python eine
alternative Programmiersprache unterstützen zu können. Aber auch hier
ist nach Nokias Strategiewechsel kaum abzusehen, wie sich Shiboken in
Zukunft entwickeln wird. Nach den letzten Benchmarks des
PySide-Teams ist PySide aber zumindest nicht langsamer als PyQt; mit
Erscheinen dieses Buches haben sich die Entwickler eventuell sogar
schon einen Vorsprung erarbeitet.
\ospsection{Eine erste Anwendung}{Eine erste Anwendung}{sec:erstesprojekt}
\index{Hauptfenster}%
\index{Anwendung}%
\index{Programmgerüst|see{Anwendung}}%
\index{Programmtemplate|see{Anwendung}}%
\index{Projekttemplate|see{Anwendung}}%
\index{Hallo Welt}%
Nachdem nun alle Vorbereitungen getroffen wurden, können wir also mit
der ersten PyQt-Anwendung beginnen. Für den Sprung ins kalte Wasser
erzeugen wir gleich einmal eine vollständige Anwendung inklusive
Hauptfenster. Hier der Quelltext, den wir dann Zeile für Zeile
betrachten werden:
\index{QApplication}%
\index{QApplication!exec\_()}%
\index{QMainWindow}%
\index{QMainWindow!show()}%
\begin{osplisting}{Python}{Erste Hallo-Welt-Anwendung}{code:hallowelt}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
def main(argv):
app = QtGui.QApplication(argv)
mainwindow = QtGui.QMainWindow()
mainwindow.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
\end{osplisting}
Speichern Sie diesen Quelltext in einer Datei
\ospfile{hallowelt.py} und starten Sie das Skript dann in einer
Kommandozeile mit dem Python-Interpreter, indem Sie den Befehl
\ospcmd{python hallowelt.py} in eine Kommandozeile eingeben. Unter
Windows müssen Sie möglicherweise den kompletten Pfad zum Interpreter
angeben, etwa \ospcmd{c:\bsl{}Python27\bsl{}python.exe hallowelt.py}.
So sollten Sie schon Ihr erstes PyQt-Hauptfenster vor sich haben! Nun
aber zu den einzelnen Schritten seiner Erzeugung.
\index{QtGui}%
\index{QtCore}%
Zunächst erfolgt der Import der beiden PyQt-Module \ospcmd{QtCore} und
\ospcmd{QtGui}. Die Trennung dieser Module findet sich (weniger
explitit) auch im Basis-C++-Framework: Grob gesagt, enthält
\ospcmd{QtCore} alle nicht sichtbaren Qt"=Komponenten, \ospcmd{QtGui}
eben die GUI-Klassen und -Komponenten. Haben Sie PySide auf Ihrem
Rechner installiert, ersetzen Sie die \ospcmd{import}-Zeile einfach
folgendermaßen:
\index{PySide}
\begin{ospsimplelisting}
from PySide import QtCore, QtGui
\end{ospsimplelisting}
Schon läuft das Skript mit dem installierten PySide-Modul. Oft
beschränkt sich der Wechsel von PyQt zu PySide oder umgekehrt auf
genau diese Anpassung.
Danach werden in \ospcmd{main} ein Objekt aus der Klasse
\ospcmd{QApplication} sowie das Hauptfenster aus \ospcmd{QMainWindow}
erstellt. Das Hauptfenster ist eines der sogenannten Qt-Widgets, die
uns als GUI-Elemente noch häufiger begegnen werden. Mit der Methode
\ospcmd{show()} wird das Hauptfenster auf dem Bildschirm angezeigt.
Zuletzt wird die Event-Loop der Anwendung mit der Methode
\ospcmd{exec\_()} gestartet. Die Anwendung nimmt nun alle
Benutzereingaben entgegen und gibt sie an die entsprechenden Elemente
innerhalb des Hauptfensters weiter. Die Event-Loop wird beendet, wenn
das Hauptfenster geschlossen wird. Übrigens heißt die Original-Methode
für den Einstieg in die Event-Loop unter C++ \ospcmd{exec()} (also
ohne Unterstrich). Da es sich bei \ospcmd{exec()} unter Python aber um
einen reservierten Ausdruck handelt, haben die PyQt-Entwickler einfach
einen Unterstrich an die Methode angefügt -- nur für den Fall, dass Sie
sich einmal in der C++Dokumentation des Qt-Frameworks umschauen und
dort kein \ospcmd{exec\_()} finden.
Nun benötigt das Hauptfenster erste Inhalte. Dafür erstellt man
zunächst eine eigene Klasse \ospcmd{MainWindow}, die man aus der
Basisklasse \ospcmd{QMainWindow} ableitet. Im Konstruktor der Klasse
erzeugt man dann die gewünschten GUI-Elemente, in unserem Beispiel ein
Label, einen Button und ein Eingabefeld. Sie können alles Weitere in
die Datei \ospcmd{hallowelt.py}
schreiben.\ospfootnote{fn:einedatei}{Freundlicherweise erlaubt Python
ja die Definition mehrerer Klasssen und Funktionen in einer
Quelltextdatei. Wenn die Anwendung größer wird, lassen sich
diese Klassen gut in separate Module auslagern.} Die Klasse
muss nur vor dem Aufruf von \ospcmd{main()} deklariert werden, also
fügen Sie folgende Zeilen vor der \ospcmd{if}-Anweisung hinzu:
\index{QMainWindow}%
\index{QMainWindow!setWindowTitle()}%
\index{QLabel}%
\index{QPushButton}%
\index{QLineEdit}%
\begin{osplisting}{Python}{Erste Hauptfenster-Klasse}{code:ersteshautpfenster}
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args):
QtGui.QMainWindow.__init__(self, *args)
self.labelHalloWelt = QtGui.QLabel(self.tr("Hello World!"))
self.buttonTextAktualisieren =
QtGui.QPushButton(self.tr("Update"))
self.editText = QtGui.QLineEdit()
self.setWindowTitle(self.tr("Hello World"))
\end{osplisting}
\index{Lokalisierung}%
\index{Unicode}%
\index{Uebersetzung@Übersetzung|see{Lokalisierung}}%
\index{Lokalisierung!tr()-Methode}%
Der Konstruktor ruft den Konstruktor der Basisklasse
\ospcmd{QMainWindow} auf und erzeugt drei GUI-Widgets. Gespeichert
werden diese Objekte als Attribute in \ospcmd{self}, so dass wir in
anderen Methoden darauf zugreifen können. Dies werden wir bald nutzen,
um auf Benutzeraktivitäten und andere Ereignisse mit den GUI-Elementen
zu reagieren. In der letzten Zeile des Kontruktors wird noch per
\ospcmd{setWindowTitle()} der Fenstertitel gesetzt; dieser erscheint
in der Titelleiste des Hauptfensters. Der String des Fenstertitels
sowie alle anderen Strings im Beispiel für die Beschriftung der
GUI-Elemente sind in einen Aufruf der Methode \ospcmd{tr()}
eingebettet. \ospcmd{tr()} steht für \dqo{}translate\dqc{}, was
bedeutet, dass das Qt-Framework an dieser Stelle nach Übersetzungen
für die aktuelle Sprache des Betriebssystems sucht. Wir werden in
diesem Kapitel noch darauf zurückkommen; Kapitel
\ref{sec:lokalisierung} widmet sich dem Thema ausführlich. An dieser
Stelle sei aber bereits festgestellt, dass die Funktion \ospcmd{tr()}
keine Unicode-Zeichenketten entgegennimmt. Darum verwenden wir an
dieser Stelle und in allen weiteren Beispielen dieses Kapitels
englische Begriffe. Deutsche Sonderzeichen wie Umlaute lassen sich
leider eben nicht als Unicode an \ospcmd{tr()} übergeben, so dass man
auf Englisch als Default-Sprache angewiesen ist. Kapitel
\ref{sec:lokalisierung} zeigt, wie man Übersetzungen für alle
GUI-Strings in die Anwendung integriert, so dass die Oberfläche immer
in der Sprache des Betriebssystems erscheint, soweit die Strings
in dieser Sprache vorliegen.
Falls Sie von vornherein sicher sind, keine übersetzte Version Ihrer
Soft\-ware zu benötigen, können Sie selbstverständlich die Aufrufe von
\ospcmd{tr()} weglassen. In diesem Fall müssen Sie Unicode-Strings an
die Konstruktoren bzw. Methoden übergeben. Der Code unserer ersten
Beispielanwendung \ospcoderef{code:ersteshautpfenster} sieht dann
folgendermaßen aus:
\begin{osplisting}{Python}{Erste Hauptfenster-Klasse ohne Internationalisierung}{code:ersteshautpfensterohneint}
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args):
QtGui.QMainWindow.__init__(self, *args)
self.labelHalloWelt = QtGui.QLabel(u"Hallo Welt!")
self.buttonTextAktualisieren =
QtGui.QPushButton(u"Aktualisieren")
self.editText = QtGui.QLineEdit()
self.setWindowTitle(u"Hallo Welt")
\end{osplisting}
Im Weiteren werden wir jedoch sämtliche Beispiele für die
spätere Übersetzung vorbereiten.
Sie müssen in der \ospcmd{main()}-Funktion des Hauptprogramms nun natürlich ein Objekt der abgleiteten Klasse \ospcmd{MainWindow} anstatt aus \ospcmd{QMainWindow} erstellen; die entsprechende Zeile lautet dann:
\begin{ospsimplelisting}
mainwindow = MainWindow()
\end{ospsimplelisting}
\index{QWidget}%
\index{Layout}%
\index{Layout!addWidget()-Methode}%
\index{QVBoxLayout}%
\index{QVBoxLayout!addWidget()}%
\index{Zentrales Widget}%
Wenn Sie die Anwendung nun starten, ist aber immer noch nichts auf dem
Bildschirm zu sehen. Dazu muss man noch zwei wichtige Objekte in der
Qt-Welt definieren, nämlich ein zentrales Widget (Klasse
\ospcmd{QWidget}), dem wir ein vertikales Layout (Klasse
\ospcmd{QVBoxLayout}) zuweisen. Dem Layout übergibt man dann die
einzelnen GUI-Elemente per \ospcmd{addWidget()}, in unserem Fall
werden diese dann vertikal angeordnet. Dazu fügen Sie dem
Konstruktor folgende Zeilen hinzu:
\index{QMainWindow!setCentralWidget()}%
\begin{osplisting}{Python}{Erstes Layout}{code:ersteslayout}
widgetZentral = QtGui.QWidget()
layoutZentral = QtGui.QVBoxLayout()
layoutZentral.addWidget(self.labelHalloWelt)
layoutZentral.addWidget(self.editText)
layoutZentral.addWidget(self.buttonTextAktualisieren)
widgetZentral.setLayout(layoutZentral)
self.setCentralWidget(widgetZentral)
\end{osplisting}
\index{QObject}%
\index{Klassenhierarchie}%
Die letzte Zeile weist das zentrale Widget schließlich dem
Hauptfenster zu. Nun lässt sich die Anwendung starten, und die drei
Elemente sind auf dem Bildschirm zu sehen. Damit haben Sie schon die
wichtigsten Elemente einer Qt-Anwendung kennengelernt: das
Hauptfenster, das ein zentrales Widget enthält; diesem wiederum weist
man ein Layout zu.\ospfootnote{fn:layout}{Man wird wohl häufig mehrere
Arten von Layouts ineinander verschachteln, um eine Oberfläche mit
mehreren Elemente zu versehen; dazu mehr in Abschnitt
\ref{sec:layouts}.} Die einzelnen GUI-Elemente wiederum werden dem
Layout übergeben, das sich selbstständig um Anordnung und Darstellung
kümmert. Alle GUI-Elemente, also Widgets, sind grundsätzlich von der
Klasse \ospcmd{QWidget} abgeleitet. Diese wiederum hat als Basisklasse
\ospcmd{QObject}, die die Python-Basisklasse
\ospcmd{object} um einige nützliche Funktionen erweitert. Unter C++
erfüllt diese Hierarchie u.a. eine wichtige Funktion in der
Speicherverwaltung, aber auch unter Python werden wir nützliche
Funktionen dieser Klassen kennenlernen. Mehr zur Klassenhierarchie in
Qt behandelt Kapitel \ref{sec:klassenhierarchie}.
\index{Garbage Collection}%
\index{Objekthierarchie}%
Beachten Sie, dass hier das Layout und das zentrale Widget nur als
lokale Variablen definiert wurden. Das heißt, dass diese Objekte im
Normalfall durch den Python-Interpreter und seine \dqo{}Garbage
Collection\dqc{} gelöscht würden. Indem wir das Layout dem zentralen
Widget und das zentrale Widget wiederum dem Hauptfenster zuweisen,
bildet PyQt eine sogenannte \emph{Objekthierarchie}. Erst wenn das
Basisobjekt, in diesem Fall das Hauptfenster, gelöscht wird, werden
auch alle seine zugehörigen Objekte gelöscht, auch wenn man diese nur
als lokale Variablen definiert hat. Diese Eigenschaft von Qt wird uns
im Laufe dieses Kapitels noch einige Male begegnen und in Kapitel
\ref{sec:objekthierarchie} noch einmal zusammenfassend erläutert.
Abbildung \ospfigref{fig:hallowelt2} zeigt das Hauptfenster unserer Anwendung unter Ubuntu.
\ospfigure{0.4}{images/hallowelt2}{Hauptfenster der ersten Hallo-Welt-Anwendung mit Layout}{fig:hallowelt2}
\ospsection{Das Hauptfenster}{Das Hauptfenster}{sec:hauptfenster}
\index{QDialog}%
\index{QFileDialog}%
\index{Hauptfenster}%
\index{Layout}%
Das Hauptfenster ist der zentrale Einstiegspunkt einer Qt-Anwendung.
Es wird von der Klasse \ospcmd{QMainWindow} abgeleitet und
initialisiert alle GUI"=Elemente. Es gibt in Qt noch andere
Fensterklassen: Mit \ospcmd{QDialog} und seinen Unterklassen wie
\ospcmd{QFileDialog} lassen sich einfache Dialoge zur Benachrichtigung
für den Benutzer oder Abfragen von Daten erstellen. Grundsätzlich
kann jedes Widget auch als Hauptfenster dienen. Wenn Sie im bisherigen
Code alle Vorkommen von \ospcmd{QMainWindow} durch \ospcmd{QWidget}
ersetzen, erscheint dasselbe Fenster. Sie können und dürfen jedoch
kein zentrales Widget definieren, da nur ein Objekt der Klasse
\ospcmd{QMainWindow} dieses Konzept kennt. Stattdessen können Sie per
\ospcmd{self.setLayout(layoutZentral)} das Layout direkt an das von
Ihnen abgeleitete und als Hauptfenster verwendete
\ospcmd{QWidget}-Objekt übergeben. Neben dem zentralen Widget stellt
ein Objekt der Klasse \ospcmd{QMainWindow} auch weitere Eigenschaften
wie Menü- und Statusleiste zur Verfügung. Diesen Eigenschaften eines
Hauptfensters widmen wir uns im Folgenden.
\index{Anwendung}%
\index{Anwendung!Template}%
Bei der Entwicklung von Qt-Anwendungen ist es sinnvoll, den Code
immer wiederkehrender Initialisierungsabläufe einheitlich zu
organisieren. Es ist bei der Entwicklungsarbeit auf lange Sicht
vorteilhaft, wenn vor allem das Hauptfenster durch feste Methoden und
einer Art Standardkonstruktor initialisiert wird. Auch benutzen viele
PyQt-Anwendungen und -Beispiele aus dem Internet den hier vorgestellten
oder einen ähnlichen Aufbau, so dass man sich als Entwickler dort
schnell zurechtfindet, wenn man sich an dieses einheitliche
Programmgerüst erst einmal gewöhnt hat.
Das hier vorgestellte Programmgerüst wird im Verlauf dieses Buches
erweitert und angepasst, etwa wenn ein mit dem Qt Designer erstelltes
Layout verwendet werden soll. Schließlich werden wir in Kapitel
\ref{chap:projektsstruktur} auch die \ospcmd{main()}-Funktion
erweitern, damit sich das Projekt sowohl auf dem Entwicklerrechner als
auch als installiertes Paket auf dem Rechner des Anwenders ausführen
lässt.
Hier nun der Vorschlag für das Grundgerüst des Hauptfensters einer
PyQt-Anwendung, das wir anschließend abschnittsweise besprechen. Es
enthält bereits alle bislang besprochenen GUI-Elemente:
\begin{osplisting}{Python}{Grundstruktur des Hauptfensters}{code:grundstruktur}
class MainWindow(QtGui.QMainWindow):
def __init__(self, *args):
QtGui.QMainWindow.__init__(self, *args)
self.createMenu()
self.createComponents()
self.createLayout()
self.createConnects()
self.setWindowTitle(self.tr(u"Hello World"))
def createMenu(self):
pass
def createComponents(self):
self.labelHalloWelt = QtGui.QLabel(self.tr(u"Hello World!"))
self.buttonTextAktualisieren =
QtGui.QPushButton(self.tr(u"Update"))
self.editText = QtGui.QLineEdit()
def createConnects(self):
pass
def createLayout(self):
layoutZentral = QtGui.QVBoxLayout()
layoutZentral.addWidget(self.labelHalloWelt)
layoutZentral.addWidget(self.editText)
layoutZentral.addWidget(self.buttonTextAktualisieren)
widgetZentral = QtGui.QWidget()
widgetZentral.setLayout(layoutZentral)
self.setCentralWidget(widgetZentral)
\end{osplisting}
Der Konstruktor unserer \ospcmd{MainWindow}-Klasse ruft lediglich eine
Reihe von Klassenmethoden auf. Einige sind noch leer und werden in den
folgenden Kapiteln nach und nach gefüllt. Aber der Reihe nach:
\begin{ospdeflist}
\ospdefitem{\ospcmd{createMenu()}} erstellt das Anwendungsmenü sowie
die darin enthaltenen Menüpunkte. Diese Methode implementieren wir
im anschließenden Abschnitt \ref{sec:anwendungsmenue}.
\osppagebreak
\ospdefitem{\ospcmd{createComponents()}} hier werden die GUI-Elemente
erstellt. In unserem Fall sind es die drei schon oben besprochenen
Elemente Label, Button und Eingabefeld.
\ospdefitem{\ospcmd{createLayout()}} erstellt das zentrale Layout,
erzeugt ein zentrales Widget und weist diesem Widget das zentrale
Layout zu.
\ospdefitem{\ospcmd{createConnects()}} sorgt u.\,a. dafür, dass die
GUI-Elemente später auf Aktionen des Benutzers reagieren. Qt basiert
hierzu auf dem sogenannten Signal-Slot-Konzept, ein
Entwicklungsmuster, das Objekte als Sender und Empfänger von
Nachrichten voneinander entkoppelt. Dieses Muster wird in Abschnitt
\ref{sec:signalslot} implementiert und erläutert.
\end{ospdeflist}
Damit haben wir ein geeignetes Grundgerüst für alle weiteren
PyQt"=Anwendungen definiert. Es ist keine zwingende Voraussetzung für
die Entwicklung mit PyQt, erleichtert die Arbeit in vielen Situationen
aber sehr.
\ospsection{Das Anwendungsmenü}{Das Anwendungsmenü}{sec:anwendungsmenue}
\index{Menü}%
\index{Anwendungsmenü|see{Menü}}%
\index{Menüleiste|see{Menü}}%
Beim Anwendungsmenü handelt es sich um die klassische Menüleiste einer
Anwendung, die je nach Betriebssystem unterhalb der Titelleiste oder
in einer eigenen, zentralen Menüleiste am Bildschirmrand erscheint.
PyQt unterstützt Untermenüs und alle gängigen Varianten für
einzelne Menüpunkte, wie z.\,B. ein- und ausschaltbare Optionen im Menü.
\ospsubsection{Einfache Menüs mit Untermenüs}{Einfache Menüs mit Untermenüs}{sec:einfachesmenue}
\index{QMenu}%
\index{QMenu!addAction()}%
\index{QMenu!addSeparator()}%
\index{QMenuBar}%
\index{QMenuBar!addMenu()}%
\index{QAction}%
\index{QAction!setMenuRole()}%
Für ein einfaches Menü genügen zunächst drei Klassen:
\ospcmd{QMenuBar} für die Menüleiste, \ospcmd{QMenu} für die einzelnen
Hauptmenüeinträge (\ospmenu{Datei}, \ospmenu{Bearbeiten} usw.) sowie
\ospcmd{QAction} für die einzelnen Menüeinträge. Um ein Menü mit einem
Hauptmenü \ospmenu{Datei} und drei Einträgen zu definieren, ändern Sie
die Methoden \ospcmd{createMenu()} des Programmgerüsts
folgendermaßen:
\begin{osplisting}{Python}{Erstes Hauptmenü}{code:ersteshauptmenue}
def createMenu(self):
self.actionDateiOeffnen =
QtGui.QAction(self.tr("Open file..."), self)
self.actionDateiSpeichern =
QtGui.QAction(self.tr("Save"), self)
self.actionBeenden = QtGui.QAction(self.tr("Quit"), self)
self.actionBeenden.setMenuRole(QtGui.QAction.QuitRole)
menuDatei = self.menuBar().addMenu(self.tr("File"))
menuDatei.addAction(self.actionDateiOeffnen)
menuDatei.addAction(self.actionDateiSpeichern)
menuDatei.addSeparator()
menuDatei.addAction(self.actionBeenden)
\end{osplisting}
Den Aufbau des so erzeugten Menüs unter Windows zeigt Abbildung
\ospfigref{fig:hallowelt2_menu}.
\ospfigure{0.4}{images/hallowelt2_menu}{Hauptmenü der Hallo-Welt-Anwendung}{fig:hallowelt2_menu}
In \ospcmd{createMenu()} werden zunächst die Punkte für das Datei-Menü
erzeugt. Die Menüeinträge werden als Objekte von \ospcmd{QAction}
erzeugt. Die Klasse \ospcmd{QAction} stellt zahlreiche Methoden zur
Verfügung, durch die Sie Menüeinträge um Shortcuts, Icons usw.
erweitern können. Da dieselben \ospcmd{QAction}-Objekte neben der
Menüleiste auch in anderen GUI-Elementen wie Toolbars auftauchen
können, hat man sich für einen recht allgemeinen Namen für die Klasse
entschieden (im Gegensatz zu Namen wie \dqo{}MenuItem\dqc{} und
ähnlichem in anderen Toolkits). Für uns ist aber im Moment nur die
Funktion der \ospcmd{QAction} als Menüeintrag interessant. Im
Gegensatz zu den GUI-Elementen müssen Objekte des Typs \ospcmd{QAction}
immer gleich bei der Erzeugung mit einem Elternobjekt verbunden
werden. Dies geschieht wie im Beispiel durch die Übergabe des
Parameters \ospcmd{self} als zweites Argument.
\index{QAction!QuitRole}%
\index{QAction!AboutRole}%
\index{QAction!PreferencesRole}%
\index{Menu Role}%
Der Menüeintrag für \ospmenu{Beenden} erhält zusätzlich eine Rolle.
Eine solche \emph{Menu Role} wird allerdings nur unter Mac OS
relevant, damit der Menüpunkt dort korrekt im Anwendungsmenü
erscheint. Soll die Anwendung also plattformunabhängig bleiben und
sich auch unter Mac OS wie eine native Anwendung verhalten, ist die
entsprechende Rolle zu definieren. Außer der \ospcmd{QuitRole} gibt es
noch zwei relevante Rollen: die \ospcmd{AboutRole} für einen möglichen
\ospmenu{Über}-Eintrag und die \ospcmd{PreferencesRole} für einen
Menüpunkt \ospmenu{Einstellungen}.
Nachdem die Menüeinträge erzeugt wurden, fügen wir dem Hauptfenster
per \ospcmd{menuBar().addMenu()} einen Hauptmenüeintrag hinzu. Jedes
Objekt der Klasse \ospcmd{QMainWindow} hat standardmäßig eine
Menüleiste, die aber mangels Menüeinträgen in unseren ersten
Beispielen nicht sichtbar war. Mit der Hauptfenster-Methode
\ospcmd{menuBar()} erhalten wir ein Objekt der Klasse
\ospcmd{QMenuBar} zurück, das wiederum eine Methode \ospcmd{addMenu()}
zum Hinzufügen von Untermenüs besitzt. Diese liefert uns ein Objekt
der Klasse \ospcmd{QMenu}, das wir in der Variablen \ospcmd{menuDatei}
speichern. Per \ospcmd{addAction()} weist man schließlich einzelne
Aktionen dem Untermenü zu. Neben dieser Methode existieren in der
Klasse \ospcmd{QMenu} noch andere, wie \ospcmd{addSeparator()} und
\ospcmd{addMenu()} zum Hinzufügen von Trennelementen und weiteren,
verschachtelten Untermenüs. Beachten Sie, dass die Aktionen wiederum
als Attribute des Hauptfenster-Objekts definiert werden, so dass wir
die Aktionen später auch mit bestimmten Methodenaufrufen verbinden
können. Weitere Hauptmenüpunkte fügen Sie danach einfach mit weiteren
Aufrufen von \ospcmd{addMenu()} hinzu. Hier ein
\ospmenu{Hilfe}-Menü mit einem \ospmenu{Über...}-Menüeintrag:
\begin{ospsimplelisting}
self.actionUeber =
QtGui.QAction(self.tr("About Hello World..."), self)
menuUeber = self.menuBar().addMenu(self.tr("Help"))
menuUeber.addAction(self.actionUeber)
\end{ospsimplelisting}
An dieser Stelle sei kurz eine Alternative zum Erzeugen einer
solchen Menüleiste erwähnt. Sie können sowohl die Menüleiste als
auch das Untermenü zunächst als (lokale) Objekte der Klassen
\ospcmd{QMenuBar} bzw. \ospcmd{QMenu} erzeugen. Diese Objekte
weist man dann mit den entsprechenden \ospcmd{add()}-Methoden der
Elternobjekte zu: das Untermenü der Menüleiste, die Menüleiste dem
Hauptfenster. Das funktioniert ganz ähnlich wie im Beispiel oben mit
den GUI-Elemente, dem Layout und dem zentralen Widget, so dass sich
letztlich wieder eine Qt-Objekthierarchie ergibt:
\begin{osplisting}{Python}{Alternative Hauptmenü-Erzeugung}{code:alternativeshauptmenue}
# [...] Aktionen definieren
menuDatei = QtGui.QMenu(self.tr("File"), self)
# [...] add-Aufrufe wie oben
menuBar = QtGui.QMenuBar(self)
menuBar.addMenu(menuDatei)
self.setMenuBar(menuBar)
\end{osplisting}
Beachten Sie, dass in diesem Fall die Objekte mit dem Parameter
\ospcmd{self} erzeugt werden, damit die Qt-Objekthierarchie definiert
ist und die Objekte nach Verlassen der Methode erhalten bleiben. Die
Lebenszeit der Objekte wird danach vom Qt-Framework weiter verwaltet.
Generell ist die erste Methode vorzuziehen, da so immer nur das
Hauptfenster die Objekte erzeugt und automatisch alle Beziehungen
untereinander geklärt sind.
\ospsubsection{Verschachtelte Menüs und erweiterte Menüeinträge}{Verschachtelte Menüs und erweiterte Menüeinträge}{sec:komplexemenues}
\index{Menü!Untermenü}%
Qt erlaubt nicht nur einfache Menüs mit Menüeinträgen, sondern auch
die Konstruktion komplexer Untermenüs. Daneben können einzelne
Menüeinträge auch zum Ein- bzw. Ausschalten von Optionen bzw. zur
Auswahl einer Option aus einer Liste verwendet werden.
Ein weiteres Untermenü erezugt man, indem man ein Objekt der
Klasse \ospcmd{QMenu} erzeugt und parallel zu den Aktionen und
Trennlinien einem anderen \ospmenu{QMenu}-Objekt per
\ospcmd{addMenu()} hinzufügt. Das Menü enthält dann ein weiteres
Untermenü, das auf den meisten Betriebssystemen mit einem kleinen
Pfeil neben dem Menüeintrag als ausklappbar gekennzeichnet wird. Um
ein solches Untermenü zu erzeugen, fügen Sie folgenden Code vor den
Aufruf von \ospcmd{addSeparator()} im bisherigen Beispielprogramm
hinzu:
\index{QMenuBar!addMenu()}%
\begin{osplisting}{Python}{Erzeugen eines Untermenüs}{code:untermenue}
self.actionLinksAusrichten =
QtGui.QAction(self.tr("Align Left"), self)
self.actionRechtsAusrichten =
QtGui.QAction(self.tr("Align Right"), self)
menuAnsicht = menuDatei.addMenu(self.tr("View"))
menuAnsicht.addAction(self.actionLinksAusrichten)
menuAnsicht.addAction(self.actionRechtsAusrichten)
\end{osplisting}
\index{Menü!Optionen}%
\index{Menü!Checkbox}%
Damit erscheint zwischen dem Menüeintrag \ospmenu{Speichern} und der
Trennlinie ein Untermenü-Einträge \ospmenu{Ansicht} mit zwei
Untereinträgen. In diesem Fall sollen die Menüeinträge möglicherweise
verwendet werden, um später in einem Text-Widget zwischen dem links
und rechts ausgerichteten Text zu wechseln. Solche Optionen werden
auch in Menüs meist mit einer Kennzeichnung des gerade aktivierten
Modus versehen, unter Windows beispielsweise ein kleiner, ausgefüllter
Kreis. Wenn die Optionen nicht exklusiv sind, findet man häufig einen
Haken neben der aktivierten Option. In diesem Fall ist eine
nicht-exklusive Auswahl wenig sinnvoll -- schließlich kann man einen
Text unmöglich gleichzeitig links und rechts ausrichten. Dennoch
wollen wir auch diesen Fall an dem vorgestellten Beispiel
demonstrieren.
\index{QActionGroup}%
\index{QAction!setCheckable()}%
\index{QAction!setChecked()}%
\index{QAction!setExclusive()}%
Zur Realisierung solcher Menü-Optionen stellt Qt die Klasse
\ospcmd{QActionGroup} bereit. Ein Objekt dieser Klasse dient als
Elternobjekt derjenigen Menüeinträge, die zu einer Option gehören, die
also logisch zusammengehören wie die Einstellung der Textausrichtung.
Das Objekt der Klasse \ospcmd{QActionGroup} ist kein sichtbares
Wigdet, und wir werden dem Menü auch weiterhin nur die beiden Objekte
der Klasse \ospcmd{QAction} hinzufügen. Es dient also nur dem
Zusammenhalt der Aktionen. Qt stellt noch andere Gruppierungsklassen
zur Verfügung, die wir noch kennenlernen werden. Neben der
Gruppierung legen wir mit der Methode \ospcmd{setCheckable()} der
Klasse \ospcmd{QAction} die beiden Aktionen als auswählbare Elemente
fest. Der gesamte Code für unser Untermenü sieht dann folgendermaßen
aus:
\begin{osplisting}{Python}{Erzeugen eines Untermenüs mit Optionen}{code:untermenueoptionen}
actiongroupAnsichten = QtGui.QActionGroup(self)
self.actionLinksAusrichten =
QtGui.QAction(self.tr("Align Left"), actiongroupAnsichten)
self.actionLinksAusrichten.setCheckable(True)
self.actionLinksAusrichten.setChecked(True)
self.actionRechtsAusrichten =
QtGui.QAction(self.tr("Align Right"), actiongroupAnsichten)
self.actionRechtsAusrichten.setCheckable(True)
actiongroupAnsichten.setExclusive(True)
menuAnsicht = menuDatei.addMenu(self.tr("View"))
menuAnsicht.addAction(self.actionLinksAusrichten)
menuAnsicht.addAction(self.actionRechtsAusrichten)
\end{osplisting}
Die Gruppierung erfolgt also allein durch die Übergabe von
\ospcmd{actiongroup\-Ansichten} an den Konstruktor der Aktionen. Statt
\ospcmd{self}, also dem Hauptfenster (wie im ersten Ansatz), wird nun
die Gruppe das Elternobjekt der Aktionen. Wiederum übernimmt intern Qt
die Speicherverwaltung für dieses Objekt. Per \ospcmd{setChecked()}
legen wir außerdem die Linksausrichtung als Default-Einstellung für
unsere Option fest. Schließlich wird die Exklusivität der beiden
Einstellungen durch den Aufruf der Methode \ospcmd{setExclusive()}
gewährleistet. Wenn die Optionen nicht-exklusiv sein sollen, übergeben
Sie dieser Methode den Parameter \ospcmd{False}. Tatsächlich können
Sie den Aufruf mit \ospcmd{True}, wie in unserem Fall, einfach
weglassen. Die Standard\-einstellung einer \ospcmd{QActionGroup} ist
immer eine Exklusivität der einzelnen Aktionen. Lesbarer ist der Code
aber mit diesem Aufruf, so dass wir hier diese Variante verwenden.
Abbildung \ospfigref{fig:hallowelt2_menu3} zeigt schließlich das Menü
mit nicht-exklusiven bzw. exklusiven Menüeinträgen nebeneinander unter
Windows. Wie schon erwähnt, zeigen unterschiedliche Symbole neben den
Einträgen diese Eigenschaft auf den ersten Blick an, wie man es von
anderen Anwendungen unter diesem Betriebssystem gewohnt ist.
\ospfigure{0.8}{images/hallowelt2_menu3}{Untermenü mit nicht-exklusiven (links) und exklusiven (rechts) Optionen}{fig:hallowelt2_menu3}
\ospsection{Layouts}{Layouts}{sec:layouts}
\index{Layout}%
Layouts dienen bei Qt dazu, Elemente auf bestimmte Weise in einem
Fenster anzuordnen. Das vertikale Layout mit der Klasse
\ospcmd{QVBoxLayout} haben wir in unserem Hallo-Welt-Programm schon
verwendet.
\ospsubsection{Freie Layouts}{Freie Layouts}{sec:freielayouts}
\index{Layout!freies Layout}%
\index{QWidget!setGeometry()}%
Prinzipiell ist es mit Qt möglich, die Elemente ohne ein Layout im
Hauptfenster anzuordnen. Dazu definiert man für jedes Element über die
Methode \ospcmd{setGeometry()} eine feste Position und Größe. Für die
drei Elemente unseres Programmgerüsts kann man beispielsweise folgende
Definitionen in \ospcmd{createComponents()} vornehmen:
\begin{osplisting}{Python}{GUI-Elemente frei positionieren}{code:freieslayout}
def createComponents(self):
self.labelHalloWelt = QtGui.QLabel(self.tr("Hello World!"), self);
self.labelHalloWelt.setGeometry(20, 20, 100, 50)
self.buttonTextAktualisieren =
QtGui.QPushButton(self.tr("Update"), self)
self.buttonTextAktualisieren.setGeometry(20, 100, 150, 50)
self.editText = QtGui.QLineEdit(self)
self.editText.setGeometry(220, 100, 200, 50)
\end{osplisting}
Die Methode \ospcmd{createLayout()} kann dann leer bleiben, weil kein
Layout und kein zentrales Widget mehr benötigt werden. Sie können
diese auch versuchsweise im Konstruktor auskommentieren. Nun entsteht
allerdings ein Problem: Das Hauptfenster ist zu klein, um alle
Elemente anzeigen zu können. Der Benutzer kann natürlich die
Fenstergröße ändern, um den Button und das Texteingabefeld sichtbar zu
machen. Sinnvoller ist es allerdings, am Ende des Konstruktors des
Hauptfensters vor oder nach dem Aufruf von \ospcmd{setWindowTitle()}
die Größe des Fensters anzupassen:
\index{QWidget!resize()}%
\index{Hauptfenster!Größe ändern}%
\begin{ospsimplelisting}
self.resize(500, 250)
\end{ospsimplelisting}
Zurück zum freien Layout: Als Parameter der Methode
\ospcmd{setGeometry()} übergibt man die Position vom linken und oberen
Fensterrand, gefolgt von Breite und Höhe. Beachten Sie, dass nun alle
Elemente mit dem Parameter \ospcmd{self} im Konstruktor erzeugt
werden. Dies stellt die Objekthierarchie sicher: Da jetzt kein
zentrales Widget und auch kein Layout mehr existieren, über die die
Elemente dem Hauptfenster zugeordnet werden, müssen wir die Elemente
gleich bei der Erzeugung das Hauptfensters als Elternelement
übergeben. Somit werden die Elemente überhaupt erst im Hauptfenster
angezeigt; außerdem wird beim Schließen des Hauptfensters auch der
Speicherplatz der Elemente freigegeben. Das Hauptfenster unserer
Anwendung sieht dann aus wie in Abbildung
\ospfigref{fig:mainwindowmanuelleslayout}.
\ospfigure{0.6}{images/hallowelt4_freieslayout}{Hauptfenster der Anwendung mit manuellem Layout}{fig:mainwindowmanuelleslayout}
Grundsätzlich ist vom Einsatz solch manueller Layouts jedoch
abzuraten. Die Positionen der einzelnen Elemente lassen sich nur
äußerst schwer abschätzen, außerdem geraten solche Layouts später bei
der Lokalisierung der Anwendung oft durcheinander, wenn sich
String-Längen ändern. Qt bietet darum mit seinen von \ospcmd{QLayout}
abgeleiteten Klassen eine Reihe von automatischen Layouts, die dem
Entwickler einen Großteil der Arbeit beim Platzieren und Anpassen von
Elementen abnehmen.
\ospsubsection{Horizontales und vertikales Layout}{Horizontales und vertikales Layout}{sec:hvlayout}
\index{Layout!horizontales Layout}%
\index{Layout!vertikales Layout}%
\index{QHBoxLayout}%
\index{QVBoxLayout}%
Elemente lassen sich mit den Klassen \ospcmd{QHBoxLayout} und
\ospcmd{QVBoxLayout}\osplinebreak{} neben- bzw. untereinander
anordnen. Beide sind von \ospcmd{QBoxLayout} abgeleitet und teilen
sich die meisten Methoden. \ospcmd{QBoxLayout} wiederum basiert auf
\ospcmd{QLayout}, der Basisklasse für alle Layoutklassen.
Wenn wir im Programmgerüst bei der Initialisierung in
\ospcmd{createLayout} statt dem vertikalen ein horizontales Layout mit
\ospcmd{QHBoxLayout} erstellen, werden die drei Elemente nebeneinander
statt untereinander angeordnet. Abbildung
\ospfigref{fig:mainwindowverthorizlayout} stellt beide
Layout-Varianten einander gegenüber.
\ospfigure{0.9}{images/mainwindowverthorizlayout}{Hauptfenster der Anwendung mit horizontalem (links) und vertikalem (rechts) Layout}{fig:mainwindowverthorizlayout}
Versuchen Sie einmal, auch bei diesen Layouts per
\ospcmd{resize()}-Methode im Konstruktor des Hauptfensters die
Fenstergröße zu ändern. In diesem Fall passt sich die Größe der
Elemente automatisch der Fenstergröße an, aber nur in der Breite, die
Höhe der Elemente ändert sich nicht. Qt übernimmt über seine
Layout-Klassen automatisch die Kontrolle über Größe und teilweise auch
Position der GUI-Elemente. Diese Automatik erleichtert die Erstellung
von Benutzeroberflächen enorm. Bei zunehmender Komplexität kann es
aber schnell zu unerwünschten Effekten beim Layout kommen, da jedes
Qt-Widget über eine Reihe eigener Eigenschaften auf das automatische
Layout Einfluss nehmen kann. In den folgenden Abschnitten werden wir
uns darum mit diesen Eigenschaften beschäftigen und ein grundlegendes
Verständnis für den Layout-Mechanismus schaffen.
Versuchen Sie bei allen Beispielen auch, die Fenstergröße entweder per
\ospcmd{resize()} oder manuell mit der Maus zu ändern: Qt
passt nämlich beim Start der Anwendung automatisch die Größe des
Fensters an die berechnete Größe der Widgets an. Änderungen an diesen
Einstellungen werden dann erst sichtbar, wenn das Fenster zu groß oder
zu klein für die Widgets wird bzw. wenn sich die Fenstergröße bei
laufender Anwendung ändert.
\index{QSizePolicy}%
\index{Size Policy}%
\begin{ospdeflist}
\ospdefitem{\ospcmd{QSizePolicy::Fixed}} Die Größe des Widgets
entspricht den Werten, die über \ospcmd{sizeHint()} abegrufen
werden können. Die Größe wird nicht geändert.
\ospdefitem{\ospcmd{QSizePolicy::Minimum}} \ospcmd{sizeHint()} gibt
die minimale Größe an, das Widget kann aber vergrößert werden.
\ospdefitem{\ospcmd{QSizePolicy::Maximum}} \ospcmd{sizeHint()} gibt
die maximale Größe an, das Widget kann beliebig verkleinert werden.
\ospdefitem{\ospcmd{QSizePolicy::Preferred}} \ospcmd{sizeHint()}
gibt die optimale Größe an, das Widget kann aber verkleinert oder
vergrößert werden. Das Widget profitiert aber nicht von einer
Vergrößerung. Dies ist die Standardeinstellung für \ospcmd{QWidget}.
\ospdefitem{\ospcmd{QSizePolicy::Expanding}} Auch hier ist
\ospcmd{sizeHint()} ein Optimalwert, und das Widget kann vergrößert
und verkleinert werden. Jedoch sollte das Widget so groß wie möglich
dargestellt werden.
\ospdefitem{\ospcmd{QSizePolicy::MinimumExpanding}} Der Wert von
\ospcmd{sizeHint()} ist minimal und ausreichend für das Widget,
jedoch profitiert das Widget von mehr Platz und sollte, wenn
möglich, maximal vergrößert werden.
\ospdefitem{\ospcmd{QSizePolicy::Ignored}} \ospcmd{sizeHint()}
sollte ignoriert und das Widget so groß wie möglich dargestellt
werden.
\end{ospdeflist}
\index{QWidget!sizePolicy()}%
\index{QWidget!setSizePolicy()}%
\index{QWidget!sizeHint()}%
Bei automatischen Layouts übernimmt Qt, wie gesagt, weitestgehend die
Kontrolle über Größe und Position der Widgets. So zeigt Abbildung
\ospfigref{fig:mainwindowverthorizlayout}, dass bei einem horizontalen
Layout das Eingabefeld den freien Platz so weit wie möglich ausfüllt,
während der Button und das Label daneben auf eine sinnvolle Größe
schrumpfen, so dass der Text vollständig in den Elementen dargestellt
werden kann. Im vertikalen Layout dagegen breitet sich auch der Button
über die gesamte Breite des Bildschirms aus.
Dieses Verhalten lässt sich über die sogenannte \emph{Size Policy}
ändern. Jedes \ospcmd{QWidget} (von dem alle bisher verwendeten
Widget-Klassen abgeleitet sind) definiert dazu die Methoden
\ospcmd{sizePolicy()}, \ospcmd{setSizePolicy()} und
\ospcmd{size\-Hint()}. Die Methode \ospcmd{sizeHint()} gibt zurück, wie
viel Platz das Widget unter normalen Umständen beanspruchen möchte. Je
nach Size Policy kann das Widget aber vergrößert oder verkleinert
dargestellt werden. Dazu übergibt man der Methode
\ospcmd{setSizePolicy()} zwei Werte aus einem vordefinierten
Enumerator \ospcmd{QSizePolicy::Policy} für die horizontale und
vertikale Einstellung. Alle gültigen Werte finden Sie in der Liste
oben. Um beispielsweise alle Elemente gleich groß zu machen, weist man
ihnen für die Horizontale den Wert \ospcmd{QSizePolicy::Expanding} zu,
so dass sie den maximal verfügbaren Platz einnehmen. Das Eingabefeld
besitzt diesen Wert standardmäßig, so dass es in unserem Fall
ausreicht, den Wert für den Button und das Label zu setzen. Fügen Sie
dazu folgende zwei Zeilen am Anfang der
\ospcmd{createLayout()}-Methode hinzu:
\begin{ospsimplelisting}
self.buttonTextAktualisieren.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Fixed)
self.labelHalloWelt.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Fixed)
\end{ospsimplelisting}
\index{QWidget!setMinimumSize()}%
Die Einstellung für die Vertikale bleibt auf den Wert
\ospcmd{QSizePolicy::Fixed} gesetzt, so dass die Elemente immer eine
vordefinierte Höhe einnehmen. Diese feste Höhe (wie auch die Breite)
lässt sich über eine weitere Methode \ospcmd{setMinimumSize()}
festlegen. Falls sich das Element vergrößern oder verkleinern soll,
kann man so auch eine minimale Größe angeben, die nicht unterschritten
wird, wenn beispielsweise neue Elemente Platz innerhalb des Layouts
beanspruchen. Erweitern Sie \ospcmd{createLayout()} noch um folgende
Zeile, um das Eingabefeld mit einer minimalen Größe zu versehen:
\begin{ospsimplelisting}
self.editText.setMinimumSize(350, 50)
\end{ospsimplelisting}
\index{QWidget!Stretchfaktor}%
\index{Stretchfaktor}%
Neben diesen Möglichkeiten zur Beeinflussung eines automatischen
Layouts gibt es eine weitere, den sogenannten \emph{Stretchfaktor}.
Über ihn definiert man, wie sich die Größe der Elemente gegenüber der
anderer Elemente verhält. Standardmäßig wird allen \ospcmd{QWidget}
der Stretchfaktor 0 zugewiesen, so dass der Platz über die Werte der
Angabe der \ospcmd{QSizePolicy} zugewiesen wird. Um den Stretchfaktor
kontrollieren zu können, muss dieser also für alle Elemente definiert
werden. Andernfalls nimmt möglicherweise ein Element allen verfügbaren
Platz ein (z.\,B. weil es \ospcmd{QSizePolicy::Expanding} gesetzt
hat).
Wenn nun das Eingabefeld genau doppelt so groß sein soll wie die
anderen Elemente, definieren Sie statt der Mindestgröße lieber
einen Stretchfaktor. Dieser wird zu dem Zeitpunkt gesetzt, wenn das
Element dem Layout zugewiesen wird, und kann als zweiter Parameter an
\ospcmd{addWidget()} übergeben werden. Mit den folgenden Zeilen legen
Sie fest, dass der Button mindestens doppelt so groß sein soll wie die
anderen beiden Elemente:
\begin{ospsimplelisting}
layoutZentral.addWidget(self.labelHalloWelt, 1)
layoutZentral.addWidget(self.editText, 2)
layoutZentral.addWidget(self.buttonTextAktualisieren, 1)
\end{ospsimplelisting}
Aus allen Angaben errechnet Qt dann das endgültige Layout, und bei
Änderungen der Fenstergröße passen sich alle Elemente automatisch an.
Die Definition dieser Werte erleichtert es, dynamisch Elemente in das
Layout hinzuzufügen und unterschiedliche Lokalisierungen mit einer
automatischen Anpassung der Widget-Größe zu verbinden, und zwar
möglichst unabhängig von der Auflösung und dem Qt-Style des
Betriebssystems, so dass auf absolute Angaben wie bei der Geometrie und
der Größe von Widgets lieber verzichtet werden sollte.
Beachten Sie, dass außer der Auflösung und damit Größe des
Hauptfensters auch Standardgrößen von Fonts, Scollbars usw. eine Rolle
für das Layout spielen. Arbeitet man hier mit festen Werten, wird die
GUI der Anwendung in den meisten Fällen für jedes Betriebssystem
separat definiert werden müssen. Stattdessen arbeitet man am
sinnvollsten über die Definition von Size Policies und
Stretchfaktoren.
\index{Layout!Stretchelement}%
Eine weitere Möglichkeit, auf das automatische Layout Einfluss zu
nehmen, sei hier auch noch erwähnt: In allen Layout-Klassen existiert
die Methode \ospcmd{addStretch()}, mit der sich ein unsichtbares
Stretchelement in das Layout einfügen lässt. Dieses Stretchelement
nimmt immer den maximal möglichen Platz ein, so dass andere Elemente
im Layout auf ihre optimale Größe zusammengestaucht werden. Um
beispielsweise alle Elemente so weit oben wie möglich zu
positionieren, kann nach dem Hinzufügen aller Elemente in ein
vertikales Layout einfach ein \ospcmd{addStretch()} ausgeführt werden: