Background Grid in Qt Widgets Applications

C++ Qt

If you are developing a drawing application, a CAD program or any type of software, which allows the user to arrange items in a document, it is a good idea to provide some kind of a visual aid to support the proper placement of those items. Displaying a grid as a background might be just the right solution in this case. It could also enhance the visual appeal of your application, making it look more professional.

In this tutorial we are going to see how to display a grid as a background of a graphical scene in a Qt Widgets application.

Basic Application

Suppose that our application consists of a MainWindow subclassed from QWidget, which displays a QGraphicsScene with one movable QGraphicsRectItem like this:

#include "MainWindow.h"
#include <QBoxLayout>
#include <QGraphicsView>
#include <QGraphicsRectItem>

MainWindow::MainWindow(QWidget *parent) :
	QWidget(parent)
{
	auto *l = new QVBoxLayout(this);
	auto *graphicsView = new QGraphicsView(this);
	auto *item = new QGraphicsRectItem(0, 0, 100, 60);

	item->setPos(70, 90);
	item->setBrush(Qt::red);
	item->setFlags(QGraphicsItem::ItemIsSelectable
				   | QGraphicsItem::ItemIsMovable);

	graphicsView->setScene(new QGraphicsScene(this));
	graphicsView->setSceneRect(0, 0, 800, 800);
	graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
	graphicsView->scene()->addItem(item);

	// TODO Add grid

	l->addWidget(graphicsView);

	resize(400, 400);
}

As a result, the following window is produced:

Displaying a Static Grid

Now let’s say, that for a whatever reason, we need to display a background grid.

One approach would be to compose the grid by ourselves, adding the corresponding items (lines or rectangles) to the QGraphicsScene manually, e.g. using for loops. However, this is not a good idea, because:

Another approach is to take advantage of the fact, that a grid is actually a repetitive pattern. This way we could prepare a small image in advance and instead of adding it to the scene, setting it as a background of the QGraphicsView in a tiled manner.

Below are some example patterns, which might be used as a grid. Please note, that the figures represent a zoomed version of each image. The actual images, used for this tutorial, are 20px in width and height. The top left corner serves as a reference point (0, 0). The images are added to the application’s resources under pix/images.

Square Pattern
Cross Pattern
Fancy Pattern

QGraphicsView supports custom backgrounds through its backgroundBrush property. Hence, adding a background grid to the graphics view is nothing more than specifying one of the prepared images as its background brush:

graphicsView->setBackgroundBrush(QPixmap(":/pix/images/grid_square.png"));

This produces a nice background grid in a very efficient way:

Indeed we have implemented a background grid. However, it is not yet possible to make any adjustments to its appearance.

Of course, we could call QGraphicsView::setBackgroundBrush with a different argument:

graphicsView->setBackgroundBrush(QPixmap(":/pix/images/grid_cross.png"));

which would produce a different type of a grid:

Hence, to allow us to have more flexibility with regard to the change of the grid’s appearance, we could make the image path in the call to QGraphicsView::setBackgroundBrush variable:

graphicsView->setBackgroundBrush(QPixmap(":/pix/images/" + pattern + ".png"));

Now the image, used to create the grid, could be specified at runtime.

If this is all what you need for your application, this tutorial might end here. However, if you are looking for an even more flexible solution, please do read further.

Making the Grid Dynamic

Currently we are able to give the background grid an arbitrary look, as long as we have the necessary images prepared in advance. An obvious drawback of this approach is the rapid increase of the number of images we have to prepare, when adding more cutomization parameters, e.g. a grid step and a color. To overcome this drawback, instead of having a potentially huge number of prepared images, we could let an image to be drawn with the desired settings on-demand.

To demonstrate this approach, we will create a new class, PixmapBuilder, to which we are going to delegate the drawing of the images. This class will have only one public method declared in the following way:

static QPixmap drawPattern(int type, int step, const QColor &color);

The method accepts the grid’s type, step and color as arguments and returns a QPixmap, which we can directly use in the call to QGraphicsView::setBackgroundBrush. PixmapBuilder is not storing any state, hence we can make drawPattern static.

Furthermore, we declare three private static methods, one for each of the supported patterns, to perform the actual drawing:

static void drawSquare(QPainter *painter, int width, const QColor &color);
static void drawCross(QPainter *painter, int width, const QColor &color);
static void drawFancy(QPainter *painter, int width, const QColor &color);

To simplify the selection of a particular pattern, we define the pattern types as an enumeration:

enum PixmapType : int {
	PT_Square = 0,
	PT_Cross,
        PT_Fancy
};

Now, let us start with the implementation of the class methods we have declared.

The pattern will be drawn on the QPixmap by a QPainter. Thus, in PixmapBuilder::drawPattern we need to:

which in code looks like this:

QPixmap PixmapBuilder::drawPattern(int type, int step, const QColor &color)
{
	QPixmap pixmap(step, step);
	QPainter painter;
	int pixmapWidth = pixmap.width() - 1;

	pixmap.fill(Qt::transparent);

	painter.begin(&pixmap);

	switch (type) {
	case PT_Square:
		drawSquare(&painter, pixmapWidth, color);
		break;
	case PT_Cross:
		drawCross(&painter, pixmapWidth, color);
		break;
	case PT_Fancy:
		drawFancy(&painter, pixmapWidth, color);
		break;
        }

	return pixmap;
}

As for the actual drawing, the code for the pattern of a squared grid looks like this:

void PixmapBuilder::drawSquare(QPainter *painter, int width, const QColor &color)
{
	painter->setPen(color);
	painter->drawLine(0, 0, width, 0);
	painter->drawLine(0, 0, 0, width);
}

The following snippet draws the pattern of a crossed grid:

void PixmapBuilder::drawCross(QPainter *painter, int width, const QColor &color)
{
	painter->setPen(color);
	painter->drawLine(0, 0, 2, 0);
	painter->drawLine(0, 0, 0, 2);
	painter->drawLine(0, width - 1, 0, width);
	painter->drawLine(width - 1, 0, width, 0);
}

And finally, the fancy pattern is drawn in the following way:

void PixmapBuilder::drawFancy(QPainter *painter, int width, const QColor &color)
{
	int halfWidth = 0.5*width + 0.5;

	painter->setPen(color.lighter(106));
	painter->drawLine(0, halfWidth, width, halfWidth);
	painter->drawLine(halfWidth, 0, halfWidth, width);
	painter->setPen(color);
	painter->drawLine(0, 0, width, 0);
	painter->drawLine(0, 0, 0, width);
	painter->setPen(color.darker(118));
	painter->drawPoint(halfWidth, halfWidth);
	painter->setPen(color.darker(160));
	painter->drawPoint(0, 0);
}

Now we are able to use the PixmapBuilder in the MainWindow to produce the desired image. At this point changing the grid is as easy as calling PixmapBuilder::drawPattern, specifying the desired pattern type, step and color values.

For example the following code produces a square magenta grid with a step of 100px:

graphicsView->setBackgroundBrush(PixmapBuilder::drawPattern(PixmapBuilder::PT_Square, 100, "#C000C0"));

And here is how to produce a green grid with cross marks and a step of 40px:

graphicsView->setBackgroundBrush(PixmapBuilder::drawPattern(PixmapBuilder::PT_Cross, 40, "#009000"));

This way it is possible to setup the background grid at runtime.

Practical hint: In a real-world application the grid settings could be made available to the user in a dedicated input form with three controls – for the pattern type, the step and the color of the grid. The selected values could be stored as QSettings and retrieved later to be used in the calls to PixmapBuilder::drawPattern.

Exercises

The full code of this tutorial is available on Github. I highly encourage you to download, examine and play with it. To solidify the knowlege you have just obtained, do the following exercises:

Exersice 1: Add a support for a dotted pattern to the application by making the following changes to PixmapBuilder:

  • add a new value to the PixmapType enumeration,
  • declare a new private static method to draw the dotted pattern and implement it,
  • add a new case to the switch statement in PixmapBuilder::drawPattern to call the newly created draw method.

Exercise 2: Allow the fill color of the grid to be specified by making the following changes to PixmapBuilder:

  • add a new argument, fillColor, of type const QColor & to the signature of PixmapBuilder::drawPattern,
  • in drawPattern use fillColor to fill the pixmap, instead of Qt::transparent.

Exercise 3: Allow the grid to be toggled on and off by the user by making the following changes to MainWindow:

  • add a private attribute, m_grid, of type QPixmap to MainWindow and initialize it with the returned value of a call to PixmapBuilder::drawPattern,
  • add a checkable QPushButton to the layout of MainWindow,
  • connect the push button to a lambda function, where the background brush of graphicsView is set to either QBrush(), off-state, or QBrush(m_pixmap), on-state, depending on the checked state of the push button.

Conclusion

In this tutorial you have learned how to draw images at runtime with Qt and use them as a background grid of QGraphicsView. Additional benefit for you is the possibility to use this approach for drawing images on-demand for any other visual element of your application’s user interface, as the icons of the push buttons for example, thus making it easily customizable.

In the next tutorial we are going to see how to obtain the same results with QML.