1542.book Seite 2 Montag, 4. Januar 2010 1:02 13
Liebe Leserin, lieber Leser, wir freuen uns, dass Sie sich für ein Galileo Computing-Buch entschieden haben! Jürgen Wolf, der Autor mehrerer Standardwerke zu C, C++, zur Shell und zur LinuxUNIX-Programmierung, gibt Ihnen in diesem Buch einen tiefen Einblick in die Entwicklung grafischer Oberflächen mit der populären C++-Klassenbibliothek Qt. In seinem gewohnt klaren, verständlichen Schreibstil vermittelt er Ihnen alle notwendigen Grundlagen der Qt-Programmierung und gibt Ihnen Anleitungen und Tipps für die konkrete Umsetzung Ihrer Projekte. Wie auch seine anderen Werke zeichnet sich dieses Buch durch eine klare Strukturierung aus, sodass Sie es sehr gut als Nachschlagewerk nutzen können. Für die Arbeit mit diesem Buch müssen Sie keine Entwicklungsumgebung installieren. Für das Nachvollziehen der Beispiele genügt ein einfacher ASCII-Editor. Wenn Sie dennoch eine Entwicklungsumgebung nutzen möchten, schauen Sie ins Kapitel 13. Dort wird die Arbeit mit dem Qt Creator vorgestellt. Es lohnt sich auch, einen Blick auf die Buch-DVD zu werfen. Sie finden dort neben den Beispielen mehrere Kapitel zu weiterführenden Themen aus anderen Büchern von Jürgen Wolf. Wenn Sie nach der Lektüre Fragen, Verbesserungsvorschläge oder auch Lob äußern wollen, so können Sie sich jederzeit an mich oder Jürgen Wolf wenden. Wir freuen uns immer über eine Rückmeldung!
Ihre Judith Stevens-Lemoine Lektorat Galileo Computing
[email protected] www.galileocomputing.de Galileo Press · Rheinwerkallee 4 · 53227 Bonn
1542.book Seite 3 Montag, 4. Januar 2010 1:02 13
Auf einen Blick 1
Einstieg in Qt ......................................................................
15
2
Signale und Slots ................................................................
31
3
Basisklassen und Bibliotheken von Qt ...............................
57
4
Dialoge, Layout und Qt-Widgets .......................................
71
5
Qt-Hauptfenster .................................................................
323
6
Ein-/Ausgabe von Daten .....................................................
417
7
Ereignisverarbeitung ...........................................................
627
8
Drag & Drop und Zwischenablage ......................................
647
9
Grafik und Drucken ............................................................
665
10
XML ....................................................................................
713
11
Internationale Anwendungen .............................................
733
12
Weiteres zu Qt ...................................................................
741
13
Anwendungen mit Qt Creator erstellen .............................
771
1542.book Seite 4 Montag, 4. Januar 2010 1:02 13
Der Name Galileo Press geht auf den italienischen Mathematiker und Philosophen Galileo Galilei (1564–1642) zurück. Er gilt als Gründungsfigur der neuzeitlichen Wissenschaft und wurde berühmt als Verfechter des modernen, heliozentrischen Weltbilds. Legendär ist sein Ausspruch Eppur se muove (Und sie bewegt sich doch). Das Emblem von Galileo Press ist der Jupiter, umkreist von den vier Galileischen Monden. Galilei entdeckte die nach ihm benannten Monde 1610. Gerne stehen wir Ihnen mit Rat und Tat zur Seite:
[email protected] bei Fragen und Anmerkungen zum Inhalt des Buches
[email protected] für versandkostenfreie Bestellungen und Reklamationen
[email protected] für Rezensions- und Schulungsexemplare Lektorat Judith Stevens-Lemoine, Anne Scheibe Korrektorat Dr. Monika Oertner, Konstanz Cover Barbara Thoben, Köln Titelbild Barbara Thoben, Köln Typografie und Layout Vera Brauner Herstellung Steffi Ehrentraut Satz SatzPro, Krefeld Druck und Bindung Bercker Graphischer Betrieb, Kevelaer Dieses Buch wurde gesetzt aus der Linotype Syntax Serif (9,25/13,25 pt) in FrameMaker. Gedruckt wurde es auf chlorfrei gebleichtem Offsetpapier.
Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. ISBN
978-3-8362-1542-8
© Galileo Press, Bonn 2010 2., aktualisierte und erweiterte Auflage 2010 Das vorliegende Werk ist in all seinen Teilen urheberrechtlich geschützt. Alle Rechte vorbehalten, insbesondere das Recht der Übersetzung, des Vortrags, der Reproduktion, der Vervielfältigung auf fotomechanischem oder anderen Wegen und der Speicherung in elektronischen Medien. Ungeachtet der Sorgfalt, die auf die Erstellung von Text, Abbildungen und Programmen verwendet wurde, können weder Verlag noch Autor, Herausgeber oder Übersetzer für mögliche Fehler und deren Folgen eine juristische Verantwortung oder irgendeine Haftung übernehmen. Die in diesem Werk wiedergegebenen Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. können auch ohne besondere Kennzeichnung Marken sein und als solche den gesetzlichen Bestimmungen unterliegen.
1542.book Seite 5 Montag, 4. Januar 2010 1:02 13
Inhalt Vorwort .......................................................................................................
11
1
Einstieg in Qt ..........................................................................
15
1.1 1.2 1.3
Was ist Qt? ................................................................................. Lizenzierung ............................................................................... Qt installieren ............................................................................. 1.3.1 Linux .............................................................................. 1.3.2 Mac OS X ....................................................................... 1.3.3 MS-Windows (XP/Vista/Windows 7) .............................. »Hallo Welt« mit Qt .................................................................... 1.4.1 »Hallo Welt« mit der Kommandozeile ............................ 1.4.2 »Hallo Welt« mit Qt Creator ........................................... 1.4.3 Troubleshooting: »Bei mir werden keine Icons angezeigt« ......................................................................
15 16 16 17 18 18 19 19 24
Signale und Slots ....................................................................
31
2.1 2.2 2.3 2.4
33 40 41
1.4
2
Signale und Slots ermitteln ......................................................... Gegenseitiges Signal- und Slot-Konzept ...................................... Argumentenlisten von Signal-Slot-Verbindungen ........................ Eigene Klasse mit Signalen und Slots definieren bzw. erweitern .................................................................................... Widget mit eigenem Slot ............................................................ Widget mit eigenem Signal ......................................................... Zusammenfassung .......................................................................
42 49 51 55
Basisklassen und Bibliotheken von Qt ...................................
57
3.1 3.2 3.3 3.4
57 57 60 61 64 65 65 65 66 66
2.5 2.6 2.7
3
29
Basisklasse: QObject ................................................................... Qt-Klassenhierarchie ................................................................... Speicherverwaltung von Objekten .............................................. Programm-Bibliotheken von Qt .................................................. 3.4.1 QtCore ........................................................................... 3.4.2 QtGui ............................................................................. 3.4.3 QtNetwork ..................................................................... 3.4.4 QtOpenGL ..................................................................... 3.4.5 QtSql ............................................................................. 3.4.6 QtSvg .............................................................................
5
1542.book Seite 6 Montag, 4. Januar 2010 1:02 13
Inhalt
3.4.7 QtXml ............................................................................ 3.4.8 Qt3Support .................................................................... 3.4.9 QtScript ......................................................................... 3.4.10 QtWebKit ...................................................................... 3.4.11 Phonon .......................................................................... 3.4.12 Der Rest ......................................................................... Meta-Include-Headerdatei ..........................................................
67 67 67 67 68 68 68
Dialoge, Layout und Qt-Widgets ...........................................
71
3.5
4
4.1 4.2 4.3 4.4
4.5
4.6
6
Eigene Widget-Klassen erstellen ................................................. Widgets anordnen – das Layout .................................................. 4.2.1 Grundlegende Layout-Widgets ....................................... Erstellen von Dialogen (QDialog) ................................................ 4.3.1 Benutzerfreundlichkeit von Dialogen .............................. Vorgefertigte Dialoge .................................................................. 4.4.1 QMessageBox – Nachrichtendialoge ............................... 4.4.2 QFileDialog – Dialoge zur Dateiauswahl ......................... 4.4.3 QInputDialog – Eingabedialog ........................................ 4.4.4 QFontDialog – Schriftauswahl ........................................ 4.4.5 QColorDialog – Farbauswahl .......................................... 4.4.6 QPrintDialog – Druckerdialog ......................................... 4.4.7 Dialoge – Übersicht ........................................................ Qt-Widgets ................................................................................ 4.5.1 Buttons – Basisklasse QAbstractButton ........................... 4.5.2 Container-Widgets ......................................................... 4.5.3 Widgets zur Zustandsanzeige ......................................... 4.5.4 Widgets für die Eingabe ................................................. 4.5.5 Item-View-Subklassen verwenden (Ansichts-Klassen) ..... 4.5.6 Exkurs: Model-View-Controller (MVC) ........................... 4.5.7 Vordefinierte Modelle .................................................... Online-Hilfen .............................................................................. 4.6.1 Statuszeilentipp .............................................................. 4.6.2 Tooltips .......................................................................... 4.6.3 Direkthilfe ...................................................................... 4.6.4 Einfache Dokumentation mit QTextBrowser ................... 4.6.5 QAssistantClient – Qt Assistant weiterverwenden ...........
71 74 74 100 110 113 113 122 127 131 132 132 133 133 133 155 181 199 263 302 303 316 316 316 317 318 321
1542.book Seite 7 Montag, 4. Januar 2010 1:02 13
Inhalt
5
Qt-Hauptfenster ..................................................................... 323 5.1 5.2
5.3 5.4
6
Aufbau eines Hauptfensters ........................................................ Die Klasse QMainWindow .......................................................... 5.2.1 Flags für QMainWindow ................................................ 5.2.2 Eine Menüleiste mit der Klasse QMenu und QMenuBar ..................................................................... 5.2.3 Eine Statusleiste mit der Klasse QStatusBar .................... 5.2.4 Eine Werkzeugleiste mit der Klasse QToolBar ................. 5.2.5 Verschiebbare Widgets im Hauptfenster mit QDockWidget ................................................................ 5.2.6 Einstellungen der Anwendung speichern mit QSettings ....................................................................... 5.2.7 Anwendungen mit MDI-Fenster erstellen (Klasse QWorkspace) ..................................................... 5.2.8 Übersicht zu den Methoden der Klasse QMainWindow .............................................................. Fenster aufteilen – QSplitter ....................................................... 5.3.1 Splitter-Handle – QSplitterHandle .................................. Scrolling Area – QScrollArea .......................................................
323 324 326 327 345 352 358 363 383 396 400 404 409
Ein-/Ausgabe von Daten ......................................................... 417 6.1 6.2 6.3
6.4 6.5 6.6 6.7 6.8
6.9
Schnittstelle für alle E/A-Geräte – QIODevice ............................. Die Datei – QFile ........................................................................ 6.2.1 Temporäre Datei – QTemporaryFile ................................ Streams ...................................................................................... 6.3.1 Binäre Daten – QDataStream ......................................... 6.3.2 Text Daten – QTextStream ............................................. Der Puffer – QBuffer ................................................................... Verzeichnisse – QDir ................................................................... Datei-Informationen – QFileInfo ................................................. Interprozesskommunikation – QProcess ...................................... Netzwerkkommunikation (Sockets) ............................................. 6.8.1 QAbstractSocket ............................................................ 6.8.2 Das HTTP-Protokoll – QHttp .......................................... 6.8.3 Das FTP-Protokol – QFtp ................................................ 6.8.4 Ein Proxy – QNetworkProxy ........................................... Multithreads – QThread .............................................................. 6.9.1 QMutex ......................................................................... 6.9.2 QMutexLocker ...............................................................
417 421 429 431 431 444 461 464 473 481 496 496 528 544 561 562 572 574
7
1542.book Seite 8 Montag, 4. Januar 2010 1:02 13
Inhalt
6.9.3 6.9.4 6.9.5 6.9.6
6.10
6.11
6.12
7
575 577 583 586 590 591 591 593 594 601 606 609 609 610 612 613 614 614 626
Ereignisverarbeitung ............................................................... 627 7.1 7.2 7.3 7.4
7.5 7.6
8
QReadWriteLock ............................................................ QSemaphore .................................................................. QWaitCondition ............................................................. Datenstrukturen an den Thread binden – QThreadStorage ............................................................. 6.9.7 Ausblick ......................................................................... Relationale Datenbanken – QtSql ............................................... 6.10.1 Die Treiber für QtSql ...................................................... 6.10.2 Ein Verbindung zur Datenbank herstellen – QSqlDatabase ................................................................ 6.10.3 SQL-Anweisungen ausführen – QSqlQuery ..................... 6.10.4 SQL-Anweisungen der höheren Ebene – QSqlTableModel ............................................................ 6.10.5 View-Klasse QTableView mit SQL verwenden ................ Klassen und Typen zum Speichern von Daten ............................. 6.11.1 Qt-eigene Typendefinitionen .......................................... 6.11.2 QString .......................................................................... 6.11.3 QChar ............................................................................ 6.11.4 QByteArray .................................................................... 6.11.5 QVariant ........................................................................ 6.11.6 Container und Algorithmen ............................................ Datum und Uhrzeit .....................................................................
Ereignisschleife (Event-Loop) ...................................................... Ereignishandler neu implementieren ........................................... 7.2.1 event() neu implementieren ........................................... Ereignisfilter implementieren ...................................................... Eingreifen in die Ereignisverwaltung ............................................ 7.4.1 QApplication::notify() .................................................... 7.4.2 eventFilter() – Ereignisfilter ............................................. 7.4.3 event() ........................................................................... 7.4.4 Ereignishandler .............................................................. 7.4.5 Weitergabe von Ereignissen ........................................... Ereignisverarbeitung für Threads ................................................. Ereignisverarbeitung optimieren .................................................
627 629 635 636 639 639 640 640 640 640 641 644
1542.book Seite 9 Montag, 4. Januar 2010 1:02 13
Inhalt
8
Drag & Drop und Zwischenablage .......................................... 647 8.1
8.2
9
Kodierung mit QMimeData ........................................................ 8.1.1 Drop-Seite ..................................................................... 8.1.2 Drag-Seite ...................................................................... 8.1.3 Benutzerdefinierte MIME-Typen für das Drag & Drop .... Zwischenablage – QClipboard .....................................................
648 651 656 660 661
Grafik und Drucken ................................................................. 665 9.1
9.2
9.3 9.4
9.5
Zeichnen mit Qt – QPainter ........................................................ 9.1.1 QPaintEvent ................................................................... 9.1.2 Einstellungen ................................................................. 9.1.3 Transformation des Koordinatensystems ........................ Bildbearbeitung – QImage .......................................................... 9.2.1 Speicher- und Bildformate .............................................. 9.2.2 Bild laden und speichern ................................................ 9.2.3 Bildinformationen und Bild-Transformation .................... 9.2.4 Pixel auslesen ................................................................. Drucken mit Qt – QPrinter .......................................................... OpenGL mit Qt ........................................................................... 9.4.1 Spezifikation .................................................................. 9.4.2 Anwendungsbeispiele in der Praxis von OpenGL ............ 9.4.3 Portabilität ..................................................................... 9.4.4 OpenGL mit Qt anwenden ............................................. Vektorgrafik – QSvgWidget .........................................................
665 666 670 673 683 683 684 684 685 691 701 702 702 703 703 710
10 XML ......................................................................................... 713 10.1 10.2
SAX-API von Qt verwenden ........................................................ 10.1.1 Default-Handler implementieren .................................... DOM-API von Qt verwenden ..................................................... 10.2.1 Elemente suchen ............................................................ 10.2.2 Weiteres ........................................................................
714 716 721 731 732
11 Internationale Anwendungen ................................................. 733 11.1 11.2 11.3 11.4
Voraussetzung für eine Übersetzung ........................................... 11.1.1 Fehlervermeidung und Kommentare .............................. Übersetzen mit Linguist .............................................................. Übersetzung verwenden ............................................................. char-Arrays internationalisieren ..................................................
733 734 735 738 740
9
1542.book Seite 10 Montag, 4. Januar 2010 1:02 13
Inhalt
12 Weiteres zu Qt ........................................................................ 741 12.1
Dynamische Bibliotheken erstellen ............................................. 12.1.1 Dynamische Bibliothek dynamisch nachladen ............... 12.1.2 Plugins erstellen ........................................................... 12.2 Qt Mobility (alias Qt Extended (ehemals Qtopia)) ....................... 12.3 Debugging-Ausgabe ................................................................... 12.3.1 Fehlerbehebung ........................................................... 12.4 Qt Styles ..................................................................................... 12.5 QApplication, QCoreApplication und die Kommandozeile .......... 12.6 QtWebKit-Module ..................................................................... 12.7 Das Qt-Ressourcen-System ......................................................... 12.8 Qt Phonon .................................................................................. 12.9 Animation Framework ................................................................ 12.10 Weitere Klassen im Schnelldurchlauf ........................................... 12.10.1 Multitouch- und Gestensteuerung ............................... 12.10.2 State Machine Framework ............................................ 12.10.3 Qt für Symbian S60 ......................................................
741 744 746 747 747 751 752 754 756 763 765 766 770 770 770 770
13 Anwendungen mit Qt Creator erstellen ................................. 771 13.1 13.2 13.3
13.4
13.5
Die Arbeitsoberfläche von Qt Creator ......................................... Qt-Beispiele verwenden .............................................................. Der Editor von Qt Creator ........................................................... 13.3.1 Schneller durch den Code navigieren mit dem Locator 13.3.2 Tastenkombinationen ................................................... Anwendungen mit dem Qt Designer entwerfen .......................... 13.4.1 Ein Dialogfenster erstellen ............................................ 13.4.2 Ein Hauptfenster mit dem Designer entwerfen ............. Mehrere Versionen von Qt verwenden .......................................
771 772 774 775 777 777 778 799 808
Index ........................................................................................................... 811
10
1542.book Seite 11 Montag, 4. Januar 2010 1:02 13
Vorwort
Ich freue mich, Ihnen mein nächstes Buch zur GUI-Programmierung mit Qt präsentieren zu dürfen. Besonders schön finde ich, nun über eine komplette Büchersammlung zu verfügen. Wie soll man das verstehen? Meine bisherigen Bücher (bspw. »C von A bis Z«, »C++ von A bis Z«, »Linux-UNIX-Programmierung« oder »Shell-Programmierung«) waren alle eher für Konsolen-Programme gedacht (auch wenn sie einzelne GUI-Kapitel enthielten). Natürlich sind dies alles Grundlagenbücher der Programmierung und als solche unverzichtbar. Viele Leser wollten allerdings wissen, wie sie Programme mit einer grafischen Oberfläche erstellen, ohne gleich vom System abhängig zu sein und ohne sofort horrende Lizenzgebühren zu zahlen. Das vorliegende Buch füllt eine Lücke im bestehenden Angebot.
Warum Qt? Sicherlich stellen Sie sich zunächst die Frage, warum ausgerechnet Qt bzw. warum ich mich für Qt entschieden habe? Warum nicht MFC von Microsoft? Einen Vergleich mit anderen GUI-Frameworks anzustellen, ist meist wenig sinnvoll. Ich habe mich für Qt entschieden, weil dieses Framework in der Softwareszene mittlerweile die Rolle eines Platzhirsches einnimmt. Top-Software wie u. a. Google Earth, der Opera-Browser oder Skype wurde mit Qt erstellt. Die Liste der Firmen, die Qt verwenden, ist lang und beeindruckend. Natürlich bedeutet es noch nicht das Nonplusultra, wenn Firmen wie Synopsys, Motorola, Skype, Volvo, Adobe, Google, Samsung, Walt Disney Feature Animation, NASA usw. dieses Framework verwenden – aber »es hat schon was«. Den Großteil der mit Qt erstellten Software bekommt man ohnehin nie zu Gesicht, weil es sich dabei meist um Programme handelt, die speziell für Firmen erstellt wurden. Aber auch von der technischen Seite her hat Qt eine Menge zu bieten. Das Framework ist sehr flexibel und kann auf vielen gängigen Systemen eingesetzt werden. Neben den »großen« wie MS-Windows, Linux, Unix, BSD oder Mac OS X lässt sich Qt auch auf »kleinen« Systemen wie Handys oder PDAs einsetzen. Neben dem portablen Quellcode ist natürlich auch der Reichtum an Funktionali-
11
1542.book Seite 12 Montag, 4. Januar 2010 1:02 13
Vorwort
tät ein entscheidender Grund, Qt zu verwenden (davon wollen wir Sie mit unserem Buch noch überzeugen). Bei der gewaltigen Vielfalt, die Qt bietet, wurde trotzdem beachtet, dass sich das Framework auch einfach anwenden lässt. Auch die Dokumentation ist erste Sahne. Bei der Lizenzierung ist Qt sehr fair. Solange Sie Ihre Anwendungen im Open-Source-Bereich verwenden wollen, entstehen Ihnen keinerlei Unkosten. Mehr zur Lizenzierung erfahren Sie in Abschnitt 1.2. Natürlich könnte ich Ihnen das Blaue vom Himmel erzählen, schließlich verdiene ich ja Geld mit meinem Buch. Am besten wird aber sein, Sie überzeugen sich selbst von den Stärken des Qt-Frameworks.
Voraussetzungen für Qt Außer fundierten und guten C++-Kenntnissen mit all ihren Facetten wird eigentlich nicht allzu viel vorausgesetzt, um in die Qt-Bibliothek einzusteigen. Sollten Sie Defizite in C++ aufweisen, kann ich Ihnen (Achtung, Schleichwerbung!) wärmstens mein Buch »C++ von A bis Z« (im selben Verlag erschienen) empfehlen. Was die technische Seite angeht, genügt ein Rechner mit beliebigem Betriebssystem (Linux, Unix, Windows, Mac OS X etc.). Natürlich sind für ein Selbststudium entsprechende Disziplin und Eigenmotivation nötig. Der Lernende ist selbst für seinen Fortschritt verantwortlich, niemand wird diesen Fortschritt überwachen.
Ziel und Zielgruppe des Buches Die Zielgruppe des Buches sind eindeutig Leser, die sich die Grundlagen der C++Programmierung angeeignet haben und endlich »echte« professionelle Programme mit einer grafischen Oberfläche erstellen wollen. Es soll dem Leser helfen, sich die Grundlagen der GUI-Programmierung mit Qt zu erarbeiten. Weil das Qt-Framework noch viel mehr bietet als das Erstellen von Anwendungen mit einer grafischen Oberfläche gehen wir natürlich auch darauf ein. Das Buch wird so manchem recht umfangreich erscheinen, doch soll es kein Ersatz für die wirklich tolle Dokumentation von Qt sein (siehe Buch-DVD). Daher sollte man parallel immer die Dokumentation von Qt verwenden, die natürlich auf vieles wesentlich detaillierter eingeht, als es mit einem Buch wie dem vorliegenden überhaupt möglich ist.
12
1542.book Seite 13 Montag, 4. Januar 2010 1:02 13
Vorwort
Schnellübersicht zum Buch Die ersten fünf Kapitel behandeln die Grundlagen der Programmierung mit Qt. Sollten Sie über keinerlei Grundkenntnisse in Qt verfügen, empfehle ich Ihnen, diese fünf Kapitel der Reihe nach durchzuarbeiten. Nach einer allgemeinen Übersicht zu Qt (Kapitel 1) wird in Kapitel 2 das Signal- und Slot-Konzept von Qt behandelt, welches an Stelle der Callback-Funktionen aus anderen Frameworks verwendet wird. Kapitel 3 bietet eine Zusammenstellung der Bibliotheken und Klassen-Hierarchien von Qt. Kapitel 4 behandelt erst die Dialoge, dann folgt ein umfangreicher Überblick der Widgets von Qt mit vielen Beispielen. Die Erstellung eines Hauptfensters mit allen dazugehörigen Facetten wird in Kapitel 5 beschrieben. Kapitel 6 steht ganz im Zeichen der Daten: wie man Daten mit den Qt-StreamKlassen verwendet (Speichern, Eingabe, Ausgabe), binär ebenso wie ASCII. Neben der Speicherung von Daten in Dateien oder dem Verwenden von Verzeichnissen wird auch auf die Interprozesskommunikationen (synchron, asynchron) eingegangen. Auch die Netzwerkkommunikation (Sockets (TCP, UDP), HTTP, FTP) beschreiben wir dabei ausführlich. Des Weiteren werden auch Themen wie Multithreading oder die Verwendung des SQL-Moduls behandelt. Kapitel 7 beschreibt die Ereignisverarbeitung und stellt auch die Grundlage der beiden nächsten Kapitel dar – Drag & Drop und Zwischenablage (Kapitel 8), Grafikprogrammierung und Drucken (Kapitel 9). In Kapitel 10 gehen wir auf das XML-Modul von Qt ein (Qt unterstützt hier sowohl die SAX-API als auch die DOM-API). Was Sie bei internationalen Anwendungen beachten müssen, erklärt Kapitel 11. Kapitel 12 geht auf einzelne Features ein, die es wert sind, erwähnt zu werden. Hierzu gehören u. a. das Erstellen dynamischer Bibliotheken oder die Verwendung des Designers von Qt. Kapitel 13 behandelt dann die Entwicklungsumgebung Qt Creator und wie Sie sinnvoll damit arbeiten können.
Danksagung Ein Buch nebenbei in der Freizeit zu schreiben, ist nicht immer einfach – was besonders die Mitmenschen in meiner Umgebung zu spüren bekamen. Ein besonderer Dank gilt natürlich meiner Familie, die meine grummelige Laune wieder einmal überstanden hat. Ein Dankeschön geht wieder an Martin Conrad, der mich trotz eines Projekts nicht unter Druck gesetzt hat und geduldig mit mir war.
13
1542.book Seite 14 Montag, 4. Januar 2010 1:02 13
Vorwort
Ebenfalls ein großer Dank an Judith (Stevens-Lemoine), meine Lektorin, die mir dieses Buch-Projekt, das mir am Herzen lag, ermöglichte – mein fünftes Buch mit Judith. Alles klappte wie immer. Reibungslos. Viel Spaß beim Lesen. Zudem möchte ich mich recht herzlich bei Carsten Lehbrink von der Firma Trolltech bedanken, der mir alle meine Fragen beantwortet hat.
Jürgen Wolf
P.S.: Sollten Sie beim Einstieg mit den ersten Listings Probleme bekommen, so können Sie mich gerne per E-Mail kontaktieren (
[email protected]).
14
1542.book Seite 15 Montag, 4. Januar 2010 1:02 13
Anfang = der wichtigste Teil der Arbeit. – Platon (Wir könnten auch sagen: der Entschluss. Was Platon damit nicht sagen wollte, war: Der Anfang ist schon die halbe Miete. Das glaube ich keineswegs. Es gibt viele Leute, die packen vieles an und bringen nichts zu Ende. Mein Credo ist: Eins nach dem anderen. Zügig, entschlossen, zielorientiert. Es gibt viel zu tun – packen wir es an!)
1
Einstieg in Qt
1.1
Was ist Qt?
Ich denke, wer dieses Buch erworben hat, wird wissen, worauf er sich einlässt. Allerdings gibt es ja schließlich auch noch den Leser, der solche Kulturgüter in einem stationären Sortimentsbuchhandel erwirbt und sich zuvor die ersten Seiten durchliest. Ok, genug davon. Qt ist eine immer beliebter werdende Klassenbibliothek, die zur plattformunabhängigen Programmierung grafischer Benutzeroberflächen (kurz und englisch auch: GUI = Graphical User Interface) unter C++ verwendet wird. Verantwortlich für Qt ist die norwegische Firma Trolltech (ehemals Quasar Technologies). Die Qt-Bibliothek ist mittlerweile für die verschiedensten Betriebssysteme und Grafikplattformen wie X11 (Linux-/Unix-Derivate), Mac OS X, Windows oder auch als PDA-Version erhältlich. Anfang 2008 wurde das Unternehmen Trolltech von Nokia aufgekauft. Seitdem wird die Entwicklung unter dem Namen Qt Development Frameworks fortgeführt. Qt ist allerdings nicht nur eine Bibliothek, die zur Entwicklung von grafischen Benutzeroberflächen verwendet werden kann. Bei dieser Bibliothek handelt es sich vielmehr um ein mächtiges Framework, welches Dinge wie XML, Datenbanken, Internationalisierung, Netzwerke, Datei-Ein-/Ausgabe, Interprozesskommunikation, Multithreading und noch einiges mehr anbietet. Somit kann man sagen, dass sich mit Qt eigentlich alles machen lässt, und man keine weiteren Bibliotheken zusätzlich benötigt. Ein Rundum-sorglos-Paket eben.
15
1542.book Seite 16 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Zwar verwendet Qt eine Erweiterung der Programmiersprache C++, aber es gibt auch Implementierungen für C, C#, Java, Perl, Python und Ruby. Allerdings werden diese Erweiterungen nicht von Nokia gepflegt. Qt oder QT Qt (ursprünglich Quasar toolkit) wird stets mit einem kleinen »t« geschrieben. Davon zu unterscheiden ist QT, was für Apples Multimediasoftware QuickTime steht. Allerdings wird Qt heute nicht mehr als Kürzel für Quasar toolkit verstanden, sondern will offiziell wie das englische Worte cute ausgesprochen werden.
1.2
Lizenzierung
Es ist doch immer wieder überraschend, wie schnell sich die Uhr in der IT-Branche dreht. Während ich in der ersten Auflage an dieser Stelle noch geschrieben hatte, dass Qt unter einer dualen Lizenzierung (der GPL und der QPL) stehe, so trifft dies heute schon nicht mehr zu. Mit einer neuen Lizenzierung wurde Qt ab März 2009 unter die dritte Version der GPL (genauer LGPL) gestellt. Durch diese Lizenzierung können Sie mit Qt endlich auch ohne eine kostenpflichtige Lizenz proprietäre Software entwickeln, ohne dass Sie den Quellcode veröffentlichen müssen. Lediglich für Änderungen an Qt selbst und für den technischen Support brauchen Sie noch eine kostenpflichtige Lizenz. Sicher ist sicher Wie bereits erwähnt, wandelt sich die IT-Welt relativ schnell. Daher empfehle ich Ihnen dringend, sich mit der Lizenzierung von Qt zu befassen (unter http:// qt.nokia.com/), bevor Sie sich an die Aufgabe machen, eine eigene proprietäre Software zu entwickeln.
1.3
Qt installieren
Um die Beispiele im Buch auch tatsächlich verwenden zu können, müssen Sie Qt auf Ihrem Rechner installieren. Zunächst sollten Sie sich entscheiden, ob Sie die kommerzielle Version oder die Open-Source-Version von Qt installieren wollen. Für das Nachvollziehen der Beispiele im Buch spielt dies keine Rolle. Meine Testumgebungen für das Buch Die Listings im Buch wurden erfolgreich auf Windows XP, Windows Vista, Windows 7, Linux (Ubuntu 9.10) und Mac OS X (10.6) getestet.
16
1542.book Seite 17 Montag, 4. Januar 2010 1:02 13
Qt installieren
Zum Zeitpunkt der Drucklegung dieses Buches war die Qt-Version 4.6 aktuell. Wer die Entwicklung von Qt beobachtet, wird feststellen, dass hier recht viel Bewegung drin ist. Eine Version 5.x ist jedoch im Moment noch nirgendwo angedacht. Alle Versionen sollen allerdings abwärtskompatibel sein, so dass es immer möglich ist, bereits erstellten Code in zukünftigen Programmversionen zu verwenden. Wer den Schritt von Version 3.x zu 4.x mitbekommen hat, wird festgestellt haben, dass die Kompatibilitätszusage dabei gebrochen wurde, weil sich dies aus architektonischen Gründen nicht vermeiden ließ. Allerdings wird ein solcher Kompatibilitätsbruch nur dann in Kauf genommen, wenn überhaupt keine andere Lösung mehr offensteht. Die aktuellste Version von Qt beziehen Sie am besten von der Webseite selbst (http://qt.nokia.com/). Hier finden Sie auch weitere Produkte wie z. B. Qt für Symbian S60, mit dem Sie Anwendungen für Symbian-Systeme (Mobiltelefone etc.) erstellen können. SDK auf der Buch-DVD Auf der Buch-DVD haben wir Ihnen die zum Zeitpunkt der Drucklegung des Buches aktuellste Open-Source-Version des Qt-SDKs zur bequemen Installation gleich mitgeliefert. Neben der Bibliothek umfasst das Qt-SDK auch eine Menge unverzichtbarerer Demos (Qt-Demos), Qt Assistant, das als Referenz und Dokumentation unverzichtbar ist, wenn Sie sich mit Qt befassen wollen, eine komplette Entwicklungsumgebung namens Qt Creator mit Designer (RAD-Tool) und Linguist (für die Lokalisierung). Es gibt das Qt Framework auch separat, also beschränkt auf die benötigten Bibliotheken zum Download, für den Fall, dass Sie z. B. Microsoft Visual Studio oder Eclipse zur Entwicklung Ihrer Qt-Anwendungen verwenden wollen. Wie Sie Qt in andere Entwicklungsumgebungen einbinden können, wird aber in diesem Buch nicht beschrieben. Hier wird lediglich gezeigt, wie Sie mit dem hauseigenen Qt SDK Anwendungen entwickeln können. Alles andere würde den Rahmen des Buches sprengen. Auf der Webseite http://qt.nokia.com/downloads finden Sie übrigens auch ein Visual-Studio-Add-in und eine Eclipse-Integration zum Download.
1.3.1
Linux
In der Praxis empfiehlt es sich, bei Linux immer die vorkompilierten Pakete der jeweiligen Distributionen zu verwenden, welche häufig mitgeliefert und nachträglich über den entsprechenden Paketmanager nachinstalliert werden können. Sinnvoll ist, die neuesten Pakete der entsprechenden Distribution online zu beziehen.
17
1.3
1542.book Seite 18 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Um eine komplette Qt-Umgebung mithilfe eines Paketmanagers zu erstellen, benötigen Sie auf jeden Fall die Pakete libqt4-core, libqt4-dev und qt-dev-tools. Mit dieser Qt-Umgebung könnten Sie bereits Anwendungen in der Kommandozeile übersetzen. Anschließend können Sie die Entwicklungsumgebung Qt Creator, genauer das Paket qt-creator, aus den Quellen nachinstallieren. Alternativ können Sie auch das SDK (eine BIN-Datei) von der Buch-DVD verwenden oder aus dem Web herunterladen. Beachten hierbei, dass Sie die Installation noch ausführbar (chmod u+x) machen müssen. Der Vorteil des fertigen SDKs ist ein typischer Installer mit einer grafischen Oberfläche, wie man sie beispielsweise von Windows-Betriebssystemen her kennt.
1.3.2
Mac OS X
Die Installation des SDKs beim Mac gestaltet sich ebenfalls recht einfach. Auch hier genügt ein einfaches Anklicken des DMG-Images von der Buch-DVD, oder Sie laden sich die neueste Version aus dem Web herunter. Anschließend brauchen Sie nur noch den Anweisungen auf dem Bildschirm zu folgen. Gewöhnlich wird das komplette SDK beim Mac ins Wurzelverzeichnis Developer/Applications/Qt installiert. Xcode Tools für Mac OS X Zusätzlich müssen unter Mac OS X noch die Xcode Tools von Apple installiert sein. Diese liegen gewöhnlich Ihrer Apple-Installations-DVD bei oder können bei Apple (http://developer.apple.com/TOOLS/Xcode/) kostenlos heruntergeladen werden.
1.3.3
MS-Windows (XP/Vista/Windows 7)
Für Windows finden Sie wie gewöhnlich einen kompletten Installer vor, der Ihnen alles automatisch installiert. Glücklicherweise muss man sich bei der Installation des SDKs nicht mehr um den Compiler und dessen systemweite Konfiguration kümmern. Qt verwendet beim SDK den MinGW-Compiler unter Windows. Am Ende der Installation sollten Sie im Start-Menü einen neuen Ordner Namens Qt SDK by Nokia v2010.01 (Open Source) vorfinden, in dem sich die Kommandozeile zum Übersetzen der Anwendungen, die Entwicklungsumgebung Qt Creator, die Demos (Beispiele), Qt Assistant (Dokumentation, Referenz) und Qt Linguist befinden.
18
1542.book Seite 19 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
1.4
»Hallo Welt« mit Qt
Ich werde Ihnen zwei Möglichkeiten zeigen, wie Sie Ihre Software mit Qt erstellen können: einerseits mittels der Kommandozeile und andererseits mittels der Entwicklungsumgebung Qt Creator. Bei den Beispielen, die im Buch vorkommen, reicht für das Bauen der Anwendungen die Kommandozeile im Grunde völlig aus. In der Praxis werden Sie allerdings mit einer Entwicklungsumgebung wie Qt Creator erheblich effektiver, schneller und vor allem mit weniger Fehlern ans Ziel kommen. RAD-Tool versus Kommandozeile Das Buch wurde so konzipiert, dass Sie die Beispiele unabhängig von einer Entwicklungsumgebung erstellen können. Zwar umfasst das SDK auch eine hammermäßige Entwicklungsumgebung mit integriertem RAD-Tool, aber häufig wird der Einsteiger durch die Vielzahl der Funktionen förmlich erschlagen. Da Entwicklungsumgebungen mit RAD-Tools aber der Garant für schnelles und zuverlässiges Entwickeln sind, widmet sich Kapitel 13 ausschließlich Qt Creator. Gerne können Sie nach dem ersten Kapitel in jenes Kapitel springen und erst danach mit dem zweiten Kapitel fortfahren.
1.4.1
»Hallo Welt« mit der Kommandozeile
Sie benötigen keine Entwicklungsumgebung oder sonstige Software, um Programme mit dem Qt Framework zu erstellen. Es genügt, dass Sie Qt –genauer gesagt die Bibliothek – richtig installiert haben. Ein einfacher ASCII-Editor reicht aus, um den Quelltext für die Programme zu erstellen. Besser wäre natürlich ein Editor, der die Syntax von C++ hervorhebt. Da das kein Einsteiger-Buch für C++Programmierer ist, gehe ich davon aus, das Sie wissen, was man verwendet um einen Quelltext zu erstellen und diesen abzuspeichern. Hier folgt zunächst der Quellcode zum Hallo-Welt-Programm mit Qt. Im Anschluss wird er näher erläutert. 01 // beispiele/hallo/main.cpp 02 #include 03 #include 04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 QPushButton hello("Hallo Welt"); 07 hello.resize(100, 30); 08 hello.show(); 09 return app.exec(); 10 }
19
1.4
1542.book Seite 20 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Der Kommentar in Zeile 1 zeigt an, wo Sie den Quelltext auf der Buch-DVD wiederfinden. Für dieses Beispiel heißt das: Ausgehend vom Wurzelverzeichnis des CD- bzw. DVD-Laufwerks (Bezeichnung ist abhängig vom System) finden Sie diesen Quellcode im Verzeichnis beispiele/hallo unter dem Dateinamen main.cpp. Sie finden diese erste Zeile bei jedem Beispiel des Buches, sodass Sie es schnell auf der Buch-DVD finden. In Zeile 2 und 3 fügen wir die entsprechenden Headerdateien für die im Beispiel verwendeten Qt-Klassen ein. Im Beispiel benutzen wir die Klassen QApplication und QPushButton. Dem neuen C++-Standard gemäß verzichtet auch Qt auf die Dateinamenserweiterung .h bei den Headerdateien. Genau genommen enthalten die verwendeten Headerdateien im Code nur eine Include-Zeile zur passenden .h-Datei. So beinhaltet die Headerdatei QApplication lediglich folgende Zeile: // Headerdatei: QApplication #include "qapplication.h"
In Zeile 4 finden Sie die altbekannte Hauptfunktion main() mit ihren zwei Argumenten, den Kommandozeilen-Argumenten. argc entspricht hierbei der Anzahl der Argumente und argv ist ein String-Array mit Argumenten der Kommandozeile. Dies sind beides Eigenschaften von Standard-C++, weshalb hier nicht näher darauf eingegangen werden soll. Die beiden Argumente benötigen wir in Zeile 5, wo ein QApplication-Objekt angelegt wird. Das QApplication-Objekt wird für jede Qt-Anwendung benötigt, die eine grafische Oberfläche verwendet. In Zeile 6 wird ein QPushButton-Objekt mit dem Text »Hallo Welt« darauf erzeugt. Aus dem Namen der Klasse lässt sich schon herauslesen, dass es sich hierbei um einen Button (zu deutsch: Schaltfläche) handelt. In Zeile 7 legen wir die Größe der Schaltfläche mit der Methode resize() fest. Und damit die Schaltfläche auch angezeigt wird, muss in der Zeile 8 die Methode show() aufgerufen werden. Der eigentliche Start der Anwendung geschieht in Zeile 9 mit dem Aufruf der Methode exec(). Mit diesem Aufruf starten Sie eine Ereignisschleife (englisch: Event-Loop). Diese Schleife wartet auf Ereignisse der Anwendung (z. B. Mausoder Tastaturereignisse) und leitet diese an die entsprechende Klasse weiter. In diesem Beispiel wurde allerdings nichts eingerichtet, um auf ein Ereignis zu reagieren. Trotzdem wird, wenn Sie die Anwendung beenden, die quit()-Methode an QApplication gesendet. Wird quit() aufgerufen, dann beendet sich die Ereignisschleife, und auch das Programm wird beendet.
20
1542.book Seite 21 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
Ein ausführbares Qt-Programm erstellen Ich denke, das kleine Beispiel von gerade eben haut keinen von den Socken. Auch wird darin noch nicht im Detail auf die einzelnen Klassen und deren Methoden eingegangen. Dazu werden wir im Verlauf des Buches noch mehr Gelegenheit haben, als uns lieb ist. Um aus der Quelldatei ein ausführbares Programm zu machen, muss man sich vor Augen halten, dass es verschiedene Systeme gibt, bei denen die Übersetzung jeweils unterschiedlich abläuft. Ein Beispiel gefällig, wie eine solche wüste Übersetzung unter Windows aussehen könnte? C:\Qt\hallo > g++ -c -O2 -O2 -frtti -fexceptions –Wall -DUNICODE -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_THREAD_SUPPORT -DQT_NEEDS_QMAIN -I"C:/Qt/4.6.0/include/QtCore" -I"C:/Qt/4.6.0/include/QtCore" -I"C:/Qt/4.6.0/include/QtGui" -I"C:/Qt/4.6.0/include/QtGui" -I"C:/Qt/4.6.0/include" -I"." -I"C:/Qt/4.6.0/include/ActiveQt" -I"tmp\moc\release_shared" -I"." -I"..\mkspecs\win32-g++" –o tmp\obj\release_shared\main.o main.cpp C:\Qt\hallo > g++ -mthreads -Wl,-enable-stdcall-fixup -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc -Wl,-s -Wl,-s -Wl,-subsystem,windows -o "release\hallo.exe" tmp\obj\release_shared\main.o -L"c:\Qt\4.6.0\lib" -L"c:\Qt\4.6.0\lib" -lmingw32 -lqtmain -lQtGui4 -lQtCore4
Unter den verschiedenen Linux-Systemen und Mac OS X sind die Pfade und teilweise auch die Schalter wiederum anders. Zum Glück hat sich Trolltech unserer erbarmt und uns mit qmake ein Tool zur Verfügung gestellt, welches zur plattformunabhängigen Projekterstellung dient. Mit qmake wird eine Makefile gebaut, das alle nötigen Informationen enthält, um den Quelltext für die entsprechende Plattform zu übersetzen. Umgebungsvariable PATH und Kommandozeile Unter Windows sollten Sie die Kommandozeile QT COMMAND PROMPT verwenden, weil hierbei gleich sämtliche Umgebungsvariablen gesetzt sind, die Sie ansonsten selbst in der Umgebungsvariablen PATH setzen müssten.
21
1.4
1542.book Seite 22 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Wechseln Sie also zunächst in den Pfad, unter dem Sie die Quelldatei main.cpp gespeichert haben, und führen Sie qmake folgendermaßen aus: $ > cd hallo $ > qmake -project
Mit dem Schalter -project erzeugen Sie eine Projektdatei hallo.pro. Diese Projektdatei hat folgenden Inhalt: ################################## # Automatically generated by qmake ################################## TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . # Input SOURCES += main.cpp
Der Eintrag TEMPLATE gibt an, ob eine Applikation (app) oder eine Bibliothek (lib) erstellt werden soll. Zusätzlich könnte man hier auch noch ein Plugin (plugin) erstellen. Welche Dateien zum Projekt gehören, wird mit SOURCES angegeben. Die Projektdatei hallo.pro genügt, um auf dem jeweiligen System ein Makefile zu bauen. Hierzu ist ein einfacher Aufruf von qmake ausreichend: $ > qmake
Unter Windows werden hiermit drei Makefiles (Makefile, Makefile.Debug und Makefile.Release) generiert, wobei Makefile als Meta-Datei auf die beiden anderen Makefiles verweist. Auf Linux/Unix und Mac OS X hingegen wird nur die ein Makefile erzeugt. Soll nur eine Debug-Version des Makefiles gebaut werden, brauchen Sie in der Projektdatei hallo.pro lediglich folgende Zeile hinzuzufügen: CONFIG += debug_and_release
Debug-Version von Qt Um die Debug-Version von Qt wirklich verwenden zu können, muss diese auch installiert sein. Bei Linux kann es sein, dass Sie die Bibliothek nachinstallieren müssen. Ihre Kennzeichnung ist zumeist ein Paketname wie qt-debug, libqt4-debug oder libqt4-debug-dev.
22
1542.book Seite 23 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
Mac OS X, Makefile und X-Code-Projektdatei Je nach Einstellungen kann es sein, dass beim Mac mit qmake lediglich eine native X-Code-Projektdatei (*.xcodeproj) erzeugt wird und somit das anschließende make seinen Dienst verweigert bzw. keine Arbeit findet. Zwar können Sie jetzt durch Anklicken der X-Code-Projektdatei das Beispiel mit Xcode übersetzen. Wenn Sie dies jedoch nicht wollen, müssen Sie statt eines leeren qmake noch die Option für macx-g++ setzen, damit ein echtes Makefile gebaut wird. Statt qmake müssen Sie also eintippen: qmake –spec macx-g++. Dann klappt es auch anschließend mit dem makeAufruf.
Jetzt können Sie make ohne weitere Argumente aufrufen, und die Qt-Anwendung wird übersetzt. Wollen Sie gezielt eine Debug-Version erstellen, müssen Sie make folgendermaßen einsetzen: $ > make debug
Natürlich lässt sich hierbei auch gezielt eine Release-Version erstellen: $ > make release
Jetzt finden Sie die entsprechende Version im Projekt-Verzeichnis hallo unter dem Verzeichnis debug oder/und release bereit zur Ausführung. Windows, make und MinGW Unter Windows müssen Sie eventuell statt make den Namen mingw32-make eingeben, sofern Sie den MinGW-Compiler verwenden.
Das Programm starten Das Programm kann jetzt durch das einfache Anklicken der ausführbaren Datei gestartet werden. Natürlich lässt sich das Programm auch über die Kommandozeile starten, was viele Linux-Anwender nach wie vor bevorzugen. DLL-Hölle unter Windows Sofern Sie die Qt-Beispiele unter MS-Windows aus jedem beliebigen Verzeichnis heraus mit einem Mausklick ausführen wollen, müssen natürlich auch die entsprechenden DLLs von Qt für das Programm vorhanden sein. Die DLLs finden Sie gewöhnlich im bin-Verzeichnis von Qt (z. B. C:\Qt\2009.04\qt\bin). Sollten Sie einen Fehlermeldung bekommen, wie »der Einsprungspunkt von Qt…dll konnte nicht gefunden werden«, dann kopieren Sie einfach sämtliche DLLs ins Systemverzeichnis von MS-Windows (z. B. in C:\Windows\system), falls dies bei der Installation nicht schon passiert ist.
23
1.4
1542.book Seite 24 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Und so sieht das erste Beispiel bei der Ausführung aus:
Abbildung 1.1
»Hallo Welt« mit Qt unter Windows 7
Abbildung 1.2
»Hallo Welt« mit Qt unter Linux (Ubuntu 9.04)
Abbildung 1.3
»Hallo Welt« mit Qt unter Mac OS X (10.6 )
1.4.2
»Hallo Welt« mit Qt Creator
Das Hallo-Welt-Listing lässt sich natürlich noch einfacher und schneller mit Qt Creator, der Entwicklungsumgebung von Qt, erstellen. Starten Sie zuerst Qt Creator.
Abbildung 1.4
24
Die Entwicklungsumgebung Qt Creator nach dem Start
1542.book Seite 25 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
1. Wählen Sie jetzt beim Willkommensbildschirm von Qt Creator den Reiter Develop aus. Hier finden Sie auch gleich die Schaltfläche Create New Project... , welche Sie anklicken.
Abbildung 1.5
Über den Willkommensbildschirm können Sie ein neues Projekt anlegen.
2. Im nächsten Dialog müssen Sie die Art des neuen Projektes auswählen. In unserem Fall reicht die einfachste Variante, Empty Qt4 Project, vorerst völlig aus. Weiter geht es, indem Sie auf OK klicken.
Abbildung 1.6
Art des neuen Projekts auswählen
3. Im nächsten Dialog werden Sie nach dem Namen und Speicherort des neuen Projekts gefragt. Der Name ist hierbei gleichzeitig auch der Verzeichnisname des Projekts. Weiter geht es mit der Schaltfläche Next.
25
1.4
1542.book Seite 26 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
Abbildung 1.7
Vergabe des Projektnamens und Speicherorts
4. Im nächsten Dialog Project management können Sie bei einem leeren Projekt nichts mehr ändern. Sie bekommen hierbei lediglich noch den Hinweis, dass eine Projektdatei (hier halloWelt.pro) zum Projekt hinzugefügt wurde. Klicken Sie auf die Schaltfläche Finish, und es wird das neue Projekt halloWelt im Editor-Modus angezeigt. Im Projektverzeichnis links oben befindet sich im Augenblick nur die Projektdatei halloWelt.pro. 5. Klicken Sie jetzt auf das Projektverzeichnis halloWelt mit der rechten Maustaste und wählen Sie im Kontextmenü Add New... aus. Alternativ können Sie hierbei natürlich auch gleich die Quelldatei über Add Existing Files... hinzufügen, wenn Sie diese bereits erstellt haben bzw. wenn diese bereits vorhanden ist (z. B. auf der Buch-DVD).
Abbildung 1.8 Über einen rechten Mausklick auf das Projektverzeichnis öffnen Sie viele neue Kommandos.
26
1542.book Seite 27 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
6. Im sich öffnenden Dialog können Sie jetzt auswählen, was für eine Art von Datei Sie zum Projekt hinzufügen wollen. Im Beispiel reicht uns ein C++ Source File aus.
Abbildung 1.9
Art der zu erstellenden Datei auswählen
7. Jetzt können Sie den Dateinamen der Quelldatei eingeben und gegebenenfalls einen anderen Speicherort auswählen, wenn Sie mit der Standardeinstellung nicht einverstanden sind. Weiter geht es mit der Schaltfläche Next.
Abbildung 1.10
Name der Quelldatei und das Verzeichnis auswählen
27
1.4
1542.book Seite 28 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
8. Im letzten Dialog Project managment sollten Sie das Häkchen vor Add to project gesetzt lassen. Auch die Einstellungen der Projektdatei sollten Sie hier so lassen, wie sie sind. Mit einem Klick auf die Schaltfläche Finish schließen Sie das Hinzufügen einer neuen Datei ab.
Abbildung 1.11
Im Projektmanagement sollten Sie die Einstellungen belassen, wie sie sind.
Weitere Dateien zum Projekt hinzufügen So wie Sie in Schritt 5 bis 8 vorgegangen sind, können Sie auch verfahren, wenn Sie anschließend in den Beispielen weitere Quell- oder Headerdateien hinzufügen. In Schritt 6 wählen Sie dabei aus, ob es sich um eine Headerdatei (*.h) oder um eine Quelldatei (*.cpp) handelt.
Die vorgegebene(n) Zeile(n) der sich jetzt im Editor öffnenden Datei main.cpp können Sie entfernen und das klassische Hallo-Welt-Programm eintippen, wie Sie es in Abschnitt 1.4.1 bei der Methode via Kommandozeile kennengelernt haben. Nachdem Sie die Datei gespeichert haben (z. B. mit (Strg)+(S)), können Sie diese mit der kleinen, dreieckigen, grünen Schaltfläche bzw. (Strg)+(R) übersetzen und starten. Windows und seine DLLs – wie immer ... Natürlich gilt auch hier: Sofern Sie die Qt-Beispiele unter MS-Windows aus einem Verzeichnis heraus mit einem Mausklick ausführen wollen, müssen die entsprechenden DLLs von Qt für das Programm vorhanden sein. Sollten Sie eine Fehlermeldung bekommen, wie »der Einsprungspunkt von Qt…dll konnte nicht gefunden werden«, dann kopieren Sie doch einfach sämtliche DLLs ins Systemverzeichnis von MS-Windows (z. B. in C:\Windows\system), falls dies bei der Installation nicht schon passiert ist.
28
1542.book Seite 29 Montag, 4. Januar 2010 1:02 13
»Hallo Welt« mit Qt
Abbildung 1.12
1.4.3
Das »Hallo-Welt«-Listing nach der Übersetzung und bei der Ausführung
Troubleshooting: »Bei mir werden keine Icons angezeigt«
Betrachten Sie diesen Abschnitt als eine Art Vorschau darauf, welche Probleme Sie beim Starten von Anwendungen bekommen können. Wenn Sie das in der Überschrift dieses Abschnitts genannte Problem später ereilen sollte, blättern Sie einfach hierher zurück. Unter Linux/Unix macht es in diesem Zusammenhang einen Unterschied, ob Sie das Programm von der Kommandozeile oder mit einem Mausklick auf das Icon starten, da es sich hierbei um zweierlei Umgebungen mit unterschiedlichen Werten handelt (vor allem mit unterschiedlichen Arbeitsverzeichnissen). Der Unterschied macht sich erst bemerkbar, wenn Sie externe Dateien wie z. B. Grafiken im Programm verwenden und dynamisch zur Laufzeit nachladen wollen. Was beim Starten aus der Kommandozeile noch angezeigt wurde, wird beim Mausklick auf die Anwendung nicht mehr angezeigt, weil hier der Wert für das Arbeitsverzeichnis ein anderer ist. Ein Icon fügen Sie zu einem Button wie folgt hinzu: button->setIcon("./images/icon.png");
29
1.4
1542.book Seite 30 Montag, 4. Januar 2010 1:02 13
1
Einstieg in Qt
In diesem Beispiel wird davon ausgegangen, dass sich das Icon im aktuellen Arbeitsverzeichnis in einem Verzeichnis namens images befindet. Dies trifft zu, wenn Sie, wie bereits erwähnt, das Programm aus der Kommandozeile heraus starten. Beim Mausklick ist das aktuelle Arbeitsverzeichnis hingegen nicht dasselbe, und daher kann logischerweise das Icon nicht mehr angezeigt werden. Ein portabler und möglicher Weg, das Problem zu umgehen, ist es, das aktuelle Arbeitsverzeichnis direkt im Programm mit anzugeben und den Pfad zur Grafik hinzuzufügen. Sie ermitteln quasi zur Laufzeit des Programms den absoluten Pfad zum Arbeitsverzeichnis. So etwas wird mit der statischen Methode QCoreApplication::applicationDirPath() ermittelt. Zusammengebastelt würde ein solcher zur Laufzeit angegebener dynamischer Pfad wie folgt aussehen (machen Sie sich hier keine Gedanken über den Code): button01->setIcon( QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon.png") ) );
Wenn Sie Grafiken (oder auch andere Dateien) nicht zur Laufzeit laden wollen, bietet der Ressourcenmechanismus von Qt eine alternative Vorgehensweise an. Dieser Mechanismus wird in Abschnitt 12.7, »Das Qt-Ressourcensystem«, beschrieben.
30
1542.book Seite 31 Montag, 4. Januar 2010 1:02 13
Wer sich schon mit der GUI-Programmierung befasst hat, wird bei dieser Überschrift an sogenannte Callback-Funktionen denken. Doch Qt geht hier einen anderen Weg und verwendet ein Signal-Slot-Konzept. Wie dieses genau funktioniert, wollen wir im folgenden Kapitel näher beschreiben.
2
Signale und Slots
Im Grunde verläuft die GUI-Programmierung immer nach demselben Prinzip: Wenn sich bspw. ein Widget verändert hat (bspw. bei Anklicken eines Buttons), will man ein anderes Widget darüber informieren. Widgets werden also im Programm mit einer Funktion verknüpft, die ausgeführt wird, sobald der Anwender dieses etwa durch einen Mausklick aktiviert hat. Drückt der Anwender z. B. auf einen »Öffnen«-Button, wird eine entsprechende Funktion aufgerufen, die ggf. einen neuen Dialog zum Öffnen einer Datei präsentiert. Widgets Das Wort ins Deutsche zu übersetzen ist wohl wenig sinnvoll (Widget = »Dingsbums«). Als Widgets (Fensterkontrollelemente) bezeichnet man alle Interaktions- bzw. Steuerelemente in grafischen Benutzeroberflächen. Abhängig von der GUI-Bibliothek gibt es hierbei einfache Widgets, woraus sich der Programmierer seine eigenen Dialoge zusammenbasteln kann. Häufig findet man auch komplexere Widgets wie bspw. einen fertigen Dialog zum Öffnen/Speichern einer Datei.
Viele grafische Toolkits verwenden zur Kommunikation zwischen den Widgets oft eine Callback-Funktion. Ein solcher Callback ist nichts als ein simpler Zeiger auf eine Funktion. Allerdings haben solche Callbacks zwei kleine Makel. Zum einen sind sie nicht typensicher (man ist nie sicher, dass die gerade ausführende Funktion den Callback mit den richtigen Argumenten aufruft). Zweitens ist der Callback mit der auszuführenden Funktion fest verbunden, weil die auszuführende Funktion wissen muss, welcher Callback aufgerufen werden soll. Mit dem Signal-und-Slot-Konzept geht Qt hier einen etwas anderen Weg. Dieses Konzept hat den Vorteil, dass Qt die Verbindung automatisch trennt, wenn eines der kommunizierenden Objekte zerstört wird. Dadurch vermeidet man viele Abstürze, weil Versuche, auf ein nichtvorhandenes Objekt zuzugreifen, nicht mehr möglich sind.
31
1542.book Seite 32 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
Am einfachsten lässt sich das Signal-und-Slot-Konzept an unserem »Hallo Welt«Beispiel beschreiben. Das Beispiel wollen wir nun erweitern, indem wir beim Anklicken des Buttons die Anwendung beenden. 01 // beispiele/signalslot1/main.cpp 02 #include 03 #include 04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 QPushButton hello("Ende"); 07 hello.resize(100, 30); 08 hello.show(); 09 QObject::connect( &hello, SIGNAL( clicked() ), &app, SLOT( quit() ) ); 10 return app.exec(); 11 }
Verglichen mit dem ersten Beispiel wurde hier, abgesehen von einer anderen Bezeichnung des Buttons, mit der Zeile 9 eine neue statische Methode aufgerufen. Und zwar connect() von der Klasse QObject. connect() von Qt und Berkley Die Funktion connect() von der Klasse QObject hat nichts mit der gleichnamigen Socketfunktion von Berkeley zu tun. connect() ist eine statische (static) Methode, die eine Verbindung zwischen einem Signal und einem Slot herstellt. Hierzu die genaue Syntax: bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoCompatConnection ) [static]
Mit den ersten beiden Argumenten (sender, signal) bezeichnet man das Objekt, das ein Signal aufnimmt und an den Empfänger sendet – das Sender-Objekt also. Die beiden anderen Argumente (receiver, method) legen das Objekt des Empfängers des entgegenzunehmenden Slots fest. Genauer gesagt: Empfängt das Objekt sender das Signal signal, wird es an den Slot gebunden. Dies nimmt das Objekt receiver vom Slot entgegen und führt method aus. Auf die folgende Zeile bezogen, heißt das:
32
1542.book Seite 33 Montag, 4. Januar 2010 1:02 13
Signale und Slots ermitteln
QObject::connect( &hello, SIGNAL( clicked() ), &app, SLOT( quit() ) );
Hiermit verbinden Sie das Objekt hello von der Klasse QPushButton mit dem Objekt app von der Klasse QApplication. Sollte beim Button das Signal clicked() eingehen, legen Sie als Aktion fest, dass bei der Klasse QApplication die Methode quit() ausgeführt wird, was in diesem Fall das Ende des Programms bedeuten würde (siehe Abbildung 2.1).
QPushButton hello
QApplication app
clicked()
quit()
Abbildung 2.1
Zwei Objekte verbinden
Die Funktion QObject::connect() gibt bei erfolgreicher Verbindung true, ansonsten false zurück. Außerdem müssen Sie die Makros SIGNAL() und SLOT() verwenden, wenn Sie das Signal und die Methode festlegen wollen, weil für die beiden Argumente eine Zeichenkette vorgesehen und diese beiden Makros dafür sorgen, dass eine korrekte Zeichenkette eingesetzt wird. Letztes Argument von connect() Das letzte Argument type von QObject::connect() ist optional und beschreibt den Typ der Verbindung, auf dem diese aufbaut.
Somit handelt es sich bei den Slots um ganz normale Methoden einer Klasse die auf Signale einer anderen Klasse reagieren, wobei die Signale im Grunde wiederum nur einfache Methoden einer Klasse sind. Natürlich ist es auch möglich, einen Sender mit mehreren Empfängern und umgekehrt zu verknüpfen. Ist bspw. ein Signal mit zwei oder mehreren Slots verbunden, werden die Slots der Reihe nach ausgeführt, so wie sie im Code geschrieben wurden. Jede Unterklasse von QObject kann somit solche Signale und Slots definieren.
2.1
Signale und Slots ermitteln
Sicherlich stellt sich für Sie jetzt die Frage: Woher kommen die Signale und Slots? Hierzu sind tiefere Kenntnisse von Qt und deren Klassen von Vorteil, aber die einzelnen Signale und Slots kann man unmöglich auswendig kennen. Für solche
33
2.1
1542.book Seite 34 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
Fälle ist es ratsam, die Dokumentation von Qt zu verwenden, welche bei der QtDistribution mitinstalliert wurde (siehe Abbildung 2.2). Um die Dokumentation von Qt zu verwenden, müssen Sie Qt Creator (oder Qt Assistant) ausführen. Über das Menü Help können Sie mit Contents die vorhandenen Referenzen auflisten. Schneller können Sie die Referenzen beim Qt Creator mit (Strg)+(5) anzeigen.
Abbildung 2.2 Die Dokumentation von Qt mit vielen Tutorials ist bestens für eine intensive Recherche geeignet.
Wollen Sie bspw. ermitteln, welche Signale bei der Klasse QPushButton auftreten können, müssen Sie zunächst im Indexverzeichnis von Qt Assistant nach der entsprechenden Klasse suchen (siehe Abbildung 2.3). Das Indexverzeichnis können Sie über das Menü Help mit Index aufrufen.
Abbildung 2.3 Mithilfe des Indexverzeichnisses auf der Suche nach der entsprechenden Klasse
34
1542.book Seite 35 Montag, 4. Januar 2010 1:02 13
Signale und Slots ermitteln
Als Ergebnis erhalten Sie die Klassen-Referenz von QPushButton mit all ihren Eigenschaften, Funktionen, Signalen und Slots. Wichtig hierbei ist auch der Inhalt von vererbten Mitgliedern (siehe Abbildung 2.4).
Abbildung 2.4
Die Klassen-Referenz von QPushButton
Bei der Durchsicht der Klassen-Referenz von QPushButton fällt auf, dass sich hier zwar öffentliche Slots befinden, aber keine direkten Signale. Erst bei den geerbten Mitgliedern finden Sie einen Eintrag (mit Verweis) wie bspw. 4 signals inherited from QAbstractButton. Also folgen Sie dem Link zur Klasse QAbstractButton und siehe da, wir finden die gewünschten Signale, die ja auch an QPushButton weitervererbt wurden (da public). Neben clicked() finden Sie hier auch noch die Signale pressed(), released() und toogled(). Des Weiteren finden Sie hier nochmals zwei weitere Signale, die jeweils von QWidget und QObject geerbt wurden (siehe Abbildung 2.5).
Abbildung 2.5
Vererbte Signale von QAbstractButton an QPushButton
35
2.1
1542.book Seite 36 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
Wollen Sie noch mehr zu dem Signal erfahren, folgen Sie wieder einfach dem entsprechenden Verweis. Im Beispiel zuvor haben wir auch einen Slot (quit()) der Klasse QApplication verwendet. Um auch hier den oder die entsprechenden Slot(s) einer Klasse zu ermitteln, gehen Sie genauso vor wie eben bei den Signalen beschrieben. Auf der Klassen-Referenz von QApplication finden Sie dann unter dem Eintrag »Public Slots« die entsprechenden Slots (siehe Abbildung 2.6).
Abbildung 2.6
Klassen-Referenz von QApplication
Zwar sind hier sieben vorhandene Slots definiert, aber der Slot quit() ist nicht direkt in der Klasse QApplication definiert. Auch hier wurde der Slot quit() von einer anderen Klasse, hier von QCoreApplication, geerbt. Dies können Sie aus dem entsprechenden Verweis 1 public slot inherited from QCoreApplication entnehmen.
Abbildung 2.7
In der Referenz von QcoreApplication haben wir den Slot quit() gefunden.
Die Verwendung der Referenz von Qt ist unerlässlich für jeden ernsten Qt-Programmierer. Das Prinzip ist immer dasselbe: Man sucht nach der entsprechenden Klasse (Widget) im Indexverzeichnis und dann eben nach dem entsprechenden Signal, Slot, der Eigenschaft oder eben der entsprechenden Methode, die man mit einem Widget ausführen will. Hierzu soll ein weiteres Beispiel erstellt werden, das folgendes Signal-Slot-Konzept verwendet (siehe Abbildung 2.8).
36
1542.book Seite 37 Montag, 4. Januar 2010 1:02 13
Signale und Slots ermitteln
QPushButton but1
QApplication app
clicked()
aboutQt()
quit() QPushButton but2
clicked() QWidget win QPushButton but3 showNormal() clicked() showMaximized()
QPushButton but4
showMinimized()
clicked()
Abbildung 2.8
Signale und Slots verbinden
Die Zeichnung liest sich einfach. Auf der linken Seite wurden alle Objekte angegeben, die ein Signal empfangen und es an die Objekte der rechten Seite senden. Auf der linken Seite befinden sich also die Signale (mit deren Klassen) und auf der rechten Seiten die Slots (mit deren Klassen). Das Beispiel verwendet vier Buttons der Klasse QPushButton, ein Widget namens QWidget, welches den Rahmen darstellt, und natürlich die QApplication-Klasse, ohne die keine GUI-Anwendung auskommt. Der Button but1 wurde hier mit zwei Slots verbunden. Erhält dieser Button das Signal clicked(), wird zunächst der Slot aboutQt() der Klasse QApplication ausgeführt und anschließend der Slot showNormal() von der Klasse QWidget. aboutQt() ist eine einfache Nachrichtenbox über Qt und showNormal() entspricht dem Systemmenüeintrag Wiederherstellen des Fensters. Wurde bspw. die Größe des Fensters verändert, sorgt dieser Slot dafür, dass das Fenster wieder in die ursprüngliche Größe versetzt wird. Zwei weitere Buttons wurden ebenfalls mit Slots der Klasse QWidget verbunden. Erhält der Button but2 das Signal clicked(), wird bei QWidget der Slot
37
2.1
1542.book Seite 38 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
showMaximized() ausgeführt. Hierbei wird das Fenster in der Größe des Desktops maximiert. Selbiges gilt für Button but3, nur dass hierbei das Fenster minimiert wird. Der letzte Button (but4) wurde wieder mit QApplication und dessen Slot quit() verbunden. Empfängt also but4 das Signal clicked(), wird die Anwendung beendet. Im Folgenden das komplette Listing zu Abbildung 2.8: 00 01 02 03 04
// beispiele/signalslot2/main.cpp #include #include #include #include
05 int main(int argc, char *argv[]) { 06 QApplication app(argc, argv); 07 QWidget* win = new QWidget; 08 QVBoxLayout* layout = new QVBoxLayout(win); 09 QPushButton* but1 = new QPushButton("Wiederherstellen"); 10 QPushButton* but2 = new QPushButton("Maximieren"); 11 QPushButton* but3 = new QPushButton("Minimieren"); 12 QPushButton* but4 = new QPushButton("Schließen"); 13 14 15 16 17
// Größe der Buttons but1->resize(100, 30); but2->resize(100, 30); but3->resize(100, 30); but4->resize(100, 30);
18 19 20 21 22
// Widgets zur vertikalen Box hinzufügen layout->addWidget(but1); layout->addWidget(but2); layout->addWidget(but3); layout->addWidget(but4);
23 24
// Signal-und-Slot-Verbindungen herstellen QObject::connect( but1, SIGNAL( clicked() ), &app, SLOT( aboutQt() ) ); QObject::connect( but1, SIGNAL( clicked() ), win, SLOT( showNormal() ) ); QObject::connect( but2, SIGNAL( clicked() ), win, SLOT( showMaximized() ) ); QObject::connect( but3, SIGNAL( clicked() ), win, SLOT( showMinimized() ) ); QObject::connect( but4, SIGNAL( clicked() ), &app, SLOT( quit() ) );
25 26 27 28
38
1542.book Seite 39 Montag, 4. Januar 2010 1:02 13
Signale und Slots ermitteln
29 30 31 32 }
// Fenster anzeigen win->show(); return app.exec();
Neben dem bereits erwähnten neuen Widget QWidget wurde hier auch die Klasse QVBoxLayout verwendet, womit einzelne GUI-Elemente vertikal angeordnet hinzugefügt werden können. Als Argument erhält der Konstruktor die Adresse des Eltern-Widgets, was im Beispiel das Hauptfenster der Anwendung (QWidget) ist. Die einzelnen Widgets können Sie anschließend mit der Methode addWidget() und dem Widget als Argument hinzufügen. Das Thema Layout und die Anordnung einzelner Widgets ist ein sehr wichtiges Thema, so dass Sie hierzu einen eigenen Abschnitt (4.2) finden. Hier nun das Programm bei seiner Ausführung:
Abbildung 2.9
Abbildung 2.10
Ein Fenster mit vier Buttons
Button »Wiederherstellen« wurde angeklickt
39
2.1
1542.book Seite 40 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
2.2
Gegenseitiges Signal- und Slot-Konzept
Außerdem kommt es häufig vor, dass zwei Widgets voneinander abhängig sind. Wird bspw. ein Widget verändert, muss das andere ebenfalls an diese Veränderung angepasst werden – und umgekehrt genauso. Am besten sehen Sie sich hierzu folgende Abbildung (2.11) an.
QSpinBox spin
QSlider slider
valueChanged(int)
valueChanged(int)
setValue(int)
setValue(int)
Abbildung 2.11
Anpassen von zwei Widgets bei Veränderungen
In diesem Beispiel haben Sie ein Klasse QSpinBox und eine Klasse QSlider, die beide jeweils bei einer Veränderung den Wert des anderen Widgets anpassen. Wurde bspw. bei der Spinbox der Wert verändert, wird das Signal valueChanged() ausgelöst. Ist dies der Fall wird der Schieberegler (QSlider) entsprechend der Spinbox mit dem Slot setValue() angepasst. Anders betrachtet, haben Sie denselben Fall. valueChanged() ist jeweils das Signal und setValue() der Slot der beiden Widgets. Wie Sie weitere Signale und Slots in Erfahrung bringen können, wurde in Abschnitt 2.1 mit dem Assistant von Qt bereits beschrieben. Erklärungen folgen … Es lässt sich leider nicht ganz vermeiden, dass ich ein wenig auf spätere Kapitel vorgreife, anders lässt sich das Signal-Slot-Konzept nicht beschreiben.
Um die beiden Widgets miteinander zu verbinden, sind im Grunde nur zwei Verbindungen mit connect() nötig. Hier das entsprechende Programmbeispiel: 00 01 02 03 04
// beispiele/signalslot3/main.cpp #include #include #include #include
05 int main(int argc, char *argv[]) 06 QApplication app(argc, argv);
40
{
1542.book Seite 41 Montag, 4. Januar 2010 1:02 13
Argumentenlisten von Signal-Slot-Verbindungen
07 08 09 10
QWidget* win QVBoxLayout* layout QSpinBox* spin QSlider* slider
11 12 13 14 15 16 17 18 19 20 21
// Minimum-Maximum Wert für Spinbox spin->setMinimum(0); spin->setMaximum(100); // Minimum-Maximum Wert für Slider slider->setMinimum(0); slider->setMaximum(100); // Widgets hinzufügen layout->addWidget(spin); layout->addWidget(slider); // Signal-und-Slot Verbindung QObject::connect( spin, SIGNAL( valueChanged(int) ), slider, SLOT( setValue(int) ) ); QObject::connect( slider, SIGNAL( valueChanged(int) ), spin, SLOT( setValue(int) ) ); win->show(); return app.exec();
22 23 24 25 }
= = = =
new new new new
QWidget; QVBoxLayout(win); QSpinBox; QSlider(Qt::Horizontal);
Das Programm bei der Ausführung:
Abbildung 2.12
Die beiden Widgets, immer up to date
Verändern Sie hierbei den Schieberegler, wird automatisch auch die Spinbox mit dem entsprechenden Wert versehen/verändert. Verändern Sie die Spinbox mit den Pfeilen oder als gültigen Wert durch die Tastatureingabe, wird auch der Schieberegler an die Position entsprechend angepasst.
2.3
Argumentenlisten von Signal-Slot-Verbindungen
Im Beispiel zuvor hatten die Signal-Slot-Verbindungen beide dieselben Argumente. Also sowohl valueChanged(int) und setValue(int) waren vom Typ int. Dies muss unbedingt beachtet werden, weil bei einer solchen Signal-SlotVerbindung keine automatische Typenumwandlung erfolgt.
41
2.3
1542.book Seite 42 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
Benötigen Sie bspw. einen String anstelle eines Integer-Werts, müssen Sie von der Klasse ableiten und den entsprechenden Slot implementieren. Hierbei konvertiert man im Grunde nur das Argument und ruft anschließend den eigentlichen Slot auf. Seit Qt 4 kann man nicht mehr einfach beliebige Funktionen als Slots verwenden. Slots müssen Sie als solche kennzeichnen um von Qt akzeptiert zu werden. Sollte der Slot weniger Argumente enthalten als das Signal, ist dies ebenfalls gestattet. Überflüssige Argumente werden hierbei ignoriert. Bspw. ist es möglich, eine Verbindung zwischen signalFunc(int) und slotFunc() aufzubauen, obwohl der Slot keine Argumente enthält. Bei mehr als einem Parameter des Signals gilt dieselbe Regel wie bei der Funktionsüberladung von C++. So lässt sich bspw. mit signalFunc(int, float) mit den Slots slotFunc(), slotFunc(int) und slotFunc(int, float) eine Verbindung aufbauen. Nicht aber mit dem Slot slotFunc(float). Anders herum ist es nicht möglich, eine Verbindung herzustellen, wenn ein Slot mehr Argumente erwartet, als das Signal beinhaltet. Einfachstes Beispiel: QObject::connect( button, SIGNAL( clicked() ), label, SLOT( setText("Neuer Text") ) );
Hier wird versucht, eine Verbindung zwischen einem Button und einem Textlabel herzustellen. Sollte der Button gedrückt werden, dann soll der Textlabel mit einem neuen Text versehen werden. Allerdings funktioniert dies nicht, weil das Signal clicked() keine Argumente enthält, der Slot setText()aber eines erwartet. Ärgerlich ist hierbei die Tatsache, dass der Compiler Ihnen dies ohne Probleme übersetzt.
2.4
Eigene Klasse mit Signalen und Slots definieren bzw. erweitern
Klassen über Klassen Ab diesem Punkt muss ich anmerken, dass das Thema für den Einstieg schon recht komplex ist, zumal die Basisklassen von Qt bis jetzt noch nicht genauer beschrieben wurden. Allerdings wollte ich das Thema »Signale und Slots« nicht im ganzen Buch verstreuen, so dass Sie als Leser jederzeit beim Nachschlagen alles in einem Kapitel wiederfinden und nicht im ganzen Buch danach suchen müssen. Ggf. können Sie zu diesem Abschnitt später zurückkehren, sollten Sie diesen Abschnitt nicht auf Anhieb verstehen oder nur überfliegen.
42
1542.book Seite 43 Montag, 4. Januar 2010 1:02 13
Eigene Klasse mit Signalen und Slots definieren bzw. erweitern
Um eine Klasse mit eigenen Signalen und Slots zu versehen, gilt es folgende Regeln einzuhalten: 왘
Es kann keine eigene Ereignisklasse definiert werden, die nur aus Signalen und Slots besteht. Die Signale und Slots müssen immer einen Teil der Klasse darstellen.
왘
Eigene Signale und Slots können nur in Klassen definiert werden, die von der Klasse QObject abgeleitet sind.
왘
In der neu definierten Klasse muss das Makro Q_OBJECT gesetzt werden (mehr dazu in Kürze).
왘
Standard-Parameter sind für eigene Klassen bei den Signalen und Slots nicht erlaubt.
C++-Grundlagen Für den Fall, dass Ihnen Themen wie das Ableiten von Klassen in C++ fremd sind, ist es nun an der Zeit, dieses Wissensdefizit zu beheben, weil im Grunde die gesamte QtProgrammierung darauf basiert.
Um zu beschreiben, wie Sie eigene Signale und Slots in einer abgeleiteten Klasse definieren, soll zunächst die folgende normale C++-Klasse als Vergleich herhalten: // typische Form einer einfachen C++-Klasse class MyClass { public: // Konstruktor MyClass(); // Zugriffsmethoden int value() const { return val; } void setValue(int); private: int val; };
Damit daraus eine waschechte Qt-Klasse entsteht, sind folgende (fett hervorgehobene) Änderungen nötig: 00 // eine echte Qt-Klasse 01 class MyClass : public QObject { 02 Q_OBJECT 03 public: 04 // Konstruktor 05 MyClass();
43
2.4
1542.book Seite 44 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
06 // Zugriffsmethoden 07 int value() const { return val; } 08 public slots: 09 void setValue(int); 10 signals: 11 void valueChanged(int); 12 private: 13 int val; 14 };
Nachdem Sie Ihre Klasse in Zeile 1 von QObject abgeleitet haben, können Sie Signale und Slots im Grunde wie normale Methoden deklarieren. Allerdings dürfen die deklarierten Signal- und Slot-Elementefunktionen keinen Rückgabetyp haben und müssen daher void sein. Als Argumente können Sie hingegen eine beliebige Anzahl und auch beliebige Typen verwenden. Wichtig ist auch das Makro Q_OBJECT aus Zeile 2 (Achtung, ohne Semikolon!). In der Deklaration der Signal- und Slot-Elementfunktionen werden auch die neuen Spezifizierer signals (Zeile 10) und slots (Zeile 8) verwendet. Je nach erwünschter Sichtbarkeit nach außen bzw. der Weitervererbbarkeit werden noch die Spezifizierer private, public und protected vor signals und slots gesetzt. In Ihrer Eigenschaft als Programmierer definieren Sie nur die Slots als gewöhnliche Elementfunktionen (Methoden). Einsteiger in Qt, die Quellcode studieren, sind häufig verwirrt, weil sie die Definition zur Signal-Elementefunktion nicht finden. Der Code für Signal-Elementfunktionen wird aber nicht vom Programmierer, sondern vom Meta Object Compiler (kurz MOC) geschrieben. Hierzu ein Beispiel, das zunächst allerdings noch keine GUI-Elemente verwendet. Wir erstellen eine einfache Klasse mit einem Integerwert als Eigenschaft und implementieren in dieser Klasse das Signal valueChanged(int), welches ausgelöst wird, sobald man den Integerwert des Objekts verändert. Natürlich implementieren wir hierbei auch den entsprechenden Slot setValue(int), um zwei Objekte der Klasse mit connect() verknüpfen zu können. Hier zunächst die Deklaration der Klasse MyClass: 00 01 02 03
// beispiele/signalslot4/myclass.h #ifndef MY_CLASS_H #define MY_CLASS_H #include
04 // Eine Klasse, die Signals und Slots besitzt. 05 class MyClass : public QObject { 06 Q_OBJECT 07 public:
44
1542.book Seite 45 Montag, 4. Januar 2010 1:02 13
Eigene Klasse mit Signalen und Slots definieren bzw. erweitern
08 MyClass(); // Konstruktor 09 int value() const { return val; } 10 public slots: 11 // Der Wert von "val" wird geändert. 12 void setValue( int ); 13 signals: 14 // Das Signal soll ausgesandt werden, 15 // wenn "val" geändert wird. 16 void valueChanged( int ); 17 private: 18 int val; 19 }; 20 #endif
Die Definition der Klasse ist ebenso einfach. Da wir das Signal nicht definieren müssen, reichen in diesem Fall der Konstruktor und die Slot-Elementefunktion setValue(int) aus. 00 // beispiele/signalslot4/myclass.cpp 01 #include "myclass.h" 02 MyClass::MyClass() { 03 val = 0; 04 } 05 void MyClass::setValue( int v ) { 06 // val wird nur neu gesetzt, 07 // wenn tatsächlich ein anderer Wert übergeben wird. 08 if ( v != val ) { 09 val = v; 10 emit valueChanged(v); 11 } 12 }
Auffällig ist hier die Zeile 10 mit dem emit-Signalbezeichner. Mit dem Bezeichner emit machen Sie deutlich, dass es sich bei dem Aufruf um keinen normalen Funktionsaufruf handelt, sondern um einen Signalaufruf. Allerdings erreichen Sie selbiges auch ohne Bezeichner emit. Der Bezeichner ist nur dafür gedacht, sofort erkennen zu können, ob es sich um einen Signalaufruf handelt. Hierzu folgt noch die Hauptfunktion, die abgesehen von den Nachrichtenboxen (QMessageBox) auch ohne GUI-Elementen auskommt und das eigene Signal-SlotKonzept demonstriert. 00 // beispiele/signalslot4/main.cpp 01 #include "myclass.h"
45
2.4
1542.book Seite 46 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
02 #include 03 #include 04 #include 05 // simple Nachrichten-Box 06 void MyMessageBox(MyClass& a,MyClass& b,QString title) { 07 QString Qstr, Qval; 08 // String zusammenbasteln 08 Qstr.append(title); Qstr.append("\na: "); 09 Qval.setNum(a.value()); Qstr.append(Qval); 10 Qstr.append("\nb: "); Qval.setNum(b.value()); 11 Qstr.append(Qval); 12 QMessageBox::information( NULL, "MyClass Information", Qstr, QMessageBox::Ok); 13 } 14 int main(int argc, char *argv[]) 15 QApplication app(argc, argv); 16 // Zwei MyClass-Objekte 17 MyClass *a = new MyClass(); 18 MyClass *b = new MyClass();
{
19 20 21
// Das Signal des einen wird mit dem Slot // des anderen Objekts verbunden. QObject::connect( a, SIGNAL( valueChanged(int) ), b, SLOT( setValue(int) ) );
22 23 24 25 27 28 29 30 31 32 }
// b.val bekommt den Wert 100 b->setValue( 100 ); MyMessageBox(*a, *b, "b->setValue(100)"); // a.val bekommt den Wert 99. Durch die Signal-Slot// Verknüpfung bekommt jetzt auch b.val den Wert 99 a->setValue( 99 ); // Der Beweis MyMessageBox(*a, *b, "a->setValue(99)"); return 0;
Die Zeilen 5 bis 13 können Sie hierbei ignorieren. Damit wird nur eine Nachrichtenbox auf dem Bildschirm ausgegeben mit dem Inhalt der Integerwerte der beiden Objekte, die in den Zeilen 17 und 18 neu erzeugt werden. In Zeile 21 verknüpfen wir diese Objekte. Verändert man den Wert von Objekt a, wird das Signal valueChanged() ausgelöst und mit Objekt b und dem Slot setValue() verbunden. Sobald Sie also den Wert von Objekt a verändern, wird
46
1542.book Seite 47 Montag, 4. Januar 2010 1:02 13
Eigene Klasse mit Signalen und Slots definieren bzw. erweitern
der Wert von Objekt b mit unserem selbst erstellten Slot setValue() angepasst. In diesem Beispiel erhält das Objekt b durch setValue() denselben Wert wie Objekt a. In der Zeile 23 bekommt das Objekt b mit 100 einen neuen Wert zugewiesen. Wir haben keine Signal-Slot-Verknüpfung eingerichtet, a und b besitzen folgende Werte (siehe Abbildung 2.13):
Abbildung 2.13
Nachdem Objekt b einen Wert erhalten hat
In der Zeile 28 weisen wir dann dem Objekt a mit 99 einen neuen Wert zu. Und hierbei wird auch gleich unsere eingerichtete Signal-Slot-Verbindung aktiv, so dass auch Objekt b denselben Wert erhält, wie Objekt a. Dadurch ergeben sich folgende Werte (siehe Abbildung 2.14):
Abbildung 2.14
Signal wurde ausgelöst
MOC – Meta Object Compiler Dank qmake brauchen Sie sich nicht mehr groß um den Meta Object Compiler (MOC) zu kümmern. Mit dem folgenden typischen Dreisatz muss man sich um (fast) nichts mehr Gedanken machen: $ qmake –project $ qmake $ make qmake erstellt uns hier den entsprechenden Makefile, der auch den Meta Object
Compiler automatisch verwendet. Normalerweise müssen Sie MOC also nicht direkt aufrufen.
47
2.4
1542.book Seite 48 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
Dennoch ist es theoretisch möglich, den MOC anders zu verwenden. Hierzu kann man entweder die mit dem MOC erzeugte Datei der Klassendefinition in einem Schritt zu einer Objektdatei kompilieren und beim Linken der ausführbaren Datei hinzufügen. Der Vorgang würde in etwa wie folgt aussehen: $ moc myclass.h -o moc_myclass.cpp $ g++ moc_myclass.cpp -o moc_myclass.o ---( zunächst wird die moc Datei gesondert übersetzt )--$ g++ myclass.cpp -o myclass.o $ g++ myclass.o moc_myclass.o main.o -o main -L... -l...
Man kann aber auch das mit MOC erzeugte Programm der Klassendefinition mittels einer Include-Anweisung in den Quellcode mit einfügen. Meta Object Compiler Wollen Sie mehr über den Meta Object Compiler erfahren, müssen Sie bei der Qt Reference Documentation von Qt in der Sektion Tools den Link All Tools anklicken. Dort finden Sie neben der Beschreibung weiterer Werkzeuge auch eine zum Meta Object Compiler.
Als Programmierer werden Sie sich sicherlich fragen, wozu denn überhaupt ein zusätzlicher Meta Object Compiler nötig ist, um ein eigenes Signal-Slot-Konzept zu realisieren. Nun – das liegt vor allem daran, dass das Signal-Slot-Konzept kein reines C++ ist, sondern eher eine Erweiterung vom C++-Standard. Der Meta Object Compiler verwandelt daher Signale und Slots in echten C++-konformen Standard. Somit ist MOC also ein Programm, das einen C++-Quelltext einliest und die C++-Erweiterung von Qt verwaltet. Trifft MOC auf eine oder mehrere Deklarationen mit dem Makro Q_OBJECT, produziert dieser eine andere C++-Quelltextdatei. Der so produzierte Code enthält dann den Meta-Object Code für die Klassen, die das Q_OBJECT benutzen. Dieser Meta-Object Code wird für den Signal-Slot-Mechanismus, für Typeninformationen zur Laufzeit und das dynamische Eigenschaftssystem (Property-System) benötigt. Kurz gesagt, benötigt man MOC immer dann, wenn QObject als Basisklasse dient. Der MOC muss dabei jede Datei, die eine Klassendefinition einer entsprechenden Klasse enthält, bearbeiten (ähnlich wie der Präprozessor), bevor der C++-Compiler den Code erhält. Alle von MOC erzeugten Dateien weisen das Präfix moc_auf. In unserem Beispiel wird bspw. aus den Dateien myclass.h und myclass.cpp die Datei moc_myclass.cpp erzeugt. Diese Quelltextdatei muss noch extra kompiliert und mit der Implementierung der Klasse verlinkt werden. qmake erledigt dies alles zum Glück automatisch.
48
1542.book Seite 49 Montag, 4. Januar 2010 1:02 13
Widget mit eigenem Slot
2.5
Widget mit eigenem Slot
Häufig wird man zu seinen Widgets eigene Slots hinzufügen wollen. Ein gerne zitiertes Beispiel zeigt der folgende Codeausschnitt: QObject::connect( button, SIGNAL( clicked() ), label, SLOT( setText("neuer Text") ) );
Hierbei haben wir eine versuchte Signal-Slot-Verknüpfung, die keinerlei Effekt haben wird. Der Programmierer wollte hier wohl erreichen, dass beim Anklicken eines Buttons (bspw. QPushButton) der Text des Labels (QLabel) verändert wird. Der Button verwendet dabei das Signal clicked() und der Label den Slot setText(). Warum dies nicht funktioniert, wurde in Abschnitt 2.3 bereits näher beschrieben. Die Argumentenliste von clicked() und setText() stimmt nicht überein. clicked() gibt hierbei keine Argumente an den Slot setText(). Um das folgende Beispiel zu realisieren, leiten wir unsere neue Klasse MyWindow von QWidget ab. Zunächst die Headerdatei mit der Klasse: 00 01 02 03 04 05 06 07 08
// beispiele/signalslot5/mywindow.h #ifndef MYWINDOW_H #define MYWINDOW_H #include #include #include #include #include #include
09 class MyWindow : public QWidget { 10 Q_OBJECT 11 public: 12 MyWindow(QWidget *parent = 0); 13 private: 14 QLabel *label; 15 QPushButton *button0; 16 QPushButton *button1; 17 QVBoxLayout* layout; 18 private slots: 19 void setText(); 20 }; 21 #endif
Verglichen mit dem Beispiel zuvor, in dem wir eigene Signale und Slots verwendeten, finden Sie auch hier nichts Neues, außer dass wir jetzt von der Klasse QWidget (Zeile 9) – statt von QObject – ableiten und hier nur ein Slot (Zeile 18
49
2.5
1542.book Seite 50 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
und 19) verwendet wird. Die Definition des Konstruktors und der Slot-Ereignisfunktion finden Sie in der folgenden Quelldatei wieder: 00 // beispiele/signalslot5/mywindow.cpp 01 #include "mywindow.h" 02 MyWindow::MyWindow(QWidget *parent): QWidget(parent) { 03 label = new QLabel("alter Text"); 04 button0 = new QPushButton ("Label aktualisieren"); 05 button1 = new QPushButton ("Beenden"); 06 layout = new QVBoxLayout(this); 07 layout->addWidget(button0); 08 layout->addWidget(button1); 09 layout->addWidget(label); 10 setLayout(layout); 11 connect( button0, SIGNAL( clicked() ), this, SLOT( setText() ) ); 12 connect( button1, SIGNAL( clicked() ), qApp, SLOT( quit() ) ); 13 } 14 void MyWindow::setText() { 15 label->setText("neuer Text"); 16 }
Zunächst werden wie gewohnt die einzelnen Widgets wie Buttons (QPushButton) und Label (QLabel) erzeugt und der vertikalen Box (QVBoxLayout) hinzugefügt. Anschließend (Zeile 11 und 12) werden die Signal-Slot-Verknüpfungen eingerichtet. Ein besonderes Augenmerk sei hierbei auf den Slot von Zeile 11 geworfen. Hierbei handelt es sich nicht mehr um den von QLabel zur Verfügung gestellten Slot setText(), sondern um unseren selbst definierten Slot ohne Argumente, dessen Implementierung Sie in Zeile 14 bis 16 vorfinden. Klicken Sie nun im Beispiel auf den Button mit dem Label »alter Text«, wird das Signal clicked() ausgelöst. Dieses Signal ist allerdings nicht mit dem Label verbunden, sondern mit dem Fenster bzw. QWidget. Daher finden Sie hierbei auch den this-Zeiger vor. Erst mit der Slot-Ereignisfunktion MyWindow::setText() wird der Text-Label verändert. Jetzt fehlt nur noch die Hauptfunktion, die kaum noch Code enthält: 00 // beispiele/signalslot5/main.cpp 01 #include 02 #include "mywindow.h" 03 int main(int argc, char *argv[])
50
{
1542.book Seite 51 Montag, 4. Januar 2010 1:02 13
Widget mit eigenem Signal
04 05 06 07 08 }
QApplication app(argc, argv); MyWindow* window = new MyWindow; window->show(); return app.exec();
Das Programm bei der Ausführung:
Abbildung 2.15
Programm beim Start
Abbildung 2.16
Nach dem Drücken von »Label aktualisieren«
2.6
Widget mit eigenem Signal
Natürlich will ich Ihnen Ähnliches auch mit einem Signal demonstrieren. Hierfür wollen wir die QPushButton-Klasse um das Signal clicked(int) erweitern. Ursprünglich gibt es bei dieser Klasse ja nur das Signal clicked() ohne Argument. Wir wollen ein neues clicked(int) mit Argument hinzufügen, welches beim Drücken eines Buttons ein Signal mit einer Identifikationsnummer (ID) des Buttons mitschickt. Hierzu zunächst die Klasse MyButton, die wir von der Klasse QPushButton ableiten: 00 01 02 03 04 05
// beispiele/signalslot6/mybutton.h #ifndef MYBUTTON_H #define MYBUTTON_H #include #include #include
06 class MyButton : public QPushButton {
51
2.6
1542.book Seite 52 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
07 08 09
10
Q_OBJECT public: MyButton( const QString& text, int id, QWidget* parent = NULL ); MyButton( const QString& text, QWidget* parent = NULL ); private: static int nextId; int m_id; public slots: //click() überschreiben void click(); signals: //ein neues Signal hinzufügen void clicked(int id);
11 12 13 14 15 16 17 18 19 20 }; 21 #endif
Zusätzlich zu einem neuen Signal, das hier in Zeile 17 bis 19 festgelegt wird, überschreiben wir den in der Klasse QAbstractButton definierten Slot click(). Der Slot click() führt wortwörtlich einen Klick durch. Natürlich benötigen Sie nach wie vor keine Definition des Signals, da dies, wie bereits beschrieben, der Meta Object Compiler für uns übernimmt. Um den Vorgang besser zu verstehen, benötigen Sie die Implementierung der Klasse MyButton: 00 // beispiele/signalslot6/mybutton.cpp 01 #include "mybutton.h" 02 int MyButton::nextId = 0; 03 MyButton::MyButton( const QString& text, int id, QWidget* parent ) : QPushButton(text, parent), m_id(id) { 04 connect(this, SIGNAL(clicked()), this, SLOT(click())); 05 } 06 MyButton::MyButton(const QString& text, QWidget* parent ) : QPushButton(text, parent), m_id(nextId++) { 07 connect(this, SIGNAL(clicked()), this, SLOT(click())); 08 } 09 void MyButton::click() { 10 emit clicked(m_id); 11 }
52
1542.book Seite 53 Montag, 4. Januar 2010 1:02 13
Widget mit eigenem Signal
Außerdem fallen die beiden connect()in den Konstruktoren auf (Zeile 04 und 07). Hiermit verbinden wir das alte (oder nennen Sie es das echte) clicked()Signal mit dem eigenen, hausgebackenen Slot click() der Klasse MyButton. Hierbei ist maßgebend entscheidend, dass wirklich die Slot-Elementfunktion von MyButton (Zeile 9 bis 11) ausgeführt wird. Würde die Supermethode QPushButton::click() verwendet, hätte dies eine Endlos-Rekursion zur Folge, weil QPushButton::click() wieder das Signal clicked() auslöst. Sobald der Slot MyButton::click() ausgelöst wurde, können Sie das neue Signal mit der Identifikationsnummer (ID) verschicken (Zeile 10). Um aus diesem Beispiel ein stilvolles Qt-Programm zu machen, wollen wir hierzu noch eine Klasse MyWindow (abgeleitet von QWidget) mit unseren eigenen Buttons (MyButton) und einem Slot implementieren, der sich um die Ausgabe auf dem Bildschirm kümmert. Hier die Headerdatei unserer Klasse mywindow.h: 00 01 02 03 04
// beispiele/signalslot6/mywindow.h #ifndef MYWINDOW_H #define MYWINDOW_H #include #include "mybutton.h"
05 class MyWindow : public QWidget { 06 Q_OBJECT 07 public: 08 MyWindow(QWidget* parent = NULL); 09 private: 10 MyButton* myButton[3]; 11 public slots: 12 // Slot für die Ausgabe 13 void myMessageBox(int id); 14 }; 15 #endif
Unseren Button finden Sie in Zeile 10 MyButton mit eigenem implementierten Signal wieder. In Zeile 11 bis 13 deklarieren wir noch einen eigenen Slot für die Klasse MyWindow. Die Slot-Elementfunktion ist im Grunde eine einfache Nachrichtenbox (QMessageBox), welche die Identifikationsnummer des Signals clicked(int) unserer Buttons zurückgibt. Hierzu die Implementierung der Klasse MyWindow: 00 01 02 03 04
// beispiele/signalslot6/mywindow.cpp #include "mywindow.h" #include "mybutton.h" #include #include
53
2.6
1542.book Seite 54 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
05 MyWindow::MyWindow(QWidget* parent) : QWidget(parent) { 06 myButton[0] = new MyButton(tr("Drück mich")); 07 myButton[1] = new MyButton(tr("Mich auch")); 08 myButton[2] = new MyButton(tr("Und mich")); 09 QVBoxLayout* layout = new QVBoxLayout(this); 10 for (int i = 0; i < 3; ++i) { 11 layout->addWidget(myButton[i]); 12 connect( myButton[i], SIGNAL( clicked(int) ), this, SLOT( myMessageBox(int) ) ); 13 } 14 } 15 void MyWindow::myMessageBox(int id) { 16 QMessageBox::information( this, tr("Ein Button wurde betätigt"), QString(tr("Button ID: %1")).arg(id), QMessageBox::Ok ); 17 }
In Zeile 12 wird unser neues clicked(int)-Signal mit dem Ausgabe-Slot myMessageBox verbunden. Sobald also unser neues Signal eintrifft, wird eine Nachrichtenbox mit der ID des Buttons ausgegeben. Zum Schluss noch unser Hauptprogramm zur Demonstration: // beispiele/signalslot6/main.cpp #include #include "mywindow.h" #include "mybutton.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MyWindow* window = new MyWindow; window->show(); return app.exec(); }
Das Programm bei der Ausführung:
Abbildung 2.17
54
Programm nach dem Start
1542.book Seite 55 Montag, 4. Januar 2010 1:02 13
Zusammenfassung
Abbildung 2.18
2.7
Button »Mich auch« mit ID 1 wurde betätigt
Zusammenfassung
Der Umfang dieses Kapitels war zwar nicht enorm, dafür aber sehr detailreich, so dass wir hier die wichtigsten Regeln zusammenfassen, die in Bezug auf das Signal-Slot-Konzept im Allgemeinen zutreffen. 왘
Die Deklaration von Signalen und Slots ist nur in Klassen erlaubt. Eine Deklaration außerhalb von Klassen würde auch nicht in das Klassenkonzept von C++ passen.
왘
Klassen, die eigene Signale bzw. Slots enthalten, müssen von QObject abgeleitet sein. Da bei Qt die meisten GUI-Klassen von QWidget abgeleitet sind, muss man sich diesbezüglich keine großen Gedanken machen, da QWidget wiederum von QObject abgeleitet ist (mehr dazu im nächsten Kapitel).
왘
Verwenden Sie Klassen mit eigenen Signalen bzw. Slots, müssen Sie das Makro Q_OBJECT verwenden. Hinter dem Makro wird kein Semikolon verwendet.
왘
Slots können Sie wie eine gewöhnliche C++-Methode deklarieren und implementieren. Im Grunde sind Slots ja gewöhnliche Methoden, die sich auch außerhalb einer Signal-Slot-Verknüpfung mit connect() direkt verwenden lassen.
왘
Bei der Definition von Slots müssen Sie zuvor das Schlüsselwort slots mit entsprechendem Spezifizierer (private, public) hinzufügen. Wollen Sie einen Slot als virtual deklarieren, können Sie auch protected vor dem Schlüsselwort slots setzen.
왘
Es ist nicht gestattet, Slots als static zu deklarieren, was bei gewöhnlichen C++-Methoden verwendet werden darf.
왘
Wie Methoden können auch Slots Parameter besitzen. Hierbei muss beachtet werden, dass bei einem mit connect() angegebenen Signal dieselben Parametertypen wie beim entsprechenden Slot verwendet werden. Wie bei einer Funktionsüberladung ist auch erlaubt, dass ein Slot weniger Parameter haben darf als das Signal.
55
2.7
1542.book Seite 56 Montag, 4. Januar 2010 1:02 13
2
Signale und Slots
왘
Bei der Definition von Signalen in einer Klasse muss zuvor das Schlüsselwort signals angegeben werden. Ansonsten entspricht auch die Deklaration von Signalen der Deklaration gewöhnlicher Methoden. Wichtig ist hierbei allerdings, dass Signale nur deklariert und niemals direkt implementiert werden dürfen.
왘
Zum Senden von Signalen in einer Klassen-Komponente wird gewöhnlich das Qt-Schlüsselwort emit platziert. emit dient nur der besseren Erkennbarkeit und muss nicht verwendet werden.
왘
Die Signale und Slots werden mit der statischen Funktion QObject:: connect() verbunden.
왘
Wichtig: Bei den Routinen der Makros SIGNAL und SLOT sind nur Typen und keine Werte als Parameter erlaubt. Sie können also nicht bei einer Signalroutine wie SIGNAL(valueChanged( 5 )) einen Wert als Parameter verwenden, sondern dürfen hierzu nur einen Typ wie SIGNAL(valueChanged( int )) verwenden.
Hinweis Natürlich ist es auch möglich, ein Signal von seiner Verbindung, die mittels connect() erstellt wurde, mit QObject::disconnect() wieder zu lösen.
56
1542.book Seite 57 Montag, 4. Januar 2010 1:02 13
Das Qt-Framework umfasst mittlerweile weit über 400 Klassen. Hierbei die Übersicht zu behalten, ist für Einsteiger zunächst nicht ganz einfach. Qt basiert auf einer Ableitungshierarchie. Um sich mit Qt eingehender zu befassen, müssen wir daher erst die Wurzeln und den Aufbau des Frameworks kennen. Sobald Sie wissen, wie Qt »tickt«, werden Sie sich schnell zurechtfinden. Dieses theoretische Kapitel geht also kurz auf den Aufbau des Qt-Frameworks ein.
3
Basisklassen und Bibliotheken von Qt
Wie Sie bereits ein Kapitel zuvor erfahren haben, fordert auch das Signal-SlotKonzept, dass die beteiligten Klassen von der Klasse QObject abstammen. Aber auch viele andere nichtgrafische Klassen basieren auf QObject als Basisklasse. Bspw. werden Klassen, die der Kommunikation zwischen den Prozessen dienen (z. B. QThread), von QObject abgeleitet, damit diese über Signale und Slots kommunizieren können.
3.1
Basisklasse: QObject
QObject wurde im vorigen Kapitel im Zusammenhang mit dem Signal-Slot-Kon-
zept bereits erwähnt. Dabei werden Sie sicherlich festgestellt haben, dass es sich hierbei um eine ziemlich wichtige Klasse in Qt handelt. In der Tat ist QObject eine Basisklasse vieler Qt-Klassen. Alle auf dem Bildschirm dargestellten Widgets leiten bspw. von der Klasse QWidget ab. QWidget wiederum wurde von der Klasse QObject abgeleitet.
3.2
Qt-Klassenhierarchie
Natürlich gibt es auch viele weitere Klassen, die nicht von QObject abgeleitet wurden. Dies sind im Grunde alle Klassen, die kein Signal-und-Slot-Konzept und keine automatische Speicherverwaltung benötigen. Bekanntester Vertreter dürfte hier QString sein. QString ist das, was für den C++-Programmierer die Klasse string und für die Verwendung von Zeichenketten zuständig ist.
57
1542.book Seite 58 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
QString vs. string Es wird empfohlen, in Qt die Klasse QString der Standardklasse string vorzuziehen. QString speichert und verarbeitet den Text im Unicode-Format und erlaubt somit, fast alle Schriftsysteme dieser Erde zu verwenden. Oder kurz: Mit QString müssen Sie sich nicht um die Dekodierung des Zeichensatzes kümmern.
Den besten Überblick über die Klassenhierarchie von Qt bekommen Sie hierbei wieder mit der Dokumentation von Qt. Sie finden die Übersicht im Abschnitt »API Reference« unter »Inheritance Hierarchy« (siehe Abbildung 3.1). Hierbei werden Sie feststellen, dass QObject den größten Anteil von abgeleiteten Klassen besitzt.
Abbildung 3.1
Klassenhierarchie von Qt
Trotzdem werden Sie Klassen wie QPaintDevice entdecken, wovon auch QWidget wiederum einen Teil ererbt hat und somit bei den sichtbaren GUI-Elementen wieder eine Rolle spielt. Ebenso sieht dies etwa bei der Klasse QLayout aus. Diese Klasse wurde bspw. ebenfalls von QObject abgeleitet, hat aber auch die Superbasisklasse QLayoutItem. Die Klasse QLayout beinhaltet Widgets, die zur Anordnung der GUI-Elemente auf dem Fenster benötigt werden. In QVBoxLayout haben Sie bereits einen solchen Abkömmling beider Klassen kennengelernt. Der folgenden Abbildung (3.2) können Sie einen kurzen Überblick zum Aufbau der Qt-Klassenhierarchie entnehmen. Hierbei erkennt man leicht, dass viele Klassen zum Teil von mehreren Klassen abgeleitet wurden. Andere Klassen wiederum, wie bspw. QString, haben gar keine Basisklasse.
58
1542.book Seite 59 Montag, 4. Januar 2010 1:02 13
Qt-Klassenhierarchie
QLayoutItem
QObject
QLayout
QPaintDevice
QWidget
QString
QPrinter
... ...
... QBoxLayout
...
QDialog
... Abbildung 3.2
Schematischer Überblick der Qt-Klassenhierarchie
Gerade auf Einsteiger wirken solch gewaltige Frameworks zunächst ziemlich abschreckend. Sie erfuhren aber bereits in Abschnitt 2.1, wie man die ganze Klassenhierarchie mit der Referenz von Qt nach oben arbeiten kann. Um nochmals auf den Button QPushButton zurückzukommen: Bei der Klassenreferenz von QPushButton im Assistant finden Sie ganz zu Beginn eine Zeile wie »Inherits QAbstractButton«, also von QabstractButton abgeleitet. Klicken Sie auf den Verweis von QAbstractButton. Bei dieser Klassenreferenz erfahren Sie auch gleich, welche Kinder-Widgets aus QAbstractButton erstellt wurden. Dies steht in »Inherited by«, was in diesem Fall die Klassen Q3Button, QCheckBox, QPushButton, QRadioButton und QToolButton wären. Das Eltern-Widget (Inherits) ist hingegen QWidget. Auch finden Sie hier (bei QWidget) gleich endlos viele GUI-Elemente, die aus QWidget abgeleitet wurden. QWidget wiederum wurde von QObject und QPaintDevice (siehe Abbildung 3.3) abgeleitet. QObject
QPaintDevice
QWidget
QAbstractButton
QPushButton
Abbildung 3.3
Klassenhierarchie von QPushButton
59
3.2
1542.book Seite 60 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
Dieses Durchlaufen der Klassen mit der Referenz von Qt ist unerlässlich, wenn es darum geht, nach Funktionen, Methoden, Eigenschaften, Slots oder Signalen bestimmter Klassen zu suchen oder eben um eigene Widgets zu implementieren. Ich weiß, dass ich mich wiederhole, doch kann man dies nicht oft genug erwähnen.
3.3
Speicherverwaltung von Objekten
Da C++ keine automatische Speicherverwaltung wie bspw. Java besitzt, haben wir in den Beispielen des vorigen Kapitels des Öfteren den new-Operator zum Erzeugen neuer Objekte verwendet. Im Abschnitt zuvor lernten Sie die Klassenhierarchie von Qt kennen. Sie sahen (Abbildung 3.3), wie aus QObject abgeleitete Klassen eine Baumhierarchie gebildet wird. Durch eine solche Hierarchie entsteht eine Eltern-Kind-Beziehung. Wird bspw. ein Eltern-Widget gelöscht, werden alle davon abgeleiteten KinderObjekte ebenfalls gelöscht. Die Kinder dieser Kinder-Objekte wiederum löschen auch ihre Nachkommen. Auf diese Weise nimmt uns Qt einen Teil der Speicherverwaltung ab, und Sie müssen nicht jedes einzelne Objekt mit delete per Hand löschen. Damit man die Qt-Klassen verwenden kann, wird bei der Initialisierung immer die Übergabe eines Zeigers auf das aufrufende Elternobjekt verlangt. Dadurch ist gewährleistet, dass die Elternklasse Kenntnis über ihre Kindobjekte besitzt und diese bei der eigenen Löschung auch aus dem Speicher zerstört. Aus diesem Grund muss jedes Qt-Objekt im Heap erzeugt werden, was der Operator new ja leistet. Mit einem Klassen-Konstruktor auf dem Stack gelegte Objekte werden nach der Abarbeitung wieder gelöscht. Das Widget wird zwar kurz erzeugt, aber nie angezeigt. Vergleichsfall Als Beispiel nenne ich Ihnen ein altbekanntes Problem: die Rückgabe einer globalen Variablen aus einer normalen Funktion.
Hierzu bspw. folgende Zeilen: 01 02 03 04 05
60
QWidget* win = new QWidget; QVBoxLayout* layout = new QVBoxLayout(win); QPushButton* but01 = new QPushButton("Label"); // Widgets zur vertikalen Box hinzufügen layout->addWidget(but01);
1542.book Seite 61 Montag, 4. Januar 2010 1:02 13
Programm-Bibliotheken von Qt
QVBoxLayout wird hier als Kind von QWidget gezeugt. Der Button hingegen ist bei der Zeugung noch elternlos und würde jetzt noch nicht angezeigt werden. Erst beim Aufruf von addWidget() wird dafür gesorgt, dass jemand die Elternschaft für den Button übernimmt. Allerdings kommt dabei oft auch ein wenig Verwirrung auf, da nicht die Klasse QVBoxLayout die Elternschaft von QPushButton übernimmt, sondern QWidget. Betrachtet man die Widget-Hierarchie, wird man feststellen, dass QPushButton und QVBoxLayout in der Hierarchie etwa gleichgestellt sind. Dabei würde die ganze Speicherverwaltung natürlich nicht mehr funktionieren. Deswegen müssen die Kinderelemente eines Widgets immer GUI-Elemente eines übergeordneten Widgets sein. Durch den Codeausschnitt ergibt sich die in Abbildung 3.4 abgedruckte Hierarchie.
QWidget
QPushButton
Abbildung 3.4
QVBoxLayout
Eltern-Kind-Elemente
Allerdings ist es auch möglich, das Eltern-Widget gleich bei der Erzeugung mit anzugeben. Bezogen auf den eben gezeigten Codeausschnitt würde dies folgendermaßen aussehen: 01 02 03
QWidget* win = new QWidget; // Eltern-Widget als zweites Argument angeben QPushButton* but01 = new QPushButton( "Label", win );
3.4
Programm-Bibliotheken von Qt
Wenn Sie mit dem Qt-Assistant ein wenig die Klassen betrachtet haben, werden Sie feststellen, dass Qt in mehrere Pakete (Module) aufgeteilt ist. Neben der Erstellung grafischer Oberflächen bietet Qt ja noch viel mehr für die Entwicklung von Applikationen. Hier zunächst ein Überblick über die in den nächsten Abschnitten näher erörterten Qt-Bibliotheken: 왘
QtCore enthält die grundlegenden (core) Funktionen und Klassen ohne grafische Oberfläche, die von den anderen Bibliothken verwendet werden.
왘
QtGui erweitert die Bibliothek QtCore und umfasst die gesamte GUI-Funktionalität.
왘
QtNetwork enthält alle Klassen und Funktionen, die nötig sind, um bspw. TCP/IP (und UDP) Server- und/oder Client-Anwendungen zu schreiben.
61
3.4
1542.book Seite 62 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
왘
QtOpenGL enthält Klassen, die es einfach machen, die OpenGL-Schnittstelle in Qt-Anwendungen zu verwenden.
왘
QtSql zuständig für die Einbindung von SQL-Datenbanken in den Qt-Anwendung.
왘
QtSvg stellt Klassen zum Anzeigen von SVG-Grafiken (Vektorformat) zur Verfügung.
왘
QtXml enthält C++-Implementationen von SAX- und DOM-Klassen.
왘
QtDesigner beinhaltet Klassen, mit denen Sie Plugins (Widgets) für den QtDesigner erstellen können.
왘
QtUiTools – dieser Teil der Bibliothek enthält Klassen (im Augenblick eigentlich nur eine), die es Ihnen ermöglichen, Standalone-Anwendungen (Benutzerschnittstellen; User Interfaces (UI)) dynamisch zur Laufzeit zu erzeugen und in einer .ui-Datei abzuspeichern.
왘
QtAssistant ermöglicht die Verwendung der Online-Hilfe des Assistant von Qt.
왘
Qt3Support enthält Klassen, um Anwendungen von Version 3 auf Version 4 zu portieren.
왘
QtTest wird verwendet, um Qt-Anwendungen und -Bibliotheken zu testen.
왘
QtScript und QtScriptTools enthalten Klassen zur Verarbeitung von ECMAScript (= JavaScript). QtScriptTools enthält hierfür noch weitere zusätzliche Klassen.
왘
QtWebKit enthält Klassen zur Darstellung und Verarbeitung von Webseiten (seit Qt 4.4).
왘
Phonon beinhaltet Klassen zur Verwendung von Multimedia-Inhalten wie Audio oder Video (seit Qt 4.4).
왘
QtMultimedia beinhaltet Multimedia-Funktionalitäten auf der niedrigeren Ebene (seit Qt 4.6).
Die kommerzielle Version von Qt unter Windows bietet mit QAxContainer und QAxServer außerdem zwei Programm-Bibliotheken für ActiveX-Controls und ActiveX-Server an. Für alle Unix-Plattformen (und Qt-Editionen) stehen mit der Bibliothek QtDBus noch Klassen für die Interprozesskommunikation mit D-BUS zur Verfügung. D-BUS D-BUS ist ein Software-System, das mehrere Möglichkeiten anbietet, Programme miteinander kommunizieren zu lassen.
62
1542.book Seite 63 Montag, 4. Januar 2010 1:02 13
Programm-Bibliotheken von Qt
Wenn Sie qmake verwenden, um Ihre Projekte zu bauen, werden QtCore (core) und QtGui (gui) standardmäßig hinzugefügt. Wollen Sie bspw., dass bei Ihrem Projekt nur QtCore gelinkt wird, müssen Sie in der .pro-Datei (Projektdatei) folgende Zeile ändern bzw. hinzufügen: QT -= gui
Die Verwendung entspricht den erweiterten Operatoren von C/C++. Wollen Sie bspw. dem Projekt die Bibliothek QtNetwork hinzufügen, müssen Sie nur Folgendes in die .pro-Datei einfügen: QT += network
Die zu linkende Bibliothek legt qmake mit QT fest. Wenn Sie eine Qt-Bibliothek hinzufügen wollen, müssen Sie nur den +=-Operator vor der gewünschten Bibliothek setzen. Soll eine Bibliothek entfernt werden, dann erledigen Sie dies mit -=-Operator. In der folgenden Tabelle (3.1) finden Sie die möglichen Werte, die Sie für die QTVariable der .pro-Datei (Projektdatei) verwenden können, und welche Bibliothek Sie dabei hinzulinken. QT-Wert (Option)
Bibliothek
core (per Standard implementiert)
QtCore
designer
QtDesigner
gui (per Standard implementiert)
QtGui
network
QtNetwork
multimedia
QtMultimedia
opengl
QtOpenGL
phonon
Phonon
qt3support
Qt3Support
qtestlib
QtTest
script
QtScript
scripttools
QtScriptTools
sql
QtSql
svg
QtSvg
uitools
QtUiTools
webkit
QtWebKit QtXml
xml
Tabelle 3.1
Bibliotheken hinzulinken
63
3.4
1542.book Seite 64 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
Natürlich ist es auch möglich, mehrere Bibliotheken dem Projekt hinzuzulinken. Zu diesem Zweck muss man der .pro-Datei nur mehrere Bibliotheken, getrennt von einem Leerzeichen, hinzufügen: QT += network opengl sql svg xml qt3support
Hiermit hätten Sie praktisch alle Bibliotheken (gui und core sind per Standard dabei) hinzugelinkt.
3.4.1
QtCore
QtCore ist die grundlegende Basisbibliothek, auf der Qt basiert, und enthält im Grunde nur Klassen, die mit keiner grafischen Ausgabe verbunden sind. QtCore beinhaltet auch die von C++-Programmierern so bezeichnete STL (Standard Template Library) – und zwar mitsamt der Funktionalität und der Art der Verwendung. Alle Qt-eigenen Container-Klassen (und auch Strings) verwenden hierbei die Methode »copy on write«. Damit lassen sich die Klassen ohne großen Aufwand als Funktionswert zurückgeben. Auch die Klasse QString (zur Behandlung von Zeichenketten) ist ein Teil von QtCore. Es wurde bereits erwähnt, dass die einzelnen Zeichen hierbei im Unicode-Standard (UTF-16) kodiert werden. Anstelle eines char kommt hierbei ein short int (16 Bits) zum Zuge. Natürlich enthält QString auch alles Nötige für Konvertierungen unterschiedlicher Zeichensatzsysteme wie ASCII, UTF, verschiedener ISO-Standards usw. QtCore ist auch für die Klassen QObject und QCoreApplication (Basisklasse für QApplication) verantwortlich. Insgesamt beinhaltet QtCore mehr als 200 Klas-
sen. Die grundlegenden und in der Praxis gängigsten seien hier stichpunktartig notiert: 왘 왘
die grundlegenden Datentypen QString und QByteArray die grundlegenden Datenstrukturen (wie bei STL) QList, QVector, QHash QMap, usw.
왘
Klassen für die Ein-/Ausgabe wie QFile, QDir, QTextStream usw.
왘
Klassen, um Multithreads wie QThread, QMutex, QWaitCondition usw. zu programmieren
왘
Klassen für Datum und Uhrzeit wie QDate, QTime und QDateTime
왘
Laden von Bibliotheken und Plugins zur Laufzeit wie QLibrary und QPluginLoader
왘
64
Die Klasse QObject, Hauptträger vieler Konzepte wie Kommunikation zwischen Signalen und Slots, Speicherverwaltung und Objekteigenschaften.
1542.book Seite 65 Montag, 4. Januar 2010 1:02 13
Programm-Bibliotheken von Qt
QtCore wird häufig auch alleine ohne QtGui verwendet, um Client-/Server-Programme mit Multithreads oder Sockets zu schreiben. Häufig ist hierfür keine grafische Oberfläche nötig.
3.4.2
QtGui
Zunächst setzt QtGui die Bibliothek QtCore voraus. QtGui enthält alle Klassen, die etwas auf dem Bildschirm zeichnen. Dabei wird die Klasse QtWidget erweitert. QtWidget allein zeichnet im Grunde nur ein leeres Rechteck auf den Bildschirm. Auch neue GUI-Elemente werden mithilfe von QWidget erstellt, indem man die neue Klasse davon ableitet. QtGui ist wohl der Hauptgrund, weshalb viele Anwender Qt verwenden und wird wohl auch den Großteil des Buches ausmachen. Daher zählen wir im Folgenden die gängigsten Teile von QtGui stichpunktartig auf 왘
Klassen, um vorgefertigte Dialoge zu verwenden (bspw. QFileDialog (Dateiauswahl), QPrinterDialog (Dokument drucken), QColorDialog (Farbauswahl), usw.);
왘
Klassen, um die Widgets anzuordnen – sogenannte Layout-Klassen (bspw. QVBoxLayout, QHBoxLayout, QGridLayout, usw.);
왘
die Klasse QWidget und unzählige davon abgeleitete GUI-Klassen;
왘
Klassen zum Zeichnen auf dem Bildschirm (bspw. QBrush, QPainter, QPen, usw.);
왘
die Klasse QApplication.
3.4.3
QtNetwork
Die Klasse QtNetwork bietet eine Menge Klassen an, mit denen Sie TCP/IP (sowie UDP)-Client-/Server-Anwendungen schreiben können. Neben den Klassen QTcpSocket, QTcpServer und QUdpSocket, die ja in einer tieferen Ebene operieren, bietet QtNetwork mit QHttp und QFtp Klassen der höheren Ebene an, womit das HTTP- und FTP-Protokoll verwendet werden kann. QtNetwork benötigt ebenfalls die Bibliothek QtCore, nicht aber QtGui.
3.4.4
QtOpenGL
Mit dieser Bibliothek können Sie die OpenGL-API in Ihrer Qt-Anwendung verwenden. OpenGL ist eine Portable Standard-Schnittstelle zum Rendern von 3DGrafik (ähnlich wie DirectX). Mit QtOpenGL können Sie OpenGL verwenden wie jedes andere Qt-Widget.
65
3.4
1542.book Seite 66 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
3.4.5
QtSql
Die Bibliothek QtSql beinhaltet Klassen für die Unterstützung SQL-Datenbanken. In der folgenden Tabelle (3.2) sind Client-APIs aufgelistet, für die Qt einen passenden Treiber zur Verfügung stellt. Treibername
Datenbanksystem
QIBASE
Borland InterBase
QMYSQL
MySQL
QODBC
Open Database Connectivity (ODBC) (wird von Microsoft SQL-Server verwendet)
QPSQL
PostgresSQL
QSQLITE2
SQLite (Version 2)
QSQLITE
SQLite (Version 3)
Tabelle 3.2
Datenbanken, für die Qt einen Treiber mitliefert
Diese Treiber sind auf jeden Fall in der Open-Source-Edition von Qt enthalten. Weitere Datenbanksysteme bzw. Treiber, die sich mit GPL nicht unter einen Hut bringen lassen, stehen nur bei der kommerziellen Version von Qt zur Verfügung (siehe Tabelle 3.3). Treibername
Datenbanksystem
QDB2
IBM DB2
QOCI
Oracle Call Interface Treiber
QTDS
Sybase Adaptive Server
Tabelle 3.3
DBMS (nur in der kommerziellen Version von Qt)
Besonders beeindruckend an QtSql ist die Tatsache, dass man nur ein entsprechendes Datenbanksystem auszuwählen braucht und gleich danach mit dem QtCode auf jede DB-Schnittstelle zugreifen kann.
3.4.6
QtSvg
Die Bibliothek QtSvg unterstützt Klassen, mit denen sich Vektorgrafiken mit dem SVG-Format anzeigen lassen. SVG (Scalable Vector Graphics, »Skalierbare Vektorgrafiken«) ist ein Standard zur Beschreibung zweidimensionaler Vektorgrafiken in der XML-Syntax. SVG wurde im September 2001 vom W3C als Empfehlung veröffentlicht. Viele aktuelle Webbrowser sind von Haus aus in der Lage, einen Großteil des Sprachumfangs darzustellen, wie z. B. Mozilla Firefox oder
66
1542.book Seite 67 Montag, 4. Januar 2010 1:02 13
Programm-Bibliotheken von Qt
Opera. Bisher unterstützt QtSvg die Profile SVG-Basic und SVG-Tiny. ECMAScripts und DOM-Manipulationen werden von Qt zurzeit noch nicht unterstützt.
3.4.7
QtXml
Die Bibliothek QtXml stellt einen einfachen XML-Parser zur Verfügung. Dieser lässt sich direkt über die SAX2-Schnittstelle (SAX = Simple API for XML) verwenden. In QtXml ist außerdem der DOM-Standard (DOM = Document Object Model) implementiert. Damit lässt sich ein XML-Dokument parsen und die entsprechende Baumstruktur verändern bzw. anpassen. Das so veränderte Dokument kann natürlich wiederum als XML-Dokument ausgegeben werden. DOM erlaubt es sogar, ein neues XML-Dokument daraus zu erstellen.
3.4.8
Qt3Support
Da Qt4 gegenüber Qt3 enorm erweitert und verbessert wurde und tlw. inkompatibel geworden ist, liefert Trolltech diese Bibliothek mit. Wir beschreiben allerdings Qt Version 4 und gehen nicht näher darauf ein.
3.4.9
QtScript
Mit dieser Bibliothek können Sie eine zu ECMA-262 kompatible Scriptsprache (bspw. JavaScript) in C++-Anwendungen einbetten. Damit wird dann eine Umgebung angeboten, die zur dynamischen Interaktion mit Qt-Objekten verwendet werden kann. QtScript deaktiviert Ab der Version 4.6 wird QtScript auf allen Plattformen deaktiviert, wenn diese QtWebKit nicht unterstützen. Trotzdem kann man QtScript auf diesen Plattformen wieder aktivieren.
3.4.10 QtWebKit QtWebKit ist eine HTML-Rendering-Bibliothek, die alles mitliefert, womit Sie quasi einen eigenen Webbrowser entwickeln könnten. WebKit wurde ursprünglich von Apple entwickelt und als Grundlage für den Webbrowser Safari verwendet. Mittlerweile setzen mehrere Hersteller auf dieses Webkit. Bekannte Software auf Basis der Bibliothek sind Adobe AIR, Google Chrome oder Omniweb, um einige zu nennen. Aber auch viele Browser auf mobilen Endgeräten wie iPhone, Palm Pre, Nokias S60-Serie oder Googles Android sind mithilfe von Webkit aufgebaut. Die Grundlagen von Webkit stammen ursprünglich vom KDE-Projekt.
67
3.4
1542.book Seite 68 Montag, 4. Januar 2010 1:02 13
3
Basisklassen und Bibliotheken von Qt
3.4.11 Phonon Wie auch das Webkit ist Phonon (hieß früher KDEMM) eine Multimedia-API vom KDE-Projekt und seit der Version 4.4 auch bei Qt als Multimediaschnittstelle an Bord. Phonon bietet hierbei eine einheitliche (Wrapper-)Schnittstelle für Audio- und Video-Anwendungen ähnlich wie DirectShow unter Windows oder QuickTime unter Mac OS X.
3.4.12 Der Rest Auf die weiteren Bibliotheken wie QtMultimedia, QtDesigner, QtUiTools, QtTest und die kommerziellen Bibliotheken wird in diesem Buch nicht näher eingegangen. Es existiert zudem noch eine Migrationslösung in Qt4 für MFC-, Motif- und Xt-basierende Anwendungen. Auch hierbei handelt es sich um eine kommerzielle Lösung, die Trolltech separat unter dem Namen QtSolution anbietet.
3.5
Meta-Include-Headerdatei
Alle Programmbibliotheken aus Abschnitt 3.4 zuvor sind auch als Meta-IncludeHeaderdatei enthalten, um bspw. die einzelnen Klassen aus der GUI-Bibliothek QtGui wie folgt zu inkludieren: #include #include #include #include #include #include ...
Statt der vielen Inkludierungen der Headerdateien können Sie auch die MetaHeaderdatei QtGui inkludieren: #include
Wenn Sie sich diese Meta-Include-Datei ansehen, werden Sie feststellen, dass alle Klassen der GUI-Bibliothek inkludiert sind. Ein Nachteil dieser Inkludierung kann sein, dass die Kompilierung etwas mehr Zeit in Anspruch nimmt. Im Grunde trifft dies aber nur auf ältere Compiler zu, die keine vorkompilierten Headerdateien unterstützen. In den Buch-Beispielen verwenden wir zunächst noch die Klassen-Headerdateien und führen dann allmählich die Meta-Include-Datei ein. Im Prinzip entspricht der Klassenname bei Qt auch dem Headernamen. Lautet der Klassenname bspw.
68
1542.book Seite 69 Montag, 4. Januar 2010 1:02 13
Meta-Include-Headerdatei
QPushButton, müssen Sie nur den Include hinzufügen (oder eben die Meta-Include-Datei ).
Für alle anderen Bibliotheken gilt dasselbe wie für die Bibliothek QtGui mit ihrem Meta-Include. Somit können Sie der Bibliothek QtXml anstelle der unzähligen XML-Include-Dateien auch nur den Meta-Include hinzufügen.
69
3.5
1542.book Seite 70 Montag, 4. Januar 2010 1:02 13
1542.book Seite 71 Montag, 4. Januar 2010 1:02 13
In Ihrem ersten grundlegenden Kapitel zur GUI-Programmierung mit Qt gehen wir zunächst auf die Dialoge ein. Sie werden eigene Dialoge mit Unterstützung der Layout-Klassen erstellen. Anschließend erörtern wir die fertigen Dialoge von Qt. Im zweiten Teil des Kapitels behandeln wir die zahlreichen Widgets von Qt eingehender.
4
Dialoge, Layout und Qt-Widgets
4.1
Eigene Widget-Klassen erstellen
Im Kapitel zu den Signalen und Slots wurde Ihnen ab Abschnitt 2.4, in dem es darum ging, eine Klasse um eigene Signale und Slots zu erweitern, vor Augen geführt, wie man dies über die Ableitung einer Klasse erreicht. Der dabei demonstrierte Vorgang ist eigentlich ein üblicher Weg bei der Qt-Programmierung. Man leitet dabei normalerweise von einer in der Hierarchie etwas höher angesiedelten Klasse ab (bspw. meistens QWidget oder QDialog) und platziert darin die für das Teil-Objekt benötigten Widgets. In der Praxis werden die Anwendungen dann aus mehreren solchen Teilobjekten zusammengesetzt. Dieser Weg hat viele Vorteile. Will man bspw. das Teilobjekt verbessern, verändern oder erweitern, genügt es in der Regel, nur den Code dieser Klasse zu verändern. Die Hauptfunktion bleibt unverändert. Insgesamt entspricht dies den Vorteilen, die man auch mit C++ und den entsprechenden Klassen hat. Im Grunde erstellt man sein eigenes Widget meist in folgenden Schritten (wobei hier »erweitertes« eher zutrifft): 왘
Widget von der gewünschten Klasse ableiten
왘
GUI-Elemente erzeugen, die im Widget angezeigt werden sollen
왘
angezeigte GUI-Elemente anordnen (Layout)
왘
Signal- und Slot-Verbindungen einrichten
Hierzu wollen wir ein einfaches Beispiel in Form einer einfachen Nachrichtenbox mit einem Button erstellen. Das Ganze soll am Ende folgendermaßen aussehen (Abbildung 4.1):
71
1542.book Seite 72 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Abbildung 4.1
Einfache Nachrichtenbox
Anstatt also im Hauptprogramm einen Label, einen Button und ein Widget für das Layout zu erzeugen, erstellt man hier ein eigenes Widget, das sich jederzeit ohne großen Aufwand im Hauptprogramm erzeugen und anzeigen lässt. Die komfortablere Alternative: QMessageBox Qt bietet natürlich mit QMessageBox eine erheblich einfachere und komfortablere Alternative, Nachrichtenboxen zu verwenden.
Als Erstes erstellen wir das Grundgerüst der Klasse. Hierzu wird alles als Grundgerüst in einer Headerdatei zusammengefasst, was man für eine Nachrichtenbox benötigt. In diesem Beispiel wurde folgendes Gerüst erstellt: 00 01 02 03 04 05 06
// beispiele/simple/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include
07 class MyWidget : public QWidget { 08 public: 09 MyWidget(const char* qstr = "Bitte Button betätigen", const char* but = "Ende", QWidget *parent = 0 ); 10 private: 11 QPushButton *button0; 12 QLabel* label; 13 QVBoxLayout* layout; 14 }; 15 #endif
In diesem Beispiel werden keine Methoden oder eigene Signale bzw. Slots implementiert. Der erste Parameter im Konstruktor (Zeile 9) wird für das Text-Label (QLabel) verwendet. Gibt der Anwender nichts anderes an, steht hier der vorgegebene Text in der Nachrichtenbox (hier »Bitte Button betätigen«). Gleiches gilt
72
1542.book Seite 73 Montag, 4. Januar 2010 1:02 13
Eigene Widget-Klassen erstellen
für das zweite Argument des Konstruktors in Bezug auf den Button (QPushButton). Als zusätzliche Widgets in der Klasse MyWidget wurden hier noch QPushButton, QLabel und QVBoxLayout (Zeile 11 bis 13) implementiert. Nun zur eigentlichen Definition der Klasse MyWidget: 00 // beispiele/simple/mywidget.cpp 01 #include "mywidget.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyWidget::MyWidget( const char* lab, const char* but, QWidget *parent): QWidget(parent) { 05 // Elemente des Widgets erzeugen 06 button0 = new QPushButton (but); 07 layout = new QVBoxLayout(this); 08 label = new QLabel(lab); 09 // Elemente des Widgets anordnen/anpassen 10 layout->addWidget(label); 11 layout->addWidget(button0); 12 // Signale des Widgets einrichten 13 connect( button0, SIGNAL( clicked() ), qApp, SLOT( quit() ) ); 14 }
Zunächst erzeugen wir die GUI-Elemente des neuen Widgets (Zeile 6 bis 8), die anschließend einem Layout entsprechend angeordnet werden (Zeile 10 bis 11). Am Ende richten wir noch eine Signal-Slot-Verbindung (Zeile 13) ein. Dabei dürfte sich wohl gleich die Frage stellen, was qApp hier zu suchen hat. qApp ist ein Zeiger auf eine globale Variable in . Dieser verweist auf die eindeutige Instanz der Anwendung. Wir brauchen diesen globalen Zeiger, um die Anwendung bzw. in diesem Fall die Nachrichtenbox wieder zu beenden. So – oder so ähnlich – sind im Grunde alle Qt-Anwendungen aufgebaut. Fehlt eigentlich nur noch eine Hauptfunktion, um die Klasse bei der Ausführung zu demonstrieren: 00 // beispiele/simple/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget("Programm mit Ende beenden");
73
4.1
1542.book Seite 74 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
06 07 08 }
4.2
window->show(); return app.exec();
Widgets anordnen – das Layout
Ein weiteres sehr wichtiges Kapitel ist das Layout der Anwendungen, womit die Anordnung der Widgets gemeint ist. Bspw., ob die einzelnen Widgets horizontal oder vertikal angeordnet werden sollen. Oder: Was passiert, wenn der Anwender die Größe des Fensters verändert, usw. Der ein oder andere Leser wird mir nun entgegnen, das Thema Layout sei doch Sache des Designers von Qt (ein RAD-Tool). Im Grunde trifft das auch zu. Sie werden wohl kaum das Layout Zeile für Zeile selbst schreiben. Hierfür verwendet man bei einem vernünftigen Framework immer ein RAD-Tool. Allerdings ist es für den Qt-Einsteiger unerlässlich zu wissen, welche Layout-Widgets es gibt und wie man sie verwendet. Wenn die Grundlagen dieser Widgets bekannt sind, ist auch der Designer von Qt Ihr Freund (siehe Kapitel 13).
4.2.1
Grundlegende Layout-Widgets
Bevor wir das grundlegende Qt-Layout-Konzept näher erläutern, wollen wir uns zunächst die Klassenhierarchie davon ansehen (Abbildung 4.2).
QObject
QStackedLayout
QVBoxLayout
Abbildung 4.2
QLayoutItem
QLayout
QSpacerItem
QBoxLayout
QGridLayout
QWidgetItem
QHBoxLayout
Klassenhierarchie für das Qt-Layout
In der Praxis werden hierbei die Klassen QGridLayout, QStackedLayout, QVBoxLayout und QHBoxLayout am meisten (und auch direkt) verwendet. Alle Klassen stammen von der Basisklasse QLayout ab, die wiederum von der SuperBasisklasse QObject und QLayoutItem abgeleitet ist. Von QLayoutItem alleine
74
1542.book Seite 75 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
wurden die beiden Klassen QSpaceItem und QWidgetItem abgeleitet, die allerdings beide normalerweise nicht direkt verwendet werden. QBoxLayout wiederum lässt sich direkt verwenden, was aber ebenfalls eher selten der Fall ist, weil hierbei die beiden Klassen QVBoxLayout und QHBoxLayout die meisten Fälle abdecken. QGridLayout, QVBoxLayout und QHBoxLayout Der einfachste und schnellste Weg, Ihrer Anwendung ein gutes Layout zu verpassen, dürften die eingebauten Widgets (auch Layout-Manager genannt) QHBoxLayout, QVBoxLayout und QGridLayout sein. Alle drei Klassen sind von QLayout (siehe Abbildung 4.2) abgeleitet. Die einzelnen Layouts lassen sich recht schnell beschreiben: 왘
QVBoxLayout – ordnet die Widgets in einer vertikalen Spalte von oben nach
왘
QHBoxLayout – ordnet die Widgets in einer horizontalen Reihe von links nach
unten an; rechts an (in Ländern, wo die Schriftzeile von rechts nach links verläuft, ist auch die Anordnung entsprechend gestaltet); 왘
QGridLayout – ordnet die Widgets in einem zweidimensionalen Raster an.
Man kann sich dies ähnlich wie bei einer Tabellenkallkulation vorstellen. Ein Widget kommt bspw. in Zeile 1 und Reihe 3, wobei die Zählung von 0 anfängt. In der folgenden Abbildung (4.3) können Sie alle drei grundlegenden Layouts von Qt näher betrachten.
Abbildung 4.3
QVBoxLayout, QHBoxLayout und QGridGroup
75
4.2
1542.book Seite 76 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Natürlich will ich Ihnen nicht nur eine entsprechende Abbildung zeigen, sondern auch ein Beispiel liefern. Der Einfachheit halber verwenden wir zunächst nur Buttons (QPushButton) als Widgets. Am einfachsten wird es sein, Ihnen als Erstes das Grundgerüst, die Headerdatei, zu zeigen: 00 01 02 03 04 05 06 07 08
// beispiele/layout1/mylayout.h #ifndef MYLAYOUT_H #define MYLAYOUT_H #include #include #include #include #include #include
09 class MyLayoutEx : public QWidget { 10 public: 11 MyLayoutEx(); 12 private: 13 QPushButton *but01[3], *but02[3], *but03[3], *but04[3]; 14 QVBoxLayout* AllBox; 15 QVBoxLayout* VBox; 16 QHBoxLayout* HBox; 17 QGridLayout* Grid; 18 QGroupBox* VGroup; 19 QGroupBox* HGroup; 20 QGroupBox* GridGroup; 21 }; 22 #endif
Neben den Buttons finden Sie hier die drei Layout-Widgets QVBoxLayout, QHBoxLayout und QGridLayout. Dass hier zweimal Klassen vom Typ QVBoxLayout vorhanden sind, hat damit zu tun, dass die einzelnen Layout-Widgets auch wieder im Fenster angeordnet werden müssen. Wie Sie Abbildung 4.3 entnehmen können, haben wir die Layout-Widgets vertikal angeordnet. Bevor wir die einzelnen Layout-Widgets allerdings in die vertikale Box einfügen, stecken wir die Layouts mit den Buttons in eine Gruppenbox (QGroupBox). Im Grunde ist dies ein Widget, das seinerseits Widgets beinhalten kann und zusätzlich über einen Rahmen um diese Widgets mit einem Text-Label verfügt. Auch hier erscheint das Ganze wieder komplizierter, als es tatsächlich ist. Am besten sehen Sie sich dazu den entsprechenden Konstruktor und die anschließende Erläuterung an:
76
1542.book Seite 77 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
00 // beispiele/layout1/mylayout.cpp 01 #include "mylayout.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyLayoutEx::MyLayoutEx() { 05 // 3 mal 4 Buttons erzeugen 06 for( int j = 0; j < 3; ++j ) { 07 but01[j] = new QPushButton("Button01"); 08 but02[j] = new QPushButton("Button02"); 09 but03[j] = new QPushButton("Button03"); 10 but04[j] = new QPushButton("Button04"); 11 } 12 // Buttons vertikal anordnen 13 VBox = new QVBoxLayout; 14 VBox->addWidget(but01[0]); 15 VBox->addWidget(but02[0]); 16 VBox->addWidget(but03[0]); 17 VBox->addWidget(but04[0]); 18 // eine Box mit Label um die Buttons 19 VGroup = new QGroupBox("QVBoxLayout"); 20 // die vertikal Box mit den Buttons 21 // in die Group-Box stecken 22 VGroup->setLayout(VBox); 23 // Buttons horizontal anordnen 24 HBox = new QHBoxLayout; 25 HBox->addWidget(but01[1]); 26 HBox->addWidget(but02[1]); 27 HBox->addWidget(but03[1]); 28 HBox->addWidget(but04[1]); 29 // eine Box mit Label um die Buttons 30 HGroup = new QGroupBox("QHBoxLayout"); 31 // die horizont. Box mit den Buttons 32 // in die Group-Box stecken 33 HGroup->setLayout(HBox); 34 // Buttons auf einen Raster anordnen 35 Grid = new QGridLayout; 36 Grid->addWidget(but01[2], 0, 0); 37 Grid->addWidget(but02[2], 0, 1); 38 Grid->addWidget(but03[2], 1, 0); 39 Grid->addWidget(but04[2], 1, 1); 40 // eine Box mit Label um die Buttons 41 GridGroup = new QGroupBox("QGridGroup"); 42 // die Raster-Box mit den Buttons 43 // in die Group-Box stecken
77
4.2
1542.book Seite 78 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
44 45 46 47 48 49 50 60 61 62 63 64 }
GridGroup->setLayout(Grid); // eine vertikale Box erzeugen AllBox = new QVBoxLayout(this); // alle Group-Boxen in die vertik. Box stecken AllBox->addWidget(VGroup); AllBox->addWidget(HGroup); AllBox->addWidget(GridGroup); // Layout setzen setLayout(AllBox); // Fenstertitel angeben setWindowTitle("Basic Layouts");
In den Zeilen 6 bis 11 erzeugen wir zunächst jeweils 3 mal 4 Buttons der Klasse QPushButton, anschließend in Zeile 13 eine vertikale Layout-Box (QVBoxLayout), der in den Zeilen 14 bzw. 17 jeweils 4 Buttons mit addWidget() hinzugefügt werden. Jetzt wird in Zeile 19 eine Group-Box (QGroupBox) mit dem Text-Label »QGroupBox« erzeugt, worin in Zeile 22 dann die vertikale Box (QVBoxLayout) mitsamt der Buttons platziert wird. Selbiger Vorgang jetzt auch in den Zeilen 24 bis 33, nur eben bezogen auf eine horizontale Box (QHBoxLayout). Zum Schluss geschieht Ähnliches mit dem RasterLayout (QGridLayout). Allerdings gestaltet sich hier das Hinzufügen der einzelnen Widgets (hier Buttons) mit addWidget() ein wenig anders. Hierzu nochmals der Codeausschnitt, jetzt mit Kommentaren versehen: // Zeile 0; Spalte 0 Grid->addWidget(but01[2], // Zeile 0; Spalte 1 Grid->addWidget(but02[2], // Zeile 1; Spalte 0 Grid->addWidget(but03[2], // Zeile 1; Spalte 1; Grid->addWidget(but04[2],
0, 0); 0, 1); 1, 0); 1, 1);
Natürlich ist QGridLayout wesentlich vielseitiger, als es den Anschein hat. Verwendet man bspw. folgende Rasterpunkte, ergibt sich Folgendes: // Zeile 0; Spalte 0; Grid->addWidget(but01[2], // Zeile 1; Spalte 0; Grid->addWidget(but02[2], // Zeile 1, Spalte 1; Grid->addWidget(but03[2], // Zeile 2; Spalte 1 Grid->addWidget(but04[2],
78
0, 0); 1, 0); 1, 1); 2, 1);
1542.book Seite 79 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Verwendet man dieses Raster, erhält man folgende Abbildung:
Abbildung 4.4
QGridLayout im Einsatz (1)
Unschön ist hierbei, dass »Button01« und »Button04« hier relativ verloren herumstehen. Wollen Sie, dass auch die restliche Fläche mit dem Button ausgefüllt wird, müssen Sie bei den Buttons zusätzlich angeben wie weit sich die Abgrenzung der Buttons erstrecken soll. Dies erledigen Sie mit der folgenden Angabe: Grid->addWidget(but01[2], Grid->addWidget(but02[2], Grid->addWidget(but03[2], Grid->addWidget(but04[2],
0, 1, 1, 2,
0, 1, 2); 0); 1); 0, 1, 2);
Bezogen auf die erste Zeile addWidget(but01[2], 0, 0, 1, 2), bedeutet dies: Leg den Button in Zeile 0 und Spalte 0. Der Button soll sich dabei nur über die eine Zeile, aber über zwei Spalten erstrecken. Durch diese weiteren Angaben bei addWidget() ergibt sich folgende Abbildung:
Abbildung 4.5
QGridLayout im Einsatz (2)
Nun zurück zur Implementierung der Klasse MyLayoutEx. Am Ende des Beispiels erzeugen wir in Zeile 46 erneut eine vertikale Box, der in den Zeilen 48 bis 50 die Group-Box hinzugefügt wird. Am Schluss setzen wir das Layout (Zeile 61) und den Fenstertitel (Zeile 63). Jetzt benötigen wir noch eine Hauptfunktion, die dieses Programm in der Praxis demonstriert, damit Sie die Abbildung 4.3 erhalten: 00 // beispiele/layout1/main.cpp 01 #include 02 #include "mylayout.h" 03 int main(int argc, char *argv[])
{
79
4.2
1542.book Seite 80 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
04 05 06 07 08 }
QApplication app(argc, argv); MyLayoutEx* window = new MyLayoutEx; window->show(); return app.exec();
Allgemeine Methoden für das Layout (QLayout) Natürlich gibt es zu den einzelnen Klassen, angefangen bei QLayout, eine Menge Methoden und Funktionen, das Layout anzupassen. Auf alle hier einzugehen, wäre wohl zu viel des Guten und ist eher Aufgabe des Qt-Assistant. Beginnen wir mit Methoden die in der Klasse QLayout als public gekennzeichnet sind und somit allen anderen davon abgeleiteten Klassen zur Verfügung stehen. Methode
Beschreibung
SizeConstraint sizeConstraint () const;
Damit können Sie das Verhalten des Layouts abfragen. Was passiert, wenn die Größe verändert wird?
void setSizeConstraint (SizeConstraint );
Damit legen Sie das Verhalten des Layouts fest. Was passiert, wenn die Größe verändert wird?
int margin () const;
Damit fragen Sie die Weite eines (Leer-)Raums außerhalb des Rahmens vom Layout ab. Standardwerte sind hierbei meist 9 für Kinder-Widgets und 11 für Fenster.
void setMargin ( int );
Damit setzen Sie die Weite eines (Leer-)Raums außerhalb des Layout-Rahmens.
int spacing () const;
Damit können Sie den Abstand zwischen den Widgets innerhalb des Layouts ermitteln. Der Standardwert ist gewöhnlich –1, was bedeutet, dass dieser Abstand vom Eltern-Layout geerbt wird.
void setSpacing ( int );
Hiermit setzen Sie den Abstand zwischen den Widgets innerhalb des Layouts.
Tabelle 4.1
Wichtige Methoden der Klasse QLayout
sizeConstraint Mögliche Werte für SizeConstraint, wenn die Größe verändert wurde, finden Sie in der Tabelle 4.2 aufgelistet. Virtuelle Methoden der Klasse QWidget Die in der Tabelle 4.2 erwähnten Methoden minimumSize(), maximumSize() und sizeHint() sind virtuelle Methoden der Klasse QWidget.
80
1542.book Seite 81 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Konstante
Bedeutung
QLayout::SetDefaultConstraint
Die Größe der Widgets wird hierbei auf ein Minimum mit minimumSize() gesetzt, außer das Widget besitzt bereits die minimale Größe.
QLayout::SetFixedSize
Die Größe der Widgets lässt sich nicht verändern. Intern wird hierfür die Methode sizeHint() verwendet.
QLayout::SetMinimumSize
Die Widgets werden mit minimumSize() auf die kleinste Größe gesetzt. Kleiner geht es nicht mehr.
QLayout::SetMaximumSize
Die Widgets werden mit maximumSize() auf die maximale Größe gesetzt. Es geht nicht mehr größer.
QLayout::SetMinAndMaxSize
Die minimale Größe wird mit minimumSize() und die max. Größe mit maximumSize() gesetzt.
QLayout::SetNoConstraint
Hiermit setzen Sie überhaupt keine Begrenzung.
Tabelle 4.2
Mögliche Werte für SizeConstraint
Bezieht man dies bspw. auf das Beispiel mit der Klasse MyLayoutEx und der horizontalen Box (QHBoxLayout) mit den darin enthaltenen Buttons, würde eine Größenveränderung die Breite der Buttons in die Länge ziehen – was nicht sehr schön aussieht (siehe Abbildung 4.6).
Abbildung 4.6
Größenänderung ohne Vorkehrungen
Verwenden Sie hingegen die Methode setSizeConstraint() mit dem Wert QLayout::SetFixedSize folgendermaßen: HBox->setSizeConstraint(QLayout::SetFixedSize);
Damit bleiben auch bei einer Größenveränderung alle Widgets der horizontalen Box in einer festen Mindestgröße erhalten (siehe Abbildung 4.7).
Abbildung 4.7
Größenänderung und setSizeConstraint(QLayout::SetFixedSize)
margin Die Beschreibung in der Tabelle 4.1 zu margin lässt sich theoretisch nicht so leicht erkennen. Daher demonstrieren wir anhand eines Beispiels, was hier mit
81
4.2
1542.book Seite 82 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Margin und dem Raum gemeint ist. Greifen wir zunächst wieder das Beispiel MyLayoutEx und die vertikale Box auf:
Abbildung 4.8
QVBoxLayout ohne Margin
Fügen wir nun nach dem Erzeugen der Box und dem Hinzufügen der Buttons folgende Zeile hinzu: VBox->setMargin(100);
Somit erhalten Sie mittels dieser Zeile folgendes Bild:
Abbildung 4.9
QVBoxLayout mit setMargin(100)
Hierbei wird also der Bereich zwischen der Group-Box (QGroupBox) und der vertikalen Box (QVBoxLayout) mit 100 Pixel in allen Richtungen gepolstert. spacing Im Gegensatz zu margin legen Sie mit spacing einen leeren Raum innerhalb eines Widgets fest. Dies wollen wir wiederum auf unsere Klasse MyLayoutEx anwenden. Diesmal soll das Raster-Layout (QGridLayout) dazu verwendet werden, welches bisher folgendermaßen aussieht:
82
1542.book Seite 83 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Abbildung 4.10
QGridLayout ohne Spacing
Auch hier fügen Sie nach dem Erzeugen von QGridGroup und dem Hinzufügen der Buttons die folgende Zeile ein: Grid->setSpacing(50);
Dank dieser Zeile sieht das Raster-Layout nun wie folgt aus:
Abbildung 4.11
QGridLayout mit Spacing
Jetzt haben alle Widgets innerhalb der Klasse QGridLayout eine Polsterung von 50 Pixel. Wollten Sie außen herum ebenfalls einen Leerraum von 50 Pixel hinzufügen, müssten Sie nur die Methode setMargin() in der nächsten Zeile folgendermaßen anwenden: Grid->setMargin(50);
Abbildung 4.12
QGridLayout mit Spacing und Margin
Methoden für das Layout (QBoxLayout) – horizontales und vertikales Layout Die eben gezeigten Methoden von QLayout stehen somit allen Layout-Widgets zur Verfügung. Eine Klasse tiefer von QLayout finden Sie u. a. QBoxLayout. Diese Klasse bietet wiederum einige nützliche Methoden, um das Layout der einzelnen
83
4.2
1542.book Seite 84 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Widgets anzuordnen. Auch hier werden in einer Tabelle die wichtigsten und gängigsten Methoden beschrieben und anschließend etwas näher in der Praxis erläutert. Alle Methoden sind public und stehen somit auch der Klasse QVBoxLayout und QHBoxLayout zur Verfügung. Methode
Beschreibung
void addSpacing ( int size );
Fügt einen Leerraum mit size Pixel am Ende der Box hinzu. Damit lässt sich praktisch ein Leerraum zwischen zwei Widgets einer Box hinzufügen.
void addStretch ( int stretch = 0 );
Fügt einen auf das Widget bezogenen Ausdehnungsraum am Ende der Box hinzu. Damit lässt sich quasi verhindern, dass sich ein Widget bei einer Größenveränderung ausdehnt.
void addWidget ( QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 );
Fügt ein Widget am Ende der Box hinzu. Hierzu lassen sich noch ein Ausdehnungsraum wie mit addStrech() und die Ausrichtung (vertikal oder horizontal) angeben. Wird diese Methode in Bezug auf QHBoxLayout verwendet, ist die Ausrichtung horizontal und bei QVBoxLayout eben vertikal.
void setDirection ( Direction direction );
Legt die Anordnung der Widgets für das Layout fest. Hierbei kann man eben von rechts nach links, links nach rechts, oben nach unten oder unten nach oben setzen.
Direction direction () const ;
Fragt die Anordnung der Widgets ab (siehe setDirection()).
bool setStretchFactor ( QWidget * widget, int stretch );
Legt einen Ausdehnungsraum für ein bestimmtes Widget fest (siehe auch addStretch()).
void insertSpacing ( int index, int size );
Fügt einen Leerraum zwischen dem Widget mit dem index-1 und index+1 ein (index beginnt mit 0).
void insertStretch ( int index, int stretch = 0 );
Fügt einen Ausdehungsraum zwischen dem Widget mit dem index-1 und index+1 ein (0 für index bedeutet Am Anfang einfügen).
void insertWidget ( int index, QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 );
Fügt ein neues Widget zwischen index-1 und index+1 mit einer vertikalen oder horizontalen Ausrichtung in das Box-Layout ein (0 für index bedeutet Am Anfang einfügen).
Tabelle 4.3
Wichtige Methoden der Klasse QBoxLayout
Natürlich will ich einige dieser Methoden auch in der Praxis mit Abbildungen verdeutlichen. Bilder sagen gewöhnlich mehr als Worte.
84
1542.book Seite 85 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Spacing Mit den beiden Methoden addSpacing() und insertSpacing() können Sie auch hier einen Leerraum einfügen. Im Gegensatz zum Spacing der Klasse QLayout fügen Sie allerdings den Leerraum immer am Ende der Box ein. Somit gilt das Spacing innerhalb von QBoxLayout immer für einen Leerraum zwischen Widgets und nicht für die gesamte Box. Fügen Sie bspw. in das Listing mit der Klasse MyLayoutEx folgende Zeilen ein: VBox = new QVBoxLayout; VBox->addWidget(but01[0]); VBox->addWidget(but02[0]); VBox->addWidget(but03[0]); VBox->addWidget(but04[0]); // Leerraum zwischen but02[0] und but03[0] einfügen VBox->insertSpacing( 2, 50 ); ... HBox = new QHBoxLayout; HBox->addWidget(but01[1]); HBox->addWidget(but02[1]); // Leerraum einfügen HBox->addSpacing(50); HBox->addWidget(but03[1]); HBox->addWidget(but04[1]);
Diese beiden Zeilen ergeben folgende Leerräume:
Abbildung 4.13
Verwendung von insertSpacing() und addSpacing()
Stretch Wenn Sie die Größe eines Fensters verändern, dehnen sich leider häufig auch die Widgets aus. Bezogen auf unsere Klasse MyLayoutEx ergibt sich bei einer Ausdehnung bspw. folgendes Bild:
85
4.2
1542.book Seite 86 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Abbildung 4.14
Buttons in QHBox werden in die Länge gezogen
Fügen Sie nun Stretches wie folgt zwischen die Buttons ein: HBox = new QHBoxLayout; HBox->addWidget(but01[1]); HBox->addStretch(); HBox->addWidget(but02[1]); HBox->addStretch(); HBox->addWidget(but03[1]); HBox->addStretch(); HBox->addWidget(but04[1]);
Nun ergibt sich folgende Abbildung:
Abbildung 4.15
Die Verwendung von addStretch() zwischen den Buttons
Wollen Sie bspw., dass bei einer Größenänderung »Button01« von den anderen Buttons getrennt wird, müssten Sie nur einen Stretch zwischen »Button01« und »Button02« einbauen. Nun bezieht sich der komplette Stretch nur auf diesen Raum, bspw.: HBox->addWidget(but01[1]); HBox->addStretch(); HBox->addWidget(but02[1]); HBox->addWidget(but03[1]); HBox->addWidget(but04[1]);
Dadurch ergibt sich Abbildung 4.16. Alternativ zu addStretch() können Sie auch die Methode insertStretch() verwenden. Hierbei können Sie zusätzlich den Index der Position in der Box angeben, wo der Stretch eingefügt werden soll.
Abbildung 4.16
86
Die Verwendung von addStretch() zwischen »Button01« und »Button02«
1542.book Seite 87 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Wollen Sie hingegen, dass im Gegensatz zu Abbildung 4.16 der Button den Rest des Raumes zwischen Button01 und Button02 einnimmt, brauchen Sie nur die Methode setStretchFactor() zu verwenden: HBox->addWidget(but01[1]); HBox->addWidget(but02[1]); HBox->addWidget(but03[1]); HBox->addWidget(but04[1]); HBox->setStretchFactor(but01[1], 100);
Hierdurch dehnt sich der »Button01« wie folgt aus:
Abbildung 4.17
Verwendung von setStretchFactor()
Anordnung der Widgets (Direction) Interessant ist auch die Möglichkeit, die Anordnung der Widgets zu ändern. Bezogen auf Abbildung 4.17 sind die einzelnen Buttons von links nach rechts bzw. auf Abbildung 4.9 von oben nach unten angeordnet. Dies lässt sich mit den Methoden setDirection() und direction() auch ändern bzw. abfragen. Für Direction können Sie hierbei folgende Werte setzen bzw. abfragen (Tabelle 4.4). Konstante
Ausrichtung
QBoxLayout::LeftToRight
horizontal von links nach rechts
QBoxLayout::RightToLeft
horizontal von rechts nach links
QBoxLayout::TopToBottom
vertikal von oben nach unten
QBoxLayout::BottomToTop
vertikal von unten nach oben
Tabelle 4.4
Mögliche Werte zur Ausrichtung der Widgets mit QBoxLayout
Bezogen auf die Abbildung 4.17 lässt sich die Anordnung der Buttons von links nach rechts folgendermaßen in von rechts nach links ändern: HBox = new QHBoxLayout; // Anordnung der Buttons von rechts-nach-links HBox->setDirection(QBoxLayout::RightToLeft); HBox->addWidget(but01[1]); HBox->addWidget(but02[1]); HBox->addWidget(but03[1]); HBox->addWidget(but04[1]);
87
4.2
1542.book Seite 88 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Durch die Änderung der Anordnung ergibt sich folgende Abbildung:
Abbildung 4.18
Anordnung geändert von rechts nach links
Widgets hinzufügen (addWidget) Mit der Methode addWidget() fügen Sie ein Widget am Ende der Layout-Box hinzu. Hierbei kann man optional einen Stretch-Faktor und die Anordnung angeben. Der Stretch-Faktor wird nur bei entsprechender Anordnung (Direction) von QBoxLayout hinzugefügt und ist relativ zu den anderen Boxen und Widgets in diesem QBoxLayout. Widgets und Boxen mit einem höheren Stretch-Faktor dehnen sich auch mehr aus. Bspw. folgender Codeausschnitt: HBox = new QHBoxLayout; HBox->addWidget(but01[1], 100); HBox->addWidget(but02[1]); HBox->addWidget(but03[1]); HBox->addWidget(but04[1]);
Damit erreichen Sie dasselbe wie mit setStretchFactor() (Abbildung 4.17). Sollte der Stretch-Faktor 0 sein – in QBoxLayout hat nichts einen Stretch-Faktor größer als 0 –, wird der Raum für jedes Widget gemäß QWidget::sizePolicy() gefüllt. Methoden für das Layout (QGridLayout) – Raster-Layout QGridLayout bietet ebenfalls einige Methoden an, um das Layout der Widgets zu verwalten. Auch hier ein kurzer Überblick zu den gängigen Methoden von QGridLayout. Methode
Beschreibung
void setColumnMinimumWidth ( int column, int minSize );
Setzt die Mindestweite der Spalte column auf minSize Pixel.
int columnMinimumWidth ( int column ) const;
Gibt die Mindestweite der Spalte column in Pixel zurück.
Tabelle 4.5
88
Methoden der Klasse QGridLayout
1542.book Seite 89 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Methode
Beschreibung
void setColumnStretch ( int column, int stretch );
Setzt den Ausdehnungsfaktor der Spalte column auf den Wert stretch. Die erste Spalte beginnt mit der Nummer 0. Der Stretchfaktor ist relativ zu den anderen Spalten im Grid-Layout. Spalten mit einem höheren Stretchfaktor nehmen auch mehr Platz ein. Der Standardwert des Stretchfaktors ist hierbei 0.
int columnStretch ( int column ) const;
Ermittelt den Ausdehnungsfaktor der Spalte column.
void setOriginCorner ( Qt::Corner corner );
Setzt den Ausgangspunkt der Ecke auf corner. Hierfür kann man die in der Tabelle 5.32 angegebenen Werte verwenden.
Qt::Corner originCorner() const;
Ermittelt den Ausgangspunkt der Ecke. Mögliche Rückgabewerte siehe Tabelle 5.32.
void setRowMinimumHeight ( int row, int minSize );
Setzt die Mindesthöhe der Zeile row auf minSize Pixel.
int rowMinimumHeight ( int row ) const;
Gibt die Mindesthöhe der Zeile row in Pixel zurück.
void setRowStretch ( int row, int stretch );
Setzt den Stretch-Faktor der Zeile row auf den Wert stretch. Die erste Zeile beginnt mit 0. Der Stretch-Faktor ist wiederum relativ zu den anderen Zeilen im Grid-Layout. Zeilen mit einem höheren Stretchfaktor nehmen auch mehr Platz ein. Der Standardwert ist auch hier wieder 0.
int rowStretch (int row) const;
Gibt den Stretch-Faktor der Zeile row zurück.
int columnCount () const;
Gibt die Anzahl der Spalten zurück.
int rowCount () const;
Gibt die Anzahl der Zeilen zurück.
void addWidget ( QWidget * widget, int row, int column, Qt::Alignment alignment = 0 );
Fügt ein neues Element in die Zeile row und Spalte column ein. Optional kann hierbei die Ausrichtung des Widgets angegeben werden.
void addWidget ( QWidget * widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0 );
Dito, nur ist außerdem die Angabe möglich, über welche Spalten und Zeilen sich ein Widget erstrecken kann (wurde im Programmlisting gezeigt und erklärt).
Tabelle 4.5
Methoden der Klasse QGridLayout (Forts.)
89
4.2
1542.book Seite 90 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Stretch Zum
Verändern
und
Abfragen
des
Stretchfaktors
stehen
Ihnen
mit
setColumnStretch(), setRowStretch(), columnStretch() und rowStretch()
drei Methoden zur Verfügung. Beginnen wir mit dem Stretch-Faktor der Spalte. Fügen Sie Ihrem Code folgende Zeile hinzu: Grid = new QGridLayout; Grid->setColumnStretch(0, Grid->addWidget(but01[2], Grid->addWidget(but02[2], Grid->addWidget(but03[2], Grid->addWidget(but04[2],
100); 0, 0); 0, 1); 1, 0); 1, 1);
Auf diese Weise setzen Sie den Stretch-Faktor der ersten Spalte auf 100. Hiermit werden die Widgets der zweiten Spalte mit ihrer minimalen Größe erzeugt und angezeigt, während die Widgets der ersten Spalte den Rest auffüllen. Dadurch ergibt sich folgende Abbildung:
Abbildung 4.19
QGridLayout und setColumnStretch()
Um jetzt auch beim Stretch-Faktor für die Zeile mit setRowStretch() etwas zu sehen, müssen wir die maximal erlaubte Größe der Buttons ein wenig verändern. Dies erledigen Sie folgendermaßen: ... but01[2]->setMaximumSize ( 300, 100 ); but02[2]->setMaximumSize ( 300, 100 ); but03[2]->setMaximumSize ( 300, 100 ); but04[2]->setMaximumSize ( 300, 100 ); // Buttons auf einem Raster anordnen Grid = new QGridLayout; Grid->addWidget(but01[2], 0, 0); ... setMaximumSize() ist eine Methode der Klasse QWidget und legt die maximal erlaubte Größe für ein Widget fest. Nun können Sie den Stretch-Faktor für die Zeile wie folgt verwenden: Grid = new QGridLayout; Grid->setRowStretch(0, 100);
90
1542.book Seite 91 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Grid->addWidget(but01[2], Grid->addWidget(but02[2], Grid->addWidget(but03[2], Grid->addWidget(but04[2],
0, 0, 1, 1,
0); 1); 0); 1);
Wenn Sie das Programm jetzt ausführen, erkennen Sie zunächst noch nichts. Erst wenn Sie die Größe des Fensters verändern, dehnen sich die Buttons in der ersten Zeile um den Faktor 100 aus, wie die folgende Abbildung zeigt:
Abbildung 4.20
QGridLayout und setRowStretch()
Mindestgrößen festlegen Mit
den
Methoden
setColumnMinimumWidth(),
setRowMinimumHeight(),
columnMinimumWidth() und rowMinimumHeight() haben Sie die Möglichkeit, die
Mindesthöhe- bzw. -breite zu setzen bzw. abzufragen. Um auch hier etwas zu sehen, belassen Sie bitte die maximal erlaubte Größe der Buttons so, wie dies beim Stretch-Faktor für die Zeile demonstriert wurde. Zunächst wollen wir eine Mindestgröße für die Spalte setzen: Grid = new QGridLayout; Grid->setColumnMinimumWidth( Grid->addWidget(but01[2], 0, Grid->addWidget(but02[2], 0, Grid->addWidget(but03[2], 1, Grid->addWidget(but04[2], 1,
0, 200); 0); 1); 0); 1);
Dadurch setzen Sie die Mindestbreite der ersten Spalte auf 200 Pixel. Egal, wie breit das Fenster wird, die erste Spalte muss mindestens 200 Pixel breit sein. Dadurch ergibt sich folgende Abbildung:
Abbildung 4.21
QGridLayout und setColumnMinimumWidth()
91
4.2
1542.book Seite 92 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Selbiges ist natürlich auch mit einer Zeile wie folgt möglich: Grid = new QGridLayout; Grid->setRowMinimumHeight( 0, 100 ); Grid->addWidget(but01[2], 0, 0); Grid->addWidget(but02[2], 0, 1); Grid->addWidget(but03[2], 1, 0); Grid->addWidget(but04[2], 1, 1);
Damit setzen Sie die Mindesthöhe für die erste Zeile auf 100 Pixel. Im Beispiel ist der Bereich um die Buttons in der ersten Reihe immer mindestens 100 Pixel. Egal wie hoch das Fenster ist.
Abbildung 4.22
QGridLayout und setRowMinimumHeight()
Stapel-Layout (QStackedLayout) Das Stapel-Layout der Klasse QStackedLayout lagert die einzelnen Widgets übereinander. Bei den bisher gezeigten Layouts wurden die einzelnen Widgets sichtbar auf einer Ebene angeordnet. In der Praxis verwendet man dieses Layout recht häufig bei Konfigurations-Dialogen. Am besten lässt sich dies wohl wieder anhand einer Abbildung beschreiben (Abbildung 4.23).
Abbildung 4.23
Stapel-Layout (1)
Die Abbildung zeigt auf der linken Seite eine Combo-Box und auf der rechten Seiten eine Group-Box mit Buttons. Die sich überlagernden Widgets befinden sich auf der rechten Seite und werden hier über die Combo-Box ausgewählt. Wählen Sie bspw. bei der Combo-Box »Seite 2« aus, wird die Group-Box mit den Buttons
92
1542.book Seite 93 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
von einem anderen, an eben dieser Position bzw. an diesem Index befindlichen Widget überlagert (siehe bspw. Abbildung 4.24).
Abbildung 4.24
Stapel-Layout (2)
Neben einer Combo-Box (QComboBox) wird für die Steuerung des Stapel-Layouts häufig auch die Listenform (QListWidget) verwendet (siehe Abbildung 4.25).
Abbildung 4.25
Steuerung des Stapel-Layouts mit QListWidget
Die Klasse QStackedLayout lässt sich eigentlich recht einfach verwenden. Auch hier findet man die bekannte Funktion addWidget() zum Hinzufügen neuer Widgets. Um auf ein bestimmtes Widget im Stapel zugreifen bzw. um es anzeigen zu können, verwendet man meist den Slot setCurrentIndex(int) verwendet. Diese Slot-Methode erwartet den Index (angefangen bei 0) des entsprechenden Widgets. Zusätzlich gibt es den Slot setCurrentWidget(QWidget*) der statt eines Integer-Werts einen Zeiger auf eine von QWidget abgeleitete Klasse erwartet. Um sich den Aufwand der Verwaltung des Layouts von QStackedLayout zu ersparen, das meistens selbst erzeugt werden muss, bietet Ihnen Qt die Klasse QStackedWidget an. QStackedWidget besitzt dieselbe Schnittstelle wie QStackedLayout. Außerdem ist QStackedWidget intern bereits ein Widget mit Stapel-Layout. Hierzu soll wieder ein Listing erstellt werden, mit dem Sie ein Beispiel wie in den Abbildungen 4.23 und 4.24 erstellen können. Hierbei müssen wir allerdings erneut mit QComboBox auf eine Klasse vorgreifen, die noch nicht behandelt wurde. Zunächst wieder das Grundgerüst, die Headerdatei:
93
4.2
1542.book Seite 94 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
00 01 02 03 04 05
// beispiele/stapellayout/stapelwidget.h #ifndef STAPELWIDGET_H #define STAPELWIDGET_H #include #include #include
06 07 08 09 10 11 12 13 14
class StapelWidget : public QWidget { public: StapelWidget(QWidget *parent = 0); void addStapel(const QString& title, QWidget *wid); private: QStackedWidget *widgetStack; QComboBox *pageComboBox; }; #endif
Den wichtigsten Teil in dieser Klasse übernimmt die Methode addStapel() (Zeile 9). Mit ihr fügen Sie dem Stapel-Widget ein neues Widget (oder auch eine neue »Seite«) hinzu. Genaueres wird die Implementierung unserer Klasse StapelWidget zum Vorschein bringen: 00 // beispiele/stapellayout/stapelwidget.cpp 01 #include 02 #include "stapelwidget.h" 03 StapelWidget::StapelWidget( QWidget *parent ) : QWidget(parent) { 04 QHBoxLayout *lay = new QHBoxLayout(this); 05 pageComboBox = new QComboBox; 06 widgetStack = new QStackedWidget; 07 // Widgets zur horizont. Box hinzufügen 08 lay->addWidget(pageComboBox); 09 lay->addWidget(widgetStack); 10 // Signal-Slot-Verbindung einrichten 11 connect( pageComboBox, SIGNAL( activated( int ) ), widgetStack, SLOT( setCurrentIndex( int ) )); 12 setWindowTitle("Stapel Layout"); 13 } 14 void StapelWidget::addStapel( const QString& title, QWidget *wid ) { 15 pageComboBox->addItem(title); 16 widgetStack->addWidget(wid); 17 }
94
1542.book Seite 95 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Nachdem in Zeile 5 und 6 eine Combo-Box und ein Stapel-Widget erzeugt wurden, packen wir diese beiden Widgets in Zeile 8 und 9 in die in Zeile 4 erzeugte horizontale Box. Anschließend wird in Zeile 11 die Signal-Slot-Verbindung eingerichtet. Hierfür wurde das Signal activated(int) der Combo-Box mit dem Slot setCurrentIndex(int) des Stapel-Widgets verbunden. Dabei sendet activated() die Indexnummer des aktivierten Elements in der Combo-Box (angefangen bei 0). Der Slot setCurrentIndex() verwendet diesen erhaltenen Indexwert zur Anzeige des entsprechenden Widgets (gerne auch: der entsprechenden Seite) im Stapel. Der erste Parameter der Methode addStapel() (Zeile 14 bis 17) wird für den Titel (bzw. den Text) der Combo-Box verwendet. Der zweite Parameter ist das dem Stapel hinzuzufügende Widget und wird somit angewählt, wenn der entsprechende Text der Combo-Box (Parameter 1) ausgewählt wurde. Beide Elemente haben somit denselben für die Signal-Slot-Verbindung benötigten Indexwert. Hierzu
noch
ein
Beispiel-Code
(das
Hauptprogramm),
der
die
Klasse
StapelWidget in der Praxis zeigen soll: 00 // beispiele/stapellayout/main.cpp 01 #include 02 #include "stapelwidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 // neues Stapelwidget erzeugen 06 StapelWidget *w = new StapelWidget; 07 // neue Seite mit einem Label hinzufügen 08 w->addStapel("Seite 1", new QLabel("Huhu ich bin die erste Seite") ); 09 // neue Seite mit einem Button hinzufügen 10 w->addStapel("Seite 2", new QPushButton("dont't touch me")); 11 12 13 14 15 16 17 18 19
// Group-Box mit drei Buttons erzeugen ----- Anfang QPushButton *buttons[3]; for( int j = 0; j < 3; ++j ) { buttons[j] = new QPushButton("Button"); } QVBoxLayout *VBox = new QVBoxLayout; for( int j = 0; j < 3; ++j ) { VBox->addWidget(buttons[j]); }
95
4.2
1542.book Seite 96 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
20 21 22 23
// eine Box mit Label um die Buttons QGroupBox *VGroup = new QGroupBox("Seite 3"); VGroup->setLayout(VBox); // Group-Box mit drei Buttons erzeugen ----- Ende
24 25 26 27 28 29 }
// neue Seite mit der Group-Box hinzufügen w->addStapel("Seite 3", VGroup); // alles Anzeigen w->show(); return app.exec();
Zunächst wird in Zeile 6 unser Stapel-Widget erzeugt. In Zeile 8 wird das erste Widget dem Stapel hinzugefügt. Der Name, mit dem das Widget im Stapel über die Combo-Box aufgerufen werden kann, lautet »Seite«, und das Widget ist ein einfaches Textlabel (QLabel). Dasselbe geschieht in Zeile 10 mit einem Button. Praxisnäher wird es dann zwischen den Zeilen 11 bis 22. Hier verwenden wir eine vertikale Box, die drei Buttons enthält, außerdem in eine Group-Box gesteckt und anschließend (Zeile 25) dem Stapel hinzugefügt wird. Natürlich können Sie auch eigene Klassen erzeugen – alles, was irgendwie mit QWidget verwandt ist. Weitere indirekte Layout-Klassen (QSpacerItem und QWidgetItem) Beide Layout-Klassen werden normalerweise nie direkt verwendet. Die Klasse QSpacerItem erzeugt einen leeren Raum in einem Layout. Qt’s eingebaute Layout-Manager verwenden im Allgemeinen folgende Methoden, um den Leerraum im Layout zu verändern: Klasse
Methoden
QBoxLayout, QVBoxLayout, QHBoxLayout,
addSpacing(), addStretch(), insertSpacing(), insertStretch()
QGridLayout
setRowMinimumHeight(), setRowStretch(), setColumnMinimumWidth(), setColumnStretch()
Tabelle 4.6
Direkte Alternativen für QSpacerItem
Ähnlich wie die Klasse QSpacerItem wird die Klasse QWidgetItem nie direkt genutzt. QWidgetItem ist eine Layout-Klasse, die ein Widget repräsentiert. Auch hier werden die Built-in-Layout-Manager verwendet, um das Layout der Widgets zu verändern (siehe Tabelle 4.7).
96
1542.book Seite 97 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
Klasse
Methode
QBoxLayout, QVBoxLayout, QHBoxLayout
addWidget(), insertWidget(), setStretchFactor()
QGridLayout
addWidget()
QStackedLayout
addWidget(), insertWidget(), widget(), currentWidget(), setCurrentWidget()
Tabelle 4.7
Direkte Alternativen für QWidgetItem
Qt::Alignment Viele Layout-Manager verfügen über die Option, zusätzlich einen Parameter Qt::Alignment zu verwenden, um weitere Anpassungen vornehmen zu können. Dieser enum-Typ enthält horizontale und vertikale Flags, die sich auch mit dem bitweisen ODER (falls sinnvoll) kombinieren lassen. Die horizontalen Flags sind: Konstante
Beschreibung
Qt::AlignLeft
zur linken Ecke ausgerichtet
Qt::AlignRight
zur rechten Ecke ausgerichtet
Qt::AlignHCenter
zentriert im vorhandenen Platz
Qt::AlignJustify
einen Text bei vorhandenem Platz im Block ausrichten
Tabelle 4.8
Horizontale Flags
Nun zu den vertikalen Flags: Konstante
Beschreibung
Qt::AlignTop
nach oben ausgerichtet
Qt::AlignBottom
nach unten ausgerichtet
Qt::AlignVCenter
vertikal zentriert im vorhandenen Platz
Tabelle 4.9
Vertikale Flags
Außerdem gibt es ein Flag, das Sie für horizontale und vertikale Ausrichtungen verwenden können: Konstante
Beschreibung
Qt::AlignCenter
Entspricht AlignVCenter | AlignHCenter und zentriert in beide Richtungen.
Tabelle 4.10
Horizontale und vertikale Flags
97
4.2
1542.book Seite 98 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Eigene Layout-Manager erstellen Als Alternative zu den bisher erwähnten und gezeigten Layout-Managern können Sie natürlich auch Ihren eigenen erstellen. Zu diesem Zweck müssen Sie Folgendes definieren: 왘
Die neue von QLayout abgeleitete Klasse.
왘
Jedes Element, das hinzugefügt wird, ist Typ der Klasse QLayoutItem.
왘
Um ein neues Element hinzuzufügen, benötigen Sie eine Methode addItem(), die als Parameter QLayoutItem erhält.
왘
Eine Methode setGeometry() zum Aufführen des Layouts.
왘
Eine Methode sizeHint(), die die Größe des Layouts bearbeitet.
왘
Eine Methode itemAt().
왘
Eine Methode takeAt(), um ein Layoutelement zu entfernen.
왘
Meistens auch die Methode minimumSize().
Ein Beispiel kann ich mir hier ersparen, da Qt mit »Border Layout« und »Flow Layout« zwei Beispiele mitliefert, die das Beschriebene in der Praxis demonstrieren (siehe auch Buch-DVD). Manuelles Layout Bei den vielen Layout-Hilfen, die Qt anbietet, stellt sich sicherlich die Frage, wie die klassische oder gerne auch manuelle Version zur Erstellung von Layouts funktioniert. Zu diesem Zweck legt man ein Eltern-Widget der Klasse QWidget als Basisklasse mit einer festen Größe (Höhe und Breite) an. In QWidget ist die Methode setGeometry() definiert, um ein Widget auf das Eltern-Widget zu »kleben«. Die Methode setGeometry() hat vier Parameter: Die beiden ersten entsprechen der relativen x- und y-Position im Eltern-Widget. Parameter drei und vier entsprechen der Höhe und Breite, die das Widget im Eltern-Widget einnehmen soll. In diesem Zusammenhang ein einfaches Beispiel: ein simples Fenster mit vier Buttons. Das Grundgerüst: 00 01 02 03 04
// beispiele/manLayout/mylayout.h #ifndef MYLAYOUT_H #define MYLAYOUT_H #include #include
05 class ManualLayout : public QWidget { 06 public: ManualLayout();
98
1542.book Seite 99 Montag, 4. Januar 2010 1:02 13
Widgets anordnen – das Layout
07 }; 08 #endif
Nun zur Implementierung des manuellen Layouts der Klasse ManualLayout: 00 // beispiele/manLayout/mylayout.cpp 01 #include "mylayout.h" 02 // neue Widget-Klasse vom eigentlichen Widget ableiten 03 ManualLayout::ManualLayout() { 04 setFixedSize(250, 200 ); 05 QPushButton *button01 = new QPushButton("Button01", this); 06 QPushButton *button02 = new QPushButton("Button02", this); 07 QPushButton *button03 = new QPushButton("Button03", this); 08 QPushButton *button04 = new QPushButton("Button04", this); 09 button01->setGeometry( 20, 20, 100, 20 ); 10 button02->setGeometry( 20, 50, 100, 20 ); 11 button03->setGeometry( 130, 20, 100, 20 ); 12 button04->setGeometry( 130, 50, 100, 20 ); 13 setWindowTitle("Manuelles Layout"); 14 }
Zunächst weisen Sie dem Layout mit setFixedSize() (Zeile 4) eine feste und unveränderliche Größe zu. Anschließend platzieren wir die vier Buttons auf dieser Fläche. Die linke obere Ecke von »button01« befindet sich 20 Pixel vom linken und 20 Pixel vom oberen Seitenrand entfernt. Der Button wird 100 Pixel breit und 20 Pixel hoch. Gleiches geschieht mit den drei anderen Buttons, nur mit anderen Werten. Dieses manuelle Layout ergibt folgende Abbildung:
Abbildung 4.26
Manuelles Layout
99
4.2
1542.book Seite 100 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Hierzu noch eine main-Funktion zum Testen des Programmbeispiels: 00 // beispiele/manLayout/main.cpp 01 #include 02 #include "mylayout.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 ManualLayout* window = new ManualLayout; 06 window->show(); 07 return app.exec(); 08 }
Dem einen oder anderen mag die manuelle Erstellung von Layouts einfach und praktisch erscheinen, doch ist es auf Anhieb recht schwierig. Meistens artet dieser Versuch in eine wilde Probiererei aus. Kommt ein neues Widget hinzu, müssen oft alle anderen Widgets ebenfalls neu positioniert werden. Außerdem fehlt häufig die Möglichkeit, die Widgets schrumpfen oder wachsen zu lassen, was vielleicht bei Button-Widgets nicht wichtig erscheint, bei vielen anderen Widgets aber mehr oder minder »Pflicht« ist.
4.3
Erstellen von Dialogen (QDialog)
Ein Dialogfenster ist ein Top-Level-Fenster, das man gewöhnlich für kurze Anwendereingaben, Einstellungen (Konfigurationen) oder auch nur Informationen für den Anwender verwendet. Es basiert auf der Klasse QDialog, die wiederum von QWidget abgeleitet ist (siehe Abbildung 4.27). Qt bietet hierfür natürlich auch eine Menge vordefinierter Dialoge wie bspw. User-Eingaben von Zahlen oder Text, Farbauswahl, Dateiauswahl, Schriftauswahl usw. Wir gehen darauf allerdings erst in Abschnitt 4.4 näher ein. Zunächst beschreiben wir die »Basisklasse« aller Dialoge mit QDialog.
QObject
QPaintDevice
QWidget
QDialog
Abbildung 4.27
100
Klassenhierarchie von QDialog
1542.book Seite 101 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
Ein solcher Dialog mit QDialog kann modal oder eben nichtmodal sein. Modal bedeutet, dass der Dialog immer vor dem Elternfenster liegt und das Elternfenster so lange blockiert, bis der Dialog geschlossen ist. Damit zwingt man den Anwender sozusagen, die Interaktion erst auszuführen, bevor er Zugriff auf andere Fenster der Anwendung hat. Der übliche Weg, einen modalen Dialog anzuzeigen, besteht darin, die exec()Funktion zu verwenden. Bei QDialog ist diese Methode auch als Slot implementiert. Wenn der Anwender den Dialog schließt, liefert exec() einen Rückgabewert zurück. Üblicherweise wird ein Standard-Button (bspw. Ok) mit accept() und ein weiterer Button mit reject() (bspw. Abbrechen) verknüpft. Modales Dialogfenster Alternativ zu exec() kann auch die Methode setModal(true) und dann show() aufgerufen werden, um ein modales Dialogfenster anzuzeigen. Im Gegensatz zeigt man ein nichtmodales Dialogfenster an, indem man die Methode setModal(false) verwendet und anschließend show() aufruft.
Hierzu wieder ein einfaches Beispiel: Wir erstellen ein simples Fenster (QWidget) mit einem Button. Betätigt man den Button, wird ein Dialogfenster angezeigt. Zunächst das Grundgerüst für das Top-Level-Fenster: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
// beispiele/dialog1/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include "mydialog.h" class MyWidget : public QWidget { Q_OBJECT public: MyWidget( const char* qstr ="Bitte Button betätigen", QWidget *parent = 0 ); private slots: void checkInputDialog(); private: QPushButton *button0; QLabel* label1, *label2; QVBoxLayout* layout; MyDialog* dialog; }; #endif
101
4.3
1542.book Seite 102 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Nach dem Grundgerüst des Hauptfensters folgt das Grundgerüst der Headerdatei mydialog.h (Zeile 7) für den Dialog, den Sie in Zeile 18 von mywidget.h vorfinden: 00 01 02 03 04 05 06
// beispiele/dialog1/mydialog.h #ifndef MYDIALOG_H #define MYDIALOG_H #include #include #include #include
07 08 09 10 11 12
class MyDialog : public QDialog { Q_OBJECT public: MyDialog(); }; #endif
Natürlich müssen Sie hierbei auch das Makro Q_OBJECT (Zeile 8) verwenden, wie für alle direkten (und indirekten) Ableitungen von Basisklassen (hier QObject), da sonst das Signal-Slot-Konzept nicht funktionieren verwendet. Dies ist »beliebter« Fehler, der oft nicht gleich bemerkt wird, weil weder Compiler noch Linker eine Fehlermeldung ausgeben. Eingerichtete Signal-Slot-Verbindungen werden ohne Makros einfach ignoriert. Weiter mit der Implementierung der Klasse MyWidget, unserem Top-LevelFenster: 00 01 02 03
// beispiele/dialog1/mywidget.cpp #include "mywidget.h" #include "mydialog.h" #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( const char* lab, QWidget *parent): QWidget(parent) { 06 // Elemente des Widgets erzeugen 07 button0 = new QPushButton ("Dialog starten"); 08 layout = new QVBoxLayout(this); 09 label1 = new QLabel(lab); 10 label2 = new QLabel; 11 dialog = new MyDialog; 12 // Programm nicht beenden, wenn Dialog zerstört wird 13 dialog->setAttribute(Qt::WA_QuitOnClose);
102
1542.book Seite 103 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
14 15 16 17
// Elemente des Widgets anordnen/anpassen layout->addWidget(label1); layout->addWidget(button0); layout->addWidget(label2);
18 19
// Signal-Slot-Verbindungen einrichten connect( button0, SIGNAL( clicked() ), dialog, SLOT( exec() ) ); connect( dialog, SIGNAL( accepted() ), this, SLOT( checkInputDialog() ) ); connect( dialog, SIGNAL( rejected() ), this, SLOT( checkInputDialog() ) ); setWindowTitle("Hauptfenster – Anwendung");
20 21 22 23 }
24 void MyWidget::checkInputDialog() { 25 int val = dialog->result(); 26 if( val == QDialog::Accepted ) { 27 label2->setText("\"Ok\" wurde gewählt"); 28 } 29 else if( val == QDialog::Rejected ) { 30 label2->setText("\"Abbrechen\" wurde gewählt"); 31 } 32 }
In Zeile 11 erzeugen wir das neue Dialogfenster. Mit dem Attribut Qt::WA_QuitOnClose, das in Zeile 13 gesetzt wird, legen wir fest, dass beim Beenden des Dia-
logfensters nicht gleich die ganze Anwendung endet. Bei der Ausführung sieht dieses Fenster folgendermaßen aus:
Abbildung 4.28
Das Hauptfenster bei der Ausführung
Die Signal-Slot-Verbindung für den Button legen wir in Zeile 19 fest. Erhält der Button das Signal clicked(), wird der Slot exec() von QDialog ausgeführt. Damit zeigt man ein modales Dialogfenster an (siehe Abbildung 4.29). In Zeile 20 und 22 richten wir zwei weitere Signal-Slot-Verbindungen ein. Die erste SignalSlot-Verbindung von Zeile 20 wird aktiv, wenn beim Dialogfenster das Signal accepted() ausgelöst wurde. Die andere Signal-Slot-Verbindung (Zeile 22) wird beim Signal rejected() aktiv. Für beide Signale wird unsere eigene Slot-
103
4.3
1542.book Seite 104 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode checkInputDialog() aufgerufen. Wie die Signale ausgelöst werden, erfahren Sie in Kürze. In unserer eigenen Slot-Methode checkInputDialog() werten wir in Zeile 25 das Ergebnis des Signals mit der Methode result() aus. Hierbei wird entweder der enum-Typ Accepted oder Rejected von QDialog::DialogCode zurückgegeben. Entsprechend dem Rückgabewert von result() wird das Label in Zeile 27 oder 30 gesetzt. Um mehr Licht ins Dunkel zu bringen, sehen wir uns die Implementierung des Dialogfensters an: 00 // beispiele/dialog1/mydialog.cpp 01 #include "mydialog.h" 02 // neue Widget-Klasse vom eigentlichen Widget ableiten 03 MyDialog::MyDialog() { 04 setFixedSize ( 150, 100 ); 05 QVBoxLayout *vbox = new QVBoxLayout; 06 QLabel *label = new QLabel("Bitte Button betätigen"); 07 QPushButton *button01 = new QPushButton("Ok"); 08 QPushButton *button02 = new QPushButton("Abbrechen"); 09 10 11 12 13 14 15
button01->setDefault(true); vbox->addWidget(label); vbox->addWidget(button01); vbox->addWidget(button02); setLayout(vbox); connect( button01, SIGNAL( clicked() ), this, SLOT( accept() ) ); connect( button02, SIGNAL( clicked() ), this, SLOT( reject() ) );
16 }
Bei der Ausführung sieht dieser Dialog folgendermaßen aus:
Abbildung 4.29
Der Dialog bei der Ausführung
In Zeile 9 legen wir den Ok-Button als Standard-Button fest. Dies bedeutet: Drückt der Anwender auf (¢), wird der Ok-Button als gedrückt ausgelöst.
104
1542.book Seite 105 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
In Zeile 14 wird die Signal-Slot-Verbindung des Ok-Buttons eingerichtet. Erhält dieser Button das clicked()-Signal, wird der Slot accept() von QDialog ausgeführt – was bei der Klasse MyWidget das Signal accepted() der entsprechenden Signal-Slot-Verbindung auslöst. Gleiches gilt für die Signal-Slot-Verbindung in Zeile 15, doch gilt es hier für das Signal rejected() auf der anderen Seite. In beiden Fällen wird hierbei die SlotMethode checkInputDialot() von MyWidget aufgerufen und entsprechend ausgewertet. Auch der Slot reject() wird ausgeführt, wenn der Anwender die Escape-Taste betätigt oder das Fenster schließt. Beidem entspricht hierbei das »Anklicken« des Abbrechen-Buttons. Wurde bspw. der Ok-Button betätigt, wird das Dialogfenster wieder geschlossen und im Hauptfenster das entsprechende Label gesetzt (siehe Abbildung 4.30).
QObject
QPaintDevice
QWidget
QDialog
Abbildung 4.30
Der Ok-Button wurde beim Dialog ausgewählt
Beachten Sie, dass das Dialogfenster hierbei nur »versteckt« und nicht zerstört wird. Nochmals genauer: Wurde der Ok-Button betätigt, werden das Signal clicked() ausgelöst und der Slot accept() ausgeführt. Bei MyWidget wiederum löst das Signal accepted() aus, und der eigene Slot checkInputDialog() startet. Darin überprüft die Methode result(), ob Accepted oder Rejected betätigt wurde. Accepted wurde in unserem Fall an den Ok-Button und Rejected an den Abbrechen-Button des Dialogs vergeben. Analoges geschieht beim Betätigen des Abbrechen-Buttons, nur eben mit reject() und rejected(). Hierzu noch eine main()-Funktion, womit Sie das Ganze in der Praxis testen können: 00 // beispiele/dialog1/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[])
{
105
4.3
1542.book Seite 106 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
04 05 06 07 08 }
QApplication app(argc, argv); MyWidget* window = new MyWidget; window->show(); return app.exec();
In unserem Beispiel wurde der Dialog folgendermaßen gestartet: 11 12 13 ... 18 19
dialog = new MyDialog; // Programm nicht beenden, wenn Dialog zerstört wird dialog->setAttribute(Qt::WA_QuitOnClose); // Signal-Slot-Verbindungen einrichten connect( button0, SIGNAL( clicked() ), dialog, SLOT( exec() ) );
Wurde der Button betätigt, tritt das Signal clicked() ein. Letzteres wurde mit dem Objekt dialog und dem Slot exec() verbunden. Einen Dialog können Sie auch folgendermaßen starten (was durchaus gängige Praxis ist): 01 02 03 04 05 06
dialog = new MyDialog; // Programm nicht beenden, wenn Dialog zerstört wird dialog->setAttribute(Qt::WA_QuitOnClose); // Dialog in eigener Event-Loop eintreten lassen int status = dialog->exec(); // ... ist Dialog fertig, geht’s hier weiter
Nachdem hier eine Instanz des Dialogs erzeugt wurde, lassen wir diesen in Zeile 5 mittels exec() in eine Ereignisschleife eintreten. Schließt jetzt der Benutzer den Dialog bzw. das Fenster, kehrt die Ereignisschleife der Anwendung zurück – genauer: Die Anwendung des Programms fährt hinter dem exec() (im Code-Ausschnitt Zeile 6) fort. Was zurückgegeben wird, bestimmt auch hier wieder der Slot; wie bereits erläutert, bietet QDialog hierbei wiederum accept() und reject() vordefiniert an. Hierzu nun ein Überblick über die gängigsten Methoden, Signale und Slots der Klasse QDialog. Methode
Beschreibung
bool isSizeGripEnabled() const;
Überprüft, ob sich in der unteren rechten Ecke ein QSizeGrip-Widget befindet, womit die Größe des Dialogfensters verändert werden kann. Bei true trifft dies zu, ansonsten wird false zurückgegeben.
Tabelle 4.11
106
Gängige Methoden von QDialog
1542.book Seite 107 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
Methode
Beschreibung
void setSizeGripEnabled(bool);
Damit können Sie ein QSizeGrip-Widget zum Verändern der Größe an der rechten unteren Seite des Dialogfensters mit true hinzufügen bzw. mit false entfernen.
void setModal(bool modal);
Damit können Sie einen Dialog mit true auf Modal bzw. mit false auf Nichtmodal setzen.
Tabelle 4.11
Gängige Methoden von QDialog (Forts.)
Weiter mit den öffentlichen Slots von QDialog: Slot
Beschreibung
virtual void accept ();
Versteckt den modalen Dialog und setzt den Rückgabewert auf Accepted (1).
virtual void done(int r);
Schließt den Dialog und setzt den Rückgabewert auf r. Wird der Dialog mit exec() angezeigt, beendet done() die Ereignisschleife, und exec() gibt r zurück.
int exec();
Zeigt einen modalen Dialog und blockiert, bis der Anwender diesen schließt. Diese Funktion gibt einen Dialog-Rückgabewert wie Accepted oder Rejected zurück.
virtual void reject ();
Gegenstück zu accept(). Versteckt den modalen Dialog und setzt den Rückgabewert auf Rejected (0).
Tabelle 4.12
Slots von QDialog
Zum Schluss noch eine Auflistung möglicher Signale von QDialog. Signal
Beschreibung
void accepted();
Dieses Signal wird ausgelöst, wenn der Dialog akzeptiert wurde. Dies wird gewöhnlich über accept() oder done() mit QDialog::Accepted als Argument ausgelöst.
void finished(int result);
Wird gesendet, wenn der Rückgabewert des Dialogs gesetzt wurde; normalerweise mit accept(), reject() oder done() ausgelöst.
void rejected();
Wird ausgelöst, wenn der Dialog abgelehnt (Rejected) wurde; normalerweise mit reject() oder done() und QDialog::Reject als Argument ausgelöst.
Tabelle 4.13
Signale von QDialog
107
4.3
1542.book Seite 108 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Den einen oder anderen dürfte jetzt brennend interessieren, wie man einen anderen Wert als Accepted und Rejected aus einem Dialogfenster zurückgibt. Erweitern Sie am besten dazu unseren Dialog MyDialog um einen Slot: 00 01 02 03 04 05 06
// beispiele/dialog1_version2/mydialog.h #ifndef MYDIALOG_H #define MYDIALOG_H #include #include #include #include
07 08 09 10 11 12 13 14
class MyDialog : public QDialog { Q_OBJECT public: MyDialog(); public slots: void mySetResult(); }; #endif
Fügen Sie nun einen neuen Button bei der Definition der Klasse der vertikalen Layout-Box hinzu (siehe Abbildung 4.31): // beispiele/dialog1_version2/mydialog.cpp ... QPushButton *button03 = new QPushButton("Ignorieren"); ... vbox->addWidget(button03); ...
Abbildung 4.31 Ein weiterer Button »Ignorieren« wurde hinzugefügt
Für diese Buttons richten Sie nun eine Signal-Slot-Verbindung zu unserem neu implementierten Slot ein: // beispiele/dialog1_version2/mydialog.cpp ...
108
1542.book Seite 109 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
connect( button03, SIGNAL ( clicked()), this, SLOT( mySetResult() ) ); ...
Die Implementierung des Slots, der aufgerufen wird, wenn der neue Button betätigt wurde, sieht folgendermaßen aus: // beispiele/dialog1_version2/mydialog.cpp ... void MyDialog::mySetResult() { int result = 99; emit done(result); }
Hierbei rufen Sie die echte Slot-Methode done() mit dem Rückgabewert 99 auf, womit der Dialog geschlossen wird. Jetzt fügen wir in mywidget.h einen weiteren Slot hinzu – genauer: wir überladen einfach den alten Slot mit einem Integer als Argument: // beispiele/dialog1_version2/mywidget.h ... private slots: void checkInputDialog(); void checkInputDialog(int); ...
Natürlich richten wir auch hier eine neue Signal-Slot-Verbindung ein, die auf den Slot done(int) der Klasse MyDialog reagiert. Hierfür eignet sich das Signal finished(int), das ebenfalls einen Integer als Parameter hat. Als Slot für das Signal verwenden wir wieder unsere selbst implementierte und überladene SlotMethode: // beispiele/dialog1_version2/mywidget.cpp ... connect( dialog, SIGNAL ( finished( int )), this, SLOT( checkInputDialog( int ) ) ); ...
Nun gilt es noch die Definition der überladenen Slot-Methode hinzuzufügen: // beispiele/dialog1_version2/mywidget.cpp ... void MyWidget::checkInputDialog(int val) { //val = dialog->result(); if( val == 99 ) {
109
4.3
1542.book Seite 110 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
label2->setText("\"Ignorieren\" wurde gewählt"); } }
Geschafft! Mehr ist nicht nötig, um auch auf andere Rückgabewerte als Accepted oder Rejected zu reagieren. Im Beispiel wurde auf den Rückgabewert 99 reagiert.
Abbildung 4.32 Reagiert nun auch auf andere Rückgabewerte
Listing auf Buch-DVD Sie finden das Beispiel ebenfalls im Verzeichnis beispiele/dialog1, aber in einem weiteren Ordner namens version2.
4.3.1
Benutzerfreundlichkeit von Dialogen
Viele Programmierer tendieren dazu, in ihre Dialoge etwas mehr zu packen, als erforderlich ist. Viele Optionen und Erweiterungen werden aber nur selten verwendet. Hierbei sollte man bereits zu Beginn darauf achten, die Dialogfenster mit einem vernünftigen Standardwert vorzubelegen. Benötigt der Anwender dann doch weitere selten genutzte Optionen, bietet Qt hierzu über die Schnittstelle von QDialog Erweiterungen (Extensions) an. Als Beispiel habe ich unsere Klasse MyDialog hierzu nochmals um den folgenden Button erweitert:
Abbildung 4.33
110
Button »Weiteres« hinzugefügt
1542.book Seite 111 Montag, 4. Januar 2010 1:02 13
Erstellen von Dialogen (QDialog)
Bei diesem Dialog sehen Sie außer den bereits bekannten Buttons einen Button »Weiteres«. Wird er betätigt, sieht der Anwender die Erweiterung des Dialogs (vgl. Abbildung 4.34).
Abbildung 4.34
Erweiterung des Dialogs ausgefahren
Dazu bedarf es im Grunde nicht viel. Sie müssen nur das oder die entsprechenden Widget(s) (ausgehend vom Eltern-Widget) als Parameter in der Funktion setExtensions() verwenden. Sehen Sie sich hierzu einfach den Quellcode an. Im Beispiel musste hierfür nur die Datei mydialog.cpp erweitert werden: 00 // beispiele/dialog1_version3/mydialog.cpp 01 #include "mydialog.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyDialog::MyDialog() { 05 setFixedSize ( 200, 180 ); 06 07 08 09 10 11 12
QVBoxLayout *vbox = new QVBoxLayout; QVBoxLayout *VBox = new QVBoxLayout; QLabel *label = new QLabel("Bitte Button auswählen"); QPushButton *button01 = new QPushButton("Ok"); QPushButton *button02 = new QPushButton("Abbrechen"); QPushButton *button03 = new QPushButton("Ignorieren"); QPushButton *button04 = new QPushButton("Weiteres>>");
13 14
QPushButton *but01 = new QPushButton("Button01"); QPushButton *but02 = new QPushButton("Button02");
111
4.3
1542.book Seite 112 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
15 16
QPushButton *but03 = new QPushButton("Button03"); QPushButton *but04 = new QPushButton("Button04");
17 18 19 20 21 22 23 24 25
// Buttons vertikal anordnen VBox = new QVBoxLayout; VBox->addWidget(but01); VBox->addWidget(but02); VBox->addWidget(but03); VBox->addWidget(but04); // eine Box mit Label um die Buttons QGroupBox* VGroup = new QGroupBox("Erweitert"); VGroup->setLayout(VBox);
26 27
setExtension(VGroup); setOrientation(Qt::Vertical);
28 29
button02->setDefault(true); button04->setCheckable(true);
30 31 32 33 34 35
vbox->addWidget(label); vbox->addWidget(button01); vbox->addWidget(button02); vbox->addWidget(button03); vbox->addWidget(button04); setLayout(vbox);
36
connect( button01, SIGNAL( clicked() ), this, SLOT( accept() ) ); connect( button02, SIGNAL( clicked() ), this, SLOT( reject() ) ); connect( button03, SIGNAL ( clicked()), this, SLOT( mySetResult() ) ); connect( button04, SIGNAL( toggled(bool) ), this, SLOT( showExtension(bool) ) );
37 38 39 40 }
41 void MyDialog::mySetResult() { 42 int result = 99; 43 emit done(result); 44 }
Zunächst erzeugen Sie hierbei einen gewöhnlichen Button (Zeile 12), von Zeile 13 bis 25 vier weitere Buttons, die Sie in eine vertikale Box stecken. Letztere packen Sie in eine Group-Box. In Zeile 26 erklären Sie diese Group-Box mittels
112
1542.book Seite 113 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
setExtension() als eine Erweiterung (Extensions). In Zeile 27 (setOrientation()) geben Sie an, zu welcher Seite die Erweiterung ein-/ausgeklappt wer-
den soll. In diesem Beispiel erstellen wir eine vertikale Erweiterung. In Zeile 29 erklären wir unseren Button als Toggle-Button, was bedeutet, dass er bei seiner Betätigung in diesem Zustand (toggled == geschaltet) verharrt. Er kommt natürlich wie ein gewöhnlicher Button zu unserem Dialog (Zeile 34). In Zeile 39 verbinden wir den Button dann mit unserem Dialogfenster. Hierbei reagieren wir auf das Signal toogled(bool) – ob sich der Button also in einem gedrückten (bool=true) oder ungedrückten (bool=false) Zustand befindet. Als Slot verwendet wird showExtension(bool), ebenfalls ein Slot von QDialog. Befindet sich der Button dann im gedrückten Zustand, wird vom Signal toggled() true an showExtension() übergeben. In diesem Fall wird der Dialog erweitert gezeigt. Andersherum eben umgekehrt.
4.4
Vorgefertigte Dialoge
In Abschnitt 4.3 erwähnten wir, dass Qt eine Menge vorgefertigter Dialoge anbietet. Üblicherweise findet man in einem Buch zu einer GUI zuerst die Widgets beschrieben und anschließend die vorgefertigten Dialog-Boxen. Wir möchten genau umgekehrt verfahren. Bevor Sie die einzelnen Widgets kennenlernen, mit denen wir auch eigene Dialoge entwerfen, wollen wir Ihnen die vorgefertigten Dialog von Qt auflisten und auch demonstrieren, um bereits Bekanntes nicht zu wiederholen und zu verhindern, dass Sie einen Dialog erstellen, den es in ähnlicher Form schon gibt.
4.4.1
QMessageBox – Nachrichtendialoge
Relativ häufig wird die von QDialog abgeleitete Klasse QMessageBox eingesetzt, um dem Anwender kurze Informationen zu übermitteln oder ihn zu veranlassen, bestimmte Entscheidungen zu treffen. Gewöhnlich wird dieser modale Dialog mit einer kurzen Nachricht, einem Icon und Buttons angezeigt. Das Aussehen der Icons bzw. des Buttons hängt vom aktuellen Fenster-Stil und z. T. auch vom System ab. Der einfachste Weg, eine solche Nachrichtenbox anzuzeigen, besteht darin, die statischen Funktionen QMessageBox::information(), QMessageBox::question(), QMessageBox::critical() und QMessageBox::warning() zu verwenden. Bspw. folgende Nachrichtenbox:
113
4.4
1542.book Seite 114 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Abbildung 4.35
QMessage::warning()
Diese Nachrichtenbox (Abbildung 4.35) anzuzeigen, setzt Folgendes voraus: int ret = QMessageBox::warning( this, "Beenden?", "Wollen Sie die Anwendung wirklich beenden?", QMessageBox::Yes | QMessageBox::No );
Alternativ können Sie den Konstruktor von QMessageBox verwenden, um eine Nachrichtenbox mit Icon und Button(s) zu erzeugen. Bezogen auf Abbildung 4.35 sähe dies folgendermaßen aus: QString caption("Beenden?"); QString text("Wollen Sie die Anwendung wirklich beenden?"); QMessageBox msg( QMessageBox::Warning, caption, text, QMessageBox::Yes | QMessageBox::No ); msg.exec();
Mit dem ersten Argument geben Sie hier das Icon für die Nachrichtenbox an. Hierbei können Sie unter den folgenden vier Symbolen auswählen (von denen Sie QMessageBox::Warning bereits kennen): Konstante
Beschreibung
QMessageBox::Critical
Wird verwendet, um schwerwiegende Fehler anzuzeigen.
QMessageBox::NoIcon
Soll kein Icon angezeigt werden, können Sie diese Konstante verwenden.
QMessageBox::Information
Wird verwendet, um eine Information anzuzeigen.
QMessageBox::Question
Wird verwendet, wenn bei einer Dialogbox Fragen gestellt werden.
QMessageBox::Warning
Sollte eine gefährliche Aktion ausgeführt werden, können Sie dieses Symbol verwenden.
Tabelle 4.14
Icon-Konstanten für QMessageBox
Sollten Sie ein eigenes Icon hinzufügen wollen, steht Ihnen dafür die Methode setIconPixmap() zur Verfügung.
114
1542.book Seite 115 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Mit den Argumenten zwei und drei von QMessageBox geben Sie den Fenstertitel und den eigentlichen Nachrichtentext an. Der Nachrichtentext kann, wie übrigens alle Qt-Dialoge, mit HTML formatiert werden. Ändern Sie bspw. den String text folgendermaßen ab: QString text( "Wollen Sie die " "Anwendung wirklich beenden?" );
Dadurch erhält der Nachrichtentext in der Nachrichtenbox folgendes Aussehen:
Abbildung 4.36 Mit HTML formatierte Nachrichten
Mit den weiteren Argumenten legen Sie die verschiedenen Buttons fest. Die möglichen Werte und deren Bedeutung finden Sie in Tabelle 4.15 aufgelistet. Konstante
Bedeutung
QMessageBox::Ok
Ok
QMessageBox::Open
Open (Öffnen )
QMessageBox::Save
Save (Speichern)
QMessageBox::Cancel
Cancel (Abbrechen)
QMessageBox::Close
Close (Schließen)
QMessageBox::Discard
Discard (Verwerfen)
QMessageBox::Apply
Apply (Anlegen)
QMessageBox::Reset
Reset (Zurücksetzen)
QMessageBox::RestoreDefaults
RestoreDefaults (Wiederherstellen)
QMessageBox::Help
Help (Hilfe)
QMessageBox::SaveAll
Save all (Alles Speichern)
QMessageBox::Yes
Yes (Ja)
QMessageBox::YesToAll
Yes to all (Ja zu allem)
QMessageBox::No
No (Nein)
QMessageBox::NoToAll
No to all (Nein, zu allem)
QMessageBox::Abort
Abort (Aussteigen)
Tabelle 4.15
Mögliche Texte für den Button
115
4.4
1542.book Seite 116 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Konstante
Bedeutung
QMessageBox::Retry
Retry (Wiederholen)
QMessageBox::Ignore
Ignore (Ignorieren)
QMessageBox::NoButton
–
Tabelle 4.15
Mögliche Texte für den Button (Forts.)
Dialog auch mit leerer QMessageBox Es ist auch möglich, mit einem leeren QMessageBox-Konstruktor einen Nachrichtendialog zu erstellen. Hierbei stehen dem Anwender dann verschiedene set-Zugriffsmethoden zur Verfügung, um Icon, Titel oder Buttons nachträglich anzugeben.
Nachrichtendialog auswerten Auch das Auswerten der Nachrichtendialoge ist recht einfach. Im Grunde müssen Sie einfach überprüfen, ob der Button mit der entsprechenden Konstante (siehe Tabelle 4.15) gedrückt wurde. Um Ihnen das Ganze auch praxisnah zu vermitteln, erstellen wir hierzu wieder ein Beispiel. Zunächst erstellen wir wieder ein einfaches Fenster mit einem Button. Das Grundgerüst: 00 01 02 03 04 05 06 07 08
// beispiele/qmessagebox/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include
09 10 11 12 13 14 15 16 17 18 19
class MyWidget : public QWidget { Q_OBJECT public: MyWidget( const char* qstr ="Bitte Button betätigen", const char* but = "Ende", QWidget *parent = 0 ); private: QPushButton *button0; QLabel* label; QVBoxLayout* layout; public slots:
116
1542.book Seite 117 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
20 void myquit(); 21 }; 22 #endif
Dieses Beispiel haben Sie in ähnlicher Form bereits verwendet. Die Auswertung des Dialogs wird mit der eigenen Slot-Methode myquit() (Zeile 19 und 20) realisiert. Hierzu nun die Implementierung der Klasse MyWidget: 00 // beispiele/qmessagebox/mywidget.cpp 01 #include "mywidget.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyWidget::MyWidget( const char* lab, const char* but, 05 QWidget *parent): QWidget(parent) { 05 // Elemente des Widgets erzeugen 06 button0 = new QPushButton (but); 07 layout = new QVBoxLayout(this); 08 label = new QLabel(lab); 09 // Elemente des Widgets anordnen/anpassen 10 layout->addWidget(label); 11 layout->addWidget(button0); 12 // Signale des Widgets einrichten 13 connect( button0, SIGNAL( clicked() ), this, SLOT( myquit() ) ); 14 } 15 void MyWidget::myquit() { 16 int ret = QMessageBox::warning( 17 this, "Beenden?", 18 "Wollen Sie die Anwendung wirklich beenden?", 19 QMessageBox::Yes | QMessageBox::No ); 20 if( ret == QMessageBox::Yes ) 21 close(); 22 }
Die entscheidende Zeile finden Sie hier zunächst in Zeile 13, wo Sie für den Button des Hauptfensters eine Signal-Slot-Verbindung einrichten. Drückt man den Button (erhält das Signal clicked()), wird der eigene Slot myquit() ausgeführt. In der Slot-Methode myquit() (Zeile 15 bis 22) erzeugen wir zunächst mit der statischen Funktion QMessageBox::warning einen Nachrichtendialog, wie Sie ihn von Abbildung 4.35 her kennen. Die statischen Funktionen von QMessageBox geben als Rückgabewert einen Integer zurück. Welcher Wert bzw., genauer, welcher Button gedrückt wurde, werten wir in Zeile 20 aus. Entspricht der Rück-
117
4.4
1542.book Seite 118 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
gabewert in ret der Konstante QMessageBox::Yes, wurde der Yes-Button (bzw. Ja-Button) betätigt und, wir beenden die Anwendung mit close(). Ansonsten wurde der No-Button (bzw. Nein-Button) gedrückt, was in diesem Fall allerdings nicht mehr ausgewertet werden muss. Ähnlich funktioniert dies natürlich mit Objekten von QMessageBox. Das gleiche Beispiel lässt sich auch folgendermaßen erstellen: // beispiele/qmessagebox/mywidget.cpp ... void MyWidget::myquit() { QString caption("Beenden?"); QString te("Wollen Sie die Anwendung wirklich beenden?"); QMessageBox msg( QMessageBox::Warning, caption, te, QMessageBox::Yes | QMessageBox::No ); if( msg.exec() == QMessageBox::Yes ) close(); }
Sollten Sie mehrere Buttons verwenden und überprüfen wollen, lässt sich auch ein switch()-Schalter verwenden. Bspw.: switch ( msg.exec() ) { case QMessageBox::Yes: // yes wurde betätigt break; case QMessageBox::No: // no wurde betätigt break; default: // Fehler, hierher sollte es nicht gehen break; }
Eigene Schaltflächen für QMessageBox Sollten die vordefinierten Konstanten für die Knöpfe nicht ausreichen, können Sie gerne einen eigenen Button mit der mehrfach überladenen Methode addButton() hinzufügen, dem Button einen eigenen Text geben und diesen mit einer vordefinierten Button-Funktion versehen. Folgende Funktionen können dabei einem Button übergeben werden, was auch nötig ist, um ihn auswerten zu können.
118
1542.book Seite 119 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Konstante
Beschreibung
QMessageBox::InvalidRole
Der Button ist ungültig.
QMessageBox::AcceptRole
Bei Betätigung des Buttons wird der Dialog als akzeptiert (accepted) gewertet (bspw. Ok).
QMessageBox::RejectRole
Bei Betätigung des Buttons wird der Dialog als abgelehnt (rejected) gewertet (bspw. Cancel).
QMessageBox::DestructiveRole
Bei Betätigung des Buttons wird der Dialog als verworfen gewertet (bspw. Discard Changes).
QMessageBox::YesRole
Yes-ähnlicher Button
QMessageBox::NoRole
No-ähnlicher Button
Tabelle 4.16
Einige vordefinierte Button-Funktionen
Keine Signale und Slots QMessageBox besitzt keine Signale und Slots, weshalb Sie auf die vordefinierten But-
ton-Funktion neu zugreifen müssen, sofern Sie eigene verwenden wollen.
In der Praxis wird die Methode addButton() folgendermaßen verwendet: void MyWidget::myquit() { QString caption("Beenden?"); QString te("Wollen Sie die Anwendung wirklich beenden?"); QMessageBox msg( QMessageBox::Warning, caption, te ); QPushButton* yesButton = msg.addButton("Jep", QMessageBox::YesRole); QPushButton* noButton = msg.addButton("Nee", QMessageBox::NoRole); msg.exec(); if( msg.clickedButton() == yesButton ) close(); else if( msg.clickedButton() == noButton ) ;// Nein, wurde ausgewählt }
Wenn Sie wie hier im Beispiel eine Instanz von QMessageBox mit eigenen Buttons verwenden, können Sie den Wert des gedrückten Buttons mit der Methode clickedButton() nach dem Aufruf von exec() abfragen.
119
4.4
1542.book Seite 120 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
So sieht es dann aus:
Abbildung 4.37
Eigene Schaltflächen für QMessageBox
Zum Schluss noch ein kurzer Überblick über die gängigsten Methoden der Klasse QMessageBox. Methode
Beschreibung
QMessageBox ( QWidget * parent = 0 );
Konstruktor. Erzeugt eine Nachrichtenbox ohne Text und ohne Buttons sowie (optional) mit parent als Elternwidget.
QMessageBox ( Icon icon, const QString & title, const QString & text, StandardButtons buttons = NoButton, QWidget * parent = 0, Qt::WindowFlags f = Qt::Dialog | Qt:: MSWindowsFixedSizeDialogHint);
Konstruktor. Erzeugt eine Nachrichtenbox mit Icon icon, Titel title, Text text, Buttons buttons, Elternwidget parent und Fensterflags f.
~QMessageBox ()
Destruktor. Zerstört eine Nachrichtenbox.
void addButton( QAbstractButton * button, ButtonRole role );
Fügt den übergebenen Button button an den Nachrichtendialog mit der Funktion role (Tabelle 4.16).
QPushButton * addButton ( const QString & text, ButtonRole role );
Erzeugt einen Button mit dem übergebenen String text und der Funktion role (Tabelle 4.16) und fügt diese dem Nachrichtendialog hinzu. Zurückgegeben wird der erzeugte Button.
QPushButton * addButton ( StandardButton button );
Fügt einen Standard-Button (Tabelle 4.15) dem Nachrichtendialog hinzu und gibt diesen als Rückgabewert zurück.
QAbstractButton * button ( StandardButton which ) const;
Gibt einen Zeiger auf dem vorbelegten Standardbutton which zurück. Existiert dieser Button nicht im Nachrichtendialog, wird 0 zurückgegeben.
Tabelle 4.17
120
Gängige Methode für QMessageBox
1542.book Seite 121 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Methode
Beschreibung
QAbstractButton* clickedButton() const;
Gibt den vom Anwender gedrückten Button zurück, oder 0, wenn er die Escape-Taste gedrückt hat und kein Escape-Button gesetzt wurde.
QPushButton * defaultButton () const;
Gibt den Button zurück, der im Nachrichtendialog als Standard-Button vorbelegt ist (wenn bspw. (¢) gedrückt wird). Wurde kein Standard-Button gesetzt, wird 0 zurückgegeben.
QString detailedText () const
Gibt den detaillierten Text innerhalb des Nachrichtendialogs zurück.
QAbstractButton* escapeButton() const;
Gibt den Button zurück, der im Nachrichtendialog als Escape-Button vorbelegt ist (wenn bspw. (¢) gedrückt wird). Wurde kein Standard-Button gesetzt, wird 0 zurückgegeben.
Icon icon () const;
Gibt das aktuelle Icon des Nachrichtendialogs zurück.
QPixmap iconPixmap () const;
Gibt das aktuelle Icon-Pixmap des Nachrichtendialogs zurück.
void removeButton ( QAbstractButton * button );
Entfernt den Schalter button aus dem Nachrichtendialog.
void setDefaultButton ( QPushButton * button );
Setzt den Schalter button als Standard-Button im Nachrichtendialog.
void setDetailedText ( const QString & text );
Setzt den detaillierten Text innerhalb des Nachrichtendialogs auf text.
void setEscapeButton ( QAbstractButton * button );
Setzt den Schalter button als Escape-Button im Nachrichtendialog.
void setIcon ( Icon );
Setzt das Icon im Nachrichtendialog auf Icon.
void setIconPixmap ( const QPixmap & pixmap );
Setzt das Icon-Pixmap im Nachrichtendialog auf pixmap.
void setText ( const QString & text );
Setzt den Text des Nachrichtendialogs auf text.
void setWindowModality ( Qt::WindowModality windowModality );
Setzt die Modalität des Nachrichtendialogs auf einen Wert der enum-Variablen Qt::WindowModality.
void setWindowTitle ( const QString & title );
Setzt den Fenster-Titel des Nachrichtendialogs auf title.
QString text () const;
Gibt den Text des Nachrichtendialogs zurück.
Tabelle 4.17
Gängige Methode für QMessageBox (Forts.)
121
4.4
1542.book Seite 122 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
4.4.2
QFileDialog – Dialoge zur Dateiauswahl
Die Klasse QFileDialog wurde ebenfalls von QDialog abgeleitet und zeigt einen Dialog an, mit dem es möglich ist, eine Datei oder ein Verzeichnis auszuwählen. Wie auch schon bei QMessageBox bietet QFileDialog zwei Wege an: Zum einen sind auch hier wieder vorgefertigte statische Methoden zur Erzeugung vorhanden, und zum anderen ist es natürlich auch hiermit wieder möglich, eine Instanz der Klasse über den Konstruktor zu erzeugen. Der einfachste Weg, einen solchen Dateiauswahl-Dialog zu erzeugen, bietet wieder die statische Möglichkeit an. Je nach Betriebssystem wird hierbei immer der native Dateiauswahl-Dialog angezeigt. Das bedeutet: Damit bekommt der Anwender den Dialog zu sehen, den er auf seinem System gewohnt ist. Es wird also der Dateiauswahl-Dialog des Betriebssystems verwendet. Dies kann ggf. auch vermieden werden, wenn die Option QtFileDialog::DontUseNativeDialog gesetzt wird. Am besten hierzu ein einfaches Beispiel, in dem wir wieder die Slot-Methode MyWidget::myquit() aus dem Beispiel zuvor umschreiben: // beispiele/qfiledialog/mywidget.cpp ... #include #include ... void MyWidget::myquit() { QString file = QFileDialog::getOpenFileName( this, "Bitte eine Datei auswählen", QDir::homePath(), "Dokumente (*.pdf *ps *doc)" ); if( !file.isNull() ) { QString info("Folgende Datei wurde ausgewählt:\n"); info.append(file); QMessageBox::information( this, "Ihre Auswahl", info, QMessageBox::Ok ); } }
Je nach System, auf dem Sie das Beispiel ausführen, sieht der Dateiauswahl-Dialog folgendermaßen aus (siehe Abbildung 4.38). Sollten Sie eine Datei (hier eine mit der Endung PDF, PS oder DOC) ausgewählt haben (wird überprüft mit !file.isNull()), wird dies im folgenden Nachrichtendialog angezeigt. Hierzu eine etwas genauere Beschreibung.
122
1542.book Seite 123 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Abbildung 4.38
Dateiauswahl-Dialog (QFileDialog::getOpenFileName)
Nachdem Sie in QFileDialog::getOpenFileName mit dem ersten Parameter das Eltern-Widget angegeben haben, wird mit dem zweiten Parameter der Titel des Fensters vergeben. Beachten Sie Folgendes: Wenn Sie beim ersten Parameter 0 angeben, wird der Dialog nichtmodal angezeigt. Mit dem dritten Parameter können Sie das Startverzeichnis angeben. Hierbei sind natürlich auch relative und absolute Pfadnamen erlaubt. Im Beispiel haben wir mit QDir::homePath() eine statische Methode der Klasse QDir verwendet. Unter Linux/Unix und auch Mac OS X wird hierbei die Umge-
bungsvariable HOME verwendet. Ist dies nicht gesetzt, wird das Wurzelverzeichnis verwendet. Unter MS-Windows wird ebenfalls nach einer Umgebungsvariablen HOME gesucht. Existiert diese nicht, wird die Umgebungsvariable USERPROFILE verwendet. Existiert auch diese nicht, verwendet Qt HOMEDRIVE und HOMEPATH. QDir bietet hierzu noch weitere statische Methoden an, welche in der Tabelle 4.18 aufgelistet sind. Statische Methode
Beschreibung
QDir::currentPath()
Gibt das aktuelle Verzeichnis der Anwendung zurück.
QDir::rootPath()
Gibt das oberste Verzeichnis zurück. Unter Windows ist dies bspw. C:\, und unter Linux/Unix/Mac OS X wird / zurückgegeben.
QDir::tempPath()
Gibt den Pfad zum temporären Verzeichnis zurück.
Tabelle 4.18
Statische Methoden von QDir
123
4.4
1542.book Seite 124 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Der letzte Parameter von QFileDialog::getOpenFileName ist ein Dateifilter. Die Syntax hierzu ist ganz einfach: "Bezeichner (*.ext1 *.ext2 *.ext3)"
Der »Bezeichner« ist frei wählbar. Dann verwenden Sie die Dateiendungen, die Sie im Filter mit einbeziehen wollen, genauer: die im Dateiauswahl-Dialog angezeigt werden sollen. Wollen Sie mehrere Dateifilter verwenden, müssen Sie nur am Ende der geklammerten Filter zwei Semikolons setzen. Bspw: QString file = QFileDialog::getOpenFileName( this, "Bitte eine Datei auswählen", QDir::homePath(), "Dokumente (*.pdf *ps *doc);;" "Bilder (*.jpg *.png *.gif);;" "Quelldateien (*.c *cc *cpp);;" "Alle Dateien (*.*)");
Nun können Sie über eine Dropdown-Liste die hinzugefügten Filter verwenden (siehe Abbildung 4.39).
Abbildung 4.39
Mehrere Dateifilter verwenden
Neben der statischen Methode QFileDialog::getOpenFileName gibt es weitere, die Tabelle 4.19 auflistet. Statische Methode
Beschreibung
QString getExistingDirectory ( QWidget * parent = 0, const QString& caption = QString(), const QString & dir = QString(), Options options = ShowDirsOnly );
Damit erstellen Sie einen Dateiauswahl-Dialog, der nur Vereichnisse anzeigt und keine Dateien. Daher benötigt diese statische Methode keinen Filter. Zusätzlich wird überprüft, ob ein Verzeichnis tatsächlich existiert.
QStringList getOpenFileNames ( QWidget * parent = 0, const QString & caption = QString(),
Mit dieser Methode können Sie mehrere Dateien auf einmal auswählen (bspw. mit gedrückter (Strg)-Taste). Zurückgegeben wird QStringList (eine Klasse für eine Liste von Strings) mit den ausgewählten Dateien.
Tabelle 4.19
124
Weitere statische Methoden für einen Dateiauswahl-Dialog
1542.book Seite 125 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Statische Methode
Beschreibung
const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0 ); QString getSaveFileName ( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter= 0, Options options = 0 );
Tabelle 4.19
Erstellt einen Dateiauswahl-Dialog zum Speichern und Anlegen von Dateien. Existiert diese Datei, wird vor dem Überschreiben nachgefragt. Sie können dies mit der Option QFileDialog:: DontConfirmOverwrite übergehen.
Weitere statische Methoden für einen Dateiauswahl-Dialog (Forts.)
Natürlich ist es auch möglich, einen eigenen QFileDialog ohne statische Funktionen zu erzeugen. Bezogen auf das statische Beispiel, sieht die Verwendung mit dem Konstruktor der Klasse QFileDialog folgendermaßen aus: // beispiele/qfiledialog2/mywidget.cpp ... void MyWidget::myquit() { QFileDialog* filedlg = new QFileDialog( this, "Bitte eine Datei auswählen"); QStringList filters; filters setViewMode(QFileDialog::Detail); QStringList fileNames; if (filedlg->exec()) fileNames = filedlg->selectedFiles(); QString info("Ihr Auswahl:\n"); info.append(fileNames.join("\n"));
125
4.4
1542.book Seite 126 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QMessageBox::information( this, "Ihre Auswahl", info, QMessageBox::Ok ); }
Mit der Methode setFileMode() geben Sie an, was der Anwender auswählen muss. Mit QFileDialog::ExistingFiles legen Sie fest, dass mehrere vorhandene Dateien ausgewählt werden dürfen. Weitere Modi hierzu: Konstante
Beschreibung
QFileDialog::AnyFile
der Name einer Datei, egal, ob diese existiert oder nicht
QFileDialog::ExistingFile
der Name einer einzelnen existierenden Datei
QFileDialog::Directory
Der Name eines Verzeichnisses. Trotzdem werden Dateien und Verzeichnisse angezeigt.
QFileDialog::DirectoryOnly
Der Name eines Verzeichnisses. Außerdem werden auch nur Verzeichnisse angezeigt.
QFileDialog::ExistingFiles
der Name einer oder mehrerer existierender Datei(en)
Tabelle 4.20
Modi für die Methode QFileDialog::setFileMode()
Mit der Methode setDirectory() geben Sie das Verzeichnis vor, wo der Dateiauswahl-Dialog anfängt. Einen Filter setzen Sie mit der Methode setFilter() oder mit setFilters(), wenn es mehrere sein sollten. Der Dateiauswahl-Dialog hat zwei Ansichten: QFileDialog::List und QFileDia-log::Detail. QFileDialog::List zeigt den Inhalt des aktuellen Verzeichnisses mit den Dateien und Verzeichnissen an. QFileDialog::Detail hingegen zeigt ebenfalls die Dateien und Verzeichnisse an. Hierbei werden zusätzlich Details wie die Größe, der Dateityp und das Veränderungsdatum angezeigt (siehe Abbildung 4.40). Die entsprechende Ansicht wird mit der Methode setViewMode() gesetzt.
Abbildung 4.40
126
Dateiauswahl-Dialog und die Ansicht (QFileDialog::Detail)
1542.book Seite 127 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Wenn Sie einen eigenen Dateiauswahl-Dialog erzeugen, ist selectFiles() (es gibt auch selectFile() für eine einzelne Auswahl) die wichtigste Funktion. In unserem Beispiel wird der modale Dialog zunächst mit exec() angezeigt. Wenn der User Ok (oder Open bzw. Öffnen) anklickt, stehen die Dateinamen im Beispiel in der String-Liste QStringList fileNames. Da wir unseren Dialog mit dem Modus QFileDialog::ExistingFiles erzeugt haben, können hierbei durchaus mehrere Dateien ausgewählt werden (mit gehaltener (Strg)-Taste). Wir teilen diese String-Liste mit der Methode QStringList::join() in einzelne Happen auf und hängen diese zeilenweise (mit QString::append()) an den QString info. Die ausgewählten Dateien werden anschließend mit dem Nachrichtendialog ausgegeben.
4.4.3
QInputDialog – Eingabedialog
Für die Eingaben von Zahlen und Zeichenketten bietet Qt mit QInputDialog (ebenfalls von QDialog abgeleitet) auch vorgefertigte statische Eingabe-Dialoge an. In diesem Fall gibt es nur die statischen Methoden und keine Möglichkeit, eigene Instanzen dieser Klasse zu erzeugen (was allerdings auch gar nicht nötig ist). Alle Dialoge bieten jeweils den Button Ok und Abbrechen (bzw. Cancel) an. In der folgenden Tabelle (4.21) finden Sie die vier statischen Methoden aufgelistet. Statische Methode
Beschreibung
double QInputDialog::getDouble ( QWidget * parent = 0, const QString & title, const QString & label, double value = 0, double minValue = –2147483647, double maxValue = 2147483647, int decimals = 1, bool * ok = 0, Qt::WindowFlags f = 0 );
Liest eine Gleitpunktzahl vom Anwender ein. Mit parent geben Sie das Eltern-Widget an, und title ist der in der Titelleiste angezeigte Text. label ist der Text im Dialog, den der Anwender zu sehen bekommt. Mit value können Sie einen Standardwert vorbelegen. Den Mindest- bzw. Maximalwert, den der Anwender eingeben kann, legen Sie mit minValue bzw. maxValue fest. Mit decimal legen Sie die Stellen hinter dem Komma fest. Ist der Wert ok nach dem Anklicken eines Buttons wahr (true), hat der Anwender OK gedrückt. Bei false wurde »Abbrechen« (bzw. Cancel) gedrückt. Der Dialog wird modal angezeigt und verwendet außerdem das Widget-Flag f. Zurückgegeben wird im Erfolgsfall der eingegebene double-Wert.
int QInputDialog::getInteger ( QWidget * parent = 0, const QString & title, const QString & label, int value = 0,
Liest eine Ganzzahl vom Anwender ein. Mit parent geben Sie das Eltern-Widget an, und title ist der in der Titelleiste angezeigte Text. label ist der Text im Dialog, den der Anwender zu sehen bekommt. Mit value können Sie einen Standardwert vorbelegen.
Tabelle 4.21
Statische Methoden für Eingabedialoge
127
4.4
1542.book Seite 128 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Statische Methode int minValue = –2147483647, int maxValue = 2147483647, int step = 1, bool * ok = 0, Qt::WindowFlags f = 0 );
(Forts.)
Beschreibung Den Mindest- bzw. Maximalwert, den der Anwender eingeben kann, legen Sie mit minValue bzw. maxValue fest. Mit step legen Sie den Wert fest, der erhöht bzw. reduziert wird, wenn die Pfeil-Buttons gedrückt werden. Ist der Wert ok nach dem Anklicken eines Buttons wahr (true), hat der Anwender OK gedrückt. Bei false wurde »Abbrechen« (bzw. Cancel) gedrückt. Der Dialog wird modal angezeigt und verwendet zudem das Widget-Flag f. Zurückgegeben wird im Erfolgsfall der eingegebene Integerwert.
QString QInputDialog::getItem ( const QString & title, const QString & label, const QStringList & list, int current = 0, bool editable = true, bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 );
Lässt den Anwender eine Zeichenkette aus mehreren vorgegebenen Strings auswählen. Mit parent geben Sie das Eltern-Widget an, und title ist der in der Titelleiste angezeigte Text. label ist der Text im Dialog, den der Anwender zu sehen bekommt. Mit list können Sie eine Liste von Strings angeben, aus denen der Anwender wählen kann. Mit current legen Sie den String in der Liste fest, der beim Start des Dialogs vorbelegt ist. Setzen Sie editable auf true, erlauben Sie dem Anwender, dass dieser auch einen eigenen Text eingeben kann. Bei false kann der Anwender nur die vorgegebenen Strings wählen. Ist der Wert ok nach dem Anklicken eines Buttons wahr (true), hat der Anwender OK gedrückt. Bei false wurde »Abbrechen« (bzw. Cancel) gedrückt. Der Dialog wird modal angezeigt und verwendet zudem das Widget-Flag f. Zurückgegeben wird im Erfolgsfall das ausgewählte Element.
QString QInputDialog::getText ( const QString & title, const QString & label, QLineEdit::EchoMode echo = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, QWidget * parent = 0, const char * name = 0, Qt::WindowFlags f = 0 );
Liest einen eingegebenen String vom Anwender. Mit parent geben Sie das Eltern-Widget an, und title ist der in der Titelleiste angezeigte Text. label ist der Text im Dialog, den der Anwender zu sehen bekommt. Mit echo können Sie die Anzeige der Eingabe festlegen (siehe Tabelle 4.22). Im Beispiel ist der Text im Klartext lesbar. Mit text können Sie im Editierfeld einen markierten String vorbelegen. Ist der Wert ok nach dem Anklicken eines Buttons wahr (true), hat der Anwender OK gedrückt. Bei false wurde Abbrechen (bzw. Cancel) gedrückt. Der Dialog wird modal angezeigt und verwendet zudem das Widget-Flag f. Zurückgegeben wird bei Erfolg der eingegebene String.
Tabelle 4.21
128
Statische Methoden für Eingabedialoge (Forts.)
1542.book Seite 129 Montag, 4. Januar 2010 1:02 13
Vorgefertigte Dialoge
Hierzu noch eine Tabelle, mit dem enum-Typ QLineEdit::EchoMode, die beschreibt, wie der Inhalt in einem Editierfeld angezeigt werden soll. Konstante
Beschreibung
QLineEdit::Normal
Zeigt die eingegebenen Zeichen im Klartext an.
QLineEdit::NoEcho
Bei der Eingabe wird gar nichts angezeigt. Lässt sich bspw. für die Eingabe von Passwörtern verwenden, wenn die Länge des Passwortes geheim bleiben soll.
QLineEdit::Password
Bei der Eingabe werden nur Sternchen für jedes Zeichen im Editierfeld angezeigt.
QLineEdit::PasswordEchoOnEdit
Vorbelegte Zeichen werden mit Sternchen angezeigt. Wird der Text editiert, werden die Zeichen allerdings im Klartext angezeigt.
Tabelle 4.22
Konstanten für die Ausgabe der Eingabe
Hierzu wieder unsere Slot-Methode myquit(), die alle vier Eingabedialoge mitsamt Auswertung in der Praxis demonstrieren soll: // beispiele/qinputdialog/mywidget.cpp void MyWidget::myquit() { bool ok; // QInputDialog::getText() --- Anfang QString text = QInputDialog::getText( this, "QInputDialog::getText()", "Ihr Name :", QLineEdit::Normal, "Name eingeben", &ok); if (ok && !text.isEmpty()) QMessageBox::information( this, "Ihr Eingabe", text, QMessageBox::Ok ); // QInputDialog::getText() --- Ende // QInputDialog::getDouble --- Anfang double dvalue = QInputDialog::getDouble( this, "QInputDialog::getDouble()", "Wert eingeben :", 55.555, 0, 100, 3, &ok ); if(ok) { QString qsdvalue = QString("%1").arg(dvalue); QMessageBox::information( this,"Ihr double-Wert ",qsdvalue,QMessageBox::Ok); } // QInputDialog::getDouble --- Ende // QInputDialog::getInteger --- Anfang int ivalue = QInputDialog::getInteger(
129
4.4
1542.book Seite 130 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
this, "QInputDialog::getInteger()", "Wert eingeben :", 10, 0, 20, 1, &ok ); if(ok) { QString qsivalue = QString("%1").arg(ivalue); QMessageBox::information( this, "Ihr Integer-Wert ", qsivalue, QMessageBox::Ok ); } // QInputDialog::getInteger --- Ende // QInputDialog::getItem --- Anfang QStringList items; items addWidget(label1); layout->addWidget(button0); layout->addWidget(button2); layout->addWidget(button3);
21 22
// Signale des Widgets einrichten connect( button0, SIGNAL( clicked() ), this, SLOT( qpush_exec() ) ); connect( button2, SIGNAL( clicked() ), this, SLOT( qcheck_exec() ) ); connect( button3, SIGNAL( clicked() ), this, SLOT( qradio_exec() ) ); setWindowTitle("Button – Demo");
23 24 25 26 }
27 void MyWidget::qpush_exec() { 28 pushDialog = new MyQPushDialog; 29 int status = pushDialog->exec(); 30 31
32 33
34 35
36 37
if( status == QDialog::Accepted ) QMessageBox::information( this, "qpush_exec()", "Acceped zurückgegeben (\"Flat\"-Button)", QMessageBox::Ok ); else if( status == QDialog::Rejected ) QMessageBox::information( this, "qpush_exec()", "Rejected zurückgegeben (\"Default\"-Button)", QMessageBox::Ok ); else if( status == 100) QMessageBox::information( this, "qpush_exec()", "\"Normal\"-Button betätigt: " "\"Checkable\"-Button war aktiv", QMessageBox::Ok ); else if( status == 50 ) QMessageBox::information( this, "qpush_exec()", "\"Normal\"-Button betätigt:" " \"Checkable\"-Button war nicht aktiv", QMessageBox::Ok );
38 } 39 void MyWidget::qcheck_exec() { 40 checkDialog = new MyQCheckBoxDialog;
139
4.5
1542.book Seite 140 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
int status = checkDialog->exec(); QString score = "Aktiv waren: [Label 4] "; if ( status != 0 ) { if( status / 100 ) score.append(" [Label 3] "); status%=100; if( status / 10 ) score.append(" [Label 2] "); status%=10; if( status / 1 ) score.append(" [Label 1] "); } else { score.append(" Kein Label war aktiv " ); } QMessageBox::information( this, "qcheck_exec()", score, QMessageBox::Ok );
57 }
58 void MyWidget::qradio_exec() { 59 radioDialog = new MyQRadioBoxDialog; 60 int status = radioDialog->exec(); 61 QString score = "Aktiver Radio-Button: "; 62 if( status == 1 ) score.append("1"); 63 if( status == 2 ) score.append("2"); 64 if( status == 3 ) score.append("3"); 65 QMessageBox::information( this, "qradio_exec()", score, QMessageBox::Ok ); 66 }
Und so sieht das Grundgerüst aus:
Abbildung 4.50
Auswahl des Button-Demos
Auf die einzelnen Slots in diesem Beispiel gehen wir in den entsprechenden Klassen noch näher ein.
140
1542.book Seite 141 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
QPushButton Die Klasse QPushButton wurde bereits des Öfteren in diesem Buch verwendet. Sie verkörpert die klassische Schaltfläche (sowie das am meisten angewendete Widget), die beim Anklicken bspw. mit einer Maus ein Kommando oder sonstige Aktionen ausführt. Ein solcher Button besitzt gewöhnlich einen Text und optional auch ein Icon. Um einen Text bzw. ein Icon nachträglich zu setzen, greift man auf die Methoden setIcon() und setText() der Basisklasse QAbstractButton zurück. QPushButton hat keine eigenen Signale definiert und verwendet die in QAbstractButton definierten Signale (siehe Tabelle 4.24). Dasselbe gilt für die Slots, die ebenfalls von der darüber liegenden Klasse QAbstractButton verwendet werden. Abgesehen vom Slot showMenu(), mit dem man ein entsprechendes Popup-Menü anzeigen kann, sofern eines existiert. Darauf gehen wir hier aber nicht ein.
Im Grunde besitzt ein gewöhnlicher QPushButton wenige wirklich wichtige Methoden und greift immer auf die Methoden der Basisklasse zurück. Eine Methode, mit der Sie einen Button flach (flat) darstellen können, ist setFlat(bool). Dies ist allerdings weniger vorteilhaft, weil schlecht sichtbar ist, ob es sich um einen Button oder um ein bloßes Textlabel handelt. Wenn Sie einen gedrückten Button benötigen, verwenden Sie die Methode setCheckable(). Hierzu nun ein Beispiel, um die Klasse MyQPushDialog (alias QPushButton) näher zu beschreiben. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09
// beispiele/buttondemo/myqpushbuttondialog.h #ifndef MYQPUSHBUTTONDIALOG_H #define MYQPUSHBUTTONDIALOG_H #include #include #include #include #include #include #include
10 class MyQPushDialog : public QDialog { 11 Q_OBJECT 12 public: 13 MyQPushDialog(); 14 QVBoxLayout *vbox; 15 QPushButton *button01; 16 QPushButton *button02;
141
4.5
1542.book Seite 142 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
17 QPushButton *button03; 18 QPushButton *button04; 19 QPushButton *button05; 20 public slots: 21 void checktoogled(bool iftoogled); 22 void mySetResult(); 23 }; 24 #endif
Eine Erläuterung hierzu kann ich mir ersparen und fahre gleich mit der Implementation dieser Klasse fort: 00 // beispiele/buttondemo/myqpushbuttondialog.cpp 01 #include "myqpushbuttondialog.h" 02 // neue Widget-Klasse vom eigentlichen Widget ableiten 03 MyQPushDialog::MyQPushDialog() { 04 setFixedSize ( 200, 180 ); 05 06 07 08 09 10
vbox = new button01 = button02 = button03 = button04 = button05 =
11 12 13
// Attribute setzen button01->setFlat(true); button01->setIcon( QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon.png") ) ); button04->setCheckable(true); button05->setDown(true);
14 15 16 17 18 19 20 21 22 23
142
QVBoxLayout; new QPushButton("Flat"); new QPushButton("Default"); new QPushButton("Normal"); new QPushButton("Checkable"); new QPushButton("Down");
vbox->addWidget(button01); vbox->addWidget(button02); vbox->addWidget(button03); vbox->addWidget(button04); vbox->addWidget(button05); setLayout(vbox); connect( button01, SIGNAL( clicked() ), this, SLOT( accept() ) ); connect( button02, SIGNAL( clicked() ), this, SLOT( reject() ) );
1542.book Seite 143 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
24 25
connect( button03, SIGNAL ( clicked()), this, SLOT( mySetResult() ) ); connect( button04, SIGNAL( toggled(bool) ), this, SLOT( checktoogled(bool) ) );
26 } 27 // Zustand des Buttons hat sich verändert 28 void MyQPushDialog::checktoogled(bool iftoogled) { 29 if( iftoogled ) 30 QMessageBox::information( this, "checktoogled()", "Button \"Checkable\" ist aktiv", QMessageBox::Ok ); 31 else 32 QMessageBox::information( this, "checktoogled()", "Button \"Checkable\" ist nicht aktiv", QMessageBox::Ok ); 33 } 34 // Rückgabewert vom "Checkable" Button abhängig machen 35 void MyQPushDialog::mySetResult() { 36 int result; 37 if (button04->isChecked() ) 38 result = 100; 39 else 40 result = 50; 41 emit done(result); 42 }
Gestartet wird das Demo über der Klasse MyWidget mit dem »QPushButton«. Hierfür haben wir eine Signal-Slot-Verbindung eingerichtet, mit der beim Anwählen des Buttons (clicked()) der Slot qpush_exex() ausgeführt wird: // beispiele/buttondemo/mywidget1.cpp ... 27 void MyWidget::qpush_exec() { 28 pushDialog = new MyQPushDialog; 29 int status = pushDialog->exec(); 30 if( status == QDialog::Accepted ) 31 QMessageBox::information( this, "qpush_exec()", "Acceped zurückgegeben (\"Flat\"-Button)", QMessageBox::Ok ); 32 else if( status == QDialog::Rejected )
143
4.5
1542.book Seite 144 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
33
34 35
36 37
QMessageBox::information( this, "qpush_exec()", "Rejected zurückgegeben (\"Default\"-Button)", QMessageBox::Ok ); else if( status == 100) QMessageBox::information( this, "qpush_exec()", "\"Normal\"-Button betätigt: " "\"Checkable\"-Button war aktiv", QMessageBox::Ok ); else if( status == 50 ) QMessageBox::information( this, "qpush_exec()", "\"Normal\"-Button betätigt:" " \"Checkable\"-Button war nicht aktiv", QMessageBox::Ok );
38 }
In diesem Slot wird mit exec() der Dialog gestartet. Sie erhalten folgende Dialogbox:
Abbildung 4.51
Demo zu QPushButton bei der Ausführung
Der erste Button wurde mit dem Attribut setFlat() »flach« gemacht und erhält ein Icon mit der Methode setIcon(): button01->setFlat(true); button01->setIcon(QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon.png")));
Grafiken hinzufügen Wem unsere Art des Hinzufügens mit der Ermittlung des absoluten Pfades zum Arbeitsverzeichnis und dem Anhängen des Verzeichnisses mit der Grafik zu umständlich ist, der kann auch auf das Ressourcen-System von Qt zurückgreifen. Hierauf gehen wir in Abschnitt 12.7 näher ein. Warum hier dieser Weg eingeschlagen wurde, erläuterten wir bereits kurz in Abschnitt 1.4.3.
144
1542.book Seite 145 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Bei Betätigung des Buttons (Signal: clicked()),wird der Slot accepted() ausgeführt, wobei der Slot MyWidget::qpush_exec() eine entsprechende Auswertung (Zeile 30 und 31) durchführt in einem Nachrichtendialog anzeigt:
Abbildung 4.52
Der Button mit dem Label »Flat« wurde betätigt.
Selbiges passiert mit dem Button »Default«, nur dass hierbei der Slot reject() ausgeführt und beim Slot eben Entsprechendes (Zeile 32 und 33) ausgewertet wird. Wenn Sie den Button »Normal« betätigt haben, wird der Slot mySetResult() ausgeführt, womit der Button auf Eintreten des Signals clicked() verbunden wurde. Bei diesem Slot überprüfen wir zunächst, ob sich der Button »Checkable« in gedrücktem Zustand befindet (button04->isChecked()) oder nicht. Je nach Zustand übergeben wir den Wert an das Signal done() das am Ende des Slots ausgelöst wird, worauf der Slot MyWidget::qpush_exec() entsprechend reagiert (Zeile 34 bis 37). Der Button »Checkable« stellt einen klassischen toggled Button dar, der beim Niederdrücken in diesem Zustand verharrt und durch erneutes Drücken wieder gelöst werden kann. Dieser Button wurde mit der Methode setCheckable(true) eingerichtet. Natürlich hat man auch hierzu eine entsprechende Signal-Slot-Verbindung eingerichtet. Sobald der Button betätigt wurde, wird das Signal toogled() ausgelöst. Um den Booleschen Parameter des Signals auszuwerten, haben wir einen eigenen Slot checktoogled() erstellt. Der Letzte im Bunde ist ein Button, der mit setDown() zunächst in der gedrückten Version ins Bild kommt. Wird dieser Zustand (durch Anklicken) gelöst, verwandelt sich der Button in einen ganz normalen Button (keine toggled Version, wie häufig angenommen wird). QCheckBox Die Klasse QCheckBox stellt einen Button dar, der sich mit einem Häkchen oder einem Kreuz aktivieren oder deaktivieren lässt. Solche Buttons setzt man gerne in Situationen ein, um bestimmte Optionen ein- oder auszuschalten. Wird der Zustand eines Check-Buttons verändert, löst man das Signal stateChanged(int) aus. Ansonsten enthält QCheckBox keine eigenen Signale und
145
4.5
1542.book Seite 146 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Slots. Allerdings kann auch hier bei Bedarf wieder auf die Signale und Slots von QAbstractButton zurückgegriffen werden. Die gängigsten Methoden von QCheckBox finden Sie in der folgenden Tabelle aufgelistet. Methode
Beschreibung
Qt::CheckState checkState () const;
Gibt den Zustand des Check-Buttons zurück (siehe Tabelle 4.27).
bool isTristate () const;
Überprüft, ob der Check-Button drei Zustände erlaubt (siehe setTristate()).
void setCheckState ( Qt::CheckState state );
Setzt den Check-Button auf den Zustand state (siehe Tabelle 4.27).
void setTristate ( bool y = true );
Damit ist ein dritter Zustand des Check-Buttons möglich, in dem Sie diesen in einen ausgegrauten Zustand setzen. Diesen ausgegrauten Zustand können Sie entweder aktivieren oder eben nicht. Dies soll anzeigen, dass dieser Check-Button nicht mehr zu ändern ist.
Tabelle 4.26
Methoden von QCheckBox
Mögliche Werte für den enum-Wert Qt::CheckState: Konstante
Beschreibung
Qt::Unchecked
Das Element ist deaktiviert (nicht angekreuzt bzw. abgehakt).
Qt::PartiallyChecked
Das Element ist teilweise aktiviert.
Qt::Checked
Das Element ist aktiviert (angekreuzt bzw. abgehakt).
Tabelle 4.27
Zustände eines Check-Buttons
Hierzu jetzt die Klasse MyQCheckBoxDialog (alias QCheckBox) in der Praxis. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07 08 09
146
// beispiele/buttondemo/mycheckbuttondialog.h #ifndef MYQCHECKBOXDIALOG_H #define MYQCHECKBOXDIALOG_H #include #include #include #include #include #include #include
1542.book Seite 147 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// Grundgerüst der Klasse MyCheckBoxDialog() class MyQCheckBoxDialog : public QDialog { Q_OBJECT public: MyQCheckBoxDialog(); QVBoxLayout *vbox; QCheckBox *check01; QCheckBox *check02; QCheckBox *check03; QCheckBox *check04; QPushButton *button01; public slots: void isChecked(int state); void cantChecked(); void myResult(); }; #endif
Die Definition der Klasse sieht folgendermaßen aus: 00 // beispiele/buttondemo/mycheckbuttondialog.cpp 01 #include "mycheckbuttondialog.h" 02 // Neue Widget-Klasse vom eigentlichen Widget ableiten 03 MyQCheckBoxDialog::MyQCheckBoxDialog() { 04 setFixedSize ( 200, 180 ); 05 vbox = new QVBoxLayout; 06 check01 = new QCheckBox("Label 1"); 07 check02 = new QCheckBox("Label 2"); 08 check03 = new QCheckBox("Label 3"); 09 check04 = new QCheckBox("Label 4"); 10 button01 = new QPushButton("Auswerten"); 11 12 13
check02->setCheckState(Qt::Checked); check04->setTristate(); check04->setCheckState(Qt::PartiallyChecked);
14 15 16 17 18 19
vbox->addWidget(check01); vbox->addWidget(check02); vbox->addWidget(check03); vbox->addWidget(check04); vbox->addWidget(button01); setLayout(vbox);
20
connect( check01, SIGNAL( stateChanged ( int ) ), this, SLOT( isChecked( int ) ) );
147
4.5
1542.book Seite 148 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
21 22 23 24
connect( check02, SIGNAL( stateChanged ( int ) ), this, SLOT( isChecked( int ) ) ); connect( check03, SIGNAL( stateChanged ( int ) ), this, SLOT( isChecked( int ) ) ); connect( check04, SIGNAL( clicked() ), this, SLOT( cantChecked( ) ) ); connect( button01, SIGNAL( clicked() ), this, SLOT( myResult( ) ) );
25 } 26 // Zustand eines Buttons hat sich verändert 27 void MyQCheckBoxDialog::isChecked( int state ) { 28 if( state == Qt::Checked ) 29 QMessageBox::information( this, "isChecked", "Eine Checkbox wurde aktiviert", QMessageBox::Ok ); 30 else if( state == Qt::Unchecked ) 31 QMessageBox::information( this, "isChecked", "Eine Checkbox wurde deaktiviert", QMessageBox::Ok ) ; 32 } 33 // Zustand eines Buttons hat sich verändert 34 void MyQCheckBoxDialog::cantChecked( ) { 35 QMessageBox::information( 36 this, "cantChecked", "Sorry, Sie können diese Checkbox nicht ändern", QMessageBox::Ok ) ; 37 check04->setCheckState(Qt::PartiallyChecked); 38 } 39 // alle Checkboxen auswerten 40 void MyQCheckBoxDialog::myResult() { 41 int result=0; 42 if (check01->checkState() == Qt::Checked ) 43 result+=1; 44 if (check02->checkState() == Qt::Checked ) 45 result+=10; 46 if( check03->checkState() == Qt::Checked ) 47 result+=100; 48 emit done(result); 49 }
148
1542.book Seite 149 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Diese Klasse wird über die Klasse MyWidget mit dem Button »QCheckBox« gestartet. Wurde der Button betätigt, wird der dafür eingerichtete Slot qcheck_exec() ausgeführt: // beispiele/buttondemo/mywidget1.cpp ... 39 void MyWidget::qcheck_exec() { 40 checkDialog = new MyQCheckBoxDialog; 41 int status = checkDialog->exec(); 42 QString score = "Aktiv waren: [Label 4] "; 43 if ( status != 0 ) { 44 if( status / 100 ) 45 score.append(" [Label 3] "); 46 status%=100; 47 if( status / 10 ) 48 score.append(" [Label 2] "); 49 status%=10; 50 if( status / 1 ) 51 score.append(" [Label 1] "); 52 } 53 else { 54 score.append(" Kein Label war aktiv " ); 55 } 56 QMessageBox::information( this, "qcheck_exec()", score, QMessageBox::Ok ); 57 }
Auch hierbei wird mit exec() der Dialog mit folgendem Ergebnis gestartet:
Abbildung 4.53
QCheckBox in der Praxis
Bei den Buttons mit den Textlabels »Label 1« und »Label 3« wurde überhaupt nichts unternommen. Der Button »Label 2« wurde mit der Methode setCheckState() folgendermaßen abgehackt: check02->setCheckState(Qt::Checked);
149
4.5
1542.book Seite 150 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Der letzte Button »Label 4« kann mit setTristate() den dritten Zustand (ausgegraut) annehmen. Diesen Zustand wollen wir auch gleich mit setCheckState() und dem Zustand Qt::PartiallyChecked als vorausgewählt und im Beispiel als nicht mehr veränderbar verwenden: check04->setTristate(); check04->setCheckState(Qt::PartiallyChecked);
Wenn sich bei den ersten drei Check-Buttons etwas verändert, wird das Signal stateChanged() ausgelöst, das wir in diesem Beispiel mit unserem eigenen Slot isChecked() verbunden haben (Zeile 26 bis 33). Dieser Slot macht nichts anderes als zu überprüfen, ob der Zustand abgehackt (Qt::Checked) oder nicht abgehackt (Qt::Unchecked) ist. Je nach verändertem Zustand des Check-Buttons wird ein entsprechender Nachrichtendialog angezeigt. Versucht man hingegen, den Check-Button »Label 4« zu verändern, wird zwar auch das Signal stateChanged() abgefangen, aber als Slot wurde hier (wieder ein eigener) cantChecked() ausgeführt (Zeile 33 bis 38). Dieser macht wiederum nichts anderes, als den Anwender zu informieren, dass sich der Zustand des Check-Buttons nicht verändern lässt. Da ja der Zustand im Grunde doch verändert werden kann, setzen wir am Ende des Slots diesen wieder mit setCheckState() auf Qt::PartiallyChecked. Wird hingegen der Button »Auswerten« betätigt (Signal: clicked()), wird der Slot myResult() ausgeführt (Zeile 39 bis 49). Der Slot myResult() sendet dann den aktuellen Zustand aller Buttons zurück an int status=checkDialog->exec() im Slot MyWidget::qcheck_exec(). Die Rechnerei in myResult() ist wohl eher ein Eigengebäck von mir und kann selbstverständlich auch ganz anders gelöst werden. Die bitweisen Operatoren würden sich wohl besser eignen. Ist result bspw. 0, ist außer dem »Label 4«, das ja nicht verändert werden kann, kein weiterer Check-Button angehackt gewesen. Ist result 1, dann ist »Label 1« abgehackt. Ist result bspw. 101, dann sind »Label 1« und »Label 3« angekreuzt. Bei 111 sind alle Check-Buttons aktiviert und mit 11 nur »Label 1« und »Label 2«. Das Ergebnis »schneiden« wir im Slot MyWidget::qcheck_exec() mit dem Modulo- und Divisions-Operator in Scheibchen und erhalten einen entsprechenden Nachrichtendialog. Die Auswertung sieht bezogen auf Abbildung 4.53 folgendermaßen aus:
Abbildung 4.54
150
Check-Buttons auswerten
1542.book Seite 151 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
QRadioButton Die Klasse QRadioButton ist der Klasse QCheckBox recht ähnlich, nur wird normalerweise nur eine Auswahl aus einer Gruppe von Buttons getroffen und nicht mehrere. Die Radio-Buttons werden in der Praxis auch etwas anders als die Buttons der Klasse QCheckBox angezeigt. Anstelle eines Hakens oder eines Kreuzes wird hierbei gewöhnlich ein Punkt verwendet. Dies ist allerdings vom Betriebssystem und vom verwendeten Fenster-Manager (Desktop) abhängig. Radio-Buttons sind per Voreinstellung immer autoExclusive. Dies bedeutet, dass Sie aus mehreren Radio-Buttons immer nur einen auswählen können, auch wenn Sie vielleicht mehrere Gruppen im Auge haben. Wenn Sie mehrere Gruppen von Radio-Buttons benötigen, müssen Sie diese in QButtonGroup oder QGroupBox stecken. QRadioButton verfügt über keine eigenen Signale, Slots oder Methoden. Wählt
man einen Radio-Button an, wird das Signal toggled() (von der Basisklasse QAbstractButton) ausgelöst. Wollen Sie überprüfen, ob ein Button angewählt wurde oder nicht, wird die Methode isChecked() verwendet. Da uns zu QRadioButton im Augenblick nichts mehr einfällt, sollen die RadioButtons auch hier mit einer Klasse MyQRadioBoxDialog demonstriert werden. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09
// beispiele/buttondemo/myradiobuttondialog.h #ifndef MYQRADIOBOXDIALOG_H #define MYQRADIOBOXDIALOG_H #include #include #include #include #include #include #include
10 class MyQRadioBoxDialog : public QDialog { 11 Q_OBJECT 12 public: 13 MyQRadioBoxDialog(); 14 QVBoxLayout *vbox; 15 QGroupBox* groupBox; 16 QRadioButton* radio01; 17 QRadioButton* radio02; 18 QRadioButton* radio03; 19 QPushButton *button01;
151
4.5
1542.book Seite 152 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
20 public slots: 21 void changeRadio(bool status); 22 void myResult(); 23 }; 24 #endif
Nun die Definitionen der Klasse: 00 // beispiele/buttondemo/myradiobuttondialog.cpp 01 #include "myradiobuttondialog.h" 02 // neue Widget-Klasse vom eigentlichen Widget ableiten 03 MyQRadioBoxDialog::MyQRadioBoxDialog() { 04 setFixedSize ( 200, 180 ); 05 groupBox = new QGroupBox("Radio Button Demo"); 06 07 08 09 10 11
radio01 = new QRadioButton("Radio 1"); radio02 = new QRadioButton("Radio 2"); radio03 = new QRadioButton("Radio 3"); button01 = new QPushButton("Auswerten"); // radio02 vorbelegen radio02->setChecked(true);
12 13 14 15 16 17 18
vbox = new QVBoxLayout; vbox->addWidget(radio01); vbox->addWidget(radio02); vbox->addWidget(radio03); vbox->addWidget(button01); vbox->addStretch(1); groupBox->setLayout(vbox);
19 20 21
QVBoxLayout* myBox = new QVBoxLayout; myBox->addWidget(groupBox); setLayout(myBox);
22
connect( radio01, SIGNAL( toggled(bool) ), this, SLOT( changeRadio(bool) ) ); connect( radio02, SIGNAL( toggled(bool) ), this, SLOT( changeRadio(bool) ) ); connect( radio03, SIGNAL( toggled(bool) ), this, SLOT( changeRadio(bool) ) ); connect( button01, SIGNAL( clicked() ), this, SLOT( myResult( ) ) );
23 24 25 26 }
152
1542.book Seite 153 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
27 // Zustand eines Buttons hat sich verändert 28 void MyQRadioBoxDialog::changeRadio(bool status) { 29 if( status ) 30 QMessageBox::information( this, "changeRadio", "Radiobutton wurde geändert", QMessageBox::Ok ) ; 31 } 32 // Radio-Buttons auswerten 33 void MyQRadioBoxDialog::myResult() { 34 int result=0; 35 if( radio01->isChecked()) result = 1; 36 if( radio02->isChecked()) result = 2; 37 if( radio03->isChecked()) result = 3; 38 emit done(result); 39 }
Gestartet wird diese Klasse (wie bei den anderen beiden Button-Klassen) über die Klasse MyWidget mit dem eigens dafür eingerichteten Slot qradio_exec(): // beispiel/buttondemo/mywidget1.cpp ... 58 void MyWidget::qradio_exec() { 59 radioDialog = new MyQRadioBoxDialog; 60 int status = radioDialog->exec(); 61 QString score = "Aktiver Radio-Button: "; 62 if( status == 1 ) score.append("1"); 63 if( status == 2 ) score.append("2"); 64 if( status == 3 ) score.append("3"); 65 QMessageBox::information( this, "qradio_exec()", score, QMessageBox::Ok ); 66 }
Nachdem der Dialog mit exec() gestartet wurde, erhalten Sie folgendes Bild:
Abbildung 4.55
QRadioButton in der Praxis
153
4.5
1542.book Seite 154 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
In diesem Beispiel haben wir für jeden Radio-Button das Signal toggled() mit dem eigenen Slot changeRadio() verbunden. Dieser Slot überprüft nur, ob das entsprechende Radio-Element aktiviert wurde, und gibt eine entsprechende Meldung zurück (Zeile 27 bis 31). Klicken (Signal: clicked()) Sie auf den Button »Auswerten«, wird der eigene Slot myResult() ausgeführt. Dieser überprüft die einzelnen Radio-Buttons, ob sie aktiviert sind. Je nachdem, welcher Radio-Button aktiviert ist, wird wiederum mit dem Signal done() an den exec()-Aufruf im Slot MyWidget::qradio_exec() zurückgegeben, entsprechend ausgewertet und mit einem Nachrichtendialog ausgegeben:
Abbildung 4.56
Auswerten der Radio-Buttons
QToolButton Die Klasse QToolButton ist eine weitere Klasse für Buttons, die üblicherweise innerhalb einer Werkzeugleiste (QToolBar) verwendet wird.
Abbildung 4.57
QToolButton in einer Toolbar
Im Gegensatz zu normalen Buttons enthalten solche Tool-Buttons gewöhnlich keinen Text-Label sondern ein Icon. Da die Klasse allein hier zunächst keinen Sinn ergibt, wollen wir darauf erst in Abschnitt 5.2.4 näher eingehen, wo wir auch eine Werkzeugleiste mit Tool-Buttons verwenden. QButtonGroup Die Klasse QButtonGroup ist zwar keine direkt abgeleitete Klasse von QAbstractButton, sollte aber dennoch kurz hier erwähnt werden. QButtonGroup
ist von QObject abgeleitet und wird für die Verwaltung mehrerer Gruppen von Button-Widgets verwendet. QButtonGroup verwaltet alle Arten von Buttons die von QAbstractButtons abgeleitet sind. Es gilt zu beachten, dass es sich bei QButtonGroup um keine Container-Klasse handelt, die die visuellen Aspekte der Buttons beinhaltet (wie bspw. QGroupBox).
154
1542.book Seite 155 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
QButtonGroup dient vielmehr der Verwaltung des Zustands jedes Buttons in der
Gruppe. Mehr dazu entnehmen Sie bitte dem Qt-Assistant.
4.5.2
Container-Widgets
Unter Container-Widget verstehen wir Klassen, die andere Widgets als »Behälter« verwalten. Im Grunde könnte man auch QDialog und QWidget als ContainerWidgets bezeichnen. Allerdings sind die hier beschriebenen Widgets nicht ohne die Klassen QDialog oder QWidget sichtbar. Somit sind Container-Widgets weitere Sammelbehälter innerhalb von QDialog oder QWidget. Abgesehen von der Klasse QToolBox, sind alle Container-Widgets von der Basisklasse QWidget abgeleitet (siehe Abbildung 4.58).
QWidget
QGroupBox
QFrame
QTabWidget
QToolBox
Abbildung 4.58
Hierarchie der Container-Widgets
QGroupBox Die schon des Öfteren verwendete Klasse QGroupBox ist ein Widget, das über einen Rahmen mit Titel um eine Gruppe von Widgets verfügt. Zusätzlich kann auch hier mit dem Ampersandzeichen ein Tastaturschnellzugriff eingerichtet werden, so dass der Fokus auf eines der Kinder-Widgets in der Box fällt. Hierfür die gängigsten Methoden der Klasse QGroupBox (Tabelle 4.28). Methode
Beschreibung
Qt::Alignment alignment() const;
Gibt die Ausrichtung des Titels der Box zurück (siehe Tabelle 4.29).
bool isCheckable() const;
Gibt true zurück, wenn die Box einen Checkbutton im Titel hat (siehe setCheckable()). Per Standard hat eine solche Box keinen Checkbutton. Ansonsten wird false zurückgegeben.
Tabelle 4.28
Gängige Methoden von QGroupBox
155
4.5
1542.book Seite 156 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool isChecked () const;
Gibt true zurück, wenn die Box einen Checkbutton hat und dieser abgehakt ist. Ansonsten wird false zurückgegeben.
bool isFlat () const;
Gibt true zurück, wenn die Box keinen Rahmen hat (siehe setFlat()). Ansonsten wird false zurückgegeben.
void setAlignment ( int alignment );
Setzt die Ausrichtung des Titels der Box auf alignment (siehe Tabelle 4.29).
void setCheckable ( bool checkable );
Wird checkable für true verwendet, hat die Box im Titel einen Checkbutton. Mit ihm können die Widgets in der Box deaktiviert (ausgegraut) bzw. aktiviert werden.
void setFlat ( bool flat );
Wird für flat der Wert true verwendet, hat die Box keinen Rahmen.
void setTitle ( const QString & title );
Setzt den Titel des Rahmens. Mit dem Ampersandzeichen können Sie außerdem ein Tastaturkürzel einrichten.
QString title () const;
Gibt den Titel der Box zurück.
Tabelle 4.28
Gängige Methoden von QGroupBox (Forts.)
Hierzu noch die möglichen Ausrichtungen (alignment) für den Titel der Box, wobei standardmäßig Qt::AlignLeft eingestellt ist. Konstante
Beschreibung
Qt::AlignLeft
Der Text des Titels ist auf der linken Seite der Box ausgerichtet.
Qt::AlignRight
Der Text des Titels ist auf der rechten Seite der Box ausgerichtet.
Qt::AlignHCenter
Der Text des Titels ist zentriert auf der Box ausgerichtet.
Tabelle 4.29
Ausrichten des Titels von QGroupBox
Sofern die Box einen Checkbutton neben Titel verwendet, gibt es zwei Signale, die ausgelöst werden, wenn der Checkbutton betätigt wurde: Signal
Beschreibung
clicked(bool checked=false)
Das Signal wird ausgelöst, wenn der Checkbutton neben dem Titel betätigt wurde.
toggled(bool on)
Das Signal wird ausgelöst, wenn der Checkbutton angehakt bzw. angekreuzt wurde.
Tabelle 4.30
156
Signal von QGroupBox, wenn der Checkbutton aktiviert ist
1542.book Seite 157 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Die Methode setCheckable() (siehe Tabelle 4.28) können Sie zudem auch als Slot verwenden. Zwar wurde die Klasse QGroupBox schon des Öfteren verwendet, doch soll das Feature, eine solche Box zu aktivieren und zu deaktivieren, hier doch gezeigt werden. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07
// beispiele/groupbox/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include
08 class MyWidget : public QWidget { 09 Q_OBJECT 10 public: 11 MyWidget( QWidget *parent = 0); 12 private: 13 QRadioButton *rbutton1, *rbutton2, *rbutton3; 14 QRadioButton *rbutton4, *rbutton5, *rbutton6; 15 QVBoxLayout* vBox1, *vBox2; 16 QGroupBox* groupBox1, *groupBox2; 17 QButtonGroup *group1, *group2; 18 }; 19 #endif
Nun die Implementierung dieser Klasse: 00 01 02 03
// beispiele/groupbox/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 06 // Radio-Elemente des Widgets erzeugen 07 rbutton1 = new QRadioButton ("Radio 1"); 08 rbutton2 = new QRadioButton ("Radio 2"); 09 rbutton3 = new QRadioButton ("Radio 3"); 10 rbutton4 = new QRadioButton ("Radio 1a"); 11 rbutton5 = new QRadioButton ("Radio 2a"); 12 rbutton6 = new QRadioButton ("Radio 3a");
157
4.5
1542.book Seite 158 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
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 }
// Attribute setzen rbutton1->setChecked(true); rbutton4->setChecked(true); // zwei neue Group-Boxen erzeugen groupBox1 = new QGroupBox("Radio-Auswahl &1"); groupBox2 = new QGroupBox("Radio-Auswahl &2"); // Attribute setzen groupBox2->setCheckable(true); groupBox2->setChecked(false); // Layout erzeugen vBox1 = new QVBoxLayout; vBox2 = new QVBoxLayout; // Elemente ins Layout einfügen vBox1->addWidget(rbutton1); vBox1->addWidget(rbutton2); vBox1->addWidget(rbutton3); vBox2->addWidget(rbutton4); vBox2->addWidget(rbutton5); vBox2->addWidget(rbutton6); // Attribute des Layouts setzen vBox1->addStretch(1); vBox2->addStretch(1); // Layout zu den Group-Boxen hinzufügen groupBox1->setLayout(vBox1); groupBox2->setLayout(vBox2); // noch ein Layout für die Group-Boxen QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); layout->addWidget(groupBox2); setLayout(layout); setWindowTitle("QGroupBox – Demo");
In der Zeile 20 setzen Sie die zweite Group-Box auf ankreuzbar, und eine Zeile später deaktivieren Sie den Checkbutton. Ein Hauptprogramm fehlt noch dazu: 00 // beispiele/groupbox/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
158
1542.book Seite 159 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Das Programm bei der Ausführung:
Abbildung 4.59
QGroupBox mit Checkbutton im Titel
QTabWidget Die Klasse QTabWidget stellt ein Register-Widget zur Verfügung. Das Prinzip ist einem Ringordner recht ähnlich, in dem sich einzelne Trennblätter befinden, mit denen man versucht, dem Blätterchaos Herr zu werden. Ähnlich funktioniert das Tab-Widget, das einem ggf. hilft, ein Widget-Chaos zu vermeiden. Ein Tab-Widget wird in die Tab-Leiste und einen Seitenbereich aufgeteilt. Die Tab-Leiste verwendet man, um dem Inhalt des Seitenbereichs einen Zusammenhang zu geben. So können Sie bspw. eine Tab-Leiste mit dem Text »Ausgaben« und eine Tab-Leiste mit »Einnahmen« verwenden. In den Seitenbereichen dieser Tab-Leisten können Sie jetzt die jeweiligen Funktionalitäten mit passenden Widgets einbauen. Standardmäßig wird die Tab-Leiste über dem Seitenbereich angezeigt, was aber geändert werden kann. Natürlich kann immer nur ein Seitenbereich, dessen TabLeiste angewählt wurde, auf einmal gezeigt werden. Hierbei können Sie wiederum ein Tastaturkürzel mit dem Ampersandzeichen einrichten, so dass Sie mit dem (Alt)+Zeichen auf die Tab-Leiste und somit den Seitenbereich zugreifen. QTabWidget vs. QTabBar und QStackedWidget QTabWidget ist ein sehr gutes Ready-to-use-Widget, das im Allgemeinen für Zufriedenheit sorgt. Wer noch flexibler sein will und Tab-Leiste sowie Seitenbereich separat verwalten möchte, kann die Widgets QTabBar (für die Tab-Leiste) und QStackedWidget (für den Seitenbereich) verwenden.
Bevor Sie das Widget in der Praxis kennenlernen, wollen wir zunächst wieder die gängigsten relevanten Methoden näher beschreiben (Tabelle 4.31).
159
4.5
1542.book Seite 160 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
QTabWidget ( QWidget * parent = 0 );
Erzeugt eine neue Tab-Leiste mit dem parent als Eltern-Widget.
~QTabWidget ();
Zerstört eine Tab-Leiste.
int addTab ( QWidget* child, const QString &text);
Fügt ein neues Tab zur Leiste mit dem Label text und dem Seitenbereich child am Ende hinzu. Zurückgegeben wird der Index (angefangen bei 0) der neuen Tab-Leiste.
int addTab ( QWidget* child, const QIcon & icon, const QString & text );
Fügt ein neues Tab zur Leiste mit dem Label text, dem Icon icon und dem Seitenbereich child am Ende hinzu. Zurückgegeben wird der Index (angefangen bei 0) der neuen Tab-Leiste.
int count () const;
Gibt die Anzahl der Tabulatoren der Tab-Leiste zurück.
int currentIndex () const;
Gibt den Index der aktuell sichtbaren Tab in der Leiste (mit Seitenbereich) zurück.
QWidget * currentWidget () const; Gibt einen Zeiger auf den aktuellen Seitenbereich
zurück, der im Augenblick sichtbar ist. Qt::TextElideMode elideMode () const ;
Damit wird ermittelt, wie der ausgelassene Text in den Tabs angezeigt wird (siehe setElideMode()).
int indexOf (QWidget * w) const;
Gibt den Index (angefangen bei 0) der Seite mit dem Widget w zurück oder –1, wenn das Widget nicht gefunden werden konnte.
QSize iconSize () const;
Gibt die Größe für das Icon in der Tab-Leiste zurück.
int insertTab ( int index, QWidget * widget, const QString & text );
Fügt ein neues Tab in die Leiste mit dem Label text mit dem Seitenbereich widget an der Position index hinzu. Zurückgegeben wird der Index (angefangen bei 0) der neuen Tab-Leiste. Sollte sich die angegebene Position außerhalb des erlaubten Bereichs befinden, wird die neue TabLeiste ans Ende (wie addTab()) hinzugefügt.
int insertTab ( int index, QWidget * widget, const QIcon & icon, const QString & text );
Fügt ein neues Tab in die Leiste mit dem Label text, dem Icon icon und dem Seitenbereich widget an der Position index hinzu. Zurückgegeben wird der Index (angefangen bei 0) der neuen Tab-Leiste. Sollte sich die angegebene Position außerhalb des erlaubten Bereichs befinden, wird die neue Tab-Leiste ans Ende (wie addTab()) hinzugefügt.
Tabelle 4.31
160
Gängige Methoden der Klasse QTabWidget
1542.book Seite 161 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
bool isTabEnabled ( int index ) const;
Gibt true zurück, wenn der Tab mit dem Index index aktiviert ist (siehe setTabEnable()). Ansonsten wird false zurückgegeben.
void removeTab ( int index )
Entfernt das Tab mit dem Index index aus der Leiste (mitsamt Seitenbereich).
void setElideMode ( Qt::TextElideMode ) ;
Damit können Sie angeben, wie der ausgelassene Text im Tab der Leiste angezeigt wird. Dies kann bspw. verwendet werden, wenn nicht genügend Platz vorhanden ist, alle Tabs in der Leiste anzuzeigen. Der Text wird hierbei mit Ellipsen (...) abgekürzt. Mögliche Werte siehe Tabelle 4.32.
void setIconSize ( const QSize & size );
Damit lässt sich die Größe eines Icons der TabLeiste auf size setzen.
void setTabShape ( Shape shape );
Damit können Sie die Form der Tabs in der Tab-Leiste setzen. Standardmäßig ist dies QTabWidget::Rounded. Alternativ kann hierbei auch QTabWidget::Triangular verwendet werden. Wesentlich mehr Optionen bietet Ihnen QTabBar an.
void setTabEnabled ( int index, bool enabled );
Wenn enabled auf true gesetzt wird, können der Tab und der Seitenbereich mit dem Index index verwendet werden. Mit false deaktivieren Sie den Tab und den Seitenbereich (ausgrauen), so dass darauf nicht mehr zugegriffen werden kann.
void setTabIcon ( int index, const QIcon & icon );
Setzt an der Position index das Icon icon in Tab.
void setTabText ( int index, const QString & text );
Setzt für den Tab mit der Position index den Text text.
void setTabPosition ( TabPosition );
Damit können Sie die Position der Tab-Leiste setzen. Standardmäßig wird die Tab-Leiste oben (QTabWidget::North) angezeigt. Es ist aber auch rechts (QTabWidget::East), links (QTabWidget:: West) und unten (QTabWidget::South) möglich, die Tab-Leiste anzuzeigen.
void setTabToolTip ( int index, const QString & tip );
Damit lässt sich für den Tab mit dem Index index der Tooltip tip setzen. Dieser wird angezeigt, wenn sich bspw. der Mauszeiger über dem entsprechenden Tab in der Leiste befindet.
Tabelle 4.31
Gängige Methoden der Klasse QTabWidget (Forts.)
161
4.5
1542.book Seite 162 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
void setUsesScrollButtons ( bool useButtons );
Wenn die Tab-Leiste viele Einträge hat, können Sie hier einen Scroll-Button aktivieren (true) bzw. deaktivieren. Damit können Sie sich praktisch durch die einzelnen Tabs scrollen. Meistens ist dieser Wert mit true vorbelegt, doch handelt es sich hier um keinen Standard.
Shape tabShape () const;
Gibt die Form der Tabs in der Leiste zurück (siehe setTabShape()).
QIcon tabIcon ( int index ) const;
Gibt das gesetzte Icon des Tabs mit dem Index index zurück.
QString tabText ( int index ) const;
Gibt den gesetzten Text des Tabs mit dem Index index zurück.
TabPosition tabPosition () const; Ermittelt die Position der Tab-Leiste (siehe setTabPosition()). QString tabToolTip ( int index ) const;
Gibt den gesetzten Tooltip für das Tab mit dem Index index zurück.
bool usesScrollButtons () const;
Prüft, ob die Tab-Leiste ggf. bei zu vielen Einträgen scrollbar ist. Bei true trifft dies zu, bei false nicht.
Tabelle 4.31
Gängige Methoden der Klasse QTabWidget (Forts.)
Nun noch die möglichen enum-Werte, die Sie mit setElideMode() setzen bzw. mit elideMode() abfragen können. Damit geben Sie an, wo die Ellipse (...) angezeigt werden soll, wenn der Text mal zu lang ist. Konstante
Beschreibung
Qt::ElideLeft
Die Ellipsen werden am Anfang des Texts angezeigt (bspw. beim Text »langer Text« ist dies »... xt«).
Qt::ElideRight
Die Ellipsen werden am Ende des Texts angezeigt (bspw. beim Text »langer Text« ist diese »la ...«).
Qt::ElideMiddle
Die Ellipsen werden in der Mitte des Texts angezeigt (bspw. beim Text »langer Text« ist dies »la ... xt«).
Qt::ElideNone
Die Ellipsen sollten nicht im Text erscheinen.
Tabelle 4.32
Überlange Texte mit Ellipsen (...) abkürzen
Mit currentChanged( int index ) haben Sie das einzige Signal von QTabWidget. Dieses Signal wird ausgelöst, wenn sich der aktuelle Seitenindex verändert hat. Der Parameter enthält den neuen sichtbaren Seitenindex.
162
1542.book Seite 163 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Zwei Slots werden angeboten. Zum einen der Slot setCurrentIndex(int index) um mit dem Parameter index zum neuen Seitenindex zu wechseln. Selbiges leistet auch der Slot setCurrentWidget (QWidget *widget), nur dass hier zum Seitenbereich widget gewechselt wird. Hierzu nun ein Beispiel, welches die Klasse QTabWidget spielerisch in der Praxis demonstrieren soll. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11 12
// beispiele/qtabwidget/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include #include #include #include #include
13 class MyWidget : public QWidget { 14 Q_OBJECT 15 public: 16 MyWidget( QWidget *parent = 0); 17 private: 18 QRadioButton *rbutton1, *rbutton2; 19 QCheckBox *cbutton1, *cbutton2, *cbutton3; 20 QPushButton *pbutton1,*pbutton2,*pbutton3,*pbutton4; 21 QVBoxLayout* vBox1, *vBox2, *vBox3; 22 QGroupBox* groupBox1, *groupBox2, *groupBox3; 23 QTabWidget* tab; 24 QButtonGroup *group1, *group2, *group3; 25 public slots: 26 void changeTabStyle(bool status); 27 void setTabAttribute(int attribut); 28 void setPosition(bool b); 29 void getCurrentPosition( int pos ); 30 }; 31 #endif
Und nun zur eigentlichen Implementierung des Codes, der reichlich kommentiert wird. Zugegeben, der Quellcode wirkt auf den ersten Blick recht umfang-
163
4.5
1542.book Seite 164 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
reich, doch in keinem Verhältnis zu kleineren Projekten, die locker die 10K-Zeilengrenze überschreiten. 00 01 02 03
// beispiele/qtabwidget/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 06 // zwei Radio-Buttons erzeugen 07 rbutton1=new QRadioButton("Style: Rounded (default)"); 08 rbutton2 = new QRadioButton ("Style: Triangular"); 09 // Button vorbelegen 10 rbutton1->setChecked(true); 11 // drei Check-Button erzeugen 12 cbutton1 = new QCheckBox ("Icons setzen"); 13 cbutton2 = new QCheckBox ("Erstes Tab deaktivieren"); 14 cbutton3 = new QCheckBox ("Tooltips verwenden"); 15 // vier normale Buttons erzeugen ... 16 pbutton1 = new QPushButton("Norden"); 17 pbutton2 = new QPushButton("Süden"); 18 pbutton3 = new QPushButton("Westen"); 19 pbutton4 = new QPushButton("Osten"); 20 // ... und daraus toggled Buttons machen 21 pbutton1->setCheckable(true); 22 pbutton2->setCheckable(true); 23 pbutton3->setCheckable(true); 24 pbutton4->setCheckable(true); 25 // ... nur einer der vier Buttons darf 26 // niedergerückt sein 27 pbutton1->setAutoExclusive(true); 28 pbutton2->setAutoExclusive(true); 29 pbutton3->setAutoExclusive(true); 30 pbutton4->setAutoExclusive(true); 31 // drei Group-Boxen mit Label erzeugen 32 groupBox1 = new QGroupBox("Look && Feel"); 33 groupBox2 = new QGroupBox("Attribute"); 34 groupBox3 = new QGroupBox("Position"); 35 // drei vertikale Layout-Boxen erzeugen 36 vBox1 = new QVBoxLayout; 37 vBox2 = new QVBoxLayout; 38 vBox3 = new QVBoxLayout; 39 // Radio-Buttons in Layoutbox vBox1 packen 40 vBox1->addWidget(rbutton1);
164
1542.book Seite 165 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
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
vBox1->addWidget(rbutton2); // Check-Buttons in Layoutbox vBox2 packen vBox2->addWidget(cbutton1); vBox2->addWidget(cbutton2); vBox2->addWidget(cbutton3); // Toggled Buttons in Layoutbox vBox3 packen vBox3->addWidget(pbutton1); vBox3->addWidget(pbutton2); vBox3->addWidget(pbutton3); vBox3->addWidget(pbutton4); // Stretch (1) für alle Layoutboxen hinzufügen vBox1->addStretch(1); vBox2->addStretch(1); vBox3->addStretch(1); // Group-Boxen ohne Rahmen darstellen groupBox1->setFlat(true); groupBox2->setFlat(true); groupBox3->setFlat(true); // Layout der einzelnen Group-Boxen setzen groupBox1->setLayout(vBox1); groupBox2->setLayout(vBox2); groupBox3->setLayout(vBox3); // neue Tab-Leiste erzeugen tab = new QTabWidget; // Groupbox mit Radio-Buttons als // Seitenbereich hinzufügen tab->addTab(groupBox1, "Look && Feel" ); // Groupbox mit Check-Buttons als // Seitenbereich hinzufügen tab->addTab(groupBox2, "Attribute" ); //Groupbox mit toggled-Buttons hinzufügen tab->addTab(groupBox3, "Position" ); // Ausrichtung der Tab-Leiste überprüfen, um // entsprechenden toogled "niederzudrücken" if( tab->tabPosition() == QTabWidget::North ) pbutton1->setChecked(true); else if( tab->tabPosition() == QTabWidget::South ) pbutton2->setChecked(true); else if( tab->tabPosition() == QTabWidget::West ) pbutton3->setChecked(true); else if( tab->tabPosition() == QTabWidget::East ) pbutton4->setChecked(true); // Signal-Slot-Verbindung für Radio-Buttons connect( rbutton1, SIGNAL( toggled(bool) ), this, SLOT( changeTabStyle(bool) ) );
165
4.5
1542.book Seite 166 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 }
connect( rbutton2, SIGNAL( toggled(bool) ), this, SLOT( changeTabStyle(bool) ) ); // Signal-Slot-Verbindung für Check-Buttons connect( cbutton1, SIGNAL( stateChanged(int) ), this, SLOT( setTabAttribute(int) ) ); connect( cbutton2, SIGNAL( stateChanged(int) ), this, SLOT( setTabAttribute(int) ) ); connect( cbutton3, SIGNAL( stateChanged(int) ), this, SLOT( setTabAttribute(int) ) ); // Signal-Slot-Verbindung für toogled-Buttons connect( pbutton1, SIGNAL( toggled(bool) ), this, SLOT( setPosition(bool) ) ); connect( pbutton2, SIGNAL( toggled(bool) ), this, SLOT( setPosition(bool) ) ); connect( pbutton3, SIGNAL( toggled(bool) ), this, SLOT( setPosition(bool) ) ); connect( pbutton4, SIGNAL( toggled(bool) ), this, SLOT( setPosition(bool) ) ); // Signal-Slot-Verbindung für die Tab-Leiste connect( tab, SIGNAL( currentChanged( int ) ), this, SLOT( getCurrentPosition( int ) ) ); // Ein Layout-Widget für das Tab-Widget // brauchen wir noch. QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(tab); setLayout(layout); setWindowTitle("QTabWidget- Demo");
103 // Slot: Radio-Buttons auswerten 104 void MyWidget::changeTabStyle(bool b) { 105 if( rbutton1->isChecked()) 106 tab->setTabShape(QTabWidget::Rounded); 107 if( rbutton2->isChecked()) 108 tab->setTabShape(QTabWidget::Triangular); 109 } 110 // Slot: Check-Buttons auswerten 111 void MyWidget::setTabAttribute(int a) { 112 if( cbutton1->isChecked()) { 113 int i; 114 for(i=0; i < tab->count(); ++i) { 115 QString Icon = 116 QString("/images/icon%1.png").arg(i+1); 117 tab->setTabIcon( i, QIcon(QString("%1 %2")
166
1542.book Seite 167 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
.arg(QCoreApplication::applicationDirPath()) .arg(Icon) ) ); 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 }
} } if( cbutton2->isChecked() ) { tab->setTabEnabled(0, false ); } else if(! cbutton2->isChecked()) { tab->setTabEnabled(0, true ); } if( cbutton3->isChecked() ) { tab->setTabToolTip( 0, "Das Look & Feel verändern" ); tab->setTabToolTip( 1, "Gängige Attribute hinzufügen" ); tab->setTabToolTip( 2, "Position vom Tab verändern" ); } else if (!cbutton3->isChecked()){ tab->setTabToolTip ( 0, "" ); tab->setTabToolTip ( 1, "" ); tab->setTabToolTip ( 2, "" ); }
137 // Slot: toogled Buttons auswerten 138 void MyWidget::setPosition(bool b) { 139 if( pbutton1->isChecked() ) 140 tab->setTabPosition(QTabWidget::North); 141 else if( pbutton2->isChecked() ) 142 tab->setTabPosition(QTabWidget::South); 143 else if( pbutton3->isChecked() ) 144 tab->setTabPosition(QTabWidget::West); 145 else if( pbutton4->isChecked() ) 146 tab->setTabPosition(QTabWidget::East); 147 } 148 // Slot: Tab wurde gewechselt 149 void MyWidget::getCurrentPosition(int pos) { 150 QString msg = 151 QString("Zu Tab %1 gewechselt").arg( tab->tabText(pos) ); 152 QMessageBox::information( 152 this,"Tab gewechsel",msg,QMessageBox::Ok); 153 }
167
4.5
1542.book Seite 168 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Icons hinzufügen Wem das Hinzufügen des Icons hier mit der Ermittlung des absoluten Pfades zum Arbeitsverzeichnis und dem Anhängen des Verzeichnisses mit der Grafik in der Zeile 117 zu umständlich ist, der kann auf das Ressourcen-System von Qt zurückgreifen. Wir gehen darauf in Abschnitt 12.7 näher ein. Warum hier dieser Weg eingeschlagen wurde, erläuterten wir kurz in Abschnitt 1.4.3.
Nun noch ein Hauptprogramm: 00 // beispiele/qtabwidget/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei seiner Ausführung:
Abbildung 4.60
Das Programm gleich nach dem Start
Betätigen Sie jetzt bspw. in der Tab-Leiste den Tab »Look & Feel«, im Seitenbereich den Radio-Button »Style: Triangular« und wählen in der Tab-Leiste »Position« den Toggled-Button »Süden« im Seitenbereich aus, dann erhalten Sie Abbildung 4.61. Setzen Sie bspw. im Tab Attribute im Seitenbereich ein Häkchen vor Icon setzen und Tooltips verwenden, so erhalten Sie folgendes Bild (wobei hier der Mauszeiger über der Tab-Leiste Attribute verweilte) (siehe Abbildung 4.62).
168
1542.book Seite 169 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Abbildung 4.61
Style und Position der Tab-Leiste verändert
Abbildung 4.62
Tab-Leiste mit Tooltip und Icon
Da zuvor auf alle Methoden näher eingegangen wurde und das eigentliche Prinzip eines Qt-Programms (bspw. Signal-Slot-Verbindungen) »sitzen« sollte, erspare ich mir weitere Erläuterungen. QFrame Die von QWidget abgeleitete Klasse QFrame ist eine Basisklasse für Widgets mit einem Rahmen. Der Rahmen um die Widgets kann dabei verschiedene Formen annehmen. QFrame kann (und wird) auch als reiner Platzhalter ohne Inhalt verwendet. Folgende Methoden stellt QFrame hierbei zur Verfügung: Methode
Beschreibung
QFrame ( QWidget * parent = 0, Qt::WindowFlags f = 0 );
Konstruktor. Erzeugt einen Frame mit dem Stil NoFrame und 1-Pixel Rahmen-Stärke.
~QFrame ();
Destruktor. Zerstört einen Frame.
Tabelle 4.33
Methoden von QFrame
169
4.5
1542.book Seite 170 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
QRect frameRect () const;
Damit erhalten Sie die Größe des rechteckigen Framebereichs. Per Standard ist dies die Größe des darin enthaltenen Widgets. Der rechteckige Bereich des Frames wird automatisch vergrößert bzw. verkleinert, wenn das Widget verändert wurde.
Shadow frameShadow () const;
Damit können Sie den Stil des »Schattens« vom Frame abfragen (siehe Tabelle 4.34).
Shape frameShape () const;
Damit können Sie die Form vom Frame abfragen (siehe Tabelle 4.35).
int frameStyle () const;
Gibt den Stil vom Frame zurück (siehe Tabelle 4.34 und 4.35).
int frameWidth () const;
Damit wird die aktuelle Rahmenbreite des Frames zurückgegeben.
int lineWidth () const;
Damit erhalten Sie die Linienstärke zurück. Diese wird gewöhnlich für Trennlinien (Stil: HLine und VLine) verwendet. Standardwert ist hierbei 1.
int midLineWidth () const;
Damit erhalten Sie die Linienstärke der mittleren Linie zurück. Standardwert ist hierbei 0.
void setFrameRect ( const QRect & );
Damit setzen Sie die Größe des rechteckigen Framebereichs. Per Standard ist dies die Größe des darin enthaltenen Widgets. Setzen Sie den Wert auf 0 (bspw. QRect(0,0,0,0)), ist der rechteckige Bereich immer so groß wie das darin enthaltene Widget.
void setFrameShadow ( Shadow );
Damit setzen Sie den Stil des »Schattens« im Frame auf Shadow (siehe Tabelle 4.34).
void setFrameShape ( Shape );
Damit setzen Sie die Form des Frames auf Shape (siehe Tabelle 4.35).
void setFrameStyle ( int style );
Damit können Sie mit dem bitweisen ODER den »Schatten« und/oder die Form des Frames in einem Rutsch setzen (siehe Tabelle 4.34 und 4.35).
void setLineWidth ( int );
Damit setzen Sie die Linienstärke. Diese wird gewöhnlich für Trennlinien (Stil: HLine und VLine) verwendet.
void setMidLineWidth ( int );
Damit setzen Sie die Linienstärke der mittleren Linie.
Tabelle 4.33
170
Methoden von QFrame (Forts.)
1542.book Seite 171 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Hinweis Der Raum zwischen dem Frame und dem Inhalt des Frames kann mit der Methode QWidget::setContentsMartins() angepasst werden.
Die Methoden setFrameShadow() und frameShadow() verwenden den Wert Shadow, womit Sie dem Rahmen einen 3D-Effekt verleihen können. Mit dem enum-Wert QFrame::Shadow stehen Ihnen folgende Konstanten zur Verfügung: Konstante
Beschreibung
QFrame::Plain
Der Rahmen wird mit der Vordergrundfarbe ohne jeglichen 3D-Effekt gezeichnet.
QFrame::Raised
Der Rahmen wird angehoben gezeichnet.
QFrame::Sunken
Gegenstück zu Raised. Der Rahmen wird versunken gezeichnet.
Tabelle 4.34
Mögliche »Schatten«-Effekte für den Rahmen (QFrame::Shadow)
Die Form des Rahmens legen Sie mit der Methode setFrameShape() fest (Abfragen mit frameShape()). Auch hierfür ist mit QFrame::Shape ein enum-Typ definiert, womit folgende Konstanten verwendet werden können: Konstante
Beschreibung
QFrame::NoFrame
Es wird kein Rahmen gezeichnet.
QFrame::Box
Zeichnet eine Box um den Inhalt.
QFrame::Panel
Zeichnet ein versunkenes bzw. angehobenes Fach um den Inhalt (abhängig vom Wert Shadow).
QFrame::StyledPanel
Dito (wie QFrame::Panel), nur wird das Fach enstprechend dem aktuellen GUI-Stil gezeichnet. Dieser Wert ist für plattformunabhängige Anwendungen QFrame::Panel vorzuziehen.
QFrame::HLine
QFrame zeichnet eine horizontale Linie, die sich prima als Trenn-
QFrame::VLine
Dito (wie QHLine), nur wird eine vertikale Linie gezeichnet.
QFrame::WinPanel
Damit wird ein rechteckiger Rahmen wie in Windows 95 gezeichnet. Diese Form ist nur wegen der Kompatibilität vorhanden. Für einen unabhängigen GUI-Stil wird StyledPempfohlen.
linie verwenden lässt.
Tabelle 4.35
Verschiedene Formen für QFrame
Hierzu soll jetzt ein Beispiel erstellt werden, womit Sie sich die einzelnen Formen und Schatten der Rahmen selbst in der Praxis ansehen und sie testen können. Die
171
4.5
1542.book Seite 172 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Linienweite wurde hierbei mit 2 auf einen festen Wert gesetzt. Sie können aber gerne mit den Methoden setLineWidth(int) und setMidLineWidth(int) selbst herumexperimentieren. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11 12 13
// beispiele/qframe/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include #include #include #include #include #include
14 class MyWidget : public QWidget { 15 Q_OBJECT 16 public: 17 MyWidget( QWidget *parent = 0); 18 QLabel* framelabel; 19 QRadioButton *rb1, *rb2, *rb3, *rb4, *rb5, *rb6, *rb7, *rb8, *rb9, *rb10; 20 public slots: 21 void changeFrame(bool b); 22 }; 23 #endif
Jetzt zur Implementierung der Demonstration verschiedener Frames: 00 01 02 03
// beispiele/qframe/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 06 // Radio-Buttons für die Form (Shape) 07 QVBoxLayout *vbox1 = new QVBoxLayout; 08 rb1 = new QRadioButton("QFrame::NoFrame"); 09 rb2 = new QRadioButton("QFrame::Box"); 10 rb3 = new QRadioButton("QFrame::Panel");
172
1542.book Seite 173 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
11 12 13 14 15 16 17 18 19 20 21 22 23
rb4 = new QRadioButton("QFrame::StyledPanel"); rb5 = new QRadioButton("QFrame::HLine"); rb6 = new QRadioButton("QFrame::VLine"); rb7 = new QRadioButton("QFrame::WinPanel"); rb1->setChecked(true); vbox1->addWidget(rb1); vbox1->addWidget(rb2); vbox1->addWidget(rb3); vbox1->addWidget(rb4); vbox1->addWidget(rb5); vbox1->addWidget(rb6); vbox1->addWidget(rb7); vbox1->addStretch(1);
24 25 26 27 28 29
// Radio-Buttons für den Schatten (Shadow) QVBoxLayout *vbox2 = new QVBoxLayout; rb8 = new QRadioButton("QFrame::Plain"); rb9 = new QRadioButton("QFrame::Raised"); rb10 = new QRadioButton("QFrame::Sunken"); rb8->setChecked(true);
30 31 32 33
vbox2->addWidget(rb8); vbox2->addWidget(rb9); vbox2->addWidget(rb10); vbox2->addStretch(1);
34 35 36 37 38 39
// das Frame-Demo framelabel = new QLabel("Frame"); framelabel->setMargin( 25 ); framelabel->setLineWidth(2); framelabel->setAlignment( Qt::AlignCenter ); framelabel->setFrameStyle( QFrame::NoFrame | QFrame::Plain );
40 41 42 43 44
// alles in die Group-Box verpacken QGroupBox *groupBox1 = new QGroupBox("Shape"); groupBox1->setLayout(vbox1); QGroupBox *groupBox2 = new QGroupBox("Shadow"); groupBox2->setLayout(vbox2);
46 47
// Signal-Slot-Verbindungen für die Radio-Buttons connect( rb1, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) ) ); connect( rb2, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) ) );
48
173
4.5
1542.book Seite 174 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 }
connect( rb3, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb4, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb5, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb6, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb7, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb8, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb9, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) connect( rb10, SIGNAL( toggled(bool) ), this, SLOT( changeFrame (bool) // das Layout erstellen QHBoxLayout *hbox = new QHBoxLayout; hbox->addWidget(groupBox1); hbox->addWidget(groupBox2); hbox->addWidget(framelabel); setLayout(hbox); setWindowTitle("QFrame – Demo");
) ); ) ); ) ); ) ); ) ); ) ); ) ); ) );
65 // Slot: Radio-Buttons auswerten und Form/Schatten 66 // von QFrame neu einstellen 67 void MyWidget::changeFrame(bool b) { 68 if( rb1->isChecked() ) 69 framelabel->setFrameShape(QFrame::NoFrame); 70 else if( rb2->isChecked() ) 71 framelabel->setFrameShape(QFrame::Box); 72 else if( rb3->isChecked() ) 73 framelabel->setFrameShape(QFrame::Panel); 74 else if( rb4->isChecked() ) 75 framelabel->setFrameShape(QFrame::StyledPanel); 76 else if( rb5->isChecked() ) 77 framelabel->setFrameShape(QFrame::HLine); 78 else if( rb6->isChecked() ) 79 framelabel->setFrameShape(QFrame::VLine); 80 else if( rb7->isChecked() ) 81 framelabel->setFrameShape(QFrame::WinPanel); 82 83
174
if( rb8->isChecked() ) framelabel->setFrameShadow(QFrame::Plain);
1542.book Seite 175 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
84 85 86 87 88 }
else if( rb9->isChecked() ) framelabel->setFrameShadow(QFrame::Raised); else if( rb10->isChecked() ) framelabel->setFrameShadow(QFrame::Sunken);
Das Beispiel ist recht einfach aufgebaut. Zunächst packen wir mehrere Radio-Buttons in zwei Group-Boxen. Je eine Group-Box für die Form (Shape) und eine für den Schatten (Shadow). Jeder Radio-Button bekommt natürlich ein entsprechendes Shape- bzw. Shadow-Label. In den Zeilen 34 bis 39 erzeugen wir ein neues QFrame und vergeben entsprechende Attribute. In den Zeilen 46 bis 56 richten wir die Signal-Slot-Verbindungen für die Radio-Buttons ein. Wenn ein Radio-Button betätigt wurde, wird immer der eigene Slot changeFrame() ausgeführt. Darin werden dann den Radio-Buttons entsprechend die Form und der Schatten für das Frame neu gesetzt. Nun noch eine Hauptfunktion: 00 // beispiele/qframe/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Jetzt können Sie die Formen und Schatten der Frames nach Belieben kombinieren und testen:
Abbildung 4.63
Ein Frame mit QFrame::Box und QFrame::Raised
175
4.5
1542.book Seite 176 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QToolBox Die von QFrame abgeleitete Klasse ist der Klasse QTabWidget recht ähnlich. Diese Klasse ist eine Spalte mit Tab-Widgets-Elementen, wo auch immer nur der Seitenbereich eines Tab-Widgets auf einmal angezeigt wird. Da diese Klasse von QFrame abgeleitet wurde, kann sie praktisch überall mit eingebaut werden. Auch die Methoden sind denen von QTabWidget recht ähnlich, nur dass hierbei statt der Teilbezeichnung »Tab« die Teilbezeichnung »Item« verwendet wird. Außerdem können Sie auf alle Methoden von QFrame zurückgreifen. Hier ein Überblick zu den Methoden von QToolBox: Methode
Beschreibung
QToolBox ( QWidget * parent = 0, Qt::WindowFlags f = 0 );
Konstruktor. Erzeugt eine neue Toolbox.
~QToolBox ();
Destruktor. Zerstört eine vorhandene Toolbox.
int addItem ( QWidget * widget, const QIcon & iconSet, const QString & text );
Fügt das Widget widget in einem neuen Tab unten der Toolbox hinzu. Mit text setzen Sie den Text und mit iconSet das Icon für den Tab.
int addItem ( QWidget * w, const QString & text );
Dito (wie eben), nur ohne Icon.
int count () const;
Damit erhalten Sie die Anzahl der in der Toolbox befindlichen Elemente.
int currentIndex () const;
Gibt die Indexnummer (angefangen bei 0) des aktuellen Tabs in der Toolbar zurück.
QWidget * currentWidget () const;
Dito (wie eben), nur wird hier ein Zeiger auf das aktuelle Widget zurückgegeben. Gibt es hierbei kein Element, wird 0 zurückgegeben.
int indexOf ( QWidget * widget ) const;
Gibt die Indexnummer von widget zurück (angefangen mit 0). Gibt es dieses widget nicht, wird –1 zurückgegeben.
int insertItem ( int index, QWidget * widget, const QIcon & icon, const QString & text );
Fügt das Widget widget an der Position index (angefangen bei 0) in einem neuen Tab zur Toolbox hinzu. Mit text setzen Sie den Text und mit iconSet das Icon für den Tab. Wird für index ein ungültiger Wert verwendet, wird wie mit addItem() verfahren.
Tabelle 4.36
176
Gängige Methoden von QToolBox
1542.book Seite 177 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
int insertItem ( int index, QWidget * widget, const QString & text );
Dito (wie eben), nur ohne Icon
bool isItemEnabled ( int index ) const;
Ist das Element an Position index aktiviert, wird true zurückgegeben. Ansonsten false.
QIcon itemIcon ( int index ) const;
Gibt das Icon des Tabs mit der Position index zurück. Gibt es kein Icon oder ist index außerhalb des Bereichs wird NULL zurückgegeben.
QString itemText ( int index ) const;
Dito, allerdings wird der Text des Tabs zurückgegeben.
QString itemToolTip ( int index ) const;
Dito, nur wird der Text des Tooltips vom Tab zurückgegeben.
void removeItem ( int index );
Entfernt das Tab mit Inhalt an Position index von der Toolbar. Beachten Sie, dass das Element nicht gelöscht wird.
void setItemEnabled ( int index, bool enabled );
Damit aktivieren (true) bzw. deaktivieren (false; ausgrauen) Sie den Tab (und somit den Inhalt) mit der Position index.
void setItemIcon ( int index, const QIcon & icon );
Damit übergeben Sie dem Tab an der Position index das Icon icon.
void setItemText ( int index, const QString & text );
Setzt den Text des Tabs an der Position index mit text.
void setItemToolTip ( int index, const QString & toolTip );
Setzt den Tooltip-Text des Tabs an der Position index mit toolTip.
QWidget * widget ( int index ) const;
Gibt das Widget an der Position index zurück. Ansonsten bei einem Fehler NULL, wenn keines vorhanden ist.
Tabelle 4.36
Gängige Methoden von QToolBox (Forts.)
Wie schon bei der Klasse QTabWidget finden Sie mit currentChanged(int index) das einzige Signal von QToolBox. Dieses Signal wird ausgelöst, wenn der Tab gewechselt wurde. Der Parameter enthält den neuen, sichtbaren Tab-Inhalt. Dieselben Slots wie bei QTabWidget werden verwendet; der Slot setCurrentIndex(int index) wo mit dem Parameter index zum neuen Seitenindex gewech-
selt wird; Selbiges macht auch der Slot setCurrentWidget(QWidget *widget), nur dass man hierbei zum Seitenbereich des Widgets widget gewechselt.
177
4.5
1542.book Seite 178 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Hierzu nun ein Beispiel, das die Klasse QToolBox in der Praxis demonstrieren soll. Hierbei wurde – bis auf eine Signal-Slot-Verbindung, dass die Toolbox gewechselt wurde – auf jegliche Aktion verzichtet. Einfach ein Beispiel zu Anschauungszwecken. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11 12
// beispiele/qtoolbox/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include #include #include #include #include
13 class MyWidget : public QWidget { 14 Q_OBJECT 15 public: 16 MyWidget( QWidget *parent = 0); 17 private: 18 QRadioButton *rbutton1, *rbutton2, *rbutton3; 19 QCheckBox *cbutton1, *cbutton2, *cbutton3; 20 QPushButton *pbutton1,*pbutton2,*pbutton3; 21 QVBoxLayout* vBox1, *vBox2, *vBox3; 22 QGroupBox* groupBox1, *groupBox2, *groupBox3; 23 QToolBox* toolB; 24 QButtonGroup *group1, *group2, *group3; 25 public slots: 26 void getCurrentPosition( int pos ); 27 }; 28 #endif
Jetzt zur Implementierung des Codes: 00 01 02 03
// beispiele/qtoolbox/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QWidget *parent): QWidget(parent) {
178
1542.book Seite 179 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
06 07 08 09 10 11 12 13 14 15 16 17 18 19
// Radio-Buttons erzeugen rbutton1 = new QRadioButton("Radio 1"); rbutton2 = new QRadioButton("Radio 2"); rbutton3 = new QRadioButton("Radio 3"); // Button vorbelegen rbutton1->setChecked(true); // Check-Button erzeugen cbutton1 = new QCheckBox ("Check 1"); cbutton2 = new QCheckBox ("Check 2"); cbutton3 = new QCheckBox ("Check 3"); // normale Buttons erzeugen ... pbutton1 = new QPushButton("Button 1"); pbutton2 = new QPushButton("Button 2"); pbutton3 = new QPushButton("Button 3");
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
// drei Group-Boxen mit Label erzeugen groupBox1 = new QGroupBox("Bitte auswählen"); groupBox2 = new QGroupBox("Bitte ankreuzen"); groupBox3 = new QGroupBox("Bitte drücken"); // drei vertikale Layout-Boxen erzeugen vBox1 = new QVBoxLayout; vBox2 = new QVBoxLayout; vBox3 = new QVBoxLayout; // Radio-Buttons in Layoutbox vBox1 packen vBox1->addWidget(rbutton1); vBox1->addWidget(rbutton2); vBox1->addWidget(rbutton3); // Check-Buttons in Layoutbox vBox2 packen vBox2->addWidget(cbutton1); vBox2->addWidget(cbutton2); vBox2->addWidget(cbutton3); // Toggled Buttons in Layoutbox vBox3 packen vBox3->addWidget(pbutton1); vBox3->addWidget(pbutton2); vBox3->addWidget(pbutton3); // Stretch (1) für alle Layoutboxen hinzufügen vBox1->addStretch(1); vBox2->addStretch(1); vBox3->addStretch(1); // Layout der einzelnen Group-Boxen setzen groupBox1->setLayout(vBox1); groupBox2->setLayout(vBox2); groupBox3->setLayout(vBox3); // neue Toolbox erzeugen toolB = new QToolBox;
179
4.5
1542.book Seite 180 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
50 51 52 53 54 55 56 57 58 59
60
61
62 63 64 65 66 67 68 69 70 71 72 73 }
// Groupbox mit Radio-Buttons als // Seitenbereich hinzufügen toolB->addItem(groupBox1, "Radio-Buttons" ); // Groupbox mit Check-Buttons als // Seitenbereich hinzufügen toolB->addItem(groupBox2, "Check-Buttons" ); //Groupbox mit toggled-Buttons hinzufügen toolB->addItem(groupBox3, "Push-Buttons" ); // Icons zu den Tabs hinzufügen toolB->setItemIcon( 0, QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon1.png") ) ); toolB->setItemIcon(1, QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon2.png"))); toolB->setItemIcon(2, QIcon(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/icon3.png"))); // Tooltips zu den Tabs hinzufügen toolB->setItemToolTip(0, "Toolbox mit Radio-Buttons"); toolB->setItemToolTip(1, "Toolbox mit Check-Buttons"); toolB->setItemToolTip(2, "Toolbox mit Push-Buttons"); // Signal-Slot-Verbindung für die Toolbox connect( toolB, SIGNAL( currentChanged( int ) ), this, SLOT( getCurrentPosition( int ) ) ); // ein Layout-Widget für die Toolbox QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(toolB); setLayout(layout); setWindowTitle("QToolBox – Demo");
74 // Slot: Toolbox wurde gewechselt 75 void MyWidget::getCurrentPosition(int pos) { 76 QString msg = QString("Zu Tab %1 gewechselt").arg( toolB->itemText(pos) ); 77 QMessageBox::information( this,"Tab gewechsel",msg,QMessageBox::Ok); 78 }
180
1542.book Seite 181 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Ich denke, das Beispiel bedarf keiner Worte mehr. Nun noch, wie immer, ein Hauptprogramm: // beispiele/qtoolbox/main.cpp #include #include "mywidget.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MyWidget* window = new MyWidget; window->show(); return app.exec(); }
Das Programm bei der Ausführung:
Abbildung 4.64
QToolBox im Einsatz
Natürlich können Sie den Rahmen der Toolbox jetzt noch mit Methoden von QFrame verzieren, da ja QToolBox ein Abkömmling von QFrame ist. Bspw.: toolB->setFrameStyle(QFrame::Box | QFrame::Sunken);
4.5.3
Widgets zur Zustandsanzeige
Hierzu gehören alle Widgets, die einen bestimmten Vorgang bzw. Zustand auf dem Bildschirm anzeigen können, allerdings selbst keinerlei Interaktion anbieten. QProgressBar (QProgressDialog) Die Klasse QProgressBar (abgeleitet von QWidget) repräsentiert einen horizontalen bzw. vertikalen Fortschrittsbalken. Ein solcher Fortschrittsbalken zeigt dem Anwender an, wie lange bzw. wie weit eine Operation (bspw. Kopieren oder Löschen von Dateien) fortgeschritten ist oder wie lange diese noch benötigt wird. Die Zustandsleiste wird durch eine minimale und maximale Anzahl von Schritten defi-
181
4.5
1542.book Seite 182 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
niert. Damit lässt sich immer der aktuelle Schritt sowohl in Zahlen als auch in Prozent angeben. Folgende Methoden stehen Ihnen für QProgressBar zur Verfügung: Methode
Beschreibung
QProgressBar ( QWidget * parent = 0 );
Konstruktor. Erzeugt eine neue leere Zustandsleiste.
Qt::Alignment alignment () const; Gibt die Ausrichtung der Zustandsleiste zurück.
Mögliche Werte siehe Tabelle 4.8, 4.9 und 4.10. QString format () const;
Gibt das Format zurück, wie die Werte der Zustandsleiste angezeigt werden (siehe dazu. setFormat()).
bool invertedAppearance ();
Gibt true zurück, wenn die Zustandsleiste umgekehrt ausgeführt wird (also von rechts nach links). Standard ist false (von links nach rechts).
bool isTextVisible () const;
Gibt true zurück, wenn die Werte der Zustandsleiste angezeigt werden. Ansonsten wird false zurückgegeben.
int maximum () const;
Gibt den maximalen Wert der Zustandsleiste zurück.
int minimum () const;
Gibt den minimalen Wert der Zustandsleiste zurück.
Qt::Orientation orientation () const;
Gibt die Orientierung der Zustandsleiste zurück. Die Werte können Qt::Horizontal (Standard) oder Qt::Vertical sein.
void setAlignment ( Qt::Alignment alignment );
Setzt die Ausrichtung der Zustandsleiste auf alignment. Mögliche Werte siehe Tabelle 4.8, 4.9 und 4.10.
void setFormat ( const QString & format );
Damit setzen Sie den String, der für die Anzeige der Werte der Zustandsleiste verwendet wird. Folgende Formate können dabei verwendet werden: 왘
%p – wird durch komplette Prozentanzeige
왘
%v – wird durch den aktuellen Wert der Schritte
왘
%m – wird durch den gesamten Wert der Schritte
ersetzt. ersetzt. ersetzt. Der Standardwert lautet: "%p%". void setInvertedAppearance ( bool invert );
Tabelle 4.37
182
Mit true als Parameter wird die Zustandsleiste umgekehrt (von rechts nach links) ausgeführt. Standard (von links nach rechts) ist false eingestellt.
Methoden von QProgressBar
1542.book Seite 183 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setRange ( int minimum, int maximum );
Setzt die minimale und maximale Anzahl von Schritten der Zustandsleiste. Ist maximum kleiner als minimum, bekommt minimum einen gültigen Wert.
void setTextDirection ( QProgressBar::Direction textDirection );
Richtet den lesbaren Text einer vertikalen Zustandsleiste aus. Mögliche Werte hierfür sind (Standard) QProgressBar::TopToBottom und QProgressBar::BottomToTop. Diese Eigenschaft hat keinen Einfluss, wenn eine horizontale Zustandsleiste verwendet wird.
void setTextVisible ( bool visible );
Mit true (Standard) wird der Wert in der Zustandsleiste angezeigt. Mit false schalten Sie das ab.
virtual QString text () const;
Gibt den kompletten Text, wie viel Prozent bzw. und/oder Schritte (abhängig von setFormat()) ausgeführt wurden, zurück.
int value () const;
Gibt den aktuellen Wert der Zustandsleiste zurück.
Tabelle 4.37
Methoden von QProgressBar (Forts.)
Mit dem Signal valueChanged(int) von QProgressBar können Sie reagieren, wenn der angezeigte Wert in der Zustandsleiste verändert wurde. Der Parameter enthält dann den neuen Wert, der in der Zustandsleiste angezeigt wird. Die Klasse QProgressBar hingegen enthält mehrere Slots, die in der folgenden Tabelle (4.38) aufgelistet und erläutert sind. Slot
Beschreibung
void reset ();
Setzt den Fortschrittsbalken zurück. Der Fortschrittsbalken wird dabei »zurückgespult« und zeigt keinen Zustand an.
void setMaximum ( int maximum );
Setzt den maximal möglichen Wert des Fortschrittsbalkens auf maximum.
void setMinimum ( int minimum );
Setzt den mininmalen möglichen Wert des Fortschrittsbalkens auf minimum.
void setValue ( int value );
Setzt den aktuellen Wert der Zustandsleiste auf value.
void setOrientation ( Qt::Orientation ) ;
Setzt die Orientierung der Zustandsleiste auf Qt::Horizontal (Standard) oder Qt::Vertical.
Tabelle 4.38
Slots von QProgressBar
183
4.5
1542.book Seite 184 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Nun zu einem einfachen Beispiel, das die Klasse QProgressBar in der Praxis zeigen soll. Als »Aufwand«, der eben die Zustandsleiste simulieren soll, haben wir eine einfache for-Schleife verwendet, die nichts anders macht, als bis 12345678 hochzuzählen, und zwar 500 mal. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07
// beispiele/qprogressbar/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include
08 class MyWidget : public QWidget { 09 Q_OBJECT 10 public: 11 MyWidget( QWidget *parent = 0); 12 QProgressBar *pbar; 13 public slots: 14 void startProgress(); 15 }; 16 #endif
Jetzt zur Implementierung des Codes: 00 01 02 03
// beispiele/qprogressbar/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 06 // neue Zustandsleiste erzeugen 07 pbar = new QProgressBar; 08 // min. und max. Werte festlegen 09 pbar->setRange( 0, 500 ); 10 // Anzeige-Format festlegen 11 pbar->setFormat("%v von %m (%p%)"); 12 // Button zum Starten des Dialogs 13 QPushButton* but1 = new QPushButton("Starten"); 14 // ein Layout-Widget 15 QVBoxLayout *vBox = new QVBoxLayout; 16 vBox->addWidget(pbar); 17 vBox->addWidget(but1); 18 // eine Group-Box
184
1542.book Seite 185 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
19 20 21 22 23 24 25 26 27 28 29 }
QGroupBox *groupBox1 = new QGroupBox( "Fortschrittszustand"); groupBox1->setLayout(vBox); // Signal-Slot-Verbindung einrichten connect( but1, SIGNAL( clicked() ), this, SLOT( startProgress() ) ); // alles in ein Layout verpacken QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); setLayout(layout); setMinimumSize( 300, 150 ); setWindowTitle("QProgressBar – Demo");
30 // Slot: startet die Zustandsleiste und ändert den Wert 31 void MyWidget::startProgress() { 32 for (int i = 0; i < 500; i++) { 33 pbar->setValue(i); 34 for( int j=0; j < 12345678; ++j); 35 //... copy one file 36 } 37 pbar->setValue(500); 38 }
Nun noch ein Hauptprogramm dazu: 00 // beispiele/qprogressbar/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Die Zustandsleiste bei der Ausführung:
Abbildung 4.65
QProgressBar bei der Ausführung
185
4.5
1542.book Seite 186 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QProgressBar und Threads Wenn das Programm den Rechner zu stark beanspruchen sollte, können Sie Threads verwenden wie in Abschnitt 6.9 besprochen.
QProgressDialog Mit QProgressBar haben Sie eine völlig freie Gestaltungs- und Anwendungsmöglichkeit. Aber Qt bietet natürlich auch hierzu einen fertigen Dialog mit der Klasse QProgressDialog an. Die Klasse QProgressDialog bietet ebenfalls eine Menge Methoden und Slots (sowie ein Signal) an. Hierauf gehen wir jetzt allerdings nicht genauer ein, da dies eine Wiederholung der Klasse QProgressBar wäre mit z. T. anderen Bezeichnern. Für mehr Informationen sollten Sie den Assistant von Qt konsultieren. Hierzu ein einfaches Beispiel mit QProgressDialog: 00 // beispiele/qprogressdialog/main.cpp 01 #include 02 #include 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 QProgressDialog progress( 06 "Zustandsanzeige ...", "Abbrechen", 0, 2000); 07 progress.setWindowTitle("QProgressDialog"); 08 progress.show(); 09 10 11 12 13 14 15 16 17 18 }
for (int i = 0; i < 2000; i++) { progress.setValue(i); if (progress.wasCanceled()) break; for(int i=0; isetSegmentStyle(QLCDNumber::Flat); 10 lcd_time_stamp->setSegmentStyle(QLCDNumber::Flat); 11 lcd_date_time->setFrameStyle(QFrame::NoFrame); 12 lcd_time_stamp->setFrameStyle(QFrame::NoFrame); 13 lcd_time_stamp->setSmallDecimalPoint(true); 14 // Timmer starten,der alle 1000ms das Signal timeout 15 // auslöst und den eigenen Slot showTime() aufruft 16 QTimer *timer = new QTimer(this); 17 connect( timer, SIGNAL(timeout()), this, SLOT(showTime()) ); 18 timer->start(1000); 19 // die üblichen Boxen 20 QVBoxLayout *vBox1 = new QVBoxLayout; 21 vBox1->addWidget(lcd_date_time); 22 QVBoxLayout *vBox2 = new QVBoxLayout; 23 vBox2->addWidget(lcd_time_stamp); 24 // Verpacken 25 QGroupBox *groupBox1 = new QGroupBox( "Datum und Uhrzeit"); 26 groupBox1->setLayout(vBox1); 27 QGroupBox *groupBox2 = new QGroupBox( "Unix-Timestamp"); 28 groupBox2->setLayout(vBox2); 29 // ... und das Layout setzen 30 QVBoxLayout* layout = new QVBoxLayout; 31 layout->addWidget(groupBox1); 32 layout->addWidget(groupBox2); 33 setLayout(layout);
190
1542.book Seite 191 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
34 35 36 37 }
// damit die Größe passt, den Slot einmal aufrufen showTime(); setWindowTitle(tr("LCD Demo"));
38 void MyWidget::showTime() { 39 // aktuelles Datum und Uhrzeit 40 QDateTime time = QDateTime::currentDateTime(); 41 // formatieren 42 QString text=time.toString("ddd dd.MM.yyyy hh:mm:ss"); 43 // der Unix-Timestamp 44 unsigned int i_stamp = time.toTime_t(); 45 // auch Formatieren 46 QString s_stamp = QString("%1").arg(i_stamp); 47 // Anzahl der Ziffern setzen 48 lcd_date_time->setNumDigits(text.size()+1); 49 lcd_time_stamp->setNumDigits(s_stamp.size()+1); 50 // und alles anzeigen 51 lcd_date_time->display(text); 52 lcd_time_stamp->display(s_stamp); 53 }
Jetzt nur noch eine Hauptfunktion: 00 // beispiele/qlcdnumber/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Bei der Ausführung erhalten Sie folgendes Bild:
Abbildung 4.67
Die Klasse QLCDNumber bei der Ausführung
191
4.5
1542.book Seite 192 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QLabel Die von QFrame abgeleitete Klasse QLabel wurde ja schon bei den ersten Qt-Programmen verwendet und dient dazu, einen Text oder ein Bild anzuzeigen ohne jede Anwender-Interaktionen. In dieser Klasse können Sie folgende Typen »stecken«: 왘
Einfacher Text
왘
Rich-Text (RTF)
왘
Pixmap
왘
Movie (Film)
왘
Eine Zahl
Sie sehen: In QLabel steckt also ein wenig mehr als nur ein langweiliges TextWidget. Fangen wir daher am besten wieder mit den Methoden von QLabel an: Methode
Beschreibung
QLabel ( QWidget * parent = 0, Qt::WindowFlags f = 0 );
Erzeugt ein leeres Label.
QLabel ( const QString & text, QWidget * parent = 0, Qt::WindowFlags f = 0 );
Erzeugt ein Label mit dem Text text.
~QLabel ();
Destruktor. Zerstört ein Label.
Qt::Alignment alignment () const; Damit erhalten Sie die Ausrichtung des Labels
zurück. Mögliche Werte wurden bereits in Tabelle 4.8, 4.9 und 4.10 beschrieben. QWidget * buddy () const;
Gibt den Buddy des Labels zurück (siehe setBuddy()).
bool hasScaledContents () const;
Gibt true zurück, wenn das Label skalierbar ist, der Inhalt also den vorhandenen Raum ausfüllt. Wenn diese Option aktiviert ist und das Label ein Pixmap ist, wird Letzterer immer auf dem vorhandenen Raum skaliert.
int margin () const;
Damit erhalten Sie die Breite des Randbegrenzers in Pixel. Dieser Rand ist der Abstand zwischen dem innersten Pixel des Frames und dem äußersten Pixel des Inhalts. Der Standardwert ist 0.
QMovie * movie () const;
Gibt einen Zeiger auf dem Film des Labels zurück. Ist hier keiner, wird 0 zurückgegeben.
Tabelle 4.43
192
Methoden von QLabel
1542.book Seite 193 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
bool openExternalLinks () const;
Wird true zurückgegeben, wird der Link automatisch mit QDesktopServices::openUrl() aufgerufen und nicht das Signal anchorClicked() ausgelöst. Ansonsten wird false zurückgegeben.
const QPicture * picture () const;
Gibt einen Zeiger auf ein QPicture-Objekt zurück. Existiert kein solches, wird 0 zurückgegeben.
const QPixmap * pixmap () const;
Damit wird ein Zeiger auf das Pixmap des Labels zurückgegeben. Existiert kein solches Pixmap, wird ein »ungültiges« Pixmap zurückgegeben.
void setAlignment ( Qt::Alignment ) ;
Damit setzen Sie die Ausrichtung des Labels. Mögliche Werte wurden bereits in Tabelle 4.8, 4.9 und 4.10 beschrieben.
void setBuddy ( QWidget * buddy );
Damit setzen Sie den »Kumpel« von einem Label. Dieser Mechanismus ist nur dem Label mit normalem Text vorbehalten. Damit können Sie ein Tastaturkürzel für ein Label einrichten und den Fokus dazu auf das Buddy-Widget richten. Der Text im Label benötigt ein Ampersandzeichen vor einem Buchstaben. Dieser Buchstabe ist dann das Tasturkürzel für den Buddy des Labels. Hierzu sollten Sie sich anschließend das Programmbeispiel ansehen.
void setMargin ( int );
Damit setzen Sie die Breite des Randbegrenzers in Pixel. Dieser Rand ist der Abstand zwischen dem innersten Pixel des Frames und dem äußersten Pixel des Inhalts. Der Stanardwert ist 0.
void setOpenExternalLinks ( bool open );
Mit true wird bei einem Link automatisch DesktopServices::openUrl() aufgerufen und nicht das Signal anchorClicked() ausgelöst.
void setScaledContents ( bool );
Mit true legen Sie fest dass das Label (wenn es sich um eine Grafik oder einen Film handelt) skaliert werden kann, damit es den vorhandenen Platz auffüllen kann. Mit false schalten Sie es ab. Beachten Sie allerdings, dass größer skalierte Grafiken immer gröberkörniger werden.
void setTextFormat ( Qt::TextFormat ) ;
Setzt das Format eines Text-Labels. Mögliche Formate siehe Tabelle 4.44.
void setTextInteractionFlags ( Qt::TextInteractionFlags flags ) ;
Legt fest, wie der Anwender mit einem Text-Label umgehen kann (bspw. Kopieren, ...). Mögliche Werte siehe Tabelle 4.45.
Tabelle 4.43
Methoden von QLabel (Forts.)
193
4.5
1542.book Seite 194 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
void setWordWrap ( bool on );
Damit können Sie den Umbruch einer Zeile nach einem Wort einschalten (true). Ist die Zeile bspw. länger als der Frame, in dem sich das Label befindet, wird der Text umbrochen und nicht (Standard) der Frame in die Länge gezogen.
QString text () const;
Gibt den Text des Labels zurück.
Qt::TextFormat textFormat () const ;
Gibt das Format des Text-Labels zurück (mögliche Werte siehe Tabelle 4.44).
Qt::TextInteractionFlags textInteractionFlags () const;
Gibt zurück, wie der Anwender mit einem TextLabel umgehen kann (bspw. Kopieren, ...). Mögliche Werte siehe Tabelle 4.45.
bool wordWrap () const;
Ermittelt, ob der Umbruch von einer überlangen Zeile nach einem Wort eingeschalten (true) ist. Ansonsten wird false zurückgegeben.
Tabelle 4.43
Methoden von QLabel (Forts.)
Hierzu nun die möglichen Werte für das Text-Format eines Labels, die mit der Methode setTextFormat() bzw. textFormat() gesetzt bzw. abgefragt werden können. Folgende enum-Konstanten sind mit Qt::TextFormat definiert: Konstante
Beschreibung
Qt::PlainText
Der Text wird als normaler Text interpretiert.
Qt::RichText
Der Text wird als Rich-Text (RTF) interpretiert.
Qt::AutoText
Der Text wird als Rich-Text angezeigt, wenn Qt::mightBeRichtText() true zurückgibt. Ansonsten wird er als normaler Text interpretiert.
Qt::LogText
Ein spezielles Text-Format, welches normalerweise nur von QTextEdit verwendet wird.
Tabelle 4.44
Mögliche Formate für QLabel
Jetzt noch die möglichen Werte für die enum-Konstante Qt::TextInteractionFlag, mit der Sie festlegen können, wie der Anwender mit dem Text-Label umgehen kann. Alle Werte können mit der Methode setTextInteractionFlags() bzw. textInteractionFlags() gesetzt bzw. abgefragt werden.
194
1542.book Seite 195 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Konstante
Beschreibung
Qt::NoTextInteraction
Keine Aktionen mit dem Text-Label möglich.
Qt::TextSelectableByMouse
Der Text kann mit der Maus selektiert und via Kontextmenü in die Zwischenablage kopiert werden.
Qt::TextSelectableByKeyboard
Der Text kann mit den Pfeiltasten der Tastatur selektiert werden. Ein Text-Cursor wird ebenfalls angezeigt.
Qt::LinksAccessibleByMouse
Links können hervorgehoben und mit der Maus aktiviert werden.
Qt::LinksAccessibleByKeyboard
Links können den Fokus mit der (ÿ)-Taste erhalten und mit (¢) aktiviert werden.
Qt::TextEditable
Der Text kann verändert werden.
Qt::TextEditorInteraction
Eine Kombination aus: TextSelectableByMouse | TextSelectableByKeyboard | TextEditable
Qt::TextBrowserInteraction
Eine Kombination aus: TextSelectableByMouse | LinksAccessibleByMouse | LinksAccessibleByKeyboard
Tabelle 4.45
Mögliche Aktionen auf einem Text-Label
Hierzu wieder ein Beispiel, das die Klasse QLabel mit möglichst vielen Methoden in der Praxis demonstrieren soll. Für das QMovie-Objekt wurde ein neueres Datenformat mit MNG verwendet. MNG-Format MNG (kurz für Multiple-image Network Graphics) ist ein öffentliches Dateiformat zur Beschreibung animierter Grafikdateien. MNG ist eng verwandt mit dem PNG-Grafikformat. Die Entwickler des MNG-Formates hoffen, dass MNG beginnen wird, GIF für animierte Bilder im World Wide Web zu ersetzen, wie es PNG teilweise bereits für nicht animierte Bilder getan hat.
Wichtig ist auf jeden Fall, dass Sie die Grafikdatei in ein Verzeichnis imageformats legen, welches sich im selben Verzeichnis befindet wie die auszuführende Datei (siehe auch das Beispiel auf der Buch-DVD). In unserem Beispiel wurde eine sich drehende Büroklammer verwendet.
195
4.5
1542.book Seite 196 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Unterstützte Datenformate für QLabel Welche Formate unterstützt werden, müssen Sie im Verzeichnis plugins/imageformats der Qt-Distribution entnehmen. Darin finden Sie die Librarys bzw. die dazu benötigten DLLs. Hierbei können natürlich jederzeit weitere Formate nachinstalliert werden. Standardmäßig findet man hier gewöhnlich SVG, JPEG und MNG (mitsamt PNG).
Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10
// beispiele/qlabel/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include #include #include
11 class MyWidget : public QWidget { 12 Q_OBJECT 13 public: 14 MyWidget(QWidget *parent = 0); 15 QLabel *lab_movie; 16 QLabel *text; 17 QMovie *movie; 18 }; 19 #endif
Jetzt der eigentliche Code: 00 // beispiele/qlabel/mywidget.cpp 01 #include "mywidget.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 05 // zwei Label erzeugen 06 lab_movie = new QLabel; 07 // Animation laden 08 movie = new QMovie(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/imageformats/bild1.mng"));
196
1542.book Seite 197 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
09 10 11 12 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
// Animation setzen lab_movie->setMovie(movie); // Animation starten (QMovie::start()) movie->start(); lab_movie->setAlignment(Qt::AlignCenter); lab_movie->setScaledContents ( true ); text = new QLabel( "Ein simpler Text, der auch kopiert werden " "kann. Außerdem wurde der Umbruch von Worten " "aktierviert" ); text->setOpenExternalLinks ( true ); text->setAlignment(Qt::AlignCenter); text->setOpenExternalLinks ( true ); text->setTextInteractionFlags( Qt::TextEditorInteraction ); text->setWordWrap(true); text->setMargin(10); // ein Editierfeld der Klasse QLineEdit QLineEdit *name = new QLineEdit(this); // ein Textlabel mit Tastaturkürzel ALT+N QLabel *nameLabel = new QLabel( "[&Name – ALT+N drücken]", this ); // das Editierfeld zum Kumpel von nameLabel erklären // damit wird beim Drücken von ALT+N in das // Editierfeld gesprungen nameLabel->setBuddy(name); // dasselbe mit einem zweiten Editiertfeld und Label // und hier der Tastenkombination ALT+V QLineEdit *vname = new QLineEdit(this); QLabel *vnameLabel = new QLabel( "[&Vorname – ALT+V drücken]:", this ); vnameLabel->setBuddy(vname); // die üblichen Boxen QVBoxLayout *vBox1 = new QVBoxLayout; vBox1->addWidget(lab_movie); QVBoxLayout *vBox2 = new QVBoxLayout; vBox2->addWidget(text); QVBoxLayout *vBox3 = new QVBoxLayout; vBox3->addWidget(name); vBox3->addWidget(nameLabel); vBox3->addWidget(vname); vBox3->addWidget(vnameLabel); // Verpacken QGroupBox *groupBox1 = new QGroupBox( "A MNG-Movie");
197
4.5
1542.book Seite 198 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
48 49 50 51 52 53 54 55 56 57 58 59 60 }
groupBox1->setLayout(vBox1); QGroupBox *groupBox2 = new QGroupBox( "Simple Text"); groupBox2->setLayout(vBox2); QGroupBox *groupBox3 = new QGroupBox( "My Buddy"); groupBox3->setLayout(vBox3); // ... und das Layout setzen QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); layout->addWidget(groupBox2); layout->addWidget(groupBox3); setLayout(layout); setWindowTitle(tr("QLabel"));
Die hier für unser »Buddy«-Beispiel verwendete Klasse QLineEdit erläutern wir auf den nächsten Seiten näher. Nun noch eine Hauptfunktion: 00 // beispiele/qlabel/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.68
198
QLabel bei der Ausführung
1542.book Seite 199 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
4.5.4
Widgets für die Eingabe
Bei den in Kürze folgenden Klassen kommen wir zu den Widgets, die bei jeder Anwender-Interaktion erforderlich sind, ohne die keine grafische Oberfläche auskommt. QAbstractSlider Die (Basis-)Klasse QAbstractSlider unterstützt einen Integerwert der mit einem Schieberegler in einem bestimmten Bereich ausgewählt werden kann. QAbstractSlider ist die Basisklasse für QSlider, QDial und QScrollBar (siehe Abbildung 4.69).
QWidget
QAbstractSlider
QSlider
QDial
Abbildung 4.69
QScrollBar
Klassenhierarchie von QAbstractSlider
Alle Methoden, Slots und Signale können also für diese davon abgeleiteten Klassen verwendet werden. Hierzu die anschließend z. T. im Beispiel mit QSlider und QDial eingesetzten öffentlichen Methoden. Methode
Beschreibung
QAbstractSlider ( QWidget * parent = 0 );
Erzeugt einen neuen abstrakten Schieberegler. Standard ist der minimale Wert 0 und der maximale Wert 99.
~QAbstractSlider ();
Destruktor. Zerstört einen abstrakten Schieberegler.
bool hasTracking () const;
Wird true (Standard) zurückgegeben, ist das Tracking eingeschaltet. Ist dies der Fall, wir das Signal valueChanged() ausgelöst, sobald der Schieberegler gezogen wird. Ist das Tracking ausgeschaltet (false), wird das Signal valueChanged() ausgelöst, wenn der Schieberegler nach dem Ziehen losgelassen wurde.
bool invertedAppearance () const;
Wird true zurückgegeben, ist die Position des minimalen und maximalen Wertes vertauscht. Standardmäßig wird false zurückgegeben.
Tabelle 4.46
Methoden von QAbstractSlider
199
4.5
1542.book Seite 200 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool invertedControls () const;
Wir hier true zurückgegeben, ist die Kontrolle der Mausrad- und Tastatur-Events vertauscht. Wird die Pfeil-nach-oben-Taste betätigt, wird der Schieberegler dekrementiert und nicht standardmäßig inkrementiert. Das Gleiche gilt für das Mausrad. Standardmäßig ist diese Eigenschaft auf false gesetzt.
int maximum () const;
Damit wird der maximal mögliche Wert des Schiebereglers zurückgegeben.
int minimum () const;
Damit wird der minimal mögliche Wert des Schiebereglers zurückgegeben.
Qt::Orientation orientation () const ;
Gibt die Orientierung des Schiebereglers zurück. Mögliche Werte sind Qt::Vertical (Standard) oder Qt::Horizontal.
int pageStep () const;
Gibt die Anzahl der Integerwerte zurück, die inkrementiert bzw. dekrementiert werden, wenn der Anwender nicht den Schieberegler mit der Tastatur bzw. Maus zieht, sondern im Bereich der Linie des Schiebereglers klickt.
void setInvertedAppearance ( bool );
Mit true vertauschen Sie die Position der minimalen und maximalen Werte. false bewirkt das Gegenteil.
void setInvertedControls ( bool );
Mit true vertauschen Sie die Kontrolle der Maus und der Tastatur. Wenn man die Pfeil-nach-oben-Taste betätigt, wird der Schieberegler dekrementiert, anstatt standardmäßig inkrementiert zu werden. Das Gleiche gilt für das Mausrad. false bewirkt das Gegenteil.
void setMaximum ( int );
Setzt den maximal möglichen Wert des Schiebereglers.
void setMinimum ( int );
Setzt den minimal möglichen Wert des Schiebereglers.
void setPageStep ( int );
Setzt den Wert zurück, der inkrementiert bzw. dekrementiert wird, wenn der Anwender nicht den Schieberegler mit der Tastatur bzw. Maus zieht, sondern im Bereich der Linie des Schiebereglers klickt.
void setRange ( int min, int max );
Setzt den minimal und maximal möglichen Wertebereich des Schiebereglers.
void setSingleStep ( int );
Setzt die Anzahl des Werts auf dem Schieberegler, der inkrementiert bzw. dekrementiert wird, wenn der Anwender die Pfeil-Tasten verwendet.
void setSliderPosition( int ); Setzt den Schieberegler zum Programmstart auf einen
bestimmten Wert (bzw. Position). Ist der Wert außerhalb eines gültigen Bereichs, wird eben der Wert auf den minimal bzw. maximal möglichen Wert gesetzt. Tabelle 4.46
200
Methoden von QAbstractSlider (Forts.)
1542.book Seite 201 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setTracking (bool enable);
Mit false schalten Sie das Tracking ab (siehe hasTracking()). Mit true schalten Sie es wieder ein (Standard).
int singleStep () const;
Gibt einen Wert zurück, den der Schieberegler inkrementiert bzw. dekrementiert, wenn der Anwender die Pfeil-Tasten verwendet.
int sliderPosition () const;
Gibt die aktuelle Position des Schiebereglers zurück.
void triggerAction ( SliderAction action );
Damit können Sie eine bestimmte Aktion auf einem Schieberegler ausführen. Mögliche Werte siehe Tabelle 4.49.
int value () const;
Gibt den aktuellen Integerwert des Schiebereglers zurück.
Tabelle 4.46
Methoden von QAbstractSlider (Forts.)
Nun zu den öffentlichen Slots der Basisklasse QAbstractSlider, die auch den davon abgeleiteten Klassen QSlider, QDial und QScrollBar zur Verfügung stehen: Slot
Beschreibung
void setOrientation ( Qt::Orientation );
Setzt die Orientierung des Schiebereglers auf Qt::Vertical (Standard) oder Qt::Horizontal.
void setValue ( int );
Setzt den Wert des Schiebereglers.
Tabelle 4.47
Öffentliche Slots von QAbstractSlider
Öffentliche Signale der Basisklasse QAbstractSlider: Signal
Beschreibung
void actionTriggered ( int action );
Dieses Signal wird ausgelöst, wenn eine bestimmte Aktion des Schiebereglers ausgelöst wurde. Mögliche Aktionen siehe Tabelle 4.49. Beachten Sie, wenn das Signal ausgelöst wurde, dass dies nichts mit dem Wert des Schiebereglers zu tun hat, sondern nur mit einer Aktion des Schiebereglers (siehe auch triggerAction()).
void rangeChanged ( int min, int max );
Dieses Signal wird ausgelöst, wenn der minimale und/oder maximale Wertebereich des Schiebereglers verändert wurde.
Tabelle 4.48
Öffentliche Slots von QAbstractSlider
201
4.5
1542.book Seite 202 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Signal
Beschreibung
void sliderPressed ();
Signal wird ausgelöst, wenn der Anwender den Schieberegler mit der Maus niederdrückt.
void sliderReleased ();
Signal wird ausgelöst, wenn der Anwender den Schieberegler mit der Maus loslässt.
void valueChanged ( int value ); Signal wird ausgelöst, wenn der Schieberegler betä-
tigt wurde und der Wert sich verändert hat. Tabelle 4.48
Öffentliche Slots von QAbstractSlider (Forts.)
Jetzt noch die möglichen Werte der enum-Konstante SliderAction, die Sie mit der Methode triggerAction() auslösen bzw. die mit dem Signal actionTriggered() aufgefangen werden können. Konstante QAbstractSlider::SliderNoAction QAbstractSlider::SliderSingleStepAdd QAbstractSlider::SliderSingleStepSub QAbstractSlider::SliderPageStepAdd QAbstractSlider::SliderPageStepSub QAbstractSlider::SliderToMinimum QAbstractSlider::SliderToMaximum QAbstractSlider::SliderMove
Tabelle 4.49
Konstanten für QAbstractSlider::SliderAction
QSlider Die von QAbstractSlider abgeleitete Klasse QSlider wird für einen vertikalen oder horizontalen Schieberegler verwendet. Dies ist im Grunde auch das klassische Widget zum Verändern begrenzter Werte. Zusätzlich zu den Methoden der Basisklasse QAbstractSlider bietet QSlider folgende Methoden an: Methode
Beschreibung
QSlider ( QWidget * parent = 0 );
Konstruktor. Erzeugt ein neues QSlider-Objekt.
QSlider ( Qt::Orientation orientation, QWidget * parent = 0 ) ;
Dito, nur kann hierbei die Ausrichtung (Qt::Vertical oder Qt::Horizontal) mit angegeben werden.
~QSlider ();
Destruktor. Zerstört ein QSlider-Objekt.
Tabelle 4.50
202
Methoden der Klasse QSlider
1542.book Seite 203 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setTickInterval ( int ti );
Damit legen Sie das Intervall der einzelnen Striche fest. Geben Sie hier bspw. 1 an und haben einen Bereich von 0 bis 100, hätten Sie 100 Striche am Schieberegler. Bei einem Wert von 10 hätten Sie nur noch 10 Striche auf der Leiste.
void setTickPosition ( TickPosition position );
Damit legen Sie fest, wie und wo (und ob) Sie die die einzelnen Striche auf der Schiebeleiste festlegen. Mögliche Werte siehe Tabelle 4.51.
int tickInterval () const;
Gibt das Intervall der einzelnen Striche zurück. Standardmäßig wird hierbei 0 zurückgegeben.
TickPosition tickPosition () const;
Gibt die Position der Striche zurück. Mögliche Werte siehe Tabelle 4.51.
Tabelle 4.50
Methoden der Klasse QSlider (Forts.)
Hierzu nun die Werte, um die Position und die Art der einzelnen Striche auf dem Schieberegler festzulegen (setTickPosition() und tickPosition()). Konstante
Beschreibung
QSlider::NoTicks
Keine Striche werden gezeichnet.
QSlider::TicksBothSides
Zeichnet auf beiden Seiten des Schiebereglers Striche.
QSlider::TicksAbove
Zeichnet Striche über dem (horizontalen) Schieberegler.
QSlider::TicksBelow
Zeichnet Striche unter dem (horizontalen) Schieberegler.
QSlider::TicksLeft
Zeichnet Striche links vom (vertikalen) Schieberegler.
QSlider::TicksRight
Zeichet Striche rechts vom (vertikalen) Schieberegler.
Tabelle 4.51
Position und Art der Striche (Ticks) festlegen
Eigene Signale und Slots sind bei der Klasse QSlider nicht definiert und auch nicht nötig, da die Basisklasse QAbstractSlider alles anbietet, was hierfür erforderlich ist. Ein Beispiel zu QSlider, zusammen mit dem nächsten Widget QDial, finden Sie im Anschluss nach der Beschreibung von QDial. QDial Die Klasse QDial ist ein spezielles – wenig bekanntes – Widget (Dial=Skala). Dabei handelt es sich ebenfalls um eine Art Schieberegler, allerdings um einen runden, der einem Tachometer gleicht. Ansonsten ist das Anwendungsgebiet ähnlich wie bei QSlider, nur dass Sie hier wieder von vorne (oder auch von hinten) anfangen können, wenn Sie den Wertebereich über- bzw. unterschreiten.
203
4.5
1542.book Seite 204 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Neben den Methoden der Basisklasse QAbstractSlider bietet QDial folgende Methoden an: Methode
Beschreibung
QDial(QWidget * parent=0);
Konstruktor. Erzeugt eine neue Skala.
~QDial ();
Destruktor. Zerstört eine Skala.
int notchSize () const;
Gibt die Anzahl der Kerben an der Skala zurück.
Qreal notchTarget () const;
Gibt den Abstand der einzelnen Kerben in Pixel zurück. Der Standardwert lautet 3.7 Pixel.
bool notchesVisible () const;
Gibt true zurück, wenn die Kerben sichtbar sind.
void setNotchTarget( double target );
Setzt den Abstand in Pixeln zwischen den einzelnen Kerben. Der Standardwert lautet 3.7.
bool wrapping () const;
Gibt false zurück, wenn der Platz unten am Ende der Skala mit einem extra leeren Bereich ausgefüllt wird (Standard). Wird true zurückgegeben, ist kein Bereich an dieser Stelle, und die Skala ist durchgehend.
Tabelle 4.52
Methoden von QDial
Die Klasse QDial definiert keine eigenen Signale und verwendet die von der Basisklasse QAbstractSlider abgeleiteten Signale. Hingegen definiert QDial die beiden folgenden Slots: Slot
Beschreibung
void setNotchesVisible ( bool visible );
Damit können Sie die Kerben der Skala mit false abschalten bzw. mit true (Standard) wieder einschalten.
void setWrapping ( bool on );
Mit true wird der per Standard verwendete leere Bereich unten geschlossen, so dass aus dem Tachometer quasi eine Uhr wird. Standardwert ist false.
Tabelle 4.53
Slots von QDial
Hierzu nun ein einfaches Beispiel, welches die Verwendung von QSlider und QDial mit einigen Methoden in der Praxis zeigen soll. Das Grundgerüst: 00 01 02 03
204
// beispiele/qslider/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include
1542.book Seite 205 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
04 05 06 07 08 09
#include #include #include #include #include #include
10 11 12 13 14 15 16 17 18 19 20 21 22 23
class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = 0); QLabel *text; QLabel *text2; QPushButton* but1; QSlider* slider; QDial* dial; public slots: void setLabel( int val ); void setLabel2( int val ); }; #endif
Jetzt wieder der eigentliche Code: 00 // beispiele/qslider/mywidget.cpp 01 #include "mywidget.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 05 text = new QLabel("Aktueller Wert: --"); 06 text->setMargin(10); 07 text->setAlignment(Qt::AlignCenter); 08 09 10
text2 = new QLabel("Aktueller Wert: --"); text2->setMargin(10); text2->setAlignment(Qt::AlignCenter);
11 12 13 14 15
slider = new QSlider(Qt::Horizontal); slider->setTickInterval( 10 ); slider->setRange ( 0, 100 ); slider->setTickPosition(QSlider::TicksAbove); slider->setSliderPosition( 50 );
16
dial = new QDial;
205
4.5
1542.book Seite 206 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
17 18 19 20
dial->setNotchesVisible(true); dial->setRange ( 0, 100 ); dial->setNotchTarget ( 7.4 ); dial->setWrapping( true );
21 22 23 24
// die üblichen Boxen QVBoxLayout *vBox1 = new QVBoxLayout; vBox1->addWidget(text); vBox1->addWidget(slider);
25 26 27
QVBoxLayout *vBox2 = new QVBoxLayout; vBox2->addWidget(text2); vBox2->addWidget(dial);
28 29
// Verpacken QGroupBox *groupBox1 = new QGroupBox( "Ein einfacher Slider"); groupBox1->setLayout(vBox1); QGroupBox *groupBox2 = new QGroupBox( "Ein einfache Skala"); groupBox2->setLayout(vBox2);
30 31 32 33 34
35 36 37 38 39 40 41 }
connect( slider, SIGNAL( valueChanged( int ) ), this, SLOT( setLabel( int ) ) ); connect( dial, SIGNAL( valueChanged( int ) ), this, SLOT( setLabel2( int ) ) ); // ... und das Layout setzen QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); layout->addWidget(groupBox2); setLayout(layout); setWindowTitle(tr("QAbstractSlider – Demo"));
42 void MyWidget::setLabel( int val ) { 43 text->setText(QString("Aktueller Wert: %1").arg(val)); 44 } 45 void MyWidget::setLabel2( int val ) { 46 text2->setText( QString("Aktueller Wert: %1").arg(val) ); 47 }
206
1542.book Seite 207 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Nun noch eine Hauptfunktion: 00 // beispiele/qslider/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.70
QSlider und QDial bei der Ausführung
QScrollBar Die Klasse QScrollBar stellt eine vertikale oder horizontale Scroll-Leiste zur Verfügung. Diese Klasse wird gewöhnlich dort eingesetzt, wo ein Widget länger bzw. breiter ist, als dies auf dem Bildschirm dargestellt werden kann. Damit kann sich der Anwender dann zum entsprechenden Bereich navigieren. Sollten Sie ein Scrolling auf ein anderes Widget benötigen, ist es besser, wenn Sie hierfür die (hier nur der Vollständigkeit halber erwähnte) Klasse QScrollArea verwenden. QLineEdit Die von QWidget abgeleitete Klasse QLineEdit ist ein einzeiliges Texteingabefeld. Verglichen mit anderen Bibliotheken für grafische Oberflächen, ist QLineEdit schon eher ein einzeiliger Texteditor.
207
4.5
1542.book Seite 208 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QLineEdit bietet natürlich auch alle grundlegenden Aktionen, wie das Kopieren,
Ausschneiden, Einfügen und Drag & Drop von normalem Text. Hierzu zunächst eine Zusammenstellung der vielen Methoden, die die Klasse QLineEdit anbietet. Methode
Beschreibung
QLineEdit (QWidget * parent = 0);
Erzeugt ein neues einzeiliges Textfeld.
QLineEdit ( const QString & contents, QWidget * parent = 0 );
Erzeugt ein neues einzeiliges Textfeld, das mit contents vorbelegt ist.
~QLineEdit ()
Destruktor. Zerstört ein Textfeld.
Qt::Alignment alignment () const;
Gibt die aktuelle Ausrichtung des Texts zurück (mögliche Werte wurden bereits in Tabelle 4.8, 4.9 und 4.10 erläutert).
void backspace ();
Wenn kein Text markiert wurde, wird das Zeichen links vom Text-Cursor gelöscht. Sollte ein Text markiert werden, wird alles bis zum Anfang der Markierung (der markierte Text also) gelöscht.
QCompleter* completer () const
Gibt, falls vorhanden, ein Objekt zurück, welches einen oder mehrere Strings enthält, was zur Autovervollständigung des Texts verwendet wird. Siehe setCompleter().
QMenu * createStandardContextMenu ();
Damit können Sie ein eigenes Standard-Kontextmenü erzeugen, das erscheint, wenn der Anwender mit der rechten Maustaste im Textfeld klickt. Siehe auch Klasse QMenu (Abschnitt 5.2.2).
void cursorBackward ( bool mark, int steps = 1 );
Bewegt den Cursor um steps Schritte von der aktuellen Position zurück. Ist mark=true, wird jedes Zeichen außerdem markiert. Mit false kann eine Markierung zurückgenommen werden.
void cursorForward ( bool mark, int steps = 1 );
Bewegt den Cursor um steps Schritte von der aktuellen Position nach vorne. Ist mark=true, wird jedes Zeichen zudem noch markiert. Mit false kann eine Markierung zurückgenommen werden.
int cursorPosition () const;
Gibt die aktuelle Position des Cursors zurück.
int cursorPositionAt ( const QPoint & pos );
Gibt die Cursor-Position vom Punkt pos zurück.
void cursorWordBackward ( bool mark );
Bewegt den Cursor ein Wort zurück. Ist mark zudem true, wird das Wort markiert.
Tabelle 4.54
208
Methoden der Klasse QLineEdit
1542.book Seite 209 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void cursorWordForward ( bool mark );
Bewegt den Cursor ein Wort nach vorne. Ist mark zudem true, wird das Wort markiert.
void del ();
Ist kein Text markiert, wird das Zeichen rechts vom Cursor gelöscht. Ist hingegen ein Text markiert, wird der Text bis zum Anfang des markierten Texts gelöscht.
void deselect ();
Hebt die Markierung eines markierten Texts auf.
QString displayText () const;
Damit wird der angezeigte Text des Textfeldes zurückgegeben.
bool dragEnabled () const;
Gibt diese Methode true zurück, ist das Ziehen (Drag) eines Texts aktiviert. Standardwert ist false.
EchoMode echoMode () const;
Gibt den aktuellen Ausgabemodus des Eingabefeldes zurück. Mögliche Rückgabewerte siehe Tabelle 4.55.
void end ( bool mark );
Bewegt den Text-Cursor ans Ende der Zeile. Ist mark gleich true, wird außerdem der Text von der aktuellen Position bis zum Ende markiert.
bool hasAcceptableInput () const;
Hier wird true zurückgegeben, wenn die Eingabe einer Maske bzw. einem Validator entspricht. Ansonsten wird false zurückgegeben.
bool hasFrame () const;
true wird zurückgegeben (Standardwert), wenn
bool hasSelectedText () const;
Ist ein Text im Textfeld markiert, wird true zurückgegeben. Ansonsten, wenn kein Text markiert ist, wird false zurückgegeben.
void home ( bool mark );
Setzt den Cursor an den Anfang des Textfeldes. Ist mark gleich true, wird zusätzlich der Text von der aktuellen Position bis zum Anfang markiert.
QString inputMask () const;
Hiermit erhalten Sie die gesetzte Eingabemaske zurück. Ist keine solche Maske gesetzt, wird ein leerer String zurückgegeben. Siehe Methode setInputMask().
void insert ( const QString & newText );
Löscht einen markierten Text und fügt einen neuen Text ein.
bool isModified () const;
Gibt true zurück, wenn der Anwender das Textfeld verändert hat. Ansonsten wird false zurückgegeben.
der Text um das Textfeld einen Rahmen besitzt. Ansonsten wird false zurückgegeben.
Tabelle 4.54
Methoden der Klasse QLineEdit (Forts.)
209
4.5
1542.book Seite 210 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool isReadOnly () const;
Gibt true zurück, wenn das Textfeld nur lesbar ist. Ansonsten wird false zurückgegeben.
bool isRedoAvailable () const;
Gibt true zurück, wenn ein Schritt im Eingabefeld wiederholt werden kann. Ansonsten wird false zurückgegeben.
bool isUndoAvailable () const;
Gibt true zurück, wenn ein zuvor gemachter Schritt im Eingabefeld Rückgängig gemacht werden kann. Ansonsten wird false zurückgegeben.
int maxLength () const;
Gibt die maximal mögliche Länge des Eingabefeldes zurück.
virtual Qsize minimumSizeHint () const;
Gibt die minmale Größe des Textfeldes zurück.
QString selectedText () const;
Gibt den markierten Text zurück. Würde kein Text markiert, wird ein leerer String zurückgegeben.
int selectionStart () const;
Gibt die Anfangsposition des markierten Texts zurück.
void setAlignment ( Qt::Alignment flag );
Setzt die Ausrichtung des Texts auf flag. Hierbei sind allerdings nur horizontale Ausrichtungen möglich. Mögliche Werte wurden bereits in Tabelle 4.8, 4.9 und 4.10 erläutert.
void setCompleter ( QCompleter * c );
Setzt einen Autovervollständigungstext c für das Eingabefeld. Enthält das Objekt QCompleter bspw. Strings wie Auto und Asche, werden beide Wörter angezeigt, sobald der Anwender ein A eingibt (siehe hierzu auch das Programmbeispiel).
void setCursorPosition ( int );
Setzt die Position des Cursors im Textfeld.
void setDragEnabled ( bool b );
Schaltet das Ziehen (Drag) des Texts mit true ein. Mit false wird es wieder ausgeschalten.
void setEchoMode ( EchoMode );
Setzt den Ausgabemodus auf EchoMode. Mögliche Werte siehe Tabelle 4.55.
void setFrame ( bool );
Hiermit kann der Rahmen um das Textfeld mit false ausgeschaltet werden. Standardmäßig ist der Rahmen eingeschaltet (true).
void setInputMask ( const QString & inputMask );
Setzt eine Eingabemaske für das Textfeld. Hierzu können mehrere Maskenzeichen (siehe Tabelle 4.56) verwendet werden. Um eine Maske wieder zu deaktivieren, müssen Sie diese Operation wieder mit einem leeren String aufrufen.
Tabelle 4.54
210
Methoden der Klasse QLineEdit (Forts.)
1542.book Seite 211 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setMaxLength ( int );
Setzt die maximale Länge des Eingabefeldes.
void setModified ( bool );
Mit true markieren Sie das Feld als verändert. Mit false bewirken Sie das Gegenteil.
void setReadOnly ( bool );
Mit true setzen Sie das Eingabefeld auf nur lesbar. Mit false heben Sie dies wieder auf.
void setSelection ( int start, int length );
Damit markieren Sie einen Text ab der Position start mit length Zeichen.
void setValidator ( const QValidator * v );
Damit legen Sie fest, was in der Zeile eingegeben werden darf bzw. was von dem Eingabefeld akzeptiert wird. Hierzu können Sie bspw. mit QIntValidator, QDoubleValidator, und QRegExpValidator oder gar mit QValidator eigene Validatoren verwenden (siehe hierzu auch das Programmbeispiel).
QString text () const;
Gibt den im Eingabefeld enthaltenen Text zurück.
const QValidator* validator() const;
Gibt einen Zeiger auf den aktuellen Validator zurück. Existiert keiner, wird NULL zurückgegeben.
Tabelle 4.54
Methoden der Klasse QLineEdit (Forts.)
Jetzt zu den möglichen Werten für den enum-Type EchoMode, womit Sie angeben bzw. ermitteln können, wie die Eingabe des Feldes angezeigt wird. Konstante
Beschreibung
QLineEdit::Normal
Der eingegebene Text wird angezeigt. Der Standardwert.
QLineEdit::NoEcho
Die Eingabe wird gar nicht angezeigt. Dies ist bspw. sinnvoll, damit die Länge das Passwortes nicht sichtbar ist.
QLineEdit::Password
Zeigt Sternchen »*« anstatt Zeichen im Eingabefeld.
QLineEdit::PasswordEchoOnEdit
Zeigt Zeichen an, wenn das Eingabefeld editiert wird, ansonsten werden Sternchen angezeigt.
Tabelle 4.55
Mögliche Werte für EchoMode
Jetzt noch zu den möglichen Zeichen der Eingabemaske und wie diese interpretiert werden.
211
4.5
1542.book Seite 212 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Zeichen
Bedeutung
A
Alphabetische ASCII-Zeichen benötigt (A–Z, a–z).
a
Alphabetische ASCII-Zeichen erlaubt, aber nicht unbedingt erforderlich (A–Z, a–z).
N
Alphabetische ASCII-Zeichen- und Nummern erforderlich (A–Z, a–z, 0–9).
n
Alphabetische ASCII-Zeichen- und Nummern erlaubt (A–Z, a–z, 0–9) aber nicht unbedingt erforderlich.
X
Es werden Zeichen benötigt.
x
Zeichen sind erlaubt, werden aber nicht unbedingt benötigt.
9
ASCII-Nummer (0–9) werden benötigt.
0
ASCII-Nummer (0–9) werden erlaubt, aber nicht unbedingt benötigt.
D
ASCII-Nummer (1–9) werden benötigt.
d
ASCII-Nummer (1–9) sind erlaubt, werden aber nicht unbedingt benötigt.
#
ASCII-Nummer oder Plus/Minus sind erlaubt, werden aber nicht unbedingt benötigt.
H
Hexadezimale Zeichen werden benötigt (A–F, a–f, 0–9).
h
Hexadezimale Zeichen sind erlaubt (A–F, a–f, 0–9), werden aber nicht unbedingt benötigt.
B
Binäre Zeichen werden benötigt (0–1).
b
Binäre Zeichen sind erlaubt (0–1), werden aber nicht unbedingt benötigt.
>
Alle folgenden Buchstaben sind Großbuchstaben.
addWidget(radio01); vBox1->addWidget(radio02); vBox1->addWidget(radio03); vBox1->addWidget(radio04); vBox1->addWidget(but1);
30 31 32 33 34
edit2 = new QLineEdit; edit2->setAlignment(Qt::AlignHCenter); radio05 = new QRadioButton("Integer (Validator)"); // Integer-Validator verwenden edit2->setValidator(new QIntValidator(edit2));
35 36 37
40
radio05->setChecked(true); radio06 = new QRadioButton("Double (Validator)"); radio07 = new QRadioButton( "Regex (Validator) (-9999 bis 9999)" ); radio08 = new QRadioButton( "Datum (Maske) (dd.mm.yyyy)" ); radio09 = new QRadioButton( "IP-Adresse (Maske) (000.000.000.000)" ); radio10 = new QRadioButton("Read-Only");
41 42
QVBoxLayout *vBox2 = new QVBoxLayout; vBox2->addWidget(edit2);
38 39
215
4.5
1542.book Seite 216 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
43 44 45 46 47 48
vBox2->addWidget(radio05); vBox2->addWidget(radio06); vBox2->addWidget(radio07); vBox2->addWidget(radio08); vBox2->addWidget(radio09); vBox2->addWidget(radio10);
49
connect( radio01, SIGNAL( toggled(bool) this, SLOT( changeEcho(bool) ) connect( radio02, SIGNAL( toggled(bool) this, SLOT( changeEcho(bool) ) connect( radio03, SIGNAL( toggled(bool) this, SLOT( changeEcho(bool) ) connect( radio04, SIGNAL( toggled(bool) this, SLOT( changeEcho(bool) ) connect( radio05, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( radio06, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( radio07, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( radio08, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( radio09, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( radio10, SIGNAL( toggled(bool) this, SLOT( setValidMask(bool) connect( but1, SIGNAL( clicked() ), this, SLOT( readText() ) );
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69 70 71 }
216
), ); ), ); ), ); ), ); ), ) ); ), ) ); ), ) ); ), ) ); ), ) ); ), ) );
// Verpacken QGroupBox *groupBox1 = new QGroupBox( "Echo-Demo"); groupBox1->setLayout(vBox1); QGroupBox *groupBox2 = new QGroupBox( "QLineEdit – Demo"); groupBox2->setLayout(vBox2); // ... und das Layout setzen QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); layout->addWidget(groupBox2); setLayout(layout); setWindowTitle(tr("QLineEdit – Demo"));
1542.book Seite 217 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
72 // Textausgaben setzen 73 void MyWidget::changeEcho( bool b ) { 74 if( radio01->isChecked()) 75 edit1->setEchoMode(QLineEdit::Normal); 76 if( radio02->isChecked()) 77 edit1->setEchoMode(QLineEdit::Password); 78 if( radio03->isChecked()) 79 edit1->setEchoMode(QLineEdit::PasswordEchoOnEdit); 78 if( radio04->isChecked()) 79 edit1->setEchoMode(QLineEdit::NoEcho); 80 } 81 // neue Maske bzw. Validator setzen 82 void MyWidget::setValidMask( bool b ) { 83 edit2->setInputMask(""); 84 edit2->setReadOnly(false); 85 if( radio05->isChecked()) 86 edit2->setValidator(new QIntValidator(edit2)); 87 else if( radio06->isChecked()) 88 edit2->setValidator(new QDoubleValidator(edit2)); 89 else if( radio07->isChecked()) { 90 // Integerwert zwischen –9999 und 9999 91 QRegExp rx("-?[1-9]\\d{0,3}"); 92 QValidator *validator = new QRegExpValidator(rx, this); 93 edit2->setValidator(validator); 94 } 95 else if( radio08->isChecked()) 96 edit2->setInputMask("00-00-0000"); 97 else if( radio09->isChecked()) 98 edit2->setInputMask("000.000.000.000;_"); 99 else if( radio10->isChecked()) 100 edit2->setReadOnly(true); 101 } 102 // eingegebenen Text auswerten 103 void MyWidget::readText( ) { 104 QMessageBox::information( this, "Folgender Text wurde eingegeben", edit1->text(), QMessageBox::Ok ); 105 }
217
4.5
1542.book Seite 218 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Jetzt noch das Hauptprogramm: 00 // beispiele/qlineedit/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.71
QLineEdit bei der Ausführung
QComboBox Die Klasse QComboBox (abgeleitet von QWidget) ist eine Kombination aus einem Button und einer Liste. Aus dieser Liste kann der Anwender eine Option auswählen, die dann auch angezeigt wird. Dabei nimmt diese Liste relativ wenig Platz ein, weil unbetätigt immer nur ein Element der Liste angezeigt wird. Klickt der Anwender auf diese Box, wird gewöhnlich eine Liste der möglichen auszuwählenden Elemente angezeigt. Dabei ist auch möglich, dass der Anwender den Inhalt einer solchen Box wie bei einem Textfeld editieren kann. Zunächst wieder die Methoden der Klasse QComboBox.
218
1542.book Seite 219 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
QComboBox ( QWidget * parent = 0 );
Konstruktor. Erzeugt eine neue leere Combo-Box.
~QComboBox ()
Destruktor. Zerstört eine Combo-Box.
void addItem ( const QString & text, const QVariant & userData = QVariant() );
Fügt ein neues Element ans Ende der Combo-Box mit dem String text und speziellen Daten (userData) hinzu. QVariant ist ein Klasse, ähnlich wie eine Union für die meisten gängigen QtDatentypen.
void addItem ( const QIcon & icon, const QString & text, const QVariant & userData = QVariant() );
Dito, nur wird außerdem ein Icon hinzugefügt.
void addItems ( const QStringList & texts );
Fügt mit einem Mal eine ganze Stringlsite der Combo-Box hinzu.
QCompleter* completer () const;
Gibt, sofern vorhanden, ein Objekt zurück, welches einen oder mehrere Strings enthält, was zur Autovervollständigung des Texts verwendet wird. Siehe setCompleter().
int count () const;
Gibt die Anzahl der Elemente der Combo-Box zurück.
int currentIndex () const;
Gibt die Indexnummer des aktuell aktiven Elements zurück.
QString currentText () const;
Gibt den String des aktuell aktiven Elements zurück.
bool duplicatesEnabled () const;
Wird true zurückgegeben, sind auch doppelte Elemente in der Liste möglich. Bei false gibt es keine doppelten Elemente.
int findData ( const QVariant & data, int role = Qt::UserRole, Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive ) const;
Gibt die Indexnummer des Elements zurück, die den Daten data und der Funktion role entspricht. Gibt es kein solches Element, wird –1 zurückgegeben. Die Flags legen fest, wie nach den Elementen in der Combo-Box gesucht wird.
int findText ( const QString & text, Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive ) const;
Gibt die Indexnummer des Elements mit dem String text zurück. Gibt es kein solches Element, wird –1 zurückgegeben. Die Flags legen fest, wie nach den Elementen in der Combo-Box gesucht wird.
Tabelle 4.59
Methoden der Klasse QComboBox
219
4.5
1542.book Seite 220 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool hasFrame () const;
Gibt true zurück, wenn die Combo-Box einen Rahmen hat (Standard). Ansonsten wird false zurückgegeben.
virtual void hidePopup ();
Versteckt die Liste der Elemente, wenn diese gerade sichtbar sind. Ansonsten hat diese Methode keinen Effekt.
QSize iconSize () const;
Damit wird die Größe des angezeigten Icons in der Combo-Box zurückgegeben.
void insertItem ( int index, const QString & text, const QVariant & userData = QVariant() );
Fügt ein neues Elemement hinter der Position index in der Combo-Box mit dem String text und den Daten (userData) hinzu. QVariant ist eine Klasse, ähnlich wie eine Union für die meisten gängigen Qt-Datentypen.
void insertItem ( int index, const QIcon & icon, const QString & text, const QVariant & userData = QVariant() );
Dito, nur wird noch ein Icon hinzugefügt.
void insertItems ( int index, const QStringList & list );
Fügt der Combo-Box in einem Vorgang eine ganze Stringliste (list) hinter der Position index hinzu.
InsertPolicy insertPolicy () const;
Damit erhalten Sie die Art zurück, wie ein neues Element der Liste hinzugefügt wird. Der Standardwert ist AtBottom (also immer hinter dem letzten). Mögliche Werte siehe Tabelle 4.60.
bool isEditable () const;
Gibt true zurück, wenn der String in der Textbox editierbar ist. Standardwert ist false (für »nicht editierbar«).
QVariant itemData ( int index, int role = Qt::UserRole ) const;
Damit erhalten Sie die zusätzlichen Daten (sofern verwendet) des Elements mit dem Index index zurück.
QIcon itemIcon ( int index ) const;
Damit erhalten Sie das Icon des Elements mit dem Index index zurück.
QString itemText ( int index ) const;
Damit erhalten Sie den Text des Elements mit dem Index index zurück.
QLineEdit * lineEdit () const;
Gibt das Texteingabefeld der Klasse QLineEdit (siehe vorigen Abschnitt) zurück. Gibt es kein Texteingabefeld, wird 0 zurückgegeben. Nur editierbare Combo-Boxen haben ein Texteingabefeld.
Tabelle 4.59
220
Methoden der Klasse QComboBox (Forts.)
1542.book Seite 221 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
int maxCount () const;
Gibt die maximal erlaubte Anzahl von Elementen der Liste in der Combo-Box zurück.
int maxVisibleItems () const;
Gibt die maximal erlaubten sichtbaren Elemente der Combo-Box zurück.
int minimumContentsLength () const;
Diese Eigenschaft gibt die minimale Anzahl von Zeichen zurück, die ein Element in der Combo-Box enthalten sollte. Der Standardwert ist 0.
void removeItem ( int index );
Entfernt des Element mit dem Index index aus der Liste.
void setCompleter ( QCompleter * completer );
Damit können Sie bei einer editierbaren ComboBox eine Autovervollständigung setzen. Siehe hierzu das Beispiel der Klasse QLineEdit.
void setDuplicatesEnabled ( bool enable );
Damit können Sie setzen, ob Sie doppelte Einträge in der Liste zulassen wollen (mit true). Mit false verbieten Sie doppelte Einträge.
void setEditable ( bool editable );
Mit true setzen Sie das Eingabefeld als editierbar. Mit false bewirken Sie das Gegenteil.
void setFrame ( bool );
Mit false können Sie den Rahmen der ComboBox deaktivieren. Das Gegenteil bewirken Sie mit true (Standard).
void setIconSize ( const QSize & size );
Setzt die Größe des Icons in der Combo-Box auf size.
void setInsertPolicy ( InsertPolicy policy );
Damit setzen Sie, wie ein neues Element zur Liste hinzugefügt wird. Der Standardwert ist AtBottom (also immer hinter dem letzten). Mögliche Werte siehe Tabelle 4.60.
void setItemData ( int index, const QVariant & value, int role = Qt::UserRole );
Damit übergeben Sie dem Element mit dem Index index die Daten value. QVariant ist eine Klasse, ähnlich wie eine Union für die meisten gängigen Qt-Datentypen.
void setItemIcon ( int index, const QIcon & icon );
Damit setzen Sie bei dem Element mit der Nummer index ein Icon icon.
void setItemText ( int index, const QString & text );
Damit setzen Sie bei dem Element mit der Nummer index den String auf text.
void setLineEdit ( QLineEdit * edit );
Damit setzen Sie ein Textfeld der Klasse QLineEdit für ein Element in der Liste in der Combo-Box.
void setMaxCount ( int max );
Setzt die maximal erlaubte Anzahl von Elementen der Liste in der Combo-Box auf max.
Tabelle 4.59
Methoden der Klasse QComboBox (Forts.)
221
4.5
1542.book Seite 222 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
void setMaxVisibleItems ( int maxItems );
Setzt die maximal erlaubten sichtbaren Elemente der Combo-Box.
void setMinimumContentsLength ( int characters );
Setzt die minimale Anzahl von Zeichen auch character, die ein Element in der Combo-Box enthalten muss. Der Standardwert ist 0.
void setSizeAdjustPolicy ( SizeAdjustPolicy policy );
Damit legen Sie fest, wie und wann die Größe (bzw. Breite) der Combo-Box angepasst wird, wenn der Inhalt verändert wird. Mögliche Werte siehe Tabelle 4.61. Standardmäßig wird diese Größe angepasst beim ersten Anzeigen der Combo-Box.
void setValidator ( const QValidator* validator );
Damit legen Sie fest, was in der Zeile eingegeben werden darf bzw. was von dem Eingabefeld akzeptiert wird. Hierzu können Sie bspw. mit QIntValidator, QDoubleValidator, und QRegExpValidator oder gar mit QValidator eigene Validatoren verwenden (ein Beispiel dazu wurde bereits mit der Klasse QLineEdit demonstriert).
virtual void showPopup ();
Zeigt die Liste mit den Elementen der Combo-Box an. Ist die Liste leer, werden keine Elemente angezeigt.
SizeAdjustPolicy sizeAdjustPolicy () const;
Gibt zurück, wie und wann die Größe (bzw. Breite) der Combo-Box angepasst wird, wenn der Inhalt verändert wird. Mögliche Werte siehe Tabelle 4.61. Standardmäßig wird diese Größe beim ersten Anzeigen der Combo-Box angepasst.
const QValidator* validator () const;
Gibt einen Zeiger auf den aktuellen Validator zurück. Existiert keiner, wird NULL zurückgegeben.
Tabelle 4.59
Methoden der Klasse QComboBox (Forts.)
Wie neue Elemente ggf. zur Liste der Combo-Box hinzugefügt werden, wird mit der enum-Konstante QComboBox::InsertPolicy angegeben (und mit der Methode setInsertPolicy() gesetzt. Konstante
Beschreibung
QComboBox::NoInsert
Der String wird nicht zur Combo-Box hinzugefügt.
QComboBox::InsertAtTop
Der String wird an erster Position hinzugefügt.
QComboBox::InsertAtCurrent
Der aktuelle String wird durch den neuen String ersetzt.
Tabelle 4.60
222
Wie man neue Elemente der Liste hinzufügt
1542.book Seite 223 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Konstante
Beschreibung
QComboBox::InsertAtBottom
Der String wird am Ende der Combo-Box hinzugefügt (Standard).
QComboBox::InsertAfterCurrent
Der String wird hinter dem aktuellen Element hinzugefügt.
QComboBox::InsertBeforeCurrent
Der String wird vor dem aktuellen Element hinzugefügt.
QComboBox::InsertAlphabetically
Der String wird alphabetisch sortiert hinzugefügt.
Tabelle 4.60
Wie man neue Elemente der Liste hinzufügt (Forts.)
Wie die Größe der Combo-Box angepasst wird, wenn sich der Inhalt verändert, wird mit der enum-Konstante QComboBox::SizeAdjustPolicy angegeben. Konstante
Beschreibung
QComboBox::AdjustToContents
Die Combo-Box wird immer an den Inhalt angepasst.
QComboBox::AdjustToContentsOnFirstShow
Die Combo-Box wird beim ersten Anzeigen angepasst.
QComboBox::AdjustToMinimumContentsLength
Es wird empfohlen, zuvor eine der beiden Konstanten zu verwenden.
Tabelle 4.61
Anpassung der Combo-Box
Um eine Signal-Slot-Verbindung mit QComboBox einrichten zu können, sind folgende öffentliche Signale definiert: Signal
Bedeutung
void activated ( int index );
Wenn der Anwender die Combo-Box aktiviert hat, wird dieses Signal gesendet. Der Parameter ist die Nummer des aktivierten Elements.
void activated ( const QString & text );
Dito, nur steht hier im Parameter der String des aktivierten Elements.
void currentIndexChanged ( int index );
Dieses Signal wird gesendet, wenn der Anwender das aktuelle Element der Combo-Box geändert hat. Das Signal wird außerdem auch gesendet, wenn das Element programmtechnisch geändert wird. Im Parameter befindet sich die Nummer des Elements oder –1, wenn die Combo-Box ein leeres Element enthält oder zurückgesetzt (Reset) wurde.
Tabelle 4.62
Signale von QComboBox
223
4.5
1542.book Seite 224 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Signal
Bedeutung
void currentIndexChanged ( const QString & text );
Dito, nur befindet sich im Parameter der String des Elements.
void editTextChanged ( const QString & text );
Das Signal wird ausgelöst, wenn der Text in der ComboBox geändert wurde. Im Parameter befindet sich der neue Text.
void highlighted ( int index ); Dieses Signal wird gesendet, wenn ein Element in der
Combo-Box vom Anwender hervorgehoben (highlighted) wurde. Im Parameter befindet sich die Nummer des Elements. void highlighted ( const QString & text )
Tabelle 4.62
Dito, nur befindet sich im Parameter der String des hervorgehobenen Elements.
Signale von QComboBox (Forts.)
Folgende öffentliche Slots finden Sie außerdem in Klasse QComboBox definiert: Slot
Beschreibung
void clear ();
Entfernt alle Elemente einer Combo-Box.
void clearEditText ();
Entfernt den Inhalt der Zeile, um den Text in der Combo-Box zu editieren.
void setCurrentIndex ( int index );
Setzt das Element mit der Nummer index als das aktive Element in der Combo-Box.
void setEditText ( const QString & text );
Setzt das Element mit dem String »text« als aktives Element in der Combo-Box.
Tabelle 4.63
Öffentliche Slots von QComboBox
Jetzt zu einem einfachen Beispiel mit der Klasse QComboBox. Hier werden zwei Combo-Boxen verwendet. Das eine enthält Elemente mit Icons, Text und Daten vom Typ QVariant. Beim Auswählen eines Elements in der Liste werden natürlich die Daten von QVariant ausgewertet. Bei der zweiten Combo-Box können Sie neue Elemente hinzufügen. Diese Elemente werden alphabetisch sortiert. Zunächst wieder das Gründgerüst: 00 01 02 03 04 05 06 07
224
// beispiel/qcombobox/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include
1542.book Seite 225 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
08 #include 09 #include 10 class MyWidget : public QWidget { 11 Q_OBJECT 12 public: 13 MyWidget(QWidget *parent = 0); 14 QComboBox *combo1; 15 QComboBox *combo2; 16 QPushButton* but1; 17 public slots: 18 void checkCombo( int ); 19 void addToCombo(); 20 }; 21 #endif
Jetzt zum aktiven Code: 00 01 02 03 04 05 06
// beispiele/qcombobox/mywidget.cpp #include "mywidget.h" #include #include #include #include #include
07 // neue Widget-Klasse vom eigentlichen Widget ableiten 08 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 09 combo1 = new QComboBox; 10 combo1->setSizeAdjustPolicy( QComboBox::AdjustToContents ); 11 // ein paar Daten 12 QStringList wordList; 13 wordList addItem( QIcon("images/icon3.png"), "Datum", QVariant(QDate::currentDate () ) ); 19 // die üblichen Boxen 20 QVBoxLayout *vBox1 = new QVBoxLayout;
225
4.5
1542.book Seite 226 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
21
vBox1->addWidget(combo1);
22 23 24 25 26 27
combo2 = new QComboBox; // Elemente editierbar combo2->setEditable( true ); // Elemente alphabetisch hinzufügen combo2->setInsertPolicy( QComboBox::InsertAlphabetically ); but1 = new QPushButton("Hinzufügen");
28 29 30
QVBoxLayout *vBox2 = new QVBoxLayout; vBox2->addWidget(combo2); vBox2->addWidget(but1);
31 32 33 34 35 36 37
38 39 40 41 42 43 44 }
// Verpacken QGroupBox *groupBox1 = new QGroupBox( "Combo-Box 1 mit Daten (QVariant)" ); groupBox1->setLayout(vBox1); QGroupBox *groupBox2 = new QGroupBox( "Combo-Box 2"); groupBox2->setLayout(vBox2); connect( combo1, SIGNAL( activated ( int ) ), this, SLOT( checkCombo( int ) ) ); connect( but1, SIGNAL( clicked() ), this, SLOT( addToCombo() ) ); // ... und das Layout setzen QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(groupBox1); layout->addWidget(groupBox2); setLayout(layout); setWindowTitle(tr("QComboBox – Demo"));
45 // Combo-Box auswerten 46 void MyWidget::checkCombo( int var ) { 47 // Daten holen 48 QVariant data = combo1->itemData(var); 49 // Sind Daten vorhanden? 50 if( data != QVariant::Invalid ) { 51 // Datentyp ermitteln 52 switch( data.type() ) { 53 case QVariant::StringList: { 54 QStringList list = data.toStringList();
226
1542.book Seite 227 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
55 56 57
// String-List splitten QString msg = list.join("\n"); QMessageBox::information( this, "Folgende Daten sind enthalten", msg, QMessageBox::Ok );
58 59 60 61 62 63 64
} break; case QVariant::Time: { QTime time = data.toTime(); // String daraus machen QString msg = time.toString("hh:mm:ss"); QMessageBox::information( this, "Uhrzeit des Programmstarts", msg, QMessageBox::Ok ); } break; case QVariant::Date: { QDate date = data.toDate(); // String daraus machen QString msg = date.toString( "Datum: (ddd) dd.MMM.20yy"); QMessageBox::information( this, "Datum des Programmstarts", msg, QMessageBox::Ok ); } break;
65 66 67 68 69 70 71
72 73 74 75 76 }
} }
77 void MyWidget::addToCombo() { 78 // eingegeben Text abholen 79 QString select = combo2->currentText(); 80 // wurde was eingegeben 81 if( ! select.isEmpty() ) 82 // Ist der Text bereits vorhanden 83 if( combo2->findText( select ) == –1 ) { 84 // Text zur Combobox hinzufügen 85 combo2->addItem(select); 86 } 87 }
227
4.5
1542.book Seite 228 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Jetzt benötigen wir nur noch eine Hauptfunktion: 00 // beispiele/qcombobox/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.72
Die Klasse QComboBox bei der Ausführung
Abbildung 4.73
Element »Datum« in Combo-Box 1 ausgewählt
QFontComboBox Die von QComboBox abgeleitete Klasse QFontComboBox ist eine weitere ComboBox, in der der Anwender eine Schriftart auswählen kann. Die Schriftartenliste ist alphabetisch sortiert (bspw. Arial, Courier, Helvetica, Times New Roman usw.). Gewöhnlich findet man diese Combobox in einer Werkzeugleiste mit Buttons bei einem Texteditor, dazu meist drei weitere Buttons, um die Schrift zu unterstrichen und fett oder kursiv anzuzeigen. QAbstractSpinBox Die von QWidget abgeleitete Klasse QAbstractSpinBox ist eine Mischung aus einem Textfeld und Button mit Pfeilen (oder Plus/Minus), womit Werte ange-
228
1542.book Seite 229 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
zeigt und verändert werden können. Die Werte können hierbei wie bei einem gewöhnlichen Textfeld, oder eben über die Buttons verändert werden. Die Klasse QAbstractSpinBox ist die Superklasse für die davon abgeleiteten Klassen QSpinBox, QDateTimeEdit und QDoubleSpinBox. Die Klasse QDateTimeEdit wieder besitzt mit QDateEdit und QTimeEdit zwei weitere Unterklassen, die wiederum davon abgeleitet wurden (siehe Abbildung 4.74).
QAbstractSpinBox
QSpinBox
QDateTimeEdit
QDateEdit
Abbildung 4.74
QDoubleSpinBox
QTimeEdit
Klassenhierarchie von QAbstractSpinBox
Bevor es allerdings wieder was fürs Auge gibt, müssen wir zunächst die Methoden, Signale und Slots der Superklasse QAbstractSpinBox näher betrachten, da diese alle für die davon vererbte Klasse verwendet werden können. Somit stehen alle gleich erwähnten Methoden auch für QSpinBox, QDateTimeEdit (inkl. QDateEdit und QTimeEdit) und QDoubleSpinBox zur Verfügung. Hierzu nun die öffentlichen Methoden von QAbstractSpinBox: Methode
Beschreibung
QAbstractSpinBox ( QWidget * parent = 0 );
Konstruktor. Erzeugt eine neue Spinbox mit parent als Eltern-Widget.
~QAbstractSpinBox ();
Destruktor. Zerstört eine Spinbox.
Qt::Alignment alignment () const; Gibt die aktuelle Ausrichtung der Spinbox zurück. Mögliche Werte sind hierbei nur Qt::AlignLeft, Qt::AlignRight und Qt::AlignHCenter (siehe
Tabelle 4.8, 4.9 und 4.10). Der Standardwert lautet Qt::AlignLeft. ButtonSymbols buttonSymbols () const;
Tabelle 4.64
Gibt das gesetzte Symbol der Spin-Buttons zurück. Standardmäßig zeigen die Pfeile nach oben bzw. unten. Mögliche zurückgegebene Werte können Sie Tabelle 4.65 entnehmen.
Öffentliche Methoden der Klasse QAbstractSpinBox
229
4.5
1542.book Seite 230 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
CorrectionMode correctionMode () const;
Damit erhalten Sie den Modus zurück, wie der fortlaufende Zwischenwert angepasst wird, wenn das Editieren abgeschlossen ist. Mögliche Werte siehe Tabelle 4.66.
virtual void fixup ( QString & input ) const;
Diese virtuelle Methode wird von QAbstractSpinBox aufgerufen, wenn die Eingabe ungültig ist; QValidator::Acceptable, wenn der Anwender (¢) gedrückt oder die Methode interpretText() aufgerufen hat.
bool hasAcceptableInput () const; Gibt true zurück, wenn die Eingabe mit dem aktu-
ellen Validator übereinstimmt. Ansonsten wird false zurückgegeben. bool hasFrame () const;
Gibt true zurück, wenn die Spinbox einen Rahmen hat (Standard). Ansonsten wird false zurückgegeben.
void interpretText ();
Diese Methode interpretiert den Text der Spinbox. Wurde der Wert seit der letzten Interpretation verändert, wird ein Signal ausgelöst.
bool isAccelerated () const;
Diese Methode gibt true zurück, wenn die Frequenz der einzelnen Schritte beschleunigt (bzw. inkrementiert, dekrementiert), wenn die Buttons der Spinbox länger gedrückt gehalten werden; standardmäßig ist sie nicht aktiviert (false).
bool isReadOnly () const;
Diese Methode gibt true zurück, wenn der Text in der Spinbox nur lesbar ist. Es wird hierbei kein Cursor im Textfeld von QAbstractSpinBox angezeigt. Außerdem funktioniert das Kopieren und Drag & Drop von Text.
void setAccelerated ( bool on );
Mit true beschleunigen (bzw. inkrementieren, dekrementieren) Sie die Frequenz der einzelnen Schritte, wenn die Buttons der Spinbox länger gedrückt werden; standardmäßig nicht aktiviert (false).
void setAlignment ( Qt::Alignment flag );
Setzt die aktuelle Ausrichtung der Spinbox auf flag. Mögliche Werte sind hierbei nur Qt::AlignLeft, Qt::AlignRight und Qt::AlignHCenter (siehe Tabelle 4.8, 4.9 und 4.10). Werden ungültige Werte verwendet bzw. kombiniert, hat dies überhaupt keinen Effekt.
void setButtonSymbols ( ButtonSymbols bs );
Setzt das Symbol der Spin-Buttons auf bs. Mögliche Werte siehe Tabelle 4.65.
Tabelle 4.64
230
Öffentliche Methoden der Klasse QAbstractSpinBox (Forts.)
1542.book Seite 231 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setCorrectionMode ( CorrectionMode cm );
Damit setzen Sie den Modus, wie der fortlaufende Zwischenwert angepasst wird, wenn der Editiervorgang abgeschlossen ist, auf cm. Mögliche Werte siehe Tabelle 4.66.
void setFrame ( bool );
Mit false können Sie den Rahmen um die Spinbox abschalten. Einschalten lässt sich diese wieder mit true.
void setReadOnly ( bool r );
Mit true ist der Text in der Spinbox nur noch lesund nicht mehr editierbar. Außerdem funktioniert trotzdem das Kopieren und Drag & Drop von Text. Mit false stellen Sie den Standard wieder her.
void setSpecialValueText ( const QString & txt );
Damit können Sie einen Text setzen, der angezeigt wird, wenn der aktuelle Wert gleich minimum() ist.
void setWrapping ( bool w );
Standardmäßig ist dieser Wert false. Verwenden Sie hierbei true, fährt der aktuelle Wert, wenn dieser maximum() erreicht hat, mit minimum() fort. Umgekehrt dasselbe; standardmäßig ist ja bei maximum() bzw. minimum() Schluss.
QString specialValueText () const;
Gibt den Text zurück, der angezeigt, wird, wenn der aktuelle Wert gleich minimum() ist.
QString text () const;
Damit erhalten Sie den kompletten Text in der Spinbox (mitsamt Suffix und Präfix).
bool wrapping () const;
Gibt true zurück, wenn das »wrapping« eingeschaltet ist (siehe setWrapping()). Ansonsten wird false zurückgegeben.
Tabelle 4.64
Öffentliche Methoden der Klasse QAbstractSpinBox (Forts.)
Jetzt zu den möglichen enum-Werten für QAbstractSpinBox::ButtonSymbols, womit Sie die Symbole der Buttons auf der Spinbox festlegen bzw. abfragen können. Konstante
Beschreibung
QAbstractSpinBox::UpDownArrows
Der Standardwert, mit dem die üblichen Pfeile auf den Buttons dargestellt werden.
QAbstractSpinBox::PlusMinus
Damit werden die Plus- und Minus-Symbole auf den Buttons dargestellt.
Tabelle 4.65
Konstanten für die Buttons der Spinbox
231
4.5
1542.book Seite 232 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Nun zu den enum-Konstanten für QAbstractSpinBox::CorrectionMode, um festzulegen bzw. zu ermitteln, wie der fortlaufende Zwischenwert angepasst wird, wenn man mit dem Editieren fertig ist. Konstante
Beschreibung
QAbstractSpinBox:: CorrectToPreviousValue
Die Spinbox kehrt zum letzten gültigen Wert zurück.
QAbstractSpinBox:: CorrectToNearestValue
Die Spinbox kehrt zum nächsten gültigen Wert zurück.
Tabelle 4.66
Mögliche Werte für QAbstractSpinBox::CorrectionMode
Folgende öffentliche Slots bietet die Klasse QAbstractSpinBox für ihre Unterklassen an: Slot
Beschreibung
void selectAll ();
Markiert den kompletten Text in der Spinbox; ausgenommen das Präfix und Suffix.
void stepDown ();
Dekrementiert den Wert der Spinbox um –1. Dieser Aufruf ist analog zu stepBy(-1).
void stepUp ();
Inkrementiert den Wert der Spinbox um 1. Dieser Aufruf entspricht stepBy(1).
Tabelle 4.67
Öffentliche Slots von QAbstractSpinBox
An Signalen bietet die Basisklasse QAbstractSpinBox mit editingFinished() nur eines an. Es wird ausgelöst, wenn die Spinbox den Fokus verliert oder die (¢)-Taste gedrückt wurde. QSpinBox Die Klasse QSpinBox ist die klassische Integerversion der Spinbox. Sollten Sie einen Gleitpunktwert benötigen, können Sie stattdessen die Klasse QDoubleSpinBox verwenden. Die Klasse QSpinBox bietet neben den von QAbstractSpinBox vererbten Methoden noch folgende Methoden an: Methode
Beschreibung
QSpinBox ( QWidget * parent = 0 );
Erzeugt eine neue Spinbox mit parent als ElternWidget.
Tabelle 4.68
232
Methoden der Klasse QSpinBox
1542.book Seite 233 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
QString cleanText () const;
Damit erhalten Sie den reinen Wert als String der Spinbox. Ohne Präfix, Suffix oder irgendwelche Whitespace-Zeichen.
int maximum () const;
Gibt den maximal möglichen Wert der Spinbox zurück.
int minimum () const;
Gibt den minimal möglichen Wert der Spinbox zurück.
QString prefix () const;
Gibt das Präfix der Spinbox zurück (falls gesetzt).
void setMaximum ( int max );
Setzt den maximal möglichen Wert der Spinbox auf max.
void setMinimum ( int min );
Setzt den minimal möglichen Wert der Spinbox auf min.
void setPrefix ( const QString & prefix );
Setzt das Präfix prefix vor dem aktuellen Wert der Spinbox. Dieses Präfix wird nicht angezeigt, wenn value() == minimum() und specialValueText() gesetzt sind.
void setRange ( int minimum, int maximum );
Damit setzen Sie den minimal und maximal möglichen Wert der Spinbox.
void setSingleStep ( int val ); Mit val setzen Sie den Wert, der inkrementiert bzw.
dekrementiert wird, wenn die Spinbuttons betätigt werden. Standardmäßig ist dieser Wert 1. void setSuffix ( const QString & suffix );
Damit setzen Sie ein Suffix suffix, welches hinter dem aktuellen Wert der Spinbox steht. Das Suffix wird nicht angezeigt, wenn der Wert minimal() ist und specialValueText() gesetzt ist.
int singleStep () const;
Gibt den Wert zurück, der inkrementiert bzw. dekrementiert wird, wenn die Spinbuttons betätigt werden.
QString suffix () const;
Gibt das Suffix zurück (sofern gesetzt).
int value () const;
Gibt den aktuellen Integerwert der Spinbox zurück.
Tabelle 4.68
Methoden der Klasse QSpinBox (Forts.)
Folgende öffentliche Signale bietet QSpinBox: Signal
Beschreibung
void valueChanged ( int i );
Das Signal wird ausgelöst, wenn der Wert der Spinbox verändert wurde. Der neue Wert befindet sich im Integer i.
void valueChanged ( const QString & text );
Dito, nur befindet sich der Wert im String text.
Tabelle 4.69
Signal von QSpinBox
233
4.5
1542.book Seite 234 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Die Klasse QSpinBox bietet mit setValue(int) nur einen eigenen Slot an, mit dem der aktuelle Wert der Spinbox gesetzt wird. Dieser aufgerufene Slot löst außerdem das Signal valueChanged() aus, wenn der neue Wert sich vom alten unterscheidet. Ein Beispiel zur Klasse QSpinBox finden Sie am Ende des Kapitels zu den Spinboxen (S. 239 ff.). QDoubleSpinBox Die Klasse QDoubleSpinBox entspricht von der Funktion her exakt der Klasse QSpinBox. Selbst die Methoden, Signale und Slots sind die gleichen, wie soeben
am Beispiel der Klasse QSpinBox beschrieben wurde. Einzig, wo eben ein Integer als Wert gesetzt bzw. zurückgegeben wurde, müssen Sie jetzt den Typ double verwenden. Daher erspare ich mir einige Seiten Text und verweise Sie auf den Abschnitt über die Klasse QSpinBox (nur eben mit double als Datentyp). Zwei weitere Methoden, die für Gleitpunktzahlen nötig sind (Angabe der Genauigkeit nach dem Komma), gibt es dann doch (Tabelle 4.70): Methode
Beschreibung
int decimals () const;
Damit erhalten Sie die Genauigkeit der DoubleSpinbox. Standardmäßig sind zwei dezimale Zahlen hinter dem Komma gesetzt.
void setDecimals ( int prec );
Damit setzen Sie die Genauigkeit der DoubleSpinbox auf prec. Der gültige Bereich ist hier von 0 bis 13.
Tabelle 4.70
In QSpinBox nicht vorhandene Methoden
QDateTimeEdit (mit QTimeEdit und QDateEdit) Die Klasse QDateTimeEdit ist die Spinbox-Version, mit der Sie Datum und Uhrzeit auswählen können. Hiermit ist auch alles möglich, was man von Spinboxen her kennt. Datum und Uhrzeit lassen sich kopieren, ziehen (Drag) und natürlich ggf. auch editieren. Selbstverständlich können Sie herbei auch das gewünschte Format von Datum und/oder Uhrzeit vorgeben. Die Spinbox von Datum und Uhrzeit gibt es in zwei Versionen: in der klassischen (siehe Abbildung 4.75) und in der Popup-Version (siehe Abbildung 4.76).
Abbildung 4.75
234
Klassische Auswahl von Datum und Uhrzeit
1542.book Seite 235 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Abbildung 4.76
Auswahl per Popup-Fenster von Datum und Uhrzeit
Jetzt zu den Methoden der Klasse QDateTimeEdit, wovon entsprechende auch in der davon abgeleiteten Klasse QTimeEdit und QDateEdit verwendet werden können. Methoden
Beschreibung
QDateTimeEdit ( QWidget * parent = 0 );
Erzeugt eine leere Datums-Uhrzeit-Spinbox mit parent als Eltern-Widget.
QDateTimeEdit ( const QDateTime & datetime, QWidget * parent = 0 );
Erzeugt eine Datums-Uhrzeit-Spinbox mit parent als Eltern-Widget. Der Wert wird auf datetime gesetzt.
QDateTimeEdit ( const QDate & date, QWidget * parent = 0 );
Erzeugt eine Datums-Uhrzeit-Spinbox mit parent als Eltern-Widget. Der Wert wird auf date gesetzt.
QDateTimeEdit ( const QTime & time, QWidget * parent = 0 );
Erzeugt eine Datums-Uhrzeit-Spinbox mit parent als Eltern-Widget. Der Wert wird auf time gesetzt.
bool calendarPopup () const;
Gibt diese Methode true zurück; anstelle der standardmäßig klassischen Datums-Uhrzeit-Spinbox (Abbildung 4.75) wird ein Popup-Fenster (siehe Abbildung 4.76) angezeigt, wo das Datum ausgewählt werden kann.
void clearMaximumDate ();
Damit löschen Sie (falls gesetzt) das maximal mögliche Datum. Sollte das Datum kein gültiges QDate-Objekt sein, hat diese Methode keinen Effekt.
void clearMaximumTime ();
Damit löschen Sie (falls gesetzt) die maximal mögliche Uhrzeit. Sollte diese Zeit kein gültiges QTime-Objekt sein, hat diese Methode keinen Effekt.
void clearMinimumDate ();
Damit löschen Sie (falls gesetzt) das minimal mögliche Datum. Sollte das Datum kein gültiges QDate-Objekt sein, hat diese Methode keinen Effekt.
void clearMinimumTime ();
Damit löschen Sie (falls gesetzt) die minimal mögliche Uhrzeit. Sollte diese Zeit kein gültiges QTime-Objekt sein, hat diese Methode keinen Effekt.
Tabelle 4.71
Öffentliche Methoden der Klasse QDateTimeEdit
235
4.5
1542.book Seite 236 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methoden
Beschreibung
Section currentSection () const;
Damit erhalten Sie den aktuell aktiven Teil des Datums bzw. der Uhrzeit der Spinbox. Mögliche aktive Werte finden Sie in Tabelle 4.72.
QDate date () const;
Gibt das Datum (QDate-Objekt) zurück, welches in QDateTimeEdit gesetzt ist.
QDateTime dateTime () const;
Gibt das Datum und die Uhrzeit (QDateTime-Objekt) zurück, welches in QDateTimeEdit gesetzt ist.
QString displayFormat () const; Damit erhalten Sie das aktuelle Format, wie Datum/
Uhrzeit in der Spinbox angezeigt werden. Sections displayedSections () const;
Damit erhalten Sie das oder die aktuell angezeigte(n) Feld(er) im Datum bzw. der Uhrzeit zurück. Mögliche Werte siehe Tabelle 4.72.
QDate maximumDate () const;
Damit erhalten Sie (falls gesetzt) das maximal mögliche Datum.
QTime maximumTime () const;
Damit erhalten Sie (falls gesetzt) die maximal mögliche Uhrzeit.
QDate minimumDate () const;
Damit erhalten Sie (falls gesetzt) das minimal mögliche Datum.
QTime minimumTime () const;
Damit erhalten Sie (falls gesetzt) die minimal mögliche Uhrzeit.
QString sectionText ( Section section ) const;
Damit erhalten Sie vom Feld section den aktuellen Wert als String zurück. Mögliche Sektionen siehe Tabelle 4.72.
void setCalendarPopup ( bool enable );
Damit schalten Sie mit true das Popup-Fenster für die Auswahl des Datums ein. Für die klassische Ansicht wird false (Standardwert) verwendet.
void setCurrentSection ( Section section );
Damit setzen Sie das Feld section als aktives Feld in der Spinbox. Dieses Feld wird dann inkrementiert bzw. dekrementiert, wenn die Spinbuttons bzw. die Pfeil-nach-oben- bzw. Pfeil-nach-unten-Tasten betätigt wurden. Mögliche Werte für section siehe Tabelle 4.72.
void setDateRange ( const QDate & min, const QDate & max );
Damit setzen Sie den minimalen und maximalen Bereich des Datums.
void setDisplayFormat ( const QString & format );
Damit setzen Sie ein Anzeigformat, wie das Datum und/oder die Uhrzeit in der Spinbox angezeigt werden sollen.
Tabelle 4.71
236
Öffentliche Methoden der Klasse QDateTimeEdit (Forts.)
1542.book Seite 237 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methoden
Beschreibung
void setMaximumDate ( const QDate & max );
Damit setzen Sie das maximal mögliche Datum. Ist QDate kein gültiges Objekt, hat diese Methode keinen Effekt.
void setMaximumTime ( const QTime & max )
Damit setzen Sie die maximal mögliche Uhrzeit. Ist QTime kein gültiges Objekt, hat diese Methode keinen Effekt.
void setMinimumDate ( const QDate & min );
Damit setzen Sie das minimal mögliche Datum. Ist QDate kein gültiges Objekt, hat diese Methode keinen Effekt.
void setMinimumTime ( const QTime & min );
Damit setzen Sie die minimal mögliche Uhrzeit. Ist QTime kein gültiges Objekt, hat diese Methode keinen Effekt.
void setSelectedSection ( Section section );
Wie setCurrentSection(), nur wird das Feld zusätzlich markiert.
QTime time () const;
Gibt die in QDateTimeEdit gesetzte Uhrzeit (QTimeObjekt) zurück.
void setTimeRange ( const QTime & min, const QTime & max );
Damit setzen Sie den minimalen und maximalen Bereich der Uhrzeit.
Tabelle 4.71
Öffentliche Methoden der Klasse QDateTimeEdit (Forts.)
Hinweis Mehr zu den Klassen QDate, QTime und QDateTime finden Sie in Abschnitt 6.12.
Jetzt zu den möglichen Werten, womit Sie die einzelnen Felder des Datums und der Uhrzeit abfragen bzw. aktivieren können. Alle Konstanten sind in der enumVariablen QDateTimeEdit::Section definiert: Konstante
Beschreibung
QDateTimeEdit::NoSection
kein Feld
QDateTimeEdit::AmPmSection
AM/PM-Feld
QDateTimeEdit::MSecSection
Millisekunden-Feld
QDateTimeEdit::SecondSection
Sekunden-Feld
QDateTimeEdit::MinuteSection
Minuten-Feld
QDateTimeEdit::HourSection
Stunden-Feld
QDateTimeEdit::DaySection
Tages-Feld
Tabelle 4.72
Konstanten der enum-Variablen QDateTimeEdit::Section
237
4.5
1542.book Seite 238 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Konstante
Beschreibung
QDateTimeEdit::MonthSection
Monats-Feld
QDateTimeEdit::YearSection
Jahres-Feld
Tabelle 4.72
Konstanten der enum-Variablen QDateTimeEdit::Section (Forts.)
Folgende Signale können von der Klasse QDateTimeEdit ausgelöst werden: Signal
Beschreibung
void dateChanged ( const QDate & date );
Dieses Signal wird ausgelöst, wenn das Datum verändert wurde. Das neue Datum befindet sich dann in date.
void dateTimeChanged ( const QDateTime & datetime );
Das Signal wird ausgelöst, wenn das Datum oder die Uhrzeit verändert wurde. Das aktuelle Datum/Uhrzeit befindet sich in datetime.
void timeChanged ( const QTime & time );
Das Signal wird ausgelöst, wenn die Uhrzeit verändert wurde. Die neue Zeit befindet sich dann in time.
Tabelle 4.73
Signale der Klasse QDateTimeEdit
Neben drei Signalen verfügt die Klasse QDateTimeEdit über drei öffentliche Slots: Slot
Beschreibung
void setDate ( const QDate & date );
Damit setzen Sie das aktuelle Datum auf date.
void setDateTime ( const QDateTime & dateTime );
Damit setzen Sie das aktuelle Datum und Uhrzeit auf dateTime.
void setTime ( const QTime & time );
Damit setzen Sie aktuelle Uhrzeit auf time.
Tabelle 4.74
Slots der Klasse QDateTimeEdit
Die von QDateTimeEdit abgeleiteten Klassen QTimeEdit und QDateEdit haben keine eigenen Methoden, Signale und Slots – diese sind im Grunde gar nicht erforderlich, weil ja die Basisklasse QDateTimeEdit alles zur Verfügung stellt. Im Grunde werden diese beiden Klassen eigentlich nicht benötigt, da alles mit QDateTimeEdit machbar ist (was diese beiden Klassen intern ja beweisen). Die Klasse QDateEdit wird nur für das Datum verwendet und kann somit auch nur mit den entsprechenden Methoden der Basisklasse QDateTimeEdit etwas anfangen. Die Klasse QTimeEdit wiederum ist nur für das Editieren der Zeit verantwortlich und kann somit auch nur mit den entsprechenden Methoden, Slots und Signalen der Basisklasse arbeiten.
238
1542.book Seite 239 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Programmbeispiel Nun zu einem Programmbeispiel, das die hier vorgestellten Spinbox-Klassen in der Praxis zeigen soll. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09
// beispiele/spinbox/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include #include #include #include #include
10 class MyWidget : public QWidget { 11 Q_OBJECT 12 public: 13 MyWidget(QWidget *parent = 0); 14 QSpinBox *spin1; 15 QSpinBox *spin2; 16 QDoubleSpinBox *dspin; 17 QDateTimeEdit *dtspin; 18 public slots: 19 void checkSpin( const QString ); 20 void changeDecimal( int decimals ); 21 void setFormatString(const QString &formatString); 22 }; 23 #endif
Es folgt der Hauptteil des Programms: 00 01 02 03 04
// beispiele/spinbox/mywidget.cpp #include "mywidget.h" #include #include #include
05 // neue Widget-Klasse vom eigentlichen Widget ableiten 06 MyWidget::MyWidget( QWidget *parent): QWidget(parent) { 07 // QSpinBox 08 QLabel *spinLabel = new QLabel( "Wert von –100 % bis 100 % auswählen\n" "Bestätigen mit Enter" ); 09 spin1 = new QSpinBox;
239
4.5
1542.book Seite 240 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
10 11 12 13 14 15 16 17
spin1->setRange(-100, 100); spin1->setSingleStep(10); spin1->setValue(0); spin1->setSuffix("%"); spin1->setSpecialValueText("Minimum"); spin1->setButtonSymbols(QAbstractSpinBox::PlusMinus); spin1->setWrapping(true); spin1->setAccelerated ( true );
18
QLabel *spinLabel2 = new QLabel( "Genauigkeit nach dem Komma des" " double-Wertes auswählen" ); spin2 = new QSpinBox; spin2->setRange(0, 13); spin2->setValue(2); // QDoubleSpinBox QLabel *doubleLabel1 = new QLabel( "Einen Wert von 0.0 bis 10.0 auswählen" ); dspin = new QDoubleSpinBox; dspin->setRange(0.0, 10.0); dspin->setSingleStep(0.25); dspin->setValue(5.0); dspin->setSpecialValueText("Kein Wert"); dspin->setSuffix("°"); dspin->setAccelerated ( true );
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// QDateTimeEdit QLabel *dtlabel = new QLabel( "Bitte ein Datum und eine Zeit auswählen:" ); dtspin = new QDateTimeEdit( QDateTime::currentDateTime( ) ); dtspin->setCalendarPopup ( true );
36 37 38 39 40
QLabel *formatLabel = new QLabel( "Bitte den Formatstring auswählen:" ); QComboBox *formatComboBox = new QComboBox; formatComboBox->addItem("yyyy-MM-dd / hh:mm:ss"); formatComboBox->addItem("hh:mm:ss MM/dd/yyyy"); formatComboBox->addItem("hh:mm:ss dd/MM/yyyy"); formatComboBox->addItem("dd.MMM.yyyy – hh:mm:ss");
41 42 43 44
// die üblichen Boxen QVBoxLayout *vBox1 = new QVBoxLayout; vBox1->addWidget(spinLabel); vBox1->addWidget(spin1);
240
1542.book Seite 241 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
45 46 47 48 49
QVBoxLayout *vBox2 = new QVBoxLayout; vBox2->addWidget(spinLabel2); vBox2->addWidget(spin2); vBox2->addWidget(doubleLabel1); vBox2->addWidget(dspin);
50 51 52 53 54
QVBoxLayout *vBox3 = new QVBoxLayout; vBox3->addWidget(dtlabel); vBox3->addWidget(dtspin); vBox3->addWidget(formatLabel); vBox3->addWidget(formatComboBox);
55 56
// Verpacken QGroupBox *groupBox1 = new QGroupBox( "QSpinBox-Demo" ); groupBox1->setLayout(vBox1); QGroupBox *groupBox2 = new QGroupBox( "QDoubleSpinBox-Demo" ); groupBox2->setLayout(vBox2); QGroupBox *groupBox3 = new QGroupBox( "QDateTimeEdit-Demo" ); groupBox3->setLayout(vBox3);
57 58 59 60 61 62 63 64
connect( spin1, SIGNAL(valueChanged(const QString) ), this, SLOT( checkSpin( const QString ) ) ); connect( spin2, SIGNAL(valueChanged(int)), this, SLOT(changeDecimal(int))); connect( formatComboBox, SIGNAL(activated(const QString&)), this,SLOT(setFormatString(const QString&)));
65 // ... und das Layout setzen 66 QVBoxLayout* layout = new QVBoxLayout; 67 layout->addWidget(groupBox1); 68 layout->addWidget(groupBox2); 69 layout->addWidget(groupBox3); 70 setLayout(layout); 71 setWindowTitle(tr("QAbstractSpinBox – Demo")); 72 } 73 void MyWidget::checkSpin( const QString strVal ) { 72 QString svalue = QString( "Wert in QSpinBox: %1").arg(strVal); 74 QMessageBox::information( this, "Auswertung",
241
4.5
1542.book Seite 242 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
svalue, QMessageBox::Ok ); 75 } 76 void MyWidget::changeDecimal(int decimals) { 77 dspin->setDecimals(decimals); 78 } 79 void MyWidget::setFormatString( const QString &formatString ){ 80 dtspin->setDisplayFormat(formatString); 81 }
Nun nur noch eine Hauptfunktion: 00 // beispiele/spinbox/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.77
242
QSpinBox, QDoubleSpinBox und QDateTimeEdit
1542.book Seite 243 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
QTextEdit Die Klasse QTextEdit ist ein ziemlich fortgeschrittener Text-Betrachter bzw. -Editor, der das Rich-Text-Format (kurz RFT) mit HTML-Formatierung unterstützt. Auch umfangreiche Texte werden unterstützt (was bei den Text-Widgets anderer GUIs nicht immer selbstverständlich ist). Die HTML-Unterstützung macht die Formatierung des Texts einfach. Hierzu können einfache HTML-Tags verwendet werden. Setzen Sie bspw. einen Text zwischen Text, wird dieser fett dargestellt. Es werden eine Menge Tags wie Bilder, Listen, Tabellen usw. unterstützt. Natürlich können Sie sowohl HTML-Dateien als auch normalen Text laden. Beachten Sie allerdings, dass nur Tags von HTML 3.2 und 4 unterstützt werden. Man sollte die Klasse QTextEdit allerdings nicht als Webbrowser missverstehen. Das Widget will auch kein Ersatz für die klassischen Office-Editoren sein, sondern einfach ein kleiner, schneller und portabler Editor, um Hilfen und kleine Dokumente anzusehen bzw. zu editieren. Des Weiteren wurden in diesen Texteditor auch die üblichen Tasten-Kombinationen zum Editieren des Texts eingebaut. Bspw. kopieren Sie mit (Strg)+(C) einen markierten Text in die Zwischenablage, und mit (Strg)+(V) fügen Sie einen Text aus der Zwischenablage wieder in den Editor ein. Mehr zu QTextEdit Die Klasse QTextEdit werden wir hier wohl etwas unzureichend beschreiben müssen. Der Umfang mitsamt der damit zusammenhängenden Klassen ist einfach zu groß. Hier sollte man sich wieder mithilfe des Qt Assistant herantasten.
Jetzt zu den Methoden der Klasse QTextEdit, wovon eine beachtliche Menge zur Verfügung stehen. Methode
Beschreibung
QTextEdit ( QWidget * parent = 0 );
Erzeugt ein QTextEdit-Objekt mit parent als Eltern-Widget.
QTextEdit ( const QString & text, QWidget * parent = 0 );
Erzeugt ein QTextEdit-Objekt mit parent als Eltern-Widget mit text als Inhalt.
virtual ~QTextEdit ();
Destruktor. Zerstört ein QTextEdit-Objekt.
bool acceptRichText () const;
Wird true (Standardwert) zurückgegeben, kann der Anwender einen Text im RTF-Format (Rich Text Format) einfügen. Bei false ist nur normaler (Plain-)Text erlaubt.
Tabelle 4.75
Methoden der Klasse QTextEdit
243
4.5
1542.book Seite 244 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
Qt::Alignment alignment () const ;
Gibt die Text-Ausrichtung des aktuellen Absatzes zurück. Mögliche Werte siehe Tabelle 4.8, 4.9 und 4.10.
QString anchorAt ( const QPoint & pos ) const;
Gibt die Referenz des Ankers an der Position pos zurück oder einen leeren String, wenn kein Anker existiert.
AutoFormatting autoFormatting () const;
Damit erhalten Sie den eingeschalteten Wert, der für das automatische Formatieren gesetzt ist. Standardmäßig ist hierbei kein automatischer Wert (AutoNone) gesetzt. Aktuell wird mit AutoBulletList nur ein Wert unterstützt. Ist dieser Wert gesetzt, wird bei einem Sternchen (*) am Anfang der Zeile und (¢) beim Abschließen der Zeile ein Aufzählungspunkt gesetzt.
bool canPaste () const;
Gibt true zurück, wenn ein Text aus der Zwischenablage in das Textfeld kopiert werden kann. Ansonsten wird false zurückgegeben.
QMenu * createStandardContextMenu ();
Diese Methode erzeugt ein Standard-Kontextmenü, das angezeigt wird, wenn der Anwender mit der rechten Maustaste im Textfeld klickt. Aufgerufen wird es über den Standardhandler contextMenuEvent().
QTextCharFormat currentCharFormat () const;
Gibt Formatierungsinformationen der Klasse QTextCharFormat zurück, die als neuer Text eingefügt werden. Siehe setCurrentCharFormat().
QFont currentFont () const ;
Gibt die aktuell gesetzte Schriftart zurück.
QTextCursor cursorForPosition ( const QPoint & pos ) const;
Gibt einen QTextCursor an der Position pos zurück.
QRect cursorRect ( const QTextCursor & cursor ) const;
Gibt einen rechteckigen Bereich zurück, der den Cursor enthält.
QRect cursorRect () const;
Dito, nur die überladene Version.
int cursorWidth () const;
Gibt die Breite des Cursors in Pixel zurück. Der Standardwert ist 1.
QTextDocument * document () const ;
Gibt einen Zeiger auf das grundlegende Dokument zurück.
QString documentTitle () const;
Damit erhalten Sie den Titel des Dokuments zurück.
void ensureCursorVisible ();
Garantiert, dass der Cursor auch beim Scrollen des Texts beim Editieren sichtbar ist.
Tabelle 4.75
244
Methoden der Klasse QTextEdit (Forts.)
1542.book Seite 245 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
QList<ExtraSelection> extraSelections () const;
Gibt die voraussichtlich gesetzten Extra-Markierungen zurück (siehe setExtraSelections()).
bool find ( const QString & exp, QTextDocument::FindFlags options = 0 );
Findet den nächsten String exp mit entsprechender Option option. Wurde der String gefunden wird, true zurückgegeben, der Cursor an entsprechender Position platziert und der gefundene Text markiert. Ansonsten wird false zurückgegeben. Mögliche Flags für die Optionen finden Sie in Tabelle 4.76.
QString fontFamily () const;
Gibt die Schriftart des aktuellen Formates zurück.
bool fontItalic () const;
Gibt true zurück, wenn das aktuelle Format der Schrift eine Kursivschrift ist. Ansonsten wird false zurückgegeben.
qreal fontPointSize () const;
Gibt die Punktgröße des aktuellen Formats der Schrift zurück.
bool fontUnderline () const;
Gibt true zurück, wenn das aktuelle Format der Schrift eine unterstrichene ist. Ansonsten wird false zurückgegeben.
int fontWeight () const;
Gibt die Schriftgröße des aktuellen Formats zurück.
bool isReadOnly () const;
Gibt true zurück, wenn der Text im Feld nur lesbar ist. Ansonsten wird false zurückgegeben.
bool isUndoRedoEnabled () const;
Gibt true zurück, wenn dem Anwender Wiederherstellen und Rückgängig zur Verfügung stehen. Ansonsten wird false zurückgegeben.
int lineWrapColumnOrWidth () const;
Damit erhalten Sie die Position zurück wo der Text am Zeilenende umbrochen wird. Ob es sich hierbei um Pixel oder um Spalten handelt, hängt vom gesetzten Modus (LineWrapMode) ab.
LineWrapMode lineWrapMode () const;
Gibt den Modus zurück, wie die Zeile am Ende umbrochen wird. Mögliche Werte siehe Tabelle 4.77. Der Standardwert ist WidgetWidth, womit also immer am Ende des Widgets, hier der rechten Seiten vom Textfeld, umbrochen wird.
void moveCursor ( QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor );
Bewegt den Cursor mit der übergebenen Operation. Mögliche Werte für operation siehe Tabelle 4.78. Wenn der Modus QTextCursor:: KeepAnchor sein sollte, hat diese etwa denselben Effekt, wenn der Anwender an der aktuellen Position die (ª)-Taste gedrückt hält und mit den Pfeiltasten den Text markiert.
Tabelle 4.75
Methoden der Klasse QTextEdit (Forts.)
245
4.5
1542.book Seite 246 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool overwriteMode () const;
Gibt true zurück, wenn der Überschreibmodus eingeschaltet ist. Der Text hinter dem Cursor wird dann nicht weitergeschoben, sondern überschrieben. Ansonsten wird false zurückgegeben.
void setAcceptRichText ( bool accept );
Mit false schalten Sie ab, dass der Anwender einen Text im RTF-Format einfügen kann. Dann kann nur noch normaler (Plain-)Text eingefügt werden. Mit true (Standard) wird dies wieder eingeschaltet.
void setAutoFormatting ( AutoFormatting features );
Damit können Sie automatische Formatierungen setzen. Im Augenblick existiert hierbei allerdings nur AutoBulletList, womit ein Sternchen am Anfang einer Zeile durch einen Aufzählungspunkt ersetzt wird.
void setCurrentCharFormat ( const QTextCharFormat & format );
Setzt eine Formatierung für die Zeichen des Textfeldes, wenn der Anwender einen Text einfügt und die Methode setCharFormat() aufruft. Wird ein Text im Editor markiert, wird diese Auswahl gleich in die aktuelle Zeichenformatierung gesetzt.
void setCursorWidth (int width);
Damit setzen Sie die Breite des Cursors auf width Pixel.
void setDocument ( QTextDocument * document );
Macht document zum neuen Dokument des Texteditors.
void setDocumentTitle ( const QString & title );
Setzt den Titel des Dokuments auf title.
void setExtraSelections ( const QList<ExtraSelection> & selections );
Damit können Sie verschiedene Stellen im Text mit einer Farbe markieren;wird gerne in Editoren verwendet, die einzelne Zeilen mit einer bestimmten Hintergrundfarbe markieren.
void setLineWrapColumnOrWidth ( int w );
Abhängig vom gesetzten Modus (LineWrapMode) setzen Sie hier mit w, ab welcher Spalte oder ab welchem Pixel die Textzeile umbrochen wird.
void setLineWrapMode ( LineWrapMode mode );
Damit setzen Sie den Modus, wie die Zeile am Ende umbrochen wird. Mögliche Werte für mode siehe Tabelle 4.77.
void setOverwriteMode ( bool overwrite );
Mit true schalten Sie hier den Überschreibmodus ein. Damit wird jedes Zeichen hinter dem Cursor durch ein neues Zeichen überschrieben. Mit false schalten Sie diesen Modus wieder ab.
Tabelle 4.75
246
Methoden der Klasse QTextEdit (Forts.)
1542.book Seite 247 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setReadOnly ( bool ro );
Damit setzen Sie den Text im Editor auf nur lesbar. Es sind keinerlei Änderungen am Text mehr möglich. Kopieren und Ziehen des Texts funktionieren allerdings nach wie vor.
void setTabChangesFocus ( bool b );
Wird im Editor die Tabulator-Taste gedrückt, erfolgt eine Einrückung. Wollen Sie, dass stattdessen der Fokus vom Textfeld zu einem anderen Widget übergeht – was ja bei den meisten Widgets der Fall ist –, müssen Sie b auf true setzen. Der Standardwert ist false.
void setTabStopWidth ( int width );
Damit setzen Sie die Weite (Einrückung) des Tabulator-Zeichens in Pixel.
void setTextCursor ( const QTextCursor & cursor );
Damit setzen Sie den Textcursor auf cursor.
void setUndoRedoEnabled ( bool enable );
Damit können Sie mit false dem Anwender das Wiederherstellen und Rückgängigmachen der letzten Aktion verbieten. Mit true erreichen Sie das Gegenteil.
void setWordWrapMode ( QTextOption::WrapMode policy );
Damit setzen Sie den Modus, wie ein Wort am Zeilenende umbrochen wird. Mögliche Werte finden Sie in der Tabelle 4.79.
bool tabChangesFocus () const;
Wird false zurückgegeben, erfolgt bei Betätigung der (ÿ)-Taste eine Einrückung. Ist der Rückgabewert true, wird der Fokus vom Textwidget zum nächsten Widget übergeben.
int tabStopWidth () const;
Damit erhalten Sie die Weite (Einrückung) des Tabulatoren-Zeichens in Pixel.
QColor textColor () const;
Gibt die Textfarbe der aktuellen Formatierung zurück.
QTextCursor textCursor () const;
Gibt eine Kopie von QTextCursor zurück, die dem aktuell sichtbaren Cursor entspricht.
QString toHtml () const;
Gibt den Text in Editor als HTML-Text zurück.
QString toPlainText () const;
Gibt den Text im Editor als normalen (Plain-)Text zurück.
QTextOption::WrapMode wordWrapMode () const;
Damit erhalten Sie den Modus, wie ein Wort am Zeilenende umbrochen wird. Mögliche Werte finden Sie in Tabelle 4.79.
Tabelle 4.75
Methoden der Klasse QTextEdit (Forts.)
247
4.5
1542.book Seite 248 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Als Nächstes zur enum-Konstante QTextDocument::FindFlag, mit der Sie die Option für die Suche mit der Methode find() setzen können. Mehrere Optionen können mit dem ODER-Operator verknüpft werden. Konstante
Beschreibung
QTextDocument::FindBackward
Sucht rückwärts anstatt vorwärts.
QTextDocument::FindCaseSensitively
Per Standard ist die Suche case intensive (Großund Kleinschreibung werden nicht beachtet). Mit dieser Option kann die Suche case sensitive erfolgen (Groß- und Kleinschreibung werden beachtet).
QTextDocument::FindWholeWords
Nur noch ganze Wörter werden gesucht.
Tabelle 4.76
Konstanten für Optionen bei der Suche
Jetzt zu den enum-Konstanten von QTextEdit::LineWrapMode, womit Sie den Modus setzen bzw. abfragen können, wie der Text am Zeilenende umbrochen wird. Konstante
Beschreibung
QTextEdit::NoWrap
Der Text wird gar nicht umbrochen.
QTextEdit::WidgetWidth
Der Text wird am Ende der rechten Seite des Texteditors umbrochen, also abhängig von der Breite des Texteditors; das ist der Standardwert.
QTextEdit::FixedPixelWidth
Der Text wird ab einer bestimmten Pixel-Breite umbrochen; Wert kann mit der Methode setLineWrapColumnOrWidth() gesetzt werden.
QTextEdit::FixedColumnWidth
Der Text wird ab einer bestimmten Spaltenanzahl umbrochen; Wert kann mit der Methode setLineWrapColumnOrWidth() gesetzt werden.
Tabelle 4.77
Konstanten, um den Text am Zeilenende zu umbrechen
Mögliche Operationen, wo Sie den Cursor mit der Methode moveCursor() setzen können, sind in der enum-Konstante QTextCursor::MoveOperation gesetzt:
248
1542.book Seite 249 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Konstante
Beschreibung
QTextCursor::NoMove
Der Cursor bleibt, wo er ist.
QTextCursor::Start
Der Cursor wird an den Anfang des Dokuments gesetzt.
QTextCursor::StartOfLine
Der Cursor wird an den Anfang der aktuellen Zeile gesetzt.
QTextCursor::StartOfBlock
Der Cursor wird an den Anfang des aktuellen Blocks (Absatz) gesetzt.
QTextCursor::StartOfWord
Der Cursor wird an den Anfang des aktuellen Wortes gesetzt.
QTextCursor::PreviousBlock
Der Cursor wird vor den vorhergehenden Block gesetzt.
QTextCursor::PreviousCharacter
Der Cursor wird eine Position vor dem aktuellen Zeichen gesetzt.
QTextCursor::PreviousWord
Der Cursor wird eine Position vor dem aktuellen Wort gesetzt.
QTextCursor::Up
Der Cursor wird um eine Zeile nach oben versetzt.
QTextCursor::Left
Der Cursor wird um ein Zeichen nach links versetzt.
QTextCursor::WordLeft
Der Cursor wird links vom aktuellen Wort gesetzt.
QTextCursor::End
Der Cursor wird an das Ende des Dokuments gesetzt.
QTextCursor::EndOfLine
Der Cursor wird an das Ende der aktuellen Zeile gesetzt.
QTextCursor::EndOfWord
Der Cursor wird an das Ende des aktuellen Wortes gesetzt.
QTextCursor::EndOfBlock
Der Cursor wird an das Ende des aktuellen Blocks (Absatz) gesetzt.
QTextCursor::NextBlock
Der Cursor wird an den Anfang des nächsten Blocks gesetzt.
QTextCursor::NextCharacter
Der Cursor wird zum nächsten Zeichen gesetzt.
QTextCursor::NextWord
Der Cursor wird zum nächsten Wort gesetzt.
QTextCursor::Down
Der Cursor wird eine Zeile tiefer gesetzt.
QTextCursor::Right
Der Cursor wird rechts vom Zeichen gesetzt.
QTextCursor::WordRight
Der Cursor wird rechts vom aktuellen Wort gesetzt.
Tabelle 4.78
Mögliche Operationen für den Cursor
249
4.5
1542.book Seite 250 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Wie Sie den Text in einem Dokument umbrechen, gibt die enum-Konstante QTextOption::WrapMode an: Konstante
Beschreibung
QTextOption::NoWrap
Der Text wird niemals umbrochen.
QTextOption::WordWrap
Der Text wird zwischen zwei Worten umbrochen.
QTextOption::ManualWrap
Der Text wird manuell umbrochen. Bspw. durch (¢) bei der Eingabe vom Anwender.
QTextOption::WrapAnywhere
Der Text kann an jedem Punkt der Linie umbrochen werden. Auch mitten in einem Wort.
QTextOption:: Wenn möglich, wird die Zeile zwischen zwei Worten WrapAtWordBoundaryOrAnywhere umbrochen. Ansonsten kann der Text an einer belie-
bigen Position manuell umbrochen werden. Tabelle 4.79
Text in einem Dokument umbrechen
Jetzt zu den öffentlichen Signalen, die unmittelbar bei der Klasse QTextEdit auftreten können. Signal
Beschreibung
void copyAvailable ( bool yes );
Dieses Signal wird ausgelöst, wenn ein Text markiert bzw. de-markiert wurde. Wurde ein Text markiert, wird das Signal mit dem Parameter true ausgelöst. Bei einer De-markierung wird das Signal mit dem Parameter false aufgerufen. Wenn ein Text markiert wurde und das Signal mit true ausgelöst wurde, kann die Markierung bspw. in die Zwischenablage kopiert werden.
void currentCharFormatChanged ( Das Signal wird ausgelöst, wenn die aktuelle Formaconst QTextCharFormat & f ); tierung geändert wurde. Die neue Formatierung befindet sich in f. void cursorPositionChanged ();
Dieses Signal wird ausgelöst, wenn die Position des Cursors verändert wurde.
void redoAvailable ( bool available );
Dieses Signal wird ausgelöst, wenn eine Operation zum Wiederholen vorhanden ist. Ist eine Operation vorhanden, wird der Parameter auf true, ansonsten auf false gesetzt.
void selectionChanged ();
Dieses Signal wird ausgelöst, wenn eine Markierung verändert wurde.
void textChanged ();
Das Signal wird ausgelöst, wenn der Text verändert wurde.
Tabelle 4.80
250
Signale der Klasse QTextEdit
1542.book Seite 251 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Signal
Beschreibung
void undoAvailable ( bool available );
Dieses Signal wird ausgelöst, wenn eine Operation zu Rückgängig machen vorhanden ist. Ist eine Operation vorhanden, wird der Parameter auf true, ansonsten auf false gesetzt.
Tabelle 4.80
Signale der Klasse QTextEdit (Forts.)
Jetzt noch zu den öffentlichen Slots von QTextEdit: Slot
Beschreibung
void append ( const QString & text );
Fügt einen neuen Absatz mit dem Text text ans Ende des Editors.
void clear ();
Löscht den kompletten Text von Editor. Beachten Sie, dass Sie damit auch den Undo/Redo-Verlauf löschen.
void copy ();
Kopiert einen markierten Text in die Zwischenablage.
void cut ();
Schneidet einen markierten Text aus und legt ihn in die Zwischenablage.
void insertHtml ( const QString & text );
Fügt den Text text, der eine HTML-Formatierung besitzt (bspw. von einer Webseite kopiert), an der aktuellen Cursorposition ein.
void insertPlainText ( const QString & text );
Fügt den (Plain-)Text text an der aktuellen Cursorposition ein.
void paste ();
Fügt einen Text aus der Zwischenablage an der aktuellen Cursorposition ein. Befindet sich in der Zwischenablage kein Text, geschieht nichts.
void redo ();
Wiederholt die vorhergehende Operation. Gibt es keine, geschieht nichts.
void scrollToAnchor ( const QString & name );
Scrollt den Texteditor zum angegebenen String name. Gibt es diesen String nicht, geschieht nichts.
void selectAll ();
Markiert den kompletten Text im Editor.
void setAlignment ( Qt::Alignment a );
Setzt die Ausrichtung des Texts auf a. Mögliche Werte wurden bereits in Tabelle 4.8, 4.9 und 4.10 beschrieben.
void setCurrentFont ( const QFont & f );
Setzt die Schriftart f zur aktuellen Formatierung.
void setFontFamily ( const QString & fontFamily );
Setzt die Schriftart fontFamily zur aktuellen Formatierung.
void setFontItalic ( bool italic );
Mit true als Parameter setzen Sie die Schrift als Kursivschrift. Mit false heben Sie dies auf.
Tabelle 4.81
Slots der Klasse QTextEdit
251
4.5
1542.book Seite 252 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Slot
Beschreibung
void setFontPointSize ( qreal s );
Setzt die Punktgröße der aktuellen Formatierung auf s.
void setFontUnderline ( bool underline );
Mit true als Parameter wird die Schrift unterstrichen. false bewirkt das Gegenteil.
void setFontWeight ( int weight );
Setzt die Schrift- »Stärke« der aktuellen Formatierung auf weight. Hier sollte ein Wert von 0 bis 99 angegeben werden. Es gibt aber auch vordefinierte Konstanten mit enum-Konstanten von QFont::Weight.
void setHtml ( const QString & text );
Damit fügen Sie den String text in den Editor ein. Der Text wird als Rich-Text in HTML-Formatierung interpretiert. Anderer Text, der sich zuvor im Editor befand, wird gelöscht. ebenso der Verlauf von Undo/ Redo.
void setPlainText ( const QString & text );
Damit wird der (Plain-)Text text in den Editor eingefügt. Der Text wird als reiner (Plain-)Text interpretiert. Anderer Text, der sich zuvor im Editor befand, wird gelöscht, ebenso der Verlauf von Undo/Redo.
void setText ( const QString & text );
Damit wird der String text in den Editor eingefügt. Das Format kann hierbei sowohl im HTML- als auch im (Plain-)Textformat vorliegen. Der Texteditor versucht, den Text im richtigen Format einzufügen. Es wird allerdings empfohlen, entweder setHtml() oder setPlainText() zu verwenden, um Probleme bei der Textdarstellung zu vermeiden.
void setTextColor ( const QColor & c );
Setzt die Textfarbe der aktuellen Formatierung auf c.
void undo ();
Macht die zuletzt gemachte Operation rückgängig. Gibt es keine solche Operation, geschieht nichts.
void zoomIn ( int range = 1 );
Zoomt in den Text, vergrößert die Basisschriftart um range Punkte und berechnet alle Schriftgrößen neu. Bilder werden allerdings nicht vergrößert.
void zoomOut ( int range = 1 );
Zoomt aus dem Text. verkleinert die Basisschriftart um range Punkte und berechnet alle Schriftgrößen neu. Bilder werden allerdings nicht verkleinert.
Tabelle 4.81
Slots der Klasse QTextEdit (Forts.)
Hierzu nun ein ganz einfaches Beispiel eines Texteditors. Es ist wenig sinnvoll, ein umfangreicheres Beispiel zu verwenden, da wir sonst auf zu viele Themen vorgreifen müssen. Unser Texteditor kann lediglich einen Text zum Ansehen laden und demonstriert die Darstellung mit HTML-formatiertem Text, außerdem wurde eine kleine Suche implementiert, die Schriftart und -farbe können verän-
252
1542.book Seite 253 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
dert werden. Die Schriftart bezieht sich auf den kompletten Text. Um die Schriftfarbe zu ändern, markieren Sie den entsprechenden Text. Sie können für jeden Buchstaben eine Farbe auswählen. Im Beispiel wurde notgedrungen auf Menüs zurückgegriffen, um ein wenig Interaktion zu gewährleisten. Mehr zu den Menüs erfahren Sie in Abschnitt 5.2.2. Außerdem wurde kein Dialog mehr verwendet, sondern ein Hauptfenster (QMainWindow) (siehe Kapitel 5). Das Grundgerüst der Anwendung: 00 01 02 03 04 05
// beispiele/qtextedit/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include
06 07 08 09 10 11 12 13 14 15 16 17 18
class MyWidget : public QMainWindow { Q_OBJECT public: MyWidget(QMainWindow *parent = 0); QTextEdit* editor; public slots: void openFile(); void newFile(); void changeFont( ); void changeColor(); void search(); }; #endif
Jetzt die Implementierung des Codes: 00 01 02 03 04 05 06 07 08 09 10 11
// beispiele/qtextedit/mywidget.cpp #include "mywidget.h" #include #include #include #include #include #include #include #include #include #include
253
4.5
1542.book Seite 254 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
12 // neue Widget-Klasse vom eigentlichen Widget ableiten 13 MyWidget::MyWidget( QMainWindow *parent) : QMainWindow(parent) { 14 editor = new QTextEdit; 15 QString html_text( "QTextEdit unterstützt HTML-Tags zur" "Formatierung von Text.
" "- Aufzählpunkt1
" "- Aufzählpunkt2
" "" "- Aufzählpunkt3
" "
" "
" "Werte | Beschreibung | " "
fault_xxx | Fehler | " "
"); 16 17 18 19 20
21 22 23 24
25
26
27 28 29
30
254
editor->setHtml(html_text); // Text nach 80 Spalten brechen editor->setLineWrapMode( QTextEdit::FixedColumnWidth ); editor->setLineWrapColumnOrWidth (80); editor->setWordWrapMode ( QTextOption::WrapAtWordBoundaryOrAnywhere ); // das Menü QMenu *fileMenu = new QMenu(tr("&Datei"), this); menuBar()->addMenu(fileMenu); fileMenu->addAction( tr("&Neu"), this, SLOT(newFile()), QKeySequence(tr("Ctrl+N", "Datei|Neu"))); fileMenu->addAction( tr("&Oeffnen..."), this, SLOT(openFile()), QKeySequence(tr("Ctrl+O", "Datei|Oeffnen"))); fileMenu->addAction( tr("Be&enden"), qApp, SLOT(quit()), QKeySequence(tr("Ctrl+E", "Datei|Beenden"))); QMenu *workMenu = new QMenu( tr("&Bearbeiten"), this); menuBar()->addMenu(workMenu); workMenu->addAction( tr("&Suchen"), this, SLOT(search()), QKeySequence(tr("Ctrl+S", "Bearbeiten|Suchen"))); QMenu *viewMenu = new QMenu(tr("&Ansicht"), this);
1542.book Seite 255 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
31 32
33
34 35 36 }
menuBar()->addMenu(viewMenu); viewMenu->addAction( tr("&Schriftart ändern"), this,SLOT(changeFont()), QKeySequence( tr("Ctrl+F", "Ansicht|Schriftart ändern") ) ); viewMenu->addAction( tr("S&chriftfarbe ändern"), this, SLOT(changeColor()), QKeySequence( tr("Ctrl+C", "Ansicht|Schriftfarbe ändern"))); setCentralWidget(editor); setWindowTitle("QTextEdit – Demo");
37 // Datei öffnen und im Editor anzeigen 38 void MyWidget::openFile() { 39 QString fileName; 40 fileName = QFileDialog::getOpenFileName( this, tr("Datei öffnen"), "", "C++ Dateien (*.cpp *.h);;" "Text-Dateien (*.txt);;" "Alle Dateien (*.*)" ); 41 if (!fileName.isEmpty()) { 42 QFile file(fileName); 43 if (file.open(QFile::ReadOnly | QFile::Text)) 44 editor->setPlainText(file.readAll()); 45 } 46 } 47 // Inhalt des Editors löschen 48 void MyWidget::newFile( ) { 49 editor->clear(); 50 } 51 // Schriftart ändern 52 void MyWidget::changeFont( ) { 53 editor->setFont( QFontDialog::getFont(0, editor->font() ) ); 54 } 55 // Farbe von markierten Text ändern 56 void MyWidget::changeColor( ) { 57 editor->setTextColor(QColorDialog::getColor()); 58 }
255
4.5
1542.book Seite 256 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
59 // im Editor nach einer bestimmten Textfolge suchen 60 void MyWidget::search( ) { 61 bool ok; 62 QString text = QInputDialog::getText( this, "Suchdialog", "Text zur Suche eingeben :", QLineEdit::Normal, "Suche eingeben", &ok ); 63 if (ok && !text.isEmpty()) 64 editor->find(text); 65 }
Zum Schluss noch das Hauptprogramm: 00 // beispiele/qtextedit/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.78
Die Klasse QTextEdit bei der Ausführung
Es wurde bereits erwähnt, dass das Beispiel zur Klasse QTextEdit recht dürftig ausfällt, doch werden Sie diese Klasse bei einigen Beispielen des Öfteren wiederfinden. Ein Texteditor ist eben immer ein gutes und einfaches Beispiel, das viele Widgets in der Praxis demonstrieren kann.
256
1542.book Seite 257 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
QTextBrowser Wenn Ihnen jetzt vorschwebt, aus der Klasse QTextEdit einen einfachen TextBrowser mit Hypertext-Navigation zu verwenden, finden Sie hierfür etwas mit der Klasse QTextBrowser. Die Klasse QTextBrowser wurde von der Klasse QTextEdit als nur lesbar (Read-only-Mode) abgeleitet, und es wurden einige Navigationsmethoden wie Vorwärts, Rückwärts, Startseite usw. hinzugefügt. QCalendarWidget Die von QWidget abgeleitete Klasse QCalendarWidget stellt einen Monatskalender dar, in dem der Anwender ein Datum auswählen kann. Die öffentlichen Methoden der Klasse sind folgende: Methode
Beschreibung
QCalendarWidget ( QWidget * parent = 0 );
Erzeugt ein neues Kalender-Widget mit parent als Eltern-Widget.
~QCalendarWidget ();
Desktruktor. Zerstört ein Kalender-Widget.
QMap dateTextFormat () const;
Gibt eine QMap von QDate-Klasse(n) mit QTextCharFormat zurück, womit alle möglichen Formatierungen des Datums zurückgegeben werden.
QTextCharFormat dateTextFormat ( Gibt die aktuelle Formatierung (der Klasse QTextconst QDate & date ) const; CharFormat) für das Datum zurück. Wenn keine
speziellen Formatierungen gesetzt sind, kann der Rückgabewert auch leer sein. Qt::DayOfWeek firstDayOfWeek () const;
Damit erhalten Sie den Wochentag, der in der erste Spalte angezeigt wird. Standardmäßig ist dies Sonntag. Mögliche Werte für Qt::DayOfWeek siehe Tabelle 4.83.
QTextCharFormat headerTextFormat () const;
Gibt die Textformatierung für den Kopf der Tabelle zurück.
HorizontalHeaderFormat Gibt das Format des horizontalen Kopfes zurück. horizontalHeaderFormat () const; Der Standardwert hierbei ist QCalendarWidget::ShortDayNames. Mögliche Werte und
deren Bedeutung siehe Tabelle 4.84. bool isGridVisible () const;
Tabelle 4.82
Gibt true zurück, wenn das Gitter der Tabelle sichtbar ist. Per Standard ist dies nicht der Fall (false).
Öffentliche Methoden der Klasse QCalendarWidget
257
4.5
1542.book Seite 258 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
bool isHeaderVisible () const;
Gibt diese Methode true zurück (Standard), werden im Header die Kontrollen für den Monat (nächster Monat, vorheriger Monat) und die Jahresauswahl angezeigt. Bei false sind diese Steuerungen versteckt.
QDate maximumDate () const;
Damit halten Sie das maximal mögliche Datum zurück, welches der Anwender auswählen kann. Standardmäßig ist dies der letzte Tag, den die Klasse QDate behandeln kann.
QDate minimumDate () const;
Damit halten Sie das minmal mögliche Datum zurück, welches der Anwender auswählen kann. Standardmäßig ist dies der erste Tag, den die Klasse QDate behandeln kann.
int monthShown () const;
Gibt den aktuell angezeigten Monat zurück.
QDate selectedDate () const;
Damit erhalten Sie das aktutell markierte Datum zurück. Standardmäßig ist dies immer das aktuelle Datum. Wurde ein Datum ausgewählt, muss der Bereich des Datums zwischen minimumDate() und maximumDate() liegen.
SelectionMode selectionMode () const;
Wie der Anwender ein Datum auswählt, erhalten Sie mit dieser Methode. Mit dem Standardwert QCalendarWidget::SingleSelection kann der Anwender ein Datum zwischen minimumDate() und maximumDate() auswählen. Die zweite Möglichkeit ist QCalendarWidget::NoSelection, womit der Anwender kein Datum auswählen kann.
void setDateTextFormat ( const QDate & date, const QTextCharFormat & format );
Damit können Sie die Formatierung format für das Datum date setzen.
void setFirstDayOfWeek ( Qt::DayOfWeek dayOfWeek );
Damit setzen Sie den Wochentag, der in der ersten Spalte angezeigt wird. Mögliche Werte für Qt::DayOfWeek siehe Tabelle 4.83.
void setGridVisible ( bool show );
Mit true bewirken Sie, dass das Gitter sichtbar ist. Standard ist false.
void setHeaderTextFormat ( Damit setzen Sie die Formatierung für die Kopfzeile const QTextCharFormat & format ); auf format. void setHeaderVisible ( bool show );
Tabelle 4.82
258
Mit false sorgen Sie dafür, dass im Header die Kontrollen für den Monat (nächster Monat, vorheriger Monat) und die Jahresauswahl nicht angezeigt werden. Standard ist true.
Öffentliche Methoden der Klasse QCalendarWidget (Forts.)
1542.book Seite 259 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setHorizontalHeaderFormat ( Setzt das Format des horizontalen Kopfes auf HorizontalHeaderFormat format ); format. Mögliche Werte und deren Bedeutung
siehe Tabelle 4.84. void setMaximumDate ( const QDate & date );
Damit setzen Sie das max. mögliche Datum, aus dem der Anwender wählen kann, auf date.
void setMinimumDate ( const QDate & date );
Damit setzen Sie das min. mögliche Datum, aus dem der Anwender wählen kann, auf date.
void setSelectionMode ( SelectionMode mode );
Mit QCalendarWidget::NoSelection können Sie abschalten, dass der Anwender ein Datum auswählen kann. Mit QCalendarWidget::SingleSelection schalten Sie dies wieder ein.
void setVerticalHeaderFormat ( VerticalHeaderFormat format );
Damit setzen Sie das Format des vertikalen Headers auf format. Der Standardwert lautet QCalendarWidget::ISOWeekNumber, womit die Wochennummer angezeigt wird. Der zweite Wert QCalendarWidget::NoVerticalHeader sorgt dafür, dass diese Spalte nicht mehr angezeigt wird.
void setWeekdayTextFormat ( Damit setzen Sie die Formatierung für die einzelnen Qt::DayOfWeek dayOfWeek, Wochentage dayOfWeek auf format. const QTextCharFormat& format ); VerticalHeaderFormat verticalHeaderFormat () const;
Gibt die Formatierung des vertikalen Headers zurück. Siehe setVerticalHeaderFormat().
QtextCharFormat weekdayTextFormat ( Qt::DayOfWeek dayOfWeek ) const;
Damit erhalten Sie die Formatierung für den Wochentag dayOfWeek.
int yearShown () const;
Gibt das Jahr zum aktuell angezeigten Monat zurück.
Tabelle 4.82
Öffentliche Methoden der Klasse QCalendarWidget (Forts.)
Jetzt zu den einzelnen Wochentagsnamen der enum-Konstante Qt::DayOfWeek, die selbsterklärend sein sollten: Konstante Qt::Monday Qt::Tuesday Qt::Wednesday Qt::Thursday
Tabelle 4.83
Mögliche Werte für DayOfWeek
259
4.5
1542.book Seite 260 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Konstante Qt::Friday Qt::Saturday Qt::Sunday
Tabelle 4.83
Mögliche Werte für DayOfWeek (Forts.)
Nun die enum-Konstanten in QCalendarWidget::HorizontalHeaderFormat, womit sich das Format für den horizontalen Header setzen bzw. ermitteln lässt: Konstante
Beschreibung
QCalendarWidget:: SingleLetterDayNames
Im Header werden einzelnen Buchstaben angezeigt, bspw. für Montag – M.
QCalendarWidget::ShortDayNames
Der Standardwert. Damit wird für den Tagesnamen eine Abkürzung verwendet, bspw. Montag – Mon.
QCalendarWidget::LongDayNames
Mit dieser Konstante wird der komplette Tagesname angezeigt.
QCalendarWidget:: NoHorizontalHeader
Es wird überhaupt kein horizontaler Header angezeigt.
Tabelle 4.84
Konstanten für die Formatierung des horizontalen Headers
Nun zu den öffentlichen Signalen der Klasse QCalendarWidget: Signal
Beschreibung
void activated ( const QDate & date );
Das Signal wird ausgelöst, wenn der Anwender (¢), die Leertaste oder das Datum doppeltgeklickt hat. Das aktuelle Datum befindet sich in date.
void clicked ( const QDate & date );
Das Signal wird ausgelöst, wenn ein Mausbutton geklickt wurde. Das aktuelle Datum befindet sich in date.
void currentPageChanged ( int year, int month );
Das Signal wird ausgelöst, wenn der aktuelle Monat geändert wurde. Der neue Monat und das neue Jahr befinden sich in den entsprechenden Parametern.
void selectionChanged ();
Das Signal wird ausgelöst, wenn das aktuelle markierte Datum geändert wurde.
Tabelle 4.85
260
Signale der Klasse QCalendarWidget
1542.book Seite 261 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Als Nächstes die einzelnen Slots der Klasse QCalendarWidget: Slot
Beschreibung
Void setCurrentPage ( int year, int month );
Zeigt das übergebene Jahr year im Monat month an, ohne dass das markierte Datum gewechselt wird.
Void setDateRange ( const QDate & min, const QDate & max );
Setzt den Bereich für das minimal mögliche Datum auf min und das maximal mögliche auf max.
Void setSelectedDate ( const QDate & date );
Setzt date als aktuell markiertes Datum. Der Wert des Datums muss zwischen minimumDate() und maximumDate() liegen.
Void showNextMonth ();
Zeigt den nächsten Monat relativ zum aktuellen Monat an. Allerdings wird das ausgewählte Datum hierdurch nicht verändert.
Void showNextYear ();
Zeigt das nächste Jahr relativ zum aktuellen Jahr an. Auch hier wird das ausgewählte Datum nicht verändert.
void showPreviousMonth ();
Zeigt den Vormonat relativ zum aktuellen Monat an. Auch hier wird das ausgewählte Datum nicht verändert.
void showPreviousYear ();
Zeigt das Vorjahr relativ zum aktuellen Jahr an. Das ausgewählte Datum wird dadurch nicht verändert.
void showSelectedDate ();
Zeigt den Monat des ausgewählten Datums an.
void showToday ();
Zeigt den Monat des heutigen Datums an.
Tabelle 4.86
Öffentliche Slots der Klasse QCalendarWidget
Jetzt zu einem einfachen Beispiel, wo Sie durch das Kalender-Widget navigieren können. Es wurde nur eine Signal-Slot-Verbindung eingerichtet, die dem Anwender das Datum anzeigt, welches dieser »doppelklickt« bzw. durch (¢) oder die Leertaste aktiviert. Zunächst das Grundgerüst: 00 01 02 03 04 05
// beispiele/qcalendarwidget/mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include #include #include
06 class MyWidget : public QMainWindow { 07 Q_OBJECT
261
4.5
1542.book Seite 262 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
08 09 10 11 12 13 14
public: MyWidget(QMainWindow *parent = 0); QCalendarWidget *calendar; public slots: void printDate(const QDate& ); }; #endif
Nun wieder der eigentliche Code: 00 01 02 03
// beispiele/qcalendarwidget/mywidget.cpp #include "mywidget.h" #include #include
04 // neue Widget-Klasse vom eigentlichen Widget ableiten 05 MyWidget::MyWidget( QMainWindow *parent): QMainWindow(parent) { 06 calendar = new QCalendarWidget; 07 calendar->setGridVisible(true); 08 09 10 11 }
connect( calendar, SIGNAL(activated (const QDate&) ), this, SLOT(printDate(const QDate&) ) ); setCentralWidget(calendar); setWindowTitle("QTextEdit – Demo");
12 void MyWidget::printDate( const QDate& date) { 13 QString sdate = date.toString("(ddd) dd.MMM.yyyy"); 14 QMessageBox::information( this, "Datum ausgewertet", sdate, QMessageBox::Ok ); 15 }
Schließlich die Hauptfunktion: 00 // beispiele/qcalendarwidget/main.cpp 01 #include 02 #include "mywidget.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWidget* window = new MyWidget; 06 window->show(); 07 return app.exec(); 08 }
262
1542.book Seite 263 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Das Programm bei der Ausführung:
Abbildung 4.79
4.5.5
Die Klasse QCalendarWidget bei der Ausführung
Item-View-Subklassen verwenden (Ansichts-Klassen)
Die nun folgenden vorgefertigten Klassen gehören zu den Item-View-Unterklassen (Ansichts-Klassen), die sich hervorragend für die Datenvisualisierung eignen. Benutzerdefiniertes Modell Neben dieser Möglichkeit können Sie auch ein benutzerdefiniertes Modell definieren. Dieses kann erforderlich werden, wenn Sie das Modell von der Ansicht trennen wollen.
Folgende vorgefertigte Item-View-Klassen stehen Ihnen dabei zur Verfügung: 왘
QTableView stellt die Daten in einer Tabelle dar. Oberhalb der Zeilen und an der Spaltenseite befinden sich zudem Zeilen- und Spalten-Köpfe, die ebenfalls individuellen Bedürfnissen angepasst werden.
왘
QListView stellt Daten in einer eindimensionalen Liste da. Neben der Listen-
왘
QTreeView stellt die Daten in einer Baumform dar, was mit QListView nicht
darstellung von einfachem Text gibt es auch die Darstellung von Icons. möglich ist. Außerdem kann QTreeView mehrere Spalten darstellen. 왘
QHeaderView stellt keine eigenständige Klasse zur Ansicht von Datenelementen dar, sondern ist für die Kopfzeilen von QTreeView und die Kopfspalten und -zeilen von QTableView gedacht.
Alle Ansichtsklassen erben von QAbstractItemView, der Basisklasse für Ansichten. QAbstractItemView wiederum hat als Basisklasse QAbstractScrollArea. Somit kann das Widget darin viel größer sein als der Sichtbarkeitsbereich (Viewport). Ist die Ansicht also größer, als tatsächlich darstellbar (was ja häufig üblich
263
4.5
1542.book Seite 264 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
ist), wird im Rahmenwidget automatisch ein horizontaler oder/und vertikaler Laufbalken angezeigt. Solange Sie kein eigenes Modell implementieren wollen, brauchen Sie diese View-Klasse QListView, QTreeView und QTableView nicht direkt zu verwenden und können auf die davon abgeleiteten Sub-Klassen QListWidget, QTreeWidget und QTableWidget zurückgreifen. Selbstverständlich stehen Ihnen dann auch hier die Methoden der übergeordneten Basisklassen zur Verfügung. Hierbei müssen Sie sich einfach mit dem Assistant von Qt nach oben hangeln. QTableWidget und QTableWidgetItem Benötigen Sie ein Widget zur Darstellung von Tabellen (wie Excel und Co.), sollten Sie sich die Klasse QTableWidget ansehen. Die Klasse QTableWidget stellt uns dabei schon viele Tabellenkalkulationsfunktionen zur Verfügung. Hierzu ein Überblick über die Methoden der Klasse QTableWidget. Methode
Beschreibung
QTableWidget ( QWidget * parent = 0 );
Erzeugt ein neues QTableWidget-Objekt mit parent als Eltern-Widget.
QTableWidget ( int rows, int columns, QWidget * parent = 0 );
Erzeugt ein neues QTableWidget-Objekt mit rows Zeilen und columns Spalten und parent als Eltern-Widget.
~QTableWidget ();
Destruktor. Zerstört ein QTableWidget.
QWidget* cellWidget ( int row, int column ) const;
Gibt das Widget zurück, welches in der Reihe row und der Spalte column angezeigt wird.
void closePersistentEditor ( QTableWidgetItem * item );
Schließt den permanent anzeigenden Editor für das Element item. Standardmäßig ist kein permanenter Editor geöffnet (siehe openPersistentEditor()).
int column ( const QTableWidgetItem * item ) const;
Gibt vom Element item die Spaltennummer zurück.
int columnCount () const;
Gibt die Anzahl der Spalten zurück.
int currentColumn () const;
Gibt die aktuell ausgewählte Spaltenposition zurück.
QTableWidgetItem* currentItem () const;
Gibt das aktuell ausgewählte Element zurück.
Tabelle 4.87
264
Öffentliche Methoden der Klasse QTableWidget
1542.book Seite 265 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
int currentRow () const;
Gibt die aktuell ausgewählte Zeilennummer zurück.
void editItem ( QTableWidgetItem * item );
Startet das Editieren des Elements item, wenn es editierbar ist.
QList findItems ( const QString & text, Qt::MatchFlags flags ) const;
Gibt eine Liste von Elementen zurück, die mit dem String text übereinstimmen. Zur Suche können zusätzliche Optionen mit flags angegeben werden. Mögliche Werte für flags siehe Tabelle 4.88.
QTableWidgetItem* horizontalHeaderItem ( int column ) const;
Gibt das horizontale Kopf-Element für die Spalte column zurück.
QTableWidgetItem* item ( int row, int column ) const;
Gibt das Element der Zeile row und der Spalte column zurück.
QTableWidgetItem* itemAt ( const QPoint & point ) const;
Gibt einen Zeiger auf das Element zurück, welches sich an der Position point befindet. Befindet sich hier kein Element, wird 0 zurückgegeben.
QTableWidgetItem * itemAt ( int ax, int ay ) const;
Eine überladene Version von eben. Diese Methode gibt das Element an der Position ax und ay (entspricht QPoint(ax, ay)) zurück oder 0, wenn kein Element existiert.
const QTableWidgetItem* itemPrototype () const;
Gibt den Element-Prototypen zurück, den die Tabelle verwendet.
void openPersistentEditor ( QTableWidgetItem * item );
Öffnet den permanent anzeigenden Editor für das Element item. Standardmäßig ist kein permanenter Editor geöffnet.
int row ( const QTableWidgetItem* item ) const;
Gibt die Zeile für das Element item zurück.
int rowCount () const;
Gibt die Anzahl der Zeilen in der Tabelle zurück.
QList selectedItems ();
Gibt eine Liste mit allen markierten Elementen der Tabelle zurück.
QList Gibt eine Liste aller markierter Bereiche zurück selectedRanges () const; (siehe hierzu QTableWidgetSelectionRange). void setCellWidget ( int row, int column, QWidget * widget );
Tabelle 4.87
Setzt das Widget widget, das in der Zelle von Zeile row und Spalte column angezeigt werden soll.
Öffentliche Methoden der Klasse QTableWidget (Forts.)
265
4.5
1542.book Seite 266 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
void setColumnCount ( int columns );
Setzt die Anzahl der Spalten in der Tabelle auf columns.
void setCurrentCell ( int row, int column );
Setzt die Zeile row und die Spalte column als aktuelle Zellen der Tabelle. Abhängig vom eingestellten Selektiert-Modus, wird die Zelle zusätzlich markiert.
void setCurrentItem ( QTableWidgetItem * item );
Setzt das aktuelle Element auf item. Abhängig vom eingestellten Selektiert-Modus, wird die Zelle zusätzlich markiert.
void setHorizontalHeaderItem ( int column, QTableWidgetItem * item );
Setzt das horizontale Kopf-Element für die Spalte column auf item.
void setHorizontalHeaderLabels ( const QStringList& labels );
Setzt die Labels für die horizontalen Kopf-Elemente auf labels.
void setItem ( int row, int column, QTableWidgetItem * item );
Setzt das Element in der Zeile row und der Spalte column auf item.
void setItemPrototype ( const QTableWidgetItem * item );
Setzt den Elemente-Prototyp für die Tabelle auf item.
void setRangeSelected ( const QTableWidgetSelectionRange & range, bool select );
Markiert oder demarkiert einen Bereich (abhängig von select (true = Markieren / false = Demarkieren)).
void setRowCount ( int rows );
Setzt die Anzahl der vorhandenen Zeilen der Tabelle auf rows.
void setVerticalHeaderItem ( int row, QTableWidgetItem * item );
Setzt das vertikale Kopf-Element für die Zeile row auf item.
void setVerticalHeaderLabels ( const QStringList & labels );
Setzt die Labels für die vertikalen Kopf-Elemente auf labels.
void sortItems ( int column, Qt::SortOrder order = Qt::AscendingOrder );
Sortiert alle Zeilen in der Tabelle basierend auf der Spalte column und der Sortier-Option order. Mögliche Werte für order siehe Tabelle 4.89.
Tabelle 4.87
266
Öffentliche Methoden der Klasse QTableWidget (Forts.)
1542.book Seite 267 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
QTableWidgetItem * takeHorizontalHeaderItem ( int column );
Entfernt das Label des horizontalen Kopf-Elements von der Spalte column, ohne es komplett aus dem Speicher zu löschen, und setzt wieder den Standardwert dafür. Als Rückgabewert können Sie das gelöschte Element weiterhin verwalten und ggf. wiederverwenden.
QTableWidgetItem * takeItem ( int row, int column );
Entfernt das Element von der Zeile row und der Spalte column von der Tabelle, ohne es komplett aus dem Speicher zu löschen. Auch hier kann der Rückgabewert wieder verwendet werden, um das Element weiterzuverwenden.
QTableWidgetItem * takeVerticalHeaderItem ( int row );
Entfernt das Label des vertikalen Kopf-Elements von der Zeile row, ohne es komplett aus dem Speicher zu löschen, und setzt wieder den Standardwert dafür. Als Rückgabewert können Sie das gelöschte Element weiterhin verwalten und ggf. wieder verwenden.
QTableWidgetItem * verticalHeaderItem ( int row ) const;
Gibt das das vertikale Kopf-Element für die Zeile row zurück.
int visualColumn ( int logicalColumn ) const;
Gibt den rechteckigen sichtbaren Bereich für die Spalten logicalColumn zurück.
QRect visualItemRect ( const QTableWidgetItem* item ) const;
Gibt den rechteckigen sichtbaren Bereich für das Element item zurück.
int visualRow ( int logicalRow ) const;
Gibt den rechteckigen sichtbaren Bereich für die Zeilen logicalRow zurück.
Tabelle 4.87
Öffentliche Methoden der Klasse QTableWidget (Forts.)
Mit den folgenden Flags der enum-Variablen Qt::MatchFlag wird beschrieben, wie das Suchmuster verwendet wird bei der Suche nach einem Element. Konstante
Bedeutung
Qt::MatchExactly
Verwendet die auf QVariant-basierende Anpassung.
Qt::MatchFixedString
Verwendet eine String-basierende Anpassung des Suchmusters. Die Suche ist unabhängig von der Groß- und Kleinschreibung, es sei denn, es wurde entsprechendes Flag verwendet (Qt::MatchCaseSensitive).
Qt::MatchContains
Der Suchausdruck ist im Element enthalten.
Tabelle 4.88
Mögliche Konstanten für Such-Optionen
267
4.5
1542.book Seite 268 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Konstante
Bedeutung
Qt::MatchCaseSensitive
Es wird zwischen Groß- und Kleinschreibung unterschieden.
Qt::MatchRegExp
Als Suchausdruck wird ein regulärer Ausdruck verwendet.
Qt::MatchWildcard
Verwendet eine String-basierende Anpassung des Suchmusters, womit auch Wildcards verwendet werden können.
Qt::MatchWrap
Verwendet eine Suche, die nach dem Vergleich mit dem letzten Element wieder am Anfang mit der Suche so lange fortfährt, bis alle Elemente in der Liste verglichen wurden.
Qt::MatchRecursive
Sucht im gesamten Verzeichnis.
Tabelle 4.88
Mögliche Konstanten für Such-Optionen (Forts.)
Außerdem war die Rede von der enum-Variablen Qt::SortOrder, die beschreibt, wie die Elemente in einem Widget sortiert werden. Konstante
Beschreibung
Qt::AscendingOrder
Die Elemente werden aufsteigend sortiert, bspw. wird mit AAA begonnen und mit ZZZ geendet.
Qt::DescendingOrder
Die Elemente werden absteigend sortiert. Hierbei wird bspw. mit ZZZ begonnen und mit AAA geendet.
Tabelle 4.89
Optionen für das Sortieren von Elementen in einem Widget
Als Nächstes zu den öffentlichen Slots der Klasse QTableWidget: Slot
Beschreibung
void clear ();
Entfernt alle Elemente der Ansicht (auch die vertikalen bzw. horizontalen Kopf-Elemente). Die Dimension der Tabelle bleibt allerdings gleich.
void clearContents ();
Entfernt alle Elemente in der Tabelle (allerdings nicht die vertikalen bzw. horizontalen Kopf-Elemente). Die Dimension der Tabelle bleibt auch hier gleich.
void insertColumn ( int column );
Fügt der Tabelle eine leere Spalte an der Spalte column hinzu.
void insertRow ( int row );
Fügt der Tabelle eine leere Zeile in der Zeile row hinzu.
void removeColumn( int column );
Entfernt die Spalte column mitsamt der darin befindlichen Elemente.
Tabelle 4.90
268
Öffentliche Slots der Klasse QTableWidget
1542.book Seite 269 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Slot
Beschreibung
void removeRow ( int row );
Entfernt die Zeile row mitsamt der darin befindlichen Elemente.
void scrollToItem ( const QTableWidgetItem * item, QAbstractItemView::ScrollHint hint = EnsureVisible );
Wenn möglich, wird die Ansicht zum Element item gescrollt. Wie genau zu diesem Punkt gescrollt wird, gibt das optionale zweite Argument hint an.
Tabelle 4.90
Öffentliche Slots der Klasse QTableWidget (Forts.)
Jetzt zu den öffentlichen Signalen, die in Verbindung mit einem Tabellen-Widget auftreten und mit einem Slot verbunden werden können: Signal
Beschreibung
void cellActivated ( int row, int column );
Das Signal wird ausgelöst, wenn eine Zelle aktiviert wurde. In den Parametern row steht die Zeile und in column die Spalte der aktivierten Zelle.
void cellChanged ( int row, int column );
Das Signal wird ausgelöst, wenn die Daten eines Elements in einer Zelle verändert wurden. Die Parameter row (Zeile) und column (Spalte) geben die veränderte Zelle an.
void cellClicked ( int row, int column )
Das Signal wird ausgelöst, wenn auf die Zelle in der Tabelle geklickt wurde; in welcher Zeile und welcher Spalte, steht in den Parametern row und column.
void cellDoubleClicked ( int row, int column );
Das Signal wird ausgelöst, wenn auf eine Zelle einer Tabelle doppelt geklickt wurde; in welcher Zeile und Spalte, steht in den Parametern row und column.
void cellEntered ( int row, int column );
Das Signal wird ausgelöst, wenn der Maus-Cursor eine Zelle der Tabelle betritt. Dieses Signal wird allerdings nur ausgelöst, wenn Mouse-Tracking eingeschaltet ist oder der Maus-Button gedrückt wurde, während die Maus innerhalb eines Elements bewegt wird. In welcher Zelle das Signal aufgetreten ist, steht in den Parametern row und column.
void cellPressed ( int row, int column );
Das Signal wird ausgelöst wenn in einer Zelle der Tabelle die Maus gedrückt wurde (ohne loszulassen wie bei Clicked). Die Zeile des Signals befindet sich in row und die Spalte in column.
Tabelle 4.91
Öffentliche Signale der Klasse QTableWidget
269
4.5
1542.book Seite 270 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Signal
Beschreibung
void currentCellChanged ( int currentRow, int currentColumn, int previousRow, int previousColumn );
Das Signal wird ausgelöst, wenn der Fokus der Zelle verändert hat. Die aktuelle Zelle wird mit currentRow und currentColumn lokalisiert. Der Fokus der Zelle zuvor befindet sich in previousRow und previous Column.
void currentItemChanged ( QTableWidgetItem* current, QTableWidgetItem* previous );
Das Signal wird ausgelöst wenn der Fokus für das aktuelle Element verändert wurde. Das neue Element befindet sich in current und das Element, das zuvor den Fokus hatte, in previous.
void itemActivated ( QTableWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element in der Tabelle aktiviert wurde; Letzteres befindet sich im Parameter item.
void itemChanged ( QTableWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element in der Tabelle verändert wurde; Letzteres befindet sich im Parameter item.
void itemClicked ( QTableWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element in der Tabelle angeklickt wurde; Letzteres befindet sich im Parameter item.
void itemDoubleClicked ( QTableWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element in der Tabelle doppelt angeklickt wurde; Letzteres befindet sich im Parameter item.
void itemEntered ( QTableWidgetItem * item );
Das Signal wird ausgelöst wenn der Maus-Cursor sich über einem Element befindet. Dieses Signal wird allerdings nur ausgelöst, wenn Mouse-Tracking eingeschaltet ist oder wenn der Maus-Button gedrückt wurde, während die Maus innerhalb eines Elements bewegt wird. Letzteres Element befindet sich im Parameter item.
void itemPressed ( QTableWidgetItem * item );
Das Signal wird ausgelöst, wenn auf einem Element in der Tabelle die Maustaste gedrückt wurde (ohne loszulassen). Letzteres Element befindet sich im Parameter item.
void itemSelectionChanged ();
Das Signal wird ausgelöst, wenn eine Markierung verändert wurde.
Tabelle 4.91
Öffentliche Signale der Klasse QTableWidget (Forts.)
QTableWidgetItem Die Klasse QTableWidgetItem liefert die für die Klasse QTableWidget verwendeten Elemente. Diese Elemente enthalten die eigentlichen Informationen des Tabellen-Widgets, die gewöhnlich als Text, Icon (ggf. mit Text) oder Checkboxen angezeigt werden.
270
1542.book Seite 271 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Hinweis In Qt3 wurde für die Elemente des Tabellen-Widgets die Klasse QTableItem verwendet. Seit Qt4 wurde diese Klasse von QTableWidgetItem abgelöst.
Jetzt zu den einzelnen Methoden der Klasse QTableWidgetItem. Methode
Beschreibung
QTableWidgetItem ( int type = Type );
Erzeugt ein neues Tabellen-Element. Der Typ type hat keine feste Bedeutung in der Tabelle und kann für eigene Zwecke verwendet werden.
QTableWidgetItem ( const QString & text, int type = Type );
Erzeugt ein neues Tabellen-Element mit dem Text text und dem Typ type.
QTableWidgetItem ( const QIcon & icon, const QString & text, int type = Type );
Erzeugt ein neues Tabellen-Element mit dem Icon icon, dem Text text und dem Typ type.
QTableWidgetItem ( const QTableWidgetItem& other);
Erzeugt eine neues Tabellen-Element als Kopie von other. type() und tableWidget() werden allerdings nicht kopiert. Diese Methode ist hilfreich für die Reimplementierung von clone().
virtual ~QTableWidgetItem ();
Destruktor. Zerstört ein Tabellen-Element.
QBrush background () const;
Gibt ein QBrush-Objekt zurück, welches zum Rendern des Elemente-Hintergrunds verwendet wird.
Qt::CheckState checkState () const;
Gibt (wenn ankreuzbar) den Zustand eine Tabellen-Elements zurück. Die möglichen Rückgabewerte wurden in der Tabelle 4.27 bereits beschrieben.
virtual QTableWidgetItem* clone () const;
Erzeugt eine Kopie eines Tabellen-Elements.
int column () const;
Gibt die Spalte des Elements in der Tabelle zurück. Ist das Element noch nicht in der Tabelle, wird –1 zurückgegeben.
virtual QVariant data ( int role ) const;
Gibt Daten des Elements mit der Funktion role zurück.
Qt::ItemFlags flags () const;
Gibt die Flags zurück, die das Element beschreiben. Mögliche Werte und deren Bedeutung siehe Tabelle 4.93.
Tabelle 4.92
Öffentliche Methoden der Klasse QTableWidgetItem
271
4.5
1542.book Seite 272 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
QFont font () const;
Gibt die Schriftart zurück, mit der das TabellenElement gerendert wird.
QBrush foreground () const;
Gibt ein QBrush-Objekt zurück, welches zum Rendern des Elemente-Vordergrundes (gewöhnlich die Schriftfarbe) verwendet wird.
QIcon icon () const;
Gibt das Icon (falls verwendet) für ein TabellenElement zurück.
bool isSelected () const;
Gibt true zurück, wenn das Element in der Tabelle markiert (ausgewählt) ist. Ansonsten wird false zurückgegeben.
virtual void read ( QDataStream & in );
Liest ein Element vom Stream in ein.
int row () const;
Gibt die Zeile für das Element in der Tabelle zurück. Ist das Element noch nicht in der Tabelle, wird –1 zurückgegeben.
void setBackground ( const QBrush & brush );
Setzt den Hintergrund eines Elements (Zelle) auf brush.
void setCheckState ( Qt::CheckState state );
Setzt den Zustand eines ankreuzbaren Elements in der Tabelle auf state. Mögliche Werte für state wurden in Tabelle 4.27 bereits näher beschrieben.
virtual void setData ( int role, const QVariant & value );
Setzt die Daten data für ein Element in der Tabelle mit einer Funktion role.
void setFlags ( Qt::ItemFlags flags );
Setzt die Flags für das Element auf flags. Mögliche Werte und deren Bedeutung siehe Tabelle 4.93.
void setFont ( const QFont & font );
Setzt die Schriftart des Elements in der Tabelle auf font.
void setForeground ( const QBrush & brush );
Setzt den Vordergrund (gewöhnlich die Schriftfarbe) eines Elements auf brush.
void setIcon ( const QIcon & icon );
Setzt das Icon für ein Element auf icon.
void setSelected ( bool select );
Mit true wird das Element markiert und mit false eine Markierung aufgehoben.
void setStatusTip ( const QString & statusTip );
Setzt einen Status-Tipp für das Element.
void setText ( const QString & text );
Setzt den Text des Elements.
Tabelle 4.92
272
Öffentliche Methoden der Klasse QTableWidgetItem (Forts.)
1542.book Seite 273 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
void setTextAlignment ( int alignment );
Setzt die Text-Ausrichtung für das Element. Mögliche Werte wurden bereits mit enum-Variablen Qt::Alignment in Tabelle 4.8, 4.9 und 4.10 beschrieben.
void setToolTip ( const QString & toolTip );
Setzt den Text toolTip als Tooltip für das Element. Dieser Tipp wird angezeigt, wenn Sie mit der Maus über das Element fahren.
void setWhatsThis ( const QString & whatsThis );
Setzt den »Was ist das?«-Hilfetext auf whatThis.
QSize sizeHint () const;
Gibt die Größe des Elements zurück.
QString statusTip () const;
Gibt den Status-Tipp für das Element zurück.
QTableWidget* tableWidget () const;
Gibt die Tabelle zurück, in der sich das Element befindet.
QString text () const;
Gibt den Text des Elements zurück.
int textAlignment () const;
Gibt die Text-Ausrichtung des Elements zurück. Mögliche Werte wurden bereits mit enum-Variablen Qt::Alignment in Tabelle 4.8, 4.9 und 4.10 beschrieben.
QString toolTip () const;
Gibt den Text für den Tooltip zurück.
int type () const;
Gibt den Typ des Elements zurück, der beim Konstruktor gesetzt wurde.
QString whatsThis () const;
Gibt den »Was ist das?«-Hilfetext zurück.
virtual void write ( QDataStream & out ) const;
Schreibt das Element auf den Stream out.
virtual bool operator< ( const QTableWidgetItem& other) const;
Gibt true zurück, wenn das Element kleiner als das Element other ist. Ansonsten wird false zurückgegeben.
QTableWidgetItem &operator= ( const QTableWidgetItem& other);
Weist dem Element das Element other zu. type() und tableWidget() werden allerdings nicht mit zugewiesen. Diese Methode ist hilfreich für die Reimplementierung von clone().
Tabelle 4.92
Öffentliche Methoden der Klasse QTableWidgetItem (Forts.)
Zusätzlich gibt es mit den Operatoren > zwei nicht verwandte Methoden, die im Zusammenhang mit QTableWidgetItem verwendet werden können: // Schreibt item in den Stream out QDataStream& operator> ( QDataStream & in, QTableWidgetItem & item );
Jetzt zu den Flags der enum-Variablen Qt::ItemFlag, womit die Eigenschaften eines Elements beschrieben werden: Konstante
Beschreibung
Qt::ItemIsSelectable
Element kann markiert werden.
Qt::ItemIsEditable
Element kann editiert werden.
Qt::ItemIsDragEnabled
Element kann gezogen (dragged) werden.
Qt::ItemIsDropEnabled
Element kann als Ziel für das Ablegen (dropped) verwendet werden.
Qt::ItemIsUserCheckable
Das Element ist ein ankreuzbares Element.
Qt::ItemIsEnabled
Der Anwender kann mit Element arbeiten.
Qt::ItemIsTristate
Das Element ist ein ankreuzbares Element mit drei Zuständen (Tristate).
Tabelle 4.93
Konstanten, die ein Element beschreiben
Zur Demonstration folgt ein einfaches Beispiel zu QTableWidget und QTabelWidgetItem. Es wird eine Tabelle erzeugt, in die Sie die Einnahmen und Ausgaben einer Woche eintragen können. In einer dritten Spalte wird jeweils die Tages-Bilanz ausgegeben. Negative Werte der Bilanz werden rot und positive grün angezeigt. In der letzten Zeile wird die gesamte Wochen-Bilanz berechnet und ausgegeben. Ein recht einfaches Beispiel, das die Klassen aber recht gut demonstriert. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08
// beispiele/qtablewidget/MyWindow.h #ifndef MyWindow_H #define MyWindow_H #include #include #include #include #include #include
09 class MyWindow : public QMainWindow { 10 Q_OBJECT 11 public: 12 MyWindow( QMainWindow *parent = 0, Qt::WindowFlags flags = 0);
274
1542.book Seite 275 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
13 QTableWidget *tableWidget; 14 public slots: 15 void renewTable(int r, int c); 16 }; 17 #endif
Nun zur Implementierung des Codes: 00 // beispiele/qtablewidget/MyWindow.cpp 01 #include "MyWindow.h" 02 #include 03 MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { 04 // neue Tabelle 8 Zeilen x 3 Spalten erzeugen 05 tableWidget = new QTableWidget(8, 3, this); 06 // Labels für die horizontalen Kopf-Elemente 07 QStringList horizontHeader; 08 horizontHeader item( 7, 2
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
// Werte von String nach double konvertieren bool ok; double c1, c2, v1, v2; c1 = Item1->text().toDouble(&ok); if( ! ok ) { c1 = 0.0; Item1->setText(tr("0")); } c2 = Item2->text().toDouble(&ok); if( ! ok ) { c2 = 0.0; Item2->setText(tr("0")); } v1 = Val1->text().toDouble(&ok); if( ! ok ) { v1 = 0.0; Val1->setText(tr("0")); } v2 = Val2->text().toDouble(&ok); if( ! ok ) { v2 = 0.0; Val2->setText(tr("0")); } // Ergebnis in dritter Spalte berechnen und // ausgeben. Positive Werte grün und negative rot QString val(tr("%1").arg( c1-c2 )); if( (c1-c2) > 0 ) { Item3->setForeground(Qt::darkGreen); Item3->setToolTip(tr("Gewinn – Positive Billanz")); } else if( (c1-c2) < 0 ) { Item3->setForeground(Qt::darkRed);
276
0 ); 1 ); 2 ); ); ); );
1542.book Seite 277 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
69 70 71 72 73 74 75 76 77 78 79 80 }
Item3->setToolTip(tr("Verlust-Negative Billanz")); } Item3->setText(val); // Endergebnisse in letzter Zeile neu berechnen // und ausgeben QString value1(tr("%1").arg( c1+v1 )); QString value2(tr("%1").arg( c2+v2 )); QString value3(tr("%1").arg( (c1+v1) – (c2+v2))); Val1->setText(value1); Val2->setText(value2); Val3->setText(value3);
Jetzt fehlt nur noch das Hauptprogramm: 00 01 02 03
// beispiele/qtablewidget/main.cpp #include #include #include "MyWindow.h"
04 int main(int argc, char *argv[]) { 05 // Damit wird , und . als Dezimalpunkt erkannt. 06 QLocale::setDefault(QLocale::German); 07 QApplication app(argc, argv); 08 MyWindow* window = new MyWindow; 09 window->show(); 10 return app.exec(); 11 }
Das Programm bei der Ausführung:
Abbildung 4.80
QTableWidget und QTableWidgetItem bei der Ausführung
277
4.5
1542.book Seite 278 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
QTableWidgetSelectionRange Die Klasse QTableWidgetSelectionRange wird für die Auswahl (Markierungen) von Tabellen in der Klasse QTableWidget verwendet (siehe bspw. QTableWidget::setRangeSelected()). Zwar gehen wir hierauf nicht näher ein, wollen aber trotzdem die Methoden dieser Klasse der Vollständigkeit halber kurz erwähnen. Methode
Beschreibung
QTableWidgetSelectionRange ();
Erzeugt einen leeren Tabellenauswahl-Bereich, wo rowCount() und columnCount() gleich 0 sind.
QTableWidgetSelectionRange ( int top, int left, int bottom, int right );
Erzeugt einen Tabellenauswahl-Bereich mit dem Bereich top, left, bottom und right der Tabelle. Die einzelnen Werte entsprechen den Zeilen und Spalten der Tabelle.
QTableWidgetSelectionRange ( const QTableWidgetSelectionRange& other );
Erzeugt und kopiert einen TabellenauswahlBereich mit dem übergebenen Bereich other.
~QTableWidgetSelectionRange ()
Destruktor. Zerstört einen TabellenauswahlBereich.
int bottomRow () const
Gibt die unterste Zeile aus dem Bereich zurück.
int columnCount () const
Gibt die Anzahl der Spalten des ausgewählten Bereichs zurück.
int leftColumn () const
Gibt die linke Spalte aus dem ausgewählten Bereich zurück.
int rightColumn () const
Gibt die rechte Spalte des ausgewählten Bereichs zurück.
int rowCount () const
Gibt die Anzahl der Zeilen des ausgewählten Bereichs zurück.
int topRow () const
Gibt die oberste Zeile aus dem Bereich zurück.
Tabelle 4.94
Methoden der Klasse QTableWidgetSelectionRange
QListWidget und QListWidgetItem Mit der Klasse QListWidget können Sie eindimensionale Listen (ggf. mit Icons) darstellen. Jedes Element (die Daten) in der Liste von QListWidget wird dabei mit der Klasse QListWidgetItem verwaltet. Hierfür zunächst die Methoden der Klasse QListWidget.
278
1542.book Seite 279 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
QListWidget ( QWidget * parent = 0 );
Erzeugt ein neues, leeres Listen-Widget mit parent als Eltern-Widget.
~QListWidget ();
Destruktor. Löscht ein Listen-Element.
void addItem ( const QString & label );
Fügt ein neues Element mit dem Text label am Ende der Liste hinzu.
void addItem ( QListWidgetItem * item );
Eine überladene Version. Fügt ein neues ListenElement item am Ende der Liste hinzu.
void addItems ( const QStringList & labels );
Fügt eine ganze String-Liste mit labels am Ende der Liste hinzu.
void closePersistentEditor ( QListWidgetItem * item );
Schließt das Editieren des Texts für das Element item.
int count () const;
Gibt die Anzahl der Elemente in der Liste zurück (inkl. der versteckten Elemente).
QListWidgetItem * currentItem () const;
Gibt das aktuell ausgewählte Element zurück.
int currentRow () const;
Gibt die Zeile des aktuell ausgewählten Elements zurück.
void editItem ( QListWidgetItem * item );
Startet das Editieren des Elements item, wenn dies erlaubt ist.
QList findItems ( const QString & text, Qt::MatchFlags flags ) const;
Sucht ein Element mit dem String text. Zusätzlich kann zum Suchmuster ein Flag flag verwendet werden.
void insertItem ( int row, QListWidgetItem * item );
Fügt das Listen-Element item an der Zeile row in der Liste ein.
void insertItem ( int row, const QString & label );
Fügt den String label an der Zeile row als neues Listen-Element in der Liste ein.
void insertItems ( int row, const QStringList & labels );
Fügt eine ganze Stringliste labels ab der Zeile row in der Liste ein.
bool isSortingEnabled () const;
Die Methode gibt true zurück, wenn die Sortierung der Liste eingeschaltet ist. Ansonsten wird false zurückgegeben.
QListWidgetItem * item ( int row ) const;
Gibt das Element aus der Zeile row der Liste zurück oder 0, wenn es kein Element mit dieser Zeile gibt.
Tabelle 4.95
Methoden der Klasse QListWidget
279
4.5
1542.book Seite 280 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
QListWidgetItem * itemAt ( const QPoint & p ) const;
Gibt das Element aus der Liste mit den Koordinaten p zurück.
QListWidgetItem * itemAt ( int x, int y ) const;
Gibt das Element aus der Liste mit den x-, y-Koordinaten zurück.
QWidget * itemWidget ( QListWidgetItem * item ) const;
Gibt das mit item angezeigte Widget zurück.
void openPersistentEditor ( QListWidgetItem * item );
Öffnet den Editior zum Editieren des Elements item.
int row ( const QListWidgetItem * item ) const;
Gibt die Zeile für das Element item zurück.
QList selectedItems () const;
Gibt alle ausgewählten Elemente aus der Liste zurück.
void setCurrentItem ( QListWidgetItem * item );
Setzt das Element item auf das aktuell ausgewählte Element.
void setCurrentRow ( int row );
Setzt als aktuell ausgewähltes Element die Zeile row.
void setItemWidget ( QListWidgetItem * item, QWidget * widget );
Setzt widget als anzuzeigendes Element in item.
void setSortingEnabled ( bool enable );
Schaltet mit true das Sortieren für die Liste ein. Mit false (Standardwert) wird diese wieder deaktiviert.
void sortItems ( Qt::SortOrder order = Qt::AscendingOrder );
Sortiert die Elemente in den Listen in der Reihenfolge order. Standardmäßig ist hierbei Qt::As-cendingOrder gesetzt (also von AAA bis ZZZ). Alternativ kann hier Qt::DescendingOrder (ZZZ bis AAA) verwendet werden.
QListWidgetItem * takeItem ( int row );
Entfernt das Element der Zeile row von der Liste und gibt dieses zurück. Gibt es kein Element in dieser Zeile, wird 0 zurückgegeben.
QRect visualItemRect ( const QListWidgetItem * item ) const;
Gibt den rechteckigen Bereich für das Element item zurück.
Tabelle 4.95
Methoden der Klasse QListWidget (Forts.)
Jetzt zu den möglichen Slots der Klasse QListWidget, womit Sie eine Verbindung aufbauen können:
280
1542.book Seite 281 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Slot
Beschreibung
void clear ();
Löscht alle Elemente aus der Liste.
void scrollToItem ( const QListWidgetItem * item, QAbstractItemView::ScrollHint hint = EnsureVisible );
Scrollt die Ansicht, wenn nötig und möglich, zum Element item. Mit dem Parameter hint geben Sie an, wie genau das Element, nachdem es gefunden wurde, angezeigt werden soll.
Tabelle 4.96
Öffentliche Slots der Klasse QListWidget
Auf folgende Signale können Sie beim Listen-Widget außerdem reagieren (siehe Tabelle 4.97). Signal
Beschreibung
void currentItemChanged ( QListWidgetItem * current, QListWidgetItem * previous );
Das Signal wird ausgelöst, wenn das aktuelle Element gewechselt wurde. Das Element, welches das zuvor den Fokus hatte, befindet sich in previous. Das Element mit dem aktuellen Fokus ist current.
void currentRowChanged ( int currentRow );
Das Signal wird ausgelöst, wenn das aktuelle Element gewechselt wurde. Die Zeile für das aktuelle Element befindet sich in currentRow. Gibt es dort kein Element, ist currentRow gleich –1.
void currentTextChanged ( const QString & currentText);
Das Signal wird ausgelöst, wenn das aktuelle gewechselt wurde. Der String für das aktuelle Element befindet sich in currentText. Gibt es dort kein Element, ist currentText ungültig.
void itemActivated ( QListWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element aktiviert wurde. Ein Element wird aktiviert, wenn ein Anwender ein Element einfach oder doppelt anklickt (je nach Systemkonfiguration). Außerdem wird ein Element aktiviert, wenn der Anwender eine entsprechende Taste (bspw. (¢) oder (Enter) unter Windows und X11 oder (Ctrl)+(0) unter Mac OS X) betätigt. Das aktivierte Element befindet sich in item.
void itemChanged ( QListWidgetItem * item );
Das Signal wird ausgelöst, wenn sich Daten in einem Element verändert haben. Das Element befindet sich in item.
void itemClicked ( QListWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element mit der Maus angeklickt wurde. Das angeklickte Element befindet sich in item.
Tabelle 4.97
Öffentliche Signale der Klasse QListWidget
281
4.5
1542.book Seite 282 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Signal
Beschreibung
void itemDoubleClicked ( QListWidgetItem * item );
Das Signal wird ausgelöst, wenn ein Element doppelt angeklickt wurde. Das entsprechende Element befindet sich item.
void itemEntered ( QListWidgetItem * item );
Das Signal wird ausgelöst, wenn sich der MausCursor über einem Element befindet. Das entsprechende Element befindet sich in item. Dieses Signal wird nur ausgelöst, wenn das Maus-Tracking eingeschaltet oder ein Maus-Button gedrückt und dabei auf dem Element bewegt wurde.
void itemPressed ( QListWidgetItem * item );
Das Signal wird ausgelöst wenn eine Maus-Taste auf einem Element gedrückt wurde. Das entsprechende Element befindet sich in item.
void itemSelectionChanged ();
Das Signal wird ausgelöst, wenn die Selektion von Elementen geändert wurde.
Tabelle 4.97
Öffentliche Signale der Klasse QListWidget (Forts.)
Als Nächstes zu den Daten eines Listen-Widgets, das hier mit der Klasse QListWidgetItem verwendet wird. Jedes Element in QListWidget ist also ein QListWidgetItem. Methode
Beschreibung
QListWidgetItem ( QListWidget * parent = 0, int type = Type );
Erzeugt ein leeres Listen-Element mit dem Typ type und parent als Eltern-Widget. Wurde kein Eltern-Widget angegeben, muss das Element mit QListWidget::insertItem() der Liste hinzugefügt werden.
QListWidgetItem ( const QString & text, QListWidget * parent = 0, int type = Type );
Dito, nur hat das Element zusätzlich den Text text.
QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type );
Dito, nur mit zusätzlichem Icon icon
QListWidgetItem ( const QListWidgetItem & other);
Kopierkonstruktor
virtual ~QListWidgetItem ();
Destruktor. Zerstört ein Listen-Element.
QBrush background () const;
Gibt den Pinsel zurück, der für den Hintergrund des Listen-Elements verwendet wird.
Tabelle 4.98
282
Öffentliche Methoden der Klasse QListWidgetItem
1542.book Seite 283 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
Qt::CheckState checkState () const;
Gibt den ankreuzbaren Status des Listen-Elements zurück (falls gesetzt).
virtual QListWidgetItem * clone () const;
Erzeugt eine exakte Kopie des Listen-Elements.
virtual QVariant data ( int role ) const;
Gibt die Daten des Elements mit der Funktion role zurück (siehe auch Qt::ItemDataRole). Sollten Sie eine eigene Funktion (role) benötigen, müssen Sie diese Methode reimplementieren.
Qt::ItemFlags flags () const;
Gibt die gesetzten Flags für das Element zurück (siehe Qt::ItemFlags).
QFont font () const;
Gibt die Schriftart zurück, die für den Text des angezeigten Elements verwendet wird.
QBrush foreground () const;
Gibt den Pinsel zurück, der für den Vordergrund des Listen-Elements verwendet wird.
QIcon icon () const;
Gibt das Icon zurück, welches für das Listen-Element verwendet wird.
bool isHidden () const;
Gibt true zurück, wenn des Element versteckt (nicht sichtbar) ist. Ansonsten wird false zurückgegeben.
bool isSelected () const;
Gibt true zurück, wenn das Listen-Element ausgewählt wurde.
QListWidget * listWidget () const;
Gibt das Listen-Widget zurück, welches das Element beinhaltet.
virtual void read ( QDataStream & in );
Liest vom Stream in.
void setBackground ( const QBrush & brush );
Setzt den Pinsel für den Hintergrund des ListenElements auf brush.
void setCheckState ( Qt::CheckState state);
Setzt den ankreuzbaren Status für das Listen-Element auf state.
virtual void setData ( int role, const QVariant & value );
Setzt die Daten mit der übergebenen Funktion role (siehe Qt::ItemDataRole) auf den Wert value. Sofern Sie eine andere Funktion (role) benötigen, sollten Sie diese Methode reimplementieren.
void setFlags ( Qt::ItemFlags flags );
Setzt die Flags für das Listen-Element auf flags (siehe Qt::ItemFlags).
void setFont ( const QFont & font ) ;
Setzt die Schriftart für den Text des Listen-Elements auf font.
Tabelle 4.98
Öffentliche Methoden der Klasse QListWidgetItem (Forts.)
283
4.5
1542.book Seite 284 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Methode
Beschreibung
void setForeground ( const QBrush & brush );
Setzt den Pinsel für den Vordergrund des ListenElements auf brush.
void setHidden ( bool hide );
Mit true verstecken Sie das Listen-Element. Mit false wird es wieder sichtbar.
void setIcon ( const QIcon & icon );
Setzt das Icon für das Listen-Element auf icon.
void setSelected ( bool select ); Mit true setzen Sie ein Listen-Element als ausgewählt. Mit false heben Sie es wieder auf. void setSizeHint ( const QSize & size );
Schlägt die Größe für das Listen-Element mit size vor. Wird die Größe nicht vorgegeben, hängt dies von der Größe der Element-Daten ab.
void setStatusTip ( const QString & statusTip );
Setzt den Status-Tipp für das Listen-Element auf statusTip.
void setText ( const QString & text );
Setzt den sichtbaren Text für das Listen-Element auf text.
void setTextAlignment ( int alignment );
Setzt die Ausrichtung des sichtbaren Texts auf alignment.
void setToolTip ( const QString & toolTip );
Setzt den Tooltip für das Listen-Element auf toolTip.
void setWhatsThis ( const QString & whatsThis );
Setzt den »Was ist das…«-Text für das Listen-Element auf whatsThis.
QSize sizeHint () const;
Gibt die vorgeschlagene Größe für das Listen-Element zurück. Wurde keine gesetzt, hängt die Größe von den Daten des Listen-Elements ab.
QString statusTip () const;
Gibt den Status-Tipp für das Listen-Element zurück.
QString text () const;
Gibt den sichtbaren Text für das Listen-Element zurück.
int textAlignment () const ;
Gibt die Ausrichtung des sichtbaren Text für das Element zurück.
QString toolTip () const;
Gibt den Text für den Tooltip des Elements zurück.
int type () const;
Gibt den Typ type, der beim Konstruktor mit übergeben wurde, zurück.
QString whatsThis () const;
Gibt den Text für »Was ist das…« zurück.
virtual void write ( QDataStream & out ) const;
Schreibt das Element in den Stream out.
Tabelle 4.98
284
Öffentliche Methoden der Klasse QListWidgetItem (Forts.)
1542.book Seite 285 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Methode
Beschreibung
virtual bool operator< ( const QListWidgetItem & other ) const;
Gibt true zurück, wenn der Text des Elements kleiner als der des Elements other ist. Ansonsten wird false zurückgegeben.
QListWidgetItem & operator= ( const QListWidgetItem & other);
Weist die Daten des Listen-Elements other dem aktuellen Element zu. type() und listWidget() werden hierei nicht mitkopiert.
Tabelle 4.98
Öffentliche Methoden der Klasse QListWidgetItem (Forts.)
Jetzt zu einem einfachen Beispiel, in dem Sie aus einem Verzeichnis von Icons ein bestimmtes Icon auswählen können. Natürlich muss es sich hierbei nicht zwangsläufig um Icons handeln. Man könnte das Beispiel auch als Mini-Vorschau für Bilder verwenden, sollte aber bedenken, dass bei einer umfangreichen Sammlung von Bildern diese komplett in den Speicher geladen würden (wovon man also abraten kann). Zunächst das Grundgerüst des Programms: 00 01 02 03 04 05
// beispiele/qlistwidget/iconselector.h #ifndef ICONSELECTOR_H #define ICONSELECTOR_H #include #include #include
06 class IconSelector : public QDialog { 07 Q_OBJECT 08 public: 09 IconSelector( const QMap &symbolMap, QWidget *parent = 0 ); 10 void done(int result); 11 private slots: 12 void curItem(QListWidgetItem * item ); 13 private: 14 QIcon String2Icon(const QString &iconName); 15 QListWidget *listWidget; 16 QPushButton *okButton; 17 QPushButton *cancelButton; 18 int id; 19 }; 20 #endif
285
4.5
1542.book Seite 286 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Jetzt die Implementierung des Codes: 00 // beispiele/qlistwidget/iconselector.cpp 01 #include 02 #include "iconselector.h" 03 IconSelector::IconSelector( 04 const QMap &iconMap, QWidget *parent) : QDialog(parent) { 05 id = –1; 06 listWidget = new QListWidget; 07 listWidget->setIconSize(QSize(25, 25)); 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22
QMapIterator Icon(iconMap); while (Icon.hasNext()) { Icon.next(); QListWidgetItem *item = new QListWidgetItem( Icon.value(), listWidget); item->setIcon(String2Icon(Icon.value())); item->setData(Qt::UserRole, Icon.key()); if( Icon.key() % 2 ) item->setBackground(QBrush(Qt::lightGray)); else item->setBackground(QBrush(Qt::yellow)); } okButton = new QPushButton(tr("OK")); okButton->setDefault(true); cancelButton = new QPushButton(tr("Beenden"));
25 26 27 28
connect( okButton, SIGNAL(clicked()), this, SLOT(accept())); connect( cancelButton, SIGNAL(clicked()), this, SLOT(reject())); connect( listWidget, SIGNAL(itemClicked ( QListWidgetItem*)), this, SLOT(curItem(QListWidgetItem*))); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(okButton); buttonLayout->addWidget(cancelButton);
29 30 31 32
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(listWidget); mainLayout->addLayout(buttonLayout); setLayout(mainLayout);
23 24
286
1542.book Seite 287 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
33 34 }
setWindowTitle(tr("Icons auswählen"));
35 void IconSelector::done(int result) { 36 id = –1; 37 if (result == QDialog::Accepted) { 38 QListWidgetItem *item = listWidget->currentItem(); 39 if (!item) 40 return; 41 id = item->data(Qt::UserRole).toInt(); 42 QString itemText = item->text(); 43 itemText.append(tr(" (ID: ")); 44 itemText.append(QString::number(id)); 45 itemText.append(tr(")")); 46 QMessageBox msgBox( QMessageBox::NoIcon, tr("Ihre Auswahl:"), itemText, QMessageBox::Ok ); 47 msgBox.setIconPixmap( (item->icon()).pixmap(22, 22)); 48 msgBox.exec(); 49 } 50 else 51 QDialog::done(result); 52 } 53 void IconSelector::curItem( QListWidgetItem *item ) { 54 if (!item) 55 return; 56 id = item->data(Qt::UserRole).toInt(); 57 QString itemText = item->text(); 58 itemText.append(tr(" (ID: ")); 59 itemText.append(QString::number(id)); 60 itemText.append(tr(")")); 61 QMessageBox msgBox( QMessageBox::NoIcon, tr("Ihre Auswahl:"), itemText, QMessageBox::Ok ); 62 msgBox.setIconPixmap((item->icon()).pixmap(22, 22)); 63 msgBox.exec(); 64 } 65 QIcon IconSelector::String2Icon(const QString &iconName){
287
4.5
1542.book Seite 288 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
66
67 68 }
QString fileName = QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/") + iconName.toLower(); return QIcon(fileName);
Nun noch das Hauptprogramm: 00 01 02 03 04 05 06
// beispiele/qlistwidget/main.cpp #include #include #include #include #include #include "iconselector.h"
07 int main(int argc, char *argv[]) { 08 QApplication app(argc, argv); 09 QMap iconMap; 10 QDir dir(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images") ); 11 QStringList filters; 12 filters addWidget(cancelButton);
16 17 18 19 20 21 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(treeWidget); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setWindowTitle(tr("Icons auswählen"));
22 void IconSelector::addItems( const QString &name, const QStringList& iconMap ) { 23 QTreeWidgetItem *root1 = new QTreeWidgetItem(treeWidget); 24 root1->setText(0, name); 25 root1->setIcon(0, folderIcon ); 26 QFont font; 27 font.setBold(true); 28 root1->setFont(0, font); 29 QList items;
299
4.5
1542.book Seite 300 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
30 31 32 33
34
35
36 37 38 39 40 41 }
for (int i = 0; i < iconMap.size(); ++i) { QTreeWidgetItem *tItem=new QTreeWidgetItem(root1); tItem->setText( 0, iconMap.at(i).toLocal8Bit().constData()); tItem->setIcon( 0, String2Icon( iconMap.at(i).toLocal8Bit().constData() ) ); tItem->setText( 1, String2Date( iconMap.at(i).toLocal8Bit().constData() ) ); tItem->setText( 2, String2Size( iconMap.at(i).toLocal8Bit().constData() ) ); tItem->setTextAlignment ( 1, Qt::AlignCenter ); tItem->setTextAlignment ( 2, Qt::AlignCenter ); items.append( tItem ); } treeWidget->addTopLevelItems( items );
42 void IconSelector::curItem( QTreeWidgetItem *item, int col ) { 43 if (!item) 44 return; 45 QString itemText = item->text(0); 46 QMessageBox msgBox( QMessageBox::NoIcon, tr("Ihre Auswahl:"), itemText, QMessageBox::Ok ); 47 msgBox.setIconPixmap((item->icon(0)).pixmap(22, 22)); 48 msgBox.exec(); 49 } 50 QString IconSelector::String2Date( const QString &iconName) { 51 QString fileName = QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/")+ iconName.toLower(); 52 QFileInfo file(fileName); 53 QDateTime dt(file.lastRead()); 54 return dt.toString("dd.MM.yyyy hh:mm:ss"); 55 }
300
1542.book Seite 301 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
56 QString IconSelector::String2Size( const QString &iconName) { 57 QString fileName = QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/")+ iconName.toLower(); 58 QFileInfo file(fileName); 59 return QString::number(fileName.size()); 60 } 61 QIcon IconSelector::String2Icon(const QString &iconName){ 62 QString fileName = QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/images/")+ iconName.toLower(); 63 return QIcon(fileName); 64 }
Jetzt noch das Hauptprogramm: 00 01 02 03 04 05 06
// beispiele/qtreewidget/main.cpp #include #include #include #include #include #include "iconselector.h"
07 int main(int argc, char *argv[]) { 08 QApplication app(argc, argv); 09 QStringList filters; 10 filters setSorting(QDir::DirsFirst | QDir::Name); 07 reversed = false; 08 treeView = new QTreeView; 09 treeView->setModel(model); 10 treeView->header()->setClickable(true);
304
1542.book Seite 305 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
11 12
mkdirButton=new QPushButton(tr("&Neues Verzeichnis")); removeButton = new QPushButton(tr("&Löschen"));
13
connect( mkdirButton, SIGNAL(clicked()), this, SLOT(makeDirectory())); connect( removeButton, SIGNAL(clicked()), this, SLOT(removeDirectory())); connect( treeView->header(), SIGNAL(sectionClicked ( int )), this, SLOT(sortDirectory()));
14 15
16 17 18 19
QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(mkdirButton); buttonLayout->addStretch(); buttonLayout->addWidget(removeButton);
20 21 22 23 24 25 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(treeView); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setWindowTitle(tr("QDirModel – Demo"));
26 void DirectoryDialog::makeDirectory() { 27 QModelIndex index = treeView->currentIndex(); 28 if (index.isValid()) { 29 QString dirName = QInputDialog::getText( this,tr("Verzeichnis erzeugen"), tr("Verzeichnis-Name")); 30 if (!dirName.isEmpty()) { 31 if (!model->mkdir(index, dirName).isValid()) 32 QMessageBox::warning( this, tr("Verzeichnis erzeugen..."), tr("Fehler beim Erzeugen")); 33 } 34 } 35 } 36 void DirectoryDialog::removeDirectory() { 37 QModelIndex index = treeView->currentIndex(); 38 if ( index.isValid()) { 39 if (model->fileInfo(index).isDir()) { 40 if(! model->rmdir(index) )
305
4.5
1542.book Seite 306 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
41
QMessageBox::warning( this, tr("Fehler beim Löschen"), tr("Das Verzeichnis %1 " "konnte nicht gelöscht werden") .arg(model->fileName(index)));
42 43 44 45
46 47 48 }
} else { if( !model->remove(index) ) QMessageBox::warning( this, tr("Fehler beim Löschen"), tr("Die Datei %1 konnte nicht" " gelöscht werden") .arg(model->fileName(index))); } }
49 void DirectoryDialog::sortDirectory() { 50 reversed = !reversed; 51 if( reversed ) 52 model->setSorting( QDir::Reversed ); 53 else 54 model->setSorting(QDir::DirsFirst | QDir::Name); 55 }
Zunächst zum Konstruktor (Zeile 3 bis 25) des Listings. Als Erstes erzeugen wir das Modell (Zeile 4) und schalten anschließend den Schreibschutz ab (Zeile 5). Das Setzen von false bei der Methode setReadOnly() ermöglicht es, das Dateisystem zu beschreiben (Verzeichnisse und Dateien umbenennen, kopieren und löschen). Eine Zeile später (Zeile 6) verwenden wir eine eigene Sortierung der Dateien und Verzeichnisse (im Beispiel werden zuerst die Verzeichnisse angezeigt, und es wird nach Namen (A–Z) sortiert). Natürlich gibt es weitere Methoden, um die Attribute von QDirModel zu verändern. Hierzu sei wieder auf die Dokumentation verwiesen. In Zeile 8 erzeugen wir ein neues QTreeView-Objekt, das die Daten unseres Modells anzeigen soll. Das Modell für die Anzeige setzen wir in Zeile 9 mit der Methode setModel(). In Zeile 10 setzen wir die Headerzeile als anklickbar. Anschließend erzeugen wir in den Zeilen 11 und 12 zwei Buttons, wozu wir in Zeile 13 und 14 jeweils eine Signal-Slot-Verbindung einrichten. Ebenfalls eine Signal-Slot-Verbindung wird in Zeile 15 eingerichtet, wo auf das Anklicken des Headers reagiert wird. Der restliche Code des Konstruktors entspricht wieder dem typischen Erstellen eines Layouts.
306
1542.book Seite 307 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Weiter mit der Beschreibung des Slots makeDirectory() (Zeile 26 bis 35), der ausgeführt wird, wenn der Anwender den Button »Neues Verzeichnis« angeklickt hat. Hierbei kommen wir zum ersten Mal mit QModelIndex in Berührung. Mithilfe dieser Klasse kann ein Modell (hier QDirModel) die Daten zuordnen und somit eine View-Klasse (hier QTreeView) zur Ansicht versorgen. Einfach gesagt, hat somit jedes Datenelement in einem Modell einen Index, der durch ein Objekt der Klasse QModelIndex dargestellt wird. Die Hauptkomponenten eines solchen Index bestehen aus einer Zeile, einer Spalte und einem Zeiger auf das entsprechende Modell. Natürlich fällt bei einem eindimensionalen Listen-Modell die Spalte weg bzw. hat den Wert 0. Dies aber erst mal so am Rande. Um jetzt an den aktuellen Modellindex des aktuellen Elements im Verzeichnisbaum zu gelangen, wird die Methode currentIndex() verwendet. Konnte der Modellindex für das aktuelle Element ermittelt werden, gibt die Methode QModelIndex::isValid() den Wert true zurück. Anschließend kann ein Name für das neu zu erzeugende Verzeichnis vergeben werden (Zeile 29). Dieses Verzeichnis erzeugen wir mit der Zeile 31 und der Methode QDirModel::mkdir(). Dafür benötigen wir als Parameter den Modellindex für das Eltern-Modell-Element und den neuen Namen des Verzeichnisses. Auch hierbei gibt die Methode isValid() true zurück, wenn alles glatt verlaufen ist und das neue Verzeichnis erfolgreich angelegt werden konnte. Der Slot removeDirectory() bietet im Grunde nicht mehr als der eben beschriebene Slot createDirectory(). Der Unterschied besteht im Grunde nur darin, dass es sich im einen Fall um ein Verzeichnis, das mit der Methode rmdir(), und im anderen Fall um eine Datei, die mit der Methode remove() gelöscht wird, handelt. Um Informationen vom Typ QFileInfo zu erhalten, benötigen wir auch hier wieder den Index. In Zeile 39 überprüfen wir bspw., ob das aktuelle Element mit dem Modellindex index ein Verzeichnis ist. Auch zum Löschen müssen wir diesen Modellindex verwenden. Der letzte Slot sortDirectory() wird aktiviert, wenn Sie mit dem Mauszeiger auf den Header klicken. Hierbei schalten wir zunächst immer den Booleschen Wert reversed um und sortieren nach diesem Wert dann die Anzeige der Verzeichnisse und Dateien neu. Im Beispiel wurde hier einfach mit QDir::Reversed die Sortierung auf den Kopf gestellt (Z–A). Bei erneutem Anklicken des Headers wird dies wieder rückgängig gemacht. Jetzt benötigen wir noch eine Hauptfunktion: 00 // beispiele/qdirmodel/directoryDialog.h 01 #include 02 #include "directoryDialog.h"
307
4.5
1542.book Seite 308 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 DirectoryDialog directoryDialog; 06 directoryDialog.show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.86
QDirModel bei der Ausführung
Wen es stört, dass QDirModel nicht auf Mausklicks reagiert, der kann dies gerne nachrüsten. Wollen Sie bspw. eine Datei auf Doppelklick auswählen, so lässt sich dies ohne Probleme über eine Signal-Slot-Verbindung wie folgt einrichten: // beispiele/qdirmodel/directoryDialog.cpp ... connect( treeView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(getFile(const QModelIndex&)) ); ... ... // der Slot getFile() void DirectoryDialog::getFile(const QModelIndex& index) { if ( index.isValid()) { // Verzeichnisse interessieren uns nicht. if (!model->fileInfo(index).isDir()) { QMessageBox::information( this, tr("Ihre Dateiauswahl"), tr("Sie haben die Datei %1 ausgewählt") .arg(model->fileName(index)));
308
1542.book Seite 309 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
} } }
Den Slot müssen Sie natürlich auch in der Headerdatei directoryDialog.h definieren. Klicken Sie nun bei der Verzeichnisauswahl doppelt auf eine Datei, erhalten Sie eine Nachrichtenbox mit der entsprechenden Datei, die Sie ausgewählt haben. Weitere Signale, um u. a. auf Mausereignisse zu reagieren, finden Sie in der Klasse QAbstractItemView. Mehrere Dateien auf einmal auswählen Wollen Sie zudem mehrere Dateien auf einmal auswählen, können Sie die Methode QAbstractItemView::setSelectionMode() mit entsprechendem Flag verwenden.
QDirModel mit anderen Views verwenden Erinnern Sie sich an das Model-Item-View-Prinzip? Hierbei werden die Daten von der Ansicht getrennt, und das Modell dient nur als Vermittler. Wenn dies zutrifft, müssten wir auch die anderen vordefinierten Views von Qt verwenden können. In der Tat: Wenn Sie im Beispiel zuvor die Klasse QTreeView gegen QTableView austauschen und alles, was mit den Headern zu tun hat, entfernen, sieht unser Beispiel bei der Ausführung wie folgt aus:
Abbildung 4.87
QDirModel mit QTabelView als Ansicht
Selbiges funktioniert natürlich auch zur Ansicht der Daten mit QListView, auch wenn dies in diesem Beispiel wenig sinnvoll ist. QStringListModel – Stringlisten-Modell Für einfache Listen auf Textbasis ist das Stringlisten-Modell mit der Klasse QStringListModel hervorragend geeignet. Bei diesem Modell arbeiten Sie mit
einer Stringliste, die in einer Spalte dargestellt wird. Somit entspricht jeder Eintrag in der Stringliste einer Zeile im Modell.
309
4.5
1542.book Seite 310 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
Auch hierzu ein einfaches Beispiel, das nur Dateien aus einem bestimmten Verzeichnis in einer Stringliste speichert und über das Modell QStringListModel mit der Klasse QListView anzeigt. Zunächst das Grundgerüst: 00 01 02 03 04
// beispiele/qstringlistmodel/stringlistdialog.h #ifndef StringListDialog_H #define StringListDialog_H #include #include
05 06 07 08 09 10 11 12 13 14 15
class StringListDialog : public QDialog { Q_OBJECT public: StringListDialog(QWidget *parent = 0); private slots: void getFile(const QModelIndex& index); private: QListView listView; QStringListModel model; }; #endif
Jetzt die Implementierung des Codes: 00 // beispiele/qstringlistmodel/stringlistdialog.cpp 01 #include 02 #include "stringlistdialog.h" 03 StringListDialog::StringListDialog(QWidget *parent) : QDialog(parent) { 04 QDir dir(QObject::tr("../")); 05 QStringList dirList = dir.entryList(QDir::Files); 06 model.setStringList(dirList); 07 listView.setModel(&model); 08
connect( &listView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(getFile(const QModelIndex&)));
09 10 11 12 13 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(&listView); setLayout(mainLayout); setWindowTitle(tr("QStringList – Demo"));
14 void StringListDialog::getFile(const QModelIndex& index){
310
1542.book Seite 311 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
15 16
17 18 }
if ( index.isValid()) { QMessageBox::information( this, tr("Ihre Dateiauswahl"), tr("Sie haben die Datei %1 ausgewählt") .arg(model.data(index, 0).toString())); }
Bei diesem Modell haben Sie Schreibzugriff auf die Strings in der Liste. Wenn Sie bspw. einen String doppelt anklicken (z.T. abhängig vom System), erscheint der Editor. Die Änderungen sind allerdings zunächst nur visueller Natur. Wenn Sie die Änderungen auch verwenden wollen, können Sie diese Stringliste mit der Methode QStringListModel::stringList() einlesen. Nun noch eine Hauptfunktion: 00 // beispiele/qstringlistmodel/main.cpp 01 #include 02 #include "stringlistdialog.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 StringListDialog stringListDialog; 06 stringListDialog.show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.88
Das Model QStringListModel und die Ansicht QListView
QSortFilterProxyModel – Sortieren und Filtern von Daten Um die Einträge unserer Ansicht nochmals zu sortieren und/oder auszufiltern, können Sie die Modell-Klasse QSortFilterProxyModel verwenden. Auch hierzu ein recht einfaches Beispiel. Wir verwenden nochmals einen Teil des Listings, womit wir bei QListWidget Icons aus einem Verzeichnis ausgewählt haben. Hier-
311
4.5
1542.book Seite 312 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
bei bauen wir eine Textzeile mit ein, womit Sie die Anzeige der Liste ausfiltern können. Dies soll Ihnen ermöglichen, als Filter einen regulären Ausdruck, ein Wildcard-Muster oder einen festen String zu verwenden. Damit entscheiden Sie, was QListView anzeigt und was nicht. Hierzu das Grundgerüst: 00 01 02 03 04
// beispiele/qsortfilterproxymodel/sortfilterproxy.h #ifndef SortFilterProxyDialog_H #define SortFilterProxyDialog_H #include #include
05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22
class SortFilterProxyDialog : public QDialog { Q_OBJECT public: SortFilterProxyDialog(QWidget *parent = 0); private slots: void getFile(const QModelIndex& index); void useFilter(); private: QListView listView; QStringListModel stringModel; QSortFilterProxyModel proxyModel; QLabel *label1, *label2; QLineEdit *line; QRadioButton* radio01; QRadioButton* radio02; QRadioButton* radio03; }; #endif
Die Implementierung des Codes: 00 // beispiele/qsortfilterproxymodel/sortfilterproxy.cpp 01 #include 02 #include "sortfilterproxy.h" 03 SortFilterProxyDialog::SortFilterProxyDialog( QWidget *parent) : QDialog(parent) { 04 QDir dir(QObject::tr("../images/")); 05 QStringList dirList = dir.entryList(QDir::Files); 06 stringModel.setStringList(dirList); 07 08
proxyModel.setSourceModel(&stringModel); proxyModel.setFilterKeyColumn(0);
09
listView.setModel(&proxyModel);
312
1542.book Seite 313 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
10 11 12 13
listView.setEditTriggers( QAbstractItemView::NoEditTriggers ); label1 = new QLabel(tr("&Filter:")); line = new QLineEdit; label1->setBuddy(line);
14 15 16 17 18
label2 = new QLabel(tr("Muster Syntax:")); radio01 = new QRadioButton("Regulärere Ausdruck"); radio02 = new QRadioButton("Wildcard"); radio03 = new QRadioButton("Feste Zeichenkette"); radio01->setChecked(true);
19
connect( &listView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(getFile(const QModelIndex&))); connect( line, SIGNAL(textChanged(const QString &)), this, SLOT(useFilter())); connect( radio01, SIGNAL( toggled(bool) ), this, SLOT(useFilter()) ); connect( radio02, SIGNAL( toggled(bool) ), this, SLOT(useFilter()) ); connect( radio03, SIGNAL( toggled(bool) ), this, SLOT(useFilter()) );
20 21 22 23
24 25 26 27 28 29 30 31 32 33 34 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(&listView); mainLayout->addWidget(label1); mainLayout->addWidget(line); mainLayout->addWidget(label2); mainLayout->addWidget(radio01); mainLayout->addWidget(radio02); mainLayout->addWidget(radio03); setLayout(mainLayout); setWindowTitle(tr("QSortFilterProxyModel – Demo"));
35 void SortFilterProxyDialog::getFile( const QModelIndex& index) { 36 if ( index.isValid()) { 37 QMessageBox::information( this, tr("Ihre Dateiauswahl"), tr("Sie haben die Datei %1 ausgewählt") .arg(stringModel.data(index, 0).toString())); 37 } 38 }
313
4.5
1542.book Seite 314 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
39 void SortFilterProxyDialog::useFilter() { 40 QRegExp::PatternSyntax pSyntax = QRegExp::PatternSyntax(QRegExp::RegExp); 41 if( radio01->isChecked()) 42 pSyntax = QRegExp::PatternSyntax(QRegExp::RegExp); 43 else if( radio02->isChecked()) 44 pSyntax =QRegExp::PatternSyntax(QRegExp::Wildcard); 45 else if( radio03->isChecked()) 46 pSyntax = QRegExp::PatternSyntax( QRegExp::FixedString ); 47 QRegExp regExp( line->text(), Qt::CaseInsensitive, pSyntax); 48 proxyModel.setFilterRegExp(regExp); 49 }
Zunächst erstellen wir das Modell für die Stringliste (Zeile 5 und 6). Dieses Modell übergeben wir mitsamt seiner Daten eine Zeile später mit der Methode setSourceModel() an eine QSortFilterProxyModel-Instanz. In Zeile 8 teilen wir dem Proxy-Modell mit, auf welche Spalte es die Filterung anwenden soll (im Beispiel Spalte 0). Anschließend bekommt QListView (Zeile 9) dieses Modell. Entscheidend ist in diesem Listing der Slot useFilter() (Zeile 39 bis 49), der immer dann aufgerufen wird, wenn der Anwender eine andere Syntax für das Muster wählt (Radio-Button) oder den Text in der Zeile ändert. So wie der RadioButton gesetzt ist, wird auch der reguläre Ausdruck mit pSyntax gesetzt. Anschließend legen wir ein neues QRegExp-Element mit dem Text in der Zeile und der neuen Syntax des Musters an (Zeile 47). Mit setFilterRegExp() (Zeile 48) wird der Filter aktiviert und die Ansicht (QListView) entsprechend angepasst. Jetzt noch ein Hauptprogramm: 00 // beispiele/qsortfilterproxymodel/main.cpp 01 #include 02 #include "sortfilterproxy.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 SortFilterProxyDialog directoryDialog; 06 directoryDialog.show(); 07 return app.exec(); 08 }
314
1542.book Seite 315 Montag, 4. Januar 2010 1:02 13
Qt-Widgets
Das Programm bei der Ausführung:
Abbildung 4.89
Regulärer Ausdruck als Filter in QSortFilterProxyModel
Eigene Proxy-Modelle Es ist auch möglich, eigene Proxy-Modelle zu entwerfen. Hierbei muss von der Klasse QSortFilterProxyModel abgeleitet, und die beiden virtuellen Klassen mapFromSource() und mapToSource() müssen reimplementiert werden. Ein entsprechendes
Beispiel finden Sie in den mitgelieferten Demos auf der Buch-DVD.
Eigene Modelle und benutzerdefinierte Delegates Neben der Möglichkeit, die vordefinierten Modelle von Qt zu verwenden, können Sie natürlich auch benutzerdefinierte Modelle erstellen. Dies kann bspw. erforderlich sein, wenn die Daten sich mit den vordefinierten Modellen nicht optimal verwenden lassen. Sie können praktisch für die Daten optimierte Modelle erstellen. Ebenso können Sie für die einzelnen, mit den Standard-Delegates dargestellten Elementen in der Präsentation eigene Delegates entwerfen (wobei in der Praxis meist die Standard-Delegates ausreichen). Leider steht mir als Autor keine unbegrenzte Anzahl von Seiten zur Verfügung, so dass ich darauf nicht mehr näher eingehen kann. Doch sei angemerkt, dass Sie mit den bisherigen Views, Modellen und Delegates sehr weit kommen und in der Praxis nicht so schnell an irgendwelche Grenzen stoßen. Trotzdem finden Sie sehr gute Beispiele in den mitgelieferten Item-View-Demos der Buch-DVD.
315
4.5
1542.book Seite 316 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
4.6
Online-Hilfen
Dem Anwender Hilfe zum Programm mit an die Hand zu geben, ist das A und O einer guten Anwendung. Viele Programmierer vernachlässigen dies gerne. Häufig halten Entwickler ihre Arbeit für selbsterklärend, was aber leider nicht immer der Fall ist, wie ich aus Erfahrung sagen muss. Oft ist keine ganze Dokumentation erforderlich, kleine Tipps und Direkthilfen genügen.
4.6.1
Statuszeilentipp
Den Tipp in der Statuszeile werden Sie in Kapitel 5, »Qt-Hauptfenster«, noch häufiger verwenden. Oft hat es den Anschein, dass ein solcher Statuszeilentipp nur den Menü-Elementen und Werkzeugleisten-Buttons vorbehalten ist (siehe Abbildung 4.90). Da diese Methode aber auch mit QWidget::setStatusTip() vertreten ist, können Sie praktisch jedem Widget einen Status-Tipp hinzufügen (sofern dies sinnvoll ist).
Abbildung 4.90
Tipp in der Statuszeile anzeigen
Dies nur nebenbei – die Statuszeile wird im nächsten Kapitel ausführlicher behandelt bzw. beschrieben.
4.6.2
Tooltips
Tooltips können Sie ebenfalls bei jedem beliebigen Widget verwenden (QWidget::setToolTip()), auch wenn sie in der Praxis vorwiegend bei Buttons mit einem Icon verwendet werden. Ein solcher Tipp erscheint, wenn Sie mit einem Mauszeiger eine bestimmte Zeit lang über einem Widget verweilen und ihn zuvor mit der Methode setToolTip() gesetzt haben. Bspw. folgende Zeilen: edit1 = new QLineEdit; edit1->setToolTip(tr("Hier bitte einen Text eingeben"));
316
1542.book Seite 317 Montag, 4. Januar 2010 1:02 13
Online-Hilfen
Verweilt man jetzt mit dem Mauszeiger auf dem Widget QLineEdit, erscheint folgender Tooltip:
Abbildung 4.91
Tooltip im Eingabefeld
Natürlich lässt sich ein Tooltipp auch für die Buttons verwenden. Beispiel: but1 = new QPushButton(tr("Auswerten")); but1->setToolTip(tr("Den eingegebenen Text auswerten"));
Abbildung 4.92
Ein Tooltip für einen Button
Man sollte es allerdings nicht übertreiben mit den Tooltips und sie sinnvoll einsetzen. Wie bereits erwähnt, werden Tooltips am häufigsten mit Buttons oder Widgets verwendet, die nur grafische Elemente anzeigen. Dabei ist oft nicht gleich ersichtlich, was passiert, wenn man auf einen Button mit dieser Grafik klickt.
4.6.3
Direkthilfe
Wenn Sie mehr als einen Tooltipp anzeigen wollen, und vor allem ein wenig übersichtlicher, bietet sich der typische »Was ist das…?«-Dialog an. Diese Direkthilfe kann verwendet werden, wenn sich das Fenster in einem Direkthilfe-Modus befindet. Um in diesen Modus zu gelangen, muss man entweder in der Titelleiste auf das Fragezeichen klicken oder die Tastenkombination (ª)+(F1) tippen. Klicken Sie auf das Fragezeichen, ändert sich der Cursor über dem entsprechenden Widget, sofern Letzteres eine Direkthilfe beinhaltet (siehe Abbildung 4.93). Einrichten können Sie eine solche Direkthilfe für jedes Widget mit der Methode QWidget::setWhatsThis(). Bspw.: edit1 = new QLineEdit; edit1->setWhatsThis(tr(""
317
4.6
1542.book Seite 318 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
" Hier den Text eingegeben und entsprechenden" " Button betätigen:" "
- Auswerten – Wertet die Eingabe aus
" "- Abbrechen – Beenden den Dialog
") );
Abbildung 4.93
Das Eingabefeld enthält eine Direkthilfe
Klicken Sie jetzt diese Direkthilfe an oder tippen Sie eine entsprechende Tastenkombination, bekommen Sie die Hilfe wie folgt angezeigt:
Abbildung 4.94
Eine mit HTML formatierte Direkthilfe
An diesem Beispiel können Sie erkennen, dass sich die Direkthilfe auch mit HTML formatieren lässt. Natürlich nur mit den von Qt unterstützten Tags (siehe hierzu auch in der Dokumentation von »Richtext« den Abschnitt »Supported HTML Subset«). In einem Hauptfenster wird diese Direkthilfe gewöhnlich über das Menü »Hilfe« implementiert. Dazu bietet die Klasse QWhatsThis einige interessante statische Funktionen wie bspw. QWhatsThis::createAction() an.
4.6.4
Einfache Dokumentation mit QTextBrowser
Sollten Sie eine etwas umfangreichere Anleitung oder Dokumentation für Ihre Anwendung erstellen wollen, würde sich hierzu die von QTextEdit abgeleitete Klasse QTextBrowser hervorragend eignen. Die Klasse QTextBrowser liefert einen Rich-Text-Browser mit einer Hypertext-Navigation. QTextBrowser ist im Grunde nur eine Erweiterung der QTextEdit-Klasse im Read-Only-Modus. Außerdem findet man in dieser Klasse einige Navigations-Funktionalitäten, mit deren Hilfe der Anwender Links von HTML-Seiten folgen kann.
318
1542.book Seite 319 Montag, 4. Januar 2010 1:02 13
Online-Hilfen
Richtext Auch hier sei ein Blick in die Dokumentation von Richtext empfohlen (Abschnitt »Supported HTML Subset«, in dem die von Qt unterstützten HTML-Tags aufgelistet werden).
Eine solche Hilfe ist eigentlich recht einfach und schnell zusammengebaut, wie das folgende Beispiel mit den nötigsten Funktionen zeigen soll. Hier das Grundgerüst: 00 01 02 03
// beispiele/qtextbrowser/myHelpWidget.h #ifndef MYHELPWIDGET_H #define MYHELPWIDGET_H #include
04 05 06 07 08 09 10 11 12
class MyHelpWidget : public QWidget { Q_OBJECT public: MyHelpWidget(QWidget *parent = 0); private: QTextBrowser* browser; QPushButton* home, *back, *forward; }; #endif
Nun die dazugehörige Implementierung des Codes: 00 // beispiele/qtextbrowser/myHelpWidget.cpp 01 #include "myHelpWidget.h" 02 #include 03 MyHelpWidget::MyHelpWidget( QWidget *parent): QWidget(parent) { 04 browser = new QTextBrowser; 05 home = new QPushButton(tr("Startseite")); 06 back = new QPushButton(tr("Zurück")); 07 forward = new QPushButton(tr("Vorwärts")); 08 09 10 11
QHBoxLayout* butLayout = new QHBoxLayout; butLayout->addWidget(home); butLayout->addWidget(back); butLayout->addWidget(forward);
12 13 14
QVBoxLayout* mainLayout = new QVBoxLayout; mainLayout->addWidget(browser); mainLayout->addLayout(butLayout);
319
4.6
1542.book Seite 320 Montag, 4. Januar 2010 1:02 13
4
Dialoge, Layout und Qt-Widgets
15
setLayout(mainLayout);
16
connect( home, SIGNAL(clicked()), browser, SLOT(home())); connect( back, SIGNAL(clicked()), browser, SLOT(backward())); connect( forward, SIGNAL(clicked()), browser, SLOT(forward()));
17 18
19 20 21 }
browser->setSource(tr("index.htm")); setWindowTitle(tr("QTextBrowser"));
In unserem Beispiel gehen wir davon aus, dass sich die Startseite index.html im selben Verzeichnis der Anwendung befindet. Jetzt noch ein Hauptprogramm zum Testen für das Hilfe-Fenster (das Sie in der Praxis normalerweise aus seinem Menü (siehe Abschnitt 5.2.2) heraus aufrufen): 00 // beispiele/qtextbrowser/main.cpp 01 #include "myHelpWidget.h" 02 #include 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyHelpWidget* window = new MyHelpWidget; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 4.95
320
QTextBrowser als Online-Hilfe bei der Ausführung
1542.book Seite 321 Montag, 4. Januar 2010 1:02 13
Online-Hilfen
4.6.5
QAssistantClient – Qt Assistant weiterverwenden
Wenn Sie eine Online-Hilfe wie die Qt-Referenz von Trolltech verwenden wollen, können Sie dies auch für Ihre Anwendung nutzen. Sie haben dann sämtliche Features wie Textsuche, Indizierung usw. dabei. Um den Assistant auch für Ihre Anwendung zu verwenden, wird auf die Klasse QAssistantClient zurückgegriffen, die sich in der Bibliothek assistant befindet. Somit müssen Sie hier ebenfalls einen entsprechenden Eintrag in der Projekt-Datei (*.pro) vornehmen: CONFIG += assistant
Mehr dazu entnehmen Sie bitte der Dokumentation zur Klasse QAssistantClient.
321
4.6
1542.book Seite 322 Montag, 4. Januar 2010 1:02 13
1542.book Seite 323 Montag, 4. Januar 2010 1:02 13
In den bisherigen Kapiteln mussten Sie sich vorwiegend mit Dialogen zufriedengeben. In diesem Kapitel soll das Hauptfenster von Qt (die Klasse QMainWindow) näher betrachtet werden.
5
Qt-Hauptfenster
5.1
Aufbau eines Hauptfensters
Als Grundlage für ein Qt-Hauptfenster dient die Klasse QMainWindow. An dieser Klasse lassen sich dann ohne großen Aufwand weitere Widgets wie ein Menü, eine Statusleiste, eine Toolbar (dt. Werkzeugleiste) oder ein Dock-Widget anbringen (siehe Abbildung 5.1). Es ist natürlich weiterhin möglich, ohne diese Widgets ein Hauptfenster anzuzeigen und zu verwenden, allerdings würde dies dann eher einem Dialog entsprechen. Die Hauptrolle des Hauptfensters spielt das ZentralWidget. Das Zentral-Widget ist einfach das QMainWindow-Widget ohne Menü, Statusleiste, Toolbar und Dock-Widget, das angezeigt würde, wenn man eben nur ein Objekt der Klasse QMainWindow erzeugt.
Abbildung 5.1
Grundlegender Aufbau eines Hauptfensters
323
1542.book Seite 324 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
5.2
Die Klasse QMainWindow
Wie zuvor erwähnt, ist die Klasse QMainWindow für das Hauptanwendungsfenster in Qt verantwortlich. Das Prinzip von QMainWindow entspricht natürlich wieder dem üblichen GUI-Prinzip, indem man von dieser Klasse eine eigene ableitet. Im Grunde ist eigentlich alles so, wie Sie es vom vorigen Kapitel mit dem Ableiten von QWidget bzw. QDialog bereits kennen. Hierzu wird es wohl am einfachsten sein, ein minimalistisches Beispiel zu zeigen. Zunächst ein Grundgerüst für das Hauptfenster: 00 01 02 03 04 05
// beispiele/qmainwindow1/MyWindow.h #ifndef MyWindow_H #define MyWindow_H #include #include #include
06 class MyWindow : public QMainWindow { 07 Q_OBJECT 08 public: 09 MyWindow( QMainWindow *parent = 0, Qt::WindowFlags flags = 0 ); 10 QTextEdit* editor; 11 }; 12 #endif
In Zeile 6 wird unsere Klasse MyWindow von QMainWindow abgeleitet. Damit hierbei auch außerhalb von MyWindow auf die Methoden und Eigenschaften von QMainWindow zugegriffen werden kann, ist die Zugriffskontrolle public (über die C++-Grundlagen muss ich hier wohl niemanden aufklären). Im Beispiel wurde als Zentral-Widget die Klasse QTextEdit (Zeile 10) verwendet. Nun zur Implementierung unseres minimalistischen Codes: 00 // beispiele/qmainwindow1/MyWindow.cpp 01 #include "MyWindow.h" 02 #include 03 // neue Widget-Klasse vom eigentlichen Widget ableiten 04 MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags ) : QMainWindow(parent, flags) { 05 editor = new QTextEdit; 06 resize(320, 200); 07 setCentralWidget(editor); 08 setWindowTitle("QMainWindow – Demo"); 09 }
324
1542.book Seite 325 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
So weit bietet dieses Beispiel nichts Neues. Wichtig ist allerdings die Methode (Zeile 7) setCentralWidget(), womit Sie das QTextEdit-Widget zum ZentralWidget des Hauptfensters machen. QTextEdit ist also der Hauptgrund der Anwendung, wenn Sie so wollen. So benötigen Sie nur noch eine Hauptfunktion, um das Programm auszuführen: 00 // beispiele/buttondemo/main.cpp 01 #include 02 #include "MyWindow.h" 03 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 05 MyWindow* window = new MyWindow; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 5.2 Ein nacktes QMainWindow mit QTextEdit als Zentral-Widget bei der Ausführung
Auf den folgenden Seiten werden wir dieses Beispiel um einzelne Komponenten eines typischen Hauptfensters erweitern. Dabei werden Sie feststellen, dass sich mit minimalem Aufwand Beachtliches auf die Beine stellen lässt. In der folgenden Tabelle finden Sie die im Beispiel verwendeten Methoden der Klasse QMainWindow: Methode
Beschreibung
QMainWindow ( QWidget * parent = 0, Qt::WindowFlags flags = 0 );
Erzeugt eine neues Hauptfenster mit dem ElternWidget parent und den Flag(s) flags. Gängige Werte für flags siehe Tabelle 5.2.
~QMainWindow ();
Destruktor. Zerstört ein Haupfenster.
Tabelle 5.1
Einige Methoden der Klasse QMainWindow
325
5.2
1542.book Seite 326 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
void setCentralWidget ( QWidget * widget );
Tabelle 5.1
Setzt widget zum Zentral-Widget im Hauptfenster.
Einige Methoden der Klasse QMainWindow (Forts.)
Hinweis Ziel dieses Kapitel sollte es sein, Ihnen die einzelnen Komponenten eines Hauptfensters in der Theorie sowie in der Praxis zu demonstrieren. Ich habe hier nicht vor, einen kompletten Text-Editor mit allen Facetten zu programmieren.
5.2.1
Flags für QMainWindow
Bei der Erzeugung eines Hauptfensters kann mit dem zweiten Parameter ein zusätzliches Flag verwendet werden, womit einige Optionen (Rahmen, Titelleiste und Systemmenü eines Hauptfensters) gesetzt bzw. verändert werden. Gängige Werte hierzu finden Sie in der folgenden Tabelle (5.2). Sofern es sinnvoll ist, lassen sich mehrere Werte mit dem bitweisen ODER verknüpfen. Flag
Beschreibung
Qt::MSWindowsFixedSizeDialogHint
Gibt dem Fenster einen dünnen Rahmen. Die Größe lässt sich zudem nicht mehr am Rahmen größer bzw. kleiner stellen. Gewöhnlich wird dieses Flag für Dialoge mit fester Größe verwendet (MS-Windows only).
Qt::MSWindowsOwnDC
Gibt dem Fenster seinen eigenen Anzeige-Kontext (MS-Windows only).
Qt::X11BypassWindowManagerHint
Hängt den Window-Manager komplett aus. Damit erhalten Sie ein rahmenloses Fenster das nicht mehr vom Fenster-Manager (bspw. X11, KDE, usw.) verwaltet wird (X11 only).
Qt::FramelessWindowHint
Damit erzeugen Sie ein rahmenloses Fenster (ohne Titelleiste und Systemmenü). Der Anwender kann dieses Fenster weder in der Größe verändern noch verschieben. Unter X11 hängt dieses Flag vom verwendeten Fenster-Manager ab. Die meisten modernen Fenster-Manager können dieses Flag allerdings verarbeiten.
Qt::CustomizeWindowHint
Schaltet die Titelleiste und die darin befindlichen System-Funktionen (Minimieren, Maximieren etc.) aus.
Tabelle 5.2
326
Flags für QMainWindow (zweiter Parameter)
1542.book Seite 327 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Flag
Beschreibung
Qt::WindowTitleHint
Verwendet nur die Titelleiste des Fensters für den Titel. Alle anderen System-Funktionen (Minimieren, Maximieren etc.) sind nicht vorhanden.
Qt::WindowSystemMenuHint
Verwendet die Titelleiste und nur die Möglichkeit, das Fenster zu schließen. Verschieben und Größenänderungen sind hiermit auch möglich.
Qt::WindowMinimizeButtonHint
Zeigt den Minimieren-Button an.
Qt::WindowMaximizeButtonHint
Zeigt den Maximieren-Button an.
Qt::WindowMinMaxButtonsHint
Zeigt den Maximieren- und Minimieren-Button an.
Qt::WindowContextHelpButtonHint
Fügt dem Fenster einen Kontext-Hilfsbutton (meist in Form eines Fragezeichens) hinzu.
Qt::WindowShadeButtonHint
(?) Konnte nicht ermittelt werden.
Qt::WindowStaysOnTopHint
Das Fenster stellt sich vor alle anderen Fenster (on-top). Unter X11 müssen Sie außerdem das Flag Qt::X11BypassWindowManagerHint hinzufügen.
Qt::WindowType_Mask
Eine Maske, die vom Fenstertyp Teile der Flags extrahiert.
Tabelle 5.2
Flags für QMainWindow (zweiter Parameter) (Forts.)
Weitere Flags für »QMainWindow« Es gibt noch weitere Flags, wenn Sie die Dokumentation mithilfe von Qt-Assistant durchgehen. Einige davon sind allerdings veraltet und nur noch der Kompatibiliät zuliebe vorhanden.
5.2.2
Eine Menüleiste mit der Klasse QMenu und QMenuBar
Um typische Menüs für Hauptanwendungen zu verwenden, benötigt man im Grunde nur eine Menüleiste, die mit der Klasse QMenuBar dargestellt wird, und einzelne mit der Klasse QMenu realisierte Menüelemente. Der Vorgang ist mit der Klasse QMainWindow recht einfach zu lösen: QMenu *fileMenu = new QMenu(tr("&Datei"), this); menuBar()->addMenu(fileMenu);
Zunächst erzeugen Sie ein neues Menü mit der Klasse QMenu. In der nächsten Zeile hängen Sie das neue Menü an der Menüleiste von QMainWindow an. Dafür sorgt die Methode menuBar(). Falls noch keine Menüleiste im Hauptfenster existiert, erzeugt diese Methode eine neue und gibt sie zurück. Die Methode
327
5.2
1542.book Seite 328 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
addMenu() ist wieder eine Methode der Klasse QMenuBar, womit ein neues Element zur Menüleiste hinzugefügt wird. Hier wird das neue Objekt fileMenu mit dem String Datei hinzugefügt (siehe Abbildung 5.3).
Abbildung 5.3
Ein Element zur Menüleiste hinzugefügt
So werden im Grunde der Menüleiste weitere Titel hinzugefügt. Ein weiteres Element in »Bearbeiten« würde man bspw. folgendermaßen hinzufügen: QMenu *workMenu = new QMenu(tr("&Bearbeiten"), this); menuBar()->addMenu(workMenu);
Hiermit würde sich folgende Abbildung ergeben:
Abbildung 5.4
Noch ein Element zur Menüleiste hinzugefügt
Nachdem Sie eine Menüleiste mit einem Menü hinzugefügt haben, werden Sie einzelne Menüelemente einfügen wollen. Dies geschieht gewöhnlich mit der Methode addAction() der Klasse QMenu: QMenu *fileMenu = new QMenu(tr("&Datei"), this); menuBar()->addMenu(fileMenu); fileMenu->addAction( QIcon("images/page_white.png"), tr("&Neu"), this, SLOT(newFile()), QKeySequence(tr("Ctrl+N", "Datei|Neu"))); fileMenu->addAction( QIcon("images/folder_page_white.png"), tr("&Oeffnen..."), this, SLOT(openFile()), QKeySequence(tr("Ctrl+O", "Datei|Oeffnen"))); fileMenu->addAction( QIcon("images/cancel.png"), tr("Be&enden"), qApp, SLOT(quit()), QKeySequence(tr("Ctrl+E", "Datei|Beenden")));
328
1542.book Seite 329 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Durch Hinzufügen der Aktionen (der Klasse QAction) sieht die Abbildung folgendermaßen aus:
Abbildung 5.5
Einzelne Menüelemente mit Aktionen hinzugefügt
An diesem Punkt müssen Sie sich mit drei Klassen konfrontieren: der Menüleiste (QMenuBar), dem Menüelement (QMenu) und der Aktion (QAction), welche das Menü ausführen soll. Daher sollen zunächst die einzelnen Klassen etwas genauer erläutert werden. QMenuBar Die Klasse QMenuBar wird für die horizontale Menüleiste verwendet. Selten verwendet man diese Klasse beim Hauptfenster direkt. Meistens wird hierfür die Methode menuBar() von QMainWindow verwendet. Dennoch sollten wir nun auf einige Methoden und Signale der Klasse eingehen, die sich auch über menuBar()->Methode() ansprechen lassen. Methode
Beschreibung
QMenuBar ( QWidget * parent = 0 );
Erzeugt eine neue Menüleiste mit parent als Eltern-Widget.
~QMenuBar ();
Destruktor. Zerstört eine Menüleiste.
QAction * activeAction () const;
Gibt soeben markierte Aktion zurück (QActionObjekt). Ist keine Aktion aktiv, wird NULL zurückgegeben.
QAction * addAction ( const QString & text );
Diese Methode erzeugt eine neue Aktion (QAction-Objekt) mit dem String text. Die neue Aktion mit dem String wird an die Menüleiste hinzugefügt und zurückgegeben.
QAction * addAction ( const QString & text, const QObject * receiver, const char * member );
Erzeugt eine neue Aktion mit dem String text. Den Empfänger (die Klasse) der Aktion geben Sie mit receiver an. Der Empfänger reagiert immer auf das Signal triggered() und führt den Slot member aus. Die so erzeugt Aktion wird zurückgegeben.
Tabelle 5.3
Methoden der Klasse QMenuBar
329
5.2
1542.book Seite 330 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
QAction * addMenu ( QMenu * menu );
Fügt der Menüleiste ein neues Menü mit dem Text title hinzu. Zurückgegeben wird die MenüAktion.
QMenu * addMenu ( const QString & title );
Diese überladene Methode fügt ein neues Menü mit dem Text title der Menüleiste hinzu. Die Menüleiste wird dabei Eigentümer des Menüs. Zurückgegeben wird das neue Menüelement.
QMenu * addMenu ( const QIcon & icon, const QString & title );
Diese überladene Methode fügt dem Menü ein neues Menü (QMenu) mit einem Icon und dem Text title hinzu. Zurückgegeben wird das neue Menü.
QAction * addSeparator ();
Fügt eine Trennlinie als neue Aktion der Menüliste hinzu. Zurückgegeben wird die neue Aktion.
void clear ();
Entfernt alle Menü-Aktionen.
QAction * insertMenu ( QAction * before, QMenu * menu );
Fügt ein neues Menü menu vor der Aktion action ein und gibt die neue erzeugte Aktion zurück.
QAction * insertSeparator ( QAction * before );
Fügt eine Trennlinie vor der Aktion action zurück und gibt die neue Aktion zurück.
bool isDefaultUp () const;
Per Standard »poppt« ein Menü nach unten auf. Hierbei gibt diese Methode false zurück. Gibt diese Methode true zurück, fährt die Menüleiste nach oben aus.
void setActiveAction ( QAction * act );
Setzt die als aktuell markierte Aktion auf act.
void setDefaultUp ( bool );
Vewenden Sie true als Parameter, fährt das Menü statt nach unten nach oben aus. Sollte allerdings das Menü nicht auf den Bildschirm passen, wird es entsprechend angepasst. Bei false schalten Sie wieder den Standardwert.
Tabelle 5.3
Methoden der Klasse QMenuBar (Forts.)
Eigene Slots besitzt die Menüleiste nicht, dafür aber die beiden folgenden Signale: Signal
Beschreibung
void hovered ( QAction * action );
Das Signal wird ausgelöst, wenn eine Menü-Aktion markiert ist. Die entsprechende Aktion befindet sich dann im Parameter action.
Tabelle 5.4
330
Signale der Klasse QMenuBar
1542.book Seite 331 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Signal
Beschreibung
void triggered ( QAction * action );
Das Signal wird ausgelöst, wenn eine Aktion im Menü ausgewählt wurde. Die entsprechende Aktion befindet sich auch hier im Parameter action.
Tabelle 5.4
Signale der Klasse QMenuBar (Forts.)
QMenu Die Klasse QMenu ist ein Menü-Widget für eine Menüleiste, ein Kontext-Menü und andere Popup-Menüs. Ein Menü-Widget wird vom Anwender ausgewählt. Dabei kann es als Pulldown-Menü in einer Menüleiste (QMenuBar) oder in einem Kontext-Menü verwendet werden. Um ein Menü-Widget in einer Menüleiste zu verwenden, wird die Methode QMenuBar::addMenu() verwendet. Kontext-Menüs hingegen werden entweder synchron mit exec() oder asynchron mit popup() aufgerufen. Ein Kontext-Menü wird gewöhnlich geöffnet, wenn der Anwender mit der rechten Maustaste in einem Hauptfensterbereich klickt. Bspw. wurde mit folgender protectedMethode ein Popup-Fenster im Hauptfenster realisiert: void MyWindow::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = new QMenu(this); menu->addAction(act1); menu->addAction(act2); menu->addAction(act3); menu->exec(event->globalPos()); }
Für act1, act2 und act3 wurden Zeiger vom Typ QAction als Rückgabewert fileMenu->addAction(...) verwendet. Jetzt erscheint bei einem rechten Mausklick (außerhalb des Text-Editors) folgendes Popup-Menü:
Abbildung 5.6
Popup-Menü mit QMenu
Virtuelle Methode »contextMenuEvent()« Im Falle der Klasse QTextEditor sollten Sie beachten, dass es hierzu eine virtuelle Methode contextMenuEvent() gibt, die Sie in einer von QTextEdit abgeleiteten Klasse definieren müssen.
331
5.2
1542.book Seite 332 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Jetzt zu den verschiedenen Methoden der Klasse QMenu: Methode
Beschreibung
QMenu ( QWidget * parent = 0 );
Erzeugt ein neues Menü-Widget mit parent als Eltern-Widget.
QMenu ( const QString & title, QWidget * parent = 0 );
Erzeugt ein neues Menü-Widget mit parent als Eltern-Widget und dem Titel title.
~QMenu () ;
Destruktor. Zerstört ein Menü-Widget.
QAction * actionAt ( const QPoint & pt ) const;
Gibt das Element an der Position pt zurück. Existiert dort kein Element, wird 0 zurückge-geben.
QRect actionGeometry ( QAction * act ) const;
Gibt den rechteckigen Bereich der Aktion act zurück.
QAction * activeAction () const; Gibt die Aktion des aktuell markierten Menü-Wid-
gets zurück. Gibt es keine Aktion, wird 0 zurückgegeben. QAction * addAction ( const QString & text );
Erzeugt eine neue Aktion mit dem String text. Diese Methode fügt die neu erzeugte Aktion der MenüListe von Aktionen hinzu und gibt diese zurück.
QAction * addAction ( const QIcon & icon, const QString & text );
Dito, nur mit zusätzlichem Icon
QAction * addAction ( const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0 );
Erzeugt eine neue Aktion mit dem String text. Den Empfänger (die Klasse) der Aktion geben Sie mit receiver an. Der Empfänger reagiert immer auf das Signal triggered() und führt den Slot member aus. Zusätzlich kann ein Tastaturkürzel mit shortcut eingerichtet werden. Als Rückgabewert wird die erzeugte Aktion zurückgegeben.
QAction * addAction ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0 );
Dito, nur kann zusätzlich ein Icon verwendet werden.
QAction * addMenu ( QMenu * menu );
Fügt menu als ein Untermenü in das aktuelle Menü ein. Zurückgegeben wird die Aktion des Menüs.
QMenu * addMenu ( const QString & title );
Fügt dem Menü ein neues QMenu-Objekt mit dem Text title hinzu. Zurückgegeben wird das neue Menü-Widget.
Tabelle 5.5
332
Methoden der Klasse QMenu
1542.book Seite 333 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
QMenu * addMenu ( const QIcon & icon, const QString & title );
Dito, nur mit zusätzlichem Icon
QAction * addSeparator ();
Fügt der Menü-Liste eine neue Trennlinie hinzu. Zurückgegeben wird die Aktion der Trennlinie.
void clear ();
Entfernt alle Menü-Aktionen.
QAction *defaultAction () const; Gibt die aktuelle Standard-Aktion zurück. QAction * exec ();
Führt das Menü synchron aus. Zurückgegeben wird die eine getriggerte Aktion, was entweder ein Popup-Menü ist oder ein Untermenü (oder 0, wenn kein Element getriggert war).
QAction * exec ( const QPoint & p, QAction * action = 0 );
Führt das Menü ebenfalls synchron aus. Hiermit wird ein Popup-Menü mit der Aktion action an einer speziellen Position ausgeführt. Wollen Sie das Menü an der aktuellen Mausposition ausführen, reicht: exec(QCursor::pos());
Oder zum Widget ausgerichtet: exec( somewidget.mapToGlobal( QPoint(0, 0) ) ); Oder als Reaktion auf ein QMouseEvent: exec(event->globalPos()); void hideTearOffMenu ();
Versteckt, falls gesetzt, die Abrisskante vom Menü (siehe setTearOffEnabled()).
QIcon icon () const;
Gibt das Icon des Menüelements zurück.
QAction * insertMenu ( QAction * before, QMenu * menu );
Fügt das Menü menu vor der Aktion action ein und gibt die Aktion des eingefügten Menü-Widgets zurück.
QAction * insertSeparator ( QAction * before );
Fügt eine Trennlinie vor der Aktion action ein und gibt die Aktion der Trennlinie zurück.
bool isEmpty () const;
Gibt true zurück, wenn in dem Menü-Widget keine Aktion vorhanden ist. Ansonsten wird false zurückgegeben.
bool isTearOffEnabled () const;
Gibt true zurück, wenn die Abrisskante im Menü gesetzt ist. Ansonsten wird false (Standard) zurückgegeben.
bool isTearOffMenuVisible () const;
Gibt true zurück, wenn die Abrisskante im Menü sichtbar ist. Ansonsten wird false zurückgegeben.
QAction * menuAction () const;
Gibt die mit dem Menü verknüpfte Aktion zurück.
Tabelle 5.5
Methoden der Klasse QMenu (Forts.)
333
5.2
1542.book Seite 334 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
void popup ( const QPoint & p, QAction * atAction = 0 ) ;
Zeigt das Menü so an, dass atAction an der Position p angezeigt wird. Um die lokalen Koordinaten des Widgets zu erhalten, sollten Sie die Methode QWidget::mapToGlobal() verwenden.
void setActiveAction ( QAction * act );
Setzt die aktuell markierte Aktion zur aktuellen.
void setDefaultAction ( QAction * act );
Setzt act auf die Standard-Aktion.
void setIcon ( const QIcon & icon );
Setzt das Icon icon für ein QMenu-Widget.
void setTearOffEnabled ( bool ); Schaltet die Abrisskante ein. Damit kann ein kom-
plettes Menü per Klick auf die Abrisskante als Dialog angezeigt werden (siehe Abbildung 5.7 und 5.8). void setTitle ( const QString & title );
Setzt den Titel des Menüs auf title.
QString title () const;
Gibt den Titel des Menüs zurück.
Tabelle 5.5
Methoden der Klasse QMenu (Forts.)
Die in der Tabelle beschriebene Abrisskante sieht folgendermaßen aus:
Abbildung 5.7
Mit setTearOffEnabled (true) gesetzte Abrisskante
Klickt man auf diese Abrisskante, erhält man einen eigenen Dialog, der wie folgt aussieht:
Abbildung 5.8
334
Dialog aus einem QMenu mit Abrisskante erzeugt
1542.book Seite 335 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
QMenu stellt außerdem folgende Signale zur Verfügung: Signal
Beschreibung
void aboutToHide ();
Dieses Signal wird ausgelöst, bevor das Menü beim Anwählen vom Anwender ausfährt.
void aboutToShow ();
Dieses Signal wird ausgelöst, bevor das Menü, nachdem es bereits ausgefahren ist und nicht mehr benötigt wird, wieder einfährt.
void hovered ( QAction * action );
Das Signal wird ausgelöst, wenn der Anwender mit der Maus ein Menü markiert. Die entsprechende Aktion des Menü-Widgets befindet sich im Parameter action.
void triggered ( QAction * action );
Das Signal wird ausgelöst, wenn der Anwender ein MenüWidget ausgewählt hat. Die entsprechende Aktion des Menü-Widgets befindet sich im Parameter action.
Tabelle 5.6
Signale der Klasse Qmenu
Beispiel dazu Das vollständige, etwas umfangreichere Beispiel finden Sie ab Seite 372.
QAction Häufig war in den Abschnitten zuvor die Rede von Aktionen der Klasse QAction, worauf wir in diesem Abschnitt eingehen. Aktionen werden von der Klasse QAction repräsentiert und sind eine abstrakte Benutzerschnittstelle, die einem Widget hinzugefügt werden können. In vielen Anwendungen werden gewöhnlich Kommandos über Menüs, Werkzeugleisten-Buttons und Tastaturkürzel eingefügt. Dank der Klasse QAction können alle Kommandos mit demselben Weg realisiert werden. Dies bedeutet auch: Über die Klasse QAction realisierte Aktionen müssen nur einmal definiert werden und können dann sowohl für Menüs, Werkzeugleisten-Buttons als auch Tastaturkürzel verwendet werden. Solche Aktionen lassen sich entweder unabhängig vom Objekt erzeugen oder werden Konstruktion von bspw. Menüs. Die Klasse QMenu (vom Abschnitt zuvor) bspw. bietet hierzu komfortable Methoden an, solche Objekte in einem Schritt zu erzeugen und gleich dem Menü hinzuzufügen. Ein Objekt der Klasse QAction kann u. a. Icons, Texte (für das Menü), Tastaturkürzel, Texte (für die Statuszeile), einen »Was ist das?«-Text und einen Text für den Tooltip enthalten. Bei Menüs ist es außerdem möglich, eine individuelle Schriftart zu verwenden.
335
5.2
1542.book Seite 336 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Bis jetzt kennen Sie ja nur den Vorgang, dem Menü mit der Methode QMenu::addAction() eine Aktion hinzuzufügen: QMenu *fileMenu = new QMenu(tr("&Datei"), this); menuBar()->addMenu(fileMenu); // Aktion hinzufügen fileMenu->addAction( QIcon("images/page_white.png"), tr("&Neu"), this, SLOT(newFile()), QKeySequence(tr("Ctrl+N", "Datei|Neu")));
Dieser Vorgang hat allerdings den Nachteil, dass diese Aktion nicht so ohne weiteres wieder verwendet werden kann (bspw. für einen gleichwertigen Werkzeugleisten-Button). Für so einen Fall sollte man eigene Aktionen instanzieren. Bspw.: 01 QAction* underLineAct = new QAction( tr("&Unterstreichen"), this); 02 underLineAct->setCheckable(true); 03 underLineAct->setShortcut(tr("Ctrl+U")); 04 underLineAct->setStatusTip(tr("Text unterstreichen")); 05 connect( underLineAct, SIGNAL( triggered(bool) ), editor, SLOT( setFontUnderline(bool) ) );
Zunächst erzeugen Sie in Zeile 1 eine neue Aktion mit dem Textlabel »Unterstreichen«. In Zeile 2 setzen Sie die Aktion als »Ankreuzbar«. Bei einem Button bedeutet dies bspw., dass dieser »toggled« ist, und bei einem Menü ist eben ein Häkchen oder ein Kreuz dafür vorhanden. Natürlich geht es auch ohne. In Zeile drei setzen Sie mit (Ctrl)+(U) ein Tastaturkürzel, womit die Aktion ebenfalls (oder nur) aufgerufen werden kann. Hat die Anwendung eine Statusleiste, wird in Zeile 4 der Text dafür angegeben, der angezeigt wird, wenn der Anwender diese Aktion markiert. Zum Schluss wollen wir noch eine Signal-Slot-Verbindung für unsere Aktion einrichten. Das Signal triggered() wird ausgelöst, wenn der Anwender die Aktion betätigt hat. Je nachdem, welchen Wert triggered() im Parameter hat, wird beim Text-Editor editor der Slot setFontUnderline() ausgeführt. Entweder wird eben der weitere bzw. markierte Text im Editor unterstrichen oder eben nicht unterstrichen ausgegeben. Eine solche Aktion lässt sich nun bspw. folgendermaßen dem Menü »Bearbeiten« hinzufügen: QMenu *work = menuBar->addMenu(tr("&Bearbeiten")); work->addAction(underLineAct);
Manchmal ist es auch nötig, dass eine Art Radio-Button-Funktion benötigt wird, wo eben nur eine Aktion von mehreren aktiv sein darf. Für solche Zwecke ver-
336
1542.book Seite 337 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
fügt Qt über gruppierte Aktionen mit der Klasse QGroupAction. Hierzu legt man zunächst die einzelnen Aktionen vom Typ QAction an: QAction *leftAlignAct = new QAction( tr("&Left Align"), this); leftAlignAct->setCheckable(true); leftAlignAct->setShortcut(tr("Ctrl+L")); leftAlignAct->setStatusTip(tr("Text links ausrichten")); connect( leftAlignAct, SIGNAL( triggered() ), this, SLOT( leftAlignment() ) ); QAction *rightAlignAct = new QAction( tr("&Right Align"), this); rightAlignAct->setCheckable(true); rightAlignAct->setShortcut(tr("Ctrl+R")); rightAlignAct->setStatusTip(tr("Text rechts ausrichten")); connect( rightAlignAct, SIGNAL( triggered() ), this, SLOT( rightAlignment() ) ); QAction *justifyAct = new QAction(tr("&Justify"), this); justifyAct->setCheckable(true); justifyAct->setShortcut(tr("Ctrl+J")); justifyAct->setStatusTip(tr("Text bündig ausrichten")); connect( justifyAct, SIGNAL( triggered() ), this, SLOT( justifyAlignment() ) ); QAction *centerAct = new QAction(tr("&Center"), this); centerAct->setCheckable(true); centerAct->setShortcut(tr("Ctrl+E")); centerAct->setStatusTip(tr("Text zentriert ausrichten")); connect( centerAct, SIGNAL( triggered() ), this, SLOT( centerAlignment() ) );
Hier haben Sie vier verschiedene Aktionen zum Ausrichten des Textes im Texteditor. Da keine dieser Ausrichtungen gleichzeitig verwendet werden kann, benötigen Sie eine Zusammenfassung dieser vier Aktionen zu einer Gruppe. Dies lässt sich relativ einfach realisieren: QActionGroup* alignmentGroup = new QActionGroup(this); alignmentGroup->addAction(leftAlignAct); alignmentGroup->addAction(rightAlignAct); alignmentGroup->addAction(justifyAct); alignmentGroup->addAction(centerAct); leftAlignAct->setChecked(true);
337
5.2
1542.book Seite 338 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Zunächst erzeugen wir ein QActionGroup-Objekt, in das wir die vier einzelnen Aktionen reinpacken. Die Aktion »leftAlignAct« wird beim Start des Programms als Standard angekreuzt. Begehen Sie jetzt keinen Denkfehler, indem Sie versuchen, nur das QActionGroup-Objekt dem Menü hinzuzufügen. Die Klasse QActionGroup dient
lediglich dazu, einzelne Aktionen zu gruppieren. Das Visuelle müssen Sie dem Menü nach wie vor einzeln hinzufügen: QMenu *formatMenu = workMenu->addMenu(tr("&Format")); formatMenu->addAction(underLineAct); formatMenu->addSeparator(); formatMenu->addAction(leftAlignAct); formatMenu->addAction(rightAlignAct); formatMenu->addAction(justifyAct); formatMenu->addAction(centerAct); formatMenu->addSeparator();
Bei der Ausführung sieht das Ganze dann wie folgt aus:
Abbildung 5.9
Gruppierte Aktionen
Komplettes Listing Das komplette Listing zu diesem Kapitel mit den Menüs, Statusbar und den DockWidgets finden Sie am Ende des Abschnitts 5.2.6 (S. 372 ff.).
In der folgenden Tabelle sind die Methoden der Klasse QAction aufgelistet: Methode
Beschreibung
QAction ( QObject * parent );
Erzeugt eine neue Aktion mit parent als Eltern. Ist parent eine Aktionsgruppe (QActionGroup), wird die Aktion automatisch der Gruppe hinzugefügt.
Tabelle 5.7
338
Methoden der Klasse QAction
1542.book Seite 339 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
QAction ( const QString & text, Qobject * parent );
Dito, nur wird zusätzlich der String text bei der Erzeugung angelegt.
QAction ( const QIcon & icon, const QString & text, QObject * parent );
Dito, nur mit einem zusätzlichen Icon
~QAction ();
Destruktor. Zerstört eine Aktion.
QActionGroup * actionGroup () const;
Gibt die Aktionsgruppe für die Aktion zurück. Existiert keine Gruppe, wird 0 zurückgegeben.
void activate ( ActionEvent event );
Sendet das zugehörige Signal für ActionEvent event. Mögliche Werte dafür sind QAction:: Trigger und QAction::Hover, die bewirken sollen, dass die Signale QAction::triggered() oder Action::hover() ausgelöst werden.
QList associatedWidgets () const;
Gibt eine Liste von Widgets zurück, die der Aktion hinzugefügt wurden.
bool autoRepeat () const;
Wird true (Standardwert) zurückgegeben, kann eine Aktion mehrmals wiederholt werden. Wenn man bspw. eine Tastaturkombination für eine Aktion länger niedergedrückt hält, wird diese Aktion immer wieder ausgeführt. Ansonsten wird false zurückgegeben.
QVariant data () const;
Gibt Daten vom Typ QVariant (siehe Abschnitt 6.11.5) zurück, die mit der Methode setData() gesetzt wurden.
QFont font () const;
Gibt die Schriftart der Aktion zurück.
QIcon icon () const;
Gibt das Icon der Aktion zurück.
QString iconText () const;
Gibt den beschreibenden Text für das Icon zurück.
bool isCheckable () const;
Gibt true zurück, wenn die Aktion ankreuzbar ist. Standardmäßig ist dies nicht der Fall (false).
bool isChecked () const;
Gibt true zurück, wenn eine ankreuzbare Aktion angekreuzt ist. Ansonsten wird false zurückgegeben.
bool isEnabled () const;
Gibt true zurück, wenn eine Aktion eingeschaltet ist. Ansonsten wird false zurückgegeben.
bool isSeparator () const;
Gibt true zurück, wenn die Aktion eine Trennlinie ist. Ansonsten wird false zurückgegeben.
Tabelle 5.7
Methoden der Klasse QAction (Forts.)
339
5.2
1542.book Seite 340 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
bool isVisible () const;
Gibt true zurück, wenn eine Aktion »sichtbar« ist. Damit sind bspw. Menü-Widget oder Werkzeugleisten-Buttons gemeint.
QMenu * menu () const;
Gibt das in der Aktion enthaltene Menü-Widget zurück.
QWidget * parentWidget () const;
Gibt das Eltern-Widget der Aktion zurück.
void setActionGroup ( QActionGroup * group );
Setzt die Aktionsgruppe auf group. Die Aktion wird der Gruppe dann automatisch hinzugefügt.
void setAutoRepeat ( bool );
Sie können damit mit false den Standard aufheben, wonach eine Aktion mehrmals ausgeführt wird, wenn eine Tastenkombination länger niedergedrückt wurde. Damit wird jede Aktion nur bei erneutem Niederdrücken der Tastenkombination ausgeführt. Mit true stellen Sie den Standard wieder her.
void setCheckable ( bool );
Mit true könen Sie eine Aktion als ankreuzbar einrichten. Bei einem Button in der Werkzeugleiste bleibt dieser bspw. niedergedrückt (toggled), wenn er betätigt wird. Bei einem Menü findet man hierzu dann ein Häkchen.
void setData ( const QVariant & userData );
Damit können Sie (fast beliebge) Daten mit der Aktion verknüpfen, die bei Bedarf ausgwertet werden (siehe Abschnitt 6.11.5 für QVariant).
void setFont ( const QFont & font );
Setzt die Schriftart der Aktion auf font. So lässt sich bspw. die Schriftart für eine Menü-Widget verändern.
void setIcon ( const QIcon & icon );
Setzt ein Icon für die Aktion.
void setIconText ( const QString & text );
Setzt den beschreibenden Icon-Text für eine Aktion.
void setMenu ( QMenu * menu );
Verknüpft eine Aktion mit dem Menü-Widget menu.
void setSeparator ( bool b );
Wenn hier true als Parameter verwendet wird, wird die Aktion als Trennlinie bedacht.
void setShortcut ( const QKeySequence & shortcut);
Setzt eine Tastenkombination für die Aktion.
void setShortcutContext ( Qt::ShortcutContext context );
Setzt den Bezug für die Tastenkombination. Der Standardwert ist Qt::WindowShortcut. Mögliche Werte und deren Bedeutung siehe Tabelle 5.8.
Tabelle 5.7
340
Methoden der Klasse QAction (Forts.)
1542.book Seite 341 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
void setShortcuts ( const QList & shortcuts );
Verknüpft eine Liste von Tastenkombinationen mit der Aktion.
void setShortcuts ( Setzt Plattform-abhängige Tastenkombinationen QKeySequence::StandardKey key); auf key. Das Ergebnis des Aufrufs dieser Funktion ist
Plattform-abhängig. void setStatusTip ( const QString & statusTip );
Damit wird der Text der Statusleiste gesetzt.
void setText ( const QString & text );
Damit wird der beschreibende Text für die Aktion gesetzt. Fügt man diese Aktion einem Menü hinzu, wird dieser Text als Menü-Text verwendet.
void setToolTip ( const QString & tip );
Setzt den Text für den Tooltip auf tip.
void setWhatsThis ( const QString & what );
Setzt den Text für den »Was ist...?«-Text auf what.
QKeySequence shortcut () const;
Gibt die Tastenkombination für die Aktion zurück.
Qt::ShortcutContext shortcutContext () const;
Gibt den Bezug der Tastenkombination für die Aktion zurück. Mögliche Werte siehe Tabelle 5.8.
QList shortcuts () const;
Gibt eine Liste mit Tastenkombinationen zurück, die mit der Aktion verbunden sind.
bool showStatusText ( QWidget * widget = 0 );
Erneuert die Statusleiste für das Widget widget.
QString statusTip () const;
Gibt den String des Statustipps zurück.
QString text () const;
Gibt den String des beschreibenden Textes zurück.
QString toolTip () const;
Gibt den String des Tooltips zurück.
QString whatsThis () const;
Gibt den String für den »Was ist...?«-Text zurück.
Tabelle 5.7
Methoden der Klasse QAction (Forts.)
Jetzt zu den Werten der enum-Konstante Qt::ShortcutContext, womit Sie den Bezug zur Tastaturkombination festlegen bzw. abfragen können: Konstante
Beschreibung
Qt::WidgetShortcut
Die Tastenkombination hat nur einen Effekt, wenn das Eltern-Widget den Fokus hat.
Tabelle 5.8
Konstanten von Qt::ShortcutContext
341
5.2
1542.book Seite 342 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Konstante
Beschreibung
Qt::WindowShortcut
Hier hat die Tastenkombination nur einen Effekt, wenn das Eltern-Widget ein logisches Unter-Widget vom aktiven Top-Level-Fenster ist. Das ist der Standardwert.
Qt::ApplicationShortcut
Diese Tastenkombination wird aktiv, wenn die Anwendung läuft (und natürlich einen Fokus hat).
Tabelle 5.8
Konstanten von Qt::ShortcutContext (Forts.)
Jetzt zu den öffentlichen Slots und Signalen der Klasse QAction. Folgende Signale können ausgelöst werden: Signal
Beschreibung
void changed ();
Das Signal wird ausgelöst, wenn die Aktion gewechselt wurde.
void hovered ();
Dieses Signal wird ausgelöst, wenn der Anwender die Aktion »markiert« (hervorgehoben) hat. Dies ist bspw. der Fall, wenn die Maus über einem MenüElement oder einem Werkzeugleiste-Button ist.
void toggled ( bool checked );
Das Signal wird ausgelöst, wenn eine ankreuzbare Aktion den Zustand verändert hat. Ist der neue Zustand angekreuzt, ist der Parameter true, ansonsten false.
void triggered ( bool checked = false );
Dieses Signal wird ausgelöst, wenn der Anwender die Aktion aktiviert (bspw. eine Menü-Element anklickt oder über eine Tastaturkombination aktiviert). Die Aktion kann auch von Hand mit dem Slot trigger() ausgelöst werden. Sollte die Aktion ankreuzbar sein, ist der Parameter true, wenn die Aktion angekreuzt wurde.
Tabelle 5.9
Öffentliche Signale der Klasse QAction
Es folgen die öffentlichen Slots der Klasse QAction: Slots
Beschreibung
void hover ();
Damit wird Hervorheben aktiviert und somit auch das hovered()-Signal ausgelöst.
void setChecked ( bool );
Setzt eine ankreuzbare Aktion mit true auf »angekreuzt«. Mit false kann dies wieder rückgängig gemacht werden. Natürlich können nur ankreuzbare Aktionen mit diesem Slot etwas anfangen.
Tabelle 5.10
342
Öffentliche Slots der Klasse QAction
1542.book Seite 343 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Slots
Beschreibung
void setDisabled ( bool b );
Mit true kann eine Aktion abgeschaltet und mit false wieder eingeschaltet werden.
void setEnabled ( bool );
Auch hiermit lässt sich eine Aktion abschalten. Mit false wird eine Aktion abgeschaltet und mit true wieder aktiviert.
void setVisible ( bool );
Mit false können Sie Aktionen nicht mehr sichtbar machen (bspw. Menü-Widget oder Werkzeugleisten-Button).
void trigger ();
Damit wird das Signal triggered() ausgelöst.
Tabelle 5.10
Öffentliche Slots der Klasse QAction (Forts.)
Komplettes Listing Das komplette, etwas umfangreichere Beispiel finden Sie ab Seite 372.
QActionGroup Jetzt noch eine Übersicht zu den Methoden, Slots und Signalen der Klasse QActionGroup, womit sich mehrere Aktionen zu einer Gruppe zusammenfassen lassen. Damit ist es bspw. möglich, dass nur eine Aktion aus dieser Gruppe ausgewählt werden kann (wurde bereits beschrieben). Dass es immer nur eine exklusive Aktion in der Gruppe gibt, die aktiviert werden kann, ist Standard. Dies kann auch mit setExclusive(false) abgeschaltet werden. Methode
Beschreibung
QActionGroup ( QObject * parent );
Erzeugt eine neue Gruppe von Aktionen für das Eltern-Widget. Per Standard ist diese Aktion von Gruppen exklusiv.
~QActionGroup ();
Destruktor. Zerstört eine Gruppe von Aktionen.
QList actions () const;
Gibt eine Liste der Aktionen in der Gruppe zurück.
QAction * addAction ( QAction * action );
Fügt der Gruppe die Aktion action hinzu und gibt einen Zeiger darauf zurück.
QAction * addAction ( const QString & text );
Erzeugt eine neue Aktion mit dem String text, fügt sie der Gruppe hinzu und gibt diese zurück. Diese Methode wird allerdings normalerweise nicht verwendet. Der übliche Weg verläuft über QAction als Parameter.
Tabelle 5.11
Methoden der Klasse QActionGroup
343
5.2
1542.book Seite 344 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
QAction * addAction ( const QIcon & icon, const QString & text );
Dito, aber mit einem Icon
QAction * checkedAction () const; Gibt die aktuell angekreuzte Aktion der Gruppe
zurück. Ist keine Aktion angekreuzt, wird 0 zurückgegeben. bool isEnabled () const;
Gibt true zurück, wenn die ganze Gruppe von Aktionen eingeschaltet ist. Ansonsten wird false zurückgegeben.
bool isExclusive () const;
Gibt true zurück, wenn die Gruppe von Aktionen exklusiv ist – das heißt, es kann nur ein Element in der Gruppe ausgewählt werden (Standard). Ansonsten wird false zurückgegeben.
bool isVisible () const;
Gibt true zurück, wenn die Gruppe der Aktionen sichtbar ist (bspw. ein Menü oder ein Werkzeugleisten-Button).
void removeAction ( QAction * action );
Entfernt die Aktion action aus der Gruppe.
Tabelle 5.11
Methoden der Klasse QActionGroup (Forts.)
Die Klasse QActionGroup kann folgende Signale auslösen: Signal
Beschreibung
void hovered ( QAction * action );
Dieses Signal wird ausgelöst, wenn der Anwender eine Aktion in der Gruppe »markiert« (hervorgehoben) hat, was bspw. der Fall ist, wenn sich die Maus über einem Menü-Element oder einem Werkzeugleisten-Button befindet. Die entsprechende Aktion ist in action enthalten.
void triggered ( QAction * action );
Dieses Signal wird ausgelöst, wenn eine Aktion in der Gruppe vom Anwender aktiviert wurde (bspw. ein Menü-Element angeklickt oder über eine Tastaturkombination aktiviert). Die Aktion befindet sich im Parameter action.
Tabelle 5.12
Signale der Klasse QActionGroup
Jetzt noch die öffentlichen Slots der Klasse QActionGroup: Slot
Beschreibung
void setDisabled ( bool b );
Mit true kann die Gruppe von Aktionen deaktiviert und mit false aktiviert werden.
Tabelle 5.13
344
Öffentliche Slots der Klasse QActionGroup
1542.book Seite 345 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Slot
Beschreibung
void setEnabled ( bool );
Mit true kann die Gruppe von Aktionen aktiviert und mit false deaktiviert werden.
void setExclusive ( bool );
Mit false können Sie den Exclusive-Status einer Gruppe von Aktionen abschalten. Damit können dann mehrere Aktionen in der Gruppe ausgewählt werden.
void setVisible ( bool );
Mit false können Sie die Gruppe der Aktionen nicht mehr sichtbar machen (bspw. Menü-Widget oder Werkzeugleisten-Button).
Tabelle 5.13
5.2.3
Öffentliche Slots der Klasse QActionGroup (Forts.)
Eine Statusleiste mit der Klasse QStatusBar
Die Statusleiste ist eine horizontale Leiste unterhalb des Hauptfensters, die Informationen zum aktuellen Status der Anwendung ausgeben kann. Die Klasse, mit der man eine solche Leiste erzeugt, heißt QStatusBar. In der Praxis wird eine Instanz allerdings eher selten direkt über diese Klasse erzeugt. Gewöhnlich benutzt man hierfür die Methode QMainWindow::statusBar(), die einen Zeiger auf ein von QMainWindow benutztes QStatusBar-Objekt zurückgibt. Sollte das Hauptfenster noch keine Statusleiste besitzen, wird eines beim Aufruf von statusBar()erzeugt. In einer QMainWindow-Instanz lässt sich somit mit folgender Zeile eine Statusleiste erzeugen: MyWindow::MyWindow( ... ) ... (void*)statusBar(); ... }
{
Über diese Methode, kann nun auf die einzelnen Methoden, Slots und Signale der Statusleiste (QStatusBar) zugegriffen werden: statusBar()->methodenNamevonQStatusBar();
Bevor auf die einzelnen Methoden der Klasse QStatusBar eingegangen wird, wollen wir hier schon mal die drei verschiedenen Möglichkeiten erläutern, wie man Meldungen an die Statusleiste ausgibt. Temporäre Meldungen in der Statusleiste Temporäre Meldungen können jederzeit in der Statusleiste mit den Slots QStatusBar::showMessage() und QStatusBar::clearMessage() angezeigt bzw. wieder entfernt werden. Bspw. beim erfolgreichen Öffnen einer Datei:
345
5.2
1542.book Seite 346 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
// Datei öffnen und im Editor anzeigen void MyWindow::openFile() { ... // Datei erfolgreich geöffnet; Meldung an Statusleiste statusBar()->showMessage( tr("Datei erfolgreich geladen")); ... }
In der Praxis sieht dies folgendermaßen aus:
Abbildung 5.10
Temporären Text in der Statusleiste anzeigen
Diese Meldung bleibt jetzt so lange angezeigt, bis Sie sie explizit mit QStatusBar::clearMessage() löschen oder eine neue Meldung in der Statusleiste gesetzt wird. // Text in der Statusleiste löschen statusBar()->clearMessage();
Mit QStatusBar::showMessage() haben Sie allerdings auch die Möglichkeit, einen Text nur eine bestimmte Zeit lang anzuzeigen. Dieser Slot besitzt noch einen zweiten Parameter, mit dem Sie die Zeit in Millisekunden angeben, solange der Text in der Statusleiste angezeigt werden soll. Bspw. bleibt der folgende Text für 3 Sekunden in der Statusleiste sichtbar: // Text in der Statusleiste für 3 Sekunden anzeigen statusBar()->showMessage( tr("Datei erfolgreich geladen"), 3000);
Für temporäre Meldungen verwendet man normalerweise Informationen, die zeitlich begrenzt sichtbar sein sollten.
346
1542.book Seite 347 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Normale Meldungen in der Statusleiste Normale Meldungen werden immer angezeigt. Dies sind Meldungen, wenn Sie sich bspw. mit der Maus über einem Menü-Widget oder Werkzeugleisten-Button befinden (to hover) und hierfür bspw. mit der Methode setStatusTip() von QAction einen Text gesetzt haben. Bspw.: QMenu *workMenu = new QMenu(tr("&Bearbeiten"), this); menuBar()->addMenu(workMenu); QAction* act4 = workMenu->addAction( tr("&Suchen"), this, SLOT(search()), QKeySequence(tr("Ctrl+S", "Bearbeiten|Suchen"))); // den Text für die Statusleiste setzen act4->setStatusTip(tr("Nach einer Stringfolge suchen"));
In der Praxis sieht dies folgendermaßen aus:
Abbildung 5.11
Normale Meldung in der Statusleiste
In der Praxis bedeutet dies allerdings nicht, dass Sie auf normalen Text in der Statusleiste beschränkt sind. Sofern es sinnvoll ist, können Sie sich auch andere Widgets mit der Methode QStatusBar::addWidget() reinpacken und per QStatusBar::removeWidget() wieder entfernen. Wollen Sie bspw. einen Fortschrittsbalken (QProgressBar) anstelle eines gewöhnlichen Texts integrieren, können Sie folgendermaßen vorgehen: // im Editor nach einer bestimmten Textfolge suchen void MyWindow::search( ) { bool ok; QString text = QInputDialog::getText( this, "Suchdialog", "Text zur Suche eingeben :", QLineEdit::Normal, "Suche eingeben", &ok );
347
5.2
1542.book Seite 348 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
// Fortschrittsbalken QProgressBar* pbar = new QProgressBar; // min. und max. Werte festlegen pbar->setRange( 0, 500 ); // Fortschrittsbalken zur Statusleiste hinzufügen statusBar()->addWidget(pbar); for (int i = 0; i < 500; i++) { pbar->setValue(i); for( int j=0; j < 1234567; ++j); //... nur simulieren } pbar->setValue(500); // Fortschrittsbalken wieder entfernen statusBar()->removeWidget(pbar); if (ok && !text.isEmpty()) editor->find(text); // für drei Sekunden anzeigen statusBar()->showMessage(tr("Suche beendet"), 3000); }
In diesem Beispiel wird lediglich ein umfangreicherer Suchvorgang in einem Text simuliert. In der Praxis sieht dieser Vorgang folgendermaßen aus:
Abbildung 5.12
Fortschrittsbalken als normale Meldung in Statusleiste
Normale und permanente Meldungen Kommt eine temporäre Meldung zum Zuge, wird die normale Meldung überdeckt.
Permanente Meldungen in der Statusleiste Benötigen Sie Meldungen in der Statusleiste, die permanent angezeigt und garantiert von keiner temporären oder normalen Meldung überdeckt werden, müssen
348
1542.book Seite 349 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Sie die Methode QStatusBar::addPermanentWidget() verwenden. Permanente Meldungen erscheinen immer außen rechts. Wird eine weitere permanente Meldung hinzugefügt, wird die äußerste Meldung (rechts außen) um eine Position nach innen verschoben. Hier ein Beispiel einer solchen permanenten Meldung: MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { ... (void*) statusBar (); sLabel = new QLabel; sLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); sLabel->setLineWidth(2); statusBar()->addPermanentWidget(sLabel); connect( editor, SIGNAL( textChanged() ), this, SLOT( updateStatusBar() ) ); updateStatusBar(); ... }
Hier wird ein Label als permanentes Widget der Statusleiste hinzugefügt. In diesem Zusammenhang richten wir eine Signal-Slot-Verbindung zu unserem QText-Edit-Objekt und dem Hauptfenster ein. Wurde der Text verändert (textChanged()), wird der Slot updateStatusBar() ausgeführt. Dieser wird bei der Ausführung des Konstruktors zum ersten Mal auch von Hand ausgeführt. Hierzu noch der Slot: void MyWindow::updateStatusBar() { QString str = editor->toPlainText(); int count_char = str.length(); int count_words = str.count(" "); int count_lines = str.count("\n"); QString label = tr("Zeichen: %1 Wörter: %2 Zeilen: %3") .arg(count_char).arg(count_words).arg(count_lines+1); sLabel->setText(label); }
Damit haben Sie also im Grunde nichts anderes als ein weiteres Feld rechts in der Statusbar, worin sich die Anzahl der Zeichen, Wörter und Zeilen des Editors befindet und – wenn der Text geändert wurde – ständig erneuert wird. Natürlich kann man auch hier andere Widgets als nur QLabel verwenden; eine einfache Datums- und Uhrzeitanzeige mit dem LCD-Widget (QLCDNumber) lässt sich bspw. folgendermaßen realisieren:
349
5.2
1542.book Seite 350 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
// Konstruktor MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { ... time = new QLCDNumber; time->setFrameStyle(QFrame::Panel | QFrame::Sunken); time->setLineWidth(2); time->setSegmentStyle(QLCDNumber::Flat); statusBar()->addPermanentWidget(time); QTimer *timer = new QTimer(this); connect( timer, SIGNAL(timeout()), this, SLOT(updateTime()) ); // jede Sekunden Aktualisieren timer->start(1000); updateTime(); ... } ... ... void MyWindow::updateTime() { // aktuelles Datum und Uhrzeit QDateTime Time = QDateTime::currentDateTime(); // Formatieren QString text = Time.toString("dd.MM.yyyy hh:mm:ss"); // Anzahl der Ziffern setzen time->setNumDigits(text.size()+1); // und alles anzeigen time->display(text); }
In der Praxis würde unsere Statusleiste mit den permanenten Meldungen folgendermaßen aussehen:
Abbildung 5.13
350
Permanente Meldungen in der Statusleiste
1542.book Seite 351 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Hinweis Normale und temporäre Meldungen werden in der Praxis links in der Statusleiste und permanente Meldungen rechts angezeigt.
Methoden, Signale und Slots von QStatusBar Nachdem Sie die nun grundlegenden Funktionen der Statusleiste kennen, wollen wir auf die einzelnen Methoden der Klasse QStatusBar eingehen. Methode
Beschreibung
QStatusBar ( QWidget * parent = 0 );
Erzeugt eine neue Statusleiste mit einem Griff zur Größenveränderung des Fensters mit parent als Eltern-Widget.
virtual ~QStatusBar ();
Destruktor. Zerstört die Statusleiste.
void addPermanentWidget ( Fügt der Statusleiste ein Widget dauerhaft (anzeigend) QWidget * widget, hinzu. Wie viel Platz ein Widget erhält, geben Sie mit int stretch = 0 ); stretch an. 0 bedeutet hier, dass das Widget gerade so viel
Platz wie nötig erhält. Fügen Sie bspw. ein Widget mit 1 und ein weiteres Widget 2 als Stretch-Faktor mit ein, macht sich das zweite Widget doppelt so breit wie das erste. Das Widget wird auf der rechten Seite der Statusleiste hinzugefügt. void addWidget ( QWidget * widget, int stretch = 0 );
Fügt ein Widget zur Statusleiste hinzu. Wie viel Platz ein Widget erhält, geben Sie mit stretch an. 0 bedeutet hier, dass das Widget gerade so viel Platz wie nötig erhält. Fügen Sie bspw. ein Widget mit 1 und ein weiteres Widget 2 als Stretch-Faktor mit ein, macht sich das zweite Widget doppelt so breit wie das erste. Das Widget wird auf der linken Seite der Statusleiste hinzugefügt.
QString currentMessage () Gibt die temporäre Meldung in der Statusleiste zurück, die const; gerade angezeigt wird oder einen leeren String, wenn es
keine Meldung gibt. int insertPermanent Widget ( int index, QWidget * widget, int stretch = 0 );
Wie addPermanentWidget(), nur dass ein dauerhaft (anzeigendes) Widget an der Position index hinzugefügt wird.
int insertWidget ( int index, QWidget * widget, int stretch = 0 );
Wie addWidget(), nur dass ein dauerhaft (anzeigendes) Widget an der Position index hinzugefügt wird.
bool isSizeGripEnabled () Gibt true zurück, wenn der Griff zur Größenveränderung des const; Fensters sichtbar ist (Standard). Ansonsten wird false
zurückgegeben. Tabelle 5.14
Öffentliche Methoden der Klasse QStatusBar
351
5.2
1542.book Seite 352 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
void removeWidget ( QWidget * widget );
Entfernt ein Widget von der Statusleiste.
void setSizeGripEnabled ( Mit false können Sie den Griff zur Größenveränderung des bool ); Fensters deaktivieren. Mit true schalten Sie dies wieder ein.
Tabelle 5.14
Öffentliche Methoden der Klasse QStatusBar (Forts.)
Die Klasse QStatusBar umfasst nur ein Signal: Signal
Beschreibung
void messageChanged ( const QString & message );
Signal wird ausgelöst, wenn sich die Meldung in der Statusleiste verändert hat. Die neue Meldung befindet sich im Parameter message.
Tabelle 5.15
Signal der Klasse QStatusBar
Außerdem existieren zwei öffentliche Slots der Klasse QStatusBar: Slots
Beschreibung
void clearMessage ();
Entfernt eine temporäre Meldung aus der Statusleiste.
void showMessage ( Versteckt eine normale Meldung und zeigt die Meldung const QString & message, message für timeout Millisekunden (wenn nicht 0) in der int timeout = 0 ); Statusleiste an. Der Text wird entfernt, wenn die Meldung mit clearMessage() entfernt oder ein weiteres showMessage() aufgerufen wird.
Tabelle 5.16
Slots der Klasse QStatusBar
Komplettes Listing Das vollständige, etwas umfangreichere Beispiel finden Sie ab Seite 372.
5.2.4
Eine Werkzeugleiste mit der Klasse QToolBar
Zur Werkzeugleiste mit Buttons braucht man eigentlich nicht allzu viel zu erwähnen, weil alles schon bei den Menü-Widget erwähnt wurde. Sämtliche Aktionen (QAction) lassen sich auch in der Werkzeugleiste (wieder-)verwenden. Zusätzlich bietet eine Werkzeugleiste Optionen an, um diese Leiste mit den Buttons zu verschieben. Auch bei den Werkzeugleisten bietet das Hauptfenster mit QMainWindow::addToolBar () eine komfortable Methode an, ohne direkt eine Instanz von QToolBar
zu erzeugen. In der Regel geht man dabei wie folgt vor:
352
1542.book Seite 353 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
// neue Werkzeugleiste toolFile = addToolBar(tr("Datei")); toolFile->addAction(act1); toolFile->addAction(act2); toolFile->addAction(act3); // neue Werkzeugleiste toolWork = addToolBar(tr("Bearbeiten")); toolWork->addAction(underLineAct); toolWork->addSeparator (); toolWork->addAction(leftAlignAct); toolWork->addAction(rightAlignAct); toolWork->addAction(justifyAct); toolWork->addAction(centerAct); toolWork->addSeparator (); // Font-Combobox zur Werkzeugleiste QFontComboBox *cFont = new QFontComboBox; toolWork->addWidget(cFont); connect( cFont, SIGNAL(currentFontChanged ( const QFont& )), editor, SLOT(setCurrentFont ( const QFont&)));
Nachdem Sie eine Werkzeugleiste zum Hauptfenster erzeugt und gleichzeitig hinzugefügt haben, können Sie die bereits vorhandenen (oder ggf. neue) Aktionen zur Werkzeugleiste hinzufügen. Im Beispiel wurde auf die bereits im Menü-Widget erzeugten Aktionen zurückgegriffen, passend dazu eine Font-Combobox eingefügt und eine entsprechende Signal-Slot-Verbindung eingerichtet. Im Beispiel wurden der Aktion im Gegensatz zum Menü-Widget einige Icons hinzugefügt. In der Praxis sieht das Beispiel wie folgt aus:
Abbildung 5.14
Das Hauptfenster mit einer Werkzeugleiste
353
5.2
1542.book Seite 354 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Die Werkzeugleisten lassen sich nun per Standard fast beliebig an jeder Seite im Hauptfenster verschieben, wie die beiden folgenden Abbildungen demonstrieren:
Abbildung 5.15
Eine Möglichkeit, die Werkzeugleiste zu verschieben
Abbildung 5.16
Eine weitere Möglichkeit, die Werkzeugleiste anzupassen
Natürlich können Sie solche »Schiebereien« des Anwenders mit der Methode QToolBar::setMovable(false) auch unterbinden. Die Methode QMainWindow:: addToolBar() gibt es außerdem in einer zweiten Form (QMainWindow::addToolBar(Qt::ToolBarArea area, QToolBar* toolbar)), womit Sie die Position der Werkzeugleiste festlegen. Mögliche Werte und deren Bedeutung für Qt::ToolBarArea sind:
354
1542.book Seite 355 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Konstante
Beschreibung
Qt::LeftToolBarArea
Werkzeugleiste an der rechten Seite im Hauptfenster ausrichten
Qt::RightToolBarArea
Werkzeugleiste in der linken Seite des Fensters ausrichten
Qt::TopToolBarArea
Werkzeugleiste oberhalb (Standard) des Fensters aurichten
Qt::BottomToolBarArea
Werkzeugleiste unterhalb des Fensters ausrichten
Qt::AllToolBarAreas
Alle eben erwähnten Positionen. Kann nicht mit addToolBar() verwendet werden
Tabelle 5.17
Mögliche Position der Werkzeugleiste
Wollen Sie bspw., dass eine Werkzeugleiste beim Programmstart unten erscheint, müssen Sie dies nur folgendermaßen machen: toolWork = addToolBar(tr("Bearbeiten")); // Werkzeugleiste toolWork unten anzeigen addToolBar(Qt::AllToolBarAreas, toolWork );
Hierzu die öffentlichen Methoden der Klasse QToolBar und deren Bedeutung: Methode
Beschreibung
QToolBar ( const QString & title, QWidget * parent = 0 );
Erzeugt eine neue Werkzeugleiste mit parent als Eltern-Widget. Der übergebene Fenstertitel identifiziert die Werkzeugleiste und wird in einem Kontext-Menü angezeigt, wenn dies das Hauptfenster unterstützt.
QToolBar ( QWidget * parent = 0 );
Erzeugt eine neue Werkzeugleiste mit parent als Eltern-Widget.
~QToolBar ();
Destruktor. Zerstört eine Werkzeugleiste.
QAction * actionAt ( const QPoint & p ) const;
Gibt die Aktion vom Punkt p zurück. Existiert dort keine Aktion, wird 0 zurückgegeben.
QAction * actionAt ( int x, int y ) const;
Gibt die Aktion vom Koordinatenpunkt x, y zurück. Gibt es dort keine Aktion, wird 0 zurückgegeben.
QAction * addAction ( const QString & text );
Erzeugt eine neue Aktion mit dem String text. Diese Methode fügt die neu erzeugte Aktion der Werkzeugleiste von Aktionen hinzu und gibt diese zurück.
Tabelle 5.18
Methoden der Klasse QToolBar
355
5.2
1542.book Seite 356 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
QAction * addAction ( const QIcon & icon, const QString & text );
Dito, nur mit zusätzlichem Icon
QAction * addAction ( const QString & text, const QObject * receiver, const char * member );
Erzeugt eine neue Aktion mit dem String text. Den Empfänger (die Klasse) der Aktion geben Sie mit receiver an. Der Empfänger reagiert immer auf das Signal triggered() und führt den Slot member aus. Als Rückgabewert wird die erzeugte Aktion zurückgegeben.
QAction * addAction ( const QIcon & icon, const QString & text, const QObject * receiver, const char * member );
Dito, nur mit einem zusätzlichen Icon
QAction * addSeparator ();
Fügt einen Separator am Ende der Werkzeugleiste hinzu.
QAction * addWidget ( QWidget * widget );
Fügt ein Widget am Ende der Leiste hinzu (bspw. QFontComboBox).
Qt::ToolBarAreas allowedAreas () const;
Gibt die erlaubten Bereiche im Hauptfenster zurück, wo die Werkzeugleiste platziert werden darf. Mögliche Werte wurden bereits in Tabelle 5.17 beschrieben.
void clear ();
Entfernt alle Aktionen von der Werkzeugleiste.
QSize iconSize () const;
Gibt die Größe der Icons in der Werkzeugleiste zurück. Der Standardwert ist Qt::AutomaticIconSize.
QAction * insertSeparator ( QAction * before );
Fügt einen Separator in der Werkzeugleiste vor der Aktion before ein.
QAction * insertWidget ( QAction * before, QWidget * widget );
Fügt eine neues Widget in der Werkzeugleiste vor der Aktion before ein.
bool isAreaAllowed ( Qt::ToolBarArea area ) const;
Gibt true zurück, wenn die Werkzeugleiste im Bereich area angezeigt werden darf. Mögliche Werte für area wurden bereits in der Tabelle 5.17 beschrieben.
bool isMovable () const;
Gibt true zurück, wenn eine Werkzeugleiste verschiebbar ist.
Qt::Orientation orientation () const;
Gibt die Ausrichtung der Werkzeugleiste zurück. Mögliche Werte sind Qt::Horizontal (Standard) und Qt::Vertical.
Tabelle 5.18
356
Methoden der Klasse QToolBar (Forts.)
1542.book Seite 357 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
void setAllowedAreas ( Qt::ToolBarAreas areas );
Damit setzen Sie die erlaubten Bereiche im Fenster, in dem die Werkzeugleiste angezeigt werden darf. Mehrere Bereiche können auch mit dem bitweisen ODER verknüpft werden. Mögliche Werte wurden in Tabelle 5.17 bereits näher beschrieben.
void setMovable ( bool movable );
Mit false schalten Sie den Standard aus, dass eine Werkzeugleiste im Fenster verschoben werden kann. Mit true stellen Sie den Zustand wieder her.
void setOrientation ( Qt::Orientation orientation );
Damit setzen Sie die Ausrichtung der Werkzeugleiste auf Qt::Horizontal (Standard) oder Qt::Vertical.
Qt::ToolButtonStyle toolButtonStyle () const;
Damit erhalten Sie den Stil zurück, wie die Buttons in der Werkzeugleiste angezeigt werden. Mögliche Werte siehe Tabelle 5.19.
QWidget * widgetForAction ( QAction * action ) const;
Gibt das mit der Aktion action verbundene Widget zurück.
Tabelle 5.18
Methoden der Klasse QToolBar (Forts.)
In der folgenden Tabelle finden Sie Konstanten der enum-Variablen Qt::ToolButtonStyle die beschreibt, wie die Buttons in der Werkzeugleiste angezeigt werden. Konstante
Beschreibung
Qt::ToolButtonIconOnly
Zeigt nur die Icons an (Standard).
Qt::ToolButtonTextOnly
Zeigt nur den Text an.
Qt::ToolButtonTextBesideIcon
Zeigt die Icons mit dem Text daneben an.
Qt::ToolButtonTextUnderIcon
Zeigt die Icons mit dem Text darunter an.
Tabelle 5.19
Darstellungsmöglichkeiten der Buttons in der Werkzeugleiste
Nun zu den von der Klasse QToolBar ausgelösten öffentlichen Signalen: Signal
Beschreibung
void actionTriggered ( QAction * action );
Das Signal wird ausgelöst, wenn ein Button in der Werkzeugleiste betätigt wurde. Die Aktion des Buttons befindet sich in action.
Tabelle 5.20
Öffentliche Signale der Klasse QToolBar
357
5.2
1542.book Seite 358 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Signal
Beschreibung
void allowedAreasChanged ( Qt::ToolBarAreas allowedAreas);
Dieses Signal wird ausgelöst, wenn die Sammlung von erlaubten Bereichen geändert wurde. Im Parameter allowedAreas finden Sie die neue Sammlung erlaubter Bereiche, wo die Werkzeugleiste platziert werden darf.
void iconSizeChanged ( const QSize & iconSize );
Das Signal wird ausgelöst, wenn die Größe der Icons verändert wurde. Im Parameter iconSize finden Sie die neue Größe der Icons.
void movableChanged ( bool movable );
Wurde die Möglichkeit, eine Werkzeugleiste zu verschieben, verändert, wird dieses Signal ausgelöst. Ist der Parameter true, ist es jetzt möglich, die Werkzeugleiste zu verschieben. Ansonsten (bei false) wurde das Verschieben deaktiviert.
void orientationChanged ( Qt::Orientation orientation );
Dieses Signal wird ausgelöst, wenn die Ausrichtung (Qt::Horizontal (Standard) oder Qt::Vertical) verändert wurde. Die neue Ausrichtung befindet sich in orientation.
void toolButtonStyleChanged ( Qt::ToolButtonStyle toolButtonStyle );
Das Signal wird ausgelöst, wenn der Anzeigestil der Icons verändert wurde. Der neue Stil befindet sich in toolButtonStyle. Mögliche Werte siehe Tabelle 5.19.
Tabelle 5.20
Öffentliche Signale der Klasse QToolBar (Forts.)
Zum Schluss noch die beiden öffentlichen Slots der Klasse QToolBar: Slot
Beschreibung
void setIconSize ( const QSize & iconSize );
Damit können Sie eine neue Größe für Icons in der Werkzeugleiste setzen.
void setToolButtonStyle ( Qt::ToolButtonStyle toolButtonStyle );
Damit können Sie den Stil, wie die Buttons in der Werkzeugleiste angezeigt werden, ändern. Mögliche Stile siehe Tabelle 5.19.
Tabelle 5.21
Öffentliche Slots der Klasse QToolBar
Komplettes Listing Das komplette, etwas umfangreichere Beispiel finden Sie ab Seite 372.
5.2.5
Verschiebbare Widgets im Hauptfenster mit QDockWidget
Ein Widget der Klasse QDockWidget kann dazu verwendet werden, ein weiteres Fenster mit beliebigen Widgets innerhalb des Hauptfensters (QMainWindow) zu
358
1542.book Seite 359 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
platzieren. Dieses Dock-Fenster kann entweder fest in das Hauptfenster integriert oder als verschiebbares Top-Level-Fenster auf dem Desktop platziert werden. Dock-Widgets sind so etwas wie ein zweites Fenster im Hauptfenster (QMainWindow). Natürlich können auch mehrere Dock-Widgets auf einmal verwendet werden. Platziert werden diese Dock-Widgets immer um das ZentralWidget von QMainWindow. Ein Dock-Fenster lässt sich von einem Bereich in den anderen verschieben oder auch komplett vom Hauptfenster lösen. Natürlich kann wie bei einer Werkzeugleiste festgelegt werden, wohin man ein Dock-Fenster verschieben darf oder ob man es auch schließen kann. Grundlegend enthält ein Dock-Fenster eine Titelleiste und einen Bereich mit einem Inhalt. Zusätzlich befindet sich in der Titelleiste ein Button zum Schließen und Loslösen des Dock-Fensters. Allerdings lassen sich diese Optionen auch anpassen. Hierzu ein einfaches Beispiel. Mit den folgenden wenigen Zeilen wurde ein einfaches Dock-Fenster dem Hauptfenster auf der rechten Seite hinzugefügt. Dabei handelt es sich um einen einfachen WYSIWYG-Editor (auf Dt. so viel wie »Was du siehst, bekommst du auch«). Das heißt, Sie können im linken Zentral-Widget nach wie vor einen Text eingeben. Verwenden Sie hierzu bspw. HTML-Tags, wird dies im Dock-Fenster (worin ebenfalls ein Widget der Klasse QTextEdit haust) auch als HTML-formatierter Text angezeigt. Hierzu der benötigte Code: // Konstruktor MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { ... // ein neues Dock erzeugen QDockWidget *dock = new QDockWidget( tr("WYSIWYG-Editor"), this); // nur in den rechten und linken Bereich erlauben dock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); // Widget für das Dock-Widget erzeugen dock_editor = new QTextEdit; // Editor als Widget im Dock-Fenster verwenden dock->setWidget(dock_editor); // auf der rechten Seite zum Hauptfenster hinzufügen addDockWidget(Qt::RightDockWidgetArea, dock); connect( editor, SIGNAL(textChanged() ), this, SLOT( showHTML() ) ); ...
359
5.2
1542.book Seite 360 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
} ... ... // Slot showHTML() void MyWindow::showHTML() { QString str = editor->toPlainText(); dock_editor->setHtml(str); }
In der Praxis sieht dieses Dock-Fenster folgendermaßen aus:
Abbildung 5.17
Der Text-Editor mit einem Dock-Widget
Zum Hauptfenster QMainWindow wurde dieses Dock-Widget mit der Methode QMainWindow::addDockWidget() hinzugefügt. Hierzu nun der Überblick zu den Methoden von QDockWidget: Methode
Beschreibung
QDockWidget ( const QString & title, QWidget * parent = 0, Qt::WindowFlags flags = 0 );
Erzeugt ein neues Dock-Widget mit parent als Eltern-Widget und dem Fenster-Flag flag. Ohne weitere Angaben wird das Dock-Widget in der linken Seite vom Zentral-Widget platziert. Der Titel des Fensters wird auf title gesetzt.
QDockWidget ( QWidget * parent = 0, Qt::WindowFlags flags = 0 );
Dito, nur ohne den Fenster-Titel
Tabelle 5.22
360
Methoden der Klasse QDockWidget
1542.book Seite 361 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
~QDockWidget ()
Destruktor. Zerstört ein Dock-Widget.
Qt::DockWidgetAreas allowedAreas () const;
Gibt den erlaubten Bereich für das Dock-Widget um das Zentral-Widget zurück, wo das Widget »angedockt« werden darf. Mögliche Werte siehe Tabelle 5.23.
DockWidgetFeatures features () const;
Gibt die möglichen Features des Dock-Widgets zurück. Mögliche Rückgabewerte und deren Bedeutung siehe Tabelle 5.24.
bool isAreaAllowed ( Qt::DockWidgetArea area ) const;
Gibt true zurück, wenn das Dock-Widget im Bereich area (siehe Tabelle 5.23) platziert werden darf. Ansonsten wird false zurückgegeben.
bool isFloating () const;
Gibt true zurück, wenn das Dock-Widget vom Haupt-Fenster losgelöst wurde (also frei als TopLevel-Fenster sichtbar ist). Ansonsten wird false zurückgegeben.
void setAllowedAreas ( Qt::DockWidgetAreas areas );
Damit setzen Sie den Bereich, wo das Dock-Widget beim Verschieben (floating) platziert werden darf. Mögliche Werte hierfür siehe Tabelle 5.23.
void setFeatures ( DockWidgetFeatures features );
Setzt die Features des Dock-Widgets auf features. Mögliche Werte siehe Tabelle 5.24.
void setFloating ( bool floating );
Löst mit true ein Dock-Widget vom angedockten Zustand ab, womit das Fenster frei als Top-LevelFenster sichtbar ist.
void setWidget ( QWidget * widget );
Setzt widget als das Widget für das Dock-Widget.
QWidget * widget () const
Gibt das gesetzte Widget für das Dock-Widget zurück.
Tabelle 5.22
Methoden der Klasse QDockWidget (Forts.)
Jetzt zu den möglichen Werten der enum-Konstante Qt::DockWidgetArea, womit Sie den möglichen Bereich für das Dock-Widget um das Zentral-Widget setzen bzw. ermitteln können (mehrere Werte können mit dem bitweisen ODER verknüpft werden). Konstante
Beschreibung
Qt::LeftDockWidgetArea
Dock-Widget an der rechten Seite vom ZentralWidget ausrichten
Qt::RightDockWidgetArea
Dock-Widget an der linken Seite vom Zentral-Widget ausrichten
Tabelle 5.23
Mögliche Bereiche für das Dock-Widget
361
5.2
1542.book Seite 362 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Konstante
Beschreibung
Qt::TopDockWidgetArea
Dock-Widget oberhalb (Standard) des ZentralWidgets ausrichten
Qt::BottomDockWidgetArea
Dock-Widget unterhalb des Zentral-Widgets ausrichten
Qt::AllDockWidgetAreas
Alle eben erwähnten Positionen
Tabelle 5.23
Mögliche Bereiche für das Dock-Widget (Forts.)
Hier fehlen noch die möglichen Konstanten für die enum-Konstante QDockWidget::DockWidgetFeature, womit Sie die Features für ein Dock-Widget setzen bzw. abfragen können (mehrere Werte können mit dem bitweisen ODER verknüpft werden): Konstante
Beschreibung
QDockWidget::DockWidgetClosable
Das Dock-Widget kann geschlossen werden (ein Button dazu ist sichtbar).
QDockWidget::DockWidgetMovable
Das Dock-Widget kann zwischen den einzelnen Bereichen vom Anwender verschoben werden.
QDockWidget::DockWidgetFloatable
Das Dock-Widget kann frei schwebend als eigenes Fenster auf dem Bildschirm, unabhängig vom Hauptfenster, verschoben werden.
QDockWidget::AllDockWidgetFeatures
Alle drei eben erwähnten Werte zusammen (der Standard-Wert).
QDockWidget::NoDockWidgetFeatures
Das Dock-Widget kann weder geschlossen, verschoben noch frei schwebend verwendet werden.
Tabelle 5.24
Mögliche Features für das Dock-Widget
Eigene Slots besitzt die Klasse QDockWidget nicht, wohl aber drei Signale, die wir hier kurz erläutern: Signal
Beschreibung
void allowedAreasChanged ( Qt::DockWidgetAreas allowedAreas );
Das Signal wird ausgelöst, wenn die Eigenschaft, ob ein Dock-Fenster in einen anderen Bereich verschoben werden kann, verändert wurde. Der neue Wert befindet sich in allowedAreas.
Tabelle 5.25
362
Signale der Klasse QDockWidget
1542.book Seite 363 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Signal
Beschreibung
void featuresChanged ( Das Signal wird ausgelöst, wenn die Features des QDockWidget::DockWidgetFeatures Dock-Widgets verändert wurden. Der neue Wert features ); befindet sich in features. void topLevelChanged ( bool topLevel );
Tabelle 5.25
Dieses Signal wird ausgelöst, wenn die Eigenschaft für das Verschieben des Fensters als Top-LevelFenster verändert wurde. Der Parameter ist true, wenn das Dock-Widget als »freies« Fenster verschiebbar ist und false, wenn nicht.
Signale der Klasse QDockWidget (Forts.)
Komplettes Listing Das komplette, etwas umfangreichere Beispiel finden Sie ab Seite 372.
5.2.6
Einstellungen der Anwendung speichern mit QSettings
Die Klasse QSettings wird dazu verwendet, um diverse Einstellungen der Anwendung zu speichern. Sicherlich kann man so etwas auch per Hand mit sogenannten config- oder auch ini-Dateien machen. Aber Qt bietet hierzu eine Vollservicelösung an. Jeder, der schon mal umfangreichere Programme auf mehreren Systemen geschrieben hat, weiß, dass es ziemlich aufwändig werden kann, die Einstellungen auf die Gegebenheiten der verschiedenen Systemen anzupassen. Da kommen u. a. systemweite und/oder benutzerdefinierte und/oder anwenderbezogene Einstellungen, Zugriffsrechte usw. vor. Das Ziel der Klasse QSettings ist dabei ganz klar, eine portable Einstellung für die Qt-Anwendung zu ermöglichen. So werden Anwendungsdaten auf den unterschiedlichen Systemen gewöhnlich auch unterschiedlich gespeichert. MS-Windows benutzt hierzu bspw. das Registry, bei Mac OS X wird auf die XML-basierende Datei mit der Endung .plist zurückgegriffen und unter Unix/Linux sind gleich mehrere Varianten (bspw. unter /etc/xdg für systemweite oder ~/.config für benutzerdefinierte Einstellungen) bekannt (tlw. Abhängig von der Distribution). Um sich eben nicht mit solchen Dingen herumzuschlagen (bzw. sich zu ärgern), verwendet man einfach QSettings. Zum Anlegen eines neuen QSettings-Objekts genügen im Grunde zwei Parameter: QSettings* settings = new QSettings( tr("Pronix Inc."), tr("Qt-Texteditor"));
363
5.2
1542.book Seite 364 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Mit dem ersten Parameter gibt man die Firma oder den Programmierernamen an, während der zweite gewöhnlich für den Namen der Anwendung reserviert ist. Bei der folgenden Instanzierung würde man den folgenden Registry-Eintrag (unter MS-Windows) hinzufügen:
Abbildung 5.18
Neuer Eintrag in HKEY_CURRENT_USER\Software
Standardmäßig werden unsere Einstellungen nur entsprechend dem jeweiligen Benutzer gespeichert, der eingeloggt ist (QSettings::UserScope). Benötigt man Einstellungen, die systemweit jedem Benutzer, also der kompletten Anwendung entsprechen (unter MS-Windows also HKEY_LOCAL_MACHINE\Software), kann man bei der Instanzierung des QSettings-Objekts die Konstante QSettings::SystemScope verwenden: QSettings* settings = new QSettings( QSettings::SystemScope , tr("Pronix Inc."), tr("Qt-Texteditor") );
Zugriffsrechte beachten Bitte beachten Sie, dass Einstellungen mit QSettings::SystemScope Administratorrechte voraussetzen. Was hierbei unter Windows-Systemen (noch) gang und gäbe ist, ist unter unixartigen Systemen eher selten der Fall. Allerdings sollten Sie auch bei MSWindows nicht davon ausgehen, dass der Anwender schon vollen Systemzugriff hat. Vista hat da bspw. ein ausgeklügelteres Rechtesystem (sofern man dies nicht deaktiviert hat).
Natürlich gibt es auch einen Konstruktor, mit dem man eine eigene Datei für die Einstellungen (ggf. erzeugen und) verwenden kann. Mehr dazu bei den Methoden der Klasse QSettings in Tabelle 5.26.
364
1542.book Seite 365 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Allerdings gibt es eine weitere – komfortable – Möglichkeit, um nicht immer wieder ein QSettings-Objekt zu erzeugen. Hierzu schreibt man (bspw. in der main()-Funktion): QCoreApplication::setOrganizationName("Pronix Inc."); QCoreApplication::setApplicationName("Qt-Texteditor");
Jetzt kann man jederzeit eine parameterlose Instanzierung von QSettings verwenden. Daten werden mit der Methode setValue() geschrieben. Diese Methode erwartet neben dem Schlüssel auch einen Wert. Da der Wert von der Klasse QVariant ist, sind hierbei fast keine Grenzen gesetzt. Im folgenden Beispiel wird bspw. beim Slot MyWindow::openFile() die zuletzt geöffnete Datei gesetzt. Hierzu wird eine Gruppe namens »Hauptfenster« eingerichtet. Anschließend wird ein Schlüssel »lastFile« mit dem Wert der geöffneten Datei gesetzt. Zum Schluss wird die neu angelegte Gruppe »Hauptfenster« wieder gelöscht: ... QSettings* settings = new QSettings( tr("Pronix Inc."), tr("Qt-Texteditor")); ... ... // Datei öffnen und im Editor anzeigen void MyWindow::openFile() { QString fileName; fileName = QFileDialog::getOpenFileName( this, tr("Datei öffnen"), "", "C++ Dateien (*.cpp *.h);;" "Text-Dateien (*.txt);;" "Alle Dateien (*.*)" ); if (!fileName.isEmpty()) { QFile file(fileName); if (file.open(QFile::ReadOnly | QFile::Text)) { editor->setPlainText(file.readAll()); statusBar()->showMessage( tr("Datei erfolgreich geladen")); // Zuletzt geöffnete Datei setzen settings->beginGroup("HauptFenster"); settings->setValue("lastFile", fileName ); settings->endGroup(); } } }
365
5.2
1542.book Seite 366 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Mit diesen Zeilen sieht der Registry-Eintrag unter MS-Windows folgendermaßen aus:
Abbildung 5.19
Ein neuer Eintrag in der Registry
Unter unixartigen Systemen wurde eine Konfigurations-Datei unter ~/.config/ Pronix Inc./Qt-Texteditor.conf mit folgendem Inhalt angelegt: [HauptFenster] lastFile=/home/vmware/qtexample/chap005/main.cpp
Natürlich können Sie zu den Gruppen weitere Einstellungen speichern. Theoretisch ist es natürlich auch möglich, eine Einstellung ohne Gruppe zu erzeugen. Außerdem können Sie weitere Gruppen hinzufügen. Es ist auch möglich, auf eine Gruppe ohne Angabe der Gruppe mit der Methode beginGroup()zuzugreifen. Hierbei können Sie im Grunde wie bei einer Pfadangabe über die Gruppe auf die entsprechende Einstellung zurückgreifen. Bspw.: settings->setValue("HauptFenster/lastFile", fileName );
Damit erreichen Sie das Gleiche, wie eben beschrieben wurde. Natürlich sind Sie hierbei nicht auf Zeichenketten, wie eben gezeigt, angewiesen (schließlich verwenden wir ja QVariant). Bspw. können Sie einen Handler folgendermaßen einrichten, der ausgeführt wird, wenn das Fenster mit dem Schließen-Button geschlossen wird: // Wird ausgeführt, bevor das Fenster geschlossen wird. void MyWindow::closeEvent(QCloseEvent *event) { // zuletzt geöffnete Datei setzen settings->beginGroup("HauptFenster"); settings->setValue("size", size() ); settings->endGroup(); event->accept(); }
366
1542.book Seite 367 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Hierbei handelt es sich um eine protected-Methode der Klasse QWidget (QWidget::closeEvent()). Daher wird diese auch ausgeführt. Im Beispiel wird die Größe des Fensters mithilfe der Methode QWidget::size() in den Schlüssel »size« geschrieben:
Abbildung 5.20
Die Größe des Fensters beim Schließen ins Registry
Unter unixartigen Systemen wurde die Konfigurations-Datei unter ~/.config/Pronix Inc./Qt-Texteditor.conf mit folgendem Inhalt belegt: [HauptFenster] lastFile=/home/vmware/qtexample/chap005/main.cpp size=@Size(725 424)
Alle hier gesetzten Werte werden nun beim Programmstart im Konstruktor überprüft und ggf. verwendet: MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { ... settings = new QSettings( tr("Pronix Inc."), tr("Qt-Texteditor")); settings->beginGroup("HauptFenster"); // QVariant in ein QString konvertieren QString fileName = settings->value( "lastFile").toString(); // QVariant in ein Qsize konvertieren QSize size = settings->value( "size", sizeHint()).toSize(); if (!fileName.isEmpty()) { QFile file(fileName); if (file.open(QFile::ReadOnly | QFile::Text)) { editor->setPlainText(file.readAll());
367
5.2
1542.book Seite 368 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
statusBar()->showMessage( tr("Zuletzt geöffnete Datei " "erfolgreich geladen") ); // Wert wieder setzen settings->setValue("lastFile", fileName ); } else // falls die Datei nicht mehr existiert settings->setValue("lastFile", ""); } else // keine Datei gesetzt settings->setValue("lastFile", ""); settings->endGroup(); // Fenstergröße in Einstellungen vorhanden? if( size.isNull() ) resize(640, 480); // Nein else resize(size); // Ja, dann setzen ... }
Methoden der Klasse QSettings Nach dieser Einführung in die Klasse QSettings beschreiben wir die einzelnen Methoden näher. Methode
Beschreibung
QSettings ( const QString & organization, const QString & application = QString(), QObject * parent = 0 );
Erzeugt ein neues QSettings-Objekt mit den Strings organization und application und dem Eltern-Objekt parent. Als Scope ist QSettings::UserScope und als Format QSettings::NativeFormat voreingestellt.
QSettings ( Scope scope, const QString & organization, const QString & application = QString(), QObject * parent = 0 );
Dito, nur kann hier mit dem ersten Parameter zusätzlich der Scope angegeben werden. Neben dem standardmäßig eingestellten Scope QSettings::UserScope gibt es noch den systemweiten QSettings::SystemScope.
QSettings ( Format format, Scope scope, const QString & organization, const QString & application = QString(), QObject * parent = 0 );
Dito, nur kann hier zusätzlich das Format für die Eintellungen vorgegeben werden. Standardwert ist QSettings::NativeFormat. Mögliche Werte und deren Bedeutung siehe Tabelle 5.27.
Tabelle 5.26
368
Methoden der Klasse QSettings
1542.book Seite 369 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
QSettings ( const QString & fileName, Format format, QObject * parent = 0 );
Erzeugt ein QSettings-Objekt, wo die Einstellungen in einer Datei namens fileName und das Eltern-Objekt parent gespeichert werden. Wenn die Datei noch nicht existiert, wird diese erzeugt. Sollte das Format QSettings::NativeFormat sein, hängt die Bedeutung des Dateinamens fileName von der Plattform ab. Unter Unix ist fileName eine INI-Datei, auf Mac OS X eine .plistDatei, und unter MS-Windows ist der Name ein Pfad zur System-Registry. Mögliche Formate und deren Bedeutung siehe Tabelle 5.27.
QSettings ( QObject *parent = 0 ); Erzeugt ein OSettings-Objekt mit parent als
Eltern-Objekt. Dieser »leere« Konstruktor wird gewöhnlich verwendet, wenn die Organisation und Anwendung bereits global mit den Methoden QCoreApplication::setOrganizationName()
und QCoreApplication::setApplicationName() gesetzt wurden. Dies wurde bereits beschrieben. ~QSettings ();
Destruktor. Zerstört ein QSettings-Objekt.
QStringList allKeys () const;
Gibt eine Stringliste aller Schlüsselwerte (inkl. Unterschlüssel) zurück, die in einem QSettingsObjekt gespeichert sind. Ist eine Gruppe gesetzt, muss zuerst die entsprechende Gruppe mit beginGroup() ausgewählt werden, bevor man allKeys() aufrufen kann.
void beginGroup ( const QString & prefix );
Fügt das Präfix der aktuellen Gruppe hinzu. Standardmäßig ist keine Gruppe gesetzt. Diese Methode benötigen Sie ggf. vor allen Methoden, wo Sie Werte aus den Einstellungen lesen bzw. setzen, sofern Sie Gruppen verwendet haben. Am Ende müssen Sie endGroup() aufrufen, um zur aktuellen Gruppe zurückzukehren, wo Sie vor dem Aufruf von beginGroup() waren. Beachten Sie, dass Gruppen auch verschachtelt werden können. Auch darüber wurde bereits etwas im Abschnitt zu QSettings erwähnt.
int beginReadArray ( const QString & prefix );
Ähnlich wie beginGroup(). Es wird das Präfix zur aktuellen Gruppe hinzugefügt und anschließend von einem Array (Schlüssel/Wert-Paare) eingelesen. Zurückgegeben wird die Größe des Arrays.
Tabelle 5.26
Methoden der Klasse QSettings (Forts.)
369
5.2
1542.book Seite 370 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
void beginWriteArray ( const QString & prefix, int size = –1 );
Das Gegenstück zu beginReadArray(), womit das Präfix der aktuellen Gruppe hinzugefügt und mit der Größe size geschrieben wird. Ist size –1 (Standard), werden so viele Arrays geschrieben, wie Elemente vorhanden sind.
QStringList childGroups () const; Gibt eine Liste mit allen Top-Level-Schlüssel
zurück, die weitere (Unter-)Schlüssel enthalten, woraus mit einem QSettings-Objekt gelesen werden kann. Bspw.: ("Hauptfenster/color",Qt::white) ("PopupColor", Qt::white) Hier wird der Schlüssel "Hauptfenster" zurückgegeben, nicht aber "PopupColor", weil dieser
Schlüssel keine weiteren Schlüssel enthält. QStringList childKeys () const;
Gibt alle Top-Level-Schlüssel zurück, woraus mit einem QSettings-Objekt gelesen werden kann. Bspw.: ("Hauptfenster/color",Qt::white) ("PopupColor", Qt::white) Hier wird der Schlüssel "PopupColor" zurückgegeben, nicht aber "Hauptfenster", weil dieser wei-
tere Unterschlüssel besitzt. void clear ();
Entfernt alle Einträge, die mit dem QSettingsObjekt zusammenhängen. Wollen Sie nur Einträge der aktuellen Gruppe löschen, müssen Sie stattdessen remove("")verwenden.
bool contains ( const QString & key ) const;
Gibt true zurück, wenn der Schlüssel key im aktuellen QSettings-Objekt existiert. Ansonsten wird false zurückgegeben. Wenn eine Gruppe mit beginGroup() gesetzt wurde, ist der Schlüssel relativ zu dieser Gruppe.
void endArray ();
Schließt das mit beginReadArray() oder beginWriteArray() verwendete Array.
void endGroup ();
Stellt die Gruppe wieder her, die vor dem Aufruf von beginGroup() aktiv war.
QString fileName () const;
Gibt den Pfad zurück, wo die Einstellungen vom QSettings-Objekt gespeichert wurden. Unter MS-Windows, wenn das Format QSettings:: NativeFormat ist, ist der Rückgabewert ein Pfad zur Registry und kein Dateipfad. Falls Sie einen speziellen Pfad setzen wollen, können Sie sich die static-Methode setPath() von QSettings ansehen.
Tabelle 5.26
370
Methoden der Klasse QSettings (Forts.)
1542.book Seite 371 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
QString group () const;
Gibt die aktuelle Gruppe zurück.
bool isWritable () const;
Gibt true zurück, wenn sich die Einstellungen des QSettings-Objekts beschreiben lassen. Ansonsten wird false zurückgegeben (bspw. bei einem Readonly-File).
void remove ( const QString & key );
Entfernt alle gesetzten Schlüssel key mitsamt deren Unterschlüssel.
void setArrayIndex ( int i );
Setzt den aktuellen Index des Arrays auf i. Diese Methode wird für das Aufrufen von Methoden wie setValue(), value(), remove() und contains() in Verbindung mit einem Array verwendet. Logischerweise muss zuvor die Methode beginReadArray() oder beginWriteArray() aufgerufen werden, bevor damit gearbeitet werden kann.
void setValue ( const QString & key, const QVariant & value );
Setzt den Schlüssel key mit dem Wert value. Existiert der Schlüssel bereits, wird dieser überschrieben.
Status status () const;
Gibt den Status-Code des QSettings-Objekts zurück. Ist kein Fehler aufgetreten, wird QSettings::NoError zurückgegeben. Weitere Werte sind QSettings::AccessError (Zugriffsfehler, bspw. bei Schreibversuch eines Read-onlyFiles) und QSettings::FormatError (Formatierungs-Fehler).
void sync ();
Schreibt permanent ungespeicherte Änderungen und ladet jede Änderung neu, die während der Zeit gemacht wurde. Sofern Sie QSettings nicht als Kommunikation-Mechanismus zwischen mehreren Prozessen benötigen, brauchen Sie diese Methoden normalerweise nicht.
QVariant value ( const QString & key, const QVariant & defaultValue = QVariant() ) const;
Gibt den Wert für den gesetzten Schlüssel key zurück. Existiert keine Einstellung wird dazu eine Standard-QVariant zurückgegeben.
Tabelle 5.26
Methoden der Klasse QSettings (Forts.)
Jetzt zu den möglichen Werten der enum-Konstante QSettings::Format , mit der Sie das Format zum Speichern mit dem QSettings-Objekt festlegen:
371
5.2
1542.book Seite 372 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Konstante
Beschreibung
QSettings::NativeFormat
Speichert die Einstellung systemtypisch für die Plattform. Unter MS-Windows wird hierbei die Registry, unter Mac OS X die CFPreference und unter Unix normalerweise eine Textdatei im INI-Format verwendet.
QSettings::IniFormat
Speichert die Einstellungen in einer INI-Datei.
QSettings::InvalidFormat
Spezieller Wert, der von der static-Methode registerFormat() zurügegeben wird
Tabelle 5.27
Formate zum Speichern eines QSettings-Objekts
Eigenes Format für die Einstellungen Mit der static-Methode QSettings::registerFormat() ist es möglich, ein eigenes Format für die Einstellungen zu verwenden. Des Weiteren gibt es eine staticMethode setPath(), mit der Sie den Pfad den Einstellungen anpassen können. (Nicht behandelt wurde außerdem der Fallback-Mechanismus. Hier verweise ich Sie auf die Qt-Assistant-Dokumentation von QSettings; siehe auch die Methoden QSettings:: setFallbacksEnabled() und QSettings:: fallbacksEnabled().)
Nun zum versprochenen Listing, das alle in diesem Kapitel bislang beschriebenen Klassen in der Praxis demonstrieren soll. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21
372
// beispiele/qteditor/MyWindow.h #ifndef MyWindow_H #define MyWindow_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
1542.book Seite 373 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
22 class MyWindow : public QMainWindow { 23 Q_OBJECT 24 public: 25 MyWindow( QMainWindow *parent = 0, Qt::WindowFlags flags = 0); 26 QTextEdit* editor; 27 QTextEdit* dock_editor; 28 QAction* act1, *act2, *act3, *act4; 29 QAction *Start; 30 QLabel *sLabel; 31 QLCDNumber *time; 32 QToolBar *toolFile; 33 QToolBar *toolWork; 34 QSettings *settings; 35 public slots: 36 void openFile(); 37 void newFile(); 38 void search(); 39 void leftAlignment(); 40 void rightAlignment(); 41 void justifyAlignment(); 42 void centerAlignment(); 43 void printStart(); 44 void updateStatusBar(); 45 void updateTime(); 46 void showHTML(); 47 void closeEvent(QCloseEvent *event); 48 protected: 49 void contextMenuEvent(QContextMenuEvent *event); 50 }; 51 #endif
Jetzt das eigentliche Hauptprogramm, das recht umfangreich geworden ist. Natürlich finden Sie dieses Beispiel auch auf der Buch-DVD. 00 01 02 03 04 05 06 07 08
// beispiele/qteditor/MyWindow.cpp #include "MyWindow.h" #include #include #include #include #include #include #include
373
5.2
1542.book Seite 374 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
09 // neue Widget-Klasse vom eigentlichen Widget ableiten 10 MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { 11 editor = new QTextEdit; 12 13 14 15 16
17 18
19 20 21 22
23 24 25 26
27 28 29 30 31 32 33 34
374
// das komplette Menü zum Hauptprogramm QMenu *fileMenu = new QMenu(tr("&Datei"), this); menuBar()->addMenu(fileMenu); act1 = fileMenu->addAction( QIcon(":/images/page_white.png"), tr("&Neu"), this, SLOT(newFile()), QKeySequence(tr("Ctrl+N", "Datei|Neu")) ); act1->setStatusTip( tr("Löscht den aktuellen Inhalt der Datei")); act2 = fileMenu->addAction( QIcon(":/images/folder_page_white.png"), tr("&Öffnen..."), this, SLOT(openFile() ), QKeySequence(tr("Ctrl+O", "Datei|Öffnen"))); act2->setStatusTip( tr("Öffnet eine Datei in den Texteditor")); fileMenu->addSeparator(); act3 = fileMenu->addAction( QIcon(":/images/cancel.png"), tr("Be&enden"), qApp, SLOT(quit()), QKeySequence(tr("Ctrl+E", "Datei|Beenden")) ); act3->setStatusTip(tr("Programm beenden")); QMenu *workMenu = new QMenu( tr("&Bearbeiten"), this); menuBar()->addMenu(workMenu); act4 = workMenu->addAction( tr("&Suchen"), this, SLOT(search()), QKeySequence(tr("Ctrl+S", "Bearbeiten|Suchen"))); act4->setStatusTip( tr("Nach einer Stringfolge suchen") ); // Menü mit Linie von der Leiste abnehmbar fileMenu->setSeparatorsCollapsible(true); // mehrere Aktionen erzeugen QAction* underLineAct = new QAction( tr("&Unterstreichen"), this ); underLineAct->setCheckable(true); underLineAct->setShortcut(tr("Ctrl+U")); underLineAct->setStatusTip( tr("Text unterstreichen") );
1542.book Seite 375 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
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
underLineAct->setIcon( QIcon(":/images/text_underline.png")); connect( underLineAct, SIGNAL(triggered(bool)), editor, SLOT(setFontUnderline(bool)) ); QAction *leftAlignAct = new QAction( tr("&Left Align"), this); leftAlignAct->setCheckable(true); leftAlignAct->setShortcut(tr("Ctrl+L")); leftAlignAct->setStatusTip( tr("Text links ausrichten")); leftAlignAct->setIcon( QIcon(":/images/text_align_left.png") ); connect( leftAlignAct, SIGNAL(triggered()), this, SLOT(leftAlignment() ) ); QAction *rightAlignAct = new QAction( tr("&Right Align"), this); rightAlignAct->setCheckable(true); rightAlignAct->setShortcut(tr("Ctrl+R")); rightAlignAct->setStatusTip( tr("Text rechts ausrichten") ); rightAlignAct->setIcon( QIcon(":/images/text_align_right.png")); connect( rightAlignAct, SIGNAL(triggered()), this, SLOT(rightAlignment() ) ); QAction *justifyAct = new QAction( tr("&Justify"), this); justifyAct->setCheckable(true); justifyAct->setShortcut(tr("Ctrl+J")); justifyAct->setStatusTip( tr("Text bündig ausrichten")); justifyAct->setIcon( QIcon(":/images/text_align_justify.png") ); connect( justifyAct, SIGNAL(triggered()), this, SLOT(justifyAlignment() ) ); QAction *centerAct = new QAction( tr("&Center"), this); centerAct->setCheckable(true); centerAct->setShortcut(tr("Ctrl+E")); centerAct->setStatusTip( tr("Text zentriert ausrichten") ); centerAct->setIcon(
375
5.2
1542.book Seite 376 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
60
61 62 63 64 65 66 67 68 69 70 71 72 73 74
75 76
QIcon(":/images/text_align_center.png") ); connect( centerAct, SIGNAL(triggered()), this, SLOT(centerAlignment() ) ); // einige Aktionen zu einer Gruppe zusammenfassen QActionGroup* alignmentGroup = new QActionGroup( this ); alignmentGroup->addAction(leftAlignAct); alignmentGroup->addAction(rightAlignAct); alignmentGroup->addAction(justifyAct); alignmentGroup->addAction(centerAct); leftAlignAct->setChecked(true); // nochmals zwei weitere Aktionen QAction *InZoom = new QAction( tr("&Zoom in ..."), this ); InZoom->setStatusTip(tr("Zoom in the text")); connect( InZoom, SIGNAL(triggered()), editor, SLOT(zoomIn() ) ); QAction *OutZoom = new QAction( tr("&Zoom out ..."), this ); OutZoom->setStatusTip(tr("Zoom out the text")); connect( OutZoom, SIGNAL(triggered() ), editor, SLOT(zoomOut() ) );
77 78 79 80 81 82 83 84 85 86 87
// ein Sub-Menü "Format" bei "Bearbeiten" einfügen QMenu *formatMenu = workMenu->addMenu( tr("&Format") ); formatMenu->addAction(underLineAct); formatMenu->addSeparator(); formatMenu->addAction(leftAlignAct); formatMenu->addAction(rightAlignAct); formatMenu->addAction(justifyAct); formatMenu->addAction(centerAct); formatMenu->addSeparator(); formatMenu->addAction(InZoom); formatMenu->addAction(OutZoom); workMenu->setTearOffEnabled(true); workMenu->setWindowTitle("Bearbeiten");
88 89 90 91 92
// zur Demonstration eine Aktion mit QVariant Start = new QAction(tr("&Programmstart ..."), this); Start->setStatusTip(tr("Zeit des Programmstarts")); Start->setData(QVariant(QTime::currentTime())); QFont font; font.setBold(true);
376
1542.book Seite 377 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
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
Start->setFont(font); Start->setShortcut(tr("Ctrl+T")); Start->setShortcutContext(Qt::ApplicationShortcut); connect( Start, SIGNAL( triggered() ), this, SLOT( printStart() ) ); workMenu->addAction(Start); // eine neue Statusleiste erzeugen und zum // Hauptfenster hinzufügen (void*) statusBar (); // permanente Meldung in der Statusleiste // Zeichen, Wörter, Zeilen sLabel = new QLabel; sLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken ); sLabel->setLineWidth(2); statusBar()->addPermanentWidget(sLabel); connect( editor, SIGNAL( textChanged() ), this, SLOT( updateStatusBar() ) ); updateStatusBar(); // laufend aktuelle Uhrzeit in der Statusleiste time = new QLCDNumber; time->setFrameStyle(QFrame::Panel | QFrame::Sunken); time->setLineWidth(2); time->setSegmentStyle(QLCDNumber::Flat); statusBar()->addPermanentWidget(time); QTimer *timer = new QTimer(this); connect( timer, SIGNAL(timeout()), this, SLOT(updateTime()) ); // jede Sekunde updateTime() aufrufen timer->start(1000); updateTime(); // Werkzeugleiste erzeugen toolFile = addToolBar(tr("Datei")); // Icons mit Text darunter anzeigen toolFile->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); // Werkzeugleiste nur nach links // oder oben verschiebbar toolFile->setAllowedAreas( Qt::LeftToolBarArea | Qt::TopToolBarArea ); // Aktionen zur Werkzeugleiste hinzufügen toolFile->addAction(act1); toolFile->addAction(act2);
377
5.2
1542.book Seite 378 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
toolFile->addAction(act3); // noch eine Werkzeugleiste toolWork = addToolBar(tr("Bearbeiten")); // Werkzeugleiste unten anzeigen addToolBar(Qt::BottomToolBarArea, toolWork ); // Position nicht mehr veränderbar toolWork->setMovable(false); // Aktionen zur Werkzeugleiste hinzufügen toolWork->addAction(underLineAct); toolWork->addSeparator (); toolWork->addAction(leftAlignAct); toolWork->addAction(rightAlignAct); toolWork->addAction(justifyAct); toolWork->addAction(centerAct); toolWork->addSeparator (); // eine Schrift-Combobox zur Werkzeugleiste QFontComboBox *cFont = new QFontComboBox; toolWork->addWidget(cFont); connect( cFont, SIGNAL( currentFontChanged(const QFont& )), editor, SLOT(setCurrentFont ( const QFont&) ) );
149 150
// ein neues Dock-Widget erzeugen QDockWidget *dock = new QDockWidget( tr("WYSIWYG-Editor"), this ); // nur im rechten und linken Bereich erlauben dock->setAllowedAreas( Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea); // Widget für das Dock-Widget erzeugen dock_editor = new QTextEdit; // Editor als Widget im Dock-Fenster verwenden dock->setWidget(dock_editor); // auf der rechten Seite zum Hauptfenster hinzufügen addDockWidget(Qt::RightDockWidgetArea, dock); connect( editor, SIGNAL(textChanged() ), this, SLOT( showHTML() ) );
151 152 153 154 155 156 157 158 159
160 161 162 163 164 165
378
// Einstellungen des Programms speichern. // Im Beispiel nur (ggf.) die zuletzt // geöffnete Datei und die Größe des Fensters settings = new QSettings( tr("Pronix Inc."), tr("Qt-Texteditor") ); settings->beginGroup("HauptFenster"); QString fileName = settings->value("lastFile").toString();
1542.book Seite 379 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
166 167 168 169 170 171
172 173 174 175 176 177 178 179 190 191 192 193 194 195 196 197 }
QSize size = settings->value("size", sizeHint()).toSize(); if (!fileName.isEmpty()) { QFile file(fileName); if (file.open(QFile::ReadOnly | QFile::Text)) { editor->setPlainText(file.readAll()); statusBar()->showMessage( tr("Zuletzt geöffnete Datei " "erfolgreich geladen")); // wieder setzen settings->setValue("lastFile", fileName ); } else // falls die Datei nicht mehr existiert settings->setValue("lastFile", ""); } else // keine Datei gesetzt settings->setValue("lastFile", ""); settings->endGroup(); if( size.isNull() ) resize(640, 480); else resize(size); setCentralWidget(editor); setWindowTitle("QMainWindow – Demo");
198 // Datei öffnen und im Editor anzeigen 199 void MyWindow::openFile() { 200 QString fileName; 201 fileName = QFileDialog::getOpenFileName( this, tr("Datei öffnen"), "", "C++ Dateien (*.cpp *.h);;" "Text-Dateien (*.txt);;" "Alle Dateien (*.*)" ); 202 if (!fileName.isEmpty()) { 203 QFile file(fileName); 204 if (file.open(QFile::ReadOnly | QFile::Text)) { 205 editor->setPlainText(file.readAll()); 206 statusBar()->showMessage( tr("Datei erfolgreich geladen") ); 207 // zuletzt geöffnete Datei setzen 208 settings->beginGroup("HauptFenster"); 209 settings->setValue("lastFile", fileName ); 210 settings->endGroup();
379
5.2
1542.book Seite 380 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
211 212 213 }
} }
214 // Inhalt des Editors löschen 215 void MyWindow::newFile( ) { 216 editor->clear(); 217 } 218 // im Editor nach einer bestimmten Textfolge suchen 219 void MyWindow::search( ) { 220 bool ok; 221 QString text = QInputDialog::getText( this, "Suchdialog", "Text zur Suche eingeben :", QLineEdit::Normal, "Suche eingeben", &ok ); 222 // einen einfachen Fortschrittsbalken zur Suche 223 // in der Statusleiste simulieren 224 QProgressBar* pbar = new QProgressBar; 225 // min. und max. Werte festlegen 226 pbar->setRange( 0, 500 ); 227 statusBar()->addWidget(pbar); 228 for (int i = 0; i < 500; i++) { 229 pbar->setValue(i); 230 for( int j=0; j < 12345678; ++j); 231 //... copy one file 232 } 233 pbar->setValue(500); 234 statusBar()->removeWidget(pbar); 235 if (ok && !text.isEmpty()) 236 editor->find(text); 237 statusBar()->showMessage(tr("Suche beendet"), 3000); 238 } 239 // Absatz links anordnen 240 void MyWindow::leftAlignment() { 241 editor->setAlignment(Qt::AlignLeft); 242 } 243 // Absatz rechts anordnen 244 void MyWindow::rightAlignment() { 245 editor->setAlignment(Qt::AlignRight); 246 } 247 // Absatz im Block ausrichten 248 void MyWindow::justifyAlignment() {
380
1542.book Seite 381 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
249 250 }
editor->setAlignment(Qt::AlignJustify);
251 // Absatz zentrieren 252 void MyWindow::centerAlignment() { 253 editor->setAlignment(Qt::AlignCenter); 254 } 255 // Kontextmenü verwenden. contextMenuEvent 256 // ist eine virtuelle Methode. 257 void MyWindow::contextMenuEvent( QContextMenuEvent *event) { 258 QMenu *menu = new QMenu(this); 259 menu->addAction(act1); 260 menu->addAction(act2); 261 menu->addAction(act3); 262 menu->exec(event->globalPos()); 263 } 264 // Gibt die Uhrzeit des Programmstarts im Texteditor aus. 265 void MyWindow::printStart() { 266 QVariant data = Start->data(); 267 QTime time = data.toTime(); 268 // String daraus machen 269 QString msg( "(Demonstriert setData()) Programmstart: "); 270 msg.append(time.toString("hh:mm:ss")); 271 editor->setText(msg); 272 } 273 // Zeichen, Wörter und Zeilen in der Statusleiste 274 void MyWindow::updateStatusBar() { 275 QString str = editor->toPlainText(); 276 int count_char = str.length(); 277 int count_words = str.count(" "); 278 int count_lines = str.count("\n"); 279 QString label = tr(" Zeichen: %1 Wörter: %2 Zeilen: %3 ") .arg(count_char).arg(count_words).arg(count_lines+1); 280 sLabel->setText(label); 281 } 282 // Zeit in der Statusleiste 283 void MyWindow::updateTime() { 284 // aktuelles Datum und Uhrzeit
381
5.2
1542.book Seite 382 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
285 286 287 288 289 290 291 292 }
QDateTime Time = QDateTime::currentDateTime(); // Formatieren QString text = Time.toString("dd.MM.yyyy hh:mm:ss"); // Anzahl der Ziffern setzen time->setNumDigits(text.size()+1); // und alles Anzeigen time->display(text);
293 // HTML-Format im Dock-Widget anzeigen 294 void MyWindow::showHTML() { 295 QString str = editor->toPlainText(); 296 dock_editor->setHtml(str); 297 } 298 // Wird ausgeführt, bevor das Fenster geschlossen wird. 299 void MyWindow::closeEvent(QCloseEvent *event) { 300 // zuletzt geöffnete Datei setzen 301 settings->beginGroup("HauptFenster"); 302 settings->setValue("size", size() ); 303 settings->endGroup(); 304 event->accept(); 305 }
Qt-Ressourcen-System Bei den Icons wurde in diesem Beispiel auf den Ressourcenmechanismus von Qt zurückgegriffen (deshalb auch die Pfadangabe ":/images/bild.png"). Die entsprechende Ressourcendatei (mit der Endung .qrc) finden Sie auf der Buch-DVD im Verzeichnis, wo sich auch der Code befindet. Mehr zum Ressourcenmechanismus von Qt in Abschnitt 12.7.
Jetzt noch das dazugehörige Hauptprogramm: 00 // beispiele/qteditor/main.cpp 01 #include 02 #include "MyWindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWindow* window = new MyWindow; 06 window->show(); 07 return app.exec(); 08 }
382
1542.book Seite 383 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Das Programm bei der Ausführung:
Abbildung 5.21
5.2.7
Das Hauptprogramm als HTML im Editor
Anwendungen mit MDI-Fenster erstellen (Klasse QWorkspace)
MDI (Multi Document Interface) sind Anwendungen, die neben den üblichen Komponenten eines Hauptfensters wie Menüleiste, Werkzeugleiste, Statusleiste etc. mehrere (Kind-)Fenster beinhalten bzw. anzeigen können. Wobei jedes (Kind-)Fenster ein anderes Widget sein darf. Die einzelnen Widgets wiederum werden über die Menüleiste, Werkzeugleiste etc. gesteuert. Bezogen auf einen Texteditor bspw., ließen sich hiermit mehrere Textdateien auf einmal öffnen und bearbeiten. Um solche MDI-Anwendungen zu erzeugen, stellt Qt die Klasse QWorkspace zur Verfügung. Diese Klasse ist im Grunde wiederum nur ein einfaches Qt-Widget. Im Hauptfenster (QMainWindow) werden solche QWorkspace-Objekte typischerweise zum Zentral-Widget (QMainWindow::setCentralWidget()) gemacht: MainWindow::MainWindow() { workspace = new QWorkspace; setCentralWidget(workspace); ... }
MDI-Fenster mit einem Qt-Widget werden mit QWorkspace::addWindow() dem QWorkspace-Objekt hinzugefügt: workspace->addWindow(child);
383
5.2
1542.book Seite 384 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Mit diesem MDI-Fenster können Sie nun wie mit jedem beliebigen Top-LevelFenster umgehen und Methoden wie show(), hide(), showMaximized() oder setWindowTitle() verwenden: // Fenstertitel für das MDI-Fenster setzen child->setWindowTitle("Fenster-Titel"); // MDI-Fenster als Top-Level-Fenster anzeigen child->show();
Bevor Sie ein MDI-Beispiel sehen, anbei noch ein Überblick über die Methoden der Klasse QWorkspace: Methode
Beschreibung
QWorkspace ( QWidget * parent = 0 );
Erzeugt ein neues QWorkspace-Objekt mit parent als Eltern-Widget.
~QWorkspace ();
Destruktor. Zerstört ein QWorkspace-Objekt.
QWidget * activeWindow () const; Gibt einen Zeiger auf das Widget zurück, welches
das aktive MDI-Fenster der Anwendung ist. Ist kein Fenster aktiv, wird 0 zurückgegeben. QWidget * addWindow ( QWidget * w, Qt::WindowFlags flags = 0 );
Fügt das Widget w als neues Unter-Fenster dem QWorkspace hinzu. Mit flag können Sie optional zusätzliche Optionen für das Widget setzen. Zurückgegeben wird das für den Fensterrahmen benötigte Widget.
QBrush background () const;
Gibt das aktuelle Füllmuster für den Hintergrund zurück.
bool scrollBarsEnabled () const; Gibt true zurück, wenn das MDI-Fenster eine Scrollbar unterstützt. Bei false besitzt das MDI-
Fenster keine Scrollbar. void setBackground ( const QBrush & background );
Setzt das Füllmuster für den Hintergrund auf background.
void setScrollBarsEnabled ( bool enable );
Damit legen Sie fest, ob das MDE-Fenster eine Scrollbar haben darf (true/Standard) oder nicht (false).
QWidgetList windowList ( WindowOrder order = CreationOrder ) const;
Gibt eine Liste aller sichtbaren und minimierten MDI-Fenster zurück. Standardmäßig wird die Liste in der Form zurückgegeben, wie die einzelnen Widgets dem Objekt hinzugefügt wurden. Geben Sie hingegen für order StackingOrder an, werden die Widgets nach der »Sichtbarkeit« zurückgegeben. Zuerst wird das Widget ganz »oben« (Top-Level) bis zum letzten Widget ganz hinten zurückgegeben.
Tabelle 5.28
384
Methoden der Klasse QWorkspace
1542.book Seite 385 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Die Klasse QWorkspace enthält nur ein Signal: Signal
Beschreibung
void windowActivated ( QWidget * w );
Das Signal wird ausgelöst, wenn das MDI-Fenster w aktiviert wurde (den Fokus erhält).
Tabelle 5.29
Signal der Klasse QWorkspace
Jetzt fehlen uns nur noch die Slots der Klasse QWorkspace: Slot
Beschreibung
void activateNextWindow ();
Gibt den Eingabe-Fokus an das nächste MDI-Fenster in der Liste (falls vorhanden).
void activatePreviousWindow ();
Gibt den Eingabe-Fokus an das vorige MDI-Fenster in der Liste (sofern vorhanden).
void arrangeIcons ();
Ordnet alle minimierten MDI-Fenster unten am Workspace an.
void cascade ();
Ordnet alle MDI-Fenster in einem überlappenden Muster an. Siehe Abbildung 5.22.
void closeActiveWindow ();
Schließt das aktive MDI-Fenster (das gerade den Fokus hat).
void closeAllWindows ();
Schließt alle MDI-Fenster.
void setActiveWindow ( QWidget * w );
Setzt das Fenster mit dem Widget w zum aktiven MDI-Fenster.
void tile ();
Ordnet alle MDI-Fenster in einem gekachelten Muster an; siehe Abbildung 5.23.
Tabelle 5.30
Öffentliche Slots der Klasse QWorkspace
Die Klasse QSignalMapper Im anschließenden Beispiel wurde zudem auf die Klasse QSignalMapper zurückgegriffen. Diese Klasse fasst Signale von identifizierbaren Sendern zusammen. Die Klasse sammelt einen ganzen Satz von parameterlosen Signalen, löst diesen erneut mit einem Integer, String oder (in unserem Fall) Widget als Parameter aus und verknüpft ihn mit einem entsprechenden Objekt, welches das Signal gesendet hat. In unserem Beispiel benötigen wir dies, um bei mehreren Fenstern das aktive zu setzen. Mehr zur Klasse QSignalMapper finden Sie mit dem Qt-Assistenten.
Das Beispiel wurde auf das Nötigste einer Anwendung mit MDI-Fenstern beschränkt. Ein noch umfangreicheres und ähnliches Beispiel finden Sie in der Distribution von Qt.
385
5.2
1542.book Seite 386 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Abbildung 5.22
Anordnung der Fenster mit tile()
Zunächst der das Widget (hier wieder eine QTextEdit) darstellende Quellcode, wovon mehrere Fenster in der Hauptanwendung geöffnet bzw. erzeugt werden können. Das Grundgerüst: 00 01 02 03
// beispiele/mdi/mdi.h #ifndef MDI_H #define MDI_H #include
04 class MDIEdit : public QTextEdit { 05 Q_OBJECT 06 public: 07 MDIEdit(); 08 void newFile(); 09 bool loadFile(const QString &fileName); 10 QString userFriendlyCurrentFile(); 11 QString currentFile() { return curFile; } 12 protected: 13 void closeEvent(QCloseEvent *event); 14 private: 15 void setCurrentFile(const QString &fileName); 16 private: 17 QString curFile; 18 }; 19 #endif
386
1542.book Seite 387 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Jetzt die Implementierung der Klasse MDIEdit: 00 // beispiele/mdi/mdi.cpp 01 #include 02 #include "mdi.h" 03 // Konstruktor 04 MDIEdit::MDIEdit() { 05 setAttribute(Qt::WA_DeleteOnClose); 06 } 07 // neues Fenster mit Textdatei erzeugen 08 void MDIEdit::newFile() { 09 static int sequenceNumber = 1; 10 curFile = tr("Textdatei%1.txt").arg(sequenceNumber++); 11 setWindowTitle(curFile + "[*]"); 12 } 13 // neues Fenster mit zu ladender Textdatei erzeugen 14 bool MDIEdit::loadFile(const QString &fileName) { 15 QFile file(fileName); 16 if (!file.open(QFile::ReadOnly | QFile::Text)) { 17 QMessageBox::warning( 18 this, tr("MDI-Demonstration"), 19 tr("Konnte Datei %1:\n%2. nicht lesen") 20 .arg(fileName).arg(file.errorString())); 21 return false; 22 } 23 setPlainText(file.readAll()); 24 setCurrentFile(fileName); 25 return true; 26 } 27 // den Pfad entfernen und nur den Dateinamen zurückgeben 28 QString MDIEdit::userFriendlyCurrentFile() { 29 return QFileInfo(curFile).fileName(); 30 } 31 void MDIEdit::closeEvent(QCloseEvent *event) { 32 // Wird das MDIEdit-Fenster geschlossen, 33 // kann dies hier nochmals abgefangen werden. 34 } 35 // Dateinamen setzen 36 void MDIEdit::setCurrentFile(const QString &fileName)
{
387
5.2
1542.book Seite 388 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
37 38 39 40 }
curFile = QFileInfo(fileName).canonicalFilePath(); setWindowModified(false); setWindowTitle(userFriendlyCurrentFile() + "[*]");
Nun das Grundgerüst für das Hauptfenster, das auch unsere Klasse QWorkspace enthält, um mehrere MDI-Fenster zu erzeugen und anzuzeigen. 00 01 02 03 04 05 06
// beispiele/mdi/mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include "mdi.h"
07 class MainWindow : public QMainWindow { 08 Q_OBJECT 09 public: 10 MainWindow(); 11 protected: 12 void closeEvent(QCloseEvent *event); 13 private slots: 14 void newFile(); 15 void open(); 16 void cut(); 17 void copy(); 18 void paste(); 19 void updateMenus(); 20 void updateWindowMenu(); 21 MDIEdit *createMDIEdit(); 22 private: 23 void createActions(); 24 void createMenus(); 25 void createToolBars(); 26 void createStatusBar(); 27 MDIEdit *activeMDIEdit(); 28 MDIEdit *findMDIEdit(const QString &fileName); 29 30
QWorkspace *workspace; QSignalMapper *windowMapper;
31 32 33
QMenu *fileMenu; QMenu *editMenu; QMenu *windowMenu;
388
1542.book Seite 389 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
34 QToolBar *fileToolBar; 35 QToolBar *editToolBar; 36 QAction *newAct; 37 QAction *openAct; 38 QAction *exitAct; 39 QAction *cutAct; 40 QAction *copyAct; 41 QAction *pasteAct; 42 QAction *closeAct; 43 QAction *closeAllAct; 44 QAction *tileAct; 45 QAction *cascadeAct; 46 QAction *arrangeAct; 47 QAction *nextAct; 48 QAction *previousAct; 49 QAction *separatorAct; 50 }; 51 #endif
Jetzt zur Implementierung des Codes: 00 01 02 03
// beispiele/mdi/mainwindow.cpp #include #include "mainwindow.h" #include "mdi.h"
04 // Konstruktor 05 MainWindow::MainWindow() { 06 workspace = new QWorkspace; 07 workspace->setBackground(QBrush(Qt::Dense7Pattern)); 08 setCentralWidget(workspace); 09 connect( workspace,SIGNAL(windowActivated(QWidget *)), this, SLOT(updateMenus() ) ); 10 windowMapper = new QSignalMapper(this); 11 connect( windowMapper, SIGNAL(mapped(QWidget *) ), workspace, SLOT(setActiveWindow(QWidget *))); 12 createActions(); 13 createMenus(); 14 createToolBars(); 15 createStatusBar(); 16 updateMenus(); 17 setWindowTitle(tr("MDI-Demonstration")); 18 } 19 void MainWindow::closeEvent(QCloseEvent *event)
{
389
5.2
1542.book Seite 390 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
20 21 22 }
// Kann verwendet werden, wenn das Hauptfenster // beendet wird.
23 // Erzeugt ein neues Fenster mit einer leeren Textdatei. 24 void MainWindow::newFile() { 25 MDIEdit *child = createMDIEdit(); 26 child->newFile(); 27 child->show(); 28 } 29 // Erzeugt ein neues Fenster und öffnet eine Textdatei. 30 void MainWindow::open() { 31 QString fileName = QFileDialog::getOpenFileName(this); 32 if (!fileName.isEmpty()) { 33 MDIEdit *existing = findMDIEdit(fileName); 34 if (existing) { 35 workspace->setActiveWindow(existing); 36 return; 37 } 38 MDIEdit *child = createMDIEdit(); 39 if (child->loadFile(fileName)) { 40 statusBar()->showMessage( tr("Datei erfolgreich geladen"), 2000); 41 child->show(); 42 } 43 else { 44 child->close(); 45 } 46 } 47 } 48 // Ausschneiden 49 void MainWindow::cut() { 50 activeMDIEdit()->cut(); 51 } 52 // Kopieren 53 void MainWindow::copy() { 54 activeMDIEdit()->copy(); 55 } 56 // Einfügen 57 void MainWindow::paste() { 58 activeMDIEdit()->paste();
390
1542.book Seite 391 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
59 } 60 // Menü erneuern, wenn sich etwas verändert hat 61 void MainWindow::updateMenus() { 62 bool hasMDIEdit = (activeMDIEdit() != 0); 63 pasteAct->setEnabled(hasMDIEdit); 64 closeAct->setEnabled(hasMDIEdit); 65 closeAllAct->setEnabled(hasMDIEdit); 66 tileAct->setEnabled(hasMDIEdit); 67 cascadeAct->setEnabled(hasMDIEdit); 68 arrangeAct->setEnabled(hasMDIEdit); 69 nextAct->setEnabled(hasMDIEdit); 70 previousAct->setEnabled(hasMDIEdit); 71 separatorAct->setVisible(hasMDIEdit); 72 73 74 75 }
bool hasSelection = activeMDIEdit() && activeMDIEdit()->textCursor().hasSelection(); cutAct->setEnabled(hasSelection); copyAct->setEnabled(hasSelection);
76 // Fenster-Menü erneuern 77 void MainWindow::updateWindowMenu() { 78 windowMenu->clear(); 79 windowMenu->addAction(closeAct); 80 windowMenu->addAction(closeAllAct); 81 windowMenu->addSeparator(); 82 windowMenu->addAction(tileAct); 83 windowMenu->addAction(cascadeAct); 84 windowMenu->addAction(arrangeAct); 85 windowMenu->addSeparator(); 86 windowMenu->addAction(nextAct); 87 windowMenu->addAction(previousAct); 88 windowMenu->addAction(separatorAct); 89 90 91 92 93 94 95 96
QList windows = workspace->windowList(); separatorAct->setVisible(!windows.isEmpty()); for (int i = 0; i < windows.size(); ++i) { MDIEdit *child = qobject_cast<MDIEdit *>(windows.at(i)); QString text; if (i < 9) { text = tr("&%1 %2").arg(i + 1) .arg(child->userFriendlyCurrentFile()); }
391
5.2
1542.book Seite 392 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
97 98
else { text = tr("%1 %2").arg(i + 1) .arg(child->userFriendlyCurrentFile()); } QAction *action = windowMenu->addAction(text); action->setCheckable(true); action ->setChecked(child == activeMDIEdit()); connect( action, SIGNAL(triggered()), windowMapper, SLOT(map() ) ); windowMapper->setMapping(action, child);
99 100 101 102 103 104 105 106 }
}
107 // ein MDI-Fenster erzeugen 108 MDIEdit *MainWindow::createMDIEdit() { 109 MDIEdit *child = new MDIEdit; 110 workspace->addWindow(child); 111 connect( child, SIGNAL(copyAvailable(bool) ), cutAct, SLOT(setEnabled(bool))); 112 connect( child, SIGNAL(copyAvailable(bool)), copyAct, SLOT(setEnabled(bool)) ); 113 return child; 114 } 115 // die Aktionen erzeugen 116 void MainWindow::createActions() { 117 newAct = new QAction( QIcon(":/images/page_white.png"), tr("&Neu"), this); 118 newAct->setShortcut(tr("Ctrl+N")); 119 newAct->setStatusTip(tr("Erzeugt eine neue Datei")); 120 connect( newAct, SIGNAL( triggered() ), this, SLOT(newFile() ) ); 121
122 123 124
125
392
openAct = new QAction( QIcon(":/images/folder_page_white.png"), tr("&Öffnen..."), this); openAct->setShortcut(tr("Ctrl+O")); openAct->setStatusTip( tr("Öffnet eine vorhandene Datei") ); connect( openAct, SIGNAL(triggered()), this, SLOT(open() ) ); exitAct = new QAction( QIcon(":/images/cancel.png"), tr("Be&enden"), this);
1542.book Seite 393 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
126 127 128
exitAct->setShortcut(tr("Ctrl+Q")); exitAct->setStatusTip(tr("Beendet die Anwendung")); connect( exitAct, SIGNAL( triggered() ), qApp, SLOT( closeAllWindows() ) );
129
cutAct = new QAction( QIcon(":/images/cut_red.png"), tr("Ausschneiden"), this); cutAct->setShortcut(tr("Ctrl+X")); cutAct->setStatusTip(tr("Auswahl ausschneiden")); connect( cutAct, SIGNAL(triggered() ), this, SLOT(cut()));
130 131 132
133
134 135 136
137
138 139 140
141 142 143 144
145 146 147
148
copyAct = new QAction( QIcon(":/images/page_copy.png"), tr("&Kopieren"), this); copyAct->setShortcut(tr("Ctrl+C")); copyAct->setStatusTip(tr("Auswahl kopieren")); connect( copyAct, SIGNAL( triggered() ), this, SLOT(copy())); pasteAct = new QAction( QIcon(":/images/table_row_insert.png"), tr("&Einfügen"), this); pasteAct->setShortcut(tr("Ctrl+V")); pasteAct->setStatusTip( tr("Zwischenablage in Editor einfügen")); connect( pasteAct, SIGNAL(triggered()), this, SLOT(paste())); closeAct = new QAction(tr("Schließen"), this); closeAct->setShortcut(tr("Ctrl+F4")); closeAct->setStatusTip( tr("Schließt das aktive Fenster")); connect( closeAct, SIGNAL( triggered() ), workspace, SLOT(closeActiveWindow())); closeAllAct = new QAction( tr("&Alles Schließen"), this); closeAllAct->setStatusTip( tr("Schließt alle Fenster")); connect( closeAllAct, SIGNAL(triggered()), workspace, SLOT(closeAllWindows())); tileAct = new QAction(tr("&Tile"), this);
393
5.2
1542.book Seite 394 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
149 150
tileAct->setStatusTip(tr("Tile the windows")); connect( tileAct, SIGNAL(triggered()), workspace, SLOT(tile()));
151 152 153
cascadeAct = new QAction(tr("&Cascade"), this); cascadeAct->setStatusTip(tr("Cascade the windows")); connect( cascadeAct, SIGNAL(triggered()), workspace, SLOT(cascade()));
154 155 156
arrangeAct = new QAction(tr("Arrange &icons"), this); arrangeAct->setStatusTip(tr("Arrange the icons")); connect( arrangeAct, SIGNAL(triggered()), workspace, SLOT(arrangeIcons()));
157 158
nextAct = new QAction(tr("Ne&xt"), this); nextAct->setStatusTip( tr("Move the focus to the next window")); connect( nextAct, SIGNAL(triggered()), workspace, SLOT(activateNextWindow()));
159
160 161
162 163 164 165 }
previousAct = new QAction(tr("Pre&vious"), this); previousAct->setStatusTip( tr("Move the focus to the previous " "window")); connect( previousAct, SIGNAL(triggered()), workspace, SLOT(activatePreviousWindow())); separatorAct = new QAction(this); separatorAct->setSeparator(true);
166 // Menübar und Elemente erzeugen 167 void MainWindow::createMenus() { 168 fileMenu = menuBar()->addMenu(tr("&Datei")); 169 fileMenu->addAction(newAct); 170 fileMenu->addAction(openAct); 171 fileMenu->addSeparator(); 172 fileMenu->addAction(exitAct); 173 174 175 176
editMenu = menuBar()->addMenu(tr("&Bearbeiten")); editMenu->addAction(cutAct); editMenu->addAction(copyAct); editMenu->addAction(pasteAct);
177 178
windowMenu = menuBar()->addMenu(tr("&Fenster")); updateWindowMenu();
394
1542.book Seite 395 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
179 180 181 }
connect( windowMenu, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu())); menuBar()->addSeparator();
182 // Werkzeugleiste erzeugen 183 void MainWindow::createToolBars() { 184 fileToolBar = addToolBar(tr("Datei")); 185 fileToolBar->addAction(newAct); 185 fileToolBar->addAction(openAct); 186 editToolBar = addToolBar(tr("Bearbeiten")); 187 editToolBar->addAction(cutAct); 188 editToolBar->addAction(copyAct); 189 editToolBar->addAction(pasteAct); 190 } 191 // Statusbar erzeugen 192 void MainWindow::createStatusBar() { 193 statusBar()->showMessage(tr("Bereit")); 194 } 195 // das aktive Fenster zurückgeben 196 MDIEdit *MainWindow::activeMDIEdit() { 197 return qobject_cast<MDIEdit *> (workspace->activeWindow()); 198 } 199 // ein MDI-Fenster mit fileName finden 200 MDIEdit *MainWindow::findMDIEdit( const QString &fileName) { 201 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath(); 202 foreach (QWidget *window, workspace->windowList()) { 203 MDIEdit *MDIEditChild = qobject_cast<MDIEdit *>(window); 204 if ( MDIEditChild->currentFile() == canonicalFilePath ) return MDIEditChild; 205 } 206 return 0; 207 }
395
5.2
1542.book Seite 396 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Qt-Ressourcen-System Auch hier wird für die Icons auf das Ressourcen-System von Qt zurückgegriffen. Sie finden die entsprechende Ressourcen-Datei auch auf der Buch-DVD. In Abschnitt 12.7 beschreiben wir außerdem, was es damit auf sich hat und wie Sie eine solche Datei selbst erstellen.
Jetzt noch das Hauptprogramm: 00 01 02 03
// beispiele/mdi/main.cpp #include #include "mainwindow.h" #include "mdi.h"
04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 MainWindow mainWin; 07 mainWin.show(); 08 return app.exec(); 09 }
Das Programm bei der Ausführung:
Abbildung 5.23
5.2.8
MDI-Anwendung
Übersicht zu den Methoden der Klasse QMainWindow
In der folgenden Tabellen finden Sie jetzt die Methoden, Signale und Slots der Hauptfenster-Klasse QMainWindow zusammengefasst. Zunächst die einzelnen Methoden:
396
1542.book Seite 397 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
void addDockWidget ( Qt::DockWidgetArea area, QDockWidget * dockwidget );
Fügt das übergebene Dock-Widget dockwidget dem Bereich area hinzu. Mögliche Werte für area wurden in der Tabelle 5.23 bereits näher beschrieben.
void addDockWidget ( Qt::DockWidgetArea area, QDockWidget * dockwidget, Qt::Orientation orientation);
Dito, zusätzlich die horizontale bzw. vertikale Ausrichtung orientation
void addToolBar ( Qt::ToolBarArea area, QToolBar * toolbar );
Fügt eine Werkzeugleiste toolbar dem Bereich area hinzu. Mögliche Werte für area wurden bereits in Tabelle 5.17 näher beschrieben.
void addToolBar ( QToolBar * toolbar );
Die überladene Version. Fügt die Werkzeugleiste toolbar hinzu. Dieser Aufruf ist gleichwertig mit: addToolBar(Qt::TopToolBarArea, toolbar)
QToolBar * addToolBar ( const QString & title );
Noch eine überladene Version. Damit wird ein neues Objekt erzeugt mit dem Fenstertitel title und zur Werkzeugleiste im oberen Bereich (Qt::TopToolBarArea).
void addToolBarBreak ( Qt::ToolBarArea area = Qt::TopToolBarArea );
Fügt einen Leerraum in der Werkzeugleiste im Bereich area hinzu. Wird kein Bereich angegeben, wird Qt::TopToolBarArea verwendet.
QWidget * centralWidget () const;
Gibt das Zentral-Widget des Hauptfensters zurück.
Qt::DockWidgetArea corner ( Qt::Corner corner ) const;
Gibt den Bereich des Dock-Widgets zurück, welches die Ecke corner besetzt. Mögliche Werte für corner siehe Tabelle 5.32.
virtual QMenu* createPopupMenu();
Gibt ein Popup-Menü zurück, worin sich ankreuzbare Einträge vom Hauptfenster für die Werkzeugleiste und die Dock-Widgets befinden. Standardmäßig wird diese Methode vom Hauptfenster aufgerufen, wenn der Anwender ein KontextMenü aktiviert (bspw. bei einem Rechtsklick auf einer Werkzeugleiste oder einem Dock-Widget). Sofern Sie ein benutzerdefiniertes Popup-Menü erzeugen wollen, können Sie diese Methode reimplementieren, und es wird das neue Popup-Menü zurückgegeben.
Qt::DockWidgetArea dockWidgetArea ( QDockWidget * dockwidget ) const;
Gibt den Bereich für das Dock-Widget zurück (siehe Tabelle 5.23). Wurde das Dock-Widget dem Hauptfenster noch nicht hinzugefügt, wird Qt::NoDockWidgetArea zurückgegeben.
Tabelle 5.31
Öffentliche Methoden der Klasse QMainWindow
397
5.2
1542.book Seite 398 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
QSize iconSize () const;
Gibt die Größe der Icons in der Werkzeugleiste des Hauptfensters zurück.
void insertToolBar ( QToolBar * before, QToolBar * toolbar );
Fügt eine Werkzeugleiste toolbar vor der Werkzeugleiste before hinzu.
void insertToolBarBreak ( QToolBar * before );
Fügt einen Leerraum in der Werkzeugleiste vor der Werkzeugleiste before hinzu.
bool isAnimated () const;
Mit true (Standardwert) wird zurückgegeben, dass ein Dock-Widget beim Ziehen über dem Bildschirm animiert wird.
bool isDockNestingEnabled () const;
Gibt true (Standardwert) zurück, wenn ein DockWidget in einem bestimmten Bereich veschachtelt (vertikal oder horizontal) bzw. aufgeteilt werden kann. Wird false zurückgegeben, kann in einem Bereich nur ein Dock-Widget dargestellt werden.
QMenuBar * menuBar () const;
Gibt die Menüleiste vom Hauptfenster zurück. Existiert keine Menüleiste, erzeugt diese Methode eine und gibt die leere Leiste zurück.
QWidget * menuWidget () const;
Gibt die Menüleiste vom Hauptfenster zurück. Existiert keine Menüleiste, gibt diese Methode NULL zurück.
void removeDockWidget ( QDockWidget * dockwidget );
Entfernt das Dock-Widget dockwidget vom Hauptfenster und versteckt es. Das Dock-Widget wird allerdings nicht gelöscht.
void removeToolBar ( QToolBar * toolbar );
Entfernt die Werkzeugleiste toolbar vom Hauptfenster und versteckt diese. Die Werkzeugleiste wird allerdings nicht gelöscht.
bool restoreState ( const QByteArray & state, int version = 0 );
Stellt den Zustand der Werkzeugleiste und des Dock-Widgets wieder her. Die Versionsnummer version wird mit dem wiederherstellenden Zustand in state verglichen. Gibt es hierbei keine Übereinstimmung, bleibt das Hauptfenster unverändert, und die Methode gibt false zurück. Ansonsten wird der Zustand des Hauptfensters wiederhergestellt und false zurückgegeben.
QByteArray saveState ( int version = 0 ) const;
Speichert den aktuellen Zustand der Werkzeugleiste und des Dock-Widgets des Hauptfensters. Um den Zustand wiederherzustellen, müssen Sie den Rückgabewert dieser Methode und die Versionsnummer an die Methode restoreState() anpassen.
Tabelle 5.31
398
Öffentliche Methoden der Klasse QMainWindow (Forts.)
1542.book Seite 399 Montag, 4. Januar 2010 1:02 13
Die Klasse QMainWindow
Methode
Beschreibung
void setCorner ( Qt::Corner corner, Qt::DockWidgetArea area );
Setzt ein Dock-Widget im Bereich area zur Ecke corner. Mögliche Werte und deren Bedeutung siehe Tabelle 5.32 und Tabelle5.23.
void setIconSize ( const QSize & iconSize );
Setzt die Größe der Icons in der Werkzeugleiste auf iconSize.
void setMenuBar ( QMenuBar * menuBar );
Setzt die Menüleiste für das Hauptfenster auf menuBar.
void setMenuWidget ( QWidget * menuBar );
Dito
void setStatusBar ( QStatusBar * statusbar );
Setzt die Statusleiste für das Hauptfenster auf statusbar.
void setToolButtonStyle ( Qt::ToolButtonStyle toolButtonStyle );
Setzt die Darstellung der Buttons der Werkzeugleiste auf toolButtonStyle. Mögliche Werte wurden bereits in Tabelle 5.19 gezeigt.
void splitDockWidget ( QDockWidget * first, QDockWidget * second, Qt::Orientation orientation);
Teilt den Raum des Dock-Widgets first in zwei Teile auf und schiebt das erste Dock-Widget first in den ersten Teil und das zweite DockWidget second in den zweiten Teil. Die Ausrichtung der Aufteilung wird mit orientation festgelegt (Qt::Horizontal oder Qt::Vertical).
QStatusBar *statusBar () const;
Gibt die Statusleiste für das Hauptfenster zurück. Diese Methode erzeugt eine neue Statusleiste und gibt diese zurück, falls noch keine existiert.
void tabifyDockWidget ( QDockWidget * first, QDockWidget *second);
Schiebt das Dock-Widget second vor das DockWidget first und erzeugt so einen Tabbed DockBereich im Hauptfenster.
Qt::ToolBarArea toolBarArea ( QToolBar * toolbar ) const;
Gibt den Bereich für die Werkzeugleiste toolbar zurück. Befindet sich keine Werkzeugleiste im Hauptfenster, gibt diese Methode Qt::NoToolBarArea zurück.
Qt::ToolButtonStyle toolButtonStyle () const;
Gibt die aktuell gesetzte Darstellung der Buttons in der Werkzeugleiste zurück. Mögliche Werte wurden bereits in Tabelle 5.19 beschrieben.
Tabelle 5.31
Öffentliche Methoden der Klasse QMainWindow (Forts.)
Nun zu den möglichen Werten der enum-Konstante Qt::Corner, die eine Ecke in einem rechteckigen Bereich angibt.
399
5.2
1542.book Seite 400 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Konstante
Beschreibung
Qt::TopLeftCorner
Ecke oben links
Qt::TopRightCorner
Ecke oben rechts
Qt::BottomLeftCorner
Ecke unten links
Qt::BottomRightCorner
Ecke oben rechts
Tabelle 5.32
Vorhandene Werte in Qt::Corner
Die beiden folgenden öffentlichen Signale sind in der Klasse QMainWindow definiert: Signal
Beschreibung
void iconSizeChanged ( const QSize & iconSize );
Signal wird ausgelöst, wenn die Größe der Icons (in der Werkzeugleiste) verändert wurden. Die neue Größe befindet sich in iconSize.
void toolButtonStyleChanged ( Qt::ToolButtonStyle toolButtonStyle );
Das Signal wird ausgelöst, wenn sich die Darstellung der Buttons verändert hat. Die neue Darstellung befindet sich in toolButtonStyle.
Tabelle 5.33
Signale der Klasse QMainWindow
Jetzt noch die beiden vorhandenen Slots der Klasse QMainWindow: Signal
Beschreibung
void setAnimated ( bool enabled );
Mit false können Sie die standardmäßige Animation des Dock-Widgets abschalten, die beim Ziehen des Dock-Widgets angezeigt wird. Mit true stellen Sie den Standard wieder her.
void setDockNestingEnabled ( bool enabled );
Mit false können Sie abstellen, dass mehrere DockWidgets in einem Bereich aufgeteilt und angezeigt werden. Mit true stellen Sie den Standardzustand wieder her.
Tabelle 5.34
5.3
Öffentliche Slots der Klasse QMainWindow
Fenster aufteilen – QSplitter
Zugegeben, dieses Thema hätte auch gut zu den Layouts gepasst, aber spätestens nach dem Kapitel zu QMainWindow dürfte sich der eine oder andere gefragt haben, wie man benutzerdefinierte vertikale bzw. horizontale Layouts einbaut, die der Anwender selbst anpassen darf. Ein solches Widget bietet Qt mit der Klasse QSplitter an.
400
1542.book Seite 401 Montag, 4. Januar 2010 1:02 13
Fenster aufteilen – QSplitter
Die Klasse QSplitter ist ein Widget mit dem ein Fenster in zwei Teile aufgeteilt wird. Selbstverständlich kann auch das aufgeteilte Fenster mit QSplitter abermals aufgeteilt werden (oder einfach: ein QSplitter kann auch ein QSplitter als Widget enthalten). Gewöhnlich werden mehrere Widgets erzeugt und dem Splitter-Fenster mit insertWidget() oder addWidget() hinzugefügt. Bspw.: split = new QSplitter(Qt::Horizontal); text1 = new QTextEdit("Splitter 1"); text2 = new QTextEdit("Splitter 2"); text3 = new QTextEdit("Splitter 3"); split->addWidget(text1); split->addWidget(text2); split->addWidget(text3);
Hiermit wurde zunächst ein Splitter-Fenster erzeugt. Anschließend werden drei QTextEdit-Objekte erzeugt und daraufhin mit addWidget() zum Splitter hinzugefügt. Mit addWidget() wird also jedes weitere Widget an das vorherige gefügt. In der Praxis sieht dies so aus:
Abbildung 5.24
QSplitter bei der Ausführung
Wie bereits erwähnt, kann diese Klasse auch QSplitter-Objekte hinzufügen, um bspw. ein komplexeres Layout zu erzeugen: split = new Splitter(Qt::Horizontal); sub_split = new Splitter(Qt::Vertical); text1 = new QTextEdit("Splitter 1"); text2 = new QTextEdit("Splitter 2"); text3 = new QTextEdit("Splitter 3"); split->addWidget(text1); split->addWidget(sub_split); sub_split->addWidget(text2); sub_split->addWidget(text3);
Das Beispiel ist recht ähnlich, nur dass man hier einen weiteren vertikalen Splitter erzeugt hat, in den zwei QTextEdit-Objekte eingefügt wurden. Dieser verti-
401
5.3
1542.book Seite 402 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
kale Splitter wiederum wurde zum horizontalen Splitter hinzugefügt. Dadurch ergibt sich folgende Abbildung:
Abbildung 5.25
QSplitter verschachtelt
Bevor hierzu ein Beispiel gezeigt werden soll, zunächst wieder ein Überblick über die Methoden der Klasse. Methode
Beschreibung
QSplitter ( QWidget * parent = 0 );
Erzeugt einen neuen horizontalen Splitter mit dem Eltern-Widget parent, der an den QFrame-Konstruktor angepasst wird.
QSplitter ( Qt::Orientation orientation, QWidget * parent = 0 ) ;
Dito, nur kann mit dem ersten Parameter zusätzlich die Ausrichtung mit angegeben werden.
~QSplitter ();
Destruktor. Zerstört ein Splitter-Objekt.
void addWidget ( QWidget * widget );
Fügt das übergebene Widget hinter dem vorigen Widget in einem neuen Splitter-Fenster hinzu. Existiert das Widget bereits, wird es von der aktuellen Position zur neuen Position verschoben.
bool childrenCollapsible () const;
Wenn diese Methode true (Standardwert) zurückgibt, kann das Kind-Widget im Splitter-Fenster bis zur Größe 0 vom Anwender verkleinert werden. Wird false zurückgegeben, lässt sich das Widget nicht in diesem Ausmaß verkleinern.
int count () const
Gibt die Anzahl der Widgets zurück, die im Splitter-Layout enthalten sind.
void getRange ( int index, int * min, int * max ) const;
Gibt den erlaubten Bereich des Splitter-Fensters mit dem Index index zurück. Die Werte werden in *min (für minimal) und *max (für maximal) gespeichert, wenn min und max nicht 0 sind.
Tabelle 5.35
402
Öffentliche Methoden der Klasse QSplitter
1542.book Seite 403 Montag, 4. Januar 2010 1:02 13
Fenster aufteilen – QSplitter
Methode
Beschreibung
QSplitterHandle * handle ( int index ) const;
Gibt den Splitter-Handle von der linken (oder oberen) Seite des Elements mit dem übergebenen Index index zurück. Der Handle vom Index 0 ist immer versteckt. Mehr zu QsplitterHandle erfahren Sie in Abschnitt 5.3.1.
int handleWidth () const;
Damit wird die Breite vom Handle des SplitterFenster zurückgegeben.
int indexOf ( QWidget *widget) const;
Gibt die Indexnummer des Widgets widget vom Splitter-Layout zurück.
void insertWidget ( int index, QWidget * widget );
Wie addWidget(), nur wird das Widget an der Position index eingefügt. Ist index außerhalb eines gültigen Bereichs, wird das Widget am Ende (wie addWidget()) hinzugefügt.
bool isCollapsible ( int index ) const;
Gibt true zurück, wenn das Widget mit dem Index index komplett zusammenklappbar ist. Ansonsten wird false zurückgegeben.
Qt::Orientation orientation () const;
Damit erhalten Sie die Ausrichtung vom Splitter. Per Standard ist die Ausrichtung horizontal. Mögliche Werte sind Qt::Horizontal und Qt::Vertical.
void refresh ();
Erneuert den Zustand des Splitter-Layouts. Diese Methode wird im Allgemeinen nicht benötigt.
bool restoreState ( const QByteArray & state );
Stellt das Layout eines Splitters wieder her. Diese Methode wird gewöhnlich mit der Klasse QSettings (siehe Abschnitt 5.2.6) verwendet, um die Größe der einzelnen Splitter wiederherzustellen, wie diese bei der letzten Sitzung verwendet wurden (siehe auch Programmlisting).
QByteArray saveState () const;
Speichert das Layout eines Splitter-Objekts. Diese Methode wird in der Praxis normalerweise mit QSettings verwendet, um die Größe der einzelnen Splitter zu speichern (siehe hierzu Programmlisting).
void setChildrenCollapsible ( bool );
Mit true (Standardwert) können Sie setzen, dass ein Splitter komplett zusammenklappbar ist. Mit false können Sie es verhindern.
void setCollapsible ( int index, bool collapse );
Wie setChildrenCollapsible(), nur dass Sie hiermit einen bestimmten Splitterbereich mit dem Index index angeben können. Damit können Sie bspw. verhindern, dass ein Widget auf keinen Fall zusammengeklappt werden darf.
Tabelle 5.35
Öffentliche Methoden der Klasse QSplitter (Forts.)
403
5.3
1542.book Seite 404 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
void setHandleWidth ( int );
Damit können Sie die Breite des Handle angeben.
void setOrientation ( Qt::Orientation ) ;
Damit wird die Ausrichtung des Splitter-Layouts gesetzt. Mögliche Werte sind Qt::Horizontal und Qt::Vertical.
void setSizes ( const QList & list );
Setzt den Wert des Größenparameters in einer Liste. Ist das Splitter-Layout horizontal, werden die Werte für die Weite von links nach rechts für jedes Widget aus list verwendet. Ist das Layout hingegen vertikal, werden die Werte für die Höhe von oben nach unten verwendet. Sind mehr Werte in list als Widgets vorhanden, werden die überflüssigen Werte ignoriert.
void setStretchFactor ( int index, int stretch );
Erneuert die Größeneigenschaft des Widgets an der Position index und setzt den Streckungsfaktor stretch dafür.
QList sizes () const;
Gibt die Liste von Größenparametern für das Splitter-Layout zurück (siehe setSizes()).
QWidget * widget ( int index ) const;
Gibt das Widget mit dem Index index aus dem Splitter-Layout zurück.
Tabelle 5.35
Öffentliche Methoden der Klasse QSplitter (Forts.)
Eigene Slots bietet die Klasse QSplitter nicht an, dafür aber ein Signal mit void QSplitter::splitterMoved(int pos,int index). Das Signal wird ausgelöst, wenn der Handle des Splitters verschoben wurde. In den Parametern finden Sie die neue Position (pos) und den Index (index), dessen Splitter-Layout verschoben wurde.
5.3.1
Splitter-Handle – QSplitterHandle
Auch der Handle selbst wurde separat mit der Klasse QSplitterHandle implementiert. In der Praxis dürfte für die meisten Entwicklern der Handle von QSplitter ausreichend sein. Sollten Sie aber extra Funktionalitäten für den Handle hinzufügen, steht Ihnen die Klasse QSplitterHandle zur Verfügung. Handle == Griff Ich war mir nicht ganz sicher, ob ich den »Handle« zwischen den Splitter-Layouts als »Griff« übersetzen sollte (was aber irgendwie auch seltsam klingt).
Um den Handle des Splitters also zu erweitern, wird üblicherweise die protected-Methode QSplitter::createHandle() verwendet, um eine von
404
1542.book Seite 405 Montag, 4. Januar 2010 1:02 13
Fenster aufteilen – QSplitter
QSplitterHandle abgeleitete Klasse zu instanzieren. Eine solche Unterklasse von QSplitter könnte bspw. folgendermaßen aussehen: class Splitter : public QSplitter { public: Splitter( Qt::Orientation orientation, QWidget *parent = 0 ); protected: QSplitterHandle *createHandle(); };
Die Implementation des createHandle(), womit ein benutzerdefinierter Splitter-Handle erzeugt wird, könnte folgendermaßen aussehen: QSplitterHandle *Splitter::createHandle() { return new SplitterHandle(orientation(), this); }
Natürlich wird hierbei auch davon ausgegangen, dass die Klasse SplitterHandle von der Klasse QSplitterHandle abgeleitet wurde. Im folgenden Beispiel wird das Handle des Splitter-Layouts in zweifacher Ausführung angepasst. Zunächst habe ich die protected-Methode QWidget::paintEvent(QPaintEvent* event) verwendet, um das Aussehen des Handle zu verän-
dern. Zum Zeichnen in das Handle habe ich wiederum die Klasse QLinearGradient verwendet. Zusätzlich wurde die protected-Methode QWidget::mouseDoubleClickEvent(QMouseEvent * event) verwendet. Diese wurde dazu verwendet, um per Doppelklick auf dem Handle ein ganzes Splitterfenster zusammen- bzw. wieder auseinanderzuklappen. Hierzu also zunächst wieder das Grundgerüst, das allerdings in diesem Fall schon etwas vorimplementiert ist, um nicht den Quellcode in viele Einzeldateien aufzusplittern. 00 01 02 03 04 05 06 07 08 09
// beispiele/qsplitter/mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include
405
5.3
1542.book Seite 406 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
10 // benutzerdefinierter Splitter-Handle 11 class SplitterHandle : public QSplitterHandle { 12 Q_OBJECT 13 public: 14 int last_pos; 15 SplitterHandle( Qt::Orientation o, QSplitter *parent = 0 ) : QSplitterHandle(o, parent) { last_pos = 0; }; 16 protected: 17 // eigenes Layout für das Handle 18 void paintEvent(QPaintEvent *event) { 19 QPainter painter(this); 20 QLinearGradient gradient; 21 if (orientation() == Qt::Horizontal) { 22 gradient.setStart( rect().left(), rect().height()/2 ); 23 gradient.setFinalStop( rect().right(), rect().height()/2 ); 24 } 25 else { 26 gradient.setStart( rect().width()/2, rect().top() ); 27 gradient.setFinalStop( rect().width()/2, rect().bottom() ); 28 } 29 painter.fillRect( event->rect(), QBrush(gradient) ); 30 } 31 32 33 34 35 36 37
// per Doppelklick auf dem Handle ein // Splitter-Objekt schließen void mouseDoubleClickEvent(QMouseEvent *e) { QSplitter* s = splitter(); Qt::Orientation orient = s->orientation(); int pos = s->indexOf(this); QWidget* w = s->widget(pos);
38 39 40 41 42 43 44
if( last_pos == 0 ) { if( orient == Qt::Horizontal ) last_pos = w->sizeHint().width(); else // Qt::Vertical last_pos = w->sizeHint().height(); } int cur = s->sizes().value(pos-1);
406
1542.book Seite 407 Montag, 4. Januar 2010 1:02 13
Fenster aufteilen – QSplitter
45 46 47 48 49 50 51 52 };
if(cur == 0 ) moveSplitter(last_pos); else { moveSplitter(0); last_pos = cur; } }
53 // eigene Splitter-Klasse nötig für das Splitter-Handle 54 class Splitter : public QSplitter { 55 Q_OBJECT 56 public: 57 friend class SplitterHandle; 58 Splitter( Qt::Orientation o, QSplitter *parent = 0 ) : QSplitter(o, parent) {}; 59 Splitter( QSplitter *parent = 0 ) : QSplitter( parent) {}; 60 protected: 61 QSplitterHandle *createHandle(); 62 }; 63 class MainWindow : public QMainWindow { 64 Q_OBJECT 65 public: 66 MainWindow(); 67 void setSettings(); 68 protected: 69 void closeEvent(QCloseEvent *event); 70 public: 71 Splitter *split, *sub_split; 72 QTextEdit* text1, *text2, *text3; 73 }; 74 #endif
Jetzt noch die Implementation des Quellcodes: 00 01 02 03
// beispiele/qtsplitter/mainwindow.cpp #include #include #include "mainwindow.h"
04 // Konstruktor 05 MainWindow::MainWindow() { 06 split = new Splitter(Qt::Horizontal);
407
5.3
1542.book Seite 408 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
07 08 09 10 11 12 13 14 15
sub_split = new Splitter(Qt::Vertical); text1 = new QTextEdit("Splitter 1"); text2 = new QTextEdit("Splitter 2"); text3 = new QTextEdit("Splitter 3"); split->addWidget(text1); split->addWidget(sub_split); sub_split->addWidget(text2); sub_split->addWidget(text3); setCentralWidget(split);
16 17 18 }
setSettings(); setWindowTitle(tr("QSplitter – Demonstration"));
19 // beim Schließen die Größe der 20 // Splitter-Layouts speichern 21 void MainWindow::closeEvent(QCloseEvent *event) { 22 QSettings settings("Qt-Buch", "Splitterdemo"); 23 settings.setValue("Splitter1Size",split->saveState()); 24 settings.setValue( "Splitter2Size", sub_split->saveState() ); 25 } 26 // die Größe des Splitter-Layouts wiederherstellen 27 void MainWindow::setSettings() { 28 QSettings settings("Qt-Buch", "Splitterdemo"); 29 split->restoreState( settings.value("Splitter1Size").toByteArray() ); 30 sub_split->restoreState( settings.value("Splitter2Size").toByteArray() ); 31 } 32 33 34 35 36
// eine Instanz des Splitter-Handle mit // benutzerdefinierten Eigenschaften erzeugen QSplitterHandle *Splitter::createHandle() { return new SplitterHandle(orientation(), this); }
Jetzt nur noch das Hauptprogramm: #include #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWin;
408
1542.book Seite 409 Montag, 4. Januar 2010 1:02 13
Scrolling Area – QScrollArea
mainWin.show(); return app.exec(); }
Das Programm bei der Ausführung:
Abbildung 5.26
5.4
QSplitter bei der Ausführung
Scrolling Area – QScrollArea
Die Klasse QScrollArea ist von QAbstractScrollbar abgeleitet und stellt einen scrollenden Bereich auf ein anderes Widget zur Verfügung. Dieser scrollende Bereich wird verwendet, um den Inhalt eines Kinder-Widgets innerhalb eines Rahmens anzuzeigen. Ist dieses Widget größer als die Größe des Rahmens, wird zur Ansicht eine Scrollleiste hinzugefügt, so dass das ganze Widget angesehen werden kann (in dem es eben gescrollt wird). Das Kind-Widget muss hierbei mit der Methode QScrollArea::setWidget() gesetzt werden. Ein einfaches Beispiel. Sie haben einen einfachen Bilderbetrachter, der über die Klasse QLabel ein Bild (Pixmap) darstellt:
Abbildung 5.27
Einfacher Bildbetrachter mit QLabel
409
5.4
1542.book Seite 410 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Diesem Bildbetrachter fügen Sie nun eine Funktion ein, womit Sie das Bild skalieren (hinein- und hinauszoomen) können. Würden Sie dieses Skalieren des Bildes ohne Scrollbereich realisieren und in das Bild hineinzoomen, erhielten Sie folgendes Bild:
Abbildung 5.28
Bild ohne Scrollbereich skaliert
Hiermit erhalten Sie praktisch nur einen Bildausschnitt der linken oberen Ecke des Bildes, was wohl nicht die Absicht des Programmierers war. Und hierzu ergibt ein Scrollbereich der Klasse QScrollArea eben einen Sinn. Verwenden Sie bspw. hierzu einen Scrollbereich, dürfte das einem Bildbetrachter schon etwas näher kommen:
Abbildung 5.29
Bild mit einem Scrollbereich (QScrollArea)
Jetzt können Sie in das Bild hinein- bzw. herauszoomen, wie Sie wollen. Natürlich soll das Beispiel nicht darüber hinwegtäuschen, dass die Klasse QScrollArea nur für die Darstellung von Bildern verwendet wird. Nein, die Klasse kann bei jedem beliebigen Widget verwendet werden, wo es passieren kann, dass der Anwender nicht alles auf dem grundlegenden Bildschirm zu Gesicht bekommt.
410
1542.book Seite 411 Montag, 4. Januar 2010 1:02 13
Scrolling Area – QScrollArea
Das Erscheinungsbild der Scrollbalken hängt von der aktuellen Einstellung (enum Qt::ScrollBarPolicy) ab. Dieses Erscheinungsbild können Sie auch selbst mit den von QAbstractScrollBar vererbten Methoden setzen. Standardmäßig ist das Erscheinungsbild auf Qt::ScrollBarAsNeeded eingestellt. Dies bedeutet, dass nur dann eine Scrollleiste mit Balken angezeigt wird, wenn der Inhalt zu groß ist, um angepasst zu werden. Folgende Werte sind hierbei in der enum-Konstante Qt::ScrollBarPolicy definiert: Konstante
Beschreibung
Qt::ScrollBarAsNeeded
Scrollleiste mit Balken erscheint, sobald der Inhalt zu groß ist, um angepasst zu werden. Ansonsten erscheint die Scrollleiste mit Balken nicht.
Qt::ScrollBarAlwaysOff
Es wird niemals eine Scrollleiste mit Balken angezeigt. Dies bedeutet allerdings nicht, dass das Widget nicht gescrollt werden kann. Es wird eben einfach keine Leiste angezeigt.
Qt::ScrollBarAlwaysOn
Egal, ob der Inhalt passt oder nicht, es wird immer eine Scrollleiste mit Balken angezeigt.
Tabelle 5.36
Konstanten, die das Erscheinungsbild beeinflussen
Abfragen bzw. verändern können Sie diese Werte mit den folgenden Methoden der Basisklasse QAbstractScrollArea: // horizontale Scrollleiste abfragen Qt::ScrollBarPolicy horizontalScrollBarPolicy () const; // vertikale Scrollleiste abfragen Qt::ScrollBarPolicy verticalScrollBarPolicy () const; // horizontale Scrollleiste neu setzen void setHorizontalScrollBarPolicy ( Qt::ScrollBarPolicy ); // vertikale Scrollleiste neu setzen void setVerticalScrollBarPolicy ( Qt::ScrollBarPolicy );
Darüber hinaus gibt es mehrere Methoden in der Basisklasse QAbstractScrollArea, die allerdings eher selten benötigt werden. Die folgende Tabelle bietet
eine Zusammenstellung der gängigen öffentlichen Methoden der Basisklasse QAbstractScrollArea. protected-Methoden in der Basisklasse QAbstract-ScrollArea Es existiert eine Menge von protected-Methoden in der Basisklasse QAbstractScrollArea, die vorwiegend in Verbindung mit Maus-Ereignissen verwendet werden.
Hierzu sei bei Bedarf aber die Dokumentation empfohlen.
411
5.4
1542.book Seite 412 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Methode
Beschreibung
QAbstractScrollArea ( QWidget * parent = 0 );
Erzeugt ein neues Darstellungsfeld mit parent als Eltern-Widget.
~QAbstractScrollArea ();
Destruktor. Zerstört das Darstellungsfeld.
void addScrollBarWidget ( QWidget * widget, Qt::Alignment alignment);
Fügt das Widget als Scrollleisten-Widget in der durch die Anordnung alignment angegebenen Position hinzu.
QScrollBar * horizontalScrollBar () const;
Gibt die horizontale Scrollleiste zurück.
QWidgetList scrollBarWidgets ( Qt::Alignment alignment );
Gibt eine Liste der aktuelle gesetzten ScrollleistenWidgets zurück.
void setHorizontalScrollBar ( QScrollBar * scrollBar );
Ersetzt die bereits existierende horizontale Scrollleiste mit scrollBar. Die Klasse QAbstractScrollArea bietet ja bereits per Standard eine horizontale und vertikale Scrollleiste an. Mit dieser Methode können Sie die Standard-Scrollleiste durch eine eigene benutzerdefinierte Scrollleiste ersetzen.
void setVerticalScrollBar ( QScrollBar * scrollBar );
Dito, nur eben für die vertikale Scrollleiste.
QScrollBar * verticalScrollBar () const;
Gibt die vertikale Scrollleiste zurück.
Tabelle 5.37
Öffentliche Methoden der Klasse QAbstractScrollArea
Bevor Sie hierzu das Beispiel sehen, sollen alle öffentlichen Methoden der Klasse QScrollArea beschrieben werden. Methode
Beschreibung
QScrollArea ( QWidget * parent = 0 );
Erzeugt einen leeren Scroll-Bereich mit parent als Eltern-Widget.
~QScrollArea ();
Destruktor. Zerstört einen Scroll-Bereich.
Qt::Alignment alignment () const;
Gibt die Ausrichtung des Widgets im Scroll-Bereich zurück.
void ensureVisible ( int x, int y, int xmargin = 50, int ymargin = 50 );
Scrollt den Inhalt des Scroll-Bereichs, so dass der Punkt (x, y) innerhalb des Bereichs des Darstellungfeldes (Viewport) mit Rändern (angegeben in Pixel) durch xmargin und ymargin sichtbar ist. Wenn der angegebene Punkt nicht erreicht werden kann, wird der Inhalt zur nächsten gültigen Position ge-scrollt. Der Standard-Wert für die Rahmen beider Ränder ist 50 Pixel.
Tabelle 5.38
412
Öffentliche Methoden der Klasse QScrollArea
1542.book Seite 413 Montag, 4. Januar 2010 1:02 13
Scrolling Area – QScrollArea
Methode
Beschreibung
void ensureWidgetVisible ( QWidget * childWidget, int xmargin = 50, int ymargin = 50 );
Dito, nur wird anstelle des Punkts (x, y ) das Widget childWidget verwendet.
void setAlignment ( Qt::Alignment );
Damit setzen Sie die Ausrichtung des Widgets im Scroll-Bereich. Per Standard befindet sich das Widget an der oberen linken Ecke im Scroll-Bereich. Mögliche Werte für Qt::Alignment siehe Tabelle 4.8, 4.9 und 4.10.
void setWidget ( QWidget * widget );
Damit setzen Sie das Widget für den Scroll-Bereich. Das Widget wird zum Kind-Widget im ScrollBereich und wird auch bei einer Löschung des Scroll-Bereichs oder beim Setzen eines neuen Widgets zerstört.
void setWidgetResizable ( bool resizable );
Mit true können Sie dafür sorgen, dass die Größe des Scroll-Bereichs automatisch an die Größe des Widgets angepasst wird. So vermeidet man, dass eine Scrollleiste mit Balken angezeigt wird. Der hiermit gewonnene Platz kann separat verwendet werden. Der Standardwert ist false, womit bei größeren Widgets (als angezeigt werden kann) eine Scrollleiste mit Balken angezeigt wird.
QWidget * takeWidget ();
Entfernt das Widget des Scroll-Bereichs und gibt es an den Aufrufer der Methode zurück.
QWidget * widget () const;
Gibt einen Zeiger auf das Widget des Scroll-Bereichs zurück.
bool widgetResizable () const;
Siehe setWidgetResizable().
Tabelle 5.38
Öffentliche Methoden der Klasse QScrollArea (Forts.)
Nun zum Programmbeispiel. Im Beispiel wird ein einfaches größeres Bild geladen und als QLabel im Scroll-Bereich (QScrollArea) als Widget mit QScrollArea::setWidget() gesetzt. Dieses Label können Sie jetzt mit der Tastenkombi-
nation (oder auch über das Menü Ansicht) (Strg)+(+) und (Strg)+(-) hineinbzw. herauszoomen. Bilderbetrachtungssoftware erstellen Wollen Sie nun einen kleinen Bilderbetrachter erstellen, sollten Sie sich den ImageViewer ansehen, der als Beispiel der Qt-Distribution beiliegt. Natürlich habe ich mich dadurch zum folgenden Programmbeispiel inspirieren lassen.
413
5.4
1542.book Seite 414 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08
// beispiele/qscrollarea/mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include
09 class MainWindow : public QMainWindow { 10 Q_OBJECT 11 public: 12 MainWindow(); 13 void createActions(); 14 void scaleImage(double factor); 15 void adjustScrollBar( QScrollBar *scrollBar, double factor ); 16 private: 17 QLabel* label; 18 QScrollArea *area; 19 QAction* zoomOutAct; 20 QAction* zoomInAct; 21 double scaleFactor; 22 public slots: 23 void zoomIn(); 24 void zoomOut(); 25 }; 26 #endif
Jetzt noch die Implementierung des Codes: 00 01 02 03
// beispiele/qscrollarea/mainwindow.cpp #include #include #include "mainwindow.h"
04 // Konstruktor 05 MainWindow::MainWindow() { 06 label = new QLabel; 07 label->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored); 08 label->setScaledContents(true); 09 QImage image(QString("%1 %2")
414
1542.book Seite 415 Montag, 4. Januar 2010 1:02 13
Scrolling Area – QScrollArea
10
.arg(QCoreApplication::applicationDirPath()) .arg("/images/krokodil.png")); label->setPixmap(QPixmap::fromImage(image));
11 12 13 14 15 16
area = new QScrollArea; area->setBackgroundRole(QPalette::Dark); area->setWidget(label); setCentralWidget(area); createActions(); scaleFactor = 1.0;
17 18 19 20 21 22 }
QMenu* viewMenu = new QMenu(tr("&Ansicht"), this); viewMenu->addAction(zoomInAct); viewMenu->addAction(zoomOutAct); menuBar()->addMenu(viewMenu); setWindowTitle(tr("QScrollArea – Demonstration"));
23 void MainWindow::createActions() { 24 zoomInAct = new QAction(tr("Zoom &In (25 %)"), this); 25 zoomInAct->setShortcut(tr("Ctrl++")); 26 connect( zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()) ); 27 zoomOutAct = new QAction(tr("Zoom &Out (25 %)"), this); 28 zoomOutAct->setShortcut(tr("Ctrl+-")); 29 zoomOutAct->setEnabled(true); 30 connect( zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()) ); 31 } 32 void MainWindow::zoomIn() { 33 scaleImage(1.25); 34 } 35 void MainWindow::zoomOut() { 36 scaleImage(0.75); 37 } 38 // Bild (Label) skalieren 39 void MainWindow::scaleImage(double factor) { 40 scaleFactor *= factor; 41 label->resize(scaleFactor * label->pixmap()->size()); 42 // horizontale und vertikale Scrollbalken // mittig setzen 43 adjustScrollBar(area->horizontalScrollBar(), factor);
415
5.4
1542.book Seite 416 Montag, 4. Januar 2010 1:02 13
5
Qt-Hauptfenster
44 45 46 47 48 }
adjustScrollBar(area->verticalScrollBar(), factor); // maximale und minimale Grenze für das Skalieren zoomInAct->setEnabled(scaleFactor < 3.0); zoomOutAct->setEnabled(scaleFactor > 0.333);
49 // Scrollbar mittig setzen 50 void MainWindow::adjustScrollBar( QScrollBar *scrollBar, double factor) { 51 scrollBar->setValue( int(factor * scrollBar->value() + ((factor – 1) * scrollBar->pageStep()/2))); 52 }
Schließlich noch das Hauptprogramm: 00 // beispiele/qscrollarea/main.cpp 01 #include 02 #include "mainwindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MainWindow mainWin; 06 mainWin.show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung haben Sie in den Abbildungen schon des Öfteren gesehen, weshalb ich mir hier einen Screenshot erspare.
416
1542.book Seite 417 Montag, 4. Januar 2010 1:02 13
In diesem Kapitel erfahren Sie, wie Sie mit dem Qt-Framework aus Dateien lesen oder darin schreiben können, wie Sie ein Dateisystem durchlaufen oder Informationen zu Dateien bzw. Verzeichnissen ermitteln und wie Sie mit anderen Prozessen kommunizieren (auch über die lokalen Grenzen hinaus). Außerdem wird ein wenig auf die relationalen Datenbanken eingegangen, die Qt unterstützt.
6
Ein-/Ausgabe von Daten
Jeder, der gerne portable Software erstellt, hat bei der Ein-/Ausgabe meist mit Problemen zu tun. Zwar bieten die Betriebssysteme gute Mechanismen dafür an, doch sind diese leider weniger portabel. Häufig war man als Programmierer gezwungen, den Code für die Ein-/Ausgabe dem System anzupassen. Musste man dann noch etwas auf einem lokalen und etwas anderes auf einem entfernten Rechner einlesen bzw. ausgeben, war der Aufwand nicht unerheblich.
6.1
Schnittstelle für alle E/A-Geräte – QIODevice
Mit der Schnittstelle der Basisklasse QIODevice soll dies nun der Vergangenheit angehören. Diese Klasse stellt eine Basisschnittstelle für alle Klassen da, die auf ein Ein-/Ausgabegerät zugreifen. Dabei macht es keinen Unterschied, ob es sich um eine Netzwerkverbindung über TCP oder UDP handelt, eine einfache Datei, einen Puffer oder einen anderen Prozess. Die Klasse QIODevice sieht alles als Gerät an, auf dem geschrieben oder gelesen werden kann.
QIODevice
QBuffer
QFile
QTemporaryFile
Abbildung 6.1
QProcess
QAbstractSocket
QUdpSocket
QTcpSocket
Basisklasse QIODevice und die davon abgeleiteten Klassen
417
1542.book Seite 418 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
In Abbildung 6.1 können Sie die Hierarchie der Ein-/Ausgabe-Klasse QIODevice sehen. In der Praxis wird diese Basisklasse allerdings niemals direkt instanziert. Natürlich bietet QIODevice als Basisklasse für die abgeleiteten Klassen gleich einige Methoden mit an, die in allen davon abgeleiteten Klassen (siehe Abbildung 6.1) verwendet werden können. Bevor also auf die von QIODevice abgeleiteten Klassen eingegangen wird, werden in der folgenden Tabelle schon mal die öffentlichen Methoden der Klasse QIODevice aufgelistet, die Sie in den anschließenden Beispielen fallweise wiederfinden. Methode
Beschreibung
QIODevice ();
Erzeugt ein QIODevice-Objekt.
QIODevice ( QObject * parent ); Erzeugt ein QIODevice-Objekt mit parent als Eltern-
Widget. QString errorString () const;
Gibt eine lesbare Beschreibung des letzten GeräteFehlers, der aufgetreten ist, zurück.
bool getChar ( char * c );
Liest ein Zeichen vom Gerät und speichert es in c. Ist c gleich 0, wird das Zeichen verworfen. Bei Erfolg wird true, ansonsten false zurückgegeben.
bool isOpen () const;
Gibt true zurück, wenn das Gerät geöffnet ist. Ansonsten wird false zurückgegeben. Ein Gerät ist geöffnet, wenn daraus gelesen oder/und geschrieben werden kann.
bool isReadable () const;
Gibt true zurück, wenn Daten vom Gerät gelesen werden können. Ansonsten wird false zurückgegeben.
bool isTextModeEnabled () const;
Gibt true zurück, wenn das Text-Flag gesetzt ist. Ansonsten wird false zurückgegeben.
bool isWritable () const;
Gibt true zurück, wenn auf das Gerät geschrieben werden kann. Ansonsten wird false zurückgegeben.
OpenMode openMode () const;
Gibt den Modus zurück, womit das Gerät geöffnet wurde. Mögliche Werte siehe Tabelle 6.2.
qint64 peek ( char * data, qint64 maxSize );
Liest höchstens maxSize Bytes vom Gerät in data ohne sonstigen Effekt ein (bspw. kann anschließend read() aufgerufen werden, und es werden dieselben Daten zurückgegeben). Zurückgegeben, werden die Anzahl der erfolgreich gelesenen Bytes. Tritt ein Fehler auf (bspw. ein Gerät wurde nur zum Schreiben geöffnet), wird –1 zurückgegeben. Sind keine Daten mehr zum Lesen vorhanden, wird 0 zurückgegeben.
Tabelle 6.1
418
Öffentliche Methoden der Klasse QIODevice
1542.book Seite 419 Montag, 4. Januar 2010 1:02 13
Schnittstelle für alle E/A-Geräte – QIODevice
Methode
Beschreibung
QByteArray peek ( qint64 maxSize );
Eine überladene Version, die höchstens nach maxSize Bytes vom Gerät nachsieht. Die Daten werden als ein QByteArray-Objekt zurückgegeben. Wird 0 zurückgegeben, kann dies bedeuten, dass ein Fehler aufgetreten ist oder eben keine Daten mehr zum nachsehen vorhanden sind.
bool putChar ( char c );
Schreibt das Zeichen c auf das Gerät. Bei Erfolg wird true, ansonsten false zurückgegeben.
qint64 read ( char * data, qint64 maxSize );
Liest höchstens maxSize Bytes vom Gerät in data ein. Zurückgegeben, werden die Anzahl der erfolgreich gelesenen Bytes. Tritt ein Fehler auf (bspw. ein Gerät wurde nur zum Schreiben geöffnet), wird –1 zurückgegeben. Sind keine Daten mehr zum Lesen vorhanden, wird 0 zurückgegeben.
QByteArray read ( qint64 maxSize );
Eine überladene Version, die höchstens maxSize Bytes vom Gerät einliest. Die Daten werden als ein QByteArray-Objekt zurückgegeben. Wird 0 zurückgegeben, so kann dies bedeuten, dass ein Fehler aufgetreten ist oder eben keine Daten mehr zum nachsehen vorhanden sind.
QByteArray readAll ();
Liest alle vorhanden Daten vom Gerät ein und gibt diese als ein QByteArray-Objekt zurück. Wird 0 zurückgegeben, kann dies bedeuten, dass ein Fehler aufgetreten ist oder eben keine Daten mehr zum Nachsehen vorhanden sind.
qint64 readLine ( char * data, qint64 maxSize );
Diese Methode liest eine Zeile von ASCII-Zeichen vom Gerät ein. Es werden max. maxSize-1 Bytes in data gespeichert. Zurückgegeben werden die Anzahl der eingelesenen Bytes. Eingelesen wird also bis zum ersten Auftreten von '\n' oder maxSize-1 Bytes, oder das Ende der Daten auf dem Gerät wurde festgestellt.
QByteArray readLine ( qint64 maxSize = 0 );
Die überladene Version. Liest ebenfalls eine Zeile von ASCII-Zeichen vom Gerät ein. Allerdings werden nicht mehr als maxSize Bytes eingelesen. Die Daten werden als ein QByteArray-Objekt zurückgegeben. Wird 0 zurückgegeben, kann dies bedeuten, dass ein Fehler aufgetreten ist oder eben keine Daten zum Nachsehen mehr vorhanden sind.
void setTextModeEnabled ( bool enabled );
Mit true setzen Sie das Text-Flag für das Gerät. Ansonsten wird mit false das Text-Flag entfernt. Diese Methode ist nützlich für Klassen, die das Zeilenende von einem Gerät behandeln müssen.
Tabelle 6.1
Öffentliche Methoden der Klasse QIODevice (Forts.)
419
6.1
1542.book Seite 420 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
void ungetChar ( char c );
Schiebt das Zeichen c zurück in das Gerät und dekrementiert die aktuelle Position, sofern diese nicht gleich 0 ist. Damit lässt sich quasi ein getChar()Aufruf rückgängig machen.
qint64 write ( const char * data, qint64 maxSize );
Schreibt höchstens maxSize Bytes von data in das Gerät. Zurückgegeben wird die Anzahl erfolgreich geschriebener Bytes oder –1, falls ein Fehler aufgetreten ist.
qint64 write ( Schreibt alle Daten von byteArray in das Gerät. const QByteArray & byteArray ); Zurückgegeben wird die Anzahl erfolgreich geschrie-
bener Bytes oder –1, falls ein Fehler aufgetreten ist. Tabelle 6.1
Öffentliche Methoden der Klasse QIODevice (Forts.)
Jetzt zu den möglichen Werten (Flags), womit ein Gerät geöffnet werden kann, und deren Bedeutung (die Werte lassen sich mit der Methode openMode() ermitteln): Konstante
Beschreibung
QIODevice::NotOpen
Das Gerät ist nicht geöffnet
QIODevice::ReadOnly
Das Gerät ist nur zum Lesen geöffnet.
QIODevice::WriteOnly
Das Gerät ist nur zum Schreiben geöffnet.
QIODevice::ReadWrite
Wie ReadOnly|WriteOnly. Das Gerät ist zum Lesen und Schreiben geöffnet.
QIODevice::Append
Das Gerät ist im anhängenden Modus geöffnet. Alle Daten, die geschrieben werden, werden ans Ende der Datei geschrieben.
QIODevice::Truncate
Wenn möglich, wird das Gerät abgeschnitten, bevor es geöffnet wird. Der frühere Inhalt des Geräts ist verloren.
QIODevice::Text
Wenn gelesen wird, wird das Zeilenende als '\n' übersetzt. Beim Schreiben wird das Zeilenende in die lokalen Gegebenheiten enkodiert (bspw. '\n' für Linux oder '\r\n' für Win32).
QIODevice::Unbuffered
Alle Puffer des Geräts werden umgeleitet, so dass ein direkter Zugriff auf das Gerät (ungepuffert) besteht.
Tabelle 6.2
Parameter für das Öffnen eines Geräts (QIODevice)
Die Basisklasse QIODevice stellt außerdem die folgenden drei Signale zur Verfügung:
420
1542.book Seite 421 Montag, 4. Januar 2010 1:02 13
Die Datei – QFile
Signal
Beschreibung
void aboutToClose ();
Das Signal wird ausgelöst, wenn ein Gerät gerade geschlossen wird. Dieses Signal wird gewöhnlich mit einem Slot verknüpft, womit noch diverse »Arbeiten« vor dem Schließen durchgeführt werden können.
void bytesWritten ( qint64 bytes );
Das Signal wird ausgelöst, wenn Daten in das Gerät geschrieben wurden. Die Anzahl der geschriebenen Bytes befinden sich in bytes.
void readyRead ();
Das Signal wird ausgelöst, wenn Daten zum Lesen aus dem Gerät vorhanden sind. Es wird immer wieder ausgelöst, wenn neue Daten zum Lesen vorhanden sind (bspw. Netzwerkdaten sind angekommen am Socket, oder ein neuer Block von Daten wurde an das Gerät gehängt).
Tabelle 6.3
6.2
Öffentliche Signale der Klasse QIODevice
Die Datei – QFile
Die Klasse QFile bietet eine Schnittstelle an zum Lesen von und Schreiben in Dateien. Die Klasse QFile stellt ein Ein-/Ausgabegerät dar, mit dem sowohl Textalso auch Binär-Dateien gelesen bzw. geschrieben werden können. QFile kann alleine verwendet werden oder, wenn es komfortabler sein soll, mit den StreamKlassen QTextStream oder QDataStream. Zu den Streams kommen wir noch. In Verbindung mit den Klassen QFileInfo und QDir können Sie weitere systemspezifische Operationen durchführen. QFile stellt neben den üblichen Methoden eine Menge statischer Methoden für
einen komfortableren Zugriff zur Verfügung. Für die gängigsten Methoden in QFile steht also auch eine statische Variante zur Verfügung. Zuerst sollen in der
folgenden Tabelle die öffentlichen Methoden aufgelistet und beschrieben werden. Methode
Beschreibung
QFile ( const QString & name );
Erzeugt ein neues Datei-Objekt mit dem Namen name.
QFile ( QObject * parent ) ;
Erzeugt ein neues Datei-Objekt mit parent als ElternWidget.
QFile ( const QString & name, QObject * parent );
Erzeugt ein neues Datei-Objekt mit den Namen name und parent als Eltern-Widget.
Tabelle 6.4
Methode der Klasse QFile
421
6.2
1542.book Seite 422 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
~QFile ();
Destruktor. Zerstört ein QFile-Objekt.
bool copy ( const QString & newName );
Kopiert die aktuelle Datei (QFile-Objekt), das mit fileName() ermittelt werden kann, in die neue Datei newName. Bei erfolgreicher Kopie wird true, ansonsten false zurückgegeben. Die Quelldatei wird geschlossen, bevor sie kopiert wird.
FileError error () const;
Gibt einen Fehlerstatus der letzten Operation auf QFile zurück. Mögliche Werte siehe Tabelle 6.5.
bool exists () const;
Gibt true zurück, wenn die angegebene Datei fileName() bereits existiert. Ansonsten wird false zurückgegeben.
QString fileName () const;
Gibt den Namen des QFile-Objekts zurück, der mit setFileName() oder beim Konstruktor verwendet wurde.
bool flush ();
Flushed gepufferte Daten in die Datei.
int handle () const;
Gibt den Filedeskriptor des QFile-Objekts zurück. Dieser Wert ist immer ein kleiner positver Integerwert. Wurde die Datei gar nicht geöffnet, wird –1 zurückgegeben.
bool link ( const QString & linkName );
Erzeugt einen Link-Namen (Verknüpfung) linkName, der auf das aktuelle QFile-Objekt mit dem Namen fileName() verweist. Was für ein Link erzeugt wird, hängt vom Dateisystem ab. Unter Windows wird bspw. eine Verknüpfung auf die aktuelle Datei erzeugt und unter Unix ein symbolischer Link. Bei Erfolg gibt diese Methode true, ansonsten false zurück.
bool open ( FILE * fh, OpenMode mode );
Öffnet das existierende FILE-Handle fh mit dem übergebenen mode (siehe Tabelle 6.2). Bei Erfolg wird true, ansonsten false zurückgegeben. Achtung: Sollte fh stdin, stdout oder stderr sein, dürfen Sie kein seek() darauf verwenden.
bool open ( int fd, OpenMode mode );
Öffnet den vorhandenen Filedeskriptor fd mit dem übergebenen Modus. Bei Erfolg wird true, ansonsten false zurückgegeben. Vorsicht auch hier bei den Deskriptoren 0 (stdin), 1 (stdout) und 2 (stderr), die nicht mit seek() verwendet werden dürfen.
Permissions permissions () const;
Gibt die Zugriffsrechte für die Datei zurück. Mögliche Werte siehe Tabelle 6.6. Hierbei sind auch mehrere Werte mit der ODER-Verknüpfung möglich.
Tabelle 6.4
422
Methode der Klasse QFile (Forts.)
1542.book Seite 423 Montag, 4. Januar 2010 1:02 13
Die Datei – QFile
Methode
Beschreibung
bool remove ();
Entfernt die Datei fileName(). Bei Erfolg wird true, ansonsten false zurückgegeben. Die Datei wird geschlossen, bevor diese entfernt wird.
bool rename ( const QString & newName );
Benennt die aktuelle Datei (fileName()) um in newName. Bei Erfolgt wird true, ansonsten false zurückgegeben. Die Datei wird zuvor geschlossen, bevor diese umbenannt wird.
bool resize ( qint64 sz );
Setzt die Dateigröße (in Bytes) auf sz. Bei Erfolg wird true, ansonsten false zurückgegeben. Ist die neue Dateigröße größer als die aktuelle, wird der Rest der neuen Größe mit 0 aufgefüllt. Ist die Datei kleiner, wird der Rest der Datei einfach abgeschnitten.
void setFileName ( const QString & name );
Setzt den Namen der Datei auf name. Der Name kann allerdings keinen (relativen bzw. absoluten) Pfad enthalten. Diese Methode sollte nicht mehr aufgerufen werden, wenn die Datei bereits geöffnet wurde.
bool setPermissions ( Permissions permissions );
Setzt die Zugriffsrechte der Datei auf permission. Mögliche Werte siehe Tabelle 6.6.
QString symLinkTarget () const; Gibt den absoluten Pfad oder die Datei oder das Ver-
zeichnis eines Links (symbolischer Link unter Unix, Verknüpfung unter Win32) der Datei zurück oder einen leeren String, wenn das Objekt keinen Link enthält. void unsetError ();
Tabelle 6.4
Setzt den Datei-Fehler auf QFile::NoError (kein Fehler).
Methode der Klasse QFile (Forts.)
Jetzt zu den möglichen Werten der enum-Konstante QFile::FileError, die den aktuellen Fehlerstatus der letzten Operation auf QFile enthält: Konstante
Beschreibung
QFile::NoError
Kein Fehler ist aufgetreten.
QFile::ReadError
Ein Fehler ist beim Lesen von der Datei aufgetreten.
QFile::WriteError
Ein Fehler ist während des Schreibens in eine Datei aufgetreten.
QFile::FatalError
Ein fataler, nicht mehr behebbarer Fehler ist aufgetreten.
QFile::OpenError
Die Datei konnte nicht geöffnet werden.
QFile::AbortError
Die Operation wurde abgebrochen.
Tabelle 6.5
Konstanten, die den Fehlerstatus von QFile enthalten
423
6.2
1542.book Seite 424 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Konstante
Beschreibung
QFile::TimeOutError
Die Zeit der Operation ist überschritten.
QFile::UnspecifiedError
Ein Fehler ist aufgetreten, konnte aber nicht zugeordnet werden.
QFile::RemoveError
Die Datei konnte nicht gelöscht werden.
QFile::RenameError
Die Datei konnte nicht umbenannt werden.
QFile::PositionError
Die Position des Lese-/bzw. Schreibzeigers konnte nicht verändert werden.
QFile::ResizeError
Die Dateigröße konnte nicht verändert werden.
QFile::PermissionsError
Es sind keine Zugriffsrechte für die Datei vorhanden.
QFile::CopyError
Die Datei konnte nicht kopiert werden.
Tabelle 6.5
Konstanten, die den Fehlerstatus von QFile enthalten (Forts.)
Als Nächstes zu den Werten der Zugriffsrechte auf eine Datei, die über die enumKonstante QFile::Permission gesetzt bzw. abgefragt werden können. Mehrere Werte werden hierbei mit dem bitweisen ODER verknüpft. Konstante
Beschreibung
QFile::ReadOwner
Datei kann vom Eigentümer gelesen werden.
QFile::WriteOwner
Datei kann vom Eigentümer geschrieben werden.
QFile::ExeOwner
Datei kann vom Eigentümer ausgeführt werden.
QFile::ReadUser
Datei kann vom Anwender gelesen werden.
QFile::WriteUser
Datei kann vom Anwender geschrieben werden
QFile::ExeUser
Datei kann vom Anwender ausgeführt werden.
QFile::ReadGroup
Datei kann von der Gruppe gelesen werden.
QFile::WriteGroup
Datei kann von der Gruppe geschrieben werden.
QFile::ExeGroup
Datei kann von der Gruppe ausgeführt werden.
QFile::ReadOther
Datei kann von jedem gelesen werden.
QFile::WriteOther
Datei kann von jedem geschrieben werden.
QFile::ExeOther
Datei kann von jedem ausgeführt werden.
Tabelle 6.6
Konstanten, welche die Zugriffsrechte von QFile enthalten
Zugriffe teilweise plattformabhängig Viele dieser Zugriffe sind plattformabhängig. Die Rechteverwaltung wird in den zukünftigen Qt-Versionen wohl noch weiter vereinheitlicht.
424
1542.book Seite 425 Montag, 4. Januar 2010 1:02 13
Die Datei – QFile
Neben den hier vorgestellten Methoden der Klasse QFile bietet diese Klasse weitere statische Funktionen an, die relativ gerne in der Praxis eingesetzt werden, weshalb sie hier ebenfalls aufgelistet sind. Statische Methoden
Bedeutung
bool copy ( const QString & fileName, const QString & newName );
Kopiert die Datei fileName nach newName. Bei Erfolg wird true, ansonsten false zurückgegeben.
QString decodeName ( const QByteArray & localFileName );
Macht die Verwendung von QFile::encodeName() mit localFileName rückgängig und gibt den dekodierten Text zurück.
QString decodeName ( const char * localFileName );
Gibt die Unicode-Version des übergebenen localFileName dekodiert zurück.
QByteArray encodeName ( const QString & fileName );
Per Standard konvertiert diese Funktion fileName vom lokalen 8-Bit-Codierung in die lokale Anwender-Codierung.
bool exists ( const QString & fileName );
Gibt true zurück, wenn die Datei bereits existiert. Ansonsten wird false zurückgegeben.
bool link ( const QString & fileName, const QString & linkName );
Erzeugt einen Link (symbolisch unter Unix; eine Verknüpfung unter Win32) mit linkName auf die Datei fileName. Bei Erfolg wird true, ansonsten false zurückgegeben.
Permissions permissions ( const QString & fileName );
Gibt die Zugriffsrechte für die Datei fileName zurück. Mögliche Werte wurden bereits in der Tabelle 6.6 beschrieben.
bool remove ( const QString & fileName );
Löscht die Datei fileName. Bei Erfolg wird true, ansonsten false zurückgegeben.
bool rename ( const QString & oldName, const QString & newName );
Benennt die Datei oldName um in newName. Bei Erfolg wird true, ansonsten false zurückgegeben.
bool resize ( const QString & fileName, qint64 sz );
Setzt die Dateigröße (in Bytes) der Datei fileName auf sz. Bei Erfolg wird true, ansonsten false zurückgegeben. Ist die neue Dateigröße größer als die aktuelle, wird der Rest der neuen Größe mit 0 aufgefüllt. Ist die Datei kleiner, wird der Rest der Datei einfach abgeschnitten.
void setDecodingFunction ( DecoderFn function );
Damit kann eine eigene Funktion zum Dekodieren des 8-Bit-Dateinamens gesetzt werden.
void setEncodingFunction ( EncoderFn function );
Damit kann eine Funktion zum Enkodieren des dekodierten Dateinamens verwendet werden.
Tabelle 6.7
Statische Funktionen der Klasse QFile
425
6.2
1542.book Seite 426 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Statische Methoden
Bedeutung
bool setPermissions ( const QString & fileName, Permissions permissions );
Damit setzen Sie die Zugriffsrechte für die Datei fileName auf permissions. Mögliche Werte für die Zugriffsrechte wurden bereits in Tabelle 6.6 näher beschrieben.
QString symLinkTarget ( const QString & fileName );
Gibt den absoluten Pfad der Datei oder des Verzeichnisses zurück, worauf der Link (symbolischer Link unter Unix; Verknüpfung unter Win32) der Datei fileName verweist. Gibt es keinen Link, wird ein leerer String zurückgegeben.
Tabelle 6.7
Statische Funktionen der Klasse QFile (Forts.)
Als Nächstes schreiben wir ein einfaches Beispiel zur QFile-Klasse. Dabei soll es möglich sein, eine Datei zum Lesen in einen Texteditor zu laden, sie zu editieren und ggf. wieder in eine Datei zu speichern. Wollen Sie hierbei außerdem eine bereits vorhandene Datei überschreiben, wird diese zuvor noch als Sicherheitskopie mit der zusätzlichen Endung »~bak« umbenannt. Außerdem wurde die Möglichkeit hinzugefügt, eine beliebige Datei zu löschen. Alle Aktionen wurden natürlich mit Methoden bzw. statischen Methoden der Klasse QFile realisiert. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06 07 08 09
// beispiele/qfile/MyWindow.h #ifndef MyWindow_H #define MyWindow_H #include #include #include #include #include #include #include
10 class MyWindow : public QMainWindow { 11 Q_OBJECT 12 public: 13 MyWindow( QMainWindow *parent = 0, Qt::WindowFlags flags = 0); 14 QTextEdit* editor; 15 public slots: 16 void openFile(); 17 void saveFile(); 18 void deleteFile(); 19 }; 20 #endif
426
1542.book Seite 427 Montag, 4. Januar 2010 1:02 13
Die Datei – QFile
Jetzt die Implementierung des Codes: 00 01 02 03
// beispiele/qfile/MyWindow.cpp #include "MyWindow.h" #include #include
04 MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { 05 editor = new QTextEdit; 06 // das komplette Menü zum Hauptprogramm 07 QMenu *fileMenu = new QMenu(tr("&Datei"), this); 08 menuBar()->addMenu(fileMenu); 09 fileMenu->addAction( QIcon(":/images/folder_page_white.png"), tr("&Öffnen..."), this, SLOT(openFile() ), QKeySequence(tr("Ctrl+O", "Datei|Öffnen"))); 10 fileMenu->addAction( QIcon(":/images/disk_multiple.png"), tr("&Speichern unter..."),this, SLOT(saveFile() ), QKeySequence(tr("Ctrl+S","Datei|Speichern unter"))); 11 fileMenu->addAction( QIcon(":/images/delete.png"), tr("&Löschen..."), this, SLOT(deleteFile() ), QKeySequence(tr("Ctrl+D", "Datei|Löschen"))); 12 fileMenu->addAction( QIcon(":/images/cancel.png"), tr("Be&enden"), qApp, SLOT(quit()), QKeySequence(tr("Ctrl+E", "Datei|Beenden")) ); 13 14 15 16 }
resize(320, 200); setCentralWidget(editor); setWindowTitle("QFile – Demo");
17 // Datei öffnen und im Editor anzeigen 18 void MyWindow::openFile() { 19 QString fileName; 20 fileName = QFileDialog::getOpenFileName( this, tr("Datei öffnen"), "", "Alle Dateien (*.*)" ); 21 if (!fileName.isEmpty()) { 22 QFile file(fileName);
427
6.2
1542.book Seite 428 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
23
if (file.open( QIODevice::ReadOnly | QIODevice::Text)) { editor->setPlainText(file.readAll()); statusBar()->showMessage( tr("Datei erfolgreich geladen") ); setWindowTitle(fileName); }
24 25 26 27 28 29 }
}
30 // Datei im Editor speichern 31 void MyWindow::saveFile() { 32 QString fileName=QFileDialog::getSaveFileName(this); 33 if (fileName.isEmpty()) 34 return; 35 if (QFile::exists(fileName)) { 36 // Datei existiert schon-Sicherheitskopie anlegen 37 QString bakName = fileName; 38 bakName.append("~bak"); 39 QFile::copy ( fileName, bakName ); 40 } 41 QFile file(fileName); 42 if (!file.open( QIODevice::WriteOnly | QIODevice::Text)) { 43 QMessageBox::warning( this, tr("Fehler beim Öffnen"), tr("Datei nicht in Datei schreiben %1:\n%2.") .arg(fileName).arg(file.errorString())); 44 return; 45 } 46 file.write((editor->toPlainText()).toAscii()); 47 statusBar()->showMessage( tr("Datei erfolgreich gespeichert") ); 48 setWindowTitle(fileName); 49 } 50 // Datei im Editor speichern 51 void MyWindow::deleteFile() { 52 QString fileName; 53 fileName = QFileDialog::getOpenFileName( this, tr("Datei zum Löschen wählen"), "", "Alle Dateien (*.*)" ); 54 if (!fileName.isEmpty()) { 55 if (QFile::remove(fileName)) {
428
1542.book Seite 429 Montag, 4. Januar 2010 1:02 13
Die Datei – QFile
56 57 58 59 }
statusBar()->showMessage( tr("Datei erfolgreich gelöscht") ); } }
Nun noch das Hauptprogramm dazu: 00 // beispiele/qfile/main.cpp 01 #include 02 #include "MyWindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWindow* window = new MyWindow; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 6.2
6.2.1
QFile bei der Ausführung
Temporäre Datei – QTemporaryFile
Sofern Sie nur eine temporäre Datei benötigen, steht Ihnen die von QFile abgeleitete Klasse QTemporaryFile zur Verfügung. Diese Klasse wird verwendet, um eine sichere und eindeutige temporäre Datei zu erzeugen. Die Datei selbst wird mit open() erzeugt. Der Name der Datei ist garantiert eindeutig und wird auch wieder gelöscht, wenn das QTemporaryFile-Objekt zerstört wird. Neben den bisher gezeigten Methoden von QFile und QIODevice können Sie auch auf eigene öffentliche Methoden von QTemporaryFile zurückgreifen. Da der Umgang mit dieser Klasse recht ähnlich wie bei QFile ist (nur dass die Datei am Ende eben wieder gelöscht wird), gehen wir nicht näher darauf ein. Trotzdem sollen die Methoden hier aufgelistet werden.
429
6.2
1542.book Seite 430 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
QTemporaryFile ();
Erzeugt eine temporäre Datei in QDir::tempPath() und verwendet als Dateivorlage »qt_ temp.XXXXXX«. Die Datei wird gewöhnlich im temporären Verzeichnis des Systems gespeichert.
QTemporaryFile ( const Qstring& templateName );
Erzeugt eine temporäre Datei mit dem VorlageNamen templateName. Enthält der Vorlage-Name kein XXXXXX, wird es automatisch angehängt. Sollte templateName ein relativer Pfad sein, ist der Pfad relativ vom aktuellen Arbeitsverzeichnis. Hierbei können Sie natürlich wieder auf die statische Methode QDir::tempPath() zurückgreifen, wenn Sie wollen, dass die temporäre Datei im temporären Systemverzeichnis erzeugt wird.
QTemporaryFile ( QObject * parent );
Erzeugt eine temporäre Datei mit parent als Eltern-Widget in QDir::tempPath() und verwendet als Dateivorlage »qt_temp.XXXXXX«. Die Datei wird gewöhnlich im temporären Verzeichnis des Systems gespeichert.
QTemporaryFile ( const QString & templateName, QObject * parent );
Mischung aus den beiden Versionen zuvor
~QTemporaryFile ();
Destruktor. Zerstört eine temporäre Datei.
bool autoRemove () const;
Gibt true zurück, wenn die temporäre Datei automatisch wieder gelöscht wird (Standard-Wert). Ansonsten, wenn false zurückgegeben wird, müssen Sie sich selbst um die Löschung kümmern.
QString fileName () const;
Gibt den kompletten, eindeutigen temporären Dateinamen zurück.
QString fileTemplate () const;
Gibt den gesetzten Vorlagen-Namen zurück. Der Standard-Wert hierbei, wenn keiner gesetzt wurde, lautet »qt_temp« und wird im Pfad QDir::tempPath() gespeichert.
bool open ();
Eine temporäre Datei wird immer im Modus QIODevice::ReadWrite geöffnet. Diese Methode gibt true bei Erfolg zurück und setzt den temporären Namen auf einen eindeutigen Namen (fileName()).
void setAutoRemove ( bool b );
Mit false können Sie das automatische Löschen der temporären Datei abschalten. Mit true stellen Sie den Standardwert wieder her.
Tabelle 6.8
430
Öffentliche Methode der Klasse QTemporaryFile
1542.book Seite 431 Montag, 4. Januar 2010 1:02 13
Streams
Es stehen Ihnen außerdem noch zwei statische Methoden zur Verfügung: Statische Methode
Beschreibung
QTemporaryFile * QTemporaryFile::createLocalFile (QFile & file ) ;
Erzeugt eine lokale temporäre Datei welche eine Kopie des Inhalts der Datei mit dem QFile-Objekt erstellt. Zurückgegeben wird diese temp. Datei.
QTemporaryFile * QTemporaryFile::createLocalFile (const QString & fileName );
Diese Methode arbeitet ähnlich wie die eben erwähnte, nur wird anstelle eines QFile-Objekts der Dateiname mit fileName angegeben.
Tabelle 6.9
Statische Methoden der Klasse QTemporaryFile
Ein üblicher Vorgang, eine solche temporäre Datei zu erzeugen, ist: { QTemporaryFile file; if (file.open()) { // file.fileName() gibt einen eindeutigen // Dateinamen zurück ... } // Der Destruktor von QTemporaryFile löscht die // temporäre Datei wieder. }
6.3
Streams
Bei der Klasse QFile konnten Sie zwar sehen, wie man etwas aus einer Datei einlesen kann oder etwas darin schreibt, aber sonderlich komfortabel ist das nicht. Sobald man die Daten parsen will oder gar binär behandeln muss, bieten sich hierzu die beiden Stream-Klassen QDataStream und QTextStream an. Wozu und wie Sie diese Klassen verwenden können, erfahren Sie in den beiden nächsten Abschnitten.
6.3.1
Binäre Daten – QDataStream
Die Klasse QDataStream wird in der Praxis meistens für die Serialisierung von binären Daten auf alle Ein-/Ausgabegeräten (QIODevice) verwendet. Ein binärer Stream hat den Vorteil, dass seine Codierung völlig unabhängig von der CPU, dem Betriebssystem oder der Byte-Anordnung ist. Ein Daten-Stream von einer Intel-CPU mit bspw. Linux kann somit auch von einem Sun SPARC unter Solaris gelesen werden. Diese Daten wiederum können ohne Probleme in dieser Form an einen Windows-Rechner gesendet werden. Man kann schnell daraus schlie-
431
6.3
1542.book Seite 432 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
ßen, dass die Serialisierung binärer Daten mit der Klasse QDataStream hervorragend (natürlich nicht nur) zur Netzwerkprogrammierung eignet. QDataStream oder QTextStream Binäre Streams der Klasse QDataStream werden also gewöhnlich zum Schreiben bzw. Lesen roher unkodierter Binärdaten verwendet. Sollten Sie einen Stream zum Parsen von Ein-/Ausgabe-Streams benötigen, wird hierzu QTextStream verwendet. QDataStream implementiert natürlich die Serialisierung aller C++-Datentypen
wie char, char*, int, short, long etc. Komplexere Daten wie bspw. Objekte müssen Sie selbstverständlich erst mal in die einzelnen Bestandteile (wiederum C++-Datentypen) zerlegen, verarbeiten (Lesen oder Schreiben) und wiederherstellen (also zusammenbauen). Um in einen Stream der Klasse QDataStream zu schreiben, verwendet man in der Praxis gewöhnlich den str >> i;
Natürlich sollte hierbei nicht der Eindruck entstehen, man könnte standardmäßig nur C++-Datentypen einlesen bzw. schreiben. Qt unterstützt noch eine Menge Typen mehr. So kann bspw. ohne Probleme Folgendes verwendet werden: QFile file("timestamp.dat"); file.open(QIODevice::ReadOnly);
432
1542.book Seite 433 Montag, 4. Januar 2010 1:02 13
Streams
QDataStream out(&file); QString str = "Programmstart: "; QDateTime stamp = QDateTime::currentDateTime(); out > i; d.setTestInt(i); return s; } #endif
Wichtig in diesem Beispiel ist auch das Serialisieren des Operators > (Zeile 46 bis 55) ausgelesen werden müssen. Beide Male geben wir den Stream mit return weiter. Weil dies auch ein Buch über die GUI-Programmierung ist, habe ich es mir natürlich nicht nehmen lassen, diese Daten vom Anwender eingeben zu lassen, und auch für die Anzeige wurde gesorgt (wenn auch mit einfachsten Mitteln). Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11
// beispiele/qdatastream/MyWindow.h #ifndef MyWindow_H #define MyWindow_H #include #include #include #include #include #include #include #include #include
12 class MyWindow : public QMainWindow { 13 Q_OBJECT 14 public: 15 MyWindow( QMainWindow *parent = 0, Qt::WindowFlags flags = 0); 16 QTextEdit* editor; 17 public slots: 18 void openBinFile(); 19 void saveBinFile();
438
1542.book Seite 439 Montag, 4. Januar 2010 1:02 13
Streams
20 }; 21 #endif
Jetzt zur Implementierung des Codes. Das Einlesen der beiden Daten wurde über die Dialoge QInputDialog::getText() und QInputDialog::getInteger() realisiert. Hierzu zunächst der Quellcode mit einer anschließenden Erläuterung: 00 01 02 03 04 05
// beispiele/qstreamdata/MyWindow.cpp #include "MyWindow.h" #include "bindata.h" #include #include #include
06 void saveBinData( const QList &d, const QString& filename) { 07 QFile file(filename); 08 if( !( file.open( QIODevice::WriteOnly | QIODevice::Append) ) ) 09 return; 10 QDataStream ds(&file); 11 ds.setVersion(QDataStream::Qt_4_6); 12 // Datei noch leer, dann Magicnumber 13 // Version am Anfang der Datei schreiben 14 if(file.size() == 0) { 15 ds > version; // Versionsnummer überprüfen if( (quint32)BinData::VERSION != version ) { // Fehler: Falsche Versionsnummer return QList(); } // Daten auslesen und zur Liste hinzufügen while( !ds.atEnd()) { ds >> set; list.append(set); } file.close(); // alle Daten der Liste zurückgegeben return list;
53 MyWindow::MyWindow( QMainWindow *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { 54 editor = new QTextEdit; 55 // das komplette Menü zum Hauptprogramm 56 QMenu *fileMenu = new QMenu(tr("&Datei"), this); 57 menuBar()->addMenu(fileMenu); 58 fileMenu->addAction( QIcon(":/images/table_go.png"), tr("&Datensätze lesen"),this,SLOT(openBinFile()), QKeySequence( tr("Ctrl+O", "Datei|Datensätze lesen"))); 59 fileMenu->addAction( QIcon(":/images/table_add.png"), tr("&Neuer Datensatz"), this, SLOT(saveBinFile()), QKeySequence( tr("Ctrl+N", "Datei|Neue Daten hinzufügen"))); 60 fileMenu->addAction( QIcon(":/images/cancel.png"), tr("Be&enden"), qApp, SLOT(quit()), QKeySequence(tr("Ctrl+E", "Datei|Beenden")) ); 61 62 63
440
resize(320, 200); setCentralWidget(editor); setWindowTitle("QDataStream – Demo");
1542.book Seite 441 Montag, 4. Januar 2010 1:02 13
Streams
64 } 65 // Datei öffnen und im Editor anzeigen 66 void MyWindow::openBinFile() { 67 QList data; 68 QString str(""); 69 data = readBinData("data.db"); 70 foreach( BinData d, data) { 71 str.append(qPrintable(d.getTestString())); 72 str.append(" – "); 73 str.append(QString::number(d.getTestInt())); 74 str.append("\n"); 75 } 76 editor->setPlainText(str); 77 data.clear(); 78 } 79 // Datei im Editor speichern 80 void MyWindow::saveBinFile() { 81 bool ok; 82 QString text = QInputDialog::getText( this, tr("String eingeben"), tr("Eingabe: "), QLineEdit::Normal, QDir::home().dirName(), &ok); 83 if (ok && text.isEmpty()) 84 text = "NoInput"; 85 int i = QInputDialog::getInteger( this, tr("Integer eingeben"), tr("Eingabe:"), 25, 0, 100, 1, &ok); 86 QList data; 87 BinData bdata1(text, i); 88 data.append(bdata1); 89 saveBinData(data, "data.db"); 90 data.clear(); 91 }
Zunächst zum Hinzufügen neuer Datensätze. Wenn der Anwender das Menü »Neuer Datensatz« oder (Strg)+(N) betätigt, wird der Slot saveBinFile() ausgeführt. Zunächst wird dort über jeweils einen Dialog der String und der Integer eingelesen. In Zeile 87 erzeugen wir ein neues BinData-Objekt, welches eine Zeile danach unserer Liste hinzugefügt wird. Diese Liste übergeben wir mit dem Dateinamen, wo die Daten gespeichert werden, an die globale Funktion saveBinData(). In der Funktion saveBinData() (ab Zeile 6) öffnen wir den gewünschten Dateinamen zum Schreiben und Anhängen der neuen Daten am Ende. Existiert diese
441
6.3
1542.book Seite 442 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Datei noch nicht, wird sie jetzt generiert. Dann erzeugen wir (Zeile 10) einen neuen Daten-Stream mit QFile für unser Ein-/Ausgabegerät. Eine Zeile später setzen wir die aktuellste Version von QDataStream. Bevor die ersten Daten in unsere Datei geschrieben werden, überprüfen wir, ob die Datei noch leer ist. In diesem Fall bedeutet dies, dass die Datei soeben erzeugt wurde. Daher schreiben wir in den Zeilen 15 und 16 die »Magicnummer« und die Versionsnummer an den Dateianfang. Natürlich müssen Sie diese Nummern beim Einlesen der Datei als Erstes wieder bearbeiten. Jetzt können Sie in Zeile 19 und 20 den Datensatz in die Datei einfügen. Mithilfe von foreach(), und weil wir hier eine Liste verwendet haben, könnten wir mit dieser globalen Funktion durchaus mehrere Dateien auf einmal in die Datei schreiben. foreach() schiebt wie in einer for-Schleife immer das letzte Element der Liste nach data, was ja im anschließenden Anweisungsblock in den Daten-Stream geschoben wird. Jetzt haben Sie eine Datei »data.db« im Arbeitsverzeichnis des Programms mit der Magicnummer, der Versionsnummer und dem ersten Datensatz hinzugefügt. Diese Daten können Sie allerdings nicht mehr mit einem einfachen Texteditor zum Lesen öffnen. Das heißt: Sie können schon, nur werden die Daten nicht sonderlich lesbar sein. Beispiel:
Abbildung 6.3
Versuch, eine binär geschriebene Datei zu lesen
Daher wurde zum Lesen ein weiterer Slot mit openBinFile() (Zeile 65 bis 78) geschrieben, der ebenfalls über unser Menüelement »Datensätze lesen« oder (Strg)+(O) aufgerufen werden kann. In diesem Slot wird in Zeile 69 der komplette Datensatz mit der globalen Funktion readBinData() und dem entsprechenden Dateinamen in einem Rutsch an die Liste data übergeben. In dem darauf folgenden foreach()-Anweisungsblock servieren wir diese Daten in menschlich lesbaren Happen und schreiben alles in einen String. Damit die String-Objekte sauber in das lokale Format serviert werden, haben wir die Hilfsfunktion qPrintable() verwendet (die hier einen lokalen 8-Bit-String daraus macht). Den kompletten String setzen wir dann komplett als Text in den Editor zur Anzeige (Zeile 76).
442
1542.book Seite 443 Montag, 4. Januar 2010 1:02 13
Streams
Jetzt zur globalen Funktion readBinData() (Zeile 23 bis 52), die uns ja eine komplette Liste von BinData-Objekte zurückgibt. Zunächst öffnen wir die übergebene Datei zum Lesen und übergeben diese dann an den Daten-Stream. Nachdem auch hier wieder die Version (Zeile 28) des Streams gesetzt wurde, wird zunächst in Zeile 31 die »Magicnumber« aus der Datei gezogen und überprüft, ob diese überhaupt mit der Klasse BinData übereinstimmt. Dadurch stellen Sie sicher, dass Sie nicht eine andere Datei »data.db« geladen haben. In diesem Fall ist das vielleicht ein wenig trivial, aber bedenken Sie, es kann auch möglich sein, dass der Anwender eine solche Datei über einen Dateiauswahl-Dialog ladet. Selbiges machen wir nochmals mit der Versionsnummer unseres Programms. Dies ist recht sinnvoll, es könnte ja schließlich sein, dass es eine neue Version der Klasse BinData gibt, wo weitere Daten hinzugekommen sind. Durch diese beiden Maßnahmen kann man entsprechend darauf reagieren. In unserem Fall geben wir dem Aufrufer jeweils nur eine leere Liste zurück. Jetzt können wir uns ab Zeile 45 daranmachen, die Daten auszulesen. Und zwar so lange, bis der Stream am Ende (atEnd()) angekommen ist. Dazu hängen wir einfach Datensatz für Datensatz an unsere Liste und geben diese am Ende der Funktion dem Aufrufer zurück. Unser kleiner Texteditor müsste die Datei in einer reinen lesbaren Form ausgeben:
Abbildung 6.4
Das Auslesen binärer Daten war erfolgreich
Jetzt fehlt uns eigentlich nur noch die Hauptfunktion: 00 // beispiele/qdatastream/main.cpp 01 #include 02 #include "MyWindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWindow* window = new MyWindow; 06 window->show(); 07 return app.exec(); 08 }
443
6.3
1542.book Seite 444 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Qt-Ressourcen-System Sollten Sie das Programm unter Linux/Unix mit einem Mausklick (anstatt von der Kommandozeile) starten, befindet sich das Arbeitsverzeichnis im üblichen Heimatverzeichnis des Anwenders und nicht mehr im Verzeichnis des ausführbaren Programms. Sollten Sie dies ändern wollen, können Sie entweder den absoluten Pfad verwenden oder auf die Ressourcen von Qt zurückgreifen. Mehr dazu siehe Abschnitt 1.4.3 und 12.7.
6.3.2
Text Daten – QTextStream
Benötigen Sie eine Schnittstelle, die für das Lesen bzw. Schreiben von reinem Text verwendet werden kann (bspw. zum Parsen von Text), ist die Stream-Klasse QTextStream Ihr Freund. Im Gegensatz zu QDataStream ist hierbei allerdings die Implementierung des >>-Operators nicht so einfach zu realisieren. Bei QDataStream konnten Sie ohne Probleme Folgendes verwenden: // auf Daten-Stream schreiben OutStream string1 >> string2;
Mit QDataStream konnten Sie sich hierbei darauf verlassen, dass sich in string1 der String »String1« und in string2 der String »String2« befinden, weil hierbei ja die Länge von jedem String am Anfang steht. Bei QTextStream würde sich in string1 der String »String1String2« befinden, und der string2 wäre leer. Sie sehen also schon, der Umgang mit QTextStream gestaltet sich nicht ganz so komfortabel wie mit QDataStream. Daher sollte diese Klasse auch nur dann verwendet werden, wenn es um echte Text-Geschichten wie bspw. das Parsen der Eingabe geht. Die Klasse QTextStream kann direkt mit den Klassen QIODevice, QByteArray oder QString arbeiten. Mithilfe der Stream-Operatoren > können Sie einzelne Wörter, Zeilen oder Nummern lesen und schreiben. Auch die Formatierung, wie Sie es von C++ her kennen (Stichwort: Manipulatoren), das Ausfüllen und Ausrichten von Text werden von QTextStream direkt unterstützt. Bspw.: QFile data("ausgabe.txt"); if (data.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&data); out addAction( QIcon(":/images/application.png"), tr("&Asynchron"), this, SLOT(AsynchronProcess() ), QKeySequence(tr("Ctrl+A", "QProcess|Asynchron"))); 13 fileMenu->addAction( QIcon(":/images/application_term.png"), tr("&Programm starten"), this, SLOT(StartOwnProcess() ), QKeySequence(tr("Ctrl+P", "QProcess|Programm starten"))); 14 connect( asynchron, SIGNAL(readyRead()), this, SLOT(readFromProcess())); 15 connect( asynchron, SIGNAL( error(QProcess::ProcessError) ), this, SLOT(processError(QProcess::ProcessError))); 16 resize(400, 250); 17 setCentralWidget(editor); 18 setWindowTitle("QProcess – Demo"); 19 } 20 // einen synchronen Prozess starten 21 void MyWindow::synchronProcess() { 22 QString str(""); 23 QProcess ping; 24 QByteArray output; 25 bool ok; 26 QString args = QInputDialog::getText( this, tr("Zusätzliche Optionen für ping " "angeben (optional)"), tr("Optionen:"), QLineEdit::Normal, "", &ok ); 27 QString hosts = QInputDialog::getText( this, tr("Host zum Pingen eingeben"),
491
6.7
1542.book Seite 492 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 }
tr("Host(s):"), QLineEdit::Normal, "localhost", &ok ); if( args.isEmpty() ) ping.start("ping", QStringList() setPlainText( "FATAL ERROR: Der Prozess läuft nicht!?"); 72 return; 73 } 74 while(! (line = asynchron->readLine()). isEmpty() ) 75 editor->moveCursor( QTextCursor::End ); 76 QString output(QString::fromLatin1(line)); 77 output.chop(2); 78 editor->insertPlainText( output ); 79 } 80 }
{
81 // Fehler bei einem asynchronen Prozess auswerten 82 void MyWindow::processError(QProcess::ProcessError e) { 83 QString err( "FATAL Error: Der Prozess konnte" " nicht gestartet werden\n" ); 84 err.append("Folgender Fehler ist aufgetreten: "); 85 switch( e ) { 86 case QProcess::FailedToStart: 87 err.append("QProcess::FailedToStart\n"); 88 break; 89 case QProcess::Crashed: 90 err.append("QProcess::Crashed\n"); 91 break; 92 case QProcess::Timedout: 93 err.append("QProcess::Timedout\n"); 94 break;
493
6.7
1542.book Seite 494 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
95 case QProcess::WriteError: 96 err.append("QProcess::WriteError\n"); 97 break; 98 case QProcess::ReadError: 99 err.append("QProcess::ReadError\n"); 100 break; 101 case QProcess::UnknownError: 102 err.append("QProcess::UnknownError\n"); 103 break; 104 } 105 editor->setPlainText( err ); 106 }
Der synchrone Prozess wird einfach über dem Menüelement »Synchron« (Zeile 11) und dem Slot synchronProcess() gestartet. Nachdem Sie den Host eingegeben haben (Zeile 27) und optional zuvor Argumente (Zeile 26), wird der Prozess, abhängig, ob Argumente verwendet wurden oder nicht, in Zeile 29 oder 31 mit start() gestartet. Solange Daten zum Lesen vorhanden sind, läuft die Schleife in Zeile 32 und 33 und liest die Daten in einen String ein. Anschließend bereiten wir die Daten ein wenig auf, ehe sie im Texteditor angezeigt werden. Ping Im Beispiel wurde für den Prozess »ping« verwendet. Unter Linux/Unix sollten Sie hier die Anzahl der Durchläufe begrenzen, sonst wird hierbei dauerhaft »gepingt«, und der synchrone Prozess zeigt nie etwas an (weil er nicht fertig wird). Daher empfiehlt es sich, hier Argumente zu verwenden. Wollen Sie bspw. »ping« fünfmal ausführen lassen, brauchen Sie nur die Option »-c 5« anzugeben.
Beim asynchronen Ablauf sieht dies ein wenig anders aus. Auch hier können wir über ein Menüelement »Asynchron« den Slot AsynchronProcess() starten. Der Slot (Zeile 43 bis 54) startet lediglich den Prozess. Zum Lesen richten wir eine Signal-Slot-Verbindung (Zeile 14) ein. Diese reagiert immer dann, wenn der gestartete Prozess etwas geschrieben hat und wir etwas davon lesen können (Signal: readyRead()). Das Lesen erledigen wir dann mit dem Slot readFromProcess() (Zeile 68 bis 80). Das Einlesen erfolgt zeilenweise. Wurde die Zeile eingelesen, wird sie im Editor ausgegeben, und die Event-Loop kann ggf. den Slot verlassen, sobald nichts mehr zum Lesen vorhanden ist. Allerdings kann ja dank der SignalSlot-Verbindung immer wieder weitergelesen werden, wenn etwas zum Lesen aus dem Prozess vorhanden ist. Ähnlich wurde dies dann auch noch mit dem letzten Menüpunkt gemacht, womit Sie einen beliebigen asynchronen Prozess mit dem Slot StartOwnProcess() (Zeile 56 bis 66) starten können. Auch hier wird unsere Signal-Slot-
494
1542.book Seite 495 Montag, 4. Januar 2010 1:02 13
Interprozesskommunikation – QProcess
Verbindung, die eben beschrieben wurde, aktiv. Im Grunde entspricht dieser Punkt demselben wie schon eben beim ping als asynchronen Prozess, nur dass Sie hierbei auch einen beliebigen Prozess starten können. Zusätzlich wurde für die asynchronen Prozesse eine Signal-Slot-Verbindung (Zeile 15) zum Behandeln von Fehlern eingerichtet. Dieser Slot (Zeile 82 bis 106) macht im Grunde nichts anderes, als den Fehler des Prozesses auszuwerten und dies auf dem Editor auszugeben. Jetzt nur noch eine Hauptfunktion: 00 // beispiele/qprocess/main.cpp 01 #include 02 #include "MyWindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MyWindow* window = new MyWindow; 06 window->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 6.11
QProcess in der Praxis
Zeilenende anpassen Bei der Ausgabe müssen ggf. das Zeilenende bei vorbereiten den Daten zur Ausgabe anpassen. Hierbei brauchen Sie ggf. bloß die Zeilen mit der Methode QString::chop() auszukommentieren oder zu bearbeiten. Linux/Unix und Windows verwenden hier ja unterschiedliche Zeilenenden.
495
6.7
1542.book Seite 496 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
6.8
Netzwerkkommunikation (Sockets)
Was nicht selbstverständlich ist: Auch die Netzwerkkommunikation gehört zum Framework von Qt. Da auch hier QIODevice eine Eltern-Klasse des Netzwerkpakets von Qt ist, lässt sich auch die Ein-/Ausgabe wie gewohnt einsetzen. Das Netzwerkpaket von Qt besteht, ausgehend von QIODevice, aus den Unterklassen QAbstractSocket, QTcpSocket und QUdpSocket. Außerdem finden Sie hier die Klasse QTcpServer, die allerdings nicht von QIODevice abgeleitet wurde, aber viele TCP-basierende Dienste anbietet. Als weitere Netzwerkklassen wurden die beiden Protokolle HTTP und FTP mit den Klasse QHttp und QFtp implementiert. Zusätzlich existiert mit QNetworkProxy eine weitere Klasse, womit eine Socket-5-Proxy-Implementierung für TCP und UDP und eine Proxyunterstützung für die Anwendungsschichten HTTP und FTP vorhanden sind. Für den Hostnamen und IP-Adressen steht die Klasse QHostAdress zur Verfügung, die zudem IPv6-fähig ist. Was noch erwähnt werden muss: Wenn Sie Anwendungen mit Netzwerkunterstützung in Qt realisieren wollen, müssen Sie die Projektdatei-Datei (.pro) um folgende Zeile erweitern: QT += network
Damit wird beim Projekt die Netzwerkbibliothek mit hinzugelinkt. Grundlagen zur Socketprogrammierung Das Thema Netzwerk- und Socketprogrammierung ist ein Teil für sich. In diesem Abschnitt gehen wir davon aus, dass Sie mit den Grundlagen der Socketprogrammierung vertraut sind. Eine entsprechende Einführung würde viele Seiten verschlingen, die dann dem Thema »Qt« fehlen. Ich verweise Sie jedoch auf mein Buch »C++ von A bis Z«. Das entsprechende Kapitel ist auch auf der Buch-DVD als PDF-Dokument enthalten.
6.8.1
QAbstractSocket
Die Klasse QAbstractSocket bietet alle üblichen Basismethoden der davon abgeleiteten Klassen QTcpSocket und QUdpSocket an. Wenn Sie Sockets verwenden wollen, können Sie entweder eine Instanz von QTcpSocket oder QUdpSocket erstellen oder Sie verwenden einen nativen Socket-Deskriptor, erstellen eine Instanz von QAbstractSocket und rufen anschließend die darin definierte Methode setSocketDescriptor() auf.
496
1542.book Seite 497 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Hierzu eine Übersicht der öffentlichen Methoden der Basisklasse QAbstractSocket: Methoden
Beschreibung
QAbstractSocket ( SocketType socketType, QObject * parent );
Erzeugt einen neuen abstrakten Socket mit dem Typ socketType und parent als Eltern-Widget. Mögliche Werte für socketType siehe Tabelle 6.33.
virtual ~QAbstractSocket ();
Destruktor. Zerstört einen abstrakten Socket.
void abort ();
Bricht die aktuelle Verbindung ab und setzt den Socket zurück. Im Gegensatz zur Methode disconnectFromHost() schließt diese Methode den Socket sofort und löscht jegliche noch nicht verarbeitete Daten im Schreib-Puffer.
virtual qint64 bytesAvailable () const;
Gibt die Anzahl der angekommenen Bytes, die auf das Lesen warten, zurück. Eine Reimplementierung von QIODevice.
virtual qint64 bytesToWrite () const;
Gibt die Anzahl der Bytes zurück, die noch geschrieben werden müssen. Die Daten werden geschrieben, wenn die Kontrolle des Programms zurück zur Ereignisschleife kommt oder wenn flush() aufgerufen wurde. Eine Reimplementierung von QIODevice.
virtual bool canReadLine () const;
Gibt true zurück, wenn eine Zeile von Daten aus dem Socket gelesen werden kann. Ansonsten wird false zurückgegeben. Eine Reimplementierung von QIODevice.
virtual void close ();
Trennt die Verbindung des Sockets mit dem Host. Eine Reimplementierung von QIODevice.
void connectToHost ( const QString & hostName, quint16 port, OpenMode openMode=ReadWrite);
Versucht, eine Verbindung zum hostName mit dem Port port einzugehen. Der Socket wird im Modus openMode geöffnet (Standard = Lesen und Schreiben). Für Host-Name wird eine IPAdresse (bspw. »194.150.178.34«) oder ein Host-Name (bspw. »www.pronix.de«) verwendet. Hierbei kann jederzeit das Signal error() ausgelöst werden.
void connectToHost ( const QHostAddress& address, quint16 port, OpenMode openMode=ReadWrite );
Eine überladene Version. Damit stellen Sie ebenfalls eine Verbindung zu adress (ein Objekt der Klasse QHostAdress) und dem Port port mit dem Modus openMode her.
Tabelle 6.32
Öffentliche Methoden von QAbstractSockets
497
6.8
1542.book Seite 498 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methoden
Beschreibung
void disconnectFromHost ();
Versucht, den Socket zu schließen. Befinden sich noch unverarbeitete Daten zum Schreiben im Socket, nimmt der Socket einen ClosingState Zustand an und wartet, bis die Daten geschrieben werden. Der Socket kann allerdings auch einen UnconnectedState annehmen und das Signal disconnected() senden.
SocketError error () const;
Gibt den zuletzt aufgetretenen Fehler des Sockets zurück. Mögliche Werte und deren Bedeutung siehe Tabelle 6.34.
bool flush ();
Diese Methode schreibt so viel wie möglich von einen internen Schreibpuffer des Sockets, ohne zu blockieren. Wurden Daten geschrieben, wird true, ansonsten false zurückgegeben. Diese Methode wird gewöhnlich verwendet, wenn man das Senden von gepufferten Daten sofort starten will.
bool isValid () const;
Gibt true zurück, wenn ein Socket gültig und bereit zur Verwendung ist. Ansonsten wird false zurückgegeben. Logischerweise muss sich der Socket im Zustand ConnectedState befinden.
QHostAddress localAddress () const;
Gibt die Host-Adresse vom lokalen Socket zurück, wenn dieser vorhanden ist. Ansonsten wird QHostAdress::Null zurückgegeben.
quint16 localPort () const;
Gibt die Portnummer vom Host oder vom lokalen Socket zurück, sofern vorhanden. Bei einem Fehler wird 0 zurückgegeben.
QHostAddress peerAddress () const;
Gibt die Adresse des verbundenen Gegenübers zurück, wenn der Socket im Zustand Connected-State ist. Ansonsten wird QHostAdress::Null zurückgegeben.
QString peerName () const;
Gibt den Namen des verbundenen Gegenübers zurück, der mit connectToHost() festgelegt wurde. Wurde connectToHost() noch nicht aufgerufen, wird ein leerer String zurückgegeben.
quint16 peerPort () const;
Gibt die Portnummer des verbundenen Gegenüber zurück, wenn sich der Socket im Zustand ConnectedState befindet. Ansonsten wird 0 zurückgegeben.
Tabelle 6.32
498
Öffentliche Methoden von QAbstractSockets (Forts.)
1542.book Seite 499 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methoden
Beschreibung
QNetworkProxy proxy () const;
Gibt den Proxy für das Socket zurück. Standardmäßig wird QNetworkProxy::DefaultProxy verwendet.
qint64 readBufferSize () const;
Gibt die Größe des internen Lesepuffers zurück. Nachdem Sie das Limit kennen, wie viel der Client empfangen kann, können Sie read() oder read-All() aufrufen. Gibt diese Methode 0 zurück (Standard), bedeutet dies, dass der Puffer kein Limit hat.
void setProxy ( const QNetworkProxy & networkProxy );
Setzt explizit einen Proxy für den Socket auf networkProxy. Wollen Sie den Proxy abschalten, müssen Sie dieser Methode nur die Konstante QnetworkProxy::NoProxy übergeben.
void setReadBufferSize ( qint64 size );
Setzt die Göße des internen Lesepuffers auf size Bytes. War der Puffer bereits limitiert, kann ein größerer Wert nicht bewirken, dass der Puffer vergrößert wird. Mit 0 setzen Sie den Puffer auf Unbegrenzt (Standard). Beachten Sie dass nur QTcpSocket einen internen Puffer verwendet. QUdpSocket verwendet keine interne Pufferung und verwendet stattdessen die Pufferung des Betriebssystems. Somit hat der Aufruf dieser Methode bei UDP-Socket keinen Effekt.
bool setSocketDescriptor ( int socketDescriptor, SocketState socketState = ConnectedState, OpenMode openMode=ReadWrite);
Initialisiert das Objekt QAbstractSocket mit einem nativen Socket-Deskriptor socketDescriptor und gibt true zurück, wenn der Socket-Deskriptor ein gültiger ist. Ansonsten wird false zurückgegeben. Optional können der Zustand des Sockets (socketState) und der Modus (openMode) mit angegeben werden.
int socketDescriptor () const;
Gibt den nativen Socket-Deskriptor von einem QAbstractSocket-Objekt zurück. Bei einem Fehler wird –1 zurückgegeben. Verwendet der Socket ein QNetworkProxy, kann der zurückgegebene Socket-Deskriptor nicht mit nativen Socket-Funktionen verwendet werden. Der Socket-Deskriptor ist nicht vorhanden wenn sich QAbstractSocket im Zustand UnconnectState befand.
SocketType socketType () const;
Gibt den Socket-Typ zurück. Mögliche Werte siehe Tabelle 6.33.
Tabelle 6.32
Öffentliche Methoden von QAbstractSockets (Forts.)
499
6.8
1542.book Seite 500 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methoden
Beschreibung
SocketState state () const;
Gibt den aktuellen Zustand des Sockets zurück. Mögliche Werte und deren Bedeutung siehe Tabelle 6.35.
bool waitForConnected ( int msecs = 30000 );
Wartet bis zu msecs, bis das Socket verbunden ist. Konnte erfolgreich eine Verbindung hergestellt werden, wird true, ansonsten false zurückgegeben. Bei einem Fehler können Sie error() aufrufen, um mehr über die Umstände zu erfahren. Damit können Sie praktisch ein Timeout einbauen.
bool waitForDisconnected ( int msecs = 30000 );
Das Gegenstück von eben, nur dass Sie hier msecs warten, bis die Verbindung geschlossen wird. Ansonsten alles, wie eben bei waitForConnect() beschrieben wurde.
Tabelle 6.32
Öffentliche Methoden von QAbstractSockets (Forts.)
Jetzt zu den möglichen Konstanten der enum-Variablen QAbstractSocket:: SocketType, womit das Transportschicht-Protokoll beschrieben wird, welches der Socket verwendet. Konstante
Beschreibung
QAbstractSocket::TcpSocket
TCP-Socket
QAbstractSocket::UdpSocket
UDP-Socket
QAbstractSocket::UnknownSocketType Unbekannter Sockettyp
Tabelle 6.33
Mögliche Socket-Typen
Welche Fehler bei einem Socket auftreten können, beschreibt und ermittelt die enum-Variable QAbstractSocket::SocketError. Konstante
Beschreibung
QAbstractSocket:: ConnectionRefusedError
Die Verbindung wurde vom Gegenüber abgewiesen (oder ein Timeout ist eingetreten).
QAbstractSocket:: RemoteHostClosedError
Der Remote-Host hat seine Verbindung geschlossen. Hierbei wird auch der ClientSocket geschlossen, nachdem dieser die Schließung des Remote-Hosts festgestellt hat.
QAbstractSocket:: HostNotFoundError
Die Host-Adresse konnte nicht gefunden werden.
Tabelle 6.34
500
Mögliche Socket-Fehler
1542.book Seite 501 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Konstante
Beschreibung
QAbstractSocket:: SocketAccessError
Die Socket-Operation ist fehlgeschlagen, weil der Anwendung die nötigen Rechte fehlen.
QAbstractSocket:: SocketResourceError
Dem lokalen System mangelt es an Ressourcen (bspw. sind zu viele Sockets gleichzeitig offen).
QAbstractSocket:: SocketTimeoutError
Bei der Socket-Operation ist ein Timeout aufgetreten.
QAbstractSocket:: DatagramTooLargeError
Das Datagramm ist größer als das vom Betriebssystem vorgegebene Limit (ein gängiges Limit ist bspw. 8192 Bytes).
QAbstractSocket:: NetworkError
Ein Fehler im Netzwerk ist aufgetreten (bspw. wurde das Netzwerkkabel herausgezogen).
QAbstractSocket:: AddressInUseError
Die mit QUdpSocket::bind() verwendete Adresse wird bereits benutzt, und zwar ausschließlich (exklusive) von diesem einen Socket.
QAbstractSocket:: SocketAddressNotAvailableError
Die mit QUdpSocket::bind() verwendete Adresse gehört nicht zum Host.
QAbstractSocket:: UnsupportedSocketOperationError
Die angeforderte Socket-Operation wird nicht vom lokalen Betriebssystem unterstützt (bspw. bei fehlender IPv6-Unterstützung).
QAbstractSocket:: UnknownSocketError
Ein undefinierbarer Fehler ist aufgetreten.
Tabelle 6.34
Mögliche Socket-Fehler (Forts.)
In der nächsten Tabelle finden Sie einen Überblick über mögliche SocketZustände (ein solcher Zustand wird mit der enum-Variablen QAbstractSocket:: SocketState beschrieben). Konstante
Beschreibung
QAbstractSocket:: UnconnectedState
Der Socket ist noch nicht verbunden.
QAbstractSocket::HostLookupState
Der Socket führt gerade einen Host-NamenLookup durch.
QAbstractSocket::ConnectingState
Der Socket versucht gerade eine Verbindung aufzubauen.
QAbstractSocket::ConnectedState
Der Socket ist verbunden.
QAbstractSocket::BoundState
Der Socket ist fest an die Adresse und den Port gebunden (für Server).
Tabelle 6.35
Zustand eines Sockets
501
6.8
1542.book Seite 502 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Konstante
Beschreibung
QAbstractSocket::ClosingState
Der Socket wird geschlossen (Daten zum Schreiben bleiben trotzdem erhalten).
QAbstractSocket::ListeningState
Nur für interne Verwendung vorhanden.
Tabelle 6.35
Zustand eines Sockets (Forts.)
Die folgenden öffentlichen Signale können bei einem QAbstractSocket und den davon abgeleiteten Klassen auftreten: Signal
Beschreibung
void connected ();
Dieses Signal wird nach einem Aufruf von connectToHost() und einer damit einhergehenden erfolgreichen Verbindung ausgelöst.
void disconnected ();
Das Signal wird ausgelöst, wenn der Socket geschlossen wurde.
void error ( QAbstractSocket::SocketError socketError );
Das Signal wird ausgelöst, wenn ein Fehler aufgetreten ist. Die Art des Fehlers befindet sich im Parameter socketError, dessen Beschreibung Sie in Tabelle 6.34 finden.
void hostFound ();
Das Signal wird ausgelöst, nachdem connectToHost() aufgerufen wurde und das Host-Lookup erfolgreich war.
void stateChanged ( QAbstractSocket::SocketState socketState );
Das Signal wird ausgelöst, wenn der Zustand eines Sockets verändert wurde. Der neue Zustand befindet sich im Parameter socketState. Die Bedeutung des Zustands entnehmen Sie bitte Tabelle 6.35.
Tabelle 6.36
Öffentliche Signale der Klasse QAbstractSocket
Die Klasse QAbstractSocket enthält zudem einige geschützte (Zugriffsmodus: protected) Methoden, die hier auf jeden Fall noch erwähnt werden sollten. protected-Methode
Beschreibung
void setLocalAddress ( const QHostAddress& address );
Setzt die Adresse auf der lokalen Seite einer Verbindung auf adress. Beachten Sie, dass diese Methode nicht die lokale Adresse des Sockets mit der Verbindung (wie bspw. bind()) bindet.
Tabelle 6.37
502
protected-Methoden der Klasse QAbstractSocket
1542.book Seite 503 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
protected-Methode
Beschreibung
void setLocalPort ( quint16 port );
Setzt den Port auf der lokalen Seite einer Verbindung auf port. Beachten Sie, dass diese Methode nicht die lokale Adresse des Sockets mit der Verbindung (wie bspw. bind()) bindet.
void setPeerAddress ( const QHostAddress& address );
Setzt die Adresse auf der anderen Seite einer Verbindung auf adress.
void setPeerName ( const QString & name );
Setzt den Hostname auf der anderen Seite einer Verbindung auf name.
void setPeerPort ( quint16 port );
Setzt den Port auf der anderen Seite einer Verbindung auf port.
void setSocketError ( SocketError socketError );
Setzt den Typ des zuletzt aufgetretenen Fehlers auf socketError.
void setSocketState ( SocketState state );
Setzt den Zustand des Sockets auf state.
Tabelle 6.37
protected-Methoden der Klasse QAbstractSocket (Forts.)
Zusätzlich enthält die Klasse QAbstractSocket zwei Slots, die allerdings ebenfalls geschützter (protected) Natur sind. protected Slots
Beschreibung
void connectToHostImplementation ( const QString& hostName, quint16 port, OpenMode openMode=ReadWrite );
Enthält eine Implementation der Methode connectToHost(). Damit wird versucht, eine Verbindung zum Host hostName mit dem Port port herzustellen. Optional kann mit openMode der Modus (Lesen/Schreiben) dieser Verbindung gesetzt werden.
void disconnectFromHostImplementation ();
Enthält eine Implementation von disconnectFromHost().
Tabelle 6.38
protected-Slots der Klasse QAbstractSocket
QTcpSocket Aufbauend auf dem IP-Protokoll gibt es zwei wichtige Transportprotokolle: TCP/ IP (TCP = Transmission Control Protocol) und UDP/IP (UDP = User Datagram Protocol). Beide Protokolle sind auf der Transport-Ebene angesiedelt. Der Vorteil von TCP ist, dass hier eine zuverlässige Datenübertragung garantiert wird. Dabei wird bspw. sichergestellt, dass ein Paket, welches nach einer gewissen Zeit nicht beim Empfänger ankommt, erneut gesendet wird. Ebenso wird die richtige Reihenfolge der einzelnen Datenpakete von TCP garantiert, in der diese losgeschickt wurden.
503
6.8
1542.book Seite 504 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Zusammengefasst, erhalten Sie beim TCP-Protokoll eine Ende-zu-Ende-Kontrolle (Peer-to-Peer), ein Verbindungs-Management (nach dem sogenannten Handshake-Prinzip), eine Zeitkontrolle, eine Flusskontrolle sowie eine Fehlerbehandlung der Verbindung. Man spricht bei TCP von einem verbindungsorientierten Transportprotokoll. Die Klasse QTcpSocket bietet einen solchen TCP-Socket an, wobei hier eigentlich keine eigenen Methoden mehr nötig sind, weil alles schon in der Basisklasse QAbstractSocket enthalten ist. Natürlich gibt es hierzu den Konstruktor und Destruktor der Klassen: Methode
Beschreibung
QTcpSocket ( QObject * parent = 0 );
Erzeugt ein QTcpSocket-Objekt mit UnconnectedState als Zustand und optional mit parent als
virtual ~QTcpSocket ()
Zerstört ein QTcpSocket-Objekt und schließt ggf. auch die Verbindung.
Eltern-Widget.
Tabelle 6.39
Methoden der Klasse QTcpSocket
QTcpServer Die Klasse QTcpServer bietet alles Nötige an, um einen TCP-basierenden Server zu verwenden, ohne hierbei das Rad mit Methoden der Klasse QAbstractSocket neu zu erfinden. Die Klasse akzeptiert eingehende TCP-Verbindungen, man kann eine Portnummer festlegen oder veranlassen, dass QTcpServer sich automatisch eine aussucht. Sie können an einer festgelegten oder auch beliebigen Adresse auf eingehende Verbindungen warten (listen()). Am besten sehen Sie sich hierzu die vorhandenen Methoden selbst an. Methode
Beschreibung
QTcpServer ( QObject * parent = 0 );
Erzeugt ein QTcpServer-Objekt mit (optional) parent als Eltern-Widget.
virtual ~QTcpServer ();
Zerstört ein QTcpServer-Objekt. Wartet der Server auf Verbindungen (listen), wird der Socket automatisch geschlossen. Alle Client-Sockets (QTcpSocket), die noch verbunden waren, werden automatisch getrennt und von den Eltern gelöst, bevor der Server gelöscht wird.
void close ();
Schließt den Server. Der Server wartet (listen) nicht mehr auf eingehende Verbindungen.
QString errorString () const;
Gibt eine menschlich lesbare Beschreibung mit dem zuletzt aufgetretenen Fehler zurück.
Tabelle 6.40
504
Methoden der Klasse QTcpServer
1542.book Seite 505 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methode
Beschreibung
virtual bool Gibt true zurück, wenn der Server noch offene VerhasPendingConnections () const; bindungen hat. Ansonsten wird false zurückgege-
ben. bool isListening () const;
Gibt true zurück, wenn der Server auf eingehende Verbindungen wartet. Ansonsten wird false zurückgegeben.
bool listen ( const QHostAddress & address = QHostAddress::Any, quint16 port = 0 );
Teilt dem Server eingehende Verbindungen mit der Adresse adress und dem Port port mit. Ist Port gleich 0, wird der Port automatisch ausgewählt. Ist die Adresse gleich QHostAddress::Any, lauscht der Server an allen Netzwerk-Schnittstellen. Bei Erfolg wird true, ansonsten false zurückgegeben.
int Gibt die maximale Anzahl abhängiger und zu akzepmaxPendingConnections () const; tierender Verbindungen zurück. Der Standardwert
ist gewöhnlich 30. virtual QTcpSocket * nextPendingConnection ();
Gibt die nächste abhängige Verbindung als QTcpSocket-Objekt zurück. Das Socket wird als Kind des Servers erzeugt, was somit auch bedeutet, dass das Socket automatisch gelöscht wird, wenn das QTcpServer-Objekt zerstört wird. Daher kann es
sinnvoll sein, dass Sie das Objekt selbst löschen, um nicht unnötig den Speicher auszulasen. Die Methode gibt 0 zurück, wenn keine abhängigen Verbindungen vorhanden sind. QNetworkProxy proxy () const;
Gibt den Proxy für das Socket zurück. Der Standardwert ist QNetworkProxy::DefaultProxy.
QhostAddress serverAddress () const;
Gibt die Adresse des Servers zurück, wenn dieser auf Verbindungen wartet (listen). Ansonsten wird QhostAddress::Null zurückgegeben.
QAbstractSocket::SocketError serverError () const;
Gibt den Fehlercode des zuletzt aufgetretenen Fehlers zurück. Mögliche Rückgabewerte und deren Bedeutung wurden bereits in Tabelle 6.34 beschrieben.
quint16 serverPort () const;
Gibt die Portnummer des Servers, der auf Verbindungen wartet, zurück. Ansonsten wird 0 zurückgegeben.
void setMaxPendingConnections ( int numConnections );
Setzt die maximale Anzahl abhängiger und zu akzeptierender Verbindungen auf numConnections. QTcpServer akzeptiert keine weiteren Verbindungen, bevor nicht nextPendingConnection() aufgerufen wird. Der Standardwert ist 30.
Tabelle 6.40
Methoden der Klasse QTcpServer (Forts.)
505
6.8
1542.book Seite 506 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
void setProxy ( const QnetworkProxy & networkProxy );
Setzt einen explizten Proxy auf networkProxy. Zum Anschalten eines Proxy für das Socket wird der Wert QNetworkProxy::NoProxy gesetzt.
bool setSocketDescriptor ( int socketDescriptor );
Setzt den Socket socketDescriptor als ServerSocket. An diesem Socket wird dann auch auf die eingehenden Verbindungen gewartet. Die Methode gibt true, ansonsten, bei einem Fehler, false zurück. Der Socket wird dann in einem lauschenden Zustand (listen) versetzt.
int socketDescriptor () const;
Gibt den nativen Socket-Deskriptor von einem QTcpServer-Objekt zurück. Bei einem Fehler wird –1 zurückgegeben. Verwendet der Socket ein QNetworkProxy, kann der zurückgegebene Socket-Deskriptor nicht mit nativen Socket-Funktionen verwendet werden. Der Socket-Deskriptor ist nicht vorhanden, wenn sich QTcpServer im Zustand UnconnectState befand.
bool waitForNewConnection ( int msec = 0, bool * timedOut = 0 );
Wartet mindestens msec Millisekunden oder bis eine eingehende Verbindung vorhanden ist. Gibt true zurück, wenn eine Verbindung vorhanden ist. Ansonsten wird false zurückgegeben. Wenn die Operation Timeout zurück liefert und timeOut nicht 0 ist, wird *timeOut auf true gesetzt. Bei Verwendung der Methode sollte man beachten, dass die komplette GUI in einer Single-Thread-Anwendung blockiert wird, bis diese wieder zurückkehrt. Als Alternative würde sich hier das nicht-blockierende Signal newConnection() anbieten.
Tabelle 6.40
Methoden der Klasse QTcpServer (Forts.)
Jetzt zum einzigen Signal der Klasse QTcpServer, welches bereits als Alternative für die blockierende Methode waitForNewConnection() erwähnt wurde. Signal
Beschreibung
void newConnection ();
Das Signal wird immer ausgelöst, wenn eine neue Verbindung vorhanden ist.
Tabelle 6.41
Signal der Klasse QTcpServer
Hierzu ein recht einfaches und klassisches Beispiel: ein Time-Server. Wir erstellen einen Server, der von einer beliebigen Adresse an einem bestimmten Port (hier 12345) auf Client-Anfragen wartet. Trifft eine solche Anfrage ein, senden wir diesem die aktuelle Uhrzeit des Servers zu. Zunächst das Grundgerüst:
506
1542.book Seite 507 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
00 01 02 03 04 05 06 07
// beispiele/tcp/server/server.h #ifndef SERVER_H #define SERVER_H #include #include #include #include #include
08 09 10 11 12 13 14 15 16 17 18 19
class Server : public QDialog { Q_OBJECT public: Server(QWidget *parent = 0); private slots: void sendServerTime(); private: QLabel *statusLabel; QPushButton *quitButton; QTcpServer *tcpServer; }; #endif
Jetzt die Implementierung des Codes, der ausreichend dokumentiert wurde: 00 01 02 03 04
// beispiele/tcp/server/server.cpp #include #include #include #include "server.h"
05 Server::Server(QWidget *parent) : QDialog(parent) { 06 statusLabel = new QLabel; 07 quitButton = new QPushButton(tr("Beenden")); 08 quitButton->setAutoDefault(false); 09 // neuen TCP-Server erstellen 10 tcpServer = new QTcpServer(this); 11 // auf eingehende Verbindungen am Port lauschen 12 if (!tcpServer->listen(QHostAddress::Any, 12345)) { 13 QMessageBox::critical( this, tr("Zeit-Server"), tr("Fehler beim Server: %1.") .arg(tcpServer->errorString())); 14 // Server schließen 15 close(); 16 return;
507
6.8
1542.book Seite 508 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
17 18 19
20 21 22 23 24
} statusLabel->setText( tr("Der Server läuft an Port %1\n" "und wartet auf Client-Anfragen") .arg(tcpServer->serverPort()) ); // Signal-Slot-Verbindungen einrichten connect( quitButton, SIGNAL(clicked()), this, SLOT(close() ) ); // bei neuer eingehender Client-Verbindung den Slot // sendServerTime() aufrufen connect( tcpServer, SIGNAL(newConnection()), this, SLOT(sendServerTime() ) );
25 26 27 28
QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(1); buttonLayout->addWidget(quitButton); buttonLayout->addStretch(1);
29 30 31 32 33 34 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(statusLabel); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setWindowTitle(tr("Zeit-Server"));
35 void Server::sendServerTime() { 36 // die Daten werden als QByteArray gesendet 37 QByteArray block; 38 QDataStream out(&block, QIODevice::WriteOnly); 39 out.setVersion(QDataStream::Qt_4_0); 40 out write(block); // nach getaner Arbeit den Client vom Host trennen clientConnection->disconnectFromHost();
Jetzt noch das Hauptprogramm: 00 01 02 03 04
// beispiele/tcp/server/main.cpp #include #include #include <stdlib.h> #include "server.h"
05 int main(int argc, char *argv[]) { 06 QApplication app(argc, argv); 07 Server server; 08 server.show(); 09 qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); 10 return server.exec(); 11 }
Die Server-Anwendung bei der Ausführung:
Abbildung 6.12
Der Server wartet auf Client-Anfragen
Die Server-Anwendung alleine tut hierbei nichts anderes, als auf eingehende Anfragen einer Client-Anwendung zu warten. Diese Client-Anwendung wollen wir jetzt erstellen. Zunächst auch hier das Grundgerüst: 00 01 02 03
// beispiele/tcp/client/client.h #ifndef CLIENT_H #define CLIENT_H #include
509
6.8
1542.book Seite 510 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
04 05 06 07 08
#include #include #include #include #include
09 class Client : public QDialog { 10 Q_OBJECT 11 public: 12 Client(QWidget *parent = 0); 13 private slots: 14 void requestNewTime(); 15 void readTime(); 16 void displayError( QAbstractSocket::SocketError socketError ); 17 private: 18 QLabel *hostLabel; 19 QLabel *portLabel; 20 QLineEdit *hostLineEdit; 21 QLineEdit *portLineEdit; 22 QLabel *statusLabel; 23 QPushButton *getTimeButton; 24 QPushButton *quitButton; 25 QDialogButtonBox *buttonBox; 26 QTcpSocket *tcpSocket; 27 QString currentTime; 28 quint16 blockSize; 29 }; 30 #endif
Jetzt die Implementierung des Codes. Im Beispiel wird der Servername mit »localhost« (dem Loopback-Interface auch 127.0.0.1) und dem Port 12345 vorgegeben. Sollten Sie Ihre Server-Anwendung auf einem anderen Rechner stehen haben und hierbei eine andere Portnummer verwenden, müssen Sie in der Eingabezeile natürlich die entsprechende Server-Adresse und Port angeben. Im Beispiel wird einfach davon ausgegangen, dass Sie dieses Beispiel auf dem lokalen Rechner (localhost) ausführen und testen. 00 01 02 03
// beispiele/tcp/client/client.cpp #include #include #include "client.h"
04 Client::Client(QWidget *parent) : QDialog(parent) { 05 hostLabel = new QLabel(tr("&Server-Name:"));
510
1542.book Seite 511 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
06 07 08 09 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
portLabel = new QLabel(tr("S&erver-Port:")); hostLineEdit = new QLineEdit("localhost"); portLineEdit = new QLineEdit("12345"); // nur Portnummern von 1 bis 65535 erlauben portLineEdit->setValidator( new QIntValidator( 1, 65535, this ) ); // setBuddy() siehe Kapitel 4, Tabelle 4.43, S. 192 hostLabel->setBuddy(hostLineEdit); portLabel->setBuddy(portLineEdit); statusLabel = new QLabel( tr( "Damit Sie das Beispiel testen können," " muss der Zeitserver gestartet sein") ); getTimeButton = new QPushButton(tr("Zeit holen")); getTimeButton->setDefault(true); quitButton = new QPushButton(tr("Beenden")); buttonBox = new QDialogButtonBox; buttonBox->addButton( getTimeButton, QDialogButtonBox::ActionRole ); buttonBox->addButton( quitButton, QDialogButtonBox::RejectRole ); // neuen Tcp-Socket erstellen tcpSocket = new QTcpSocket(this); // Signal-Slot Verbindungen connect( getTimeButton, SIGNAL(clicked()), this, SLOT(requestNewTime() ) ); connect( quitButton, SIGNAL(clicked()), this, SLOT(close() ) ); connect( tcpSocket, SIGNAL(readyRead()), this, SLOT(readTime() ) ); connect( tcpSocket, SIGNAL(error( QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError) ) ); QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(hostLabel, 0, 0); mainLayout->addWidget(hostLineEdit, 0, 1); mainLayout->addWidget(portLabel, 1, 0); mainLayout->addWidget(portLineEdit, 1, 1); mainLayout->addWidget(statusLabel, 2, 0, 1, 2); mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
511
6.8
1542.book Seite 512 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
35 36 37 38 }
setLayout(mainLayout); setWindowTitle(tr("Zeit-Client")); portLineEdit->setFocus();
39 // Anforderung an den Server stellen 40 void Client::requestNewTime() { 41 getTimeButton->setEnabled(false); 42 blockSize = 0; 43 // falls in Verwendung, Socket zurücksetzen 44 tcpSocket->abort(); 45 // mit dem Server verbinden 46 tcpSocket->connectToHost( hostLineEdit->text(), portLineEdit->text().toInt()); 47 } 48 // die Antwort des Servers lesen 49 void Client::readTime() { 50 QDataStream in(tcpSocket); 51 in.setVersion(QDataStream::Qt_4_0); 52 if (blockSize == 0) { 53 // Sind Daten zum Lesen vorhanden 54 if ( tcpSocket->bytesAvailable() < (int)sizeof(quint16) ) 55 return; 56 in >> blockSize; 57 } 58 if (tcpSocket->bytesAvailable() < blockSize) 59 return; 60 QString nextTime; 61 in >> nextTime; 62 if (nextTime == currentTime) { 63 QTimer::singleShot(0, this, SLOT(requestNewTime())); 64 return; 65 } 66 currentTime = nextTime; 67 statusLabel->setText(currentTime); 68 getTimeButton->setEnabled(true); 69 } 70 // Tritt ein Fehler auf, wird dieser in 71 // einer Nachrichten-Box angezeigt.
512
1542.book Seite 513 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
72 void Client::displayError( QAbstractSocket::SocketError socketError) { 73 QString msg; 74 switch (socketError) { 75 case QAbstractSocket::RemoteHostClosedError: 76 break; 77 case QAbstractSocket::HostNotFoundError: 78 msg.append(tr( "Die Host-Adresse konnte nicht gefunden" " werden.\nVielleicht falscher Port oder " "falscher Host-Name")); 79 break; 80 case QAbstractSocket::ConnectionRefusedError: 81 msg.append(tr( "Die Verbindung wurde vom Gegenüber " "abgewiesen\n(oder ein Timeout ist " "eingetreten). Stellen Sie\nsicher, dass" " der Zeitserver läuft und überprüfen Sie\n" "den Hostnamen und Portnummer des Servers")); 82 break; 83 default: 84 msg.append(tr("Folgender Fehler trat auf: %1") .arg(tcpSocket->errorString())); 85 } 86 if( ! msg.isEmpty() ) 87 QMessageBox::information( this, tr("Zeit Client"), msg); 88 getTimeButton->setEnabled(true); 89 }
Jetzt nur noch die Hauptfunktion: 00 // beispiele/tcp/client/main.cpp 01 #include 02 #include "client.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 Client client; 06 client.show(); 07 return client.exec(); 08 }
513
6.8
1542.book Seite 514 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Das Programm bei der Ausführung nach dem Starten des Clients:
Abbildung 6.13
Der Client ist bereit
Wenn der Server gestartet ist und Sie den richtigen Server-Namen und Port angegeben haben, können Sie den Button »Zeit holen« drücken und erhalten die aktuelle Server-Zeit wie in der folgenden Abbildung:
Abbildung 6.14
Aktuelle Server-Zeit geholt
Projektdatei anpassen nicht vergessen! Vergessen Sie bei diesen Beispielen nicht, QT += network der Projektdatei hinzuzufügen.
QUdpSocket Das UDP-Protokoll verwendet hingegen einen verbindungslosen Datenaustausch zwischen den einzelnen Rechnern. Mit UDP haben Sie in Ihren Anwendungen die direkte Möglichkeit, Datagramme zu senden. Allerdings ohne Garantie der Ablieferung eines Datagramms beim Empfänger. Ebenso wenig ist es gegeben, dass die Datagramme in der richtigen Reihenfolge ankommen (es ist sogar möglich, dass Datagramme mehrfach ankommen). Der Vorteil, dass hier weniger Verwaltungsaufwand betrieben werden muss, ist natürlich ein höherer Datendurchsatz, der mit TCP nicht möglich ist. UDP ist bspw. recht interessant für Videoübertragung oder bei Netzwerkspielen. Hierbei ist es nicht so schlimm, wenn das eine oder andere Datenpaket mal nicht ankommt. Die UDP-Strategie kann man gerne mit dem »Erst schießen, dann Fragen«-Motto vergleichen. Und eben für solche »leichtgewichtigen« UDP-Sockets
514
1542.book Seite 515 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
steht Ihnen bei Qt die Klasse QUdpSocket zur Verfügung. In der folgenden Tabelle finden Sie die neben QAbstractSocket zusätzlich vorhandenen öffentlichen Methoden, die in dieser Klasse definiert sind: Methode
Beschreibung
QUdpSocket ( QObject * parent = 0 )
Erzeugt ein QUdpSocket-Objekt mit parent als Eltern-Objekt.
virtual ~QUdpSocket ()
Zerstört ein QUdpSocket-Objekt und schließt die Verbindung wenn nötig.
bool bind ( const QHostAddress & address, quint16 port );
Bindet den Socket an die Adresse adress und den Port port. Wenn der Socket erfolgreich gebunden werden konnte, wird true zurückgegeben, und das Signal readyRead() wird ausgelöst, wenn ein UDPDatagramm an dieser Adresse und dem Port eingegangen ist. Bei einem Fehler gibt diese Methode false zurück.
bool bind ( const QHostAddress & address, quint16 port, BindMode mode );
Eine überladene Version mit einem zusätzlichen Parameter für den Modus mode. Mögliche Werte für BinMode und deren Bedeutung siehe Tabelle 6.43.
bool bind ( quint16 port = 0 );
Eine weitere überladene Version, womit der Port port an QHostAdress::Any gebunden wird.
bool bind ( quint16 port, BindMode mode );
Noch eine überladene Version, womit der Port port mit dem Modus mode an QHostAdress::Any gebunden wird.
bool Gibt true zurück, wenn mindestens ein Datagramm hasPendingDatagrams () const; zum Lesen vorhanden ist. Ansonsten wird false
zurückgegeben. qint64 Gibt die Größe des ersten einzulesenden UDP-DatapendingDatagramSize () const; gramms zurück. Ist kein Datagramm vorhanden, wird
–1 zurückgegeben. qint64 readDatagram ( char * data, qint64 maxSize, QHostAddress * address = 0, quint16 * port = 0 );
Tabelle 6.42
Empfängt ein Datagramm, welches nicht größer als maxSize Bytes ist, und speichert dies in data. Die Adresse des Senders und dessen Port werden in adress und port gespeichert (wenn die Zeiger nicht 0 sind). Zurückgegeben wird die Größe des Datagramms bei Erfolg oder –1, wenn ein Fehler aufgetreten ist. Wenn maxSize zu klein ist, wird der Rest des Datagramms verworfen. Dies können Sie vermeiden, indem Sie vor dem Lesen die Größe des Datagramms mit pendingDatagramSize() überprüfen.
Methoden der Klasse QUdpSocket
515
6.8
1542.book Seite 516 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
qint64 writeDatagram ( const char * data, qint64 size, const QHostAddress & address, quint16 port );
Sendet das Datagramm data mit der Größe von size Bytes an die Host-Adresse adress mit der Portnummer port. Bei Erfolg wird die Anzahl der erfolgreich gesendeten Bytes oder bei Fehler –1 zurückgegeben. Datagramme werden immer im Block geschrieben. Die Größe ist meistens plattformabhängig, dürfte allerdings selten weniger als 8192 Bytes betragen. Ist das Datagramm zu groß, gibt diese Methode –1 und error() gibt DataGramTooLargeError zurück.
qint64 writeDatagram ( const QByteArray & datagram, const QHostAddress & host, quint16 port );
Die überladene Methode, mit der das Datagramm datagram an die Adresse host mit der Portnummer port gesendet wird.
Tabelle 6.42
Methoden der Klasse QUdpSocket (Forts.)
Jetzt zu den unterschiedlichen Flags der enum-Variablen QUdpSocket::BindMode, womit Sie das Verhalten von QUdpSocket::bind() verändern können. Konstante
Beschreibung
QUdpSocket::ShareAddress
Erlaubt anderen Diensten, sich mit der selben Adresse und Portnummer zu binden. Unter Unix entspricht dies dem Socketflag SO_REUSEADDR. Unter Windows wird diese Option ignoriert.
QUdpSocket::DontShareAddress
Bindet Adressen und deren Portnummer exklusiv, so dass kein weiterer Dienst damit gebunden werden kann. Unter Unix/Mac OS X ist diese Option das Standard-Verhalten, wenn die Adresse und der Port gebunden werden, so dass diese Option ignoriert wird. Unter Window entspricht diese Option dem Socketflag SO_EXCLUSIVEADDRUSE.
QUdpSocket::ReuseAddressHint
Entspricht SO_REUSEADDR unter Windows. Unter Unix hat diese Option keine Wirkung.
QUdpSocket::DefaultForPlatform Die Standardeinstellung für die aktuelle Plattform. Unter Unix und Mac OS X entspricht dies DontShareAddress | ReuseAddressHint, und unter Windows ist dies ShareAdress.
Tabelle 6.43
Konstanten, mit denen das bind()-Verhalten geändert werden kann
Auch hierzu ein einfaches Beispiel einer UDP-Client/Server-Anwendung. Zunächst soll ein Server erstellt werden, der auf Daten von einem bestimmten Port (hier 54321) wartet und diese anschließend ausliest und im serverseitigen Texteditor zeilenweise ausgibt. Das Grundgerüst:
516
1542.book Seite 517 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
00 01 02 03 04 05
// beispiele/udp/server/server.h #ifndef RECEIVER_H #define RECEIVER_H #include #include #include
06 07 08 09 10 11 12 13 14 15 16
class Receiver : public QMainWindow { Q_OBJECT public: Receiver(QMainWindow *parent = 0); private slots: void processPendingDatagrams(); private: QTextEdit *textEd; QUdpSocket *udpSocket; }; #endif
Die Implementierung des Codes: 00 01 02 03
// beispiele/udp/server/server.cpp #include #include #include "server.h"
04 Receiver::Receiver(QMainWindow *parent) : QMainWindow(parent) { 05 textEd = new QTextEdit; 06 textEd->setText( tr("UDP-Server bereit für Datenempfang\n") ); 07 QMenu *fileMenu = new QMenu(tr("&UDP-Server"), this); 08 menuBar()->addMenu(fileMenu); 09 fileMenu->addAction( QIcon(":/images/cancel.png"), tr("&Beenden"), this, SLOT( close() ), QKeySequence( tr("Ctrl+Q", "UDP-Server|Beenden") ) ); 10 // neues UDP-Socket erstellen 11 udpSocket = new QUdpSocket(this); 12 // an Port-Nummer 54321 binden 13 udpSocket->bind(54321); 14 // Sind Daten am Port zum Lesen vorhanden, 15 // dann einlesen. 16 connect( udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams() ) );
517
6.8
1542.book Seite 518 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
17 18 19 }
setCentralWidget(textEd); setWindowTitle(tr("Broadcast Receiver"));
20 // Daten am UDP-Socket lesen 21 void Receiver::processPendingDatagrams() { 22 while (udpSocket->hasPendingDatagrams()) { 23 QByteArray datagram; 24 datagram.resize(udpSocket->pendingDatagramSize()); 25 udpSocket->readDatagram( datagram.data(), datagram.size() ); 26 textEd->moveCursor( QTextCursor::End, QTextCursor::KeepAnchor ); 27 textEd->insertPlainText( tr("Daten erhalten: \"%1\"\n") .arg(datagram.data() ) ); 28 } 29 }
Jetzt fehlt nur noch das Hauptprogramm: 00 // beispiele/udp/server/main.cpp 01 #include 02 #include "server.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 Receiver receiver; 06 receiver.show(); 07 return app.exec(); 07 }
{
Das Programm bei der Ausführung:
Abbildung 6.15
Der UDP-Server bei der Ausführung
Der Server ist jetzt bereit, Nachrichten von beliebigen Clients zu empfangen, einzulesen und auf dem Editor auszugeben. Wir wollen nun eine einfache Client-
518
1542.book Seite 519 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Anwendung starten, die jede Sekunde eine Nachricht an den Server sendet. Hierzu verwenden wir die Klasse QTimer, die jede Sekunde ein Timeout auslöst und einen einfachen Text an den Server sendet. Zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08
// beispiele/udp/client/client.h #ifndef SENDER_H #define SENDER_H #include #include #include #include #include #include
09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
class Sender : public QDialog { Q_OBJECT public: Sender(QWidget *parent = 0); private slots: void startBroadcasting(); void broadcastDatagram(); private: QLabel *statusLabel; QPushButton *startButton; QPushButton *quitButton; QDialogButtonBox *buttonBox; QUdpSocket *udpSocket; QTimer *timer; int messageNo; }; #endif
Jetzt die Implementierung des Codes: 00 01 02 03
// beispiele/udp/client/client.cpp #include #include #include "client.h"
04 Sender::Sender(QWidget *parent) : QDialog(parent) { 05 statusLabel = new QLabel( tr( "Bereit zum Versenden von Datagrammen" " an Port 54321" ) ); 05 startButton = new QPushButton(tr("&Starten")); 06 quitButton = new QPushButton(tr("&Beenden"));
519
6.8
1542.book Seite 520 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
07 08 09
buttonBox = new QDialogButtonBox; buttonBox->addButton( startButton, QDialogButtonBox::ActionRole ); buttonBox->addButton( quitButton, QDialogButtonBox::RejectRole );
10 11 12
timer = new QTimer(this); udpSocket = new QUdpSocket(this); messageNo = 1;
13
connect( startButton, SIGNAL(clicked()), this, SLOT(startBroadcasting() ) ); connect( quitButton, SIGNAL(clicked()), this, SLOT(close() ) ); connect( timer, SIGNAL(timeout()), this, SLOT(broadcastDatagram() ) );
14 15
16 17 18 19 20 21 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(statusLabel); mainLayout->addWidget(buttonBox); setLayout(mainLayout); setWindowTitle(tr("Broadcast Sender"));
22 void Sender::startBroadcasting() { 23 startButton->setEnabled(false); 24 timer->start(1000); 25 } 26 void Sender::broadcastDatagram() { 27 statusLabel->setText( tr("Sende Datagramm Nummer %1").arg(messageNo)); 28 QByteArray datagram = "Broadcast message " + QByteArray::number(messageNo); 29 udpSocket->writeDatagram( datagram.data(), datagram.size(), QHostAddress::LocalHost, 54321 ); 30 ++messageNo; 31 }
Die Daten werden hierbei in der Zeile 29 direkt an den Server gesendet. Wie für UDP typisch, werden die Daten einfach gesendet, ohne zu überprüfen, ob der Server vorhanden ist oder nicht. Das bedeutet auch: Sie könnten Daten mit dem
520
1542.book Seite 521 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Clienten versenden, ohne dass die Server-Anwendung überhaupt läuft. Jetzt noch das Hauptprogramm: 00 // beispiele/udp/client/main.cpp 01 #include 02 #include "client.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 Sender sender; 06 sender.show(); 07 return sender.exec(); 08 }
{
Das Programm bei der Ausführung:
Abbildung 6.16
UDP-Client bei der Ausführung
Wenn Sie jetzt den UDP-Client starten (Button »Starten«), beginnt dieser mit dem Versenden von Datagrammen (ein Datagramm pro Sekunden):
Abbildung 6.17
Der UDP-Client wurde gestartet.
Währenddessen sollte auf der anderen Seite des Servers Folgendes auf dem Editor erscheinen:
Abbildung 6.18
Der Server hat die Nachrichten empfangen.
521
6.8
1542.book Seite 522 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Natürlich können Sie jederzeit weitere Client-Anwendungen starten und damit Daten an den Server versenden. In der folgenden Server-Abbildung wurden Datagramme von drei Client-Anwendungen gleichzeitig gesendet:
Abbildung 6.19
Mehrere Client-Anwendungen senden Datagramme
Sie dürfen gerne auch mal den Server beenden, um zu sehen, dass die ClientAnwendungen ohne zu murren weiter Daten senden. So könnten Sie bspw. ohne Problem erst den Client und dann den Server starten. Projektdatei anpassen nicht vergessen! Vergessen Sie auch bei diesem Beispielen nicht, QT += network der Projektdatei hinzuzufügen.
QHostInfo und QHostAdress Der Vollständigkeit halber sollen hier die beiden Klasse QHostInfo und QHostAdress erwähnt werden. Die Klasse QHostInfo und dessen Methoden können für die einfache Namensauflösung verwendet werden. QHostAdress hingegen kapselt die Hostnamen und IP-Adressen (IPv4 und IPv6). Zwar wird nicht näher auf diese Klassen eingegangen, trotzdem sollen auch hier die einzelnen Methoden kurz beschrieben werden. Zunächst die Methoden der Klasse QHostInfo: Methode
Beschreibung
QHostInfo ( int id = –1 );
Erzeugt ein leeres QHostInfo-Objekt mit der Lookup-ID id.
QHostInfo ( const QHostInfo & other );
Kopier-Konstruktor. Erzeugt ein neues QHostInfoObjekt aus other.
~QHostInfo ()
Destruktor. Zerstört ein QHostInfo-Objekt.
Tabelle 6.44
522
Methoden der Klasse QHostInfo
1542.book Seite 523 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methode
Beschreibung
QList addresses () const;
Gibt eine Liste von IP-Adressen zurück. Beispiel: QHostInfo hi; if(!hi.addresses().isEmpty()) { // Die erste IP-Adresse QHostAddress address = info.addresses().first(); }
HostInfoError error () const; Gibt die Art des Fehlers zurück, der bei der letzten
Namensauflösung aufgetreten ist. Ansonsten wird NoError zurückgegeben. Mögliche Werte und deren Bedeutung siehe Tabelle 6.46. QString errorString () const; Wenn die Namensauflösung fehlgeschlagen hat, liefert
diese Methode eine lesbare Beschreibung des Fehlers zurück. Ansonsten wird »Unknow error« zurückgegeben. QString hostName () const;
Gibt den Namen des Hosts der Namensauflösung der IPAdresse zurück.
int lookupId () const;
Gibt die ID des Lookup zurück.
void setAddresses ( const QList & addresses );
Setzt ein ganze Liste von Adressen im QHostInfoObjekt auf adresses.
void setError ( HostInfoError error );
Setzt den Fehler des QHostInfo-Objekts auf error. Mögliche Werte siehe Tabelle 6.46.
void setErrorString ( const QString & str );
Setzt die Beschreibung der lesbaren Fehlermeldung des Fehlers, der zuletzt aufgetreten ist, auf str, wenn die Namensauflösung fehlgeschlagen ist.
void setHostName ( const QString & hostName );
Setzt den Hostname des QHostInfo-Objekts auf hostName.
void setLookupId ( int id );
Setzt die ID der Namensauflösung auf id.
QHostInfo & operator= ( const QHostInfo & other );
Weist die Daten von other dem aktuellen QHostInfoObjekt zu und gibt ein Referenz darauf zurück.
Tabelle 6.44
Methoden der Klasse QHostInfo (Forts.)
Des weiteren sind noch folgende statische Methoden in der Klasse QHostInfo definiert: Statische Methode
Beschreibung
void abortHostLookup ( int id ); Bricht die Namensauflösung mit der ID id ab, die von lookupHost() zurückgegeben wurde.
Tabelle 6.45
Statische Methoden der Klasse QHostInfo
523
6.8
1542.book Seite 524 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Statische Methode
Beschreibung
QHostInfo fromName ( const QString & name );
Löst die IP-Adresse für den übergebenen Hostnamen name auf. Während der Auflösung des Namens blockiert diese Methode allerdings den Event-Loop, bis Erstere fertig ist. Zurückgegeben wird ein QHostInfoObjekt. Wollen Sie das Blockieren vermeiden, können Sie auf die statische Methode lookupHost() zurückgreifen.
QString localHostName ();
Gibt den Hostnamen dieser Maschine zurück, worauf diese Anwendung läuft.
int lookupHost ( const QString & name, QObject * receiver, const char * member );
Löst die IP-Adresse für den angegebenen Hostnamen name auf und gibt die ID für die Auflösung des Namens zurück. Im Gegensatz zur statischen Methode fromName() blockiert diese Methode nicht, sondern ruft den Slot member mit dem Empfänger receiver auf.
Tabelle 6.45
Statische Methoden der Klasse QHostInfo (Forts.)
Jetzt noch zu den verschiedenen Fehlern von QHostInfo, die beim Auflösen des Hostnamens mit der enum-Variablen QHostInfo::HostInfoError und dessen Konstanten beschrieben werden. Konstante
Beschreibung
QHostInfo::NoError
Kein Fehler. Namensauflösung erfolgreich.
QHostInfo::HostNotFound
Keine IP-Adresse für den Host gefunden.
QHostInfo::UnknownError
Ein unbekannter Fehler trat auf.
Tabelle 6.46
Fehler, die beim Auflösen des Hostnamens auftreten können
Am besten lässt sich eine solche Namensauflösung mit der statischen Methode lookupHost() realisieren. Ein Beispiel: // Die IP-Adresse von www.pronix.de ermitteln QHostInfo::lookupHost( "www.pronix.de", this, SLOT( printResults(QHostInfo) ) ); // Hostnamen für 194.150.178.34 ermitteln (reverse lookup) QHostInfo::lookupHost( "194.150.178.34", this, SLOT( printResults(QHostInfo) ) );
Die Implementation des Slots printResults(), der die Namensauflösung oder den Fehler (wenn einer auftritt) zurückgibt, könnte wie folgt aussehen:
524
1542.book Seite 525 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
void MyWidget::printResults( const QHostInfo &host ) { // Ist ein Fehler bei der Auflösung aufgetreten if (host.error() != QHostInfo::NoError) { qDebug() addMenu(viewMenu); 13
20 21
QAction * act1 = new QAction( tr("Plain-Text-Format"), this ); act1->setShortcut(tr("Ctrl+T")); connect( act1, SIGNAL(triggered()), this, SLOT(PlainText()) ); act1->setCheckable(true); QAction * act2 = new QAction(tr("HTML-Format"), this); act2->setShortcut(tr("Ctrl+H")); connect( act2, SIGNAL(triggered()), this, SLOT(HtmlText()) ); act2->setCheckable(true); act1->setChecked(true);
22 23 24
QActionGroup* alignmentGroup = new QActionGroup(this); alignmentGroup->addAction(act1); alignmentGroup->addAction(act2);
25 26
viewMenu->addAction(act1); viewMenu->addAction(act2);
14 15 16 17 18 19
27 28
http = new QHttp(this); progressDialog = new QProgressDialog;
29
connect( http, this, connect( http, this, connect( http,
30 31
538
SIGNAL(requestFinished(int, bool)), SLOT(httpRequestFinished(int, bool)) ); SIGNAL(dataReadProgress(int, int)), SLOT(updateDataReadProgress(int, int))); SIGNAL(responseHeaderReceived( const QHttpResponseHeader &)),
1542.book Seite 539 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
32 33 34 35 }
this, SLOT(readResponseHeader( const QHttpResponseHeader &))); connect( progressDialog, SIGNAL(canceled()), this, SLOT(cancelDownload())); setCentralWidget(edit); setWindowTitle(tr("HTTP-Client"));
36 void Receiver::downloadFile() { 37 bool ok; 38 urlLineEdit = QInputDialog::getText( this, tr("QInputDialog::getText()"), tr("URL eingeben:"), QLineEdit::Normal, tr(""), &ok); 39 if (urlLineEdit.isEmpty()) 40 return; 41 42 43 44 45 46
47 48 49 50 51 52
53 54 55 56 57 58 59 60 61
QUrl url(urlLineEdit); QFileInfo fileInfo(url.path()); QString fileName = fileInfo.fileName(); // Existiert der Dateinamen bereits? if (QFile::exists(fileName)) { QMessageBox::information( this, tr("HTTP-Client"), tr("Die Datei %1 existiert bereits" "im aktuellen Verzeichnis").arg(fileName)); return; } // neue Datei im aktuellen Verzeichnis anlegen file = new QFile(fileName); if (!file->open(QIODevice::WriteOnly)) { QMessageBox::information( this, tr("HTTP-Client"), tr("Kann %1 nicht speichern:\n%2"). arg(fileName).arg(file->errorString())); delete file; file = 0; return; } // den Server und Port für die HTTP-Anfrage setzen http->setHost(url.host(), 80); // falls eine Authentifikation nötig ist if (!url.userName().isEmpty()) http->setUser(url.userName(), url.password());
539
6.8
1542.book Seite 540 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
62 63 64
httpRequestAborted = false; // get-Anforderung stellen httpGetId = http->get(url.path(), file);
65 66
progressDialog->setWindowTitle(tr("HTTP-Client")); progressDialog->setLabelText( tr("Download: %1.").arg(fileName) );
67 } 68 // Download im Progress-Dialog abgebrochen 69 void Receiver::cancelDownload() { 70 httpRequestAborted = true; 71 http->abort(); 72 } 73 // fertig mit der Anforderung 74 void Receiver::httpRequestFinished( int requestId, bool error) { 75 // Wurde der Download abgebrochen 76 if (httpRequestAborted) { 77 if (file) { 78 file->close(); 79 file->remove(); 80 delete file; 81 file = 0; 82 } 83 progressDialog->hide(); 84 return; 85 } 86 if (requestId != httpGetId) 87 return; 88 progressDialog->hide(); 89 file->close(); 90 // Ist ein Fehler aufgetreten, wenn ja, welcher? 91 if (error) { 92 file->remove(); 93 QMessageBox::information( this, tr("HTTP-Client"), tr("Fehler beim Download: %1.") .arg(http->errorString())); 94 } 95 // kein Fehler, dann den Text im Editor anzeigen 96 else { 97 QString fileName = QFileInfo( QUrl(urlLineEdit).path()).fileName();
540
1542.book Seite 541 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
98 QFile file2(fileName); 99 file2.open(QIODevice::ReadOnly); 100 QTextStream ds(&file2); 101 QString set; 102 set = ds.readAll(); 103 file2.close(); 105 edit->setPlainText(set); 106 } 107 delete file; 108 file = 0; 109 } 110 // Antwort vom Server überprüfen 111 void Receiver::readResponseHeader( const QHttpResponseHeader &responseHeader) { 112 // 200 ist alles OK 114 if (responseHeader.statusCode() != 200) { 115 QMessageBox::information( this, tr("HTTP-Client"), tr("Fehler beim Download: %1 (Status Code: %2)") .arg(responseHeader.reasonPhrase()) .arg(responseHeader.statusCode()) ); 116 httpRequestAborted = true; 117 progressDialog->hide(); 118 http->abort(); 119 return; 120 } 121 } 122 // Zustand von Progress-Leiste erneuern 123 void Receiver::updateDataReadProgress( int bytesRead, int totalBytes) { 124 if (httpRequestAborted) 125 return; 126 progressDialog->setMaximum(totalBytes); 127 progressDialog->setValue(bytesRead); 128 } 129 void Receiver::PlainText() { 130 QString str = edit->toHtml(); 131 edit->clear(); 132 edit->setPlainText(str); 133 } 134 void Receiver::HtmlText() {
541
6.8
1542.book Seite 542 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
135 QString str = edit->toPlainText(); 136 edit->clear(); 137 edit->setHtml(str); 138 }
Jetzt noch das Hauptprogramm: 00 // beispiele/qhttp/main.cpp 01 #include 02 #include "receiver.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 Receiver receiver; 06 receiver.show(); 07 return app.exec(); 08 }
{
Rufen Sie im Menü des Programms bei der Ausführung zunächst den Menüpunkt »HTTP/Lade HTML-Seite« auf und geben Sie in dem darauf folgendem Eingabedialog die URL der Datei ein, die Sie herunterladen wollen. Bspw. die URL zur Dokumentation von Trolltech der Klasse QHttp:
Abbildung 6.20
Die URL zur Datei, zum Download angegeben
Hier fordern Sie ein Dokument qt4-6-intro.html vom Host qt.nokia.com mit dem Pfad 4.6-snapshot. Wenn ein Fehler auftritt, bekommen Sie eine Nachrichten-Box mit einer entsprechenden Fehlermeldung zurück. Ansonsten wird die Datei heruntergeladen; sie befindet sich im aktuellen Arbeitsverzeichnis der Anwendung und wird im Editor im Plain-Text-Format angezeigt. Jetzt können Sie im Menü »Ansicht« den Text im HTML-Format ausgeben lassen (da dies ja von der Klasse QTextEdit angeboten wird) und erhalten Abbildung 6.22.
542
1542.book Seite 543 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Abbildung 6.21
Die heruntergeladene Datei im rohen Format
Abbildung 6.22
Umschalten in das HTML-Format
Hinweis Für das Testen solcher Anwendungen empfiehlt es sich im Grunde immer, sich einen eigenen Webserver auf dem System zu installieren. Dies ist häufig einfacher, als man denkt, und mittlerweile werden fertig Lösungen wie bspw. WAMP (für Windows mit Apache, MySQL und PHP) bzw. LAMP (für Linux mit Apache, MySQL und PHP) angeboten. Für Windows finden Sie hierzu WAMP auf der Buch-DVD wieder. Bei Linux sind die einzelnen Pakete gewöhnlich in der Distribution enthalten.
543
6.8
1542.book Seite 544 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
6.8.3
Das FTP-Protokol – QFtp
Mit der Klasse QFtp steht Ihnen die komplette Client-Seite des FTP-Protokolls zur Verfügung. Dabei sind die gängigsten Kommandos implementiert. Sollte dies nicht ausreichen, ist es theoretisch auch möglich, nicht implementierte Kommandos auf niedriger Ebene auszuführen. So wie QHttp arbeitet auch QFtp asynchron. Werden also Methoden wie get() oder put() aufgerufen, kehrt die Steuerung des Programms sofort wieder zurück. Die Kommandos bzw. die Datenübertragung werden auch hier ausgeführt, wenn die Programmsteuerung zur Ereignisschleife zurückkehrt. Sie werden schnell feststellen, dass sich die beiden Klassen QFtp und QHttp recht ähnlich sind. Wie gewohnt wollen wir mit den öffentlichen Methoden der Klasse QFtp beginnen. Methoden
Beschreibung
QFtp ( QObject * parent = 0 );
Erzeugt ein neues QFtp-Objekt mit parent als Eltern-Objekt.
virtual ~QFtp ();
Destruktor. Zerstört ein QFtp-Objekt.
qint64 bytesAvailable () const;
Gibt die Anzahl Bytes zurück, die im Augenblick vom Daten-Socket gelesen werden können.
int cd ( const QString & dir );
Wechselt das Arbeitsverzeichnis auf dem Server nach dir (cd=change directory). Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird dies bemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
void clearPendingCommands ();
Löscht alle noch nicht ausgeführten Kommandos in der Warteschlange. Dies hat allerdings keinen Effekt für das aktuelle Kommando, welches ausgeführt wird. Wenn Sie das aktuelle Kommando abbrechen wollen, müssen Sie abort() verwenden.
int close ();
Schließt die Verbindung zum FTP-Server. Dabei wird das Signal stateChanged() ausgelöst, weil sich der Zustand der Verbindung (Unconnected) verändert hat. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt.
Tabelle 6.55
544
Methoden der Klasse QFtp
1542.book Seite 545 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methoden
Beschreibung
(Forts.)
Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und wenn ausgeführt, das Signal commandFinished() ausgelöst.
int connectToHost ( const QString & host, quint16 port = 21 ) ;
Baut eine Verbindung zum FTP-Server host mit der Portnummer port auf. Standardmäßig wird die Portnummer 21 für das FTP-Protokoll verwendet. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
Command currentCommand () const; Gibt den Typ des Kommandos zurück, der gerade ausgeführt wird, oder None, wenn kein Kommando ausgeführt wird. Mögliche Werte für Command und
deren Bedeutung siehe Tabelle 6.56. QIODevice * currentDevice () const ;
Gibt einen Zeiger auf das QIODevice-Gerät zurück, welches von FTP verwendet wird, um Daten vom FTP-Server zu lesen bzw. wo die Daten davon gespeichert werden. Sofern noch kein FTP-Kommando ausgeführt wurde, existiert noch kein Ein-/ Ausgabe-Gerät, und diese Methode gibt 0 zurück.
int currentId () const;
Gibt den Identifizierer des FTP-Kommandos zurück, welches im Augenblick ausgeführt wird, oder 0, wenn gerade kein Kommando ausgeführt wird.
Error error () const;
Gibt den Fehler zurück, der zuletzt aufgetreten ist. Dies wird gewöhnlich verwendet, wenn bei commandFinished() oder dem done()-Signal das error-Argument true ist. Wenn Sie ein neues Kommando starten sollten, wird der Status von Error wieder auf NoError zurückgesetzt. Mögliche Werte und deren Bedeutung siehe Tabelle 6.57.
QString errorString () const;
Gibt eine lesbare Version des zuletzt aufgetretenen Fehlers zurück. Dies wird gewöhnlich verwendet, wenn bei commandFinished() oder dem done()Signal das error-Argument true ist.
Tabelle 6.55
Methoden der Klasse QFtp (Forts.)
545
6.8
1542.book Seite 546 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methoden
Beschreibung
(Forts.)
Wenn Sie ein neues Kommando starten sollten, wird der Status von Error wieder auf NoError zurückgesetzt. Beachten Sie, dass der Fehler-String meistens vom Server zurückgegeben wird, so dass es nicht immer möglich ist, den String zu übersetzen. Daher sollten Sie den String immer zwischen tr() selbst übersetzen.
int get ( const QString & file, QIODevice * dev = 0, TransferType type = Binary );
Ladet die Datei file vom Server herunter. Wenn dev gleich 0 ist, wird das Signal readyRead() ausgelöst, wenn Daten zum Lesen vorhanden sind. Dann können Sie die Daten mittels read() oder readAll() einlesen. Wenn dev nicht 0 ist, werden die Daten direkt in das Gerät dev geschrieben, und das Signal readyRead() wird nicht ausgelöst. Die Daten können entweder binär (type=Binary) oder im ASCII-Format (type=Ascii) übertragen werden. Standardmäßig ist Binary gesetzt. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und wenn ausgeführt, das Signal commandFinished() ausgelöst.
bool hasPendingCommands () const;
Gibt true zurück, wenn sich noch Kommandos in der Warteschlange befinden, die noch nicht ausgeführt wurden. Das aktuell ausgeführte Kommando wird nicht mehr berücksichtigt. Ansonsten gibt diese Methode false zurück.
int list ( const QString & dir = QString() );
Listet den Inhalt des Verzeichnisses dir auf dem FTP-Server auf. Ist dir leer, wird der Inhalt des aktuellen Verzeichnisses aufgelistet. Dabei wird das Signal listInfo() ausgelöst für jedes Verzeichnis, das gefunden wurde. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
Tabelle 6.55
546
Methoden der Klasse QFtp (Forts.)
1542.book Seite 547 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methoden
Beschreibung
int login ( const QString & user = QString(), const QString & password = QString() );
Loggt sich in dem FTP-Server mit dem Usernamen user und dem Passwort password ein. Hierbei wird das Signal stateChanged() ausgelöst, wenn die Verbindung gerade aufgebaut wird, und der Zustand auf LoggedIn gesetzt. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
int mkdir ( const QString & dir );
Erzeugt das Verzeichnis dir auf dem FTP-Server. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
int put ( QIODevice * dev, const QString & file, TransferType type = Binary );
Liest die Daten vom Ein-/Ausgabe-Gerät dev ein und schreibt diese in die Datei file auf dem Server. Die Daten werden in mehreren Happen aus dem Ein-/Ausgabe-Gerät eingelesen. Die Übertragung der Daten erfolgt auch hier standardmäßig binär (type=Binary), kann aber auch im ASCII-Format (type=Ascii) erfolgen.
int put ( const QByteArray & data, const QString & file, TransferType type = Binary );
Eine überladene Version, wo die Daten aus einem QByteArray, statt einem QIODevice-Gerät, eingelesen werden.
int rawCommand ( const QString & command );
Sendet ein rohes FTP-Kommando command an den FTP-Server. Dies kann sinnvoll für einen Zugriff auf niedrigerer FTP-Ebene sein. Es wird allerdings empfohlen, die Methoden von QFtp zu verwenden, da diese zum einen einfacher und vor allem sicherer sind. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt.
Tabelle 6.55
Methoden der Klasse QFtp (Forts.)
547
6.8
1542.book Seite 548 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methoden
Beschreibung
(Forts.)
Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
qint64 read ( char * data, qint64 maxlen );
Liest maxlen Bytes vom Daten-Socket nach data ein und gibt die Anzahl erfolgreich eingelesener Bytes zurück. Bei einem Fehler wird –1 zurückgegeben.
QByteArray readAll ();
Liest alle vorhandenen Bytes vom Daten-Socket ein und gibt dies als QbyteArray zurück.
int remove ( const QString & file );
Löscht die Datei file vom FTP-Server. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
int rename ( const QString & oldname, const QString & newname );
Benennt auf dem Server die Datei oldname um in die Datei newname. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird dies gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
int rmdir ( const QString & dir )
Löscht das Verzeichnis dir auf dem Server. Die Methode blockiert nicht und kehrt unverzüglich zurück. Wenn die Ausführung nicht gleich stattfinden kann, wird diese gemerkt und asynchron ausgeführt. Die Methode gibt eine eindeutige Identifizierung zurück, die von commandStarted() und commandFinished() angepasst wurde. Wenn das Kommando gestartet wurde, wird das Signal commandStarted() und, wenn ausgeführt, das Signal commandFinished() ausgelöst.
Tabelle 6.55
548
Methoden der Klasse QFtp (Forts.)
1542.book Seite 549 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Methoden
Beschreibung
int setProxy ( const QString & host, quint16 port ) ;
Verwendet den Proxy host mit der Portnummer port für die FTP-Verbindung. Ausschalten können Sie diese wieder, indem Sie diese Methode mit einem leeren Host aufrufen. QFtp unterstützt keinen FTP-über-HTTP-Proxy-Server.
int setTransferMode ( TransferMode mode );
Setzt den aktuellen Übertragungsmodus für die FTPVerbindung. Standardmäßig ist die QFtp::Passive. Damit verbindet sich der Client mit dem Server, um Daten zu übertragen. Ein zweiter Übertragungsmodus wäre QFtp::Active, womit sich der Server mit dem Client verbindet, um Daten zu übertragen.
State state () const ;
Gibt den aktuellen Zustand des QFtp-Objekts zurück. Wenn sich der Zustand verändert, wird das Signal stateChanged() ausgelöst. Mögliche Werte und deren Bedeutung siehe Tabelle 6.58.
Tabelle 6.55
Methoden der Klasse QFtp (Forts.)
Welches Kommando gerade ausgeführt wird, lässt sich mit der Methode currentCommand() ermitteln. Zurückgegeben wird dabei eine Konstante aus der enum-Variablen QFtp::Command. Konstante
Beschreibung
QFtp::None
Im Augenblick wird kein Kommando ausgeführt.
QFtp::SetTransferMode
Der Übertragunsmodus wird gerade gesetzt.
QFtp::SetProxy
Der Proxy wird gerade ein- bzw. ausgeschaltet.
QFtp::ConnectToHost
connectToHost() wird gerade ausgeführt.
QFtp::Login
login() wird gerade ausgeführt.
QFtp::Close
close() wird gerade ausgeführt.
QFtp::List
list() wird gerade ausgeführt.
QFtp::Cd
cd() wird gerade ausgeführt.
QFtp::Get
get() wird gerade ausgeführt.
QFtp::Put
put() wird gerade ausgeführt.
QFtp::Remove
remove() wird gerade ausgeführt.
QFtp::Mkdir
mkdir() wird gerade ausgeführt.
QFtp::Rmdir
Tabelle 6.56
rmdir() wird gerade ausgeführt.
Konstanten zum Ermitteln eines Kommandos
549
6.8
1542.book Seite 550 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Konstante
Beschreibung
QFtp::Rename
rename() wird gerade ausgeführt.
QFtp::RawCommand
rawCommand() wird gerade ausgeführt.
Tabelle 6.56
Konstanten zum Ermitteln eines Kommandos (Forts.)
Der zuletzt aufgetretene Fehler wird mit der enum-Variablen QFtp::Error beschrieben. Folgende Konstanten und deren Bedeutung sind darin definiert: Konstante
Beschreibung
QFtp::NoError
Kein Fehler ist aufgetreten.
QFtp::HostNotFound
Die Namensauflösung ist fehlgeschlagen.
QFtp::ConnectionRefused
Der Server hat die Verbindung abgewiesen.
QFtp::NotConnected
Es wurde versucht, ein Kommando zu senden, aber es bestand keine Verbindung zum Server.
QFtp::UnknownError
Ein anderer unbekannter Fehler ist aufgetreten.
Tabelle 6.57
Konstanten für den zuletzt aufgetretenen Fehler
Jetzt fehlen uns nur noch die Konstanten der enum-Variablen QFtp::State, die uns den Zustand einer Verbindung beschreiben. Konstante
Beschreibung
QFtp::Unconnected
Es besteht keine Verbindung zum Host.
QFtp::HostLookup
Die Namensauflösung ist gerade in Arbeit.
QFtp::Connecting
Die Verbindung mit dem Host wird gerade aufgebaut.
QFtp::Connected
Die Verbindung mit dem Host ist ausgeführt.
QFtp::LoggedIn
Die Verbindung und das User-Login sind ausgeführt.
QFtp::Closing
Die Verbindung wird geschlossen, ist aber im Augenblick noch offen.
Tabelle 6.58
550
Konstanten, die den Zustand einer Verbindung beschreiben
1542.book Seite 551 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
Slot hat die Klasse QFtp, wie schon die Klasse QHttp, nur einen: Slot
Beschreibung
void abort ();
Bricht das aktuelle Kommando bei der Ausführung ab und löscht alle noch wartenden Kammandos aus der Warteschlange. Mit diesem Slot wird ein ABORT-Kommando an den Server gesendet.
Tabelle 6.59
Slot der Klase QFtp
Signale sind auch hier einige mehr definiert und denen von QHttp nicht unähnlich: Signal
Beschreibung
void commandFinished ( int id, bool error );
Das Signal wird ausgelöst, wenn das auszuführende Kommando mit der ID id fertig ist. Ist error gleich true, ist ein Fehler aufgetreten. Ansonsten wenn error gleich false ist, wurde das Kommando ohne Fehler beendet.
void commandStarted ( int id ); Das Signal wird ausgelöst, wenn das Kommando mit der ID id ausgeführt wird. void dataTransferProgress ( qint64 done, qint64 total );
Das Signal wird ausgelöst als Antwort auf ein get() oder eine put()-Anfrage um anzuzeigen, wie viele Daten bereits herunter- bzw. hochgeladen wurden. done enthält die Anzahl der Daten, die bereits übertragen wurden, und total enthält die gesamte Anzahl der Daten, die geschrieben bzw. gelesen werden. done und total sind nicht unbedingt als Bytes angegeben. Bei größeren Dateien werden die beiden Werte skaliert, um einen Überlauf zu vermeiden.
void done ( bool error );
Das Signal wird ausgelöst, wenn alle Kommandos (auch die wartenden) ausgeführt wurden (also nach dem letzten commandFinished()-Signal). Ist der Wert von error gleich true, ist ein Fehler während der Arbeit aufgetreten. Ansonsten ist error gleich false.
void listInfo ( const QUrlInfo & i );
Das Signal wird ausgelöst für jedes Verzeichnis, wo das Kommando list() einen Eintrag gefunden hat. Die Details der Einträge sind in i gespeichert.
void rawCommandReply ( int replyCode, const QString & detail );
Das Signal wird ausgelöst als Antwort einer rawCommand()-Methode. In replyCode befindet sich der 3-stellige Zahlencode und in detail der Text zum entsprechenden Code.
Tabelle 6.60
Signale der Klasse QFtp
551
6.8
1542.book Seite 552 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Signal
Beschreibung
void readyRead ();
Das Signal wird als Antwort auf ein get()-Kommando gesendet, wenn neue Daten zum Lesen vorhanden sind. Wenn Sie ein Gerät in get() gesetzt haben, wird dieses Signal nicht ausgelöst, und die Daten werden direkt in dieses Gerät geschrieben. Die Daten können Sie mit den Methoden read() oder readAll() einlesen.
void stateChanged ( int state ); Das Signal wird ausgelöst, wenn sich der Zustand der FTP-Verbindung geändert hat. Im Argument state
befindet sich der neue Zustand (siehe dazu auch Tabelle 6.58). Tabelle 6.60
Signale der Klasse QFtp (Forts.)
Jetzt soll hierzu ein einfacher FTP-Client erstellt werden, womit Sie sich auf einem beliebigen FTP-Server ggf. mit Username und Passwort einloggen können. Wegen des Programmumfangs sind die Aktionen zum Herunterladen von Dateien von einem FTP-Server beschränkt. Trotzdem wurde ein Splitter verwendet, der Ihnen auch den Inhalt des aktuellen Arbeitsverzeichnisses auflistet und zeigt, welche Dateien sich darin befinden. Das lokale Verzeichnis wird auch aktualisiert, wenn neue Daten eingegangen sind. Im Grunde ist das Beispiel eine Erweiterung des Qt-Beispiels, das der Distribution beigelegt ist. Hier zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
// beispiele/qftp/ftp.h #ifndef FTPWINDOW_H #define FTPWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include
17 class FtpWindow : public QDialog { 18 Q_OBJECT
552
1542.book Seite 553 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
19 public: 20 FtpWindow(QWidget *parent = 0); 21 private slots: 22 void connectOrDisconnect(); 23 void downloadFile(); 24 void cancelDownload(); 25 void ftpCommandFinished(int commandId, bool error); 26 void addToList(const QUrlInfo &urlInfo); 27 void processItem(QListWidgetItem *item); 28 void cdToParent(); 29 void updateDataTransferProgress( qint64 readBytes, qint64 totalBytes); 30 void enableDownloadButton(); 31 private: 32 QLabel *ftpServerLabel; 33 QLineEdit *ftpServerLineEdit; 34 QLabel *ftpUser; 35 QLineEdit *ftpUserLineEdit; 36 QLabel *ftpPassword; 37 QLineEdit *ftpPasswordLineEdit; 38 QLabel *statusLabel; 39 QListWidget *fileList; 40 QListWidget *fileHomeList; 41 QPushButton *cdToParentButton; 42 QPushButton *connectButton; 43 QPushButton *downloadButton; 44 QPushButton *quitButton; 45 QDialogButtonBox *buttonBox; 46 QProgressDialog *progressDialog; 47 QDirModel *model; 48 QTreeView *tree; 49 QHash isDirectory; 50 QString currentPath; 51 QFtp *ftp; 52 QFile *file; 53 }; 54 #endif
Jetzt die (umfangreiche) Implementierung des FTP-Clients: 00 01 02 03
// beispiele/qftp/ftp.cpp #include #include #include "ftp.h"
553
6.8
1542.book Seite 554 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
04 FtpWindow::FtpWindow(QWidget *parent) : QDialog(parent), ftp(0) { 05 ftpServerLabel = new QLabel(tr("Ftp &server:")); 06 ftpServerLineEdit= new QLineEdit("ftp.trolltech.com"); 07 ftpServerLabel->setBuddy(ftpServerLineEdit); 08 ftpUser = new QLabel(tr("&User:")); 09 ftpUserLineEdit = new QLineEdit(""); 10 ftpUser->setBuddy(ftpUserLineEdit); 11 ftpPassword = new QLabel(tr("&Passwort:")); 12 ftpPasswordLineEdit = new QLineEdit(""); 13 ftpPasswordLineEdit->setEchoMode(QLineEdit::Password); 14 ftpPassword->setBuddy(ftpPasswordLineEdit); 15 statusLabel = new QLabel(tr( "Bitte geben Sie den Namen des FTP-Server ein.")); 16 17
fileList = new QListWidget; fileList->setEnabled(false);
18 19 20 21
model = new QDirModel; tree = new QTreeView(this); tree->setModel(model); tree->setRootIndex(model->index(QDir::currentPath()));
22 23 24
QSplitter *splitter = new QSplitter(parent); splitter->addWidget(fileList); splitter->addWidget(tree);
25 26 27 28
connectButton = new QPushButton(tr("Verbinden")); connectButton->setDefault(true); cdToParentButton = new QPushButton; cdToParentButton->setIcon( QPixmap(":/images/arrow_up.png")); cdToParentButton->setEnabled(false); downloadButton = new QPushButton(tr("Download")); downloadButton->setEnabled(false); quitButton = new QPushButton(tr("Beenden")); buttonBox = new QDialogButtonBox; buttonBox->addButton( downloadButton, QDialogButtonBox::ActionRole); buttonBox->addButton( quitButton, QDialogButtonBox::RejectRole);
29 30 31 32 33 34 35
36
554
progressDialog = new QProgressDialog(this);
1542.book Seite 555 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
37
38
39 40 41 42 43
connect( fileList, SIGNAL(itemActivated(QListWidgetItem *)), this, SLOT(processItem(QListWidgetItem *))); connect( fileList, SIGNAL(currentItemChanged( QListWidgetItem *, QListWidgetItem *)), this, SLOT(enableDownloadButton())); connect( progressDialog, SIGNAL(canceled()), this, SLOT(cancelDownload())); connect( connectButton, SIGNAL(clicked()), this, SLOT(connectOrDisconnect())); connect( cdToParentButton, SIGNAL(clicked()), this, SLOT(cdToParent())); connect( downloadButton, SIGNAL(clicked()), this, SLOT(downloadFile())); connect( quitButton, SIGNAL(clicked()), this, SLOT(close()));
44 45 46 47 48 49 50 51 52
QHBoxLayout *topLayout = new QHBoxLayout; topLayout->addWidget(ftpServerLabel); topLayout->addWidget(ftpServerLineEdit); topLayout->addWidget(ftpUser); topLayout->addWidget(ftpUserLineEdit); topLayout->addWidget(ftpPassword); topLayout->addWidget(ftpPasswordLineEdit); topLayout->addWidget(cdToParentButton); topLayout->addWidget(connectButton);
53 54 55 56 57 58 59 60 }
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topLayout); mainLayout->addWidget(/*fileList*/splitter); mainLayout->addWidget(statusLabel); mainLayout->addWidget(buttonBox); setLayout(mainLayout); setWindowTitle(tr("FTP-Client"));
61 void FtpWindow::connectOrDisconnect() { 62 if (ftp) { 63 ftp->abort(); 64 ftp->deleteLater(); 65 ftp = 0; 66 fileList->setEnabled(false); 67 cdToParentButton->setEnabled(false); 68 downloadButton->setEnabled(false);
555
6.8
1542.book Seite 556 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
69 70 71 72 73 74 75 76 77 78
79 80 81 82 83 84 85 86 87 88 89 90 91
connectButton->setEnabled(true); connectButton->setText(tr("Verbinden")); setCursor(Qt::ArrowCursor); return; } setCursor(Qt::WaitCursor); ftp = new QFtp(this); connect( ftp, SIGNAL(commandFinished(int, bool)), this, SLOT(ftpCommandFinished(int, bool))); connect( ftp, SIGNAL(listInfo(const QUrlInfo &)), this, SLOT(addToList(const QUrlInfo &))); connect( ftp, SIGNAL(dataTransferProgress( qint64, qint64) ), this, SLOT(updateDataTransferProgress( qint64, qint64) ) ); fileList->clear(); currentPath.clear(); isDirectory.clear(); ftp->connectToHost(ftpServerLineEdit->text()); if( (ftpUserLineEdit->text().isEmpty()) && (ftpPasswordLineEdit->text().isEmpty())) ftp->login(); else ftp->login( ftpUserLineEdit->text(), ftpPasswordLineEdit->text() ); ftp->list(); fileList->setEnabled(true); connectButton->setEnabled(false); connectButton->setText(tr("Trennen")); statusLabel->setText( tr("Verbinde mit FTP-Server %1...") .arg(ftpServerLineEdit->text()));
92 } 93 void FtpWindow::downloadFile() { 94 QString fileName = fileList->currentItem()->text(); 94 if (QFile::exists(fileName)) { 95 QMessageBox::information( this, tr("FTP"), tr("Es existiert bereits ein Datei %1 im " "aktuellen Verzeichnis").arg(fileName)); 96 return; 97 } 98 file = new QFile(fileName);
556
1542.book Seite 557 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
99 100 101 102 103 104 105 106 107 108 109 110 111 }
if (!file->open(QIODevice::WriteOnly)) { QMessageBox::information( this, tr("FTP"), tr("Unable to save the file %1: %2.") .arg(fileName).arg(file->errorString())); delete file; return; } ftp->get(fileList->currentItem()->text(), file); progressDialog->setLabelText( tr("Downloading %1...").arg(fileName)); downloadButton->setEnabled(false); progressDialog->exec();
112 void FtpWindow::cancelDownload() { 113 ftp->abort(); 114 } 115 void FtpWindow::ftpCommandFinished(int, bool error) { 116 setCursor(Qt::ArrowCursor); 117 model->refresh(); 118 if (ftp->currentCommand() == QFtp::ConnectToHost) { 119 if (error) { 120 QMessageBox::information( this, tr("FTP"), tr("Konnte kein Verbindung zum Server " "%1 herstellen. Bitte überprüfen Sie" "den Servername und ob Sie ggf. den " "Usernamen und ein Passwort benötigen.") .arg(ftpServerLineEdit->text())); 121 connectOrDisconnect(); 122 return; 123 } 124 statusLabel->setText(tr("Eingeloggt auf %1.") .arg(ftpServerLineEdit->text())); 125 fileList->setFocus(); 126 downloadButton->setDefault(true); 127 connectButton->setEnabled(true); 128 return; 129 } 130 131
if (ftp->currentCommand() == QFtp::Get) { if (error) {
557
6.8
1542.book Seite 558 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
132
133 134 135 136 137
statusLabel->setText( tr("Canceled download of %1.") .arg(file->fileName())); file->close(); file->remove(); } else { statusLabel->setText( tr("Downloaded %1 to current directory.") .arg(file->fileName())); file->close(); } delete file; enableDownloadButton();
138 139 140 141 142 } 143 else if (ftp->currentCommand() == QFtp::List) { 144 if (isDirectory.isEmpty()) { 145 fileList->addItem(tr("<empty>")); 146 fileList->setEnabled(false); 147 } 148 } 149 }
150 void FtpWindow::addToList(const QUrlInfo &urlInfo) { 151 QListWidgetItem *item = new QListWidgetItem; 152 item->setText(urlInfo.name()); 153 QPixmap pixmap(urlInfo.isDir() ? "images/folder.png" : "images/file.png"); 154 item->setIcon(pixmap); 155 isDirectory[urlInfo.name()] = urlInfo.isDir(); 156 fileList->addItem(item); 157 if (!fileList->currentItem()) { 158 fileList->setCurrentItem(fileList->item(0)); 159 fileList->setEnabled(true); 160 } 161 } 162 void FtpWindow::processItem(QListWidgetItem *item) { 163 QString name = item->text(); 164 if (isDirectory.value(name)) { 165 fileList->clear(); 166 isDirectory.clear(); 167 currentPath += "/" + name; 168 ftp->cd(name); 169 ftp->list();
558
1542.book Seite 559 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
170 171 172 173 174 }
cdToParentButton->setEnabled(true); setCursor(Qt::WaitCursor); return; }
175 void FtpWindow::cdToParent() { 176 setCursor(Qt::WaitCursor); 177 fileList->clear(); 178 isDirectory.clear(); 179 currentPath = currentPath.left( currentPath.lastIndexOf('/') ); 180 if (currentPath.isEmpty()) { 181 cdToParentButton->setEnabled(false); 182 ftp->cd("/"); 183 } 184 else { 185 ftp->cd(currentPath); 186 } 187 ftp->list(); 188 } 189 void FtpWindow::updateDataTransferProgress( qint64 readBytes, qint64 totalBytes) { 190 progressDialog->setMaximum(totalBytes); 191 progressDialog->setValue(readBytes); 192 } 193 void FtpWindow::enableDownloadButton() { 194 QListWidgetItem *current = fileList->currentItem(); 195 if (current) { 196 QString currentFile = current->text(); 197 downloadButton->setEnabled( !isDirectory.value(currentFile) ); 198 } 199 else { 200 downloadButton->setEnabled(false); 201 } 202 }
Jetzt fehlt nur noch das Hauptprogramm: 00 // beispiele/qftp/main.cpp 01 #include 02 #include "ftp.h"
559
6.8
1542.book Seite 560 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 FtpWindow ftpWin; 06 ftpWin.show(); 07 return ftpWin.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 6.23
QFtp bei der Ausführung
In der Abbildung habe ich mich auf dem FTP-Server von Trolltech eingeloggt, um mir den neuesten Snapshot von Qt herunterzuladen. Natürlich funktioniert dies auch mit der Authentifikation auf einem anderem FTP-Server. In der folgenden Abbildung habe ich mich beim Hoster meiner Webseite eingeloggt:
Abbildung 6.24
560
QFtp mit User- und Passwort-Eingabe
1542.book Seite 561 Montag, 4. Januar 2010 1:02 13
Netzwerkkommunikation (Sockets)
6.8.4
Ein Proxy – QNetworkProxy
Die Klasse QNetworkProxy wird für einen Proxy bzw. Proxyserver verwendet. Im Augenblick unterstützen folgende Qt-Klassen diese Proxyfunktion: QAbstractSocket, QTcpSocket, QUdpSocket, QTcpServer, QHttp und QFtp. Proxy Ein Proxy ist ein Dienstprogramm für Computernetze, das im Datenverkehr vermittelt. Es macht den Datentransfer effizienter (weniger Netzbelastung durch große Datenmengen) bzw. schneller, kann aber auch durch Einsatz von Zugriffskontrollmechanismen die Sicherheit erhöhen. Die Vermittlung erfolgt zwischen Computern oder Programmen in so genannten Rechnernetzen. Aus Sicht des Servers verhält sich der Proxy wie ein Client, dem Client gegenüber wie ein Server. Wenn von einem Proxy die Rede ist, ist gewöhnlich ein HTTP-Proxy gemeint, der zwischen einem Webbrowser und einem Webserver vermittelt. Damit lassen sich folgende Funktionen realisieren: Zwischenspeicher, Filter, Zugriffssteuerung, Vorverarbeiten von Daten, Anonymisierung, Bandbreitenregelung, Spam-Filter usw.
Um einen (SOCKS5-)Proxy für eine Qt-Anwendung zu erstellen, reicht folgender Code aus: QNetworkProxy proxy; proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setHostName("proxy.example.com"); proxy.setPort(1080); proxy.setUser("username"); proxy.setPassword("password"); QNetworkProxy::setApplicationProxy(proxy);
Damit haben Sie einen anwendungsweiten Proxy erstellt. Eine zweite Möglichkeit ist es natürlich, für die einzelnen Sockets einen Proxy mit den entsprechenden Methoden wie QAbstractSocket::setProxy() oder QTcpServer::setProxy() zu setzen (bzw. wieder abzuschalten). Der Netzwerk-Proxy wird nicht verwendet, wenn die Adresse in connectToHost(), bind() oder listen() verwendet wird. Socket-Version von Qt Qt4.x unterstützt das SOCKS-Protokoll der Version 5 (SOCKS5), das ein InternetProxy-Protokoll ist, das Client-Server-Anwendungen erlaubt, transparent die Dienste einer Firewall zu nutzen. Mehr dazu entnehmen Sie bitte den RFC 1928 und RFC 1929.
561
6.8
1542.book Seite 562 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
6.9
Multithreads – QThread
Die Klasse QThread kann für die Verwendung von plattformunabhängigen Threads verwendet werden. Bisher konnten Sie bei den Anwendungen immer nur jeweils eine Operation ausführen. Führen Sie bspw. gerade eine umfangreiche Berechnung aus, kann es häufig passieren, dass Ihre GUI so lange einfriert, bis diese Berechung beendet ist. Dies ist ärgerlich für den Anwender, wenn dieser doch nur eine Berechnung durchführt und unterdessen eigentlich auch etwas anderes tun könnte. Um dies zu vermeiden, haben Sie die Möglichkeit, entweder einen neuen Prozess zu starten (Forking und Interprozesskommunikation), eine eigene Ereignisverarbeitung zu schreiben, oder aber – darum geht es diesem Abschnitt – Multithreads mit der Klasse QThread zu verwenden. Wenn Sie einen Rechner mit mehreren Prozessoren haben, wird es mit den Threads erst möglich, dass Sie mehrere Operationen gleichzeitig ausführen können (immer von der Anzahl der Prozessoren abhängig) – also eine echte parallele Ausführung. Im Gegensatz zu den Prozessen haben Sie bei den Threads außerdem den Vorteil, dass alle Threads den Heap, die Daten und den Code-Teil miteinander verwenden. Die einzelnen Threads selbst verfügen nur über einen eigenen Stack und Prozessorregister. Somit ist der Verwaltungsaufwand für Threads auch erheblich geringer als für Prozesse. Man spricht daher bei den Threads auch von Leichtgewichtsprozessen. POSIX-Threads Noch mehr Informationen zu den Threads finden Sie auf der Buch-DVD wieder. Dabei wird ein Kapitel aus meinem Buch »Linux-UNIX-Programmierung« zu den POSIX-Threads verwendet. Allerdings sind die POSIX-Threads nicht auf Linux-/Unix beschränkt und lassen sich selbstverständlich auch unter MS-Windows oder Mac OS X verwenden. Als Einführung für den, der noch nie etwas mit Thread-Programmierung zu tun hatte, ist dieses Kapitel genau das Richtige.
Der mit der main()-Funktion gestartete Thread ist immer der Haupt-Thread in einer Anwendung. Alle weiteren Threads werden bei QThread mit run() gestartet. Dazu erzeugen Sie eine Unterklasse von QThread und reimplementieren dann die rein virtuelle Methode run(). Hierzu zunächst die Beschreibung zu den Methoden, Signalen und Slots der Klasse QThread.
562
1542.book Seite 563 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Methode
Beschreibung
QThread ( QObject * parent = 0 );
Erzeugt einen neuen Thread mit optionalem Eltern-Objekt parent. Der Thread wird allerdings noch nicht gestartet, bevor die Methode start() aufgerufen wird.
~QThread ();
Zerstört einen Thread. Beachten Sie allerdings, dass das Löschen des Objekts nicht automatisch das Anhalten der Ausführung des Threads bedeutet. Es empfiehlt sich, auf den Thread mittels wait() zu warten, sonst könnte das Programm unerwartet abstürzen.
void exit ( int returnCode = 0 );
Teilt der Thread-Ereignisschleife das Ende des Threads mit dem Exit-Code returneCode mit. Nach dem Aufruf dieser Methode verlässt der Thread die Ereignisschleife und kehrt zum Aufruf QEventLoop::exec() zurück. QEventLoop::exec() gibt returnCode als Rückgabewert zurück. Gewöhnlich bedeutet ein Rückgabewert von 0, dass alles in Ordnung war, und ein Wert ungleich 0 deutet auf einen Fehler hin.
bool isFinished () const;
Gibt true zurück, wenn der Thread fertig ist. Ansonsten wird false zurückgegeben.
bool isRunning () const;
Gibt true zurück, wenn der Thread gerade ausgeführt wird. Ansonsten wird false zurückgegeben.
Priority priority () const; Gibt die Priorität für den laufenden Thread zurück. Wird der Thread nicht ausgeführt, wird InheritPriority aus-
geführt. Mögliche Werte und deren Bedeutung siehe Tabelle 6.62. void setPriority ( Priority priority );
Die Methode setzt die Priorität für den laufenden Thread auf priority. Läuft der Thread nicht, hat diese Methode keinen Effekt und kehrt unverzüglich zurück. Mögliche Werte und deren Bedeutung siehe Tabelle 6.62.
void setStackSize ( uint stackSize );
Setzt die max. Größe des Stacks für den Thread auf stackSize. Wenn stackSize gleich 0 ist, wird die Stackgröße automatisch vom Betriebssystem gesetzt. Beachten Sie bitte, dass jedes Betriebssystem eine minimale und maximale Begrenzung für die Stackgröße vorgibt. Sollten Sie einen Wert außerhalb dieses Limits setzen, kann es passieren, dass der Thread nicht startet.
uint stackSize () const;
Gibt die maximale Stackgröße für den Thread zurück (wenn dieser mit setStackSize() gesetzt wurde). Ansonsten wird 0 zurückgegeben.
Tabelle 6.61
Öffentliche Methoden der Klasse QThread
563
6.9
1542.book Seite 564 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
bool wait ( unsigned long time=ULONG_MAX );
Blockiert den Thread, bis
Tabelle 6.61
왘
entweder der Thread, der dem QThread-Objekt angehört, seine Ausführung beendet hat (bspw. von run() zurückkehrt). Die Methode gibt dann true zurück, wenn der Thread fertig ist. Es wird eben true zurückgegeben, wenn der Thread noch gar nicht gestartet wurde;
왘
oder time Millisekunden vorbei sind. Ist time gleich ULONG_MAX (Standard), wird nicht auf ein Timeout gewartet (und der Thread muss wieder von run() zurückkehren. Die Methode gibt false zurück, wenn die Zeit abgelaufen ist.
왘
Die Methode entspricht in etwa der POSIX-Funktion pthread_join().
Öffentliche Methoden der Klasse QThread (Forts.)
Mit den Konstanten der enum-Variable QThread::Priority wird angegeben, mit welcher Priorität das Betriebssystem den neuen Thread behandeln soll, wenn dieser erzeugt wird. Die Werte sind zum Teil auch vom Betriebssystem abhängig, so dass diese hier nur verallgemeinert sind. Konstante
Beschreibung
QThread::IdlePriority
Wird nur ausgeführt, wenn gerade kein anderer Thread ausgeführt wird.
QThread::LowestPriority
niedrigste mögliche Priorität
QThread::LowPriority
niedrige Priorität
QThread::NormalPriority
Normale Priorität. Der Standard bei vielen Betriebssystemen
QThread::HighPriority
hohe Priorität
QThread::HighestPriority
höchste mögliche Priorität
QThread::TimeCriticalPriority
den Thread so oft ausführen wie möglich
QThread::InheritPriority
Verwendet dieselbe Priorität wie der erzeugende Thread (Eltern-Thread). Dies ist der Standardwert von QThread.
Tabelle 6.62
564
Priorität von Threads
1542.book Seite 565 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Jetzt zu den öffentlichen Slots der Klasse QThread: Slot
Beschreibung
void quit ();
Teilt der Ereignisschleife vom Haupt-Thread mit, dass der aktuelle Thread mit dem Exit-Code 0 (Erfolgreich) beenden will. Der Aufruf ist gleichwertig mit QThread::exit(0). Hat der Thread keine Ereignisschleife, so hat der Aufruf keinen Effekt.
void start ( Priority priority = InheritPriority );
Beginnt die Ausführung des Threads, in dem run() aufgerufen wird. Diese protected-Methode run() sollten Sie natürlich in einer QThread-Unterklasse reimplementiert haben. Optional können Sie die Priorität (siehe Tabelle 6.62) des Threads angeben. Standardmäßig wird die Priorität des Eltern-Threads verwendet. Wenn der Thread bereits laufen sollte und Sie die Methode start() verwenden, hat dieser Aufruf keinen Effekt.
void terminate ();
Beendet die Ausführung des Threads. Der Thread wird allerdings nicht sofort beendet. Dies hängt vom Betriebssystem (genauer: von scheduling policies). Sie sollten außerdem QThread::wait() nach einem terminate()-Aufruf für eine synchrone Beendigung des Threads verwenden. In der Praxis sollte dieser Slot nicht verwendet werden, da hiermit ein Thread an jedem Punkt der Ausführung beendet werden könnte, also auch, wenn gerade Daten verändert werden. Dabei können keinerlei Säuberungsaktionen mehr durchgeführt werden, was besonders kritisch beim Freigeben einer MutexSperre sein kann, wenn Sie denn eine verwenden. Dies würde bedeuten, dass alle anderen Threads, die ebenfalls von dieser Mutex-Sperre abhängig sind, unbrauchbar sind, weil die Sperre niemals mehr freigegeben wird.
Tabelle 6.63
Öffentliche Slots der Klasse QThread
Als Nächstes die öffentlichen Signale der Klasse QThread: Signal
Beschreibung
void finished ();
Das Signal wird ausgelöst, wenn der Thread mit seiner Ausführung fertig ist.
void started ();
Das Signal wird ausgelöst, wenn der Thread seine Ausführung startet.
void terminated ();
Dieses Signal wird ausgelöst, wenn der Thread vorzeitig beendet wurde.
Tabelle 6.64
Öffentliche Signale der Klasse QThread
565
6.9
1542.book Seite 566 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Die beiden folgenden statischen Methoden sind außerdem in Qthread definiert: Statische Methode
Beschreibung
QThread * currentThread ();
Gibt einen Zeiger auf ein QThread-Objekt zurück, welches dem aktuell auszuführenden Thread entspricht.
Qt::HANDLE currentThreadId (); Gibt den Handle des aktuell auszuführenden Threads
zurück. Diese Methode ist nicht portabel, weil Windows hierbei einen Pseudo-Handle zurückgibt und dieser nicht für numerische Vergleiche verwendet werden kann wie unter Linux. Tabelle 6.65
Öffentliche statische Methoden der Klasse QThread
Zusätzlich sind zwei protected-Methoden in QThread enthalten, wovon die virtuelle Variante run() die wohl wichtigste überhaupt ist. protected-Methode
Beschreibung
int exec ();
Tritt in die Ereignisschleife und wartet, bis exit() aufgerufen oder das Haupt-Widget zerstört wird; der Rückgabewert wird mit exit() gesetzt. Diese Methode muss aufgerufen werden, wenn Sie die Ereignisbehandlung starten wollen.
virtual void run () = 0;
Dies ist eine reine virtuelle Methode, die in einer von QThread-abgeleiteten Klasse implementiert werden muss, damit ein Thread überhaupt sinnvoll arbeitet. Eine Rückkehr von diesen Methoden bedeutet auch das Ende der Ausführung des Threads.
Tabelle 6.66
protected-Methoden der Klasse QThread
Außerdem sind in der Klasse QThread vier statische protected-Methoden implementiert. Statische protected-Methode
Beschreibung
void msleep ( unsigned long msecs );
Legt den laufenden Thread für msecs Millisekunden schlafen.
void setTerminationEnabled ( bool enabled = true );
Damit lässt sich das Beenden des aktuell laufenden Threads ein- bzw. ausschalten. Wenn Sie hierbei enable auf false setzen, so hat der Aufruf von QThread::terminate() keinen Effekt. Setzt man hingegegen enable auf true, beendet ein Aufruf von QThread::terminate() den Thread wie gewöhnlich.
Tabelle 6.67
566
Statische protected-Methoden der Klasse QThread
1542.book Seite 567 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Statische protected-Methode
Beschreibung
void sleep ( unsigned long secs );
Zwingt den aktuellen Thread, sich für secs Sekunden schlafen zu legen.
void usleep ( unsigned long usecs );
Legt den aktuell laufenden Thread für usecs Mikrosekunden schlafen.
Tabelle 6.67
Statische protected-Methoden der Klasse QThread (Forts.)
Hierzu ein recht einfaches Beispiel. Nehmen wir an, wir haben ein Programm erstellt, womit Sie Sicherheitskopien von mehreren Rechnern anfertigen können. In unserem Beispiel sollen es eben zwei Rechner bzw. zwei Threads sein. Hierzu finden Sie über ein Menü jeweils einen entsprechenden Menüpunkt. Starten Sie nun das erste Backup, erscheint im Editor, dass der Vorgang (hier Thread) gestartet wurde. Im Menü finden Sie jetzt noch einen zweiten Menüpunkt, um das Backup für den zweiten Rechner zu starten. Rufen Sie auch diesen Thread auf, wird dies ebenfalls im Editor angezeigt, und auch dieser Vorgang wird gestartet. Beide Threads arbeiten jetzt gleichzeitig den Code ab – wofür die beiden vorgesehen sind –, und die Haupt-GUI bleibt nach wie vor ansprechbar. Ohne die Threads ließe sich hier nur ein Vorgang abarbeiten, und auch die Haupt-GUI wäre während dieser Zeit blockiert bzw. nur sporadisch ansprechbar (abhängig von der CPU-Last, die der Vorgang benötigt). Würden Sie ohne Threads trotzdem einen zweiten Vorgang starten, wäre auch dieser so lange blockiert, bis der erste Thread mit der Abarbeitung fertig ist. Zunächst benötigen wird dazu eine Klasse, die von QThread abgeleitet ist. Hierzu unsere Header-Datei: 00 01 02 03
// beispiele/qthread/thread.h #ifndef THREAD_H #define THREAD_H #include
04 05 06 07 08 09 10 11 12 13 14
class Thread : public QThread { Q_OBJECT public: Thread(); void setValues( int min, int max ); protected: void run(); private: int qmin, qmax; }; #endif
567
6.9
1542.book Seite 568 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Jetzt noch die Implementierung der Klasse »Thread«, mit einer anschließenden Erläuterung: 00 // beispiele/qthread/thread.cpp 01 #include 02 #include "thread.h" 03 Thread::Thread() { 04 qmin = 0; 05 qmax = 0; 06 } 07 void Thread::setValues( int min, int max ) { 08 qmin = min; 09 qmax = max; 10 } 11 void Thread::run() { 12 for (int i = 0; i < qmax; i++) { 13 // Hier findet die eigentliche Arbeit statt 14 // wir simulieren diese mit einer Schleife 15 for( volatile int j = 0; j < 12345; j++); 16 } 17 }
Unsere Klasse Thread erbt von QThread und implementiert die Klasse run() neu. Zusätzlich erhält die Klasse noch mit setValues() eine neue Funktion. Die wichtigste Implementierung finden Sie hier mit der Reimplementierung von run(). Diese Methode wird aufgerufen, um den Thread überhaupt zu starten. Eine echte Arbeit haben wir hier nicht implementiert und stattdessen einfach eine Schleife eingebaut, die CPU-Zeit vertrödeln soll. Hierzu wollen wir jetzt unsere HauptGUI erstellen, die unsere Klasse Thread verwendet. Zunächst wieder das Grundgerüst: 00 01 02 03 04 05 06
// beispiele/qthread/mainThread.h #ifndef MAINTHREAD_H #define MAINTHREAD_H #include #include #include #include "thread.h"
07 class MainThread : public QMainWindow { 08 Q_OBJECT
568
1542.book Seite 569 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public: MainThread(QMainWindow *parent = 0); protected: void closeEvent(QCloseEvent* event); private slots: void startThread1(); void startThread2(); void thread1Ready(); void thread2Ready(); private: QTextEdit *edit; Thread thread1; Thread thread2; }; #endif
Auch hierzu wieder die Implementierung des Codes, mit einer anschließenden Erläuterung: 00 01 02 03
// beispiele/qthread/mainThread.cpp #include #include "mainThread.h" #include "thread.h"
04 MainThread::MainThread(QMainWindow *parent) : QMainWindow(parent) { 05 edit = new QTextEdit; 06 QMenu *fileMenu = new QMenu(tr("Thread"), this); 07 menuBar()->addMenu(fileMenu); 08 fileMenu->addAction( QIcon(":/images/arrow_up.png"), tr("&Starte Thread 1"), this, SLOT(startThread1()), QKeySequence(tr("Ctrl+1","Thread|Starte Thread 1"))); 09 fileMenu->addAction( QIcon(":/images/arrow_merge.png"), tr("&Starte Thread 2"), this, SLOT(startThread2()), QKeySequence(tr("Ctrl+2","Thread|Starte Thread 2"))); 10 fileMenu->addAction( QIcon(":/images/cancel.png"), tr("&Beenden"), this, SLOT( close() ), QKeySequence(tr("Ctrl+E", "Thread|Beenden"))); 11 setCentralWidget(edit); 12 setWindowTitle(tr("QThread – Demo ")); 13 }
569
6.9
1542.book Seite 570 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
14 void MainThread::startThread1() { 15 if( thread1.isRunning() ) return; 16 edit->moveCursor ( QTextCursor::End) ; 17 edit->insertPlainText( tr("Backup 1 (Thread 1) gestartet\n")); 18 // Werte wurden willkürlich gewählt 19 thread1.setValues( 0, 51234 ); 20 connect( &thread1, SIGNAL(finished()), this, SLOT( thread1Ready())); 21 thread1.start(); 22 } 23 void MainThread::startThread2() { 24 if( thread2.isRunning()) return; 25 edit->moveCursor ( QTextCursor::End) ; 26 edit->insertPlainText( tr("Backup 2 (Thread 2) gestartet\n")); 27 thread2.setValues(0,48763); 28 connect( &thread2, SIGNAL(finished()), this, SLOT( thread2Ready())); 29 thread2.start(); 30 } 31 void MainThread::closeEvent(QCloseEvent *event) { 32 thread1.wait(); 33 thread2.wait(); 34 event->accept(); 35 } 36 void MainThread::thread1Ready() { 37 edit->moveCursor ( QTextCursor::End) ; 38 edit->insertPlainText( tr("Backup1 (Thread 1) ist fertig\n") ); 39 } 40 void MainThread::thread2Ready() { 41 edit->moveCursor ( QTextCursor::End); 42 43 }
570
edit->insertPlainText( tr("Backup 2 (Thread 2) ist fertig\n"));
1542.book Seite 571 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Die ganze Thread-Maschinerie wird in den Menüpunkten »Starte Thread 1« und »Starte Thread 2« (Zeile 8 und 9) in Gang gesetzt. Damit wird der jeweilige Slot startThread1() (Zeile 14 bis 22) bzw. startThread2() (Zeile 23 bis 30) ausgeführt. In diesen Slots prüfen wir zunächst, ob der Thread bereits ausgeführt wird (isRunning()). Wenn dies der Fall ist, wird nichts weiter mehr gemacht, und der Slot kehrt mit return zurück. Sollte der Thread noch nicht aktiv sein, übergeben wir ihm erst mal seine Daten mit der eigenen Methode setValues(). Die Daten übergeben wir hier einfach frei gewählt als Demonstration. Bevor wir jetzt den jeweiligen Thread mittels start() starten, richten wir noch eine Signal-Slot-Verbindung für den laufenden Thread ein. Dabei wird auf das Signal finished() gewartet. Tritt dieses Signal ein, wird unser eigener Slot thread1Ready() (Zeile 36 bis 39) bzw. thread2Ready() (Zeile 40 bis 43) ausgeführt. Dieser Slot bewirkt im Grunde nichts, außer auf dem Text-Editor auszugeben, dass der entsprechende Thread mit seiner Ausführung fertig ist. Wenn die Methode start() aufgerufen wird, wird automatisch die reimplementierte Methode run() aufgerufen. Und darin wird ja unsere Schleife gestartet, was auch die eigentliche Arbeit simulieren soll. Zusätzlich wurde die virtuelle Methode QWidget::closeEvent() reimplementiert (Zeile 31 bis 35). Darin verwenden wir für jeden Thread jeweils die Methode wait() um so zu verhindern, dass auf alle noch laufenden Threads gewartet wird, wenn der Anwender das Hauptfenster schließen will. Erst wenn sich alle Threads beendet haben, lässt wait() die Codeausführung vorbei, und die Anwendung kann mit QCloseEvent::accept() beendet werden. Damit gehen Sie sicher, dass die Anwendung in einem sauberen Zustand beendet wird. Jetzt fehlt nur noch das Hauptprogramm: 00 // beispiele/qthread/main.cpp 01 #include 02 #include "mainThread.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 MainThread mainThread; 06 mainThread.show(); 07 return app.exec(); 08 }
{
571
6.9
1542.book Seite 572 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Das Programm bei der Ausführung (siehe Abbildung 6.25).
Abbildung 6.25
6.9.1
Der Hauptthread und zwei weitere Threads nach der Ausführung
QMutex
Ein Mutex, wie dies Qt mit der Klasse QMutex anbietet, wird dazu verwendet, einen Codeausschnitt oder eine Variable zu schützen, so dass jeweils nur ein Thread darauf zurückgreifen kann. Hierzu reicht lediglich eine Methode zum Sperren und eine, um die Sperre wieder freizugeben, aus. Es folgt der Überblick zu den Methoden der Klasse QMutex. Methode
Beschreibung
QMutex ( RecursionMode mode = NonRecursive ) ;
Erzeugt einen neuen Mutex. Der Mutex wird in einem ungesperrten Zustand erzeugt. Standardmäßig wird der nichtrekursive Modus verwendet. Mögliche Werte für den Modus und deren Bedeutung siehe Tabelle 6.69.
~QMutex ();
Zerstört einen Mutex. Beachten Sie allerdings, dass die Zerstörung eines gesperrten Mutex ein undefiniertes Verhalten hervorbringt.
void lock ();
Sperrt den Mutex. Wenn ein anderer Thread den Mutex gesperrt hat, blockiert der Thread so lange, bis die Sperre wieder aufgehoben wurde.
bool tryLock ();
Versucht, einen Mutex zu sperren. Konnte die Sperre gesetzt werden, gibt diese Methode true zurück. Wenn ein anderer Thread den Mutex gesperrt hat, wird false zurückgegeben. Damit können bei einer gesetzten Sperre ggf. noch andere Arbeiten des Threads durchgeführt werden und somit das Blockieren von lock() umgehen.
void unlock ();
Gibt den gesperrten Mutex wieder frei. Sollte versucht werden, einen Mutex freizugeben, der gar nicht gesperrt wurde, ist das weitere Verhalten undefiniert.
Tabelle 6.68
572
Öffentliche Methoden der Klasse QMutex
1542.book Seite 573 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Jetzt zu den beiden möglichen Modi, die als Konstanten in der enum-Variablen QMutex::RecursionMode definiert sind, die für einen Mutex verwendet werden können. Konstante
Beschreibung
QMutex::Recursive
Mit diesem Modus kann ein Thread den gleichen Mutex mehrmals sperren. Die Sperre wird zudem nicht eher aufgehoben, solange die Anzahl der Sperren mit unlock() aufgehoben wurde.
QMutex::NonRecursive
Der Standardmodus, womit ein Thread nur ein einziges Mal gesperrt werden kann.
Tabelle 6.69
Mögliche Modis für QMutex
Um Ihnen einen Mutex in der Praxis zu demonstrieren, brauchen wir in unserem Beispiel hinsichtlich der Threads zuvor die Methode run()eigentlich nur etwas zu verändern. Wir stellen uns vor, dass theoretisch beide Threads in eine Datei schreiben würden. In der Praxis würde dies wohl einen Datensalat bedeuten, wenn beide Threads gleichzeitig in dieselbe Datei schreiben. Sollte dieser Fall eintreten, müssen wir einen Mutex vor dem kritischen Bereich sperren. Hierzu wird nur die Implementierung des Codes der Klasse Thread ein wenig verändert: // beispiele/qmutex/thread.cpp #include #include "thread.h" QMutex mutex; ... ... void Thread::run() { while( ! mutex.tryLock() ) { msleep(1000); } for (int i = 0; i < qmax; i++) { // Hier findet die eigentliche Arbeit statt. // Wir simulieren diese mit einer Schleife. for( volatile int j = 0; j < 123456; j++); } mutex.unlock(); }
573
6.9
1542.book Seite 574 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Das Beispiel ist im Grunde einfach (und natürlich vollständig in der Buch-DVD enthalten). Vor dem kritischen Codebereich versuchen wir mittels tryLock() einen Mutex zu sperren. Gelingt dies nicht, durchlaufen wir sekündlich (msleep(1000)) die Warteschleife. Am Ende – wenn ein Thread mit seiner Arbeit fertig ist – geben wir diesen Mutex mittels unlock() wieder frei. Ab der Stelle im Code, wo die Sperre gesetzt ist, kann jetzt nur noch jeweils der Thread, der die Sperre gesetzt hat, ausgeführt werden; alle anderen Threads warten auf das Freigeben des Mutex.
6.9.2
QMutexLocker
Sollten Sie C++-typische Exceptions oder komplexere Methoden verwenden, kann das Setzen und Wiederfreigeben von Sperren mit Mutexen recht fehleranfällig sein, weshalb die Qt-Entwickler mit der Klasse QMutexLocker eine recht einfach anzuwendende Mutex-Klasse erstellt haben. Hiermit genügt es, dem Konstruktor von QMutexLocker den Mutex zu übergeben. Der Destruktor von QMutexLocker gibt den Mutex wieder frei. Bezogen auf das eben gezeigte Beispiel sieht das äquivalente Gegenstück mit derselben Funktion wie eben folgendermaßen aus: // beispiele/qmutexlocker/thread.cpp #include "thread.h" #include #include QMutex mutex; ... ... void Thread::run() { QMutexLocker locker(&mutex); for (int i = 0; i < qmax; i++) { // Hier findet die eigentliche Arbeit statt. // Wir simulieren diese mit einer Schleife. for( volatile int j = 0; j < 12345; j++); } }
Auch hierzu nochmals alle vorhandenen Methoden der Klasse QMutexLocker, die als Vereinfachung der Klasse QMutex dient.
574
1542.book Seite 575 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Methode
Beschreibung
QMutexLocker ( QMutex * mutex ); Erzeugt ein neues QMutexLocker-Objekt und sperrt den Mutex mutex. Der Mutex wird wieder freigegeben, wenn das Objekt QMutexLocker zerstört wird. Wenn mutex gleich Null ist, passiert gar nichts. ~QMutexLocker ();
Zerstört ein QMutexLocker-Objekt und gibt die gesetzte Sperre vom Mutex, die mit dem Konstruktor gesetzt wurde, wieder frei.
QMutex * mutex () const ;
Gibt einen Zeiger auf dem Mutex zurück, der mit dem Konstruktor gesperrt wurde.
void relock ();
Sperrt einen freigegebenen Mutex erneut.
void unlock ();
Gibt den gesperrten Mutex frei.
Tabelle 6.70
6.9.3
Öffentliche Methoden der Klasse QMutexLocker
QReadWriteLock
Die Klasse QReadWriteLock ist im Grunde der Klasse QMutex recht ähnlich, mit dem Unterschied, dass zwischen dem Lesen und Schreiben der zusammen verwendeten Daten unterschieden wird. Sie können sich dies gerne wie einen Mutex für einen Lesezugriff und/oder einen Mutex für einen Schreibzugriff verstehen. In der Praxis wird sogar empfohlen, die Klasse QReadWriteLock, statt der Klasse QMutex zu verwenden, wenn es möglich ist, da diese Klasse weniger Leistungen benötigt. Diese Klasse wird gerne bei mehreren Threads verwendet, die gleichzeitig aus derselben Variablen lesen wollen, aber nur ein Thread existiert, der in diese Ressource schreibt. Wenn er einen Thread in diese Variable schreibt, müssen alle anderen Threads so lange blockiert werden, bis das Schreiben abgeschlossen wurde. Zugegeben, dass kann mit der Klasse QMutex auch realisiert werden, doch sollte es hierbei viele Lese-Threads geben, würde man schnell die Leistungsgrenzen erreichen. Folgender Codeausschnitt zeigt die Verwendung von QReadWriteLock in der Praxis: QReadWriteLock lock; MyDataClass myData; // Der Lese-Thread void ReaderThread::run() { ... // Lese-Sperre setzen lock.lockForRead();
575
6.9
1542.book Seite 576 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
// Lesen My_read_file(&myData); // Lese-Sperre wieder aufheben lock.unlock(); ... } // der Schreib-Thread void WriterThread::run() { ... // Schreib-Sperre setzen lock.lockForWrite(); // Schreiben My_write_file(&myData); // Schreib-Sperr freigeben lock.unlock(); ... }
Alternativ gibt es auch hier die try-Versionen wie bei der Klasse QMutex, um das Setzen einer Sperre zu versuchen. Aber auch hierzu am besten ein Überblick über die Methoden der Klasse QReadWriteLock und deren Bedeutung. Methode
Beschreibung
QReadWriteLock ();
Erzeugt ein QReadWriteLock-Objekt.
~QReadWriteLock ();
Zerstört ein QReadWriteLock-Objekt. Das Zerstören eines Objekts mit gesetzter Lese- oder Schreibsperre kann zu einem undefinierten Verhalten führen.
void lockForRead ();
Setzt eine Lesesperre. Hat ein anderer Thread gerade eine Schreibsperre gesetzt, wird dieser Thread so lange blockiert, bis diese Sperre aufgehoben wurde.
void lockForWrite ();
Setzt eine Schreibsperre. Solange andere Threads eine Lese- oder Schreibsperre gesetzt haben, blockiert diese Methode den aktuellen Thread, bis keine Sperren mehr vorhanden sind.
bool tryLockForRead ();
Versucht, eine Lesesperre zu setzen. Beim erfolgreichen Setzen einer Lesesperre gibt diese Methode true zurück. Ansonsten wird false zurückgegeben, und die Methode blockiert die weitere Ausführung des Threads nicht. Diese Methode wird so lange fehlschlagen, bis ein anderer Thread eine Schreibsperre gesetzt hat.
Tabelle 6.71
576
Öffentliche Methoden der Klasse QReadWriteLock
1542.book Seite 577 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Methode
Beschreibung
bool tryLockForWrite ();
Versucht, eine Schreibsperre zu setzen. Bei Erfolg wird true zurückgegeben. Ansonsten wird false zurückgegeben und die weitere Ausführung des Threads nicht blockiert. Solange ein anderer Thread eine Lese- bzw. Schreibsperre gesetzt hat, gibt diese Methode immer false zurück.
void unlock ();
Hebt eine Sperre wieder auf. Wenn Sie versuchen, eine Sperre aufzugeben, wo keine gesetzt war, ist dies ein Fehler, und das Programm wird beendet.
Tabelle 6.71
6.9.4
Öffentliche Methoden der Klasse QReadWriteLock (Forts.)
QSemaphore
Die Klasse QSemaphore ist im Grunde wiederum einem Mutex nicht unähnlich. Im Gegensatz zu einem Mutex können Sie allerdings mit einem Semaphor mehrere Ressourcen schützen. Hierzu die Methoden von QSemaphore: Methode
Beschreibung
QSemaphore ( int n = 0 );
Erzeugt eine neue Semaphore und initialisiert die Nummer der Ressource, die geschützt werden soll, auf n (per Standard auf 0).
~QSemaphore ();
Zerstört eine Semaphore. Wird eine Semaphore zerstört, die im Augenblick verwendet wird, so ist das weitere Verhalten undefiniert.
void acquire ( int n = 1 );
Versucht, n Ressourcen anzuschaffen, die vom Semaphore geschützt werden sollen. Ist n > available(), blockiert diese Methode so lange, bis wieder genügend Ressourcen vorhanden sind.
int available () const;
Gibt die Anzahl möglicher Ressourcen zurück, die dem Semaphore aktuell zur Verfügung stehen. Dieser Wert kann niemals negativ sein.
void release ( int n = 1 );
Gibt n Ressourcen, die vom Semaphore geschützt werden, wieder frei.
bool tryAcquire ( int n = 1 ); Versucht, n Ressourcen, die vom Semaphore geschützt
werden, anzuschaffen. Bei Erfolg gibt diese Methode true zurück, und bei einem Fehler, wenn die Ressourcen nicht angeschafft werden konnten, wird false zurückgegeben. Ist n > available(), gibt diese Methode immer false zurück. Im Gegensatz zu acquire() blockiert
diese Methode nicht und kehrt sofort wieder zurück. Tabelle 6.72
Öffentliche Methoden der Klasse QSemaphore
577
6.9
1542.book Seite 578 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Gewöhnlich wird eine Semaphore bei Übertragungen von bestimmten Datenmengen zwischen Threads mit einem gemeinsamen zirkulären Puffer einer bestimmten Größe verwendet. Hierbei schreibt ein produzierender Thread laufend Daten in ein Puffer, wobei die Daten nach jedem Durchlauf überschrieben werden. Auf der anderen Seite liest ein konsumierender Thread diese Daten wieder ein, wie diese vom Producer erzeugt wurden. Würden Sie hierbei keine Synchronisation verwenden, so wird es passieren, dass der Produzent schneller Daten schreibt, als der Konsument lesen kann. Somit könnten bereits Daten, die der Konsument noch nicht gelesen hat, überschrieben werden und am Ende, wenn der Produzent fertig ist, würde der Konsument gar noch Datenmüll lesen. Hierzu ein Beispiel, wie diese Synchronisation mit einem Semaphor gemacht werden kann, mit einer anschließenden Erläuterung. Zunächst das Grundgerüst für die GUI, um die Threads zu starten: 00 01 02 03 04 05 06
// beispiele/qsemaphor/mainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include "thread.h"
07 08 09 10 11 12 13 14 15 16 17 18 19 20
class MainThread : public QMainWindow { Q_OBJECT public: MainThread(QMainWindow *parent = 0); protected: void closeEvent(QCloseEvent* event); private slots: void startThreads(); private: QTextEdit *edit; ProducerThread thread1; ConsumerThread thread2; }; #endif
Die Implementierung der GUI: 00 // beispiele/qsemaphor/mainWindow.cpp 01 #include 02 #include "mainWindow.h"
578
1542.book Seite 579 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
03 #include "thread.h" 04 MainThread::MainThread(QMainWindow *parent) : QMainWindow(parent) { 05 edit = new QTextEdit; 06 QMenu *fileMenu = new QMenu(tr("Thread"), this); 07 menuBar()->addMenu(fileMenu); 08 fileMenu->addAction( QIcon(":/images/arrow_divide.png"), tr("&Start Consumer/Producer"), this, SLOT(startThreads()), QKeySequence( tr("Ctrl+S","Thread|Start Consumer/Producer"))); 09 fileMenu->addAction( QIcon(":/images/cancel.png"), tr("&Beenden"), this, SLOT( close() ), QKeySequence(tr("Ctrl+E", "Thread|Beenden"))); 10 setCentralWidget(edit); 11 setWindowTitle(tr("Threads mit QSemaphore")); 12 } 13 void MainThread::startThreads() { 14 if( thread1.isRunning() ) return; 15 if( thread2.isRunning()) return; 16 thread1.setValues(edit); 17 thread2.setValues(edit); 18 thread2.start(); 19 thread1.start(); 20 } 21 void MainThread::closeEvent(QCloseEvent *event) { 22 thread1.wait(); 23 thread2.wait(); 24 event->accept(); 25 }
Die beiden Thread-Klassen (Consumer und Producer) wurden aus Platzgründen in eine Datei gepackt. Zugegeben, nicht sehr stilvoll, aber … 00 01 02 03 04
// beispiele/qthread/thread.h #ifndef THREAD_H #define THREAD_H #include #include
579
6.9
1542.book Seite 580 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
05 06 07 08 09 10 11 12
class ProducerThread : public QThread { public: ProducerThread(QObject *parent=0):QThread(parent) {}; QTextEdit *qedit; void setValues( QTextEdit * ); protected: void run(); };
13 14 15 16 17 18 19 20 21
class ConsumerThread : public QThread { public: ConsumerThread(QObject *parent=0):QThread(parent) {}; QTextEdit *qedit; void setValues( QTextEdit * ); protected: void run(); }; #endif
Jetzt die Implementierung des Codes: 00 01 02 03
// beispiele/qsemaphor/thread.cpp #include #include #include "thread.h"
04 const int cData = 20; 05 const int BufSize = 32; 06 char buffer[BufSize]; 07 QSemaphore freeBytes(BufSize); 08 QSemaphore usedBytes(0); 09 void ProducerThread::setValues( QTextEdit *edit ) { 10 qedit = edit; 11 } 12 void ProducerThread::run() { 13 for( int j = 0; j < cData; j++) { 14 msleep(500); 15 freeBytes.acquire(); 16 int n = (rand() % BufSize); 17 for( int i=0; i < n; ++i ) { 18 if( i == 0 ) 19 buffer[i] = j+65;
580
1542.book Seite 581 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
20 21 22 23 24 25 26 27 28 }
else if( j % 2 ) buffer[i] = 'X'; else buffer[i] = 'Y'; } buffer[n] = '\0'; usedBytes.release(); }
29 void ConsumerThread::setValues( QTextEdit *edit ) { 30 qedit = edit; 31 } 32 void ConsumerThread::run() { 33 for( int j = 0; j < cData; j++) { 34 usedBytes.acquire(); 35 QString str; 36 str.append(buffer); 37 str.append("\n"); 38 freeBytes.release(); 39 qedit->moveCursor ( QTextCursor::End ) ; 40 qedit->insertPlainText(str); 41 } 42 }
Um zu sehen, was hier passiert, wurde die Ausgabe auf dem Texteditor gemacht, wovon jeder Thread über setValues() die Adresse bekommt. Jetzt zur Synchronisation der beiden Threads: In den Zeilen 7 und 8 legen wir zwei Semaphore dazu an. Der Semaphor freeBytes ist für die Verwaltung der Daten (genauer des Puffers) vom Produzenten verantwortlich, der gefüllt werden kann. Der Semaphore usedBytes hingegen wird für den Bereich verwendet, der vom Konsumenten gelesen werden kann. freeBytes wird BufSize (hier 32) initialisiert. Somit verfügen wir über genauso viele Ressourcen, wie angefordert werden können. Wenn das Beispiel gestartet wird, fängt der Consumer-Thread an, freie Bytes anzufordern und diese als verwendbare Bytes umzuwandeln. Damit der Consumer-Thread nicht mit dem Lesen von Datenmüll beginnt, wurde der Semaphor usedBytes mit 0 initialisiert. Der Durchlauf des Produzenten (Zeile 12 bis 28) beginnt in der Schleife damit, ein freies Byte anzufordern (Zeile 15). Sollte der Puffer noch voll mit Daten sein, die der Konsument nicht gelesen hat, blockiert acquire(), bis der Konsument
581
6.9
1542.book Seite 582 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
die Daten gelesen hat. Wollen Sie das Blockieren vermeiden und währenddessen andere Arbeiten im Thread ausführen, können Sie auch tryAcquire() verwenden. Hat der Produzent sein freies Byte erhalten, können wir den Puffer mit Daten befüllen (hier Zeile 16 bis 25). Im Beispiel befüllen wir ein zufällig langes charArray abwechselnd mit X und Y. Anfangs fügen wir jeweils in alphabetischer Reihenfolge (A, B, C) einen Buchstaben ein, um anschließend beim Konsument zu überprüfen, ob auch alle Daten angekommen sind (hier A bis T). Jetzt können wir das benutzte Byte mit release() freigeben (Zeile 26), so dass der konsumierende Thread anfangen kann, aus dem Puffer zu lesen. Jetzt zum Durchlauf des Konsumenten-Threads (Zeile 32 bis 42): Beim Konsument wird gleich versucht, das benutzte Byte (Zeile 34) zu verwenden. Enthält der Puffer keine lesbaren Daten, wird auch hier der Aufruf von acquire() blockiert, bis Daten vom Produzenten in den Puffer geschrieben wurden. Sind Daten im Puffer vorhanden, lesen wir diese in einen String (QString) ein und geben mit release() (Zeile 38) das freie Byte wieder zurück, wodurch der Produzent ggf. wieder neue Daten in den Puffer schreiben kann. Nach jedem Konsum geben wir diese Daten zeilenweise auf dem Editor aus. Jetzt fehlt nur noch ein Hauptprogramm: 00 // beispiele/qsemaphore/main.cpp 01 #include 02 #include "mainWindow.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 MainThread mainThread; 06 mainThread.show(); 07 return app.exec(); 08 }
{
Das Programm nach der Ausführung (siehe Abbildung 6.26). Das Beispiel wurde so geschrieben, dass immer der Konsument gleich etwas liest, sobald der Produzent etwas in den Puffer geschrieben hat. Das Beispiel könnte man allerdings noch verbessern, indem der Produzent den kompletten Puffer mit Daten füllt, ehe der Konsument davon liest. Natürlich kann man noch beliebig viele andere Strategien mit den Semaphoren verwenden.
582
1542.book Seite 583 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
Abbildung 6.26
6.9.5
Producer-/Consumer-Threads mit Semaphore
QWaitCondition
Eine weitere Klasse, mit der Sie einen Produzent und einen Konsumenten synchronisieren können, finden Sie in der Klasse QWaitCondition. Mit dieser Klasse kann ein Thread einen anderen Thread aufwecken, wenn eine bestimmte Bedingung erfüllt ist. Damit lässt sich eine bessere Steuerung eines Mutex realisieren. Bevor Sie ein Beispiel dazu sehen, zunächst wieder die Beschreibung der Methoden der Klasse. Methode
Beschreibung
QWaitCondition ();
Erzeugt eine neues QWaitCondition-Objekt.
~QWaitCondition ();
Zerstört ein QWaitCondition-Objekt.
bool wait ( QMutex * mutex, unsigned long time=ULONG_MAX);
Gibt einen gesperrten Mutex frei und wartet auf die Wartebedingung. Der Mutex mutex muss hierbei ein gesperrte Mutex desselben Threads sein. Ist der Mutex nicht gesperrt, kehrt diese Methode sofort zurück. Ebenso sofort kehrt diese Methode zurück, wenn der Mutex ein rekursiver ist.
Tabelle 6.73
Öffentliche Methoden der Klasse QWaitCondition
583
6.9
1542.book Seite 584 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Methode
Beschreibung
(Forts.)
Nach dem Aufruf dieser Methode wird der Mutex freigegeben und der aufrufende Thread blockiert so lange, bis eine der folgenden Bedingungen eintritt: 왘
Ein anderere Thread verwendet wakeOne() oder wakeAll(). In diesem Fall gibt wait() als Rückgabewert true zurück.
왘
time Millisekunden sind vergangen. Dann gibt diese Methode false zurück, wenn die Zeit abgelaufen ist. Beim Standardwert ULONG_MAX gibt es keine Zeit, auf die gewartet wird.
왘
Ist diese Methode fertig, wenn eben eine der eben erwähnten Bedingungen auftritt, wird der Mutex wieder in gesperrtem Zustand wie vor dem Aufruf von wait() zurückversetzt.
void wakeAll ();
Weckt alle Threads, die auf eine Wartebedingung warten, auf. Die Reihenfolge hängt vom Betriebssystem ab und kann nicht beeinflusst werden.
void wakeOne ();
Weckt einen Thread, der auf eine Wartebedingung wartet, auf.
Tabelle 6.73
Öffentliche Methoden der Klasse QWaitCondition (Forts.)
Als Beispiel dazu brauchen Sie nur den Code von thread.h vom Beispiel zuvor (mit QSemaphor) neu erstellen. Auch hierbei wollen wir wieder auf das Producer/Consumer-Prinzip zugreifen. Dieses Mal sollen nochmals die Fenster mit dem Fortschrittsbalken verwendet werden. Dabei wollen wir wieder einen Vorgang simulieren, wo ein Fenster mit einem Balken das Schreiben von Daten (der Produzent) übernimmt und das andere Fenster (Konsument) das Lesen. Allerdings soll dies jetzt abwechselnd in Scheibchen geschehen. Sprich, der Produzent schreibt 20 Prozent der Daten in einen Puffer. Jetzt ist der Puffer voll, und der Konsument soll 20 Prozent der Daten aus dem Puffer lesen. Natürlich wartet der Produzent während dieser Zeit, bis der Konsument fertig ist, mit dem Lesen. Ist der Konsument fertig, wartet dieser wieder auf den Produzenten usw. Hierzu die neue Implementierung von »thread.cpp« (wo übrigens die Methoden setValues() entfernt wurden) mit einer anschließenden Erläuterung. 00 // beispiele/qwaitcondition/thread.cpp 01 #include 02 #include
584
1542.book Seite 585 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
03 #include 04 #include 05 #include "thread.h" 06 07 08 09 10 11
QWaitCondition QWaitCondition QMutex mutex; int usedBuf = int maxBuf = int DataSize =
WritingStopp; ReadingStopp; 0; 1000; 5000 ;
12 void ProducerThread::run() { 13 while( ! mutex.tryLock() ) { 14 msleep(1000); 15 } 16 for (int i = 0; i < DataSize; i++) { 17 if( usedBuf == maxBuf ) { 18 WritingStopp.wait(&mutex); 19 edit->moveCursor ( QTextCursor::End) ; 20 edit->insertPlainText( tr("Producer : 20 % geschrieben\n")); 21 ReadingStopp.wakeAll(); 22 } 23 for( volatile int j = 0; j < 123456; j++); 24 ++usedBuf; 25 } 26 edit->moveCursor ( QTextCursor::End) ; 27 edit->insertPlainText(tr("Producer : fertig\n")); 28 mutex.unlock(); 29 } 30 void ConsumerThread::run() { 31 msleep(1000); 32 while( ! mutex.tryLock() ) { 33 msleep(1000); 34 } 35 for (int i = 0; i < DataSize; i++) { 36 if( usedBuf == 0 ) { 37 WritingStopp.wakeAll(); 38 ReadingStopp.wait(&mutex); 39 edit->moveCursor ( QTextCursor::End) ; 40 edit->insertPlainText( tr("Consumer : 20 % gelesen\n")); 41 }
585
6.9
1542.book Seite 586 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
42 43 44 45 46 47 48 }
for( volatile int j = 0; j < 123456; j++); --usedBuf; } edit->moveCursor ( QTextCursor::End) ; edit->insertPlainText(tr("Consumer : fertig\n")); mutex.unlock();
Der Vorgang der Wartebedingung beginnt beim Produzenten in der Zeile 17, wo zunächst überprüft wird, ob der verwendete Puffer voll ist. Ist der Puffer voll, warten wir auf die Bedingung WritingStopp (Zeile 18) und wecken die Wartebedingung ReadingStopp (Zeile 21) des Conumser-Threads auf. Ist der Puffer hingegen nicht voll, werden weiterhin Daten produziert. Auf der Consumer-Seite (wo eigentlich genau das Gegenteil passiert) beginnt die Wartebedingung ab Zeile 36, wo überprüft wird, ob der verwendete Puffer leer ist. Ist dies der Fall, wecken wir die Bedingung WrittingStop (Zeile 37) auf und setzen die Wartebedingung ReadingStopp (Zeile 38). Sind im verwendeten Puffer Daten, beginnen wir beim Consumer-Thread mit dem Einlesen dieser Daten. Sämtliche Methoden werden natürlich über den Texteditor der Haupt-GUI mitprotokolliert. Hierzu wurde in der Headerdatei eine Inline-Methode implementiert, womit wir dem jeweiligen Thread einen Zeiger auf ein QTextEdit-Objekt der Haupt-GUI übergeben haben. Der Mutex wird hierbei verwendet, um die Zugriffe auf die Variable usedBuf zu schützen. Bei der Ausführung des Programms werden jetzt immer jeweils 20 % vom Produzenten verarbeitet, ehe der Consumer diese 20 % wieder verarbeitet. Ist der Consumer fertig, macht der wartende Produzent weiter, um die nächsten 20 % zu schreiben, und der Consumer liest daraufhin abermals 20 % ein usw., bis eben 100 % erreicht sind. Komplettes Listing Das komplette Listing finden Sie natürlich auf der Buch-DVD wieder.
6.9.6
Datenstrukturen an den Thread binden – QThreadStorage
Wenn ein Thread auf eine globale Variable zugreift und diese verändert, bedeutet dies auch, dass der neue Wert für alle anderen Threads ebenfalls gilt. Wollen Sie, dass eine globale Variable bei den laufenden Threads unterschiedliche Werte ent-
586
1542.book Seite 587 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
hält, bspw. zum Speichern threadspezifischer Daten, können Sie dies mit der Template-Klasse QThreadStorage realisieren. Compiler-Limits Aufgrund der Compiler-Limitierung kann QThreadStore nur zeigerbasierte Objekte aufnehmen.
Der Vorteil von QThreadStorage ist, dass mit diesem Zwischenspeicher der Aufwand des Sperrens, Freigebens und vor allem des Wartens auf einen Mutex entfällt. Hier die Methoden der Klasse QThreadStorage: Methode
Beschreibung
QThreadStorage ();
Erzeugt ein neues QThreadStorage-Objekt.
~QThreadStorage ();
Destruktor. Zerstört ein QThreadStorage-Objekt. Beachten Sie allerdings, dass die Daten in diesem Objekt explizit gelöscht werden müssen, bevor Sie das Objekt zerstören, da Sie sonst ein Memory-Leak haben.
bool hasLocalData () const;
Gibt true zurück, wenn der aufrufende Thread keine leeren Daten enthält. Ansonsten wird false zurückgegeben.
T & localData ();
Gibt eine Referenz auf die Daten zurück, die vom aufrufenden Thread gesetzt wurden. Beachten Sie, dass QThreadStorage eigentlich nur Zeiger speichern kann, somit liefert diese Methode nur eine Referenz auf dem Zeiger zurück. Wurden keine Daten gesetzt, ist die Referenz gleich 0.
T localData () const;
Gibt eine Kopie auf die Daten zurück, die vom aufrufenden Thread gesetzt wurden. Beachten Sie, dass QThreadStorage eigentlich nur Zeiger speichern kann, somit liefert diese Methode nur eine Referenz auf den Zeiger zurück. Wurden keine Daten gesetzt, ist die Referenz gleich 0.
void setLocalData ( T data ); Setzt die lokalen Daten für den Thread auf data. Zugreifen können Sie darauf mit der Methode localData().
Tabelle 6.74
Öffentliche Methoden der Klasse QThreadStorage
Hierzu wieder ein einfaches Beispiel, wo zwei Threads in das globale QThreadStorage-Objekt Daten speichert. Im Beispiel werden zwei Threads
gestartet, wo jeweils jeder Thread fünfmal die aktuelle Uhrzeit in das threadlokale QThreadStorage-Objekt schreibt. Anschließend wird das Ganze ausgewertet und auf dem Editor ausgegeben. Auch dieses Beispiel lässt sich in das bereits
587
6.9
1542.book Seite 588 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
erstellte Beispiel mit dem Editor einbauen, nur dass jetzt die Dateien thread.cpp und thread.h erneuert werden. Komplettes Listing Der komplette Quellcode befindet sich natürlich wieder auf der Buch-DVD.
Zunächst das Grundgerüst: 00 01 02 03 04
// beispiele/qthreadstorage/thread.h #ifndef THREAD_H #define THREAD_H #include #include
05 06 07 08 09 10 11 12 13 14 15 16 17
class Thread : public QThread { Q_OBJECT public: Thread() {}; void setValues( QTextEdit* edit, QString threadName ); void printResult(); protected: void run(); private: QTextEdit* qedit; QString qname; }; #endif
Hier wurde wieder die Methode setValues() eingebaut, mit einem zusätzlichen String, wo wir den Threadnamen angeben, um diesen bei der Ausgabe des Editors unterscheiden zu können. Jetzt zur Implementierung des Codes mit einer anschließenden Erläuterung: 00 01 02 03 04
// beispiele/qthreadstorage/thread.cpp #include #include #include #include "thread.h"
05 QThreadStorage storage; 06 QMutex mutex; 07 void Thread::run() { 08 for(int i = 0; i < 5; ++i) {
588
1542.book Seite 589 Montag, 4. Januar 2010 1:02 13
Multithreads – QThread
09 10 11 12 13 14 15 16 17 18 }
QMutexLocker lock(&mutex); if( !storage.hasLocalData()) { // Liste erstellt storage.setLocalData(new QList); } storage.localData()->append(QTime::currentTime()); sleep( (rand() % 4) +1 ); } printResult();
19 void Thread::setValues( QTextEdit* edit, QString threadName ) { 20 qedit = edit; 21 qname = threadName; 22 } 23 void Thread::printResult() { 24 // Auswerten und Ausgeben 25 QList::iterator i; 26 QString str; 27 for (i = storage.localData()->begin(); i != storage.localData()->end(); ++i) { 28 str.append(qname); 29 str.append( i->toString(" hh:mm:ss\n")); 30 } 31 qedit->moveCursor ( QTextCursor::End ) ; 32 qedit->insertPlainText(str); 33 }
Das Programm bei der Ausführung:
Abbildung 6.27
QThreadStorage bei der Ausführung
589
6.9
1542.book Seite 590 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Die Ausgabe soll zeigen, dass sich in jedem Thread andere threadlokale Daten befinden, obwohl hier eine globale Variable verwendet wurde. Maximale Anzahl von QThreadStorage-Objekten Hier muss noch angemerkt werden, dass für einen Prozess (nicht Thread) maximal 256 solcher QThreadStorage-Objekte gespeichert werden können.
6.9.7
Ausblick
Mit diesem Abschnitt zu den Multithreads haben Sie die Grundlagen zur Erstellung von Qt-Anwendungen mit Threads kennengelernt. Natürlich war dies im Grunde auch wieder nur ein Anriss. Manchmal wünscht man sich als Autor einfach mehr Seiten (die allerdings am Ende wieder nicht ausreichen würden ...). Trotzdem noch einige weitere Notizen. forever In den Beispielen wurde das Qt-Schlüsselwort forever mit Absicht beiseite gelassen, weil dieses zunächst recht verwirrend sein kann. Wenn Sie sich schon mal andere Qt-Beispiele angesehen haben, wird Ihnen dieses Wort sicherlich das eine oder andere Mal aufgefallen sein. Im Grunde ist diese Bezeichnung gleichwertig mit while(true), also entspricht in Qt das Konstrukt forever { // Anweisungen }
exakt dem folgenden Konstrukt: while(true) { // Anweisungen }
Weitere Beispiele Bei der Qt-Distribution sind natürlich auch tolle Beispiele mit den Threads vorhanden. Besonders empfehlenswert wäre hierbei das für Threads berüchtigte »Mandelbrot«. Aber auch ein TCP-Server-Client (»Threaded Fortune Server« und »Blocking Fortune Client«) als Beispiel ist hierbei vorhanden (weshalb hierzu auch keine solchen Beispiele im Buch aufgenommen wurden). Und natürlich sollte wie immer die Referenz von Qt dazu gelesen werden. Darin findet sich ein separater Abschnitt zum Thema »Multithreads mit Qt«.
590
1542.book Seite 591 Montag, 4. Januar 2010 1:02 13
Relationale Datenbanken – QtSql
6.10
Relationale Datenbanken – QtSql
Kaum eine Anwendung, die umfangreiche Mengen von Daten verwalten soll, kommt ohne relationales Datenbankmanagementsystem (kurz DBMS) aus. Das QtSql-Modul von Qt hilft Ihnen dabei, problemlos eine Datenbank in die QtAnwendung zu integrieren. Wer selbst schon mal versucht hat, bei einer GUIAnwendung eine Datenbank zu integrieren, wird feststellen, dass das QtSqlModul eine erhebliche Erleichterung darstellt. Mehr SQL Der Umfang des QtSql-Moduls ist beachtlich. Eine Beschreibung der verschiedenen Klassen mit deren Methoden usw., wie dies bisher bei den anderen Klassen gemacht wurde, würde locker 100 Seiten beanspruchen. Das will ich niemanden zumuten, weshalb Sie nur einen kurzen Rundumschlag zu diesem Thema vorfinden, um zu erfahren, wie Sie überhaupt Datenbanken mit Qt verwenden können. Wenn Sie sich intensiver damit befassen wollen, sei die hervorragende Referenz von Qt empfohlen. Natürlich setzt das Thema auch voraus, dass Sie mit relationalen Datenbanken (genauer SQL) vertraut sind. Ein Kapitel aus meinem Buch »Linux-UNIX-Programmierung« und eines aus dem Buch »C von A bis Z« zum Einstieg dazu finden Sie auf der Buch-DVD wieder.
6.10.1 Die Treiber für QtSql In der untersten Schicht des QtSql-Moduls befindet sich die Treiberschicht mit den Klassen QSqlDriver, QSqlDriverCreator, QSqlDriverBase, QSqlDriverPlugin und QSqlResult. In dieser Schicht sind die Schnittstellen zu den verschie-
denen Datenbanken implementiert. Folgende Datenbanken werden von QtSql unterstützt (mit dem dazugehörigen Treibernamen): Datenbank
Treibername in QtSql
IBM DB2 (Version >= 7.1) (kein GPL)
QDB2
Borland InterBase
QIBASE
MySQL
QMYSQL
Oracle (Vers. 8, 9 und 10) (keinGPL)
QOCI
ODBC (verwendet von Microsoft SQL-Server und anderen)
QODBC
PostgresSQL ( Version >= 7.3)
QPSQL
SQLite (Version 2)
QSQLITE2
Tabelle 6.75
Vorhandene Treiber für QtSql
591
6.10
1542.book Seite 592 Montag, 4. Januar 2010 1:02 13
6
Ein-/Ausgabe von Daten
Datenbank
Treibername in QtSql
SQLite (Version 3)
QSQLITE
Sybase Adaptive Server (kein GPL)
QTDS
Tabelle 6.75
Vorhandene Treiber für QtSql (Forts.)
Die mit »kein GPL« gekennzeichneten Datenbanken lassen sich nicht mit der GPL-Lizenz vereinbaren und sind somit auch nicht bei der Open-Source-Edition von Qt vorhanden. Projektdatei anpassen nicht vergessen! Wie beim Netzwerk-Teil von Qt wird der SQL-Teil nicht automatisch in ein Qt-Projekt eingefügt, so dass Sie auch hier in der Projekt-Datei einen Eintrag zur Bibliothek hinzufügen müssen: QT += sql
Wer eine Linux-Distribution verwendet, hat es meistens recht leicht, diese Treiber zu verwenden. Hier müssen gewöhnlich nur doch die benötigten Pakete nachinstalliert werden. Bei Ubuntu (hier bspw. Version 7.04) benötigen Sie nur das Paket libqt4-sql. Andere Distributionen benötigen häufig neben dem Paket qt-sql ein weiteres datenbankspezifisches Paket, wie bspw. für MySQL das Paket qt-sql-mysql oder für PostgresSQL das Paket qt-sql-psql. SQL-Treiber bauen Sollten Sie ggf. Qt aus den Quellen bauen wollen, können Sie sich die Ausgabe von ./configure -help ansehen. Darin finden Sie auch Schalter, womit Sie SQL-Treiber als Plugin oder separat verwenden können (-qt-sql- bzw. -plugin-sql).
Lange Gesichter hingegen gibt es meistens bei den MS-Windows-Anwendern, besonders, wenn Letztere SQL-Treiber mit dem MinGW-Compiler verwenden wollen. Gewöhnlich findet man unter MS-Windows standardmäßig nur die Treiber zu SQLite und ODBC installiert (meistens zu finden in :\Qt\4.6\ plugins\sqldrivers). Erfahrungsgemäß will man allerdings in der Praxis mit der MySQL- oder auch der PostgresSQL-Datenbank arbeiten. Hierbei werden Sie nicht um das berühmte »Herumfrickeln« herumkommen und sich den Datenbank-Treiber selbst zusammenbauen müssen.
592
1542.book Seite 593 Montag, 4. Januar 2010 1:02 13
Relationale Datenbanken – QtSql
SQL-Treiber für Windows bauen Da das Selbsterstellen der SQL-Treiber unter MS-Windows etwas aufwändiger und umständlicher ist, finden Sie auf der Buch-DVD einige nützliche Webseiten-Artikel, die ich zu dieser Thematik zusammengetragen haben.
6.10.2 Ein Verbindung zur Datenbank herstellen – QSqlDatabase Ist ein entsprechender Treiber vorhanden, können Sie eine Verbindung zum Datenbank-Server herstellen. Voraussetzung für die Beispiele Um dieses Beispiel in der Praxis zu testen, setzt dies natürlich voraus, dass ein entsprechender Datenbank-Server ausgeführt wird. Natürlich ist es möglich, dass Sie hiermit auf einen entfernten Server zurückgreifen können, aber in der Praxis sollte man so etwas immer erst auf dem lokalen Rechner testen. Hierzu müssen Sie natürlich entsprechende Software installiert haben. Ich empfehle Ihnen hierzu, neben dem Datenbank-Server gleich einen Webserver-Server und PHP zu installieren. Das Ganze ist besser bekannt unter LAMP (Linux Apache MySQL PHP) bzw. WAMP (Windows Apache MySQL PHP) und kostenlos erhältlich. Mehr dazu finden Sie auf der BuchDVD mit zwei Kapiteln aus meinen Büchern »C von A bis Z« und »Linux-UNIX-Programmierung«.
Somit lässt sich bspw. folgendermaßen eine Verbindung zu einem MySQLDatenbank-Server auf dem lokalen Rechner herstellen: // QMYSQL ist der Datenbanktreiber in Stringform. QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); // der Host, mit dem wir uns verbinden db.setHostName("localhost"); // der Name der Datenbank db.setDatabaseName("dvd_archiv"); // der Benutzername db.setUserName("wolf"); // das Passwort db.setPassword("asdfjklo"); // jetzt die Verbindung herstellen if( ! db.open() ) { // Fehler, keine Verbindung qDebug() " + title + "" "
" + content + " |
"; 60 61 62 }
} printPage(print);
63 void Window::printPage(const QString &text) { 64 QPrinter printer; 65 QTextDocument textDocu; 66 textDocu.setHtml( text ); 67 if( pdf ) { 68 printer.setOutputFileName(QString("%1 %2") .arg(QCoreApplication::applicationDirPath()) .arg("/doku.pdf")); 69 textDocu.print(&printer); 70 pdf = false; 71 } 72 else { 73 QPrintDialog printDialog( &printer, this ); 74 if( printDialog.exec() == QDialog::Accepted) { 75 printer.setOutputFileName(tr("")); 76 textDocu.print(&printer); 77 } 78 } 79 }
Die Methode addContent() (Zeile 30 bis 48) fügt immer jeweils ein neues Titel/ Content-Paar, getrennt mit einem #-Zeichen, zur Stringliste hinzu. Wurde auf »Drucken« gedrückt, wird die Stringliste mit dem Titel und Inhalt ins HTML-Format umgewandelt und für den Druck vorbereitet, was im Beispiel mit der Methode preparePage() (Zeile 53 bis 62) geschieht. Selbiges passiert auch beim Auswählen von »PDF erstellen«, nur dass hier zuerst der Umweg über die Methode createPDF() führt (Zeile 49 bis 52), wo der Boolesche Wert dazu entsprechend gesetzt wird. Das Ausdrucken, das vorbereiteten Textes geschieht dann in den Zeilen 63 bis 79 mit der Methode printPage(), wo wir den präparierten Text an ein QTextDocument-Objekt übergeben. Anschließend überprüfen wir, ob ein PDFDokument erstellt werden soll (ab Zeile 67) oder nicht. Im Grunde ist hier also alles nicht viel anders als im Beispiel, in dem wir ein Bild ausdruckten, nur dass wir anstelle von QPainter eben die Klasse QTextDocument und statt einer
700
1542.book Seite 701 Montag, 4. Januar 2010 1:02 13
OpenGL mit Qt
draw...()-Methode die print()-Methoden von QTextDocument verwendeten. Jetzt noch das zugehörige Hauptprogramm: 00 // beispiele/qprinter2/main.cpp 01 #include 02 #include "mainWindow.h" 03 int main(int argc, char *argv[]) 04 QApplication app(argc, argv); 05 Window window; 06 window.show(); 07 return app.exec(); 08 }
{
Manuelles Drucken mit Qt Neben der gerne verwendeten Möglichkeit, über QTextDocument etwas auszudrucken, kann man natürlich auch alles gleich selbst in die Hand nehmen bzw. – genauer – selbst zeichnen und den Seitenumbruch selbst durchführen. Wenn es um das eigene Zeichnen geht, wird natürlich wieder auf QPainter zurückgegriffen.
9.4
OpenGL mit Qt
OpenGL (Open Graphic Library) ist eine Schnittstelle (API = Application Programm Interface), die gewöhnlich zum Rendern einer Szene verwendet wird – oder, anders ausgedrückt, zum Erzeugen einfacherer bis komplexerer 3D-Grafiken (natürlich auch 2D). Die Bibliothek ist relativ einfach gehalten, und deren ca. 250 Routinen werden (wenn möglich) direkt in der Grafikkarte ausgeführt. Der Vorteil der direkten Ausführung in der Grafik-Hardware sorgt natürlich für einen enormen Geschwindigkeitsgewinn. Komplizierte Berechnungen nimmt Ihnen die API ab – so dass auch Mathematik-Laien recht Beachtliches damit vollbringen können. Was die OpenGL-Bibliothek nicht (direkt) bietet: 왘
keine Funktionen zur Kommunikation mit der grafischen Oberfläche (GUI = Graphical User Interface) des Betriebssystems – also kein Fenstersystem
왘
keine Funktionen zum Laden von Szenen-Beschreibungen oder Geometrie
왘
keine Druckfunktion
왘
keine Objekte wie Kugeln oder Tori (in der Praxis müssen diese mit Polygonen zusammengesetzt werden)
왘
keine Verwaltung einer Objekt-Hierarchie von Szenen
701
9.4
1542.book Seite 702 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
9.4.1
Spezifikation
Die aktuellste OpenGL-Spezifikation (hier 2.0) von OpenGL findet sich unter www.opengl.org und umfasst gewöhnlich die GL-, GLU- und GLX-Bibliothek. Verantwortlich für dieses Spezifikation ist das ARB-Entscheidungsgremium (ARB = Architecture Review Board), wo sich u. a. neben SGI Riesen wie Nvidia, ATI oder Intel befinden. Microsoft gehört mittlerweile nicht mehr zu diesem Gremium, was wohl auch mit der direkten Konkurrenz-Grafik-API DirectX zu tun hat. Irgendwie auch logisch, da die aktuelle Xbox 360 Videospiel-Konsole von Mircosoft auf DirectX setzt, die Konkurrenz mit Playstation 3 von Sony auf die OpenGL-API. Generell kommt heutzutage kein Hersteller von (Consumer-)Grafikkarten daran vorbei, diese Spezifikation zu implementieren, auch wenn Microsoft neuerdings versucht, dies zu stören (siehe den nächsten Abschnitt). Dazu gehören namhafte Hersteller wie Nvidia oder ATI, um nur einige zu nennen. Natürlich kann es sein, dass Ihre Grafikkarte noch der Spezifikation 1.4 oder 1.5 angehört – dies ist abhängig vom Herstelldatum und der damals aktuellen Spezifikation. MesaGL – Die OpenGL-Software-Emulation MesaGL ist eine freie Implementierung von OpenGL und eine reine SoftwareEmulation. Die aktuelle stabilste Version von MesaGL ist 6.0 und lässt sich über die Webseite www.mesa3d.org beziehen. MesaGL ist im Gegensatz zu OpenGL frei von der ARB-Lizensierung. Neben MesaGL gibt es selbstverständlich auch eine reine Microsoft-Implementierung von OpenGL auf Softwarebasis.
9.4.2
Anwendungsbeispiele in der Praxis von OpenGL
Dank der enormen Geschwindigkeit wird OpenGL besonders dort eingesetzt, wo man schnelles Neuzeichnen bei mittlerer Ausgabequalität benötigt. Dies wird angewendet bei CAD-Systemen, Animations-Vorschauen, Visualisierungen in Echtzeit, Modellern, Virtual Reality, Augmented Reality, Bildschirmschonern, VRML und natürlich (allerdings immer seltener) Computerspielen. Gerade in der Spielebranche hat die Verwendung von OpenGL den Vorteil, dass sich diese eventuell auf andere Systemen portieren lässt. Die besten Beispiele sind Ego-Shooter Unreal Tournament, Quake Arena, Heretic und noch einige mehr. Viele dieser Games sind sowohl unter Linux als auch MS Windows erhältlich. Allerdings wird eine solche Portierung generell nur bei Megahits realisiert, wo sich die Unkosten locker wieder einspielen.
702
1542.book Seite 703 Montag, 4. Januar 2010 1:02 13
OpenGL mit Qt
Platzhirsch DirectX Der Platzhirsch in der Spieleprogrammierung ist nach wie vor ganz klar die DirectXAPI von Microsoft. Natürlich mit dem Nachteil, dass DirectX eben nur unter MS-Windows verwendet werden kann.
Neben der Spielebranche macht auch die Filmbranche regen Gebrauch von den grafischen Effekten per Computer. Kaum ein Film kommt heute noch ohne den einen oder anderen Spezialeffekt aus. Die Effekte sind mittlerweile so realitätsnah, dass es dem menschliche Auge schwerfällt, zwischen Realität und Spezialeffekten zu unterscheiden. Wer sich zwischendurch mal die »Making of …« ansieht, die sich häufig als Bonusmaterial auf vielen DVDs wiederfinden, weiß, wovon ich rede.
9.4.3
Portabilität
OpenGL steht für alle gängigen Hardware-Plattformen wie bspw. Sun, SGI, HP-UX, Macintosh, MS-Windows (ab 9x/NT alle Versionen) oder Linux zur Verfügung. Unter den Unixen wurde mit OpenGL das X11-Protokoll nochmals erweitert und ist somit wie X11 selbst auch netzwerkfähig. Allerdings stehen Ihnen mit OpenGL nur Funktionen zur Definition der dreidimensionalen Geometrie zur Verfügung. Fenstermanagement (grafische Oberfläche) oder Ereignisverarbeitung (Maus, Tastatur, etc.) und dgl. sind nicht portabel. Hierbei müssen Sie entweder die API der grafischen Oberfläche des entsprechenden Betriebssystems (bspw. Win32-API, X11, etc.) verwenden oder aber eine Cross-PlatformBibliothek wie GLUT oder eben – ganz einfach: Sie greifen auf Qt zurück. Mehr auf der Buch-DVD Weiterführende Informationen rund um die Programmierung mit OpenGL finden Sie auf der Buch-DVD.
9.4.4
OpenGL mit Qt anwenden
Um OpenGL mit Qt zu verwenden, müssen Sie nur eine Klasse von QGLWidget ableiten und einige darin definierte virtuelle Methoden neu implementieren. Im Grunde können Sie das Verhältnis von QWidget und QPainter zu QGLWidget mit OpenGL zum Zeichnen vergleichen. QGLWidget wurde übrigens auch von QWidget abgeleitet.
703
9.4
1542.book Seite 704 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
Um überhaupt Anwendungen mit dem QtOpenGL-Modul und der OpenGLBibliothek verwenden zu können, ist wieder ein Eintrag in die Projektdatei (.pro) nötig: QT += opengl
Die folgenden drei in QGLWidget definierten virtuellen Methoden sollten beim Ableiten davon neu implementiert werden, um eine typische OpenGL-Anwendung zu erstellen: 왘
initializeGL() – Diese Methode wird vor dem ersten resizeGL() oder paintGL() ein einziges Mal aufgerufen. Darin wird gewöhnlich der Render-
kontext von OpenGL eingerichtet, Anzeigenlisten werden definiert und weitere Initialisierungen vorgenommen. 왘
resizeGL() – Diese Methode wird nach dem Aufruf von initializeGL() und noch vor paintGL() aufgerufen. Darin werden der Viewport, die Projektion und andere Einstellungen von OpenGL eingerichtet. Diese Methode wird außerdem jedesmal aufgerufen, wenn die Größe verändert wurde.
왘
paintGL() – Die Methode wird immer aufgerufen, wenn das Widget neu
gezeichnet wird. Darin wird die OpenGL-Szene gerendert. Auch hierbei sind die Ähnlichkeiten zum Ereignishandler paintEvent() recht auffällig. Und auch hier können Sie das Neu-Zeichnen der OpenGL-Szene (bspw. mit einem QTimer, um eine Szene zu animieren) von Hand mit updateGL() erreichen (ähnlich wie update() beim paintEvent()). Das Grundgerüst einer aus QGLWidget abgeleiteten Unterklasse sieht somit wie folgt aus: class MyGLDrawer : public QGLWidget { Q_OBJECT public: MyGLDrawer(QWidget *parent) : QGLWidget(parent) {} protected: // Renderkontexst einrichten, Anzeigelisten definieren, ... void initializeGL() { ... glClearColor(0.0, 0.0, 0.0, 0.0); glEnable(GL_DEPTH_TEST); ... } // Viewport setzen, Projektion setzen, ... void resizeGL(int w, int h) { glViewport(0, 0, (GLint)w, (GLint)h); ...
704
1542.book Seite 705 Montag, 4. Januar 2010 1:02 13
OpenGL mit Qt
glFrustum(...); ... } // OpenGL-Szene rendern void paintGL() { ... glRotatef(...); glMaterialfv(...); glBegin(GL_QUADS); glVertex3f(...); glVertex3f(...); ... glEnd(); ... } };
Natürlich will ich Sie hier nicht mit einem Grundgerüst abspeisen und noch ein einfaches Beispiel in der Praxis zeigen. Im Beispiel wird ein 3D-Würfel gerendert, welchen Sie mit gedrückter Maustaste und -Bewegung auch drehen können. Anstelle des Ereignishandles für die Maus könnten Sie natürlich auch den Ereignishandler timerEvent() neu implementieren, womit Sie die Szene dauerhaft animieren. Zunächst das Grundgerüst: 00 01 02 03
// beispiele/opengl/mainWindow.h #ifndef WINDOW_H #define WINDOW_H #include
04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
class CubeGL : public QGLWidget { Q_OBJECT public: CubeGL(QWidget *parent = 0); protected: void initializeGL(); void resizeGL(int width, int height); void paintGL(); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); private: void draw(); GLfloat rotationX; GLfloat rotationY; GLfloat rotationZ; GLuint texture[1];
705
9.4
1542.book Seite 706 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
20 QPoint lastPos; 21 }; 22 #endif
Jetzt zur Implementierung des Codes. Alle Funktionen, die hierbei mit gl...() beginnen, sind echte OpenGL-Funktionen, also unabhängig vom Qt-Framework. 00 01 02 03
// beispiele/opengl/mainWindow.cpp #include #include #include "mainWindow.h"
04 CubeGL::CubeGL(QWidget *parent) : QGLWidget(parent) { 05 setFormat(QGLFormat( QGL::DoubleBuffer | QGL::DepthBuffer) ); 06 rotationX = –21.0; 07 rotationY = –57.0; 08 rotationZ = 0.0; 09 } 10 void CubeGL::initializeGL() { 11 glShadeModel(GL_SMOOTH); 12 glClearColor(0.0f, 0.0f, 0.0f, 0.5f); 13 glClearDepth(1.0f); 14 glEnable(GL_DEPTH_TEST ); 15 glDepthFunc(GL_LEQUAL); 16 glEnable(GL_CULL_FACE); 17 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 18 } 19 void CubeGL::resizeGL(int width, int height) { 20 glViewport(0,0,width,height); 21 glMatrixMode(GL_PROJECTION); 22 glLoadIdentity(); 23 gluPerspective(45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 100.0f); 24 glMatrixMode(GL_MODELVIEW); 25 glLoadIdentity(); 26 } 27 void CubeGL::paintGL() { 28 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 29 draw(); 30 }
706
1542.book Seite 707 Montag, 4. Januar 2010 1:02 13
OpenGL mit Qt
31 void CubeGL::mousePressEvent(QMouseEvent *event) { 32 lastPos = event->pos(); 33 } 34 void CubeGL::mouseMoveEvent(QMouseEvent *event) { 35 GLfloat dx = GLfloat( event->x() – lastPos.x()) / width(); 36 GLfloat dy = GLfloat( event->y() – lastPos.y()) / height(); 37 if (event->buttons() & Qt::LeftButton) { 38 rotationX += 180 * dy; 39 rotationY += 180 * dx; 40 updateGL(); 41 } 42 else if (event->buttons() & Qt::RightButton) { 43 rotationX += 180 * dy; 44 rotationZ += 180 * dx; 45 updateGL(); 46 } 47 lastPos = event->pos(); 48 } 49 void CubeGL::draw() { 50 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 51 glLoadIdentity(); 52 glTranslatef(0.0f,0.0f,-5.0f); 53 glRotatef(rotationX,1.0f,0.0f,0.0f); 54 glRotatef(rotationY,0.0f,1.0f,0.0f); 55 glRotatef(rotationZ,0.0f,0.0f,1.0f); 56 // Würfelzeichnen beginnen 57 glBegin(GL_QUADS); 58 // Vorderseite 59 glColor3f(1.0f,1.0f,0.0f); // gelb 60 glTexCoord2f(0.0f, 0.0f); 61 glVertex3f(-1.0f, –1.0f, 1.0f); 62 glTexCoord2f(1.0f, 0.0f); 64 glVertex3f( 1.0f, –1.0f, 1.0f); 65 glTexCoord2f(1.0f, 1.0f); 66 glVertex3f( 1.0f, 1.0f, 1.0f); 67 glTexCoord2f(0.0f, 1.0f); 68 glVertex3f(-1.0f, 1.0f, 1.0f); 69 // Rückseiten 70 glTexCoord2f(1.0f, 0.0f); 71 glVertex3f(-1.0f, –1.0f, –1.0f); 72 glTexCoord2f(1.0f, 1.0f);
707
9.4
1542.book Seite 708 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
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
708
glVertex3f(-1.0f, 1.0f, –1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, –1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, –1.0f, –1.0f); // Obere Seite glColor3f(0.0f,0.0f,1.0f); // blau glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, –1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, –1.0f); // Untere Seite glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, –1.0f, –1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, –1.0f, –1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, –1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, –1.0f, 1.0f); // Rechte Seite glColor3f(0.0f,1.0f,0.0f); // grün glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, –1.0f, –1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, –1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, –1.0f, 1.0f); // Linke Seite glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, –1.0f, –1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, –1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, –1.0f); glEnd(); rotationX+=0.4f; // X Achsen Rotation
1542.book Seite 709 Montag, 4. Januar 2010 1:02 13
OpenGL mit Qt
118 rotationY+=0.6f; // Y Achsen Rotation 119 rotationZ+=0.1f; // Z Achsen Rotation 120 }
Jetzt noch das Hauptprogramm, in dem natürlich noch geprüft wird, ob das System OpenGL unterstützt. Wenn nicht, erscheint ein entsprechender NachrichtenDialog. 00 01 02 03
// beispiele/opengl/main.cpp #include #include #include "mainWindow.h"
04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 if (!QGLFormat::hasOpenGL()) { 07 QMessageBox::critical( 08 0, QObject::tr("Kein OpenGL"), 09 QObject::tr("Auf diesem System gibt es" " kein OpenGL!") ); 10 return 1; 11 } 12 CubeGL cubeGL; 13 cubeGL.setWindowTitle(QObject::tr("CubeGL")); 14 cubeGL.resize(300, 300); 15 cubeGL.show(); 16 return app.exec(); 17 }
Das Programm bei der Ausführung:
Abbildung 9.11
Ein 3D-Würfel mit OpenGL und Qt
709
9.4
1542.book Seite 710 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
9.5
Vektorgrafik – QSvgWidget
Qt unterstützt seit der Version 4.1 auch das Scalable Vector Graphics-Format (oder kurz meistens SVG). SVG ist ein Standard zur Beschreibung zweidimensionaler Vektorgrafiken in der XML-Syntax. SVG wurde im September 2001 vom W3C als Empfehlung veröffentlicht, und einen Großteil des Sprachumfangs können die meisten gängien Webbrowser – vom Internet Explorer abgesehen – von Haus aus darstellen. Beim Internet Explorer ist die Darstellung durch ein Plugin wie den SVG-Viewer von Adobe möglich. Auch Animationen werden von SVG unterstützt. Manipulationen des SVG-DOM sind mithilfe eingebetteter Funktionen via Skriptsprachen möglich. Zurzeit wird die Kombination und Integration von Standards des W3C untereinander in der Arbeitsgruppe Compound Document Formats erarbeitet. Die implementierte Version von Qt unterstützt allerdings derzeit noch keine Manipulationen der SVG-Grafiken über das SVG-DOM. Auch die als JavaScript für SVG bekannte ECMA-Scriptsprache wird noch nicht von Qt unterstützt. Qt unterstützt zurzeit nur SVG-Tiny und SVG-Basic mit den Versionen 1.1 und 1.2. SVG-Dateiformat SVG ist ein XML-basiertes Dateiformat, weshalb SVG-Dateien mithilfe eines Texteditors bearbeitet werden können. Es gibt aber auch spezielle Programme zur Bearbeitung, zum Beispiel die freien Vektorgrafik-Programme Sodipodi und Inkscape. Die Dateiendung ist .svg und der MIME-Typ image/svg+xml.
Um Anwendungen mit den Klassen von QtSvg zu verwenden, muss auch hier die Projekt-Datei (.pro) wie folgt angepasst werden: QT += svg
Die Include-Datei mit den Definitionen wird mit folgender Direktive angegeben: #include
Die Klasse zum Anzeigen von SVG-Dateien lautet QSvgWidget. Diese Klasse lässt sich genauso wie die Anzeige eines Bitmaps mit QLabel verwenden. Die SVGDatei zum Anzeigen wird entweder beim Konstruktor mit angegeben oder kann nachträglich mit der zweifach überladenen Version von QSvgWidget::load() geladen werden. Neben der Version mit den Dateinamen gibt es eine load()Version mit einem QByteArray. Hierzu ein einfaches Beispiel, wie eine simple SVG-Datei angezeigt wird. Das Grundgerüst:
710
1542.book Seite 711 Montag, 4. Januar 2010 1:02 13
Vektorgrafik – QSvgWidget
00 01 02 03
// beispiele/svg/mainWindow.h #ifndef WINDOW_H #define WINDOW_H #include
04 05 06 07 08 09
class SVGWidget : public QSvgWidget { Q_OBJECT public: SVGWidget(QWidget *parent = 0); }; #endif
Der Code zum Grundgerüst: 00 01 02 03
// beispiele/svg/mainWindow.cpp #include #include #include "mainWindow.h"
04 SVGWidget::SVGWidget(QWidget *parent):QSvgWidget(parent){ 05 load(tr("./images/SVG-Logo.svg")); 06 }
Jetzt noch das Hauptprogramm: 00 // beispiele/svg/main.cpp 01 #include 02 #include "mainWindow.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 SVGWidget sVGWidget; 06 sVGWidget.setWindowTitle( QObject::tr("SVGWidget – Demo") ); 07 sVGWidget.resize(300, 300); 08 sVGWidget.show(); 09 return app.exec(); 10 }
Das ausgeführte Programm sehen Sie in Abbildung 9.12. Wollen Sie das Rendern einer SVG-Datei mehr kontrollieren, können Sie die Klasse QSvgRenderer verwenden. Mit dieser Klasse können Sie auf anderen Zeichengeräten wie QImage und QGLWidget zeichnen. Um von QSvgWidget einen Zeiger auf die SVG-Datei für das Rendern zurückzugeben, kann die Methode QSvgWidget::renderer() verwendet werden. Damit ist es auch möglich, ani-
711
9.5
1542.book Seite 712 Montag, 4. Januar 2010 1:02 13
9
Grafik und Drucken
mierte SVG-Dateien anzuzeigen. Ein entsprechendes Beispiel finden Sie in den von Qt mitgelieferten Demos (SVG Viewer) (siehe auch Buch-DVD).
Abbildung 9.12
712
Eine SVG-Datei wird angezeigt (SVG-Logo).
1542.book Seite 713 Montag, 4. Januar 2010 1:02 13
XML (Abk. für Extensible Markup Language, dt. »Erweiterbare Auszeichnungssprache«) ist eine häufig verwendete Auszeichnungssprache für die Darstellung hierarchisch strukturierter Daten in Form einer Textdatei. In der IT-Branche wird immer häufiger auf das Dateiformat (besonders im Internet) zurückgegriffen, weil sich damit der Austausch von Daten zwischen unterschiedlichen Systemen problemlos realisieren lässt. Bekannte Beispiele für XML-Sprachen sind: RSS, MathML, GraphML, XHTML, Scalable Vector Graphics, XML-Schema etc.
10
XML
Qt bringt mit dem QtXml-Modul ebenfalls zwei APIs zum Lesen von XMLDateien mit: 왘
SAX – Eine SAX-API (Simle API for XML) repräsentiert ein XML-Dokument als sequentiellen Datenstrom und ruft für im Standard definierte Ereignisse (bspw. wenn es auf einen sich öffnenden oder schließenden Tag stößt) vorgegebene Rückruffunktionen auf. Dadurch können Anwendungen, die SAX verwenden, eigene Methoden als Rückruffunktionen registrieren und die XMLDaten auswerten. Bei Qt werden die Parsing-Ereignisse direkt über eine virtuelle Methode (es müssen also Methoden reimplementiert werden) and die Anwendung gemeldet. SAX arbeitet auf einer niedrigeren Ebene als DOM, weshalb diese API gewöhnlich schneller ist und sich gut für das Lesen und Auffinden von Tags bei umfangreichen Dokumenten eignen.
왘
DOM – Ein DOM-API (Document Object Model) repräsentiert ein XML-Dokument als Baumstruktur und gewährt wahlfreien Zugriff auf die einzelnen Bestandteile der Baumstruktur. DOM erlaubt außer dem Lesen von XMLDokumenten auch die Manipulation der Baumstruktur und das Zurückschreiben der Baumstruktur in ein XML-Dokument. DOM wird von den meisten Webbrowsern verwendet, weil man hierbei an jedes beliebge Element im XML-Baum kommt und es auslesen kann. Im Gegensatz zu SAX ist DOM wesentlich komfortabler zu verwenden, allerdings auch wesentlicher teurer im Speicher- und Geschwindigkeitsbereich. DOM wird in verschiedene Levels eingeteilt, wobei Qt Level 2 unterstützt. Mit DOM kann man im Gegensatz zu SAX außerdem den XML-Code schreiben.
713
1542.book Seite 714 Montag, 4. Januar 2010 1:02 13
10
XML
Um eine der beiden APIs zu verwenden, muss auch hier die Projektdatei (.pro) um folgenden Eintrag erweitert werden: QT += xml
Um hierbei gleich alle Implementierungen von Klassenimplementierungen zu verwenden, kann man den Meta-Include QtXml einsetzen: #include
Mehr über XML Sofern Sie mit dem XML und den APIs SAX oder DOM nicht vertraut sind, finden Sie auf der Buch-DVD einige interessante Weblinks und Informationen, um sich intensiver damit zu befassen. Die Beschäftigung mit XML lohnt sich.
10.1
SAX-API von Qt verwenden
Der Standard zum Lesen ist die SAX-API, die weitgehend der Referenzimplementierung von JAVA entspricht. Allerdings mussten einige Namen den Qt-Konventionen angepasst werden. Mehr über SAX Mehr Informationen zum SAX und deren Spezifikation finden Sie unter http:// www.saxproject.de.
Hierzu ein kurzer Durchlauf, wie die SAX-API einen XML-Quelltext parst. Nehmen wir folgenden XML-Ausschnitt: <dokument> Ein einfaches Zitat.
In diesem Beispiel löst der SAX-Parser folgende (vereinfachte) Ereignisse aus: startDocument(); // Es wurde angefangen zu lesen. startElement("dokument"); // Starttag wurde gefunden. startElement("zitat"); // Starttag wurde gefunden. characters("Ein einfaches Zitat"); // Text wurde gefunden. endElement("zitat"); // Endtag wurde gefunden. endElement("dokument") // Endtag wurde gefunden. endDocument(); // fertig mit dem Lesen
714
1542.book Seite 715 Montag, 4. Januar 2010 1:02 13
SAX-API von Qt verwenden
Die hier gezeigten virtuellen Methoden sind alle in der Klasse QXmlContentHandler deklariert (allerdings mit mehreren Parametern). Damit Sie diese Ereignisse auch abfangen können, müssen sie reimplementiert werden. SAX-Parser-Ereignisse Es sollte auf der Hand liegen, dass diese SAX-Parser-Ereignisse nichts mit Ereignissen der Tastatur oder Maus gemein haben.
Die SAX-API bietet ein ganzes Geschwader solcher Handlerklassen. Die meisten von ihnen deklarieren rein virtuelle Methoden und werden verwendet, um unterschiedliche Ergebnisse beim Parsen von XML auszuwerten. In der Praxis werden allerdings meistens nur die Klassen QXmlDefaultHandler und QXmlErrorHandler benötigt. Die Klasse QXmlDefaultHandler behandelt die Tagund Text-Ergebnisse und erbt zudem von allen anderen Handlerklassen deren virtuelle Methoden. Ein solches Modell ist ziemlich ungewöhnlich – wir erwähnten ja bereits, dass man sich hier an die Implementierung des Java-Models gehalten hat. Die Klasse QXmlErrorHandler wird hingegen benötigt, wenn beim Parsen ein syntaktischer Fehler auftritt. Natürlich gibt es auch Klassen wie QXmlDTDHandler und QXmlDeclHandler, die Ereignisse bei den DTD (Document Type Definition) einer XML-Datei auslösen. Für lexikalische Ereignisse wird die Handlerklasse QXmlLexicalHandler und für XML-Entities die Klasse QXmlEntityResolver verwendet. Diese Handler benötigt man allerdings eher selten. Hierzu eine einfache XML-Datei, die einen Ausschnitt über den Fortschritt dieses Buches beim Schreiben darstellen soll, wie ich es immer gerne verwende, um einen Überblick zu behalten: <eintrag kapitel="1. Einstieg in Qt"> <seiten>7 80 % <eintrag kapitel="1.1 Was ist Qt"> <seiten>1 100 % <eintrag kapitel="1.2 Lizensierung"> <seiten>1 100 % <eintrag kapitel="1.3 Qt installieren">
715
10.1
1542.book Seite 716 Montag, 4. Januar 2010 1:02 13
10
XML
<seiten>2 25 % <eintrag kapitel="1.3.1 Windows"> <seiten>0 0 % <eintrag kapitel="1.3.1 Linux/Unix"> <seiten>2 25 % <eintrag kapitel="2.Signale und Slots"> <seiten>31 100 % <eintrag kapitel="2.1 Grundlagen"> <seiten>4 100 %
Diese XML-Datei soll jetzt mit der SAX-API von Qt eingelesen und über ein TreeWidget dargestellt werden (siehe Abbildung 10.1).
Abbildung 10.1
10.1.1
Die XML-Datei in einem QtreeWidget
Default-Handler implementieren
Um unsere XML-Datei mit SAX zu lesen, benötigen wir zunächst einen StandardHandler, denn wir als Unterklasse von QXmlDefaultHandler erstellen. Unsere neue Klasse erbt somit von QXmlDefaultHandler und fängt die Ereignisse startElement(), endElement() und characters() aus der Klasse QXmlContentHandler und fatalError() von QXmlErrorHandler ab. Hierzu das Grundgerüst:
716
1542.book Seite 717 Montag, 4. Januar 2010 1:02 13
SAX-API von Qt verwenden
00 01 02 03 04
// beispiele/sax/mainWindow.h #ifndef SAX_H #define SAX_H #include #include
05 class SAX : public QXmlDefaultHandler { 06 public: 07 SAX(QTreeWidget *tree); 08 bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &attributes); 09 bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); 10 bool characters(const QString &str); 11 bool fatalError(const QXmlParseException &exception); 12 private: 13 QTreeWidget *treeWidget; 14 QTreeWidgetItem *item; 15 QString itemText; 16 }; 17 18 19 20 21
class mainWindow : public QTreeWidget { public: mainWindow(); }; #endif
Jetzt die Implementierung des Codes mit einer anschließenden Erläuterung: 00 // beispiele/sax/mainWindow.cpp 01 #include 02 #include "mainWindow.h" 03 SAX::SAX(QTreeWidget *tree) { 04 treeWidget = tree; 05 item = 0; 06 } 07 bool SAX::startElement( 08 const QString &, const QString &, 09 const QString &qName, const QXmlAttributes &attributes) {
717
10.1
1542.book Seite 718 Montag, 4. Januar 2010 1:02 13
10
XML
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 }
if (qName == "eintrag") { if (item) { item = new QTreeWidgetItem(item); } else { item = new QTreeWidgetItem(treeWidget); } item->setText(0, attributes.value("kapitel")); } else if (qName == "seiten") { itemText.clear(); } else if (qName == "fertig") { itemText.clear(); } return true;
27 bool SAX::characters(const QString &str) { 28 itemText += str; 29 return true; 30 } 31 bool SAX::endElement( 32 const QString &, const QString &, const QString &qName) { 33 if (qName == "eintrag") { 34 item = item->parent(); 35 } 36 else if (qName == "seiten") { 37 if (item) { 38 QString allPages = item->text(1); 39 allPages += itemText; 40 item->setText(1, allPages); 41 item->setTextAlignment ( 1, Qt::AlignCenter ); 42 } 43 } 44 else if (qName == "fertig") { 45 if (item) { 46 QString allPages = item->text(2); 47 allPages += itemText; 48 item->setText(2, allPages); 49 item->setTextAlignment ( 2, Qt::AlignCenter ); 50 if( itemText == "0 %" ) 51 item->setBackground ( 2, QBrush(Qt::red) );
718
1542.book Seite 719 Montag, 4. Januar 2010 1:02 13
SAX-API von Qt verwenden
52 53 54 55 56 57 58 59 }
else if( itemText == "100 %" ) item->setBackground ( 2, QBrush(Qt::green) ); else item->setBackground ( 2, QBrush(Qt::yellow)); } } return true;
60 bool SAX::fatalError(const QXmlParseException &e) { 61 QMessageBox::warning(0, QObject::tr("SAX Handler"), QObject::tr("Fehler in Zeile %1, Spalte %2:\n%3.") .arg(e.lineNumber()).arg(e.columnNumber()) .arg(e.message())); 62 return false; 63 } 64 mainWindow::mainWindow() { 65 QStringList labels; 66 labels -Tags, der hier "kapitel" lautet (bspw. <eintrag kapitel="1. Einstieg in Qt">). Diesen Wert erhalten wir durch die Methoden QXmlAttributes::value() und verwenden ihn in Zeile 17, um den Text für des QTreeWidget zu setzen.
Wenn der Tag <seiten> oder lautet, setzen wir den String itemText auf einen leeren String. Der String von itemText wird mit dem Text zwischen den Tags <seiten> und bzw. und verwendet. Durch die Rückgabe von true am Ende der reimplementierten Methode startElement() teilen Sie der SAX-API mit, mit der Analyse der XML-Datei fortzufahren. Geben Sie false zurück, wird die Analyse abgebrochen. Der eigentliche String zwischen zwei Tags wird in der reimplementierten Methode characters() eingelesen (Zeile 27 bis 30). Die Zeichen werden immer an den String itemText gehängt. Auch hier wird, wenn kein Fehler aufgetreten ist, true zurückgegeben, so dass man mit der Analyse der XML-Datei fortfahren kann.
720
1542.book Seite 721 Montag, 4. Januar 2010 1:02 13
DOM-API von Qt verwenden
Der Ereignishandler endElement() (Zeile 31 bis 59) wird aufgerufen, wenn ein schließender Tag gelesen wurde. Wurde bspw. der Tag gelesen, aktualisieren wir nur die Variable item, damit diese auf das aktuelle ElternQTreeWidgetItem zeigt (Zeile 34). Dadurch erhält item auch den Wert, der gesetzt wurde, bevor der entsprechende öffnende Tag <eintrag> gelesen wurde. Anders hingegen, wenn der schließende Tag lautet (Zeile 36 bis 42). Dann fügen wir die Seitenanzahl zur Liste der Spalte 1 hinzu. Der aktuelle Inhalt zwischen <seiten> und wurde im Ereignishandler characters() gelesen. Gleiches geschieht beim schließenden Tag (Zeile 44 bis 56) wo wir den aktuellen Inhalt zwischen den Tags und in der Liste der Spalte 2 hinzufügen. Zusätzlich setzen wir den Hintergrund dieser Spalte noch mit einer bestimmten Farbe: Grün, wenn das Kapitel komplett fertig ist, Rot, wenn noch gar kein Text vorhanden ist; in allen anderen Fällen – wenn der Text nur tlw. fertig ist – verwenden wir Gelb. Auch hier geben wir am Ende true zurück, um dem Parser mitzuteilen, dass er mit der Analyse fortfahren kann. Der Ereignishandler fatalError() (Zeile 60 bis 63) wird aufgerufen, wenn beim Lesen der XML-Datei ein Fehler aufgetreten ist. Ich habe bspw. in Zeile 20 Folgendes geschrieben ; der Ereignishandler wird folgendermaßen aktiv:
Abbildung 10.2
10.2
Ereignishandler fatalError()
DOM-API von Qt verwenden
Die vom W3C (World Wide Web Consortium) entwickelte DOM-API stellt die bequemere Art, XML-Dateien einzulesen, zur Verfügung. Bei der DOM-API wird eine XML-Datei als ein Baum im Speicher dargestellt. Durch diesen DOM-Baum können Sie sich beliebig bewegen, um bspw. nach beliebigen Elementen zu suchen. Neben der Möglichkeit, aus einer XML-Datei einen DOM-Baum zu generieren, gibt es die Möglichkeit, einen DOM-Baum in eine XML-Datei zu schreiben. Das Ganze nun ein wenig detaillierter: Die komplette XML-Datei wird durch die Klasse QDomDocument dargestellt. Die Klasse QDomElement repräsentiert die einzel-
721
10.2
1542.book Seite 722 Montag, 4. Januar 2010 1:02 13
10
XML
nen Elemente des DOM-Baumes. Die Elemente selbst können in ihren Start-Tags natürlich wieder Attribute enthalten. Zwischen den Elementen (Start- und EndTags) können sich erneut Daten wie Text oder ggf. weitere Kinderelemente befinden. Alle Knotenarten in der DOM-API von Qt haben das Präfix QDom. Alle Element im DOM-Baum sind somit auch DOM-Knoten (QDomNodes). Beim Betrachten der Klassenreferenz von QDomNodes werden Sie feststellen, dass es neben den Elementen weitere Knoten gibt. So kann ein Knoten vom Typ QDomElement weitere Elemente und zusätzliche Knoten enthalten. Verwenden wir bspw. folgende XML-Datei: <dokument version="1.2"> Ein einfaches Zitat. Text1234 Text5678
Aus dieser XML-Datei generiert Qt den folgenden DOM-Baum: QDomDocument
QDomElement +<dokument>
QDomAttr +version
QDomAttr +show
QDomElement +
QDomText +Ein einfaches ...
QDomElement +
QDomElement +
QDomText +Text1234
Abbildung 10.3
QDomElement +
QDomText +Text1234
Ein XML-generierter DOM-Baum mit Qt
Auch zur DOM-API von Qt soll das Beispiel geschrieben werden, welches unsere XML-Datei (buch.xml) aus dem Abschnitt der SAX-API, lesen kann. Außerdem wurde die Funktionalität hinzugefügt, den Text-Inhalt der XML-Datei zu ändern
722
1542.book Seite 723 Montag, 4. Januar 2010 1:02 13
DOM-API von Qt verwenden
und zu speichern (auch unter einem anderen Dateinamen). Hierzu zunächst das Grundgerüst: 00 01 02 03 04 05 06 07 08
// beispiele/dom/domparser.h #ifndef DOM_H #define DOM_H #include #include #include #include #include #include
09 10 11 12 13 14 15 16
22 23 24
class DOMTree : public QTreeWidget { Q_OBJECT public: DOMTree(QWidget *parent = 0); bool read(QIODevice *device); bool write(QIODevice *device); private slots: void updateDomElement( QTreeWidgetItem *item, int column ); private: void parseFolderElement( const QDomElement &element, QTreeWidgetItem *parentItem = 0 ); QTreeWidgetItem *createItem( const QDomElement &element, QTreeWidgetItem *parentItem = 0 ); QDomDocument domDocument; QHash domElementForItem; QIcon folderIcon; QIcon bookmarkIcon; };
25 26 27 28 29 30 31
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); private slots: void open(); void saveAs();
17 18
19
20 21
723
10.2
1542.book Seite 724 Montag, 4. Januar 2010 1:02 13
10
XML
32 private: 33 void createActions(); 34 void createMenus(); 35 DOMTree *domTree; 36 QMenu *fileMenu; 37 QAction *openAct, *saveAsAct, *exitAct; 38 }; 39 #endif
Jetzt die Implementierung des Codes mit anschließender Erläuterung: 00 01 02 03
// beispiele/dom/domparser.cpp #include #include #include "domparser.h"
04 DOMTree::DOMTree(QWidget *parent) : QTreeWidget(parent) { 05 QStringList labels; 06 labels text(1)); 49 newTitleElement.appendChild(newTitleText); 50 element.replaceChild( newTitleElement, oldTitleElement); 51 } 52 else { 53 QDomElement oldTitleElement = element.firstChildElement("fertig");
725
10.2
1542.book Seite 726 Montag, 4. Januar 2010 1:02 13
10
XML
54
QDomElement newTitleElement = domDocument.createElement("fertig"); QDomText newTitleText = domDocument.createTextNode(item->text(2)); newTitleElement.appendChild(newTitleText); element.replaceChild( newTitleElement, oldTitleElement);
55 56 57 58 59 60 }
} }
61 void DOMTree::parseFolderElement( const QDomElement &element, QTreeWidgetItem *parentItem) { 62 QTreeWidgetItem *item=createItem(element, parentItem); 63 item->setFlags(item->flags() | Qt::ItemIsEditable); 64 item->setIcon(0, folderIcon); 65 item->setText(0, element.attribute("kapitel")); 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 }
QDomElement child = element.firstChildElement(); while (!child.isNull()) { if (child.tagName() == "eintrag") { parseFolderElement(child, item); } else if (child.tagName() == "seiten") { item->setFlags(item->flags()|Qt::ItemIsEditable); item->setText(1, child.text());//title); } else if (child.tagName() == "fertig") { item->setFlags(item->flags()|Qt::ItemIsEditable); item->setText(2, child.text());//title); } child = child.nextSiblingElement(); }
82 QTreeWidgetItem *DOMTree::createItem( const QDomElement &element, QTreeWidgetItem *parentItem) { 83 QTreeWidgetItem *item; 84 if (parentItem) { 85 item = new QTreeWidgetItem(parentItem); 86 } 87 else {
726
1542.book Seite 727 Montag, 4. Januar 2010 1:02 13
DOM-API von Qt verwenden
88 89 90 91 92 }
item = new QTreeWidgetItem(this); } domElementForItem.insert(item, element); return item;
93 MainWindow::MainWindow() { 94 domTree = new DOMTree; 95 setCentralWidget(domTree); 96 createActions(); 97 createMenus(); 98 statusBar()->showMessage(tr("Bereit ...")); 99 setWindowTitle(tr("DOM-API Demo")); 100 resize(480, 320); 101 } 102 void MainWindow::open() { 103 QString fileName = QFileDialog::getOpenFileName( this, tr("XML-Datei öffnen"), QDir::currentPath(),tr("XML-Datei (*.xml)")); 105 if (fileName.isEmpty()) 106 return; 107 QFile file(fileName); 108 if (!file.open(QFile::ReadOnly | QFile::Text)) { 109 QMessageBox::warning( this, tr("Fehler beim Lesen"), tr("Kann die Datei %1 nicht lesen:\n%2.") .arg(fileName).arg(file.errorString())); 110 return; 111 } 112 if (domTree->read(&file)) 113 statusBar()->showMessage(tr("Datei geladen"),3000); 114 } 115 void MainWindow::saveAs() { 116 QString fileName = QFileDialog::getSaveFileName( this, tr("XML-Datei speichern"), QDir::currentPath(), tr("XML Datei (*.xml)")); 117 if (fileName.isEmpty()) 118 return; 119 120
QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) {
727
10.2
1542.book Seite 728 Montag, 4. Januar 2010 1:02 13
10
XML
121
122 123 124 125
QMessageBox::warning( this, tr("Fehler beim Lesen"), tr("Kann die Datei %1 nicht lesen:\n%2.") .arg(fileName).arg(file.errorString())); return; } if (domTree->write(&file)) statusBar()->showMessage( tr("Datei gespeichert"), 3000);
126 } 127 void MainWindow::createActions() { 128 openAct = new QAction(tr("&Öffnen..."), this); 129 openAct->setShortcut(tr("Ctrl+O")); 130 connect( openAct, SIGNAL(triggered()), this, SLOT(open()) ); 131 saveAsAct=new QAction(tr("&Speichern unter..."),this); 132 saveAsAct->setShortcut(tr("Ctrl+S")); 133 connect( saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()) ); 134 exitAct = new QAction(tr("&Ende"), this); 135 exitAct->setShortcut(tr("Ctrl+Q")); 136 connect( exitAct, SIGNAL(triggered()), this, SLOT(close())); 137 } 138 void MainWindow::createMenus() { 139 fileMenu = menuBar()->addMenu(tr("&Datei")); 140 fileMenu->addAction(openAct); 141 fileMenu->addAction(saveAsAct); 142 fileMenu->addAction(exitAct); 143 menuBar()->addSeparator(); 144 }
Auf die gewöhnlichen GUI-Elemente gehen wir hier nicht mehr ein. Nach den vorangegangenen Kapiteln sollte der Leser dazu selbst in der Lage sein. So richtig los mit dem Einlesen geht es, wenn der Anwender über das Menü Datei eine XML-Datei öffnet. Dabei wird ein QFile-Objekt als Ein-/Ausgabegerät für unsere Methode DOMTree::read() (Zeile 10 bis 32) als Parameter verwendet. Der Aufruf erfolgt in der Zeile 112. Um die vom QIODevice bereitgestellte Datei zu lesen, rufen wir, in Verbindung mit dem QDomDocument-Objekt, die Methode setContent() auf (Zeile 14). Falls das Gerät zum Lesen noch nicht geöffnet ist, wird es damit automatisch geöffnet. Tritt hierbei schon ein Fehler auf, zeigt dies die Nachrichten-Box an, und wir geben false zurück.
728
1542.book Seite 729 Montag, 4. Januar 2010 1:02 13
DOM-API von Qt verwenden
Der nächste Aufruf von documentElement() in Zeile 18 (bis 22) für das QDomDocument dient als Überprüfung, ob das nächste und hier auch einzige untergeordnete Element ein -Tag ist. Trifft dies nicht zu, können wir diese XML-Datei nicht parsen, weil unser Programm nicht dafür geschrieben wurde. Da wir ja jederzeit ein anderes XML-Dokument öffnen und lesen können, löschen wir alle bis dahin vorhandenen Elemente und Auswahlen von Tree-Widget (Zeile 23). Ebenso trennen wir alle bisherigen Signal-Slot-Verbindungen, wenn ein Element im Baum (Tree) verändert wurde (Zeile 24). In Zeile 25 ermitteln wir das nächste vom -Tag untergeordnete Element <eintrag> und rufen nach jedem gefundenen <eintrag>-Tag parseFolderElement() zum Analysieren auf (Zeile 27). Dieser Vorgang zwischen den Zeilen 26 bis 29 wird so lange wiederholt, bis kein <eintrag>-Tag mehr vorhanden ist. In der Zeile 30 stellen wir noch eine Signal-Slot-Verbindung her, womit die einzelnen Elemente im Tree-Widget editiert werden können und auch der DOMBaum aktualisiert wird. Dieses Aktualisieren ist notwendig, damit der DOMBaum auch mit entsprechenden Daten geschrieben wird. Wenn alles glatt verlaufen ist, gibt diese Methode true zurück, und der DOM-Baum wird korrekt angezeigt. Jetzt zur Methode DOMTree::parseFolderElement() (Zeile 61 bis 81), die bei der XML-Datei alle Tags zwischen <eintrag> und analysiert. Zunächst wurde das Anlegen eines neuen Tree- bzw. Unter-Tree-Widgets in die Methode DOMTree::createItem() (Zeile 82 bis 92) ausgelagert. Die Funktionalität entspricht in etwa dem Beispiel der SAX-API. Das neue Element setzen wir in den Zeilen 63 bis 65 zunächst als editierbar, übergeben ein Icon und verwenden den Text vom <eintrag>-Attribut "kapitel". Dann holen wir uns erneut mit firstChildElement() das nächste Element unterhalb von <eintrag>. Der erste Fall (Zeile 68 bis 70) behandelt die Möglichkeit, dass ein weiteres <eintrag>-Tag verschachtelt wurde. In diesem Fall starten wir die Methode parseFolderElement() erneut mit einer Rekursion. Der zweite Fall überprüft, ob es sich bei dem Element um ein <seiten>-Tag handelt (Zeile 71 bis 74). Trifft dies zu, fügen wir zur zweiten Spalte unseres Tree-Widgets die Anzahl der Seiten (also den Text, der sich zwischen <seiten> und befindet) als editierbar hinzu. Gleiches machen wir, wenn ein -Tag vorhanden ist (Zeile 75 bis 78), nur dass wir den Inhalt zur dritten Spalte hinzufügen. Auf farbliches Erkenntlichmachen wurde in diesem Beispiel verzichtet. Vor dem Ende der Schleife springen wir mit nextSilbingElement()
729
10.2
1542.book Seite 730 Montag, 4. Januar 2010 1:02 13
10
XML
auf das nächste Element, und der Durchlauf beginnt von neuem, wenn hier das nächste Element nicht Null ist. Vielleicht noch einige Zeilen zum Slot DOMTree::updateDomElement(). Diese Methode wird benötigt, um Änderungen im DOM-Baum beim Abspeichern auch in die XML-Datei zu schreiben. Dieser wird aufgerufen, sobald der Text von einem Element im DOM-Baum verändert wurde. Außerdem entsprechenden Element wird die Spalte als Parameter übergeben. Handelt es sich um Spalte 1 (Zeile 42 bis 44), ersetzen wir einfach das Attribut "kapitel" durch den geänderten Spaltentext. Betrifft die Änderung Spalte 1 (Zeile 45 bis 51) oder Spalte 2 (Zeile 52 bis 58), dann holen wir uns das Element mit firstChildElement() und erzeugen ein neues Element mit createElement(). Den Textinhalt für das neue Element erzeugen wir auch gleich mit createTextNode() und fügen diesen dem soeben (neu) erzeugten Element hinzu. Anschließend ersetzen wir mit der Methode replaceChild() das alte Element durch das neue. Jetzt wurde auch der DOMBaum geändert, was spätestens beim nächsten Abspeichern auffallen dürfte. Nun noch eine Hauptfunktion: 00 // beispiele/dom/main.cpp 01 #include 02 #include "domparser.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 MainWindow mainWin; 06 mainWin.show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 10.4
730
Noch wurde keine XML-Datei geladen.
1542.book Seite 731 Montag, 4. Januar 2010 1:02 13
DOM-API von Qt verwenden
Abbildung 10.5
Nach dem Laden von buch.xml
Abbildung 10.6
Nach einigen Änderungen wurde die XML-Datei gespeichert.
Der Vorgang ist wie bei diesem Beispiel – fast immer – ähnlich. Es wird im Prinzip immer eine Liste von Knoten mit firstChildElement() und nextSilbingElement() durchlaufen. Alternativ stehen auch die Methoden lastChildElement() und previousSilbingElement() zur Verfügung.
10.2.1
Elemente suchen
Neben der Möglichkeit, einen DOM-Baum zu durchlaufen, bietet Qt Methoden an, um gezielt nach bestimmten Elementen zu suchen. Verwenden wir hierzu einfach nochmals den folgenden XML-Ausschnitt: <dokument version="1.2"> Ein einfaches Zitat. Text1234 Text5678
731
10.2
1542.book Seite 732 Montag, 4. Januar 2010 1:02 13
10
XML
Wollen Sie jetzt bspw. ganz gezielt nach einem Element suchen, bietet DOM die Methode elementByTagName() an. Diese Methode wird zusammen mit einer QDomDocument-Instanz aufgerufen und sucht so im gesamten Dokument danach. Da es ja mehrere Vorkommen geben kann, gibt die Methode auch eine Liste von Elementen in Form von QDomNodeList aus. (Achtung, hier ist nicht die Rede von QList, weshalb foreach() hier nicht zur Verfügung steht). Es folgt ein einfacher Codeausschnitt (wie wir bspw. an das Attribut vom -Tag gelangen): QDomNodeList domList = doc.elementsByTagName("content"); // Liste mit den -Tags durchlaufen for( int i=0; i < domList.length(); i++) { // in ein DOM-Element konvertieren QDomElment domElement = domList.at(i).toElement(); // Wert des Attributes einlesen QString content = domElement.attribute("show"); if( content == "yes" ) // -Attribut von show ist hier yes. else if( content == "no" ) // -Attribut von show ist hier no. else // kein -Attribut vorhanden }
Benötigen Sie eine Liste aller vorhandenen Unterknoten, gibt die Methode QDomNode::childNodes() ebenfalls eine Liste mit QDomNodeList zurück. Bezogen auf das XML-Dokument, wären alle Kinder-Knoten hinter <dokument> gemeint. Dies wären die Knoten , und zweimal . Deren Verwendung ist dem eben gezeigten Codeausschnitt recht ähnlich.
10.2.2 Weiteres Mit diesem Thema, besonders im Zusammenhang mit der DOM-API, ließen sich noch viele Seiten füllen. So gibt es natürlich weitere Methoden zum Manipulieren eines DOM-Baumes. Auch das Schreiben eines DOM-Baumes lässt sich ohne save() realisieren, indem man den XML-Code von Hand generiert. Wer sich also tiefer mit XML und der SAX- bzw. DOM-API befassen will/muss, der findet in der Qt-Dokumentation wieder einen hervorragenden Begleiter. Sollten mir in der nächsten Auflage des Buches mehr Seiten zur Verfügung stehen, werde ich das Kapitel auf jeden Fall erweitern.
732
1542.book Seite 733 Montag, 4. Januar 2010 1:02 13
Sobald Sie Ihre ersten Anwendungen schreiben und der Öffentlichkeit zugänglich machen wollen, müssen Sie sich Gedanken über eine Internationale Version machen. Es empfiehlt sich, als weitere Sprache zumindest Englisch zu verwenden.
11
Internationale Anwendungen
11.1
Voraussetzung für eine Übersetzung
In vielen Beispielen der Listings zuvor wurde die statische Funktion tr() aus QObject verwendet, ohne jemals genauer darauf einzugehen. Grundvoraussetzung für die Übersetzung einer Anwendung ist also, dass Sie alle übersetzbaren Strings im Programmcode zwischen der statischen Methode QObject::tr() setzen. Als Beispiel für die Praxis wollen wir den WYSIWYG-Editor aus Kapitel 5 (qteditor) verwenden. Darin wurden alle Strings zwischen der statischen Methode QObject::tr() gesetzt. Zuerst müssen wir unsere Projektdatei (.pro) anpassen, wenn diese Datei bisher wie folgt aussieht: TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . HEADERS += MyWindow.h SOURCES += MyWindow.cpp main.cpp
Um hier noch eine Übersetzung mit einzubeziehen, fehlt ein Eintrag TRANSLATION. Darin können Sie die Übersetzungsdatei(en) als Argument eintragen. Im Beispiel wollen wir eine Übersetzungsdatei für die englische Sprache als Argument übergeben. Somit müssen Sie zur Projektdatei folgenden Eintrag hinzufügen: TRANSLATIONS = MyWindow_en.ts
733
1542.book Seite 734 Montag, 4. Januar 2010 1:02 13
11
Internationale Anwendungen
Natürlich können Sie hierbei auch mehrere Übersetzungen auf einmal hinzufügen: TRANSLATIONS = MyWindow_en.ts \ MyWindow_it.ts \ MyWindow_es.ts \ MyWindow_fr.ts
Hier wurden zur englischen Version Übersetzungen für Italienisch, Spanisch und Französisch hinzugefügt. Als Nächstes müssen wir die Zeichenketten, die zwischen QObject::tr() stehen, extrahieren. Hierfür genügt ein Aufruf von lupdate und der Projektdatei in der Kommandozeile: $> lupdate MyWindow.pro
Jetzt finden Sie alle extrahierten Zeichenketten in der Datei MyWindow_en.ts als XML-basiertes Format wieder. Ein Ausschnitt der Datei MyWindow_en.ts: MyWindow <message> <source>&Datei <message> <source>&Neu ...
Sofern Sie dem Quellcode nun neue Strings hinzufügen, müssen Sie mittels "lupdate MyWindow.pro" die Übersetzungsdatei immer wieder auf den neuesten Stand bringen.
11.1.1
Fehlervermeidung und Kommentare
Jetzt haben Sie die Vorbereitungen für eine Übersetzung getroffen. Trotzdem muss ich Sie noch auf eine Fehlerquelle aufmerksam machen, die im Zusammenhang mit der statischen Methode QObject::tr() auftreten kann. Ein Problem bei
734
1542.book Seite 735 Montag, 4. Januar 2010 1:02 13
Übersetzen mit Linguist
der Verwendung von QObject::tr() tritt besonders gerne bei dynamischen Strings auf, wenn man unachtsam ist. Beispiel: QString msg = tr("File " + filename + " not found!");
Weil sich der String abhängig von filename ändert, wird er von tr() nicht korrekt übersetzt – genauer: Hinter filename kann der Übersetzer keinen Text platzieren. In solchen Fällen müssen Sie auf QString::arg() und einen Platzhalter zurückgreifen. In diesem Fall sieht die Lösung wie folgt aus: QString msg = tr("File %1 not found!").arg(filename);
Wenn Sie sich außerdem die Syntax von tr() angesehen haben, werden Sie festgestellt haben, dass neben dem eigentlichen String zusätzlich ein Kommentar verwendet werden kann. Dies kann man bspw. bei zweideutigen Wörtern verwenden. Bspw. befinden sich zwei Strings Size im Programm zum Übersetzen. Dies kann zweideutig sein. Einmal ist die Größe des Hauptfensters gemeint und dann die Größe einer Dialog-Box. Hier empfiehlt es sich, die Kommentare zu verwenden: QString size1 = tr("Size", "the main window"); QString size2 = tr("Size", "the dialog box");
Nach einem Update mit lupdate stehen auch in der Übersetzungsdatei die beiden Strings mit den jeweiligen Hinweisen.
11.2
Übersetzen mit Linguist
Um jetzt die Übersetzungsquelle zu bearbeiten, wird in der Praxis gewöhnlich auf Qt Linguist zurückgegriffen. Das Programm hat den Vorteil, dass auch NichtProgrammierer bzw. professionelle Übersetzer die Strings übersetzen können. Starten Sie also den Linguist von Qt, und öffnen Sie die Datei mit der .ts-Endung. In unserem Fall die Datei MyWindow_en.ts. Im linken Dock-Fenster (siehe Abbildung 11.1) sehen Sie jetzt eine Übersicht des Übersetzungskontexts. Hier finden Sie auch die Anzahl der Elemente und wie viele bisher übersetzt wurden. Im Beispiel haben wir 47 Strings und noch keinen davon übersetzt. Als Kontext wird gewöhnlich der Name der Klasse verwendet (hier MyWindow). Wenn Sie das orange Fragezeichen davor doppelt klicken, finden Sie einen Überblick zu allen Strings (siehe Abbildung 11.2).
735
11.2
1542.book Seite 736 Montag, 4. Januar 2010 1:02 13
11
Internationale Anwendungen
Abbildung 11.1 Beim Laden der Datei »MyWindow_en.ts« können Sie auch gleich die Quell- und Zielsprache (inklusive der Region) einstellen.
Abbildung 11.2
MyWindow_en.ts in Qt Linguist geladen
Wenn das Fragezeichen noch blau ist, bedeutet dies, dass der String noch nicht übersetzt wurde. Ein übersetzter String besitzt eine orange Farbe. Sollte etwas
736
1542.book Seite 737 Montag, 4. Januar 2010 1:02 13
Übersetzen mit Linguist
nicht ganz klar sein bei der Übersetzung (bspw. fehlende »…«), markiert Linguist dieses Element mit einem roten Ausrufezeichen. Sind Sie mit der Übersetzung zufrieden, können Sie die orangefarbenen Fragezeichen doppelt klicken, und es wird ein Häkchen daraus. Um einen String zu übersetzen, müssen Sie ihn nur anklicken und können ihn im Editfeld »Translation« eintippen. Der extrahierte Originaltext steht unter »Source Text« (siehe Abbildung 11.3).
Abbildung 11.3
Einige übersetzte Elemente
Natürlich hat der Linguist von Qt noch viel mehr Features, doch wollen wir in diesem Abschnitt nur auf den Standardgebrauch eingehen. Wenn Sie alle Übersetzungen gemacht haben, müssen Sie diese mit dem Linguist in der .ts-Datei speichern. Im nächsten Schritt geht es erneut in die Kommandozeile, um aus den Übersetzungsquellen eine spezielle Binärdatei zu machen, die unser Qt-Programm anschließend verwenden kann. Dies erledigen Sie mit dem Kommando lrelease wie folgt:
737
11.2
1542.book Seite 738 Montag, 4. Januar 2010 1:02 13
11
Internationale Anwendungen
$> lrelease MyWindow.pro Updating 'MyWindow_en.qm'... Generated 47 translations (47 finished and 0 unfinished) $>
Jetzt finden Sie eine neue binäre Datei MyWindow_en.pm wieder, die alle englischen Übersetzungen unseres Programms enthält.
11.3
Übersetzung verwenden
Um jetzt die Übersetzungen im Programm zu verwenden, muss man auf die Klasse QTranslator zurückgreifen. Gewöhnlich erledigt man diesen Vorgang als Erstes in der main()-Funktion. Am besten sehen wir uns die entsprechende main()-Funktion an: 00 01 02 03 04
// beispiele/qteditor/main.cpp #include #include #include #include "MyWindow.h"
05 int main(int argc, char *argv[]) { 06 QApplication app(argc, argv); 07 QTranslator editTranslator; 08 QString filename; 09 filename = QString("MyWindow_%1"). arg(QLocale::system().name()); 10 editTranslator.load( filename, qApp->applicationDirPath() ); 11 app.installTranslator(&editTranslator); 12 MyWindow* window = new MyWindow; 13 window->show(); 14 return app.exec(); 15 }
Um in der Zeile 9 die entsprechende QM-Übersetzungsdatei zu finden, wird die statische Methode QLocale::system() verwendet, um Informationen zum lokalen System zu erhalten. Zusätzlich mit der Methode name() erhalten wir dann einen String aus Sprachcode und Ländercode. Bei Deutschland wäre dies bspw. de_DE. In diesem Beispiel würde somit in filename der String MyWindow_de_DE stehen. Die Endung .qm wird von QTranslator automatisch angefügt. In Zeile 10 wird versucht, die entsprechende Übersetzungsdatei filename zu laden. Als zweites Argument von QTranslator::load() geben wir das Verzeich-
738
1542.book Seite 739 Montag, 4. Januar 2010 1:02 13
Übersetzung verwenden
nis an, wo sich die Übersetzungsdatei befindet; in diesem Fall im selben Verzeichnis wie die Anwendung. Der Algorithmus von load() gibt nicht gleich auf, wenn die Übersetzungsdatei MyWindow_de_DE.qm nicht vorhanden ist, sondern streicht von hinten zunächst die Endung und dann die Unterstriche bis zum letzten Unterstrich heraus. Bspw. würde man in diesem Fall der Reihe nach folgende Übersetzungsdateien suchen: MyWindow_de_DE.qm MyWindow_de_DE MyWindow_de MyWindow.qm MyWindow
Sollte allerdings bis hierher noch keine entsprechende Übersetzungsdatei gefunden worden sein, wird die im Quellcode benutzte Sprache verwendet. In diesem Fall wollen wir aber aus der deutschen eine englische Version machen. Hierzu könnten Sie bspw. die lokale Einstellung auf dem System ändern (auf en_UK oder en_US), oder aber Sie benennen für einen Test die Datei MyWindow_en.qm in MyWindow_de.qm. um. Es ist immer schwer, einen Übersetzer für eine entsprechende Sprache zu finden, mit der man nichts am Hut hat. So kann es recht nützlich sein, Versionen für andere Sprachen wie Französisch, Spanisch, Italienisch etc. bereitzustellen, indem man einfach eine Kopie der englischen Version anfertigt und die Endung für den entsprechenden Sprachcode hinzufügt (bspw. MyWindow_fr.qm für Französisch). Wenn Sie mindestens eine englische Sprachversion verwenden, können Sie sicher sein, dass sich Ihr Programm auf der ganzen Welt verwenden lässt. Englisch als Standardsprache In der Praxis sollte man im Quellcode am besten gleich Englisch verwenden und am Ende die extrahierten Strings ins Deutsche usw. übersetzen.
Nach dem Laden der entsprechenden Übersetzungsdatei müssen wir nur noch die Methode installTranslator() (Zeile 11) in unserer QApplication-Instanz aufrufen. Nach dem nächsten show()-Aufruf sollte die neu übersetzte Benutzeroberfläche angezeigt werden (siehe Abbildung 11.4). Das Programm ist jetzt zwar übersetzt, meist werden Programme aber weiterentwickelt, wobei man schnell mal vergisst, einen String zwischen den statischen Methoden tr() einzuschließen. Als Ergebnis erhält man dann oft ein halb lokalisiertes Programm, welches zweierlei Sprachen aufweist. Es ist unschön, im Menü ein Element wie »Ausschneiden« und eine Zeile später ein Menüelement »Paste« anzutreffen. Um so etwas zu vermeiden, können Sie das Präprozessorsymbol QT_
739
11.3
1542.book Seite 740 Montag, 4. Januar 2010 1:02 13
11
Internationale Anwendungen
NO_CAST_FROM_ASCII verwenden. Dadurch erzwingen Sie, dass jeder Stringliteral zwischen einem tr() gesetzt werden muss. Ist ein String nicht zwischen einem tr() gesetzt, gibt der Compiler einen Fehler zurück. Das Symbol setzen Sie am besten in der Projektdatei (.pro) wie folgt: DEFINES += QT_NO_CAST_FROM_ASCII
Abbildung 11.4
11.4
In die englische Sprache übersetzter Editor
char-Arrays internationalisieren
Wenn Sie in Ihrem Programm ein statisches char-Array angelegt haben – was ja keiner Klasse von Qt entspricht –, können Sie auch hier dafür sorgen, dass diese Zeichenketten extrahiert werden. Qt bietet hierzu das Makro QT_TR_NOOP() an. Ein Beispiel: ... static const char* colors[] = { QT_TR_NOOP("red"), QT_TR_NOOP("green"), QT_TR_NOOP("blue"), 0 }; ...
Durch das Makro QT_TR_NOOP() wird einfach das Argument (also der String) dazwischen zurückgegeben. Dies reicht aus, dass ein Aufruf von lupdate auch die in QT_TR_NOOP() verpackten Strings extrahiert.
740
1542.book Seite 741 Montag, 4. Januar 2010 1:02 13
In diesem Kapitel finden Sie einige Themen, die ich gerne intensiver beschrieben hätte. Aus Platzgründen musste ich aber bestimmte Grenzen einhalten. Dennoch sollen diese Themen kurz, aber informativ angeschnitten werden.
12
Weiteres zu Qt
12.1
Dynamische Bibliotheken erstellen
Natürlich lassen sich mit Qt auch dynamische Bibliotheken (auch als DLLs oder gemeinsame Bibliotheken bekannt) verwenden. Diese dynamische Bibliotheken haben den Vorteil, dass von mehreren Anwendungen darauf zurückgegriffen werden kann. In der Praxis dürften Sie die Bibliothek meistens schon beim Binden des Programms mit angeben. Dabei wird die Bibliothek sofort zum Programmstart automatisch geladen. Es reicht aus, die Projektdatei (.pro) um die Einträge der Bibliothek (LIBS) und meistens auch um den Ort, wo sich die entsprechende Include-Datei (INCLUDEPATH) befindet, zu ergänzen. Bspw.: ... INCLUDEPATH += /usr/include/myLib LIBS += /usr/lib/myLib/nameDerLib.a ...
Zur Demonstration hierzu ein Minimalbeispiel. Es soll eine dynamische Bibliothek erstellt werden, die im Grunde nur eine Nachrichtenbox erzeugt, die zur Kenntnis bringt, dass die dynamische Bibliothek erfolgreich ausgeführt wurde. Zunächst die Headerdatei: 00 // beispiele/dynamicLib/qtestdll.h 01 #ifndef QTTESTDLL_H 02 #define QTTESTDLL_H 03 #ifdef BUILD_DLL 04 #define EXPORT_DLL Q_DECL_EXPORT 05 #else 06 #define EXPORT_DLL 07 #endif
// für Windows
741
1542.book Seite 742 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
08 #include 09 #include 10 11 12 13 14 15 16 17
class EXPORT_DLL myTestLib : public QObject { Q_OBJECT public: myTestLib() {}; ~myTestLib(){}; void printOut(); }; #endif
Jetzt noch die Implementierung der einen Methode in qtestdll.h: 00 // beispiele/dynamicLib/qtestdll.cpp 01 #include "qtestdll.h" 02 void myTestLib::printOut() { 03 QMessageBox::information ( NULL, QObject::tr("Erfolgreich"), QObject::tr("Dynamische Bibliothek" " erfolgreich ausgeführt") ); 04 }
Nun müssen wir die Projektdatei nach einem "qmake –project" und qmake-Aufruf ein wenig anpassen, um auch eine dynamische Bibliothek zu erstellen: TEMPLATE = lib DEPENDPATH += . INCLUDEPATH += . CONFIG += dll qt thread # DEFINES nur für Windows nötig DEFINES += BUILD_DLL HEADERS += qtestdll.h SOURCES += qtestdll.cpp
Dass hier tatsächlich eine Bibliothek erzeugt werden soll, teilen Sie make mit der ersten Zeile bei TEMPLATE mit. Bisher haben Sie hier immer app, für die Erstellung einer Anwendung verwendet. Mit lib geben Sie an, dass jetzt eine Bibliothek daraus gemacht werden soll.
742
1542.book Seite 743 Montag, 4. Januar 2010 1:02 13
Dynamische Bibliotheken erstellen
Hinweis Wir streifen hier wieder nur die Spitze des berühmten Eisberges in Sachen »Bibliotheken«. Ich empfehle Ihnen, sich auf jeden Fall mit qmake vertrauter zu machen. Die Dokumentation von Qt bietet hierzu neben einem tollen Tutorial auch eine umfangreiche Referenz.
Nachdem Sie mit make Ihre Bibliothek gebaut haben, benötigen wir noch ein Programm, das unsere Bibliothek testet. Hierzu ein einfaches Beispiel: 00 01 02 03
// beispiele/testLib/main.cpp // Pfad zum Header ggf. anpassen #include "../dynamicLib/qtestdll.h" #include
04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 myTestLib *qtTest=new myTestLib(); 07 qtTest->printOut(); 08 return 0; 09 }
Auch hier müssen Sie nach einem "qmake -project" und qmake-Aufruf die Projektdatei (.pro) ein wenig verändern: TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . # Pfad ggf. anpassen LIBS += ./libdynamicLib.a SOURCES += main.cpp
Wichtig ist die Angabe von LIBS zur dynamischen Bibliothek. Ein häufiger Fehler ist es außerdem, dass die Pfadangabe falsch ist. Im Beispiel habe ich die dynamische Bibliothek (.a und .dll) ins aktuelle Arbeitsverzeichnis des Programms gelegt. Den Include-Pfad können wir uns hier ersparen, weil wir ihn im Hauptprogramm schon angegeben haben. Wenn alles glatt verlaufen ist, sollten Sie folgendes Bild erhalten:
Abbildung 12.1
Dynamische Bibliothek bei der Ausführung
743
12.1
1542.book Seite 744 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
12.1.1
Dynamische Bibliothek dynamisch nachladen
Es ist auch möglich, eine Bibliothek zur Laufzeit erst nachzuladen bzw. sie wieder aus dem Speicher zu entfernen. Hierzu bietet Qt die Klasse QLibrary an. Mit QLibrary ist dies gar plattformunabhängig möglich. Der Vorgang ist im Grunde recht einfach: Man gibt im Konstruktor den Bibliotheksnamen an. QLibrary sucht nun in den entsprechenden Pfaden bzw. wenn der Pfad angegeben wurde, im entsprechenden Verzeichnis nach der angegebenen Bibliothek. Hierbei ist es nicht nötig, die Endung (bspw. .dll unter Windows; .so unter Linux oder .dylib unter MacOS X) anzugeben. Anschließend können Sie die benötigten Symbole auflösen. Dazu bietet QLibrary die Methode resolve() an. Die Methode resolve() übernimmt auch das Laden der Bibliothek (was auch mit load() erledigt werden könnte). Um die Bibliothek wieder freizugeben, wird unload() aufgerufen. Um ein Symbol mit resolve() aufzulösen, müssen wir es als C-Funktion aus der Bibliothek exportieren. Handelt es sich beim Compiler um einen C++-Compiler, was in der Praxis meistens der Fall ist, müssen Sie vor die Funktion ein extern "C" stellen und unter Windows zusätzlich die Funktion von der DLL exportieren, indem Sie noch die Compiler-Direktive __declspec(dllexport) verwenden, was auch das Makro Q_DECL_EXPORT für uns erledigt (wie im Beispiel zuvor). Daher haben wir in der Projektdatei zuvor auch bei DEFINES das Makro BUILD_DLL gesetzt, was Sie natürlich unter Linux/Unix und Mac OS X nicht zu setzen brauchen. Jetzt müssen wir den Code unserer Bibliothek verändern. Zunächst wieder die Headerdatei mit einer neuen Zeile: 00 01 02 03 04 05 06 07
// beispiele/dynamicLib2/qtestdll.h #ifndef QTTESTDLL_H #define QTTESTDLL_H #ifdef BUILD_DLL #define EXPORT_DLL Q_DECL_EXPORT #else #define EXPORT_DLL #endif
08 #include 09 #include 10 extern "C" EXPORT_DLL void showMsg(); 11 class EXPORT_DLL myTestLib : public QObject { 12 Q_OBJECT
744
1542.book Seite 745 Montag, 4. Januar 2010 1:02 13
Dynamische Bibliotheken erstellen
13 public: 14 myTestLib() {}; 15 ~myTestLib(){}; 16 void printOut(); 17 }; 18 #endif
Natürlich finden Sie jetzt auch bei der Implementierung den Code zur Funktion showMsg() wieder: 00 // beispiele/dynamicLib2/qtestdll.cpp 01 #include "qtestdll.h" 02 void myTestLib::printOut() { 03 QMessageBox::information ( NULL, QObject::tr("Erfolgreich"), QObject::tr("Dynamische Bibliothek" " erfolgreich ausgeführt" ) ); 04 } 05 extern "C" EXPORT_DLL void showMsg() { 06 myTestLib *qtTest = new myTestLib(); 07 qtTest->printOut(); 08 }
Die Projektdatei verändert sich nicht und bleibt die gleiche wie im Abschnitt zuvor, als die dynamische Bibliothek beim Programmstart automatisch hinzugefügt wurde. Jetzt können wir das Testprogramm erstellen, welches diese Bibliothek zur Laufzeit ladet, das Symbol (hier showMsg) auflöst, die Prozedur ausführt und anschließend die Bibliothek wieder aus dem Speicher lädt. 00 01 02 03
// beispiele/testLib2/main.cpp #include "../dynamicLib2/qtestdll.h" #include #include
04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 QLibrary lib("./dynamicLib2"); 07 typedef void (*MyPrototype)(); 08 MyPrototype myFunction = (MyPrototype) lib.resolve("showMsg"); 09 if (myFunction)
745
12.1
1542.book Seite 746 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
10 11 12
13 14 15 16 17 }
myFunction(); else { QMessageBox::critical ( NULL, QObject::tr("Fehler"), QObject::tr("Konnte dynamische Bibliothek nicht" " laden\n%1").arg(lib.errorString() ) ); return 1; } lib.unload(); return 0;
Im Konstruktor (Zeile 6) geben wir die Bibliothek an, die wir beabsichtigen, zur Laufzeit zu laden. Natürlich sollten Sie auch hier den Pfad zur Bibliothek ggf. anpassen. Nachdem wir uns in Zeile 7 die Lesbarkeit vereinfachen, wird in Zeile 8 die Bibliothek automatisch geladen; es wird versucht, das Symbol showMsg aufzulösen. Klappt dies nicht, weil bspw. das Symbol oder die Bibliothek nicht vorhanden ist, wird 0 zurückgegeben, und die Prozedur (Zeile 10) wird nicht aufgerufen. Was für ein Fehler aufgetreten ist, können Sie mit QLibrary::errorString() im Klartext ausgeben lassen (Zeile 12). In Zeile 13 geben wir die Bibliothek wieder frei. Hierbei sollte angemerkt werden, dass die Bibliothek nur dann freigegeben wird, wenn keine Instanz mehr auf diese zurückgreift. Die Projektdatei (.pro) zum Testprogramm können Sie nochmals ein wenig kürzen, da wir ja LIBS nicht mehr benötigen: TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . SOURCES += main.cpp
12.1.2
Plugins erstellen
Das Verwenden von DLLs ist noch nicht das Ende der Fahnenstange. Qt unterstützt, wie viele andere moderne GUI-Frameworks, auch die Erstellung von Plugins. Hierbei gibt es zwei Arten, um Plugins zu entwickeln: 왘
Die höhere Ebene, womit Sie das Framework von Qt um Plugins erweitern. Häufige Erweiterungen sind Bild- und Grafikformate, Codecs für Texte, Datenbanktreiber und verschiedene Stile (Look & Feel) der Widgets.
왘
Die niedrigere Ebene. Damit lassen sich Erweiterungen entwickeln, die eben nur für bestimmte Qt-Programme gültig sind.
746
1542.book Seite 747 Montag, 4. Januar 2010 1:02 13
Debugging-Ausgabe
Hinweis Weil das Thema recht komplex ist und somit auch einiges an Seiten benötigt, gehen wir hier nicht näher darauf ein. Sollten Sie trotzdem ein Plugin benötigen, können Sie sich in der Dokumentation das Tutorial »How to Create Qt Plugins« ansehen.
12.2
Qt Mobility (alias Qt Extended (ehemals Qtopia))
Qt Extended hieß ehemals Qtopia und wird zur Erstellung von Software für Linux-basierte PDAs, Webpads und Mobiltelefone verwendet. Das Framework unterstützt auch OpenGL ES (OpenGL für eingebettete Systeme). Ungewöhnlich an diesem Framework ist, dass es direkt in den Framebuffer schreibt, anstatt einen X-Server zu verwenden, wie es sonst auf Linux üblich ist. Die Entwicklung von Qt Extended wurde im März 2009 eingestellt. Die letzte Version trug die Nummer 4.4.30. Künftig werden die bisherigen (und viele weitere) Funktionen von Qt Extended unter dem Namen Qt Mobility mit der Qt Bibliothek zur Verfügung gestellt. Das ehrgeizige Ziel von Qt Mobility ist es, eine API zur Crossplattform-Entwicklung von mobilen Geräten anzubieten. Damit soll es möglich werden, mobile Anwendungen zu entwickeln, die auf mobilen Plattformen mit Symbian S40/ S60, Windows CE und Maemon laufen. Bisher musste man für jede dieser Plattformen eine Extralösung programmieren. Man darf gespannt sein, wie sich dieses künftige Feature des Qt-Frameworks entwickelt.
12.3
Debugging-Ausgabe
In diesem Abschnitt will ich nicht das klassische Debuggen mit einem Debugger beschreiben, sondern die Möglichkeit, wie Sie Debugging-Ausgaben machen können. Das Debuggen von Programmen über die Ausgabe ist neben einem Debugger die häufig angewandte Überprüfungs-Methode. Das Programm stürzt bspw. ab, ohne erkennbaren Grund; nun setzt man einfach an einer Stelle, die einem suspekt erscheint, eine Debugging-Ausgabe und beobachtet, ob diese Ausgabe durchlaufen wird oder ob das Programm vorher abstürzt. Dies kann man Zeile für Zeile testen. Natürlich werden solche Debugging-Ausgaben auch verwendet, um Zwischenergebnisse, Berechnungen oder bestimmte Zustände anzuzeigen. Dies ist auch hilfreich, um den Programmfluss besser nachvollziehen zu können.
747
12.3
1542.book Seite 748 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
Natürlich ist es auch hier möglich, die Debugging-Ausgabe mit dem C-like printf() oder C++-typischen cerr bzw. cout zu absolvieren. Doch sobald das Programm beendet ist, muss man die Debugging-Ausgaben wieder mühsam entfernen und möchte die eine oder andere Meldung vielleicht stehen lassen. Qt bietet für die Debug-Ausgabe mehrere Funktionen an. Für die normale Ausgabe wird bspw. qDebug() verwendet. qDebug() sieht der Funktion printf() recht ähnlich. Natürlich brauche ich jetzt auch Argumente, warum Sie bspw. qDebug() anstatt printf() oder cerr verwenden sollten. Nun, mithilfe der Debug-Ausgaben über die Qt-Funktionen können Sie bspw. die Debug-Meldungen bzw. -Ausgaben ignorieren, wenn Sie das Makro QT_NO_DEBUG_OUTPUT während des Kompiliervorgangs verwenden. Dies geschieht übrigens auch automatisch, wenn Sie ein Programm ohne Debug-Unterstützung (in der Projektdatei: CONFIG -= debug) erstellen. Wird QT_NO_DEBUG_OUTPUT hingegen nicht verwendet oder ist es nicht definiert, ist es systemabhängig, wohin die Debug-Meldung geht. Unter Linux und Unix geht dies in die Fehlerausgabe (stderr). Windows hingegen schickt die Meldungen an einen Debugger. Dies ist allerdings nicht immer erwünscht. Häufig wünscht man sich als Windows-Entwickler auch eine Konsole, um die Standardfehlerausgabe auszugeben. Eine solche kann man sich aber mit einem Handler selbst implementieren, wie wir in Kürze demonstrieren werden. Hierzu ein einfaches Beispiel, um einfache Debug-Meldungen mit qDebug() auszugeben. Der Codeausschnitt stammt aus dem Beispiel des umfangreichen WYSIWYG-Editors aus Kapitel 5. Hier wurden einige Debug-Meldungen eingebaut: ... // Datei öffnen und im Editor anzeigen void MyWindow::openFile() { qDebug("openFile() gestartet"); QString fileName; fileName = QFileDialog::getOpenFileName( this, tr("Datei öffnen"), "", "C++ Dateien (*.cpp *.h);;" "Text-Dateien (*.txt);;" "Alle Dateien (*.*)" ); if (!fileName.isEmpty()) { QFile file(fileName); if (file.open(QFile::ReadOnly | QFile::Text)) { editor->setPlainText(file.readAll()); statusBar()->showMessage( tr("Datei erfolgreich geladen") ); // zuletzt geöffnete Datei setzen
748
1542.book Seite 749 Montag, 4. Januar 2010 1:02 13
Debugging-Ausgabe
settings->beginGroup("HauptFenster"); settings->setValue("lastFile", fileName ); settings->endGroup(); qDebug("openFile() Datei %s ausgewählt", qPrintable(fileName) ); } } qDebug("openFile() beendet()"); } ...
Anhand der Verwendung erkennen Sie sofort, dass sich diese Funktion wie printf() verwenden lässt. Wenn Sie das Programm jetzt übersetzen und der Slot openFile() ausgeführt wurde, finden Sie in der Konsole bzw. dort, wo immer die Standardfehler ausgegeben werden, z. B. Folgendes wieder: openFile() gestartet openFile() Datei /home/me/main.cpp ausgewählt openFile() beendet
Für QString und QByteArray wird die Hilfsfunktion qPrintable() verwendet, die daraus einen echten C-konformen String (const char*) macht. Windows-Programmierer, die nicht mit Visual C++ arbeiten, bekommen allerdings keine Meldung zu sehen, da diese Ausgabe hier an den Debugger geht. Für solche Zwecke können Sie einen Nachrichten-Handler einrichten (natürlich gilt dies nicht nur für Windows), um die Ausgabe für Debug-Zwecke umzuleiten. Einen solchen Nachrichten-Handler kann man mit der Funktion qInstallMsgHandler() einrichten. Am einfachsten realisiert man dies in der Hauptfunktion wie folgt: int main(int argc, char *argv[]) { QApplication app(argc, argv); #ifdef Q_WS_WIN qInstallMsgHandler( debugOutput ); #endif ... return app.exec(); }
Das Makro Q_WS_WIN stellt sicher, dass wir den Handler auch wirklich unter Windows installieren. Hierbei können Sie selbstverständlich auch auf anderen Systemen einen Handler (bspw. Q_WS_X11, Q_WS_MAC, usw.) einrichten, um bspw. die Debug-Ausgabe in eine Datei zu schreiben. Die Parameter der Funktion qInstallMsgHandler() sind fest vorgeschrieben, der Funktionsname hingegeben ist frei wählbar. Hier die Funktion debugOutput():
749
12.3
1542.book Seite 750 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
void debugOutput( QtMsgType type, const char* msg ) { static QTextBrowser* browser = new QTextBrowser(); browser->setWindowTitle("Debug-Fenster"); browser->move(0,0); browser->show(); switch(type) { case QtDebugMsg: browser->append(QString("Debug : %1").arg(msg)); break; case QtWarningMsg: browser->append(QString("Warning : %1").arg(msg)); break; case QtCriticalMsg: browser->append(QString("Critical : %1").arg(msg)); break; case QtFatalMsg: browser->append(QString("Fatal : %1").arg(msg)); break; } }
Für die Debug-Ausgabe verwenden wir QTextBrowser, die hier – da es sich um static handelt –, nur beim ersten Lauf mit new erzeugt wird. Entscheidend ist die Auswertung des Nachrichten-Typs in der switch-Anweisung. Im Beispiel des Slots openFile() ist der Nachrichten-Typ QtDebugMsg, was sich durch die Funktion qDebug() ergibt. Bei der Ausführung sieht unser Debug-Fenster unter Windows jetzt wie folgt aus:
Abbildung 12.2
Debug-Fenster bei der Ausführung
Wenn das Programm auf Herz und Nieren getestet wurde, wollen wir natürlich nichts mehr von diesen Debug-Ausgaben sehen. Hierzu setzen wir in der Projektdatei QT_NO_DEBUG_OUTPUT: DEFINES += QT_NO_DEBUG_OUTPUT
750
1542.book Seite 751 Montag, 4. Januar 2010 1:02 13
Debugging-Ausgabe
Damit dies unter dem installierten Nachrichten-Handler unter Windows sicher funktioniert, bauen wir dies als Überprüfung in die main-Funktion mit ein: int main(int argc, char *argv[]) { QApplication app(argc, argv); #ifdef Q_WS_WIN #ifndef QT_NO_DEBUG_OUTPUT qInstallMsgHandler( debugOutput ); #endif #endif ... return app.exec(); }
Hinweis Damit dies auch mit der Debug-Ausgabe funktioniert, müssen Sie den Header mit einbinden.
Anhand des Fehler-Nachrichten-Handler und dessen Typenüberprüfung dürfte auffallen, dass es noch andere Debug-Typen für die Ausgabe gibt: neben qDebug() gibt es noch qCritical(), qWarning() und qFatal(). Diese Funktionen unterscheiden sich nicht von deren Anwendung, wohl aber von ihren Auswirkungen. Für Debug-Fehlermeldungen wird gewöhnlich qCritical() und für Warnungen qWarning() verwendet. Hier muss man aber gleich hinzufügen, dass es sich dennoch um echte Debug-Meldungen handeln sollte und Sie dies nicht durch echte Interaktionen wie bspw. eine Fehlermeldung als Nachrichtenbox ersetzen sollten, was voraussetzt, dass der Anwender auf die Konsole achtet. Auch für qWarning() gibt es mit QT_NO_WARNING_OUTPUT ein Makro, mit dem diese Warnungen nicht mehr ausgegeben und beim Auftreten im Programm ignoriert werden. Setzen Sie die Umgebungsvariable QT_FATAL_WARNINGS, würde ein Aufruf von qWarning()gleich das Ende des Programms bedeuten. Das Makro QT_FATAL_WARNINGS können Sie sich auch sparen und stattdessen die Funktion qFatal() verwenden. qFatal() gibt noch schnell die Debug-Meldung aus und beendet anschließend das Programm mit dem Rückgabewert 1.
12.3.1
Fehlerbehebung
Anstatt zur Überprüfung spezieller Ausdrücke auf Richtigkeit mit assert() bietet Qt mit Q_ASSERT() eine eigene Version an, die im Gegensatz zum Ur-assert() zwischen der Debug- und Release-Version unterscheidet. Im Gegensatz zur
751
12.3
1542.book Seite 752 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
Release-Version bricht Q_ASSERT() bei der Debug-Version das Programm mit einer Fehlermeldung ab. Benötigen Sie zusätzlich die Zeilennummer und den Dateinamen, können Sie auch Q_ASSERT_X() verwenden. Auch für das größte Übel, einer undefinierten bzw. fehlgeschlagenen Zeigerzuweisung, existiert mit Q_CHECK_PTR() ein Makro. Auch hier reagiert bei einem fehlerhaften Zeiger das Makro mit einem Programmabruch. Zusätzlich werden die Zeilennummer und der Dateiname des Fehlers ausgegeben.
12.4
Qt Styles
Die Klasse QStyle ist eine abstrakte Basisklasse, die das Look & Feel der GUI kapselt. Qt bietet einen ganzen Satz von QStyle-Unterklassen an, die verschiedene Plattformen emulieren können (QWindowsStyle, QWindowsXPStyle, QMacStyle, QPlastiqueStyle, QCDEStyle, QCleanlooksStyle und QMotifStyle). Diese Stile sind zur Gänze per Standard in die QtGui-Bibliothek eingebaut. Hierzu diverse Stile als Radio- und Check-Box im Überblick (das entsprechende Beispiel befindet sich bei den Demos von Qt in der Sektion Widgets).
Abbildung 12.3
Windows-Stil (QWindowsStyle)
Abbildung 12.4
WindowsXP-Stil (QWindowsXPStyle)
Abbildung 12.5
Motif-Stil (QMotifStyle)
752
1542.book Seite 753 Montag, 4. Januar 2010 1:02 13
Qt Styles
Abbildung 12.6
CDE-Stil (QCDEStyle)
Abbildung 12.7
Plastique-Stil (QPlastiqueStyle)
Abbildung 12.8
Cleanlook-Stil (QCleanlooksStyle)
Um den Stil der Anwendung zu verändern, kann die Methode QApplication::setStyle() verwendet werden. Um etwa den Motif-Stil zu verwenden, fügt man in der Hauptfunktion am besten Folgendes hinzu: #include ... int main(int argc, char *argv[]) { QApplication app(argc, argv); QApplication::setStyle(new QMotifStyle); ... return app.exec(); }
Auch wenn es nicht im Programm angegeben ist, kann der Anwender den Stil in der Kommandozeile mit dem Schalter -style beim Programmstart setzen: user$>
./myapp -style motif
Wenn ein Stil nicht vorhanden ist, wird automatisch der Standard-Stil verwendet, den der Anwender auf dem Desktop verwendet.
753
12.4
1542.book Seite 754 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
Es ist auch möglich, einen Stil für einzelne Widgets zu setzen. Hierzu existiert die Methode QWidget::setStyle(). Wem das nicht reicht, der kann seine Widgets noch tiefer mit QPainter selber zeichnen. Und last but not least: Wer will, kann einen eigenen Stil für seine Anwendung entwickeln. Mehr dazu lesen Sie in der Dokumentation von QStyle nach und entdecken im entsprechenden Demo der Buch-DVD.
12.5
QApplication, QCoreApplication und die Kommandozeile
Die von QCoreApplication abgeleitete Klasse QApplication verwaltet den GUIKontrollfluss der Anwendung und Haupteinstellungen. Für eine Anwendung mit einer GUI ist hierbei genau ein QApplication-Objekt nötig, egal wie viele Fenster die Anwendung hat. Die Klasse enthält die Haupt-Ereignisschleife (main event loop), wo alle Ereignisse des Fenstersystems und andere Quellen abgearbeitet und abgefertigt werden. Um einen direkten Zugriff auf das QApplication-Objekt zu erhalten, können Sie die Methode instance() verwenden oder von überall den globalen Zeiger qApp. Die Klasse QApplication ist für viele Dinge verantwortlich – wie Systemeinstellungen, Ereignisverarbeitung, GUI-Stile, Farben, Texte lokalisieren, fortgeschrittene Cursorverarbeitung, Synchronisation des X-Window-System, Session-Verwaltung, Kommandozeilen-Parameter verarbeiten und eine Menge mehr. Ein Blick in die Dokumentation zu dieser Klasse bringt viele nützliche Methoden zum Vorschein. Für Qt-Anwendungen, die keine GUI benötigen, sollten Sie die Eltern-Klasse QCoreApplication verwenden, weil diese Klasse nicht von der Bibliothek QtGui
abhängig ist. QCoreApplication enthält, wenn nötig, eine Haupt-Ereignisschleife exec() (bspw. bei typischen Client-/Server-Anwendungen), in der alle Ereignisse des Betriebssystems (wie bspw. Timer oder Netzwerk-Ereignisse) verarbeitet und abgefertigt werden. Auch hier findet man Methoden zu system- und anwendungsweiten Einstellungen. Auch die Kommandozeile lässt sich mit arguments() einfach auswerten. Hinweis In der Praxis wird immer empfohlen, ein QCoreApplication oder QApplicationObjekt in der main()-Funktion sobald wie möglich zu erzeugen.
754
1542.book Seite 755 Montag, 4. Januar 2010 1:02 13
QApplication, QCoreApplication und die Kommandozeile
Wer bereits versucht hat, Konsolen-Anwendungen mit Qt zu erstellen, der dürfte sich besonders unter Windows das eine oder andere Mal die Zähne ausgebissen haben. Ein Beispiel sehen Sie hier: 00 01 02 03 04 05
// beispiele/cmdline/main.cpp #include #include #include #include using namespace std;
06 int main(int argc, char *argv[]) { 07 QCoreApplication app(argc, argv); 08 QStringList args = app.arguments(); 09 QStringList::const_iterator constIt; 10 cout pageAction(QWebPage::Back)); toolBar->addAction(view->pageAction(QWebPage::Forward)); toolBar->addAction(view->pageAction(QWebPage::Reload)); toolBar->addAction(view->pageAction(QWebPage::Stop)); toolBar->addWidget(urlEdit);
26 27
// Demonstriert den Zugriff auf QWebSettings. QMenu *effectMenu = menuBar()->addMenu( tr("&Einstellungen") ); effectMenu->addAction( "Schriftgröße ändern", this, SLOT(fontSize() ) ); effectMenu->addAction( "Kein Bilder laden", this, SLOT(noImage() ) );
28 29
30 31 32 33 }
760
// eine Statusbar (void*)statusBar(); setCentralWidget(view);
1542.book Seite 761 Montag, 4. Januar 2010 1:02 13
QtWebKit-Module
34 35 36 37 38 39 40
// Hier kommt alles hin, wenn der Ladevorgang // einer Webseite gestartet wird. void MyWindow::startLoading() { QUrl url = QUrl(urlEdit->text()); view->load(url); view->setFocus(); }
41 // Hier kommt alles hin, was während des Ladevorgangs passiert. 42 void MyWindow::whileLoading() { 43 if (progress = 100) 44 setWindowTitle(view->title()); 45 else { 46 statusBar()->showMessage(QString("%1 (%2 %)"). arg(QUrl(view->url()).toString()).arg(progress)); 47 setWindowTitle(QString("[ %1 \%] %2 %"). arg(progress).arg(view->title())); 48 } 49 urlEdit->setText(view->url().toString()); 50 } 51 // Fortschrittszustand 52 void MyWindow::setProgress(int p) { 53 progress = p; 54 whileLoading(); 55 } 56 // Hier kommt alles hin, wenn der Ladevorgang beendet wurde. 57 void MyWindow::finishLoading(bool) { 58 progress = 100; 59 whileLoading(); 60 statusBar()->showMessage(tr("Fertig")); 61 } 62 // Demonstriert QWebSettings – hier mit der Schriftgröße. 63 void MyWindow::fontSize() { 64 QWebSettings *set = QWebSettings::globalSettings(); 65 set->setFontSize(QWebSettings::MinimumFontSize, 20); 66 QUrl url = QUrl(urlEdit->text()); 67 view->load(url); 68 view->setFocus(); 69 } 70 // Demonstriert QWebSettings – hier mit dem Laden 71 // einer Seite ohne Bilder.
761
12.6
1542.book Seite 762 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
72 void MyWindow::noImage() { 73 QWebSettings *set = QWebSettings::globalSettings(); 74 set->setAttribute(QWebSettings::AutoLoadImages ,false); 75 QUrl url = QUrl(urlEdit->text()); 76 view->load(url); 77 view->setFocus(); 78 }
In den Zeilen 07 bis 10 sehen Sie den typischen Vorgang zum Herunterladen der Webseite. Nachdem ein Objekt erzeugt wurde, wird mit QWebView::load() die Webseite heruntergeladen. Zum Anzeigen der Webseite wird die Methode QWebView::show() mehrmals im Listing verwendet. Anschließend werden in den Zeilen 11 bis 14 die Signale für unser QWebViewObjekt angelegt. Hierbei reagieren wir auf das Signal QWebView::titleChanged(), welches ausgelöst wird, wenn der Titel des Hauptframes geändert
wurde. In unserem Fall geschieht dies dann, wenn der Anwender eine neue Adresse eingegeben und bestätigt hat. Um auf den Fortschrittszustand beim Herunterladen einer Webseite zu reagieren, wird das Signal QWebView:: loadProgress() behandelt. Wenn die Seite komplett heruntergeladen wurde, wird das Signal QWebView::loadFinished() ausgelöst, wie im Beispiel zu sehen ist. Die Zeile für die Adresseingabe einer URL, die Signal-Slot-Behandlung, finden Sie in den Zeilen 15 bis 18. Eine Werkzeugleiste mit den typischen Navigationselementen eines Webbrowsers wird in den Zeilen 19 bis 24 angelegt. Einfache Menübefehle, die den Zugriff auf die Klasse QWebSettings demonstrieren, werden in den Zeilen 26 bis 29 eingerichtet. In den Zeilen 34 bis 40 finden Sie den Slot startLoading(), der aufgerufen wird, wenn der Anwender eine neue Adresse ins Textfeld eingegeben und mit (¢) bestätigt hat. Zunächst lesen Sie die URL aus dem Textfeld aus und laden diese im QWebView-Objekt. Am Ende setzten Sie mit setFocus() den Tastaturfocus auf das Objekt. In den Zeilen 41 bis 50 finden Sie sämtlichen Code, der ausgeführt wird, wenn die Webseite geladen wird. Im Beispiel handelt es sich hierbei lediglich um die Ausgabe des Fortschrittszustands der Seite, was hier in der Statusleiste und der Titelleiste ausgegeben wird. Die Zeilen 51 bis 55 behandeln den Slot des Fortschrittszustands selbst. Den Wert benötigen Sie für den Slot whileLoading() aus den Zeilen 41 bis 50. Alles, was gemacht wird, wenn der Ladevorgang fertig ist, finden Sie in den Zeilen 56 bis 61. In den Zeilen 62 bis 69 bzw. 70 bis 78 wird noch gezeigt, wie Sie
762
1542.book Seite 763 Montag, 4. Januar 2010 1:02 13
Das Qt-Ressourcen-System
die Klasse QWebSettings verwenden können. In diesem Beispiel wurde die minimale Schriftgröße auf »20« gesetzt. Beim zweiten Beispiel soll das Laden von Bildern bei Webseiten verhindert werden, so dass nur der reine Text der HTMLSeite angezeigt wird. Jetzt noch die Hauptfunktion dazu: 00 // beispiele/webbrowser/main.cpp 01 #include 02 #include "mainwindow.h" 03 int main(int argc, char * argv[]) { 04 QApplication app(argc, argv); 05 MyWindow *browser = new MyWindow; 06 browser->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 12.10
12.7
Der simple Webbrowser mithilfe der Klasse QWebView im Einsatz
Das Qt-Ressourcen-System
In vielen Programmbeispielen haben wir besonders bei den Grafiken wie Icons auf das Ressourcen-System von Qt zurückgegriffen, das seit der Version 4 vorhanden ist. Ihnen ist dieses Ressourcen-System eigentlich nur durch das Pfad-
763
12.7
1542.book Seite 764 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
präfix :/ (Doppelpunkt und dann ein Slash) aufgefallen (und natürlich, wenn Sie auf der Buch-DVD die Ressourcendatei gesichtet haben. Ressourcen-Verwaltung ist ja nichts Neues mehr und wird von vielen anderen GUI-Frameworks ähnlich verwendet. Mit einer Ressource haben Sie die Möglichkeit, binäre Dateien (bspw. Grafiken) in die ausführbare Datei einzubinden und Sie dort direkt zu verwenden. Meist werden diese Dateien in einem Unterverzeichnis gespeichert (wir verwendeten meistens images). Um dieses Ressourcen-System überhaupt verwenden zu können, benötigen wir auch eine Ressourcen-Datei, die in Qt mit der Dateiendung .qrc versehen wird. Die Ressource-Datei verwendet ein einfaches XML-Format und listet darin alle Dateien auf, die beim Programm der Laufzeit hinzugefügt werden können. Hier sehen Sie einen Auszug einer solchen Ressourcen-Datei: images/cancel.png images/folder_page_white.png images/page_white.png images/text_align_center.png images/text_align_justify.png images/text_align_left.png images/text_align_right.png images/text_underline.png
In diesem Beispiel befinden sich im Verzeichnis images mehrere PNG-Bilddateien. Um jetzt auf diese Ressource-Datei zuzugreifen, müssen wir sie in die Projektdatei (.pro) wie folgt eintragen (was auch automatisch geschieht, wenn sich die .qrc-Datei bereits im aktuellen Verzeichnis befindet und Sie "qmake -project" aufrufen): RESOURCES = ResourcenDateiName.qrc
Der mitgelieferte Ressourcen-Compiler »rcc« erstellt nun aus dieser Datei eine Objekt-Datei und fügt diese endgültig der Anwendung hinzu. Der Vorteil ist ganz klar: Man benötigt keine unzähligen Bilddateien mehr, sondern hat alles in einer Datei, womit auch das versehentliche Löschen oder Vergessen einer Datei entfällt. Ein weiterer Vorteil natürlich: Sie müssen sich nicht mehr viele Gedanken um das Heimatverzeichnis des Programms machen, wo die Dateien liegen könnten. Hier gibt es bspw. unter Linux einen Unterschied, wenn Sie das Programm aus der Kommandozeile oder per Mausklick starten.
764
1542.book Seite 765 Montag, 4. Januar 2010 1:02 13
Qt Phonon
Statt über einen Dateinamen wird jetzt immer über das Pfadpräfix :/ auf die Datei zurückgegriffen. Bspw.: end = new QAction( QIcon(":/images/cancel.png"), tr("B&eenden"), this);
Natürlich bezieht sich dieses Ressourcen-System nicht nur auf Grafiken und kann bei Dateien aller Art verwendet werden. Die Pfadangabe können Sie überall dort verwenden, wo Sie eine Pfadangabe machen (bspw. QFile, QDir, QPixmap, QImage etc.). Natürlich besteht auch die Möglichkeit, bei den Ressourcen-Dateien eine andere Sprache zu berücksichtigen. Haben Sie bspw. Icons in anderen Sprachen, bspw. den Buchstaben »K« für Kursiv und die englische Alternative »I« für Invers, können Sie die Ressourcen-Datei folgendermaßen erstellen: kursiv.jpg kursiv_en.jpg
Durch die Verwendung des alias bei der englischen Version wird bei der Übersetzung auf ein System, wo die Lokale auf en_En gesetzt ist, automatisch das Icon kursiv_en.jpg verwendet, auch wenn im Code :/kursiv.jpg gesetzt war. Hinweis Mehr über das Ressourcen-System von Qt finden Sie in der entsprechenden Dokumentation von Qt.
12.8
Qt Phonon
Phonon ist die Multimediaschnittstelle zur Entwicklung von Audio- und VideoAnwendungen von KDE und wird auch seit Qt 4.4 verwendet. Um allerdings gleich zu hohe Erwartungen zu bremsen: Phonon bietet kein eigenes Backend. Die Multimedia-Funktionen werden also nicht von Phonon selbst implementiert, sondern von einem Backend. Somit ist Phonon eine Wrapperschicht (oder auch Hüllenklasse) um die plattformabhängigen Multimediafunktionalitäten. Als Backend benötigt Phonon (abhängig vom Betriebssystem):
765
12.8
1542.book Seite 766 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
왘
DirectShow (welches in DirectX enthalten ist) unter Windows
왘
QuickTime unter Mac OS X
왘
Qstreamer unter Linux
Abhängig vom darunterliegenden Backend entscheidet sich, welche Media-Dateiformate unterstützt werden (AVI, mp3, ogg usw.). Den entsprechenden Quelltext, um die erforderlichen Bibliotheken bauen zu können, finden Sie im Quellverzeichnis \src\plugins\phonon. ds9 steht hier für DirectX9, qt7 für QuickTime 7, und gstreamer erklärt sich selbst. Möglicherweise finden Sie hier noch weitere Ordner für weitere Backends. Phonon bauen Eine Anleitung dazu, wie Sie Phonon auf Ihrem System bauen können, finden Sie gewöhnlich in der Dokumentation. Um den Windows-Anwendern Frust zu ersparen, sei gleich darauf hingewiesen, dass Phonon sich nicht mit MinGW bauen lässt. Sie benötigen MS Visual C++ dazu.
Da an Phonon immer noch weiter gearbeitet wird, darf man sich in Zukunft noch auf weitere Funktionen, wie z. B. zum Schneiden oder Manipulieren von Mediadaten, freuen. Niedrigere Ebene Sind Sie auf der Suche nach Funktionalitäten in einer niedrigeren Ebene, steht Ihnen mit QtMultimedia ein Modul zur Verfügung, mit dem Sie z. B. direkten Zugriff auf ein Audiogerät haben. Mehr dazu finden Sie in der QtMultimedia-Dokumentation.
12.9
Animation Framework
Neu hinzugekommen in Version 4.6 von Qt ist das Animation Framework. Dieses Framework hilft Ihnen dabei, eine animierte grafische Oberfläche zu erstellen, ohne auf komplexe Techniken wie Timer usw. zuzugreifen. Es ist wirklich einfach, das Framework zu verwenden, um QObjects (inklusive QWidgets) zu animieren. Für manche mag das Spielerei sein, aber mittlerweile besitzt jede moderne Software animierte Steuerelemente. Dieses Framework benutzt Funktionen wie start(), stop() oder pause(), um eine Animation spielend zu steuern. Das Framework verfügt außerdem über eine Schnittstelle zur neuen Qt Statemachine. Auch Qt Statemachine wurde mit der Version 4.6 von Qt neu eingeführt.
766
1542.book Seite 767 Montag, 4. Januar 2010 1:02 13
Animation Framework
Hier ein kurzer Überblick über die Architektur des Animation Frameworks und einige Bemerkungen dazu, wie Sie die Eigenschaften in einem Beispiel verwenden können.
QAbstractAnimation
QVariantAnimation
QPropertyAnimation
Abbildung 12.11
Als
QAnimationGroup
QParallelAnimationGroup
QSequentialAnimationGroup
Die Architektur des Animation Frameworks
Grundlage
für
das
Animation
Framework
dienen
die
Basisklasse
QAbstractAnimation und die davon abgeleiteten Klassen QVariantAnimation
und QAnimationGroup. Die Klasse QAbstractAnimation enthält alle wichtigen Eigenschaften für das Framework (z. B. das Starten, Stoppen oder Anhalten einer Animation). Der einfachste Weg, ein Element zu animieren, dürfte über die Klasse QPropertyAnimation gehen. Hier legen Sie die Eigenschaften für die Animation fest und deklarieren diese Eigenschaft als ein QObject. Beachten Sie, dass der Weg über ein QObject Ihnen erst die Freiheit gibt, fast alle Widgets und andere QObjects zu animieren. Wie dies mit der Klasse QPropertyAnimation funktioniert, will ich Ihnen anschließend mit einem einfachen Beispiel zeigen. Mit der Klasse QAnimationGroups und seinen zwei Subklassen finden Sie einen Container für mehrere Animationen vor. Hiermit können Sie praktisch mehrere Animationen parallel (QParallelAnimationGroup) oder in der Sequenz (QSequentialAnimationGroup) ausführen. Zusätzlich zu diesen Klassen finden Sie hier noch Klasse QEasingCurve vor, mit der Sie über (vordefinierte) Kurven die Animationen kontrollieren bzw. bewegen können. Für den zeitlichen Ablauf der Animationen werden Sie außerdem noch die Klasse QTimeLine benötigen. Hierzu ein einfaches Beispiel, welches Sie vielleicht noch vom ersten Teil des Buches her in Erinnerung haben. Die »Hallo Welt«-Schaltfläche, die jetzt allerdings zusätzlich von der linken oberen Ecke zur Bildschirmmitte und wieder zurück bewegt wird. Hier das Listing dazu:
767
12.9
1542.book Seite 768 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
00 01 02 03
// beispiele/animation/main.cpp #include #include #include
04 int main(int argc, char *argv[]) { 05 QApplication app(argc, argv); 06 // eine einfache Schaltfläche 07 QPushButton hello("Hallo Welt"); 08 hello.resize(100, 30); 09 // Anzeigen 10 hello.show(); 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// die Klasse für die Eigenschaften einer Animation // im Bezug auf die Schaltfläche anlegen QPropertyAnimation animation(&hello, "geometry"); // Dauer der Animation in Millisekunden festlegen animation.setDuration(10000); // Startposition der Animation animation.setKeyValueAt(0, QRect(50, 50, 100, 30)); // an die Position (400, 400) in 8 Sekunden // Button dabei auf die Maße 200x130 "aufblasen" animation.setKeyValueAt(0.8, QRect(400, 400, 200, 130)); // Endposition und Button auf Normalgröße (100x30) animation.setKeyValueAt(1, QRect(50, 50, 100, 30)); // Mit Easing Curves sind spezielle Animationen möglich. animation.setEasingCurve(QEasingCurve::OutBounce); // Animation starten animation.start();
27 28 }
return app.exec();
Das Programm bei der Ausführung (siehe Abbildung 12.12). Das Hauptaugenmerk sollte hier auf die Zeilen 11 bis 26 gelegt werden. Zunächst stellen Sie in Zeile 13 eine Verknüpfung zur Schaltfläche und zu einer Animationseigenschaft her. In Zeile 15 legen Sie fest, wie viele Millisekunden diese Animation dauern soll. Eine Zeile später, in 17, geben Sie den Startpunkt der Animation an. Mit dem ersten Wert von QVariantAnimation::setKeyValueAt() geben Sie den Schrittwert an, der zwischen 0 und 1 liegen muss. Bezogen auf die Zeitlinie, die Sie in Zeile
768
1542.book Seite 769 Montag, 4. Januar 2010 1:02 13
Animation Framework
15 festgelegt haben, bedeutet hier die 0, dass dies der Anfang der Animation ist. Mit dem zweiten Wert geben Sie über QRect() die Startposition (400, 400) und Größe der Schaltfläche (100, 30) an. Statt QRect() können Sie an dieser Stelle auch andere QVariant-Klassen verwenden (z. B. Double, Float, QLine, QLineF, QPoint, QSize, QRectF usw.).
Abbildung 12.12
Unser Button schwebt hier praktisch über den Bildschirm.
In Zeile 20 legen Sie einen zweiten Punkt der Animation fest. Hier bestimmen Sie, dass diese Position (400, 400) in 80 % der Zeit (0.8), hier also in 8 Sekunden erreicht sein soll. Außerdem »pumpen« wir die Schaltfläche auf eine Größe von 200x130 auf. In Zeile 22 legen wir noch eine letzte Positionsangabe fest und reduzieren die Schaltfläche wieder auf den Ursprungszustand. In Zeile 24 legen wir noch anhand eines Kurventyps fest, in welcher Weise die Animation über den Bildschirm schwebt. Sie finden in der Klasse QEasingCurve weitere Typen definiert, die Sie hier gerne verwenden können. In Zeile 26 wird die Animationseigenschafts-Klasse, die ja mit der Schaltfläche verknüpft wurde, gestartet. Es werden während der nächsten 10000 Millisekunden die Vorgaben der Zeilen 15 bis 24 ausgeführt. Noch mehr zum Animation Framework Zugegeben, diese Einführung berührt nur einen schmalen Bereich der Dimensionen des Animation Frameworks. Mit dem hier gezeigten Weg über die Klasse QPropertyAnimation dürften Sie aber in den meisten Fällen ans Ziel kommen. Die Handhabung ist, wie Sie gesehen haben, kinderleicht. Außerdem bietet das Qt-Framework eine sehr gute Dokumentation zum Animation Framework und viele nützliche Beispiele an, welche diese Klassen in ihrer praktischen Anwendung demonstrieren.
769
12.9
1542.book Seite 770 Montag, 4. Januar 2010 1:02 13
12
Weiteres zu Qt
12.10
Weitere Klassen im Schnelldurchlauf
Der Umfang von Qt ist wirklich beeindruckend. Nicht alles kann ich in diesem Buch erwähnen, doch sollen wenigstens noch einige weitere Klassen und Frameworks angesprochen werden. Ein Blick in die Dokumentation von Qt liefert Ihnen weitere ausführliche Informationen.
12.10.1
Multitouch- und Gestensteuerung
Derzeit lässt sich der Trend beobachten, Rechner vermehrt mit einem Touchscreen auszurüsten. Selbst Notebooks werden mit solchen Bildschirmen ausgestattet. Ganz klar, dass sich auch das Qt-Framework für diesen Trend rüstet und dem Entwickler die Möglichkeit gibt, mit der Klasse QTouchEvent den neuen Anforderungen gerecht zu werden. Mithilfe dieser Klasse können Sie in Ihre Anwendungen eine fingerberührende Benutzerschnittstelle selbst einbauen und den Benutzer die Anwendung auch auf diese Weise steuern lassen. Die Multitouch- und Gestensteuerung wurde mit der Qt-Version 4.6 eingeführt. Beispiele hierzu wurden mit der Qt-Version mitgeliefert. Mehr Informationen dazu finden Sie in der Dokumentation von QTouchEvent.
12.10.2
State Machine Framework
Auf der Suche nach einer State Machine (zu deutsch etwa: Zustandsmaschine, endlicher Automat) werden Sie seit der Version 4.6 bei Qt ausgehend von der Basisklasse QAbstractState mit dem State Machine Framework fündig. Das Framework bietet ein Zustandsübergangsdiagramm basierend auf Harel statecharts und SCXML an.
12.10.3
Qt für Symbian S60
Neu seit der Version 4.6 ist auch die Integration von Qt für die Symbian-Plattform S60. Das Framework für die Symbian-Plattform enthält alle nötigen Funktionen (QtCore, QtGui, QtNetwork, QtScript, QtSql, QtSvg, QtWebKit, QtXml, QtTest und Phonon), um schöne GUI-Anwendungen für mobile Geräte mit dem Symbian-System zu entwickeln. Zum Zeitpunkt der Drucklegung dieses Buches lag zwar nur die Beta-Version des Symbian-Pakets vor, doch ließen sich damit fast problemlos Anwendungen für mein Nokia Express 5800 entwickeln und darauf ausführen. Sehr schön auch, dass Qt Creator 1.3 (derzeit ebenfalls noch in der Beta-Phase) Symbian als Entwicklungsziel unterstützt.
770
1542.book Seite 771 Montag, 4. Januar 2010 1:02 13
Qt Creator ist eine leichtgewichtige und sehr leistungsfähige Entwicklungsumgebung für GNU/Linux, Mac OS X und Microsoft Windows. Die Entwicklungsumgebung eignet sich bestens zum Erstellen von QtAnwendungen. Ein guter Grund also, diese Entwicklungsumgebung hier etwas näher zu erläutern.
13
Anwendungen mit Qt Creator erstellen
Qt Creator enthält keinen eigenen Compiler und verwendet stattdessen den GNU Compiler GCC bzw. G++ (unter Windows MinGW) zur Erstellung ausführbarer Programme. Wenn Sie das komplette Software Development Kit (SDK) von Qt verwenden, ist Qt Creator bereits enthalten und eingerichtet. Falls Sie nur die Bibliotheken und Entwicklungstools von Qt installiert haben, können Sie Qt Creator auch nachträglich herunterladen und installieren. Entsprechende Binaries oder auch den Source Code hierzu finden Sie wie immer unter http://qt.nokia.com/downloads.
13.1
Die Arbeitsoberfläche von Qt Creator
Sobald Sie Qt Creator gestartet haben, können Sie auf der linken Seite aus sechs verschiedenen Arbeitsmodi 1 (siehe folgende Abbildung) auswählen: 왘
Welcome – Hier wird ein Willkommensbildschirm angezeigt, über den Sie schnell die letzten Sitzungen und Projekte wieder aufrufen können. Auch die unzähligen Beispiele von Qt lassen sich hier öffnen und anschließend übersetzen und ausführen. Der Clou an den Projekten ist, dass sich jeweils ein zusätzliches Fenster öffnet, welches die wichtigen Codestellen des Projekts genauer beschreibt.
왘
Edit – In diesem Modus können Sie das Projekt und die Codedateien editieren. In einer weiteren Seitenleiste werden die einzelnen Dateien aufgelistet. Abbildung 13.1 zeigt den Edit-Modus bei der Ausführung.
왘
Debug – Für die Suche nach Fehlern finden Sie natürlich auch hier, wie es sich für eine Entwicklungsumgebung gehört, einen Debug-Modus.
771
1542.book Seite 772 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
왘
Projects – Hier können Sie alle Einstellungen zu einem Projekt vornehmen, und zwar vom Bauen einer Debug- oder Release-Version bis hin zur Ausführung. Wenn mal etwas nicht klappt oder fehlt, können Sie hier nachsehen.
왘
Help – Hier finden Sie die komplette Dokumentation, die vom Qt Assistant registriert ist, und Dokumentationen zu verschiedenen Qt-Tools (inklusive Qt Creator). Der beträchtliche Umfang der Dokumentationen hat viel dazu beigetragen, dass Qt mittlerweile eines der beliebtesten Frameworks überhaupt ist.
왘
Output – Die Ausgaben, die beim Bauen, Kompilieren und Verlinken erzeugt werden, finden Sie hier. Diese Informationen können Sie auch über die vier Schaltflächen 2 (Build Issues, Search Results, Application Output, Compile Output) am unteren Rand des Bildschirmes aufrufen.
1
2 3
Abbildung 13.1
Qt Creator bei der Ausführung
Unter den sechs Arbeitsmodi finden Sie drei runde Schaltflächen 3, über die Sie Ihr Projekt ausführen (Run), debuggen (Debug) und bauen (Build all) können.
13.2
Qt-Beispiele verwenden
Nachdem Sie dieses Buch durchgearbeitet haben, werden Sie sicherlich auf der Suche nach Quellcodes zum Studieren sein. Hiervon liefert Qt eine gewaltige Menge mit. Klicken Sie einfach auf den Arbeitsmodus Welcome und wählen Sie
772
1542.book Seite 773 Montag, 4. Januar 2010 1:02 13
Qt-Beispiele verwenden
im Bereich Getting Started in der Dropdownliste das gewünschte Beispiel aus. Klicken Sie anschließend auf die Schaltfläche Open.
Abbildung 13.2 Im Arbeitsbereich »Welcome« finden Sie einen gewaltigen Fundus an Quellcodes, die sich kinderleicht übersetzen und ausführen lassen.
Wenn Sie eines der Qt-Beispiele aufrufen, wird das komplette Projekt mit allem, was dazu gehört, im Arbeitsmodus Edit geöffnet. Wollen Sie das Beispiel sofort übersetzen und ausführen, so genügt ein einfacher Mausklick auf die Run-Schaltfläche bzw. (Strg)+(R).
Abbildung 13.3
Eines der Qt-Beispiele wurde geöffnet.
773
13.2
1542.book Seite 774 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Der Clou dabei: Wenn Sie jetzt zum Arbeitsmodus Help wechseln, erhalten Sie eine komplette Beschreibung des geöffneten Beispiels.
Abbildung 13.4 Hier bleiben kaum noch Wünsche offen: QT Creator liefert unzählige Beispiele mit, die detailliert beschrieben werden.
Ich würde Ihnen auf jeden Fall empfehlen, sich die Beispiel anzusehen, die von Qt mitgeliefert werden. Es lohnt sich auch, damit zu experimentieren.
13.3
Der Editor von Qt Creator
Wie Sie mit dem Editor von Qt Creator ein einfaches Codebeispiel erstellen können, haben Sie bereits in Abschnitt 1.4.2 erfahren. An dieser Stelle will ich daher auf den Editor nur kurz eingehen. Der Editor bietet im Grunde alles, was das Entwicklerherz begehrt, um einen Code zu entwerfen, zu editieren und darin zu navigieren. Neben der Codehervorhebung (Syntax highlighting) bietet der Editor auch Autovervollständigung, Kontexthilfe und eine Fehlerüberprüfung während der Eingabe an. Die Kontexthilfe wird aktiviert, wenn Sie mit dem Mauscursor über einer Klasse oder einer entsprechenden Methode stehen bleiben. Betätigen Sie zusätzlich noch die Taste (F1), wird die Hilfe dazu aufgerufen. Auch etwaige Fehler werden hier gleich rot unterstrichen. In der Abbildung 13.6 fehlt bspw. das Semikolon vor der fehlerhaften Zeile.
774
1542.book Seite 775 Montag, 4. Januar 2010 1:02 13
Der Editor von Qt Creator
Abbildung 13.5 Autovervollständigung ist sehr hilfreich. Hier auf der Suche nach einer richtigen Klassenmethode von QTabWidget.
Abbildung 13.6
Die Kontexthilfe des Editors
Optionen für den Editor Die Optionen für den Editor können Sie über das Menü Tools 폷 Options unter Text Editor ändern bzw. anpassen.
13.3.1
Schneller durch den Code navigieren mit dem Locator
Die Suchleiste links unten in Qt Creator, auch »Locator« genannt, hat es in sich. Wenn Sie auf der Suche nach einer Datei sind, die Sie kürzlich geöffnet haben, brauchen Sie lediglich den Dateinamen oder einen Teil davon einzugeben. Auch die Verwendung des Wildcard-Zeichens * ist gestattet. Eine Liste aller Dateien, die dem Suchkriterium entsprechen, wird ausgegeben. Die Suche ist aber nicht nur auf Dateien beschränkt, wie Sie sehen können, wenn der Textcursor im leeren Textfeld blinkt (oder Sie (Strg)/(°)+(K) betätigen).
775
13.3
1542.book Seite 776 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Abbildung 13.7
Die Suchleiste wurde salopp »Locator« genannt, hat es aber in sich.
Die Filter lassen sich verwenden, indem Sie das entsprechende Präfix voransetzen – gewöhnlich ein einzelner Buchstabe. Hinter dem Präfix folgen ein Leerzeichen und dann der gewünschte Suchstring. Suchen Sie z. B. nach einer Klasse »GenericTab«, können Sie Folgendes in den Locator schreiben: c GenericTab
Diese Präfixe werden aufgelistet, wenn Sie die Tastenkombination (Strg)/ (°)+(K) betätigen. Folgende Filter sind derzeit vorhanden: Filter
Beschreibung
l
zu einer bestimmten Zeile springen
:
zu einem betimmten Symbol springen
?
bestimmte Hilfe im Index aufrufen
o
zu einem geöffneten Dokument springen
f
zu einer Datei im Dateisystem gehen (durchsucht das komplette Dateisystem)
a
zu einer Datei in irgendeinem aktuell geladenen Projekt springen
p
zu einer Datei im aktuell geladenen Projekt springen
c
zu Klassendefinition springen.
m
zu einer Methodendefinition springen
Tabelle 13.1
776
Vorhandene Filter für den Locator
1542.book Seite 777 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Eigene Filter für den Locator Wenn Sie im Locator auf das kleine Dreieck klicken, können Sie im Kontextmenü über Configure einen eigenen Filter erzeugen. Gleiches erreichen Sie auch über Tools 폷 Options mit dem Locator.
13.3.2 Tastenkombinationen Neben dem Locator lohnt es sich bei Qt Creator auch, einige Tastenkombinationen zu kennen, um schneller ans Ziel zu kommen. Die folgende Tabelle listet einige spezielle Tastenkombinationen und deren Bedeutungen auf (beim Mac müssen Sie die (°)-Taste statt der (Strg)-Taste drücken): Tasten
Bedeutung
(Strg)+(ª)+(/)
Zeile mit der Position des blinkenden Textcursors oder markierte Zeilen im Code auskommentieren bzw. Kommentarzeichen wieder entfernen
(Strg)+(ª)+(¼) (Strg)+(ª)+(½)
Zeile mit der Position des blinkenden Textcursors oder markierte Zeilen im Code eine Zeile noch oben bzw. unten verschieben
(ª)+(æ_)
eine Zeile löschen
(Strg)+(1)–(5)
zwischen den Arbeitsmodi umschalten
(Strg)+(L)
zu einer bestimmten Zeile springen
(Alt)+(1)–(4)
zwischen den verschiedenen Ausgaben umschalten
(Strg)+(R)
ausführen (ggf. mit übersetzen).
(F5)
Debuggen starten
(Strg)+(ª)+(B) Tabelle 13.2
13.4
alles bauen (ohne ausführen)
Nützliche Tastenkombinationen für Qt Creator, die man kennen sollte
Anwendungen mit dem Qt Designer entwerfen
Wenn man mit der grundlegenden Programmierung von Qt vertraut ist, kann man sich hinsetzen und sich seine Anwendungen mit dem Rapid Application Development Tool (RAD) des Qt Designers zusammenklicken. Dies ist gerade bei der Gestaltung, Anordnung und dem Layout der Widgets eine enorme Erleichterung. Einige Personen behaupten sogar, dass man mit den RAD-Tools ganz ohne Programmierkenntnisse eigene Anwendungen zusammenklicken könne. Nun, ganz so einfach ist dies auch wieder nicht. Die GUI mag man vielleicht noch hinbekommen, aber die Interaktion (Signale und Slots) mit dem Anwender muss ja
777
13.4
1542.book Seite 778 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
auch noch implementiert werden. RAD-Tools sind außerdem nicht für jede Aufgabe geeignet. Will man etwas Spezielles machen, muss man doch wieder auf den Editor zurückgreifen und seinen Code schreiben. Besonders häufig und gerne wird der Qt Designer beim Gestalten von Dialogen verwendet. Es ist aber seit der Version 4 von Qt auch möglich, ein Hauptfenster mit dem Designer zu erstellen.
13.4.1 Ein Dialogfenster erstellen Um ein neues Dialogfenster mit dem Designer zu erstellen, müssen Sie zunächst eine neue Datei anlegen. Rufen Sie File 폷 New File or Project bzw. (Strg)+(N) auf. Wählen Sie im sich öffnenden Dialog Qt Designer Form aus und bestätigen Sie mit der Schaltfläche Ok.
Abbildung 13.8
Eine neue Datei für den Designer erstellen
Im nächsten Schritt erscheint jetzt ein Fenster, in dem Sie festlegen müssen, was Sie erstellen wollen. Als Beispiel erzeugen wir eine neue Dialogbox mit den Buttons an der unteren Seite (hier der erste Eintrag bei den Templates). Drücken Sie als Nächstes auf den Button Next. Im nächsten Dialog werden Sie nach dem Namen und Speicherort für das Dialogfenster gefragt.
778
1542.book Seite 779 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.9
Abbildung 13.10
Ein Template auswählen
Name und Speicherort für die UI-Datei
Den letzten Dialog Project management können Sie mit der Schaltfläche Finish bestätigen. Nun sollten Sie im Designer den ersten Entwurf der Dialogbox sehen (siehe Abbildung 13.11).
779
13.4
1542.book Seite 780 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Abbildung 13.11
Eine leere Dialogbox im Qt Designer
Die Button-Box in diesem Dialog benötigen wir nun nicht länger. Wir wollen sie durch einen eigenen QPushButton ersetzen. Zum Entfernen müssen Sie einfach auf die Button-Box im Designer klicken. Dadurch wird die Button-Box mit einem Rahmen markiert (siehe Abbildung 13.12).
Abbildung 13.12
Markieren der Button-Box
Jetzt können Sie mit der Taste (æ_) bzw. (Entf) die Button-Box aus dem Dialog entfernen. Als Nächstes wählen Sie im Dock-Fenster in der Widget-Box (siehe Abbildung 13.13) Push Button aus und ziehen das Symbol in die Dialogbox.
780
1542.book Seite 781 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.13 In der Widget-Box auf der linken Seite werden die Widgets ausgewählt und per Drag & Drop in den Dialog gezogen.
Von der Widget-Box aus ziehen Sie (per Drag und Drop) jetzt weitere Widgets in Ihre Dialogbox. In unserem Fall verwenden wir zwei weitere Labels, ein einzeiliges Textfeld, eine Spin-Box, eine Group-Box und zwei Radio-Buttons. Zusätzlich benötigen wir noch einen horizontalen und einen vertikalen Spacer. Am Ende erhalten Sie in etwa folgende Abbildung:
Abbildung 13.14
Ein erster grober Entwurf unserer Dialogbox
Zunächst ist noch nicht wichtig, dass alles millimetergenau auf seinem Platz ist. Das erledigen wir im nächsten Arbeitsschritt. Priorität hat zunächst das Layout für die beiden Text-Labels, das einzeilige Eingabefeld und die Spin-Box. Hierzu markieren Sie mit gedrückter linker Maustaste die vier genannten Widgets. Sie können diese auch mit gedrückter (Strg)-Taste (bzw. beim Mac: (°)-Taste) einzeln anklicken (siehe Abbildung 13.15).
781
13.4
1542.book Seite 782 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Abbildung 13.15
Markieren der Widgets für das Layout
Zur Anordnung der vier Widgets bietet sich das Grid-Layout an. Hierfür gibt es in der Werkzeugleiste einen entsprechenden Button (siehe Abbildung 13.16). Alternativ können Sie die Tastenkombination (Strg)+(G) verwenden.
Abbildung 13.16
Das Grid-Layout verwenden
Wenn Sie das Grid-Layout aktiviert haben, erhalten Sie eine saubere Formatierung der markierten Widgets (siehe Abbildung 13.17). Ähnliches wollen wir jetzt auch mit der Group-Box und den Radio-Buttons machen, nur das wir hierbei statt des Grid-Layouts ein vertikales Layout (Tastenkombination: (Strg)+(L)) verwenden. Also zunächst wieder die entsprechenden Widgets markieren und das vertikale Layout auswählen (siehe Abbildung 13.18 und 13.19)!
782
1542.book Seite 783 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.17
Formatierung mit einem Grid-Layout
Abbildung 13.18
Die Widgets markieren
Abbildung 13.19
Ein sauberes Layout der Group-Box mit den Radio-Buttons
783
13.4
1542.book Seite 784 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Gleiches habe ich auch noch unten mit dem horizontalen Spacer, dem Push-Button und einem horizontalen Layout (Tastenkombination (Strg)+(H)) gemacht. Jetzt können Sie alle Element nochmals markieren (siehe Abbildung 13.20).
Abbildung 13.20
Nochmals alle layouteten Widgets markieren
Jetzt verwende ich nochmals ein vertikales Layout ((Strg)+(L)), und die Dialogbox kann sich allmählich sehen lassen (siehe Abbildung 13.21).
Abbildung 13.21
Fertiges Layout der einzelnen Widgets
Befehle über das Menü Natürlich können Sie sämtliche Befehle des Designers auch über das Menü Tools 폷 Form Editor aufrufen.
Jetzt können Sie noch die Größe der Dialogbox oder auch der vertikalen Layouts ein wenig anpassen und sich anschließend das fertige Widget über Tools 폷 Form Editor 폷 Preview oder die Tastenkombination (Alt)+(Strg)+(R) ansehen (siehe Abbildung 13.22).
784
1542.book Seite 785 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.22
Vorschau der Dialogbox
Vorschau in anderen Stilen Wenn Sie wollen, können Sie auch über das Menü Tools 폷 Form Editor 폷 Preview die Vorschau in anderen Stilen (Motif, CDE usw.) betrachten.
Im Designer können Sie sich jetzt die Hierarchie der einzelnen Widgets im DockWidget Object Inspector ansehen (siehe Abbildung 13.23).
Abbildung 13.23
Der Objekt Inspector bringt Übersichtlichkeit in die Hierarchie
Um die Eigenschaften der Widgets zu bearbeiten, können Sie auf einen entsprechenden Eintrag im Object Inspector klicken oder auch direkt in der Dialogbox auf das entsprechende Widget. Für das Anpassen der Eigenschaften des Widgets gibt es dann den Property Editor. Klicken Sie z. B. im Object Inspector auf
785
13.4
1542.book Seite 786 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
QDialog oder auf den Fensterrahmen, so listet der Property Editor alle Eigenschaften des Widgets für Sie auf (siehe Abbildung 13.24).
Abbildung 13.24
Der Property Editor zeigt die Eigenschaften von QDialog an.
Der Editor ist in zwei Spalten gegliedert. In der linken befindet sich der Name der Eigenschaft, in der rechten der entsprechende Wert. Der Wert ist es, den Sie ändern können. Wollen Sie z. B. einen modalen Dialog verwenden, klicken Sie einfach neben windowModality in die rechte Spalte. Es erscheint eine Liste mit den möglichen Werten für diese Eigenschaft. Aus dieser Liste können Sie nun auswählen (siehe Abbildung 13.25).
Abbildung 13.25
786
Wahl eines modalen Dialogs
1542.book Seite 787 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Um den Fenstertitel zu ändern, suchen Sie einfach nach der Eigenschaft windowTitle und tragen unter Value den entsprechenden Titel ein (siehe Abbildung 13.26).
Abbildung 13.26
Festlegen des Fenstertitels
So können Sie praktisch alle Beschriftungen und Eigenschaften eines Widgets anpassen. Auch exakte Werte – wie im Beispiel für die Spin-Box – lassen sich in dieser Weise festlegen (siehe Abbildung 13.27).
Abbildung 13.27
Eigenschaften der Spin-Box bearbeiten
Die Bearbeitung wirkt sich natürlich direkt auf die Dialogbox aus (siehe Abbildung 13.28).
787
13.4
1542.book Seite 788 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Abbildung 13.28
Nach einigen Anpassungen mit dem Property Editor
Nach einigen weiteren Anpassungen der Eigenschaften der Widgets sieht unsere Dialogbox in der Vorschau wie folgt aus:
Abbildung 13.29
Mit dem Designer zusammengeklickte Dialogbox
Wenn man ein wenig Übung mit dem Designer hat, lässt sich ein solcher Dialog in zwei bis drei Minuten zusammenklicken. Mit reinem C++-Code wäre man da schon ein wenig länger beschäftigt – besonders, was das Layout betrifft. Experimente erwünscht Die Verwendung des Property Editors lädt geradezu zum Herumexperimentieren ein – was man natürlich auf keinen Fall versäumen sollte, auch zu tun.
Auch für die Signal-Slot-Verbindungen sieht der Designer etwas Besonderes vor. Im Menü Edit finden Sie einen Eintrag Edit Signal/Slots, den Sie auch mit (F4)
788
1542.book Seite 789 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
aktivieren können. Zurück kommen Sie im selben Menü wieder mit Edit Widget oder (F3). Aktivieren Sie also jetzt mit (F4) das Bearbeiten einer Signal-Slot-Verbindung. Dann wählen Sie das Widget für das Signal aus und ziehen von dort mit gedrückt gehaltener linker Maustaste auf das entsprechende Widget für den Slot, wo Sie dann die Maustaste wieder loslassen können. Im Beispiel wollen wir auf ein Signal von QPushButton reagieren, und den Slot stellt der Dialog (QDialog) zur Verfügung (siehe Abbildung 12.30).
Abbildung 13.30
Eine Signal-Slot-Verbindung
Die Verbindung wird vom Designer angezeigt; angefangen beim Widget, welches das Signal auslöst, bis hin zum Widget, welches den Slot enthält. Für das ZielWidget wird ein Erdungssymbol angezeigt. Jetzt erscheint ein Dialog, in dem Sie sich die passende Signal-Slot-Verbindung aussuchen können (siehe Abbildung 13.31).
Abbildung 13.31
Signal-Slot-Verbindung auswählen
789
13.4
1542.book Seite 790 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Im Dialog werden allerdings nur die gängigsten Signal-Slot-Verbindungen angezeigt. Benötigen Sie alle vorhandenen Verbindungen, müssen Sie die Checkbox Show signal and slots inherited from QWidget aktivieren. Im Beispiel wurde für QPushButton das Signal clicked() eingerichtet, welches im Fall eines Auftretens mit dem Slot accept() von QDialog reagiert. Haben Sie die neue Signal-SlotVerbindung eingerichtet, wird dies im Dialog des Designers auch angezeigt (solange der Modus Edit Signal/Slots aktiv ist). Siehe hierzu auch die folgende Abbildung:
Abbildung 13.32
Übersicht der Signal-Slot-Verbindungen
Auch hierzu gibt es im Designer einen speziellen Editor: den Signals and Slots Editor (siehe Abbildung 13.33).
Abbildung 13.33
Signal-Slot-Editor
Natürlich können Sie eine Signal-Slot-Verbindung auch direkt mit dem Signals and Slots Editor einrichten bzw. vorhandene Verbindungen ändern oder löschen. Mit dem Minuszeichen unten entfernen Sie eine Signal-Slot-Verbindung. Mit dem Pluszeichen können Sie eine neue Verbindung einrichten. Sie müssen dazu in den entsprechenden Werten der einzelnen Spalten den Sender
790
1542.book Seite 791 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
unter Signal und den Empfänger unter Slot aus einer Liste auswählen (siehe Abbildung 13.34).
Abbildung 13.34 Signal-Slot-Verbindungen über den Signals and Slots Editor ändern bzw. anpassen
Ein weiteres Tool des Designers ist die Überprüfung der Tab-Reihenfolge. Viele Anwender verwenden bei Dialogen gerne die Tabulatortaste (ÿ_), um in das nächste Widget zu wechseln. Gerade wenn man mit dem Designer sein Layout zusammenklickt und das eine oder andere Widget hinzufügt, stimmt die Reihenfolge oft nicht mehr. Den Überblick verschaffen Sie sich über das Menü Edit 폷 Edit Tab Orders (siehe Abbildung 13.35).
Abbildung 13.35
Falsche Tab-Reihenfolge
Die Tabulator-Reihenfolge der Abbildung 13.35 dürfte wohl nicht die sein, die der Programmierer beabsichtigte. Zuerst wird der Radiobutton Weiblich angesprungen. Nach einem Tab geht es zu Geburtstag und nach einem weiteren Tab zu Name usw.
791
13.4
1542.book Seite 792 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Um die korrekte Reihenfolge einzurichten, klicken Sie einfach in die Zahlen, bis der gewünschte Wert darin erscheint. Dies wiederholen Sie mit den anderen Widgets so lange, bis Sie die gewünschte Reihenfolge erhalten (siehe Abbildung 13.36).
Abbildung 13.36
Jetzt ist die Tab-Reihenfolge korrekt.
Auch die Tastaturkürzel und Buddys können Sie über das Menü Edit 폷 Edit Buddies mit dem Designer einrichten. Hierbei geht man ähnlich wie beim Einrichten von Signal-Slot-Verbindungen vor. Wählen Sie das Label, das Sie verwenden wollen, und ziehen Sie dieses per Drag und Drop auf seinen Kumpel (Buddy) (siehe Abbildung 13.37).
Abbildung 13.37
Einzeiliges Eingabefeld als Buddy vom Label Name
Jetzt müssen Sie noch mit dem Property-Editor (bei der Eigenschaft text) vor dem Buchstaben, mit dem Sie auf das Zeilenfeld springen wollen, ein Ampersandzeichen (&) setzen, wodurch dieser Buchstabe dann im Label unterstrichen ist. Mit »&Name« wird in unserem Beispiel also der Buchstabe »N« unterstrichen, so dass man mit der Tastenkombination (Alt)+(N) direkt auf das Zeilenfeld springen kann.
792
1542.book Seite 793 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.38 Mit »&« vor »Name« kann künftig mit (Alt)+(N) direkt auf das entsprechende Editierfeld gesprungen werden.
Designer-Datei in einer Qt-Anwendung verwenden Als Nächstes müssen Sie den erstellten Dialog abspeichern. Dies machen Sie über das Menü File 폷 Save "xxx.ui" bzw. unter einem anderen Namen über File 폷 Save as "xxx.ui" mit der Dateiendung .ui. Im Beispiel wurde die Datei unter dem Namen dialog.ui im Projektverzeichnis gespeichert. Diese Datei können Sie sich gerne mit einem Text-Editor ansehen. Dabei werden Sie feststellen, dass es sich hierbei um nichts anderes als um eine Datei im XML-Format handelt. In die Projektdatei (.pro) wird diese Datei wie folgt hinzugefügt: FORMS += dialog.ui
Weitere Designer-Dateien (.ui) können Sie, getrennt mit einer Leerzeile, hinten anfügen. Wenn Sie nach qmake -project und qmake anschließend make aufrufen, wird für die UI-Datei der UI-Compiler (User Interface Compiler; kurz uic) verwendet. Dieser spezielle Compiler erstellt aus der .ui-Datei eine Headerdatei mit dem entsprechenden Code. Der Name der Datei hängt vom Namen der gespeicherten .ui-Datei ab. In unserem Fall wird aus der .ui-Datei dialog.ui die Headerdatei mit dem Namen ui_dialog.h erstellt. Es wird also stets aus NAME.ui der Headername ui_NAME.h generiert. Logischerweise müssen Sie in dem Quellcode, in dem Sie den Dialog verwenden wollen, die entsprechende Headerdatei mit #include hinzufügen. Das machen Sie am besten gleich noch vor dem qmake-Aufruf, um Probleme mit dem UI-Compiler zu vermeiden. Die Headerdatei (in unserem Fall ui_dialog.h) können Sie sich gerne mit Ihrem Editor ansehen. Ein auf das Grundgerüst gekürzter Ausschnitt davon:
793
13.4
1542.book Seite 794 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
#ifndef UI_DIALOG_H #define UI_DIALOG_H // viele Headerdateien class Ui_Dialog { public: QWidget *widget; QVBoxLayout *vboxLayout; ... void setupUi(QDialog *Dialog) ... } // setupUi
{
void retranslateUi(QDialog *Dialog) { ... } // retranslateUi }; namespace Ui { class Dialog: public Ui_Dialog {}; } // namespace Ui #endif // UI_DIALOG_H
In der vom UI-Compiler generierten Klasse sind generell immer nur die beiden Klassen setupUi() und retranslateUi() enthalten. Die Klasse setupUi() enthält alles, was die GUI erzeugt, und die Klasse retranslateUi() kann verwendet werden, wenn das Programm in einer anderen oder mehreren Sprache verwendet werden soll. Beide Methoden erhalten als Parameter einen Zeiger auf die Klasse, welche auf die vom Designer erstellte GUI angewendet werden soll. Der Klassenname (hier Ui_Dialog) wird in dem objectName-Attribut des Dialogs gesetzt, welchen Sie im Designer verwendet haben (siehe Abbildung 13.39).
Abbildung 13.39
794
Das objectName-Attribut der Klasse QObject legt den Klassennamen fest.
1542.book Seite 795 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Sollten Sie den Namen aus Versehen löschen oder sollte das Feld leer bleiben, so würden Sie die vom Designer erstellte Headerdatei nicht verwenden können, weil Sie eine anonyme Klasse ohne Namen erzeugt hätten. Der UI-Compiler stellt dem Klassennamen dann noch ein Ui_ voran, so dass aus dem Objektnamen Dialog im Beispiel die Klasse Ui_Dialog wird. Um anschließend doch wieder mit dem Objektnamen auf die vom Designer entwickelte GUI zurückgreifen zu können, wird ein eigener Namensraum Ui generiert und eine Klasse Dialog öffentlich von Ui_Dialog abgeleitet. In der Praxis bedeutet dies, dass Sie folgendermaßen ein Objekt des vom Designer entworfenen Dialogs erzeugen können: Ui::Dialog myUi;
Als zweite Möglichkeit steht Ihnen natürlich nach wie vor der vom Designer außerhalb des Namensbereiches Ui generierte Namensbereich Ui_Dialog zur Verfügung: Ui_Dialog myUi;
Die vom Designer generierten Klassen sind also immer über Ui::Klassenname oder Ui_Klassenname verfügbar. Jetzt wollen wir die vom Designer erzeugte Klasse in einem einfachen Programm verwenden. Zunächst das Grundgerüst: 00 01 02 03 04 05
// beispiele/uiexample/form.h #ifndef FORM_H #define FORM_H #include #include #include "ui_Dialog.h"
06 07 08 09 10 11 12 13 14 15 16
class myDialog : public QDialog { Q_OBJECT public: myDialog(); ~myDialog() {} private: Ui::Dialog ui; private slots: void analyseDialog(); }; #endif
795
13.4
1542.book Seite 796 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
In Zeile 12 befindet sich die Deklaration der vom UI-Compiler generierten Klasse als privates Objekt. Natürlich darf die entsprechende Headerdatei (Zeile 5) nicht fehlen, in der diese Klasse auch definiert ist. Jetzt folgt die Implementierung des Codes: 00 // beispiele/uiexample/form.cpp 01 #include "form.h" 02 myDialog::myDialog (){ 03 ui.setupUi(this); 04 connect( ui.pushButton, SIGNAL( clicked() ), this, SLOT(analyseDialog()) ); 05 } 06 void myDialog::analyseDialog() { 07 QString str; 08 if( ui.lineEdit->text().isEmpty()) 09 str.append("Name: keine Angaben\n"); 10 else { 11 str.append("Name: "); 12 str.append(ui.lineEdit->text()); 13 str.append("\n"); 14 } 15 if( ui.spinBox->cleanText().isEmpty()) 16 str.append("Geburtsdatum: keine Angaben\n"); 17 else { 18 str.append("Geburtsdatum: "); 19 str.append(ui.spinBox->cleanText()); 20 str.append("\n"); 21 } 22 if( ui.radioButton->isChecked()) 23 str.append("Geschlecht: weiblich\n"); 24 else 25 str.append("Geschlecht: männlich\n"); 26 QMessageBox::information( 0, tr("Die Auswertung"), str ); 27 }
Das Wichtigste und Erste, was wir im Konstruktor erledigen, ist setupUi() aufzurufen, um überhaupt die UI-Klasse verwenden zu können. Wenn Sie setupUi() nämlich zu spät aufrufen und das Programm auf eine Mitgliedsvariable der vom Designer generierten Oberfläche zurückgreift, so stürzt das Programm ab, weil auf einen undefinierten Speicherbereich zugegriffen wird.
796
1542.book Seite 797 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Zugriff auf die Bezeichner Auch hier gilt für den Zugriff auf die einzelnen Bezeichner im Designer, dass diese aus dem Attribut objectName generiert werden. Ein Blick in ui_DATEI.h hilft weiter, wenn man die Namen nicht mehr genau weiß oder dem Designer die Benennung überlassen hatte.
Jetzt fehlt uns nur noch eine Hauptfunktion, um unsere GUI auszuführen: 00 // beispiele/uiexample/main.cpp 01 #include 02 #include "form.h" 03 int main(int argc, char *argv[]) { 04 QApplication app(argc, argv); 05 myDialog *dlg = new myDialog; 06 dlg->show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 13.40 Der Dialog bei der Ausführung
Abbildung 13.41 Die Auswertung des Dialogs
Auf UI-Elemente ohne Hilfsvariable zugreifen Neben der eben gezeigten Möglichkeit können Sie die vom Designer generierte Klasse auch durch eine Mehrfachvererbung in einer eigenen Subklasse verwenden. Der Vorteil dabei ist, dass Sie die Widgets der vom UI-Compiler generierten Klasse ohne Hilfsvariable (Ui::Klassenname) und ohne Umwege verwenden können. Hierzu das Grundgerüst:
797
13.4
1542.book Seite 798 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
00 01 02 03 04 05
// beispiele/uiexample2/form.h #ifndef FORM_H #define FORM_H #include #include #include "ui_Dialog.h"
06 07 08 09 10 11 12 13 14
class myDialog : public QDialog, private Ui::Dialog { Q_OBJECT public: myDialog(); ~myDialog() {} private slots: void analyseDialog(); }; #endif
Am Ende von Zeile 6 tritt eine private Vererbung unserer Designerklasse in Aktion. Indem die Klasse privat vererbt wird, bleibt auch der OOP-Ansatz mit seiner Verkapselung erhalten. Jetzt können Sie innerhalb der Klasse myDialog ohne Umwege auf die Mitglieder der Designerklasse zugreifen. Hier die Implementierung: 00 // beispiele/uiexample2/form.cpp 01 #include "form.h" 02 myDialog::myDialog (){ 03 setupUi(this); 04 connect( pushButton, SIGNAL( clicked() ), this, SLOT(analyseDialog()) ); 05 } 06 void myDialog::analyseDialog() { 07 QString str; 08 if( lineEdit->text().isEmpty()) 09 str.append("Name: keine Angaben\n"); 10 else { 11 str.append("Name: "); 12 str.append(lineEdit->text()); 14 str.append("\n"); 15 } 16 if( spinBox->cleanText().isEmpty()) 17 str.append("Geburtsdatum: keine Angaben\n"); 18 else { 19 str.append("Geburtsdatum: "); 20 str.append(spinBox->cleanText());
798
1542.book Seite 799 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
21 22 23 24 25 26 27
str.append("\n"); } if( radioButton->isChecked()) str.append("Geschlecht: weiblich\n"); else str.append("Geschlecht: männlich\n"); QMessageBox::information( 0, tr("Die Auswertung"), str );
28 }
Reihenfolge beachten Damit die Mehrfachvererbung auch funktioniert und keine Probleme mit dem MetaCompiler auftreten, müssen Sie die Reihenfolge einhalten. Zuerst muss hier die Klasse von QDialog und dann von der Designer-Klasse erben.
13.4.2 Ein Hauptfenster mit dem Designer entwerfen Um ein neues Hauptfenster mit dem Designer zu erstellen, müssen Sie wiederum zunächst eine neue Datei erzeugen. Rufen Sie File 폷 New File or Project bzw. (Strg)+(N) auf. Wählen Sie im sich öffnenden Dialog Qt Designer Form aus und bestätigen Sie mit der Schaltfläche Ok. Im nächsten Schritt erscheint das Fenster, in dem Sie festlegen müssen, was Sie erstellen wollen. Wählen Sie diesmal Main Window aus (siehe Abbildung 13.42) und klicken Sie auf die Schaltfläche Next.
Abbildung 13.42
Ein neues Hauptfenster (Main Window) soll es sein.
799
13.4
1542.book Seite 800 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Im nächsten Dialog werden Sie nach dem Namen und Speicherort für das Dialogfenster gefragt. Anschließend müssen Sie den letzten Dialog nur noch mit der Schaltfläche Finish bestätigen. Im Object Inspector können Sie sich gleich einen Überblick verschaffen, was der Designer alles zu einem Hauptfenster hinzugefügt hat (siehe Abbildung 13.43). Es handelt sich um das Zentralwidget, eine Menüleiste und eine Statusbar.
Abbildung 13.43
Objekte nach dem Erzeugen eines Hauptfensters
Experimentieren erwünscht Natürlich gilt auch hier wieder die Devise: Probieren geht über Studieren! Testen Sie die vielen Möglichkeiten, die Einstellungen eines Hauptfensters über den Property Editor zu verändern.
Als Nächstes wollen wir dem Hauptfenster eine Menüleiste hinzufügen. Wählen Sie dazu den Eintrag Type Here in der Menüleiste (doppelklicken) (siehe Abbildung 13.44).
Abbildung 13.44
Menüleiste
Geben Sie jetzt den Titel ein, den das Menü haben soll. Im Beispiel erstellen wir zuerst das Menü Datei (siehe Abbildung 13.45). Mit einem Blick in den Object Inspector können Sie sich jederzeit einen Überblick über die Hierarchie verschaffen und (vor allem) behalten (siehe Abbildung 13.46).
800
1542.book Seite 801 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.45
Menü »Datei«
Abbildung 13.46
Object Inspector mit dem neuen Menü (QMenu)
Um dem Element weitere Menüeinträge hinzuzufügen, brauchen Sie wiederum nur Type Here anzuklicken (siehe Abbildung 13.47).
Abbildung 13.47
Neue Aktionen für das Menü
Im Beispiel wurden dem Menü Datei die Aktionen Öffnen, Speichern und Beenden spendiert. Um einen Separator einzufügen, brauchen Sie nur auf Add Separator zu klicken. Natürlich können Sie auch den Aktionen Attribute wie Tool-
801
13.4
1542.book Seite 802 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
tipp, Statustipp, Tastaturkürzel usw. hinzufügen. Hierzu wählen Sie am besten über den Object Inspector die entsprechende Aktion aus (siehe Abbildung 13.48).
Abbildung 13.48
Aktion zum Öffnen ausgewählt
Jetzt können Sie im Property Editor entsprechende Werte für die Eigenschaften vergeben (siehe Abbildung 13.49).
Abbildung 13.49
Tastaturkürzel (Strg)+(O) für »Öffnen« eingerichtet
Natürlich ist es auch möglich, bei den Menüeinträgen weitere Untermenüeinträge einzurichten. Hierzu müssen Sie nur auf das Plus-Symbol neben dem Menüeintrag klicken (siehe Abbildung 13.50).
802
1542.book Seite 803 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.50
Weitere Untermenüeinträge lassen sich einrichten.
Beim Designer finden Sie unten auch einen Action Editor vor, der alle vorhanden Aktionen des Hauptfensters anzeigt (Abbildung 13.51).
Abbildung 13.51
Übersicht über alle existierenden Aktionen (QAction)
Durch ein Doppelklicken im Action Editor können Sie auch ein Icon, eine Tastenkombination oder einen ToolTip vergeben, falls Sie das nicht bereits im Property Editor gemacht haben. Die Icons werden dann auch gleich bei den Menüeinträgen verwendet (siehe Abbildung 13.52).
Abbildung 13.52
Icons für die Menüeinträge
803
13.4
1542.book Seite 804 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Das Hinzufügen einer Werkzeugleiste ist ebenfalls recht einfach (wenn man weiß wie). Klicken Sie dazu auf einen leeren Bereich des UI-Fensters mit der rechten Maustaste und wählen Sie im Kontextmenü den Eintrag Add Tool bar aus. Unter dem Menü befindet sich nun eine schmale Leiste (siehe Abbildung 13.53).
Abbildung 13.53
Eine leere Toolbar wurde hinzugefügt.
Da wir ja die Aktionen bereits im Menü festgelegt haben, müssen wir die gewünschten Aktionen der Werkzeugleiste nun nur noch aus dem Action Editor in die Werkzeugleiste ziehen (siehe Abbildung 13.54). Mit einem Rechtsklick in die Werkzeugleiste erscheint ein Kontextmenü, in dem Sie einen Separator zur Leiste hinzufügen können.
Abbildung 13.54
Aktionen werden aus dem Action Editor in die Werkzeugleiste gezogen.
Um den Überblick zu behalten, sollten man immer wieder mal einen Blick in den Object Inspector werfen (siehe Abbildung 13.55).
804
1542.book Seite 805 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Abbildung 13.55
Der Object Inspector sorgt für Übersicht.
Zu guter Letzt wollen wir noch ein mehrzeiliges Textfeld als Zentralwidget einfügen. Dieses Widget müssen Sie nur von der Widget-Box in das Hauptfenster ziehen und dann die Größe entsprechend anpassen (siehe Abbildung 13.56).
Abbildung 13.56
QtextEdit als Zentralwidget
Ein Blick auf den Object Inspector zeigt uns, dass QTextEdit das zentrale Widget unserer Anwendung ist (siehe Abbildung 13.57).
805
13.4
1542.book Seite 806 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
Abbildung 13.57
Ein letzter Überblick
Mit dem Designer erstelltes Hauptfenster verwenden Jetzt können Sie das erstellte Hauptfenster wieder im Projektverzeichnis abspeichern. Im Beispiel wird der Name mainForm.ui dafür verwendet. Ansonsten ist die Verwendung der vom Designer erstellten Klasse so wie schon im Abschnitt zuvor beim Dialog gezeigt wurde, nur dass Sie natürlich statt von einem QDialog von einem QMainWindow ableiten müssen. Hier das Grundgerüst: 01 02 03 04 05
// beispiele/mainForm/mainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include "ui_mainForm.h"
06 class MWindow : public QMainWindow, private Ui::MainWindow { Q_OBJECT public: MWindow(); ~MWindow() {} protected slots: void openFile(); void saveFile(); void info(); }; #endif
07 08 09 10 11 12 13 14 15 16
806
1542.book Seite 807 Montag, 4. Januar 2010 1:02 13
Anwendungen mit dem Qt Designer entwerfen
Jetzt zur Implementierung des Codes, wobei auch hier wieder im Konstruktor zuallererst setupUi() aufgerufen werden soll, damit Sie auf alle Membervariablen der Designer-Klasse Zugriff haben. Zusätzlich richten wir dabei die SignalSlot-Verbindungen ein und schreiben auch den Code für die entsprechenden Slots. 00 // beispiele/mainForm/mainWindow.cpp 01 #include "mainWindow.h" 02 MWindow::MWindow() { 03 setupUi(this); 04 // Signal-Slot-Verbindungen 05 connect( actionBeenden, SIGNAL(triggered(bool)), qApp, SLOT(quit())); 06 connect( action_ffnen, SIGNAL(triggered(bool)), this, SLOT(openFile())); 07 connect( actionSpeichern, SIGNAL(triggered(bool)), this, SLOT(saveFile())); 08 connect( action_ber, SIGNAL(triggered(bool)), this, SLOT(info())); 09 } 10 void MWindow::openFile() { 11 QString fileName = QFileDialog::getOpenFileName(this); 12 QFile file(fileName); 13 if( file.open(QIODevice::ReadOnly|QIODevice::Text)) { 14 textEdit->setPlainText( QString::fromUtf8(file.readAll() ) ); 15 statusBar()->showMessage( tr("Datei erfolgreich geladen"), 5000); 16 } 17 } 18 void MWindow::saveFile() { 19 QString fileName = QFileDialog::getSaveFileName(this); 20 if( fileName.isEmpty() ) return; 21 QFile file(fileName); 22 if( file.open(QIODevice::WriteOnly|QIODevice::Text) ){ 23 file.write(textEdit->toPlainText().toUtf8()); 24 statusBar()->showMessage( tr("Datei erfolgreich gespeichert"), 5000); 25 } 26 }
807
13.4
1542.book Seite 808 Montag, 4. Januar 2010 1:02 13
13
Anwendungen mit Qt Creator erstellen
27 void MWindow::info() { 28 QMessageBox::information( 0, tr("Über..."), tr("Ein kleiner Texteditor\n" "der mit dem Designer erstellt wurde")); 29 }
Jetzt noch das Hauptprogramm: 00 // beispiele/mainForm/main.cpp 01 #include "mainWindow.h" 02 #include 03 int main(int argc, char*argv[] ) { 04 QApplication app(argc, argv); 05 MWindow window; 06 window.show(); 07 return app.exec(); 08 }
Das Programm bei der Ausführung:
Abbildung 13.58
13.5
Der Texteditor bei der Ausführung
Mehrere Versionen von Qt verwenden
Da die Entwicklung von Qt recht schnell vorangeht und da man als Entwickler immer up to date sein muss, ist es mit Qt Creator selbstverständlich auch möglich, unterschiedliche Versionen von Qt zu verwenden und zwischen diesen hinund herzuschalten.
808
1542.book Seite 809 Montag, 4. Januar 2010 1:02 13
Mehrere Versionen von Qt verwenden
Weitere Versionen von Qt können Sie über das Menü Tools 폷 Options 폷 Qt4 폷 Qt Versions unter Windows und Linux bzw. bei Mac OS X über Qt Creator 폷 Einstellungen 폷 Qt4 폷 Qt Versions hinzufügen. Die Einstellungen und Pfade hängen hier selbstverständlich vom Betriebssystem ab.
Abbildung 13.59 Hier finden Sie alle nötigen »Hebel«, um weitere Qt-Versionen zu verwenden.
Wird Qt in der Umgebungsvariablen PATH gefunden, wird qmake als Auto-Detected angezeigt. Bei Linux und Mac OS X ist dies im Grunde immer der Fall. Ansonsten können Sie über das Plus-Symbol manuell den Pfad zu QMake angeben. Wenn Sie mehrere Bibliotheken verwenden, wird eshäufig nötig sein, die Qt-Version manuell nachzutragen. Über die Liste Default Qt Version können Sie dann die gewünschte Version auswählen, mit der Sie Ihren Quellcode übersetzen wollen. MinGW oder MSVC++ unter Windows Bei Windows müssen Sie Qt Creator zusätzlich noch mitteilen, wo MinGW installiert ist. Sollten Sie außerdem eine Qt-Version mit Microsoft Visual C++ kompiliert haben, setzt Qt Creator die Umgebungsvariablen automatisch ein.
809
13.5
1542.book Seite 810 Montag, 4. Januar 2010 1:02 13
1542.book Seite 811 Montag, 4. Januar 2010 1:02 13
Index .config-Eintrag 363 .pro 22 68
A Aktionen QAction 335 QActionGroup 343 Algorithmen 614, 621 kopieren 624 sortieren 621 suchen 622 Animation Framework 766 assert Q_ASSERT() 751 Ausgabe, Debugging 747
B Baummodell 289 Behälter-Widget 155 Beispiele übersetzen 772 Bibliotheken erstellen 741 linken 63 Bildbearbeitung 683 Binäre Daten 431 Button 133 QButtonGroup 154 QCheckBox 145 QPushButton 141 QRadioButton 151 QToolButton 154
C Check-Boxen 145 Condition-Variable 583 config-Eintrag 363 Container 614 QHash 620 QLinkList 616 QList 615 QMap 619
Container (Forts.) QMultiHash 620 QMultiMap 619 QQueue 618 QSet 620 QStack 617 QVector 616 Container-Widget 155 QFrame 169 QGroupBox 155 QTabWidget 159 QToolBox 176 Container-Widget 씮 Behälter-Widget
D Datei Ein- und Ausgabe 421 Informationen 473 QFile 421 QTemporaryFile 429 temporäre Dateien 429 Datenbanken 591 Daten abfragen 597, 603 Daten ändern 596, 602 Daten hinzufügen 595, 602 Daten löschen 603 Item-View 606 QSqlDatabase 593 QSqlQuery 594 QSqlTableModel 601, 606 QtSql 591 SQL-Anweisungen ausführen 594 Transaktionen 598 Treiber 591 Verbindung herstellen 593 Datentypen Qt 609 Datum 234, 257, 626 D-BUS 62 Debugging Ausgabe 747 Demos übersetzen 772 Designer 777 Hauptfenster entwerfen 799
811
1542.book Seite 812 Montag, 4. Januar 2010 1:02 13
Index
Dialoge Benutzerfreundlichkeit 110 Dateiauswahl 122 Druckerdialog 132 Eingabe 127 Erstellen 100 Farbauswahl 132 Layout 74 QColorDialog 132 QFileDialog 122 QFontDialog 131 QMessageBox 113 QPrintDialog 132 Schriftauswahl 131 vorgefertigte 113 Dialog-Fenster erstellen Qt-Designer 778 Direkthilfe 317 DLL erstellen 741 Dock-Widget 358 DOM 713 API 721 Drag und Drop 647 benutzerdefinierte MIME-Typen 660 Drag-Seite 656 Drop-Seite 651 Kodierung 648 QMimeData 648 Drucken 691 Grafik 691 HTML 696 PDF 691 PDF erstellen 695 Druckerdialog 132 Dynamische Bibliotheken 741 Laden 744 QLibrary 744
E Editor, Qt Creator 774 Ein- und Ausgabe 417 Binäre Daten 431 Datei 421 Dateiinformationen 473 Puffer 461 QBuffer 461
812
Ein- und Ausgabe (Forts.) QDataStream 431 QDir 464 QFile 421 QFileInfo 473 QIODevice 417 QTemporaryFile 429 QTextStream 444 Streams 431 temporäre Datei 429 Textdaten 444 Verzeichnisse 464 Zwischenspeicher 461 Eingabedialog 127 Eingabe-Widget 199 QAbstractSlider 199 QAbstractSpinBox 228 QCalendarWidget 257 QComboBox 218 QDateTimeEdit 234 QDial 203 QDoubleSpinBox 234 QFontComboBox 228 QLineEdit 207 QScrollBar 207 QSlider 202 QSpinBox 232 QTextEdit 243 Einstellungen speichern 363 Ereignis-Handler 629 Ereignisschleife 627 Ereignisverarbeitung 627 Drag und Drop 647 Drag-Seite 656 Drop-Seite 651 Filter implementieren 636 Handler 629 Maus 647 Multithreads 641 neu implementieren 635 optimieren 644 QEvent 627 QPaintEvent 666 Tastatur 629 Übersicht 639 Verwaltung 639
1542.book Seite 813 Montag, 4. Januar 2010 1:02 13
Index
F Farbauswahl 132 Fenster aufteilen QSplitter 400 QSplitterHandle 404 forever 590 FTP-Protokoll 544
G Gestensteuerung 770 Grafik Bild laden 684 Bild speichern 684 Bildbearbeitung 683 Bildformate 683 Bildinformationen 684 Bild-Transformationen 684 draw...() 666 Drucken 691 Matrix 682 OpenGL 701 Pinsel 671 Pixel manipulieren 685 QPainter 665 QPaintEvent 666 Scherung 674 Schrift 670 Skalieren 674 Stift 671 SVG 710 Transformationen 673 Vektorgrafik 710 Versetzen 674 Grafik 씮 Zeichnen
H Hauptfenster 323 Aktionen 335 Aktionsgruppe 343 aufteilen 400 Dock-Widget 358 MDI (Multi Document Interface) 383 Menü-Elemente 327 Menüleiste 327 QAction 335
Hauptfenster (Forts.) QActionGroup 343 QDockWidget 358 QMainWindow 323 QMenu 327 QMenuBar 327 QScrollArea 409 QSettingsBar 363 QStatusBar 345 QSToolBar 352 QWorkspace 383 Scrollbereich 409 Statusleiste 345 verschiebbares Fenster 358 Werkzeugleiste 352 Zustand sichern 363 Hauptfenster entwerfen Designer 799 HTTP-Protokoll QHttp 528 QHttpHeader 534
I Internationale Anwendungen 733 Linguist 735 Interprozesskommunikation QProcess 481 Item-View 263 Baummodell 289 Listenmodell 278 Model-View 302 QListWidget 278 QListWidgetItem 278 QTableWidget 264 QTableWidgetItem 264 QTreeWidget 289 QTreeWidgetItem 289 Tabellenmodell 264
K Klassenhierarchie Qt 57 Kodierung QMimeData 648 Kommandozeile auswerten 755 Konsolenprogramm erstellen 755
813
1542.book Seite 814 Montag, 4. Januar 2010 1:02 13
Index
L Layout 74 eigenen Manager erstellen 98 Programmbeispiel 76 QBoxLayout 83 QGridLayout 75, 88 QHBoxLayout 75 QLayout 80 QSpacerItem 96 QStackedLayout 92 QStackedWidget 93 Qt 97 QVBoxLayout 75 QWidgetItem 96 Widget 74 Linguist Übersetzen mit 735 Listenmodell 278 Lizenzierung 16 lupdate 734
M make 23 MDI-Anwendungen 383 Menüleiste 327 Meta Object Compiler 47 Meta-Include-Datei 68 MOC 47 Modell-Präsentation 302 QDirModel 304 QSortFilterProxyModel 311 QStringListModel 309 Stringliste 309 Verzeichnishierarchien 304 vordefinierte 303 Model-View 302 Multi Document Interface 383 Multithreads 562 Condition 583 Daten an Thread binden 586 Ereignisverarbeitung 641 Mutex 572, 574 QMutex 572 QMutexLocker 574 QReadWriteLock 575 QSemaphore 577
814
Multithreads (Forts.) QWaitCondition 583 Semaphore 577 Multitouch-Steuerung 770 Mutex 572, 574
N Netzwerkkommunikation 496 QAbstractSocket 496 QFtp 544 QHostAdress 522 QHostInfo 522 QHttp 528 QHttpHeader 534 QHttpRequestHeader 534 QHttpResponseHeader 534 QNetworkProxy 561 QTcpServer 504 QTcpSocket 503 QUdpSocket 514 QUrl 535 TCP 503–504 UDP 514
O Online-Hilfen 316 Direkthilfe 317 Qt Assistant 321 QTextBrowser 318 Statuszeile 316 Tooltipps 316 OpenGL 701 MesaGL 702 QGLWidget 703 Spezifikation 702
P PDF aus HTML erstellen 697 drucken 695 erstellen 695 Phonon 765 Plug-Ins 746 Programm starten 23 Projektdatei 22, 63
1542.book Seite 815 Montag, 4. Januar 2010 1:02 13
Index
Prozess QProcess 481 starten 481
Q Q_ASSERT() 751 Q_CHECK_PTR() 752 QAbstractAnimation 767 QAbstractButton 133 QAbstractSlider 199 QAbstractSocket 496 QAbstractSpinBox 228 QAbstractState 770 QAction 335 QActionGroup 343 QAnimationGroup 767 QAnimationGroups 767 qApp 73 QApplication 754 QAssistantClient 321 QBoxLayout 83 QBrush 671 QButtonGroup 154 QByteArray 613 QCalendarWidget 257 QChar 612 QCheckBox 145 QClipboard 661 QColorDialog 132 QComboBox 218 QCoreApplication 754 QDataStream 431 QDate 626 QDateTime 626 QDateTimeEdit 234 QDial 203 QDialog 100 QDir 464 QDirModel 304 QDockWidget 358 QDoubleSpinBox 234 QEasingCurve 769 QEvent 627 QFile 421 QFileDialog 122 QFileInfo 473 QFont 670 QFontComboBox 228
QFontDialog 131 QFrame 169 QFtp 544 QGLWidget 703 QGridLayout 75, 88 QGroupBox 155 QHash 620 QHBoxLayout 75 QHostAdress 522 QHostInfo 522 QHttp 528 QHttpHeader 534 QHttpRequestHeader 534 QHttpResponseHeader 534 QImage 683 Bild laden 684 Bild speichern 684 Bildformate 683 Bildinformationen 684 Bild-Transformationen 684 Pixel manipulieren 685 QInputDialog 127 QInputEvent 629 QIODevice 417 QKeyEvent 629 QLabel 192 QLayout 80 QLCDNumber 187 QLibrary 744 QLineEdit 207 QLinkList 616 QList 615 QListWidget 278 QListWidgetItem 278 QMainWindow 323–324 qmake 21, 63, 742 make 23 Projektdatei 22 QMap 619 QMatrix 682 QMenu 327, 331 QMenuBar 327, 329 QMessageBox 113 QMimeData 648 benutzerdefinierte MIME-Typen 660 QMultiHash 620 QMultiMap 619 QMutex 572 QMutexLocker 574
815
1542.book Seite 816 Montag, 4. Januar 2010 1:02 13
Index
QNetworkProxy 561 QObject 57, 733 QPainter 665 draw...() 666 Matrix 682 Pinsel 671 Scherung 674 Schrift 670 skalieren 674 Stift 671 Transformationen 673 versetzen 674 QPaintEvent 666 QParallelAnimationGroup 767 QPen 672 QPrintDialog 132 QPrinter 691 QProcess 481 QProgressBar 181 QProgressDialog 186 QPropertyAnimation 767 QPuffer 461 QPushButton 141 QQeue 618 QRadioButton 151 QReadWriteLock 575 QRect() 769 QScrollArea 409 QScrollBar 207 QSemaphore 577 QSequentialAnimationGroup 767 QSet 620 QSettings 363 QSlider 202 QSortFilterProxyModel 311 QSpacerItem 96 QSpinBox 232 QSplitter 400 QSplitterHandle 404 QSqlDatabase 593 QSqlQuery 594 QSqlTableModel 601, 606 QStack 617 QStackedLayout 92 QStackedWidget 93 QStatusBar 345 QString 610 QStringList 611 QStringListModel 309
816
QStyle 752 QSvgWidget 710 Qt 97 Bibliotheken 61 Datentypen 609 Demos verwenden 772 Designer 777 Hallo Welt 19 Installieren 16 Klassenhierarchie 57 Lizenzierung 16 Meta Object Compiler 47 Programm starten 23 qmake 21 Quellcode übersetzen 21 Ressourcen-System 763 Speicherverwaltung 60 Styles 752 Typendefinitionen 609 Qt Creator 771 Action Editor 803 Arbeitsoberfläche 771 Buddies 792 Code-Navigation 775 Dialog-Fenster erstellen 778 Editor 774 Hauptfenster entwerfen 799 Locator 775 Object Inspector 785 Property Editor 786 RAD-Tool 777 Signal/Slot-Editor 790 TAB-Order 791 Tastenkombinationen 777 UI-Datei 793 Qt Designer Dialog-Fenster erstellen 778 Qt Mobility 747 QT_TR_NOOP() 740 Qt3Support 62, 67 QTableWidget 264 QTableWidgetItem 264, 270 QTableWidgetSelectionRange 278 QTabWidget 159 QtAssistant 62 QtCore 61, 64 QTcpServer 504 QTcpSocket 503 QtDBus 62
1542.book Seite 817 Montag, 4. Januar 2010 1:02 13
Index
QtDesigner 62 QTemporaryFile 429 QTextBrowser 257, 318 QTextEdit 243 QTextStream 444 QtGui 61, 65, 68 QThread 562 QThreadStorage 586 QTime 626 QTimeLine 767 QTimer 189, 641 QtMultimedia 766 QtNetwork 61, 65 QToolBar 352 QToolBox 176 QToolButton 154 QtOpenGL 62, 65 Qtopia Qt Mobility 747 QTouchEvent 770 QTreeWidget 289 QTreeWidgetItem 289 QtSql 62, 66, 591 Treiber 591 QtSvg 62, 66 QtTest 62 QtUiTools 62 QtWebKit 756 QtXml 62, 67, 713 QUdpSocket 514 Quellcode übersetzen 21 QUrl 535 QVariant 614 QVariantAnimation 767 QVBoxLayout 75 QVector 616 QWaitCondition 583 QWebDatabase 757 QWebFrame 757 QWebHistory 757 QWebHistoryInterface 757 QWebHistoryItem 757 QWebHitTestResult 757 QWebPage 757 QWebPluginFactory 757 QWebSecurityOrigin 758 QWebSettings 758 QWebView 758
QWidgetItem 96 QWorkspace 383
R Radio-Button 151 RAD-Tool Qt Designer 777 Referenz 34 Registry-Eintrag 363 Ressourcen-System 763
S SAX 713 API 714 Schriftauswahl 131 Scrollbereich 409 Semaphore 577 Signale Argumentenliste 41 Grundlagen 32 Slots Argumentenliste 41 Grundlagen 32 Sockets 496 QAbstractSocket 496 QTcpServer 504 QTcpSocket 503 QUdpSocket 514 Speicherverwaltung 60 SQL 591 State Machine Framwork 770 Statusleiste 345 normale Meldungen 347 permanente Meldungen 348 temporäre Meldungen 345 Statuszeilentipp 316 Strings 610 Styles Qt- 752 SVG 710 Symbian S60 770
T Tabellenmodell 264, 606 mit SQL 606 TCP-Sockets 503
817
1542.book Seite 818 Montag, 4. Januar 2010 1:02 13
Index
Threads 562 Toolbar 352 Tooltipps 316 tr() 733
U UDP-Sockets 514 Uhrzeit 234, 626 UI-Datei 793 Union QVariant 614
V Verzeichnishierarchien anzeigen 304 Verzeichnisse QDir 464
W Webkit 756 Werkzeugleiste 352 Widget Button 133 Container 155 Datum 234, 257 Eingabe-Widget 199 Layout 74 mit eigenem Signal 51 mit eigenem Slot 49 QAbstractButton 133 QAbstractSlider 199 QAbstractSpinBox 228 QButtonGroup 154 QCalendarWidget 257 QCheckBox 145 QComboBox 218 QDateTimeEdit 234 QDial 203 QDoubleSpinBox 234 QFontComboBox 228 QFrame 169 QGroupBox 155 QLabel 192
818
Widget (Forts.) QLCDNumber 187 QLineEdit 207 QProgressBar 181 QProgressDialog 186 QPushButton 141 QRadioButton 151 QScrollBar 207 QSlider 202 QSpinBox 232 QTabWidget 159 QTextBrowser 257 QTextEdit 243 QToolBox 176 QToolButton 154 Uhrzeit 234 Zustandsanzeige 181
X XML DOM-API 721 DOM-Baum durchsuchen 731 Einführung 713 SAX-API 714 SAX-Handler 716
Z Zeichen QChar 612 Zeichenketten QByteArray 613 QString 610 QStringList 611 Zeichnen 665 Ziehen und Fallenlassen 647 Zustandsanzeige 181 QLabel 192 QLCDNumber 187 QProgressBar 181 QProgressDialog 186 Zwischenablage QClipboard 661