Qt 5 Projects

5 (4 reviews total)
By Marco Piccolino
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Writing Acceptance Tests and Building a Visual Prototype

About this book

Qt is a professional cross-platform application framework used across industries like automotive, medical, infotainment, wearables, and more. In this book you’ll initially create a to-do style app by going via all stages for building a successful project. You'll learn basics of Qt's C++ and QML APIs, test-driven development with Qt Test, application architecture, and UIs with Qt Quick & Quick Controls 2.

Next, you’ll help two startups build their products. The first startup, Cute Comics, wants to help independent comic creators with a suite of apps that let them experiment with comic pages, image composition, comic dialogues, and scene descriptions.  While developing these apps you’ll deepen your knowledge of Qt Quick's layout systems, and see Qt 3D and Widgets in action.

The second startup, Cute Measures, wants to create apps for industrial and agricultural sectors, to make sense of sensor data via a monitoring system. The apps should run seamlessly across devices and operating systems like Android, iOS, Windows, or Mac, and be cost-effective by integrating with existing web technologies. You take the role of lead developer and prototype the monitoring system. In doing so you’ll get to know Qt's Bluetooth and HTTP APIs, as well as the Charts and Web Engine UI modules.

These projects will help you gain a holistic view of the Qt framework.

Publication date:
February 2018
Publisher
Packt
Pages
360
ISBN
9781788293884

 

Chapter 1. Writing Acceptance Tests and Building a Visual Prototype

Qt (pronounced like the English adjective cute) is just that, incredibly cute.

If you start working with Qt extensively, you will hardly find another piece of software that tickles your imagination and creativity while catering for your professional needs as much as Qt does. It's got it all. As a professional, you will certainly value its more than 20 years of maturity, solid release cycle, and backward compatibility promises. As a hobbyist, you will fall for its cutting-edge features. In both cases, you will appreciate its smooth and powerful graphic capabilities, extensive collection of general purpose programming libraries, unrivaled cross-platform support, great all-around tooling, good documentation, and thriving community. Furthermore, you will treasure its concise syntax with the QML and JavaScript languages, and its horsepower and expressiveness with the C++ language, as well as its language bindings for Python, Go, and more.

Given its magnificence, you will be tempted to just jump into coding and learn things as you go along. I know how it goes; I went through it, and now I am here, and I probably have something to say about it. Do I regret having taken the hard route? Well, yes and no. Yes, because it took me a few complete app remakes to get things reasonably right; no, because, of course, I learned a lot along the way. At least, I learned exactly (and this is still an ongoing process) what not to do with the many facilities that Qt has to offer.

On the other hand, you have this book in front of you; it probably means that you didn't want to begin this journey all by yourself, right? Maybe you did, and you soon realized that you needed a travel mate. Well, here I am, your new travel mate. Where shall we start from? I can see you now; you are all excited. However, before we jump in, let us first look at the larger picture.

You won't be able to dwell in the house you are trying to build if you don't first sit down to do your planning and research before laying the very first brick. You can take care of how the mirror in the bathroom will look later on. You just need to start with the most important thing, and that is a high-level plan that provides you with an overview about what you want to build.

 

Don't come to me with an idea, come to me with a plan


One summer, I had the pleasure to be mentored by a guy called Shai. From what I gathered, Shai was probably a serial entrepreneur, certainly an investor, and most definitely an excellent trainer.

One of the things I learned from him during the brief summer school, which he was leading, is that you have to drink plenty of water for your body to function properly and your ideas to flow.

Another thing that I learned from him is that in entrepreneurship, just as in software development, plans are far superior to ideas. Having a good idea is essential, but it is not enough. You need a plan. This is what he used to say to soon-to-be entrepreneurs who went to him to present their shiny, groundbreaking ideas:

"Don't come to me with an idea. Come to me with a plan."

Exploration is one thing, learning to achieve expertise is another. While exploring, you taste a bit of this and a bit of that, without a clear purpose or plan — that is, without a blueprint. Exploration is good and necessary, but it has its limits. If you want to just explore what Qt does, there is plenty of very good material covering that:

I'll give you specific pointers to many of these resources wherever needed. I also covered many of these in the Preface.

In this book, I will follow a strong goal, project, and scenario-based approach, showing you how to apply current best practices of software development by leveraging what Qt has to offer. I'll provide you with some ready-made plans in the hope that you will come up with even better ones for your own projects, be it a hobby project or a business-related endeavor. We will use an outside-in approach, starting from clear functional goals all the way inwards to implementation details. Enough said! Let's get started with the first project.

In this chapter, we will lay the foundations for a simple to-do list-like application by dealing with its intended goals, main scenarios and usecases, and UI prototyping. This will give you a good introduction to how you can perform Behavior-Driven Development (BDD) with the QtTest framework, Qt's object model, introspection features and signals/slots, an overview of available Qt rendering frameworks, and UI prototyping with Qt Creator's Quick Designer.

 

The problem — what's in my fridge?


If you are an out-of-town student, and even if you are not, you may know all too well the sensation of desolation that often surfaces when you open the fridge and find empty shelves. Look! A lonely slice of cheese is greeting you and asking for your companionship. Oh, and that thing nearby probably used to be an apple a few months back. Dammit! You were working on your much-beloved personal project and completely forgot to buy groceries, and now the shops are closed, or are too far away to bother going.

The solution — an app idea

No problem, right? If you are lucky, there is yet another cheap pizza waiting for you in the deep freeze, otherwise you will be once again eating some cheap takeaway.

Wrong! If this becomes the normal solution, in a few years' time, your liver will curse you in ways you cannot even imagine. The real solution is this: don't be lazy, take good care of yourself. Remember Shai? Besides drinking plenty of water, you should also eat stuff that is good for your body (at the entrepreneurship camp, apart from great catering, next to the water bottles, there was a pile of apples, and both the water bottles and the apples were freely accessible all day long).

How could you start implementing this sensible advice? One good thing to do would be to keep track of your food resources and intervene before it's too late. Of course, you could do that with just a pencil and paper, but you are a nerd, right? You need an app, and a cute one! Well, here you go; finally, your personal project will contribute to your health rather than taking its share from it.

The plan — start from user stories

OK, we need an app. Let's start building the user interface (UI).

But what if we wanted the same application to have a graphical UI and at the same time leave the door open to add a console-based UI, or even a voice interface in the future? We need a way to specify our app's functional requirements before describing how it will look, or how it will be delivered to our users. Also, it would be very useful if we could verify that those requirements are actually met within the code. Even better, in an automatic fashion, by means of what are usually labeled as acceptance tests, using procedures that verify that all or most of our app's usage scenarios are actually working as expected. We can achieve that by starting from user stories.

 

Writing features and scenarios


Behavior-Driven Development (BDD) is a way of developing software that encourages starting from user stories (or features) and then moving on from those to system implementation. According to Dan North, one of BDD's initiators, a feature is a description of a requirement and its business benefit, and a set of criteria by which we all agree that it is “done”. The main goal of BDD is for project stakeholders (customers, business people, project managers, developers, and people who work in quality assurance) to share common expectations about a feature. The description of how a feature should behave in a specific context of preconditions and outcomes is called a scenario. All scenarios outlined for a specific feature constitute its acceptance criteria: if the feature behaves as expected in all scenarios, it can be considered as done. For a clear and synthetic introduction to BDD take a look at https://dannorth.net/whats-in-a-story/.

BDD is now a widespread approach, and some standards exist to make it easier for stakeholders to share the description of scenarios and their verification. Gherkin (https://github.com/cucumber/cucumber/wiki/Gherkin) is such a standard: a human-readable language, which can also be used by a software system to link usage expectations to system instructions by means of acceptance tests, which strictly follow the structure of scenarios.

The following is what a Gherkin feature specification (a user story outlined as a set of scenarios) looks like:

Feature: Check available groceries
   I want to check available groceries in my fridge
   to know when to buy them before I run out of them
 
   Scenario: One or more grocery items available
      Given there is a list of available grocery items
      And one or more grocery items are actually available
      When I check available groceries
      Then I am given the list of available grocery items
      And the grocery items are ordered by name, ascending

   Scenario: No grocery items available
      Given there is a list of available grocery items
      And no grocery items are actually available
      When I check available groceries
      Then I am informed that no grocery items are available
      And I am told to go buy some more

The Check available groceries feature, which encapsulates assumptions and expected outcomes relative to a specific user action, is analyzed in two scenarios, which show how the outcomes vary depending on different assumptions (one or more grocery items are available versus no grocery items are available).

I guess you can figure out the basic structure of a scenario in its commonest form: one or more Given clauses describing preconditions, one or more  When clauses describing user-initiated or system-initiated actions, and one or more Then clauses describing expected outcomes.

Specifying the behavior of your application in terms of feature scenarios has many benefits, as follows:

  • The specification can be understood even by nontechnical people (in the case of What's in my fridge, this means that your family members can offer their expertise in taking care of home food provisions to help you sketch out the most important features for the app).
  • There are quite a few libraries around that can help you link the specification to the actual code and can then run the gherkin feature file and check whether the preconditions, actions, and expected outcomes (collectively known as steps) are actually implemented by the system (these constitute the acceptance tests for a feature).
  • You need, either individually or as a group of stakeholders, to actually sit down and write the acceptance criteria for a feature in full before writing any code that relates to it. By doing so, you often find out early on about any inconsistencies and corner cases you would have otherwise ignored.

Does Qt provide off-the-shelf support for gherkin-style feature descriptions and for writing automated acceptance tests? Currently, it doesn't. Is there any way to implement this very sensible approach to software development in Qt projects? Yes, there is.

I know of at least the following ways:

  • You can download and build the cucumber-cpp (https://github.com/cucumber/cucumber-cpp) project, which also contains a Qt driver, and try and link it to your project. I haven't tested this way yet, but if you are braver than I, you could give it a go.
  • You can buy a (admittedly, not cheap) license for Froglogic Squish (https://www.froglogic.com/squish/editions/qt-gui-test-automation/), a professional grade solution for many types of application testing, including BDD, which fully supports Qt.
  • You can write your acceptance tests with the Qt Test framework and give them a Gherkin-style structure. This is the approach I currently use in my projects, and in the next section I will show you a couple of ways to achieve this.

So, now that we have written our first feature, with as many as two scenarios, we are ready to dive into code, right?

Not really. How would you add grocery items to the list? How about removing them from the list when you take them out of the fridge? We'll first need to write those two other features at least, if we want to have a minimum viable product.

Note

If you haven't done it yet, now is the time to download Qt for Application Development distribution and install it.There are a few options available, in terms of licensing (commercial, GPL, and LGPLv3), supported host (macOS, Windows, and Linux), target platforms (several available) and installation mode (online versus offline). Regarding licensing, take your time to make an informed choice, and ask a lawyer in case of doubt. For more information, take a look at the Qt Project's licensing page at http://doc.qt.io/qt-5/licensing.html. To download Qt with the online installer for your host platform, go to http://www.qt.io/download, choose one of the available options, and follow the installation instructions. The projects contained in this book are based on Version 5.9, which is a Long Term Support (LTS) version. For a smooth ride, you are encouraged to use the latest available bugfix release of version 5.9. If you are adventurous enough, you could also install a later version. The book's projects should still work with any later 5.x version, but please understand that they haven't been tested for that, so your mileage may vary.

 

Implementing scenarios as acceptance tests


As I mentioned in the preceding section, the standard Qt distribution does not give off-the-shelf support for BDD. However, it does provide a rich testing framework (Qt Test), which can be leveraged to implement most kinds of tests, from unit tests to acceptance tests. In the coming sections, we will implement our first acceptance test with Qt Test. First, however, let us spend a few words on project organization and introduce the two main programming languages currently used in the Qt world: C++ and QML.

Our project structure

Note

Throughout the book, all online documentation links will point to Version 5.9 for consistency. If you want or need to access the latest version of a document, you can just remove the minor version from the URL, as follows: http://doc.qt.io/qt-5.9/qobject.html > http://doc.qt.io/qt-5/qobject.html. Similarly, a later minor version can be accessed by changing the minor version in the URL: http://doc.qt.io/qt-5.10/qobject.html

This book uses the Qt Creator IDE for project development, together with the QMake build and project organization system. Both tools are included in the default precompiled Qt distributions. I will give you hints on how to carry out project-specific operations with both Qt Creator and QMake. For further details on how to operate them, you can look at other specific Packt titles, as well as the official Qt documentation for Qt Creator (http://doc.qt.io/qtcreator/) and QMake (http://doc.qt.io/qt-5.9/qmake-manual.html).

Note

Both Qt Creator and Qt support other build and project organization systems beyond QMake. The most widespread are Qbs (pronounced as cubes, which is part of the Qt Project, http://doc.qt.io/qbs/) and CMake (http://doc.qt.io/qtcreator/creator-project-cmake.html). The deep integration with Qt Creator makes QMake the best choice to get started. You can take a look at using the other systems if your workflow benefits from it. CMake is often used for larger projects, and is sometimes preferred by C++ programmers who are already familiar with it. Qbs might replace QMake as the default build system for the Qt project in future versions of Qt.

The QMake build system provides a way to organize projects into subprojects. Each project structure is described in a .pro file. It is good practice to keep the test suites in separate subprojects (also called test harnesses), which can be run independently of the app's client code. If you look at a file like part1-whats_in_my_fridge.pro file in folder part1-whats_in_my_fridge, at the beginning of the file you'll see the following statements:

# part1-whats_in_my_fridge.pro
TEMPLATE = subdirs

SUBDIRS += usecases

The preceding statements just say this QMake project has child projects, and the first of these is calledusecases. If a folder called usecases that contains a  usecases.pro file is found, a node representing the subproject will appear in Qt Creator's Projects pane.

Note

Qt Creator provides several kinds of project templates. These templates create most or some of the project's boilerplate code for you. To achieve a structure, such as the one described earlier, from scratch, you will need to first create a  SUBDIRS project (New file or Project... > Other Project > Subdirs Project) called qt5projects, then click on Finish & Add Subproject and add another SUBDIRS project called part1-whats_in_my_fridge, and then in turn add to it a SUBDIRS project  called usecases.

At this point, we face our first, fairly important, technological decision.

QML and C++ — when to use each of them

Many Qt modules are offered with two different APIs; one for QML and one for C++. While I can take for granted that you know enough about the C++ language and how to work with it, the same may not be true for QML, which is a language that was born in and is mostly used within the Qt world.

You may hear sooner or later that QML is the language used for building modern UIs with Qt. Although that is certainly true, this often implies to many that QML is only good for implementing UIs. That's not the case, although you should always carefully think what kind of API (C++ or QML) is best suited for the project or the component at hand.

QML is a declarative language, which, on one hand, supports JavaScript expressions and features (such as garbage collection), and, on the other hand, allows us to use Qt objects defined in C++ in both a declarative and an imperative way by taking care of most data conversions between C++ and JavaScript. Another of QML's strengths is that it can be extended from C++ to create new visual and non-visual object types.  For more details about the QML language, you can read the documentation of the QtQml module (http://doc.qt.io/qt-5.9/qtqml-index.html), which provides QML-related functionality to Qt.

The following is a brief comparison of the most remarkable differences between the two languages:

C++

QML

  • Compiled
  • Imperative syntax
  • APIs available for most Qt modules
  • Integration with other C++ modules
  • Power of expression
  • Richer debugging information
  • No property bindings
  • Less overhead
  • Interpreted with optional compilation
  • Declarative + imperative syntax
  • APIs available for selected Qt modules
  • Integration with existing JavaScript code
  • Conciseness of expression
  • Limited debugging information
  • Property bindings
  • Garbage collected
  • Extensible from C++

 

We'll touch upon some of these differences later on.

So, depending on the nature of the project at hand, and of the specific aspect you are working on, you will want to pick one over the other, knowing that some minimal C++ boilerplate code is required for Qt applications to run as executables.

When deciding what language to write a specific application layer in, the availability of relevant Qt APIs for that language is a key factor that influences our choice. At the time of writing, for example, there is no public C++ API for most Qt Quick UI components, and, conversely, there is no QML API for most Qt Widgets UI components. Although it is always possible to write interface code to pass data between C++ and QML, you should always consider whether it is strategic and affordable to do so.

If your Qt application has an evident structure and distinct layers (that is, if it is well organized), it is sometimes possible to swap a specific application layer or a single component written in QML with one written in C++, and vice versa, provided the needed Qt APIs are available in both languages. QML has some more constraints, as it requires you to instantiate a QQmlEngine C++ object in order to be used, and thread-based programming is supported in QML code in a limited way.

There are Qt developers, especially on mobile platforms, who write their applications almost entirely in QML, and developers who just use it for writing Qt Quick UIs, some of them only because there is no C++ API available for the Qt Quick module yet. Sometimes, taking a side for one approach over the other causes almost-religious wars in the community. After you have written a few applications, you'll find where the sweet spot lies for you and your team, also taking into account your specific project constraints (deadlines, target device, developer language skills, and so on). However, as I am here to help you, I will give you all the advice I can about this matter.

A typical Qt-based application has all of its visual appearance defined in QML and all or most of its functional building blocks defined in C++. This allows you on the one hand to make rapid UI changes and explore new UI concepts, and, on the other, to build the UI on a solid foundation. Besides the technology advantages and disadvantages, you will also experience that developers using QML think more like front-end developers and lean towards UI design, while those mostly using Qt's C++ APIs think more like backend developers and lean more towards system functionality, and they are also aware of memory and performance issues. This mix makes Qt great for creating high-performance UI applications.

If you can afford to write all your non-UI components in C++, then go for it. It is definitely a more solid choice and gives you a potential performance edge. However, writing good C++ can be hard and time-consuming. In projects where I already know that I'll be using QML for the UI, I often sketch most of the other application layers in QML and then fully develop some of them in C++ later on, wherever performance or other considerations require it. It's a personal and project-dictated choice, and I reserve the right to change my mind in the future. So, I ask you not to go shouting around that I was the one responsible for instilling bad practices in you.

To give you a taster of how it feels to write Qt components in QML as opposed to C++, I'll show you now how you could express your use case tests both in C++ and QML/JS.

Writing the first acceptance tests in C++

The QtTest framework (http://doc.qt.io/qt-5.9/qttest-index.html, and, in overview, http://doc.qt.io/qt-5.9/qtest-overview.html) is a collection of C++ classes and QML types that provides many tools for writing effective test cases for Qt applications. To create a test case with the C++ API, you should subclass a QObject, and add a method for each test (each method/test corresponding to a BDD scenario).

Note

Qt Creator provides specific templates to generate projects for both single test cases (the Qt Unit Test template) and complete test suites (the Auto Test Project template, which shows up after enabling the AutoTest plugin from the About Plugins... menu entry).

For learning's sake, we'll configure our C++ acceptance tests project from scratch by adding the needed subprojects to the usecases project, and then make the first test fail. We'll make the test pass in the next chapter by implementing the relevant code.

Creating the first C++ test case

At this level of analysis, we may consider a test case as the representation in code of all scenarios that pertain to a certain feature. Each test, in turn, represents a specific scenario. The term use case comes from Alistair Cockburn's classic Writing Effective Use Cases. In our context, you can consider a use case as the system-level description of a feature, a set of instructions that have as an outcome the user story's success or failure, depending on the given preconditions (the current system state).

As we started our feature enumeration by working on check available groceries, we will begin adding tests for that.

Each test case requires its own subproject to be run as an executable. This means that we will subclass QObject into a class that we could call Usecases_check_available_groceries.

Note

Incorporating Usecases_ as a prefix will make it much easier to run the tests with Qt Creator's plugins, such as AutoTest, which scans for all test cases available within a certain project and allows them to be run selectively without switching the build target. In fact, until Qt Creator's version 4.5, AutoTest did not group test cases by folder or subproject, so using a common prefix is the only way to group test cases of the same kind together. In Qt Creator 4.6, AutoTest does provide folder-based test grouping.

We should by now have a subdirs project called usecases, which contains nothing except an empty usecases.pro file, which, in turn, contains a TEMPLATE = subdirs directive.

Note

Ensure that you don't confuse QMake templates with Qt Creator's templates. QMake templates help you specify whether QMake should build an executable (app), a library (lib) ,or nothing (subdirs) with the current project. Qt Creator templates, on the contrary, are just collections of boilerplate code and project configuration wizards. You can learn more about the kinds of projects available for QMake at http://doc.qt.io/qt-5.9/qmake-common-projects.html and on how to create your own Qt Creator's template wizards at http://doc.qt.io/qtcreator/creator-project-wizards-json.html.

We will now add a Qt Unit Test project for our first test case as a subproject of usecases, which we will call check_available_groceries. Because we are using Qt Creator's Qt Unit Test template wizard, we can specify the name of the first test (which we will call one_or_more_grocery_items_available) in the wizard's mask. Here are the values I put in for this test case.

As they differ from the defaults, you may want to take a look at them. Note how the Requires QApplication box is also checked:

Note

Qt Creator's wizard will present you with a list of Qt modules to include in the test case's QMake project. You don't need to select any of them for the time being.

If everything goes as planned, you should end up with a project structure as follows:

Generally speaking, the practice of starting with tests before moving to implementations is called test-driven-development (TDD). TDD is a common way to translate BDD features into working code. The TDD development process goes as follows:

  1. Write a failing test
  2. Run all tests you have written so far, and see the new test failing
  3. Write the minimum amount of functionality to make the new test pass
  4. Run all tests you have written so far and see them all passing — if not, go back to
  5. (If needed) refactor the code that you wrote to make the new test pass
  6. Move to a new test and repeat the procedure

TDD is there to give you as a developer confidence about the functionality that is deemed important for the system you are creating, and confidence about refactoring as well. In fact, when you make a change to your code, by running the test under scrutiny, and all other tests of your test suite, you can immediately notice if you have introduced unintended side effects.

If you now click on the big bold Run button in Qt Creator, you should see output as follows in Qt Creator's Application Output pane:

********* Start testing of Usecases_check_available_groceries *********
Config: Using QtTest library 5.9.3, Qt 5.9.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 7.0.2 (clang-700.1.81) (Apple))
PASS : Usecases_check_available_groceries::initTestCase()
PASS : Usecases_check_available_groceries::one_or_more_grocery_items_available()
PASS : Usecases_check_available_groceries::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 1ms
********* Finished testing of Usecases_check_available_groceries *********

Wonderful! Our first use case test is passing—but, wait a minute, it shouldn't be passing, we haven't written a single line of code to make it pass yet.

This is indeed an unfortunate default choice for the template. If you take a look at the implementation of  one_or_more_grocery_items_available() in tst_check_available_groceries.cpp, you'll note the following statement:

QVERIFY2(true, "Failure");

The QVERIFY2 macro checks the truth value of the first argument, and, in case it is false, it prints the message contained in the second argument. To make the test fail, as a placeholder, you just need to change the first value to false, and perhaps change the message to something more informative, such as "not implemented":

QVERIFY2(false, "not implemented");

Alternatively, you can change it as follows:

QFAIL("not implemented");

Congratulations! Your test does nothing interesting! Don't worry, we will deal with that in the next section.

Now, take your time and start inspecting the contents of the implementation file, tst_check_available_groceries.cpp. If this is your first encounter with a source file based on some Qt classes, you will note a few unusual things:

  • A Q_OBJECT macro at the beginning of the class.
  • A Q_SLOTS macro after the private access keyword.
  • A  top-level QTEST_MAIN(Usecases_check_available_groceries) macro.
  • A top-level #include "tst_check_available_groceries.moc".

The last two points are mostly specific to QtTest; the former provides a default main function to call in order to run the test, and the latter explicitly includes the meta-code file generated by Qt. When a class is defined in a header file, as is customary in application and library code, there is no need to include that file explicitly.

Conversely, you will often encounter the first two macros in many kinds of Qt classes. The macros augment the class definition with, respectively, Qt's object model capabilities and the signals & slots communication system.

Some of the most notable features brought by the Qt object model are as follows:

  • Introspection through the meta-object system

  • The parent-child relationship between objects (which also defines a common pattern of memory management in Qt)

  • Support for properties (intended as groups of accessor methods)

  • Support for the signals & slots communication system

Most of these features, which we will encounter again and again, are activated by including the Q_OBJECT macro in classes that inherit from the QObject class.

Slots are normal class methods, with the added benefit that they can be invoked automatically whenever another method (a signal), from an object of either a different class or the same class (including the same object instance), is called. In other words, a slot subscribes to a specific signal and runs whenever the signal is triggered. For a test case, marking the test functions as slots makes it possible to use them within a test harness so that the tests are run automatically by the system.

Note

This is the right time to start familiarizing yourself with Qt's object model (http://doc.qt.io/qt-5.9/object.html) and the signals and slots communication system (http://doc.qt.io/qt-5.9/signalsandslots.html). Please do it before moving forward! We will have many chances to discuss some aspects of these when they come into play, but a solid understanding of the basics will help you along the way.

Now that the armature of our first test case is in place, we can start writing the first acceptance test.

Adding the first C++ test

Let us recall the first scenario that we came up with:

   Scenario: One or more grocery items available
      Given there is a list of available grocery items
      And one or more grocery items are actually available
      When I check available groceries
      Then I am given the list of available grocery items
      And the grocery items are ordered by name, ascending

Our first test will need to make sure that this scenario can be completed using our implementation. To do this in an efficient manner, we will have to simulate a few things, including the initiation of the user action (I check available groceries), as well as the precondition and outcome data. We want to use mock data rather than making database or web service calls so that we can write our tests focusing on business logic rather than low-level system details and run our test suites multiple times without experiencing unneeded latencies. To make sure that our database or web service calls work as intended, we will later encapsulate those aspects into dedicated classes that can be tested independently.

By implementing the one_or_more_grocery_items_available() test, we are obliged to start thinking about the entities (or business objects) that are needed by our application. We'll perhaps want to model a user, certainly a grocery item, as well as a collection of such items. The details of those entities, especially the method and properties that allow entities to interact with the context they live in (their API), will gradually surface while we keep adding features and scenarios.

From the preceding scenario description, we reckon that we will have to implement a few steps (Given-And-When-Then-And) for the test to pass. We thus revisit the first test and make it fail four times rather than one, by adding a comment for each step that we need to implement:

void Usecases_check_available_groceries::one_or_more_grocery_items_available()
{
    // Given there is a list of available grocery items
    QFAIL("not implemented");
    // And one or more grocery items are actually available
    QFAIL("not implemented");
    // When I check available groceries
    QFAIL("not implemented");
    // Then I am given the list of available grocery items
    QFAIL("not implemented");
    // And the grocery items are ordered by name, ascending
    QFAIL("not implemented");
}

This way, we can make sure that we are fulfilling each scenario step, as each QFAIL statement needs to be substituted with some form of verification that will have to pass, instead of failing.

Let's start with the first step.

Given there is a list of available grocery items

First of all, we should make sure that a list of available grocery items exists. Here is a way of expressing it:

auto groceryItems = new entities::GroceryItems(this);
QVERIFY(groceryItems);

Of course you shouldn't expect this snippet to compile; we have not defined the relevant classes yet; we are just calling their interfaces! Let's look at the snippet line by line. We first create an object representing a collection of GroceryItems. To keep our code well structured, we decide that objects of this kind (business objects or entities, as we have called them) will be grouped under the entities namespace:

auto groceryItems = new entities::GroceryItems(this);

Note

The parameter passed to the constructor (this) represents the parent of that object. As already mentioned, the parent/child relationship in Qt, among other things, plays an important role in memory management: When the parent object (the QObject-based test case) gets destroyed, the child will be destroyed too, without the need for explicit deletion.

Then, we are using the QVERIFY macro (similar to the already-encountered QVERIFY2 macro, but without the option to print a custom message) to make sure the object to which groceryItems is pointing has been created:

QVERIFY(groceryItems);
And (given) one or more grocery items are actually available

This means that somewhere in our system, either locally or remotely, there are one or more objects that represent the actual grocery items currently available in the fridge. For convenience, these data repositories should be distinct from the business objects (the entities) that interact in our application. Repositories should just act as an abstraction to fetch the data from specific storage implementations (databases, files, the web... maybe even a sensor in the fridge?) while keeping the business logic of the entities untouched. For example, it would be very convenient if I could retrieve a list of grocery items in exactly the same way without caring whether they are stored in a local database, in memory, or on the web.

So, whatever the backend of the repository in the final app will be (local JSON file, web-service, SQL database, serialized binary), for the time being we just need to define and then create a dummy object which, upon request, returns the count of available grocery items. We assume that, for this particular scenario to be fulfilled, that count should be greater than zero. The API for the verification of such an object could look like this:

auto groceryItemsRepoDummy = new repositories::GroceryItemsRepoDummy(groceryItems);
groceryItems->setRepository(groceryItemsRepoDummy);
QVERIFY(groceryItemsRepoDummy->count() > 0);

Let us take a look at the above code line by line. First, we want to instantiate the dummy data repository and give it the groceryItems entity as a parent. This way, when we destroy the entity, we will also destroy the repository that is feeding the data to it:

auto groceryItemsRepoDummy = new repositories::GroceryItemsRepoDummy(groceryItems);

Also, we want to connect the dummy repository to the corresponding entity, to make sure this is the repository used by the entity to fetch the data:

groceryItems->setRepository(groceryItemsRepoDummy);

Finally, we want to verify the precondition, that is to say make sure that the repository contains at least one object.

QVERIFY(groceryItemsRepoDummy->count() > 0);
When I check available groceries

After having checked that the app's initial state for our particular scenario is satisfied, we want to simulate a user-initiated action to trigger the use case.

As always, there are many ways we can implement this. For example, we could create a CheckAvailableGroceries class and call some action method (for example, run()) to trigger the logic manipulations that will ultimately result in the Then steps (the scenario outcomes) being fulfilled. Another way is to create a collection of all possible system actions (both user- and system-initiated) in a file, and make use of Qt's signals and slots communication system to fire the actions and handle them in a listening class instance. Alternatively, we could implement the use case actions as pure functions. Here, we choose to model the use case as a class and implement a run() method.

Here is the implementation of this step:

auto checkAvailableGroceries = new usecases::CheckAvailableGroceries(groceryItems, this);
QSignalSpy checkAvailableGroceriesSuccess(checkAvailableGroceries, &usecases::CheckAvailableGroceries::success);
checkAvailableGroceries->run();
QTRY_COMPARE_WITH_TIMEOUT(checkAvailableGroceriesSuccess.count(), 1, 1000);

Let's go through the step definition line by line again.

In the first line, we are creating an instance of the use case object, which will encapsulate all the logic operations that we perform over entities, as well as govern their interactions, when more than one entity is involved. this has the same meaning that it had in the constructor of entities::GroceryItems. Notice also how we are passing a pointer to the groceryItems instance as the first argument:

auto checkAvailableGroceries = new usecases::CheckAvailableGroceries(groceryItems, this);

Then, we are constructing an instance of another important class that comes with the QtTest framework: QSignalSpy. A signal spy makes it possible to listen for a Qt signal within a test:

QSignalSpy checkAvailableGroceriesSuccess(checkAvailableGroceries, &usecases::CheckAvailableGroceries::success);

By being able to wait for a signal to fire before continuing the program flow, we have a convenient means of implementing asynchronous programming techniques, which are very useful if we want to avoid to block the UI whenever a result takes time to be computed. With the preceding line, we are expressing the following:

  • The checkAvailableGroceries will have a signal called success
  • We set up a QSignalSpy called checkAvailableGroceriesSuccess that should inform us once the system action checkAvailableGroceries has been carried out successfully (that is, in practice, when we decide that it's meaningful to emit the success signal because all our use case-related business logic has completed)

Next, we start our use case action by invoking the run method:

checkAvailableGroceries->run();

Finally, this is how we are making sure that the success signal has been fired once (at least and at most) within a given timeout (1,000 msecs):

QTRY_COMPARE_WITH_TIMEOUT(checkAvailableGroceriesSuccess.count(), 1, 1000);

Note

QCOMPARE (as well as the asynchronous versions QTRY_COMPARE and QTRY_COMPARE_WITH_TIMEOUT) is preferable to QVERIFY whenever you can check for equality, as QtTest will provide you with both the actual and expected values in the test diagnostics. QVERIFY is needed when you are checking for inequality, or other kinds of relationships (such as greater than and less than).

Then I am given the list of available grocery items

After the use case is completed, we expect a couple of outcomes. The first of these is that the data stored in the repository is loaded into the groceryItems entity and made accessible through a list. The entity, or some other application layer (such as a presentation layer), will then be responsible for making the data available to a UI, which will take care of showing it somehow (a print statement, a spoken enumeration, or, in our case, a list view in the UI). To verify this, we can make sure that the count of the items that the groceryItems list delivers corresponds to the number of objects stored in the repository.

Note

Here we are taking for granted that the count of objects in the repository and the count of items in the groceryItems list coincide. The addition of other features, such as a search feature, may require us to revise this assumption.

Thus, we could verify that the first Then step is met with the following check:

QCOMPARE(groceryItems->list().count(), groceryItemsDummy->count());
And (then) the grocery items are ordered by name, ascending

In this last step, we are checking that the grocery items are returned in ascending order by their name. This seemingly simple statement actually tells us a bit more about what we should include in our groceryItems entity:

  • A method that checks whether the grocery items are ordered by name in ascending order
  • A field for each grocery item that defines its name (which could just be a string)
  • A definition of ascending order for the name field

For now, we just need to name the first method. Let's call it isSortedBy:

QVERIFY(groceryItems->isSortedBy("name","ASC"));

In the method implementation, we will be able to leverage algorithms and iterators provided by either Qt or some other library to perform the check efficiently.

Note

The correctness of the isSortedBy method should not be taken for granted. In a complete project, you should add a unit test at the entity level (more on this in Chapter 2) to make sure the method behaves properly. Alternatively, and perhaps even better, the sorting check should be taken out of the entity's API and performed in the acceptance test itself with a utility function.

A huge step for humanity

Congratulations! Our first acceptance test has been written. Here is the entire test:

void Usecases_check_available_groceries::test_one_or_more_grocery_items_available()
{
    // Given there is a list of available grocery items
    auto groceryItems = new entities::GroceryItems(this);
    QVERIFY(groceryItems);
    // And one or more grocery items are actually available
    auto groceryItemsRepoDummy = new repositories::GroceryItemsRepoDummy(groceryItems);
    groceryItems->setRepository(groceryItemsRepoDummy);
    QVERIFY(groceryItemsRepoDummy->count() > 0);
    // When I check available groceries
    auto checkAvailableGroceries = new usecases::CheckAvailableGroceries(groceryItems, this);
    QSignalSpy checkAvailableGroceriesSuccess(checkAvailableGroceries, &usecases::CheckAvailableGroceries::success);
    checkAvailableGroceries->run();
    QTRY_COMPARE_WITH_TIMEOUT(checkAvailableGroceriesSuccess.count(), 1, 1000);
    // Then I am given the list of available grocery items
    QCOMPARE(groceryItems->list().count(), groceryItemsRepoDummy->count());
    // And the grocery items are ordered by name, ascending
    QVERIFY(groceryItems->isSortedBy("name","ASC"));
}

Note

In this test we are not dealing with the destruction of objects created on the heap, because for now this is the only test running, after which the test application quits. In the next chapters we will see a few Qt strategies to deal with this issue.

Sure enough, as we go along, we will come up with better checks for each of the steps, but this test should suffice to give us enough confidence about this first scenario and continue to build the rest of the implementation. Also, when implementing our components, we will need to add a few lines of code to make things work properly.

However, not only does the test still certainly fail, it also does not even compile. Be patient, we'll make it compile (and pass!) in the next chapter.

After all this hard work, you deserve some fun. In short we'll be turning our attention to prototyping the UI. Yay! but first, I'll give you a glimpse about how the same test could be written in QML.

Writing usecase tests in QML

As we previously mentioned, a use case test, such as the one we have just written in C++, could also be written in QML, a declarative language with support for imperative JavaScript expressions.

A short QML primer

Here is what a simple QML document looks like:

import QtQml 2.2

QtObject {
    id: myQmlObject
    readonly property real myNumber: {
        return Math.random() + 1
    }
    property int myNumber2: myNumber + 1
    property var myChildObject: QtObject {}
    signal done(string message)
    onDone: {
        print(message);
        doSomething();
    }
    function doSomething() {
        print("hello again!");
    }
    Component.onCompleted: {
        console.log("Hello World! %1 %2".arg(myNumber).arg(myNumber2))
        done("I'm done!")
    }
}

Yes, it's got curly braces and colons, but it's not JSON. If you run this document, you'll see nothing but a couple of print statements:

qml: Hello World! 1.88728 2

qml: I'm done!
qml: Hello again!

Note

You can run this snippet in Qt Creator by creating a new QML document (New File or Project...> Qt >QML File(Qt Quick 2)), replacing the generated code with the snippet, and running qmlscene (Tools > External > QtQuick > QtQuick 2 Preview (qmlscene)). The same can be achieved from the command line by looking for qmlscene in the Qt distribution's bin folder for your host platform. Alternatively, create a new Qt Quick UI Prototype (New File or Project... > Other Project > Qt Quick UI Prototype), paste the contents into the newly created QML file, save it, and hit the Run button in Qt Creator.

Take a moment to go through the document and recall the documentation for the QtQml module, which, following my recommendations, you should have read already. The object does nothing interesting really, apart from highlighting a few QML-specific constructs that you will encounter again and again:

  • import QtQml 2.2: An import statement which exposes a few QML types (such as QtObject).
  • QtObject {...}: A QML object definition. Every QML document requires one, and only one, root component.
  • id: myQmlObject : An id that identifies the QML object uniquely within the document.
  • readonly property real myNumber: A property declaration. The property evaluates to the result of a JavaScript expression, which is cast to a real. The property is readonly; it cannot be re-assigned.
  • property int myNumber2: myNumber + 1: A property that is bound to the value of another property (myNumber). This means that every time myNumber changes, myNumber2 will also change automatically without the need to use setters and getters. This mechanism is called property binding. It is one of QML's most powerful features (see http://doc.qt.io/qt-5.9/qtqml-syntax-objectattributes.html#property-attributes).
  • property var myChildObject: QtObject {}: We are creating a pointer to another QtObject instance. The child instance will follow the parent-child memory management that we already encountered in C++, with the addition that objects owned by QML to which there are no pointers will be garbage collected at some point.
  • signal done(string message): A signal declaration.
  • onDone:{...}: A signal handler expression. The signal handler (a slot) is created automatically from the preceding signal declaration. As you can notice, an on prefix is added to the signal name in capitals.
  • function doSomething(): A JavaScript member function of the QML object that we call in the onDone signal handler.
  • Component.onCompleted: This is a built-in signal handler that is called as soon as the QML object is complet, useful to perform post-creation initialization operations.
  • console.log, print: Some of the functions accessible in QML's global scope.

This is lots of information, and it is only intended to give you a bit more information about what comes in the next section. Once again, please familiarize yourself with the Qt documentation if any of the preceding points do not make much sense. And, please also read the docs even if you think they all make sense! That said, we'll have a chance to encounter each of these constructs again and again.

Expressing the first acceptance test in QML

Now that you know a bit more about the structure of QML, here is what the same functional test that we wrote in C++ could look like in QML:

import QtTest 1.0

TestCase {
    name: "Usecases_check_available_groceries"

    function test_one_or_more_grocery_items_available() {
        // Given there is a list of available grocery items
        var groceryItems = createTemporaryObject(groceryItemsComponent,this);
        verify(groceryItems);
        // Given one or more grocery items are available
        var groceryItemsRepoDummy = createTemporaryObject(groceryItemsRepoDummyComponent, groceryItems);
        groceryItems.repo = groceryItemsRepoDummy;
        verify(groceryItemsRepoDummy.count > 0);
        // When I check available groceries
        var checkAvailableGroceries = createTemporaryObject(CheckAvailableGroceriesComponent, this);
        checkAvailableGroceries.run();
        checkAvailableGroceriesSuccess.wait();
        tryCompare(checkAvailableGroceriesSuccess.count, 1);
        // Then I am given the list of available grocery items
compare(groceryItems.list.count, groceryItemsDummy.count);
        // And the grocery items are ordered by type, ascending
        verify(groceryItems.isSortedBy("type","ASC"));
    }
}

The QML + JavaScript syntax is a bit different, but you should easily notice that there is almost a one-to-one correspondence between the two APIs. Someone may tell you that C++ is always the language of choice if you are not working on the UI layer. That makes sense when you mainly think about bare performance and, possibly, maintainability in the long term. However, if you also consider a developer's skillset and development time, there may be situations where writing good QML + JS could be a compromise, especially in the prototyping phase. That said, Qt's C++ APIs together with recent C++ language features also make for a very concise way of expression. So, when implementing logic layers, whenever possible, C++ is preferred.

Note that the QML example above is not intended to be run, but only to show you how the QML and C++ APIs compare, as it lacks several object definitions.

 

Building a visual prototype


What we have seen so far is all nice and useful, but probably not exciting — unless you are an application architecture dude like me, that is.

It's likely you have come to Qt because of its rendering capabilities, not so much for its general programming facilities. Yet, I hope that by now you have come to appreciate the non-rendering capabilities of Qt as well.

Now that we have spent a little brain power on laying down our first use case and its main scenario, we can consider a UI that will serve the use case well. Right, serve. The UI is there to serve a purpose, that is, enable a user to carry out some task (check out what's left in the fridge). While we are at it, we should also add the other two usecases that we deemed essential for a minimum viable product:

  • Add a grocery item to the list
  • Remove a grocery item from the list

Let's do it straight away — by now we know the rules of the game:

Feature: Add grocery item
   Scenario: Item can be added
      Given I am given a list of available groceries
      When I add a grocery item with name X
      Then the grocery item with name X is contained in the list
      And the groceries are ordered by name, ascending

Feature: Remove grocery item
   Scenario: Item can be removed
      Given I am given a list of available groceries
      And the grocery item with name X is contained in the list
      When I remove the grocery item with name X
      Then the grocery item with name X is not contained in the list
      And the groceries are ordered by name, ascending

In the previous feature scenarios, we are assuming that our system only supports one item per name (for example, bananas). An extension to this could be to add the quantity for each item, or, alternatively, support more than one item with the same name (in which chase, we would need to add an attribute other than the name that identifies each item uniquely).

We have lazily limited ourselves to two very simple scenarios, and also to the optimistic path (what if something goes wrong while we are adding/removing the item?), but it will suffice for now. Fleshing out these examples is, as usual, left to your good will. A few suggestions will be left as an exercise at the end of this chapter.

So, we have a minimal specification for our little app, where a user can add, remove, and check available grocery items. Let's sketch a simple and clean UI to serve these usecases well.

Deciding upon the UI technology

A past Qt webinar was so entitled: The Curse of Choice: An overview of GUI technologies in Qt (https://www.qt.io/event/the-curse-of-choice-an-overview-of-gui-technologies-in-qt/). In its description, you could find a list like this:

QWidgets, QPainter, QGraphicsView, Qt Quick 1, Qt Quick 2, Qt Quick Controls 1 and 2, QWindow, OpenGL, Vulkan, Direct3D.

And that's not all; you could also find the following mentioned too: Qt3D, Qt Canvas 3D, QtQuick Canvas.

All these elements refer somehow to graphics, so it is therefore legitimate to ask what is available in Qt for creating graphical UI, and what should be used when.

As always, the answer is it depends. While the choice is wide, if the focus is on usecases and context, it is not so hard to choose the right graphics framework from the ones that Qt provides.

So, let's start with our What's in my fridge? application and answer a couple of questions to make the decision easier and motivated.

What kind of visual metaphors should our application use?

A first split is between 2D and 3D UIs. Do we aim for a classic 2D interface or do we want to actually recreate a fridge in 3D and fill it with banana models? If the latter is the case, then Qt 3D is probably a better choice than Qt Canvas 3D. It is newer, it's got almost feature-pare QML and C++ APIs, and it is much more powerful. Also, Qt 3D is soon going to support virtual reality systems, and it has many more features beyond UI. We will explore it in Part II. Qt Canvas 3D, on the other hand, supports a port of the three.js 3D JavaScript framework, and may thus still be a valid option if you already have some code written in that JS library (see http://blog.qt.io/blog/2015/06/05/porting-three-js-code-to-canvas3d/).

If we go for a 2D interface, we may as well go for classic UI controls, as found on desktop, mobile, and embedded, or just come up with something completely different, more similar to a video game UI. Also, in this case there is plenty of choice: both Qt Widgets and Qt Quick Controls 2 provide classic UI controls out of the box, while QPainter, QGraphicsView, and Qt Quick 2 are choices which give more freedom, each one with its own limitations and benefits. For example, because of QML and OpenGL, Qt Quick 2 provides a very powerful animation framework.

Let's say for now that for our app we want a classic 2D UI with standard controls. That's probably what an average user would expect in the first place.

What kind of devices should our application run on?

We could run the fridge application on our desktop, on our mobile phone, or even on an embedded device that we glue to the fridge's door. Depending on the intended deployment device, we will pick one framework over another. Qt Widgets were born in the desktop era, and still serve the desktop context primarily. They provide a rich set of controls and layouts, with a C++ API. However, they are not particularly suited for devices with touch input. In this case, Qt Quick 2 is more suitable, as it was developed with touch devices in mind. It also provides a rich set of controls via the Qt Quick Controls 2 module: a recent module which was designed for devices having limited resources (for example, embedded) in mind. Qt Quick Controls 2 are also well supported on desktop. They do not provide, however, native look and feel on all platforms out of the box, and lack (at the time of writing) some widely used controls like tables and treeviews, although these should be added fairly soon.

Note

There are also third parties providing additional control libraries, both for Qt Quick and Qt Widgets. These are available as open source projects as well as commercial offerings. Among the former, QWT is a set of Qt Widgets for scientific applications (http://qwt.sourceforge.net/); among the latter, the V-Play framework based on Qt Quick 2 provides a rich set of controls and functional plugins particularly suited for mobile platforms (https://v-play.net/apps/). That said, creating your own custom Qt Widgets and Qt Quick components is very doable, but it requires a bit of experience.

Should a non-coding designer implement the UI?

This is also an important consideration before deciding upon one framework. Both Qt Widgets and Qt Quick are supported by dedicated visual editors that ship with Qt Creator: Designer and Qt Quick Designer. These fully fledged visual editors output clean markup files, also known as forms (XML for the Qt Widgets - .ui file extension, QML for the Qt Quick - .ui.qml file extension) without requiring any coding. The same facilities are not yet fully available for other frameworks, such as Qt 3D, although that's in the plans.

Why limit yourself to one?

Lastly, you should consider that Qt provides many integration options for all its graphic frameworks. This means that, in the same application, and even in the same UI, you could potentially mix Qt Quick controls, Qt 3D components, and Qt Widgets controls. We will see an example of this kind later on. Whether this makes sense is again a matter of taking a good look at the usage context and at the available technologies.

Also, if you find a clean way of structuring your application, as I am advocating here, you should be able to swap the UI layer later in the project without suffering too much, just a bit.

Our initial choice

As you may have guessed, for the fridge project we will be using a UI based on Qt Quick Controls 2 and Qt Quick 2. This will give us the option to go from desktop to embedded seamlessly by providing a good user experience on each platform. Also, the current public Qt Quick API is QML only, which will give us a way to get more familiar with the language and its advantages.

Prototyping with Qt Quick Designer

Not too many Qt developers are huge fans of Qt Quick Designer. Indeed, in the past it was usually easier and quicker to write QML code by hand, perhaps with the aid of a live preview tool, because Designer's functionality was limited with respect to coding, and also because stability was not one of Designer's virtues. Recently, however, the Qt Company seems to have invested a good amount of resources in the tool, and the fruits of this overhaul can already be appreciated in current versions of Qt Creator.

Using the Designer represents a good alternative to writing UIs completely in code when non-programmers are allocated this task, or when the project in question does not require the development of custom components.

Creating the UI subproject

We start by adding a new subproject to the part1-whats_in_my_fridge project. To do this, as before, right click on the project and click New Subproject...:

We will start off from a Qt Quick Application - Empty template and call the subprojectgui (graphical UI). Let us leave all other wizard options unaltered. When Qt Creator prompts for a kit, if you have several options, select the one starting with Desktop....

Note

A kit consists of a set of values that define one environment, such as a device, compiler, and the Qt version. For more information about this important Qt Creator concept, see http://doc.qt.io/qtcreator/creator-configuring-projects.html.

If all goes well, you should see a summary page like the following at the end of the wizard:

By clicking Finish, the gui subproject will show up in the project tree.

Next, we want to disable, for the time being, the usecases subproject, so that the fact that it does not compile yet does not get in our way. To achieve this, it's enough to comment with a # the project inclusion line in part1-whats_in_my_fridge.pro:

# part1-whats_in_my_fridge.pro
TEMPLATE = subdirs

SUBDIRS += \
#    usecases \
    gui

The next thing we should do is to add a QML file an its corresponding UI form to file qml.qrc. To do so, right click on qml.qrc in the project tree and then click on Add New... :

The QML component should be called Page1. The corresponding UI file will automatically be called Page1Form.qml:

Once the file creation wizard has completed, in the project tree we want to click on Page1Form.ui.qml. By doing this, Qt Creator will recognize the .ui.qml extension and take us automatically to the Design mode.

By default, the design mode offers a visual representation of the form we are working on. Starting with Qt Creator 4.3, we can get the code representation (Text Editor) of the same form in parallel. To do this, we click on the tiny vertical split icon at the bottom right of the main form area:

By activating the Text Editor, you can understand how the visual representation corresponds to the structure of the QML document.

Laying out the UI components required by the scenarios

Going back to our scenarios, we need:

  1. A way to represent the list of available grocery items
  2. A way to add new grocery items by their name
  3. A way to remove existing grocery items that are no longer in the fridge

We first add Page1 to  main.qml, and make changes, until we get the following:

// gui/main.qml

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3

ApplicationWindow {
    visible: true
    width: 320
    height: 480
    title: "What's in my fridge?"

    Page1 {
        anchors.fill: parent
    }
}

Note

The width and height of 320x340 has been chosen as it is the minimum resolution of some mobile devices still present on the market, and thus serves as a minimal area for which you should make sure the UI is functional, if you decide to also target these smaller screens. Feel free to increase it if you have other requirements.

Check available groceries

We open again Page1Form.ui.qml in the Designer. By browsing the components available by default in Qt Quick Designer (the QML Types tab on the top left), we will find a few that we may think are suitable for representing a list. Yet, if we perform a keyword lookup for a list in the search field at the top of QML Types, we'll only see one result: List View. A quick look at Qt's documentation (http://doc.qt.io/qt-5.9/qml-qtquick-listview.html) will tell us what ListView is useful for, and whether it can be useful to us. It turns out it is. The ListView is capable of showing data dynamically from a model, updating itself every time the model changes. On top of that, ListView provides scroll support for touch interactions, in case the height of our grocery items list exceeds the available vertical screen estate.

It's a deal! If we drag the ListView component into the Form Editor, we will see both the canvas and the Text Editor change at once as follows:

Let us examine the code that has been generated:

ListView {
        id: listView
        x: 118
        y: 63
        width: 110
        height: 160
        delegate: Item {
            x: 5
            width: 80
            height: 40
            Row {
                id: row1
                Rectangle {
                    width: 40
                    height: 40
                    color: colorCode
                }
                Text {
                    text: name
                    anchors.verticalCenter: parent.verticalCenter
                    font.bold: true
                }
                spacing: 10
            }
        }
        model: ListModel {
            ListElement {
                name: "Grey"
                colorCode: "grey"
            }
            ...
        }
    }

A few properties of the ListView QML type should be self-explanatory, while others certainly require a bit of explanation. model is the data model that feeds data into the view. Ultimately, it will be our groceryItems.getList(). For the time being, we can adapt the simple QML model called ListModel to generate some mock data. Each grocery item will be a ListElement. Instead of the name and colorCode fields of the example, we are only interested in a name field (go back to our scenarios and you'll remember why). Thus, in the text editor, we can modify the ListModel as follows:

ListModel {
    ListElement {
        name: "Bananas"
    }
    ListElement {
        name: "Orange Juice"
    }
    ListElement {
        name: "Grapes"
    }
    ListElement {
        name: "Eggs"
    }
}

A bit further down in ListView's definition we find the delegate property. The delegate is the visual representation of each list element (or record) contained in the model. Besides describing the visual appearance of the delegate, the code describes how the attributes of each list element should be displayed. In the default delegate that Designer provides, name shows up as a text label (it is bound to the text property of a Text QML type), while colorCode defines the background color (the color property) of a Rectangle.

Since we just want to display the type of our grocery item, we can change the delegate from the default types to the convenient ItemDelegate control type. In order to have the ItemDelegate available in Designer, we should first import the QtQuick.Controls 2 module that exposes it:

Once we have added the import, we switch to the Text Editor and change the delegate's type and properties as follows:

delegate: ItemDelegate {
            width: parent.width            
            text: modelData.name || model.name
            font.bold: true
        }

Note

the modelData.name || model.name JS expression allows us to use the same delegate with both proper Qt data models and simpler, JS-array like models. For historical reasons, JS models can be accessed through the modelData context property, while Qt models require the use of the model property.

If we now refresh the Form Editor (click on the small counter-clockwise arrow button Reset view at the top right of the editor), we shall see our list view containing the grocery items:

Let us now have the list view occupy all available space. Instead of using absolute width and height values, we can tell the list view to occupy all available space by going to the Layout tab on the right (Properties pane) and selecting Anchors on all four sides to fill ListView's parent (the Item). Now the list fills all of the available space:

If you now push the Run button (green arrow) in Qt Creator, you should be able to run the application and see the list view. By dragging the mouse on to the list view, and then up and down, you'll see the list sliding and coming back. Fancy, huh?

Add grocery item

To be able to add a new grocery item, we need a way to specify its type, and a way to confirm the addition once we have entered the type. There are of course many ways to accomplish this. Let's keep it simple by providing an input field and a submission button. We start by adding aRow component to contain our text field and button, anchor it to the bottom, left, and right of the root Item, by also removing any margin that may have been set by Designer. we set the Row's height to a reasonable amount, such as 64, which is a sufficient height for a toolbar that contains touch controls.

Then, we add two controls, a TextField and a Button as children to the Row, by also setting their placeholderText and text property to enter item name and Add item, respectively, and give a left margin and a spacing of 8 to the Row:

If you are looking for the placeholderText property in the Properties pane, you won't find it, as it is not exposed. You can however just add it to the TextField from the Text Editor, as seen in the preceding screenshot. This is one more reason to inspect a component's description and API thoroughly in the docs before starting to use it.

Note

If you want to quickly access the documentation for a component in Qt Creator, click on the component's type in the Text Editor and press F1.

If you now press the Run button, you will see the row with the text field and button at the bottom of the page. Clicking on either the TextField will bring the focus to it, while clicking on the Button will display a visual effect.

Remove grocery item

Removing a grocery item can also be achieved in several ways: for example, long press on a delegate, swipe, and so on.

We will take a more visual way and simply add an "X" button to each delegate. Because the delegate is part of the ListItem, we will need to make any modifications through the Text Editor, as follows:

delegate: ItemDelegate {
            width: parent.width
            text: modelData.name || model.name
            font.bold: true
            Button {
                width: height
                height: parent.height
                text: "X"
                anchors.right: parent.right
            }
        }

From a visual point of view, we have everything that is needed to cover our first usecases. Congratulations! Of course, as we haven't tied any logic to the button and item presses, nothing will happen. Run the completed UI again and check it out.

 

Taking it further


Here are a few suggestions if you feel confident enough to expand upon the minimal use case scenarios and UI we have outlined in this chapter.

  • The Gherkin feature Check available groceries also contains a scenario for when no grocery items are available (No grocery items available). In such an event, we may want our use case to return a message which indicates this fact. Try and implement the test function for this second scenario.
  • Try and refine Check available groceries by extending its requirements in terms of preconditions and/or expected outcomes. For example, we may want to display the number of available pieces for each grocery item next to the type. If this is the case, we'd also want to be able to increase/decrease the number of available pieces.
  • We have written scenarios for Add grocery item and Remove grocery item, but haven't written any test cases for them. Create a new subproject for each test case and add a test for the main scenario.
  • Come up with a different layout for a list of grocery items. For example, there is a GridView component that supports dynamic models much like ListView does.
  • Add a background to the row that contains the TextField and the Button. You can use a Rectangle for that purpose, as it was done for the list view delegate example.
  • Add a header which displays the name of the application. You could use a ToolBar (which is provided by the Qt Quick Controls 2 module) or  a Rectangle, for example, with a Text or a Label inside.
 

Summary


In this chapter, we showed how careful planning by means of BDD and upfront testing with QtTest can help you shape an application by focusing on its features rather than on the technical details of file I/O and UI.

We also discussed the relative merits and usage contexts for the various UI technologies offered by Qt.

We finally created a prototype for the UI of our application with Qt Quick and Qt Quick Controls 2, by leveraging Qt Creator's Quick Designer.

In the next chapter we will implement the usecases and test them in a complete scenario by creating a simple command line application, and in the following one we will mount the UI on to the application logic and refine it.

About the Author

  • Marco Piccolino

    Marco Piccolino is a consultant, technical trainer and speaker developing Qt apps for businesses and consumers on a daily basis. He is the founder of the QtMob Slack chat, a community of Qt application developers with a focus on mobile, resource sharing and problem solving. Marco's main professional interests include application architecture, test-driven development, speech and language technologies, and everything Qt.

    Browse publications by this author

Latest Reviews

(4 reviews total)
:-) Znakomita pozycja za znakomitą cenę.
Another good Qt5 book with good details in software testing with Qt5, plus some good examples of UI design and techniques to deal with sensor data acquisition. In our current "IoT" scenario, this is a good book for those willing to develop GUI for supervisory control and device monitoring.
Quick, easy with zero issues

Recommended For You

Qt5 C++ GUI Programming Cookbook - Second Edition

Use Qt 5 to design and build functional, appealing, and user-friendly graphical user interfaces (GUIs) for your applications.

By Lee Zhi Eng
Mastering Qt 5 - Second Edition

An In-depth guide updated with the latest version of Qt 5.11 including new features such as Quick Controls and Qt Gamepad

By Guillaume Lazar and 1 more
Hands-On High Performance Programming with Qt 5

Build efficient and fast Qt applications, target performance problems, and discover solutions to refine your code

By Marek Krajewski
Hands-On Mobile and Embedded Development with Qt 5

Explore Qt framework and APIs for building cross-platform applications for mobile devices, embedded systems, and IoT

By Lorn Potter