An introduction into Qt - Part II
published at 21.07.2013 13:16 by Jens Weller
Save to Instapaper Pocket
Welcome to Part II of our little tour through Qt. This time I'll focus on GUI, Widgets and Qts Model/View system. As Qt is a quite large framework, I can not cover many details. Also, as all Qt classes start with Q, there is going to be alot of Qs in this series. Qt it self uses only a few namespaces, and does not have a central namespace wrapping the library like boost has. In the previous, first part I gave a short overlook about Qt Core.
Qt GUI
This module has changed with Qt5, it now contains no longer QWidgets, its now a basic module for UI classes, which are not based on QWidget. It contains the old Qt OpenGL module. The two most important classes are QGUIApplication and QWindow. QGuiApplication is a UI Specific Application class. Like QApplication, it handles the main-event loop. QWindow is derived from QSurface, another important part of QGui. QWindow servers as the base class for 2d Windows and OpenGL views. The new QML UI is based on this basic API, provided by this module. QWindow supports rendering in OpenGL, OpenGL ES 1.1 and OpenGL ES 2.0. This module also holds Qts highly optimized 2D vector drawing API, aka QPainter. This module is also the base for Qts new UI Technique QML/Qt Quick.
QWidgets
The QWidgets module is new with Qt5, it contains now the QWidgets related classes. With Qt5.1, there is support to integrate QWindow based Surfaces into QWidget, as QWidget it self, is its own UI Module and not based on QWindow. This module contains the 5 important parts, that make up QWidgets:
- QWidget
- QLayout
- QStyle
- Model/View framework
- GraphicsView framework
The QWidget part contains the classes used to build the GUI it self, controls like labels, checkboxes, textboxes and buttons. These can be put into a layout, so that the size is automatically adjusted when the Window resizes. QStyle allows to use CSS for styling the UI Controls. By default Qt uses a Style that draws it self to mimic the native widgets. The model/view framework is a very important part of Qt, especially when building UI Applications. It allows to use Model-View-Controller style application logic in Qt, the views get automatically updated when the data changes in the model. The GraphicsView Framework is a whole windowing framework on its own, its based on 2D Surfaces, and allows to implement all kind of 2D Drawing scenes.
So, lets start with some code, a little hello world with Qt Widgets. Fire up QtCreator and start a new Qt GUI Application Project with clicking File-> New. QtCreator will now load the standard Widget Template for most Qt widgetbased applications. If you compile, you'll see an empty Window with the title MainWindow. I prefer using QtCreator for working with Qt, but you also can use other IDEs, QtCreator has already integrated a RAD Building tool, for clicking our surfaces. But first, lets look at the code, main.cpp:
int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
This is the standard main function, which starts our Application. You can add code for translation or loading config files before the Application starts here, but mostly we will work in the class MainWindow for our Hello Widgets project. The MainWindow class is derived from QMainWindow:
namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; };
First, we see the forward declaration of a MainWindow class in a namespace ui. Then a normal class with constructor/destructor follows, holding a pointer to ui::MainWindow. And then there sits this Q_OBJECT thing in the code, thats something to get used to, when using Qt. QObject derived classes use this macro to generate the code for Qts Meta Object System. The Moc will turn Q_OBJECT into something use full. Looking at the cpp file:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; }
Nothing too crazy. The pointer to ui is created and freed, and ui->setupUi is called with the this pointer in the constructor. This Ui::MainWindow class has to do something with the moc. Qt saves its UI templates internally as .ui, which is a xml format, the moc has a code generator, which turns this xml into a c++ class containing the UI code. As this is generated at compiletime, you will find the corresponding code in the folder also containing the object files. So, lets add some UI controls to our example, simply open mainwindow.ui in QtCreator. Now the view changes to the design perspective, if you'll click on the edit perspective, you get to see the xml format behind the UI Editor.
In the UI Design view, to the left is a list of Controls and Layouts you can drag onto the window in the center. To the right you see a detailed property view of your current selection and an object tree of the current window. We add to the scene the following controls:
- Horizontal Layout
- drag into this layout a LineEdit and a PushButton
- Add below a ListWidget. (Widget, not view)
Your window should look now somewhat arkward, with controls placed on its surface. You could drag and adjust the size of each window, to get closer to the look and layout for this application. Or, we could let Qt do that. Click on MainWindow in the Object Tree on the right. Now, open the contextmenu, and go to Layout, to see which different Layouts you can apply to MainWindow as a default Layout. For our usecase, press CTRL + L. Suddenly our window looks a little better. If you select a control, you can edit its properties on the property view on the right. Objectname will also be the name of the member of Ui::MainWindow, with which we will be accessing the UI controls. With clicking on the PushButton we can rename its text to "Add Text". Next we select the PushButton again, and open the Context Menu, and go to slots, we'll add a slot for the clicked() signal. Now you should end up in mainwindow.cpp, in a newly created slot method. What happend? QtCreator automatically adds the slots from the UI to the code. The moc will generate the code needed to glue the SIGNAL clicked() to our SLOT on_pushbutton_clicked(). Now, lets add some glue code, to make theHelloWidgets App do something:
void MainWindow::on_pushButton_clicked() { QString text = ui->lineEdit->text(); if(text.isEmpty()== false) ui->listWidget->addItem(text); ui->lineEdit->clear(); }
These 4 lines of code are pretty easy to understand: QLineEdits method text() returns the current text, which gets stored into a QString. Then text is tested for being not empty, and added to the listWidget via QListWidget::addItem. The last line clears the QLineEdit.
So, this is a very basic example on how to use widgets to make GUI Applications with Qt. As you can see in the Design View of Qt Creator there are lots of Controls and widgets, you'll find a complete overview here.
With this example, we already used the QLayouts for layouting our window. Qt offers several different Layout options, the five main classes used for layouting are:
- QHBoxLayout (Horizontal Layout)
- QVBoxLayout (Vertical Layout)
- GridLayout
- FormLayout
- Spacer
The first 4 are layout classes used to achieve a certain layout in a window, Spacers are used to insert a blank into a window. For example when inserting a row of buttons into a horizontal layout, a spacer can be used to let only the spacer grow, and makes the buttons keeps their size when resizing the window. There is a more complete overview into QLayouts in the documentation.
Model/View Framework
In the previous example we only had a LineEdit and a Button to add its text to a ListWidget. A ListWidget is a item based class, meaning, that the control keeps its own list of Items which it displays. This leads to problems, when you want to display data in multiple places, also editing would require us to extract the selected item, put its text in the line edit and, oh wait the button click will add it again. Models are a way to manage your data easily within Qt, and let Qt do the work for updating the views and doing the editing. So, in the above example, one simply could add a QStringListModel to the MainWindow class, and replace the ListWidget with a QListView:
// in MainWindow.h QStringListModel model;//a simple model holding a list of strings // in the constructor we initialize the model and the view ui->listView->setModel(&model); // the code for the pushbutton changes slightly: QString text = ui->lineEdit->text(); if(text.isEmpty()== false) { model.insertRow(model.rowCount()); model.setData(model.index(model.rowCount()-1),text); } ui->lineEdit->clear();
As you can see, QStringListModel is not prepared to have data added from the outside, except its setStringList method, which lets you change the list displayed. Inserting into the model is done over the traditional model interface, which for example, does not know anything about StringLists. In Qt all models operate on a certain concept of indexes storing data at a row/column scheme. In this case, the model will update our view when we add new data to it. Also, a model can have multiple views.
Qt offers a few predefined Model classes, such as QStringListModel or some models for accessing database tables or queries. There are also 4 base classes available for implementing your own model classes:
- QAbstractItemModel - the base class for all model classes in Qt
- QAbstractListModel
- QAbstractProxyModel
- QAbstractTableModel
The last three are deriving from the first. QStringListModel derives from QAbstractListModel. I'd like to close this part with a simple example on how to use QFileSystemModel to create a picture viewer like Application. Simply open a new Qt GUI Application, and drag a TreeView and a Label into the MainWindow. CTRL + H should give you the horizontal layout. Mark the TreeView, go over the context menu to the slots, and create a slot for clicked(const QModelIndex& index). And now to the code, which we need for our Image Viewer:
//add to the MainWindow class in mainwindow.h + include QFileSystemModel filemodel; // setup in the constructor of MainWindow filemodel.setRootPath(QDir::rootPath()); QStringList filters; filters << "*.png" << "*.jpg" << "*.bmp"; filemodel.setNameFilters(filters); //hide filtered files filemodel.setNameFilterDisables(false); ui->treeView->setModel(&filemodel); // code to display the image in onclicked if(!filemodel.isDir(index)) { QString path = filemodel.filePath(index); QPixmap img(path); ui->label->setPixmap(img.scaled(ui->label->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation)); }
From the top:
- add a QFileSystemModel as a member variable to the MainWindow class
- the setup:
- set the Path to the Root Path for the FileSystemModel
- Prepare a list of filters, so other files are not visible
- In order to achive this, we call setNameFilter
- setNameFiltersDisables(false) is needed, to not display the filtered files, by standard the model displays all files, and greys the filtered out
- Set the model to the treeView
- When a node in the treeview ist selected:
- first, make sure its not a directory
- Load the image into a QPixmap
- set the pixmap to the label, scaled adjusts the image size to the size of the label.
Thats it. In a few lines we have written a simlpe image viewer. I'd like to close Part 2 with this, Part 3 is going to start with how to implement your own model, and how XML and Databases can be used in Qt.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!