5. MainWindow mit Dialog - Beispiel

In diesem Beitrag geht es darum ein sehr einfaches Hauptfenster -QMainWindow genannt- zu erstellen, welches eine Liste von Namen enthält. Mittels eines find-Dialogs, soll nach einem bestimmten Namen gesucht werden können. Dazu müssen wir im Qt-Designer zwei Oberflächen erstellen. Eine für das MainWindow und ein Dialog-Fenster. Fangen wir mit dem MainWindow an. Wir wählen Neu, MainWindow und Neu von Vorlage. Ein MainWindow besteht aus einer menubar (das obere, herunterklappbare Menü), einer toolbar (eine Reihe von Schaltflächen zur schnelleren Auswahl), einem zentralem Widget (centralwidget) und einer statusbar (eine Leiste am unteren Fensterrand die Statusinformationen anzeigen kann). Mittels des Qt-Designers ist es ein leichtes sich das Menü oder die Toolbar zusammen zu klicken. Zuerst klicken wir doppelt in der menubar auf den Text "Geben Sie Text ein" und tragen im leeren Feld den Begriff Find ein. Sofort wird ein neuer Menüpunkt unter dieser Bezeichnung erstellt und die Worte "Geben Sie Text ein" rutschen um eine Stelle nach rechts, falls man weitere Menüpunkte erstellen möchte. Außerdem klappt unser erster Menüpunkt auf und man kann Unterpunkte angeben. Man hat die Wahl entweder mit "Geben Sie Text ein" einen neuen Unterpunkt zu erstellen oder mit "Trenner hinzufügen" einen Trennstrich einzufügen. Mittels Doppelklick fügen wir den Unterpunkt Name hinzu. Menüeinträge eines MainWindow lösen nicht wie bei einem QPushButton ein Signal clicked() aus, sondern das Signal triggered(). Jeder Menüpunkt erzeugt ein sogenanntes QAction-Element. Als wir unseren Unterpunkt Name erstellt haben, wurde ein solches Element automatisch vom Qt-Designer erstellt und ihm der Name actionName zugewiesen. Eine Übersicht über sämtliche Actions liefert das Fenster Aktionseditor.

Bild: Aktionseditor.jpg

Hier hat man einen Überblick über die Aktionen. Möchte man aber etwas feiner die Eigenschaften der Aktion verändern, z.B. ob sie mittels einer kleinen Box ankreuzbar sein soll, hilft das Eigenschaften Fenster weiter, welches bei Klick auf den jeweiligen Menüeintrag angezeigt wird. In dem Sinne ist die Spalte Ankreuzbar im Aktionseditor keine Möglichkeit zum wählen, sondern lediglich eine Anzeige ob die entsprechende Eigenschaft im Eigenschafts-Fenster gewählt wurde. Natürlich kann man auch den umgekehrten Weg gehen, den wir mit unserem zweiten und letzten Menüpunkt beschreiben. Wir klicken rechts mit der Maus in das Aktionseditor-Fenster und wählen Neu. Es erscheint das Fenster Neue Aktion. Als Text geben wir Schließen an und als Objektname actionClose. Nun haben wir eine neue Aktion geschaffen und müssen diese lediglich in unser Menü einfügen. Dazu ziehen wir die Aktion aus dem Aktionseditor mit der Maus in unser MainWindow unter die entsprechende Stelle im Menü. In unserem Falle gleich unterhalb des Menüeintrags Name. Übrigens kann man so -durch Ziehen mit der Maus- das gesamte Menü umgestalten. Um den Eintrag Schließen ein wenig von Find abzugrenzen fügen wir noch einen Trenner ein und schieben ihn mit der Maus zwischen unsere beiden Menüpunkte.

Als Beispiel fügen wir jetzt noch eine Toolbar ein, auch wenn diese lediglich zwei Punkte enthält. Dazu klicken wir mit der rechten Maustaste unterhalb unseres Menüs und wählen Werkzeugleiste hinzufügen. Genau wie beim Menü kann man die entsprechenden Aktionen einfach in die Toolbar ziehen. Leider fehlt hier eine Kleinigkeit, eine kleine Grafik, welche man mit der jeweiligen Aktion verknüpfen sollte. Die Toolbar zeigt dann lediglich diese Grafik an, während im Menü beides angezeigt wird. Hinzufügen kann man die Grafik, wenn man im Aktionseditor die jeweilige Aktion doppelt klickt und da wo die 3 Punkte sind, entweder Ressource auswählen oder Datei auswählen wählt. Theoretisch ist es sinnvoller eine Ressourcen-Datei im Qt-Designer zu erstellen, da diese mit dem Programm verknüpft wird und damit vom Pfad her unabhängig. Eine Ressourcen-Datei kann einen Pool von Bildern enthalten für das eigene Programm, welche man dann in dem Programm beliebig verwenden kann. Da wir jetzt aber keine Bildchen haben, belassen wir die Toolbar als Beispiel mit den Namen unserer Aktionen.

Als Namen für unser MainWindow vergeben wir myMainWindow. Als letztes fügen wir in das centralWidget unseres MainWindow ein QTableWidget ein, dessen Namen wir auf tableWidget belassen. Damit unser TableWidget seine Größe automatisch anpaßt wenn wir unser Fenster maximieren, wählen wir unser Hauptfenster aus und klicken auf das Grid-Layout (heißt in Deutsch "Objekte tabellarisch anordnen"). Wie man sieht, kann man ein Layout nicht nur für mehrere markierte Objekte vergeben, sondern auch für das Hauptfenster allgemein. Das funktioniert auch im übrigen mit Container-Widgets, wie z.B. einer GroupBox oder einem Frame. Der Inhalt der GroupBox oder des Frames paßt sich dann automatisch an dessen Größe an. Nachdem wir unserer Tabelle noch 16 Zeilen und 2 Spalten spendiert haben, sieht unser Hauptfenster jetzt so aus und wird unter dem Namen MainWindowBeispiel.ui gespeichert:

Bild: Hauptfenster.jpg

Nun müssen wir noch unseren Dialog erstellen, welchem wir den Speichernamen findDialog.ui geben. Dazu wählen wir Neu, Dialog without Buttons und Neu von Vorlage. Der Name unseres Dialogs lautet myDialog und er enthält lediglich ein LineEdit für die Eingabe des zu suchenden Namens und den Button Find der die Suchaktion auslöst. Dazu fügen wir ein QLineEdit und einen QPushButton ein und vergeben die Namen nameLineEdit und findButton. Für die Formatierung fügen wir noch einen Horizontal Spacer ein. Nach Auswahl des Dialogs und dem wählen des GridLayouts sieht unser Dialog in etwa so aus:

Bild: Hauptfenster.jpg

Damit haben wir unser Grundgerüst und wir müssen nun die jeweiligen Dateien erstellen. Wir benötigen insgesamt 7 Dateien. Wie immer unsere main.cpp, die jeweilige ui-Datei für unser MainWindow und unseren Dialog und jeweils eine .cpp und .h Datei für selbige.

  • main.cpp - das Hauptprogramm.
  • mainWindowBeispiel.ui - die grafische Oberfläche unseres Hauptfensters.
  • findDialog.ui - die grafische Oberfläche unseres Dialogs.
  • myMainWindow.h - enhält die Deklarierung unseres Hauptfensters.
  • myMainWindow.cpp - enthält die Verbindungen und Funktionen des Hauptfensters.
  • myDialog.h - enhält die Deklarierung unseres Dialogs.
  • myDialog.cpp - enthält die Verbindungen und Funktionen des Dialogs.

main.cpp

#include <QApplication>

#include "myMainWindow.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
myMainWindow *mainWindow = new myMainWindow;
mainWindow->show();
return app.exec();
}

Die main.cpp ist wie immer schlicht und dient lediglich zum Anzeigen unseres Hauptfensters. Machen wir mit unserem Dialog weiter:

myDialog.h

# ifndef MYDIALOG_H
# define MYDIALOG_H

# include <QDialog>

# include "ui_findDialog.h"

class myDialog : public QDialog, public Ui::myDialog
{
Q_OBJECT

public:
myDialog(QWidget *parent = 0);

private slots:
void suchName();

signals:
void retName(const QString &);

};

# endif

Auch hier keine großen Besonderheiten, bis auf Eine: Bisher haben wir nur eigene Slots definiert. Jetzt brauchen wir ein eigenes Signal für die Kommunikation mit unserem Hauptfenster, damit wir den zu suchenden Namen an unser Hauptfenster übergeben können. Dies machen wir mit dem Signal retName(const QString &). Damit unterschiedliche Widgets (hier ein MainWindow mit einem Dialog) kommunizieren können, kann man sich der Signal/Slot Technologie bedienen. Diese Methode eignet sich am Besten um Daten hin und her zu schicken.

myDialog.cpp

# include <QtGui>

# include "myDialog.h"

myDialog::myDialog(QWidget *parent): QDialog(parent)
{
setupUi(this);

connect(findButton, SIGNAL(clicked()), this, SLOT(suchName()));
}

void myDialog::suchName()
{
QString Name = nameLineEdit->text();
if(!Name.isEmpty())
emit retName(Name);
close();
}

Unsere myDialog.cpp ist recht einfach. Wir verbinden lediglich unseren findButton unseres Dialogs mit dem slot suchName(), damit wir den Suchbegriff aus dem nameLineEdit auslesen können. Anschließend folgt eine Sicherheitsabfrage, ob der Name leer ist (also ob wir überhaupt was eingegeben haben) und falls nicht, wird mittels emit das Signal retName(Name) ausgegeben, welches den Suchbegriff mit auf den Weg bekommt.

myMainWindow.h

# ifndef MYMAINWINDOW_H
# define MYMAINWINDOW_H

# include <QMainWindow>

# include "ui_MainWindowBeispiel.h"

class myDialog;

class myMainWindow : public QMainWindow, public Ui::myMainWindow
{
Q_OBJECT

public:
myMainWindow(QWidget *parent = 0);

private slots:
void openFindDialog();
void markiereName(const QString &text);

private:
myDialog *findDialog;

};

# endif

Was an der myMainWindow.cpp als erstes auffällt, ist natürlich die Ableitung des Widgets. Da wir bisher nur Dialoge erstellt haben, mußten wir auch immer von QDialog ableiten. Da es sich hier aber um ein Hauptfenster handelt, muß die Ableitung von QMainWindow erfolgen. Dementsprechend muß auch QMainWindow im Header inkludiert werden. Auch neu ist die Klassen-Angabe class myDialog; unserer abgeleiteten Klasse von unserem Dialog. Durch diese Angabe wird es möglich einen Zeiger auf unsere Dialog-Klasse zu erzeugen namens *findDialog. Das benötigen wir um später eine Abfrage ausführen zu können, ob das Objekt schon existiert. Würden wir das nicht abfragen, würde mit jedem Klick auf den findDialog-Öffnen-Button ein neues Dialog-Fenster geöffnet werden, auch wenn schon eins geöffnet ist. In der Slots-Sektion definieren wir einmal den Slot der unseren Dialog aufruft, namens openFindDialog() und einmal den Slot der durch unser selbsterstelltes Signal retName(const QString &) unseres Dialogs aufgerufen wird.

MyMainWindow.cpp

# include <QtGui>

# include "myMainWindow.h"
# include "myDialog.h"

myMainWindow::myMainWindow(QWidget *parent): QMainWindow(parent)
{
setupUi(this);

findDialog = 0;

connect(actionName, SIGNAL(triggered()), this, SLOT(openFindDialog()));
connect(actionClose, SIGNAL(triggered()), this, SLOT(close()));

}

void myMainWindow::openFindDialog()
{
if(!findDialog)
{
findDialog = new myDialog(this);
connect(findDialog, SIGNAL(retName(const QString &)), this, SLOT(markiereName(const QString &)));
}
findDialog->show();
findDialog->raise();
findDialog->activateWindow();
}

void myMainWindow::markiereName(const QString &text)
{
tableWidget->clearSelection();

for(int row=0;row<tableWidget->rowCount();row++)
{
for(int column=0;column<tableWidget->columnCount();column++)
{
if(tableWidget->item(row,column)!=0)
{
QString inhalt = tableWidget->item(row,column)->text();
int vergleich = QString::compare(inhalt, text, Qt::CaseInsensitive);
if(vergleich==0)
{
tableWidget->setCurrentCell(row,column);
return;
}
}
}
}
}

Im Kopf unserer myMainWindow.cpp müssen wir natürlich neben unserer MyMainWindow.h auch die Header-Datei unseres Dialogs myDialog.h inkludieren, damit wir unseren Dialog überhaupt aufrufen können. Und damit es nicht zu einem Programmabsturz kommt, weil der Zeiger findDialog ins Leere zeigt, müssen wir ihn mittels findDialog = 0 erstmal initialisieren. Danach folgen die Signal-Slot-Verbindungen für unsere Menü-, bzw. Toolbar-Schaltflächen. Der Klick auf Finde/Name löst die Aktion actionName aus, welche das Signal triggered() sendet und damit den Slot openFindDialog() aufruft. Dieser Slot fragt erstmal ab, ob findDialog schon existiert und falls nicht, ihn mittels findDialog = new myDialog(this); aufruft. Wichtig ist hierbei der Zeiger this, denn läßt man den weg, wird unser Dialog nicht als Kindobjekt unseres Hauptfensters erzeugt, sondern als eigenständiges Fenster und bleibt somit nach dem Schließen unseres Hauptfensters erhalten! Im Anschluß folgt die Verbindung unseres selbsterstellten Signals retName(const QString &) unseres Dialogs mit dem Slot markiereName(const QString &). Durch die if-Abfrage wird nicht nur verhindert, daß sich der Dialog zig-fach öffnet, sondern die Verbindung nur angelegt, wenn auch benötigt. Das spart ein klein wenig Ressourcen. Anschließend wird unser Dialog mittels show(), raise() und activateWindow() angezeigt und in den Vordergrund geholt.

Der Slot markiereName(const QString &) erinnert ein klein wenig an unser Tabellen-Beispiel. Zuerst einmal wird mit clearSelection() sämtliche vorher getätigten Markierungen entfernt. Anschließend wird die gesamte Tabelle mittels der beiden for-Schleifen einmal für die Zeilen (rowCount) und einmal für die Spalten (columnCount) durchlaufen. Dabei wird abgefragt, ob ein QTableWidgetItem existiert oder nicht. Im Falle daß eins existiert, wird es mit unserem per Signal übergebenem Suchbegriff verglichen. Das erledigt die QString-Klasse mit der Funktion compare. Ich habe deswegen compare gewählt, weil man bei diesem Vergleich das Flag Qt::CaseInsensitive setzen kann und somit die Groß- und Kleinschreibung nicht mehr beachtet wird. Egal ob man Arnold mit großem oder kleinem A eingibt, Arnold wird gefunden. Dabei gibt compare einen Zahlenwert zurück. Null bei Gleichheit und eine andere Zahl bei Ungleichheit. Also lautet die folgende Abfrage, wenn unsere Vergleichszahl gleich Null ist, setzen wir mit setCurrentCell(row, column) unsere Position im QTableWidget auf besagte Zelle. Durch diesen Aufruf wird die Zelle nicht nur sichbar hergescrollt, sondern auch zusätzlich markiert.

Das wars auch schon wieder mit unserem kleinem Beispiel wie man aus einem QMainWindow heraus einen QDialog öffnet und mit ihm Daten austauscht. Wer sich das Programm runterladen will um es selbst zu kompilieren, kann sich hier ein komprimiertes tar-Archiv mit den benötigten Dateien runterladen und obwohl wir jeweils zwei ui-Files, cpp-Dateien und h-Dateien haben, lautet der Befehl immer noch:
qmake -project; qmake; make

Komprimiertes tar-Archiv mit allen Dateien: MainWindow-mitDialog-Beispiel.tar.gz