Windows & Dialogs
Subclassing QMainWindow
This first task involves the creation of a small but useful image viewing application that demonstrates the most important features of QMainWindow. It is bigger than anything you’ve implemented thus far in these exercises and should give you a good feel for what is involved in building a fully-featured desktop application using Qt.

Widgets & Status Bar
Create a directory for this exercise, download
viewer.zipinto it, then unzip the archive. This should give you a subdirectoryviewer, containing- A CMake build file
- Source files
viewer.hpp,viewer.cpp,main.cpp - Subdirectory
images, containing a few JPEG images1
Open
viewer.hppin a text editor and examine it. This file defines a class namedImageViewerthat inherits fromQMainWindow. If you look at the corresponding implementation file,viewer.cpp, you will see that the methods ofImageViewerare mostly empty, but enough has been implemented that you can run the application.Use CMake to build the application in the normal way, then run it with
./viewer. You should see a blank window appear.Complete the
createWidgets()method by adding the following code to it:imageWidget = new QLabel(); imageWidget->setScaledContents(true); QScrollArea* view = new QScrollArea(); view->setBackgroundRole(QPalette::Dark); view->setWidget(imageWidget); setCentralWidget(view);QMainWindowuses the notion of a central widget, occupying the main area of the window. This code defines that central widget to be aQScrollAreawidget, containing aQLabelwidget. An image will be associated with the latter.Rebuild the application and run it again to check that it still creates a blank window.
Add a status bar to the application by putting the following code in the
createStatusBar()method:fileInfo = new QLabel(); zoomInfo = new QLabel("x1"); QStatusBar* status = statusBar(); status->addWidget(fileInfo); status->addPermanentWidget(zoomInfo);This code gives the window a status bar containing two
QLabelwidgets. One of them,fileInfo, will be used to show the filename of the currently loaded image. The other,zoomInfo, will be used to show the current magnification factor at which the image is displayed.The latter is added as a ‘permanent widget’, meaning that it will be positioned at the far right of the status bar and will always be visible. The filename, on the other hand, will appear on the left and can be temporarily hidden—e.g., by ‘status tips’ for menu entries.
Rebuild the application and run it again. You should see a status bar at the bottom of the window now, with
zoomInfo’s default text ofx1visible on the right.
Final Steps
ImageViewerhas two helper methods namedupdateStatus()andsetDisplayedSize()that are needed by its slots. The first is used to update the status bar with the current image zoom factor and the second is used to resize the label holding the image, according to the current image zoom factor. Add code to these methods now so that they look like this:void ImageViewer::updateStatus() { QString zoomText = "x" + QString::number(zoomFactor); zoomInfo->setText(zoomText); } void ImageViewer::setDisplayedSize() { imageWidget->resize(zoomFactor*image.width(), zoomFactor*image.height()); }The penultimate step is to finish the implementation of the
openImage()slot. Add the following code to this method:QString path = QFileDialog::getOpenFileName( this, "Open Image", ".", "Image files (*.jpg *.png)"); if (path.size() > 0) { image.load(path); imageWidget->setPixmap(image); fileInfo->setText(QFileInfo(path).fileName()); zoomFactor = 1; setDisplayedSize(); updateStatus(); }This code begins with a call to the
getOpenFileName()function of standard dialog classQFileDialog. This will open a standard dialog for selecting an image file. The string arguments to this call are the dialog’s title, the initial directory displayed by the dialog (the current directory, in this case) and a filter that restricts displayed files to those with.jpgand.pngfilename extensions.If the user selects an image file, the
pathvariable will be a non-empty string and theifstatement body will run. This will load and display the image, and also update the status bar with the name of the image file.Rebuild and run the application now and try opening one of the provided image files. Notice how the dialog only shows these files, and how it displays small thumbnails beside their names.
Finally, we need to add support for zooming in and out of an image. Find the
zoomIn()slot and add the the following code to it:if (image.width() > 0 and zoomFactor < MAX_ZOOM) { zoomFactor++; setDisplayedSize(); updateStatus(); }Write something similar for the body of the
zoomOut()slot. (Here, you will need to ensure thatzoomFactoris limited to a minimum value of 1.)Rebuild the application and run it again. Open an image file and try zooming in and out, using the menu actions and the corresponding keyboard shortcuts.
Subclassing QDialog
Many GUIs combine a main application window with smaller windows know as dialogs. The main window is always visible, but the dialogs tend to be more transient, appearing for a short time to provide information or allow the user to perform a particular task. Dismissing a dialog will not affect the main window, whereas closing the main window will also destroy any dialogs associated with it.
Let’s add a simple dialog to the image viewer application. This dialog will provide some information about the currently-loaded image.
The InfoDialog Class
In the same directory as
viewer.hpp, create a new header file namedinfo.hpp, containing the following:#pragma once #include <QDialog> class QPushButton; class QTextEdit; 1class InfoDialog: public QDialog { public: 3 InfoDialog(QWidget*); 4 void updateInfo(const QPixmap&); private: 2 QTextEdit* info; QPushButton* closeButton; void createWidgets(); void arrangeWidgets(); };- 1
-
InfoDialogis a subclass ofQDialog, Qt’s generic dialog class. - 2
-
This dialog contains only two widgets: a
QTextEditwidget, which will be used (in read-only mode) to display the information; and a button, which will be used to dismiss the dialog. - 3
-
When an
InfoDialogis created, it must be supplied with a pointer to its parent, the main application window that effectively owns it. - 4
-
The
updateInfo()method will be called by the application’s main window. The latter will supply a reference to the currently-displayed image. The method will extract information from the image and use it to update what is displayed by the dialog.
Now create the implementation file for the dialog,
info.cpp. Begin by writing the#includedirectives and the constructor:#include <QtWidgets> #include "info.hpp" InfoDialog::InfoDialog(QWidget* parent): QDialog(parent) { createWidgets(); arrangeWidgets(); setWindowTitle("Image Info"); }This follows the pattern seen in earlier exercises, delegating the work of widget creation and layout to private helper methods.
Now add those methods:
void InfoDialog::createWidgets() { info = new QTextEdit(); 1 info->setReadOnly(true); closeButton = new QPushButton("Close"); 2 connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); } void InfoDialog::arrangeWidgets() { QVBoxLayout* box = new QVBoxLayout(); box->addWidget(info); box->addWidget(closeButton); setLayout(box); }- 1
-
QTextEditis used solely for display, so we disable editing. - 2
-
Connecting the
clicked()signal to theclose()slot inherited fromQDialogmeans that button clicks will dismiss the dialog.
The last piece of code needed in
InfoDialogis an implementation of theupdateInfo()method:void InfoDialog::updateInfo(const QPixmap& image) { 1 QString text("No image loaded!"); 2 if (image.width() > 0) { 3 text = QString("Width = %1\nHeight = %2\n") .arg(image.width()) .arg(image.height()); } info->setText(text); }- 1
- The default message displayed by the dialog will be that there is no image!
- 2
- If the pixmap has a width greater than 0 then an image must exist, so we can replace the default message with actual information about that image.
- 3
-
Qt’s
QStringclass allows you to replace placeholders like%1,%2with values supplied via thearg()method.
Finally, modify
CMakeLists.txt, adding a dependency oninfo.cpp. You will need to alter the call to theqt_add_executable()function so that it looks like this:qt_add_executable(viewer main.cpp viewer.cpp info.cpp )Save your changes and rerun CMake to update the makefile, then run the build to check that you’ve not made any mistakes:
cmake .. make
Changes to ImageViewer
Make the following three changes to the definition of
ImageViewerinviewer.hpp:Add a forward reference to the
InfoDialogclass, underneath the other forward references:class InfoDialog;Add a new field to the
privatesection of the class definition, representing the dialog:InfoDialog* infoDialog;Declare a new slot in the
private slotssection:void showInfo();
Now turn your attention to
viewer.cpp.Add a
#includedirective forinfo.hppto the top of the file.Modify the constructor so that it make the
infoDialogfield a null pointer, in the constructor’s initializer list:ImageViewer::ImageViewer(): QMainWindow(), zoomFactor(1), infoDialog(nullptr) { ... }Modify
addViewMenu()so that it creates an action that will open the dialog. These lines will need to be added:QAction* infoAction = new QAction("Info", this); infoAction->setStatusTip("Displays some basic info on this image"); connect(infoAction, SIGNAL(triggered()), this, SLOT(showInfo())); ... viewMenu->addAction(infoAction);(Note: the ellipsis above represents code from the method that isn’t shown here)
Implement the
showInfo()slot inviewer.cpp:void ImageViewer::showInfo() { 1 if (infoDialog == nullptr) { infoDialog = new InfoDialog(this); } infoDialog->updateInfo(image); 2 infoDialog->show(); 3 infoDialog->raise(); 4 infoDialog->activateWindow(); }- 1
-
If
infoDialogis null then the dialog hasn’t been created yet, so we will need to create it. - 2
-
Invoking
show()will make the dialog visible if necessary. - 3
-
Invoking
raise()will move the dialog above any other windows that may be covering it. - 4
-
Invoking
activateWindow()will grant focus to the dialog.
Rebuild and run the application. Try activating the dialog when no image is loaded, then try doing the same after loading an image. You should see something like this appear:

What happens if you open a new image file while the dialog is on screen?
Fixing The Problem
There are two ways of fixing the problem.
One way would be to make the dialog modal. A modal dialog seizes focus and prevents the user from interacting with any other part of the application until the dialog has been dismissed. To implement this new behaviour, you need to replace the lines invoking the show(), raise() and activateWindow() methods with a line that invokes the exec() method:
infoDialog->exec();The other approach would be to add code to the openImage() method that invokes updateInfo() on the dialog if it exists and is currently visible:
if (infoDialog != nullptr and infoDialog->isVisible()) {
infoDialog->updateInfo(image);
}Apply one of these fixes, then rebuild the application and run it to check that the fix has worked.
Footnotes
These are images from the Rosetta, Dawn and New Horizons spacecraft.↩︎
