Qt 4.6 - GUI-Entwicklung mit C++: Das umfassende Handbuch

  • 31 29 9
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up

Qt 4.6 - GUI-Entwicklung mit C++: Das umfassende Handbuch

1542.book Seite 2 Montag, 4. Januar 2010 1:02 13 Liebe Leserin, lieber Leser, wir freuen uns, dass Sie sich für ein Gal

2,414 94 57MB

Pages 818 Page size 131.81 x 197.86 pts Year 2011

Report DMCA / Copyright

DOWNLOAD FILE

Recommend Papers

File loading please wait...
Citation preview

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 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 & 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
  • " "
    • Unterpunkt2
    " "
  • Aufzählpunkt3

" "
" "" "" "
" "WerteBeschreibung
fault_xxxFehler

"); 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(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 (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(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("&Synchron"), this, SLOT(synchronProcess() ), QKeySequence(tr("Ctrl+S", "QProcess|Synchron"))); 12 fileMenu->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 #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("")); 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()