Komplexe Anwendungsfenster erstellen

Natürlich kann man sich komplexe Fenster durch das Verschachteln von Widgets selbst erstellen. Die Klasse QMainWindow bietet jedoch bereits eine Vorlage für übliche Hauptfenster, sodass nur mehr entsprechende Methoden der Klasse aufgerufen werden müssen.
Zuerst sehen wir uns mal ein typisches Hauptfenster an:


An diesem Beispiel wollen wir uns nun die Klasse QMainWindow ansehen. Gleichzeitig soll unsere Todo-Anwendung aus dem Kapitel Dialoge erweitert werden. Unser Hauptprogramm sieht während der ganzen Entwicklung so aus:

// main.cpp
#include <QApplication>
#include "MainWindow.h"
 
int main ( int argc, char *argv[] )
{
  QApplication app( argc, argv );
  MainWindow mw;
 
  mw.show();
 
  return app.exec();
}

Außerdem wird die Klasse MessageListWidget aus dem genannten Kapitel verwendet. Wir fügen aber noch ein paar Signale hinzu, die uns die Kommunikation erleichtern:

// MessageListWidget.h
#ifndef MESSAGELISTWIDGET_H
#define MESSAGELISTWIDGET_H
 
#include <QWidget>
 
class QListWidget;
class QPushButton;
class QInputDialog;
 
class MessageListWidget : public QWidget
{
 
    Q_OBJECT
 
  public:
    MessageListWidget();
 
  private:
    QListWidget *messageList;
    QPushButton *addButton, *removeButton;
    QInputDialog *dialog;
 
  private slots:
    void showInputDialog();
    void removeSelectedMessage();
 
  signals:
    void entryAdded( const QString& );
    void entryRemoved( const QString& );
    void numEntriesChanged( const int );
 
};
 
#endif // MESSAGELISTWIDGET_H
// MessageListWidget.cpp
#include "MessageListWidget.h"
#include <QPushButton>
#include <QListWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QInputDialog>
#include <QList>
 
MessageListWidget::MessageListWidget()
  : messageList( new QListWidget() ),
    addButton( new QPushButton ("+" ) ),
    removeButton( new QPushButton( "-" ) ),
    dialog( new QInputDialog() )
{
  QVBoxLayout *vLayout = new QVBoxLayout();
  QHBoxLayout *hLayout = new QHBoxLayout();
 
  messageList->setSelectionMode( QAbstractItemView::SingleSelection );
  dialog->setInputMode( QInputDialog::TextInput );
  dialog->setModal( true );
  dialog->setWindowTitle( "New Entry" );
 
  connect( addButton, SIGNAL( clicked() ),
           this, SLOT( showInputDialog() ) );
  connect( removeButton, SIGNAL( clicked() ),
           this, SLOT( removeSelectedMessage() ) );
 
  hLayout->addStretch();
  hLayout->addWidget( addButton );
  hLayout->addWidget( removeButton );
  vLayout->addWidget( messageList );
  vLayout->addLayout( hLayout );
 
  setWindowTitle( "Todo" );
  setLayout( vLayout );
  resize( 400, 400 );
}
 
 
void MessageListWidget::showInputDialog()
{
  dialog->setTextValue( "" );
  dialog->exec();
  if( dialog->textValue().trimmed().size() > 0 )
  {
    messageList->addItem( dialog->textValue() );
    emit entryAdded( dialog->textValue() );
    emit numEntriesChanged( messageList->count() );
  }
}
 
 
void MessageListWidget::removeSelectedMessage()
{
  QList<QListWidgetItem *> items = messageList->selectedItems();
  if( items.size() > 0 )
  {
    for( int i = 0; i < messageList->count(); i++ )
    {
      if( messageList->item( i ) == items.first() )
      {
        QListWidgetItem *item = messageList->takeItem( i );
        QString text = item->text();
        delete item;
        emit entryRemoved( text );
        emit numEntriesChanged( messageList->count() );
        return;
      }
    }
  }
}

Zentrales Widget


Wie der Name schon sagt ist das zentrale Widget der Mittelpunkt der Anwendung. Wird die Größe des Hauptfensters verändert, hat das nur Einfluss auf das zentrale Widget, alle anderen Teile bleiben unbeeinflusst. Gesetzt wird das Widget intuitiv über die Methode setCentralWidget() des QMainWindow-Objektes. Da das zentrale Widget ziemlich wahrscheinlich ein zusammengesetztes Widget ist, muss dafür wie im Kapitel Layouts beschrieben ein Wrapper-Widget verwendet werden, das alle anderen Widgets enthält.

Da das zentrale Widget bereits fertig implementiert ist, fällt unser Code hier entsprechend kurz und einfach aus:

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
class MessageListWidget;
 
class MainWindow : public QMainWindow
{
 
  Q_OBJECT
 
  public:
    MainWindow();
 
  private:
    MessageListWidget *m_listWidget;
 
};
 
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
 
#include "MessageListWidget.h"
 
MainWindow::MainWindow()
  : m_listWidget( new MessageListWidget() )
{
  setCentralWidget( m_listWidget );
  setWindowTitle( "Todo" );
  resize( 400, 500 );
}



Optisch sieht man hier noch keinen Unterschied, jedoch befindet sich unser Widget bereits innerhalb des Hauptfensters.


Die Menüleiste steht in vielen Programmen im Mittelpunkt der Navigation. Über verschachtelte Menüpunkte können komplexe Navigationsstrukturen abgebildet werden. Eine durch ein QMenuBar-Objekt repräsentierte Menüleiste ist nichts weiter als ein Container für QMenu-Objekte, die wiederum selbst QMenu-Objekte oder QAction-Objekte enthalten. QAction-Objekte sind schließlich die endgültigen Menü-Einträge, die mit Slots verbunden werden können und reagieren, wenn auf sie geklickt wird. Außerdem können diese Elemente auch das Verhalten von QCheckBox oder QRadioButton nachahmen. Angezeigt wird die Menüleiste über die Methode setMenuBar() der Hauptfenster-Klasse.

Praktisch wäre natürlich, wenn man unsere Anwendung über das Menü beenden könnte. Außerdem sollten die Buttons zum Hinzufügen und Löschen dort ebenfalls vorhanden sein. Der Übersicht wegen erstellen wir eine Methode createMenuBar() die uns ein passendes QMenuBar-Objekt erstellt und zurückliefert.

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
class MessageListWidget;
class QMenuBar;
 
class MainWindow : public QMainWindow
{
 
  Q_OBJECT
 
  public:
    MainWindow();
 
  private:
    MessageListWidget *m_listWidget;
    QMenuBar *createMenuBar();
 
};
 
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
 
#include "MessageListWidget.h"
#include <QApplication>
#include <QMenuBar>
 
MainWindow::MainWindow()
  : m_listWidget( new MessageListWidget() )
{
  setCentralWidget( m_listWidget );
  setMenuBar( createMenuBar() );
  setWindowTitle( "Todo" );
  resize( 400, 500 );
}
 
 
QMenuBar *MainWindow::createMenuBar()
{
  // Die eigentliche Menüleiste erstellen
  QMenuBar *mBar = new QMenuBar();
 
  // 1. Menü mit dem Namen "File"
  QMenu *fileMenu = new QMenu( "File" );
  // Wird der Eintrag "Quit" betätigt, führt das Objekt "qApp"
  // den Slot "quit()" aus.
  fileMenu->addAction( "Quit", qApp, SLOT( quit() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( fileMenu );
 
  // 2. Menü mit dem Namen "Edit"
  QMenu *editMenu = new QMenu( "Edit" );
  // Wird der Eintrag "Add" betätigt, führt das Objekt "m_listWidget"
  // den Slot "showInputDialog()" aus.
  editMenu->addAction( "Add", m_listWidget, SLOT( showInputDialog() ) );
  // Wird der Eintrag "Remove" betätigt, führt das Objekt "m_listWidget"
  // den Slot "removeSelectedMessage()" aus.
  editMenu->addAction( "Remove", m_listWidget, SLOT( removeSelectedMessage() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( editMenu );
 
  // Menüleiste zurückliefern
  return mBar;
}

In nur wenigen Zeilen haben wir über das Signal/Slot-Konzept ein funktionsfähiges Menü erstellt:

Werkzeugleiste


Die Werkzeugleiste besteht aus grafischen Symbolen. Oftmals sind diese Symbole oft nur Abkürzungen, um auf oft verwendete Elemente in der Menüleiste (z.B. „Speichern“, „Drucken“, …) zugreifen zu können. Da ein Programm auch mehrere Werkzeugleisten haben kann, werden diese über die Methode addToolBar() hinzugefügt. Ähnlich wie QMenuBar ein Container für QMenu-Objekte ist, werden in einer QToolBar Objekte des Typs QToolButton angezeigt. Diese sind ganz normale Widgets und können über connect() mit Slots verbunden werden.

In unseren Beispiel wollen wir einfach die gleichen Einträge wie in der Menüleiste darstellen. Folgende Icons werden verwendet:
appointment-new.png archive-remove.png application-exit.png
Diese Icons sind Teil der KDE-Desktopumgebung, deshalb solltet ihr euch vor der Verwendung in euren Anwendungen über die Lizenzbedingungen informieren.
Um die Icons darzustellen, werden sie mit dem Resource Compiler in unsere Anwendung eingebunden. Dabei wird das Präfix „icons“ verwendet. Abermals erstellen wir uns eine eigene Methode createToolbar() und bauen sie in unser Programm ein:

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
class MessageListWidget;
class QMenuBar;
class QToolBar;
class QIcon;
 
class MainWindow : public QMainWindow
{
 
  Q_OBJECT
 
  public:
    MainWindow();
    ~MainWindow();
 
  private:
    MessageListWidget *m_listWidget;
    QIcon *m_addIcon, *m_removeIcon, *m_quitIcon;
    QMenuBar *createMenuBar();
    QToolBar *createToolBar();
 
};
 
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
 
#include "MessageListWidget.h"
#include <QApplication>
#include <QMenuBar>
#include <QToolBar>
#include <QToolButton>
#include <QIcon>
 
MainWindow::MainWindow()
  : m_listWidget( new MessageListWidget() ),
    m_addIcon( new QIcon( ":/icons/appointment-new.png" ) ),
    m_removeIcon( new QIcon( ":/icons/archive-remove.png" ) ),
    m_quitIcon( new QIcon( ":/icons/application-exit.png" ) )
{
  setCentralWidget( m_listWidget );
  setMenuBar( createMenuBar() );
  addToolBar( createToolBar() );
  setWindowTitle( "Todo" );
  resize( 400, 500 );
}
 
 
MainWindow::~MainWindow()
{
  delete m_quitIcon;
  delete m_removeIcon;
  delete m_addIcon;
}
 
 
QMenuBar *MainWindow::createMenuBar()
{
  // Die eigentliche Menüleiste erstellen
  QMenuBar *mBar = new QMenuBar();
 
  // 1. Menü mit dem Namen "File"
  QMenu *fileMenu = new QMenu( "File" );
  // Wird der Eintrag "Quit" betätigt, führt das Objekt "qApp"
  // den Slot "quit()" aus.
  fileMenu->addAction( "Quit", qApp, SLOT( quit() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( fileMenu );
 
  // 2. Menü mit dem Namen "Edit"
  QMenu *editMenu = new QMenu( "Edit" );
  // Wird der Eintrag "Add" betätigt, führt das Objekt "m_listWidget"
  // den Slot "showInputDialog()" aus.
  editMenu->addAction( "Add", m_listWidget, SLOT( showInputDialog() ) );
  // Wird der Eintrag "Remove" betätigt, führt das Objekt "m_listWidget"
  // den Slot "removeSelectedMessage()" aus.
  editMenu->addAction( "Remove", m_listWidget, SLOT( removeSelectedMessage() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( editMenu );
 
  // Menüleiste zurückliefern
  return mBar;
}
 
 
QToolBar *MainWindow::createToolBar()
{
  // Die eigentliche Werkzeugleiste erstellen
  QToolBar *tBar = new QToolBar();
  // Werkzeugleisten können normal verschoben werden, das wollen wir aber nicht
  tBar->setMovable( false );
 
  // Neuen Eintrag zum Hinzufügen von Einträgen anlegen
  QToolButton *addButton = new QToolButton();
  // Icon für den Eintrag setzen
  addButton->setIcon( *m_addIcon );
  // Wird der Eintrag betätigt, führt das Objekt "m_listWidget"
  // den Slot "showInputDialog()" aus.
  connect( addButton, SIGNAL( clicked() ), m_listWidget, SLOT( showInputDialog() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( addButton );
 
  // Neuen Eintrag zum Entfernen von Einträgen anlegen
  QToolButton *removeButton = new QToolButton();
  // Icon für den Eintrag setzen
  removeButton->setIcon( *m_removeIcon );
  // Wird der Eintrag betätigt, führt das Objekt "m_listWidget"
  // den Slot "removeSelectedMessage()" aus.
  connect( removeButton, SIGNAL( clicked() ), m_listWidget, SLOT( removeSelectedMessage() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( removeButton );
 
  // Ein Trenn-Objekt einfügen, um die Einträge optisch zu trennen
  tBar->addSeparator();
 
  // Neuen Eintrag zum Beenden der Anwendung anlegen
  QToolButton *quitButton = new QToolButton();
  // Icon für den Eintrag setzen
  quitButton->setIcon( *m_quitIcon );
  // Wird der Eintrag betätigt, führt das Objekt "qApp"
  // den Slot "quit()" aus.
  connect( quitButton, SIGNAL( clicked() ), qApp, SLOT( quit() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( quitButton );
 
  // Werkzeugleiste zurückliefern
  return tBar;
}


Statusleiste


Am unteren Fensterrand befindet sich gewöhnlich die Statusleiste des Programms. Sie enthält meist aktuelle Informationen über den Zustand der Anwendung, kann aber jedes beliebige Widget enthalten.
Zusätzlich zur Methode setStatusbar(), muss die Statusleiste explizit über die Methode setVisible() sichtbar gemacht werden. Über die Methode showMessage() kann eine Nachricht angezeigt werden, die jedoch nach Ablauf eines ebenfalls übergebenen Timeouts verschwindet. Außerdem können noch Widgets in der Statusleiste angezeigt werden. Hier gibt es jedoch 2 Möglichkeiten:

  • Widget kann von Nachrichten verdeckt werden: addWidget()
  • Widget kann nicht verdeckt werden (permanentes Widget): addPermanentWidget()



Unsere Anwendung soll den aktuellen Status in der Statusleiste darstellen. Dazu gehören Benachrichtigungen, wenn ein Eintrag hinzugefügt bzw. gelöscht wird und die aktuelle Anzahl an Einträgen:

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
class MessageListWidget;
class QMenuBar;
class QToolBar;
class QIcon;
class QStatusBar;
class QLabel;
 
class MainWindow : public QMainWindow
{
 
  Q_OBJECT
 
  public:
    MainWindow();
    ~MainWindow();
 
  private:
    MessageListWidget *m_listWidget;
    QIcon *m_addIcon, *m_removeIcon, *m_quitIcon;
    QLabel *m_counter;
    QMenuBar *createMenuBar();
    QToolBar *createToolBar();
    QStatusBar *createStatusBar();
 
  private slots:
    void showAddedText();
    void showRemovedText();
    void updateCount( const int count );
 
};
 
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
 
#include "MessageListWidget.h"
#include <QApplication>
#include <QMenuBar>
#include <QToolBar>
#include <QToolButton>
#include <QIcon>
#include <QStatusBar>
#include <QLabel>
 
MainWindow::MainWindow()
  : m_listWidget( new MessageListWidget() ),
    m_addIcon( new QIcon( ":/icons/appointment-new.png" ) ),
    m_removeIcon( new QIcon( ":/icons/archive-remove.png" ) ),
    m_quitIcon( new QIcon( ":/icons/application-exit.png" ) ),
    m_counter( new QLabel( "0" ) )
{
  setCentralWidget( m_listWidget );
  setMenuBar( createMenuBar() );
  addToolBar( createToolBar() );
  setStatusBar( createStatusBar() );
  statusBar()->setVisible( true );
  setWindowTitle( "Todo" );
  resize( 400, 500 );
}
 
 
MainWindow::~MainWindow()
{
  delete m_quitIcon;
  delete m_removeIcon;
  delete m_addIcon;
}
 
 
QMenuBar *MainWindow::createMenuBar()
{
  // Die eigentliche Menüleiste erstellen
  QMenuBar *mBar = new QMenuBar();
 
  // 1. Menü mit dem Namen "File"
  QMenu *fileMenu = new QMenu( "File" );
  // Wird der Eintrag "Quit" betätigt, führt das Objekt "qApp"
  // den Slot "quit()" aus.
  fileMenu->addAction( "Quit", qApp, SLOT( quit() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( fileMenu );
 
  // 2. Menü mit dem Namen "Edit"
  QMenu *editMenu = new QMenu( "Edit" );
  // Wird der Eintrag "Add" betätigt, führt das Objekt "m_listWidget"
  // den Slot "showInputDialog()" aus.
  editMenu->addAction( "Add", m_listWidget, SLOT( showInputDialog() ) );
  // Wird der Eintrag "Remove" betätigt, führt das Objekt "m_listWidget"
  // den Slot "removeSelectedMessage()" aus.
  editMenu->addAction( "Remove", m_listWidget, SLOT( removeSelectedMessage() ) );
  // Menü in die Menüleiste einfügen
  mBar->addMenu( editMenu );
 
  // Menüleiste zurückliefern
  return mBar;
}
 
 
QToolBar *MainWindow::createToolBar()
{
  // Die eigentliche Werkzeugleiste erstellen
  QToolBar *tBar = new QToolBar();
  // Werkzeugleisten können normal verschoben werden, das wollen wir aber nicht
  tBar->setMovable( false );
 
  // Neuen Eintrag zum Hinzufügen von Einträgen anlegen
  QToolButton *addButton = new QToolButton();
  // Icon für den Eintrag setzen
  addButton->setIcon( *m_addIcon );
  // Wird der Eintrag betätigt, führt das Objekt "m_listWidget"
  // den Slot "showInputDialog()" aus.
  connect( addButton, SIGNAL( clicked() ), m_listWidget, SLOT( showInputDialog() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( addButton );
 
  // Neuen Eintrag zum Entfernen von Einträgen anlegen
  QToolButton *removeButton = new QToolButton();
  // Icon für den Eintrag setzen
  removeButton->setIcon( *m_removeIcon );
  // Wird der Eintrag betätigt, führt das Objekt "m_listWidget"
  // den Slot "removeSelectedMessage()" aus.
  connect( removeButton, SIGNAL( clicked() ), m_listWidget, SLOT( removeSelectedMessage() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( removeButton );
 
  // Ein Trenn-Objekt einfügen, um die Einträge optisch zu trennen
  tBar->addSeparator();
 
  // Neuen Eintrag zum Beenden der Anwendung anlegen
  QToolButton *quitButton = new QToolButton();
  // Icon für den Eintrag setzen
  quitButton->setIcon( *m_quitIcon );
  // Wird der Eintrag betätigt, führt das Objekt "qApp"
  // den Slot "quit()" aus.
  connect( quitButton, SIGNAL( clicked() ), qApp, SLOT( quit() ) );
  // Eintrag zur Werkzeugleiste hinzufügen
  tBar->addWidget( quitButton );
 
  // Werkzeugleiste zurückliefern
  return tBar;
}
 
 
QStatusBar *MainWindow::createStatusBar()
{
  // Eigentliche Statusleiste anlegen
  QStatusBar *sBar = new QStatusBar();
 
  // Zähl-Widget permanent anzeigen
  sBar->addPermanentWidget( m_counter );
  // Benachrichtigung anzeigen, wenn ein Eintrag hinzugefügt wurde
  connect( m_listWidget, SIGNAL( entryAdded( const QString& ) ),
           this, SLOT( showAddedText() ) );
  // Benachrichtigung anzeigen, wenn ein Eintrag entfernt wurde
  connect( m_listWidget, SIGNAL( entryRemoved( const QString& ) ),
           this, SLOT( showRemovedText() ) );
  // Zähler aktualisieren, wenn ein Eintrag hinzgefügt oder entfernt wurde
  connect( m_listWidget, SIGNAL( numEntriesChanged( const int ) ),
           this, SLOT( updateCount( const int ) ) );
 
  // Statusleiste zurückliefern
  return sBar;
}
 
 
void MainWindow::showAddedText()
{
  // Nachricht für 3 Sekunden in der Statusleiste anzeigen
  statusBar()->showMessage( "Entry added", 3000 );
}
 
 
void MainWindow::showRemovedText()
{
  // Nachricht für 3 Sekunden in der Statusleiste anzeigen
  statusBar()->showMessage( "Entry removed", 3000 );
}
 
 
void MainWindow::updateCount( const int count )
{
  // Permanentes Zähler-Label in der Statusleiste aktualisieren
  m_counter->setText( QString::number( count ) );
}


Dock-Widgets

Dock-Widgets sehen wir auf der Abbildung oben nicht. Sie werden relativ selten verwendet, können ein Programm aber viel übersichtlicher gestalten. Diese Widgets sind zwar Teile des Hauptfensters, können aber am Rand des Fensters frei verschoben oder gruppiert werden. Sie können auch komplett aus dem Hauptfenster gelöst und auf dem Bildschirm verteilt werden.
Unser Paradebeispiel Wireshark unterstützt leider keine Dock-Widgets, deswegen sehen wir uns den KDE-Dateibrowser Dolphin an:

Dock-Widgets können über die Methode setWidget() ein anderes Widget aufnehmen und danach über die Methode addDockWidget() zum Hauptfenster hinzugefügt werden. Alle anderen Funktionalitäten übernimmt das Framework bereits für uns.

Hier wird gerade das Terminal-Dock-Widget über das Hauptfenster gezogen. Die Position des Widgets beim Loslassen der Maustaste wird deshalb hervorgehoben. Da Dock-Widgets für unsere Anwendung eher unpassend sind, verzichten wir auf eine Implementierung.