I Want to Write QML

I like writing QML, and now I am going to explain why.

First, let me just introduce what it is. QML a new(-ish) language from the Qt project for building cross-platform user interfaces. Technically, QML is the language, QtQuick is the standard library with the visible components, and there are other official and non-official add-ons and libraries as well. However, but I will just lump them together because their basic advantage is not the set of components, but rather how the code is written.

The important thing about QML is that it is declarative. In an imperative programming language, such as C, if you want to set a value of a variable, you write something like x = 1. What this line does is assign a value of 1 to variable x. If you have multiple variables, and your code reads

  int width = 50;
  int height = width;
  width = 100;

then the value of height at the end will be 50, not 100.

In a declarative language, on the other hand, you don’t only assign a value to, you declare that is is equal to some expression, and it will always stay equal to that expression even if if inputs change. For example, we want a button that should always be square, but its size may change due to user input. We can write it like this:

  Button {
    height: 50
    width: height

    onClicked: height = 100
  }

The first two lines of the Button declaration specify the values of two properties: height and width. height is set to 50, as we don’t expect the value of 50 to change while the program is running, there is no difference between assignment and binding here. However, the second line creates a true binding, whenever the value of width changes, the value of height will be updated. Indeed, with this example, the button remains square after clicking, just grows a little.

This may not sound like much, but real programs contain more than just one button. The true strength of the declarative approach comes when connecting multiple components.

Connecting to an event

Let’s say you have a button, and you want an action to trigger when the user presses a button. How much code does that take? If you’re using Java, you write something like

button.setOnClickListener(new OnClickListener() {
  public void onClick(View v)
  {
    // do your thing here
  }
});

Using modern C++ with Qt, it’s rather similar

connect(button, &QPushButton::clicked, [=] {
  // do your thing here
});

If you’re stuck on something later than C++11 and/or Qt4, you have to replace your lambda with an actual function. It takes a little more code, but it’s annoying especially because you no longer have all the code relating to this button in a single place. Still, as long as you don’t have a huge number of buttons, it’s not so bad.

connect(button, SIGNAL(clicked()), this, SLOT(onSomeButtonClicked());
...
void YourClass::onSomeButtonClicked()
{
  // do your thing here
}

In QML, it’s not very different

Button {
  onClicked: {
    // do your thing here
  }
}

It looks about the same as it does in any language with lambda functions (or Java’s Anonymous Inner Classes, which pretty much do the same thing). So far, we are still not impressed.

Binding to a value

Things change considerably when you want to bind one value to another. For example, while the user is typing into a text input, you want to display the edited text somewhere else. The solution is simple: You connect to the event of the input text changing, and in the handler you change the text of the label.

Something like this

connect(textInput, &QTextEdit::textChanged, textLabel, &QLabel::setText);

It only takes a single line of code, either with or without the c++11 syntax, but that is because the signal-slot mechanism is a very important part of Qt. Still, we missed one small thing: initializing the value at the start. What if the text input contains some text before we make the connection? So the example becomes

connect(textInput, &QTextEdit::textChanged, textLabel, &QLabel::setText);
textLabel->setText(textInput->text());

which is starting to get complicated, especially if you don’t know whether to connect first or the set the value first. Even in the simplest case, this is blatantly breaking the DRY principle. If you decide to change the text of textLabel, such as prefixing or suffixing a quantity with a unit, you have to do it in two places to keep consistency.

On the other hand, QML is as simple as it gets

TextEdit {
   id: textEdit
   text: "Some initial text"
}
Label {
   text: textEdit.text
}

The single line of binding, text: textEdit.text, takes care of both: it sets the inital values, and it adds a connection that will make sure that any change to the TextEdit will reflect in the Label. Since the connection is made in only a single line, it also strictly adheres to the DRY principle.

Things get even better if you want to make changes to the text before displaying it. Remove all whitespace? Simple, just write

text: textEdit.text.replace(" ", "")

but that’s not hard to do in other languages either. What is hard is combining data from multiple sources. Let’s say you want to show a summary of multiple inputs fields. In QML, you write

Label {
  text: "Hello " + nameInput.text + ", you are sending " + numberSpinBox.value + " apples to " + recipientInput.text
}

or, using the more idiomatic Qt syntax

Label {
  text: qsTr("Hello %1, you are sending %2 apples to %3").arg(nameInput.text).arg(numberSpinBox.value).arg(recipientInput.text)
}

Using a ‘serious’ language, this would require connecting to three separate signals, as well as setting the intial value. Then, everytime you change the label text, you only change one line and all connections happen automatically behind the scenes.

Extending QML

Of course, QML is an extremely limited language. The components provided by Qt are the basic UI elements, but real programs often need to interact with things outside of the UI, such as the filesystem, network, as well as other more exotic components. These interactions can be written in JavaScript, which is fully embdedded into QML. However, JavaScript itself is limited in how it can interact with the system, in addition to having all the disadvantages of an interpreted programming language.

Fortunately, extensions to QML can be written in other languages. Behing the scenes, the magic of self-updating values is implemented with QObject and its property system, which is at the core of the Qt libraries. It is very easy to expose custom QObject types to the QML engine, where they can be used and interacted with. As long as the Qt property system is used correctly, the magic never runs out. Additionally, it has further support for the Model-View architecture, so you can wrap your data in an QAbstractItemModel and present that to the QML view. Even better, bindings for Qt exist in many languages, some of which are listed here. I typically swear by C++, but I have written a project where I exposed some Python functionality to a QML program, and it went as smoothly as expected. If you want to do something new and connect it to QML, just wrap it in a QObject with properties or a QAbstractItemModel subclass, and you can use it freely from QML.

Conclusion

To conclude, I really like writing QML, mostly because its declarative nature results in much shorted code. I was also to finish by listing all the reason why I don’t use it, but I figured that none of them are very good. However, I actually do use it quite often, both for commercial projects and my own hobby programming. So, the proper summary that I like writing in QML, and I actually write it, so I really can’t complain.

comments powered by Disqus