Different User Interfaces - Same Logic

Written by Johan Thelin, Thelins Teknikkonsult

Ever since we learned to program our first user interface, we have known that keeping the user interface code and the business logic separated is a key to a good design. Ever so often, we fail here. Code grows old, it rots, everything melts together, nothing is separated.

This was true while the user interface was built using the same tools and languages as the business logic. Qt developers has had many choices. The most popular over the years seems to have been C++ and Python. Both are general purpose languages.

Recently, there has been a shift in the user interface tools department. In the Qt space, Qt Quick has entered the scene, and in the big world HTML 5 is expanding the possibilities of a web-based user interface. Of course, Qt supports both these technologies - and the good old general purpose language approach.

The big change in all this is that you implement your business logic in your general purpose language. This forces you to separate it from the user interface - making your business logic independent of the user interface technology that you choose.

When using Qt as the base of your application, the beauty is that the way that you interface your logic from your user interface is the same regardless of technology. The QObject class serves as the natural bridge. It provides introspection in the form of signals, slots, invokable methods, properties and more. All this helps binding them to HTML 5's JavaScript and Qt Quick's QML. As for those of you who stick to C++-based user interfaces, a QObject is just like any other object.

Qt Quick

A Qt Quick user interface is built from QML. All QML code is executed in a context. Each context is represented by a QDeclarativeContext object. For QDeclarativeEngine, there is a root context that contains globally available objects and values.

To expose a QObject to a piece of QML code, the object is set as the value of a context property, as shown in the following snippet.

  1. QObject *obj = new MyLogic();
  2. QDeclarativeEngine *engine = ...;
  3. engine->rootContext()->setContextProperty("logic", obj);

 

This exposes the object to the QML code, where it can be accessed using the name logic.

  1. MouseArea {
  2.     ...
  3.     onClicked: { logic.actOnClick(); }
  4. }
  5. </div>
  6.  
  7. <p> </p>
  8.  
  9. <p>The nice thing about exposing context properties to QML, is that they mix well with the binding nature of QML. This means that if you set the context property <tt>sceneWidth</tt> to 400 and later change it to 500 (or any other value) - all expressions in QML depending on the <tt>sceneWidth</tt> will automatically be updated.</p>
  10.  
  11. <p>For larger applications, the number of properties exposed to the declarative context can grow large. Relying on <tt>setContextProperty</tt> in that scenario quickly leads to a situation where you need to spend time to make sure that all properties are updated at the right time, and that no property update is forgotten. You will also get a code base where calls to <tt>setContextProperty</tt> are sprinkled all over the place. In this situation a context object is a better approach.</p>
  12.  
  13. <p>A context object is a <tt>QObject</tt> exposing a set of properties. Each property of the object are exposed as context properties to the QML code. This means that you can gather all your properties exposed to QML in one place.</p>
  14.  
  15. <p>For a property to be useful to QML, it must provide at least a <tt>READ</tt> method and a <tt>NOTIFY</tt> signal. For instance, exposing the <tt>sceneWidth</tt> value from the previous example would look something like this.</p>
  16.  
  17. <pre class="brush:cpp;">
  18. class MyProps : public QObject
  19. {
  20.     Q_OBJECT
  21.     Q_PROPERTY(int sceneWidth READ sceneWidth NOTIFY sceneWidthChanged)
  22.     ...
  23. };
  24.  
  25. ...
  26.  
  27. MyProps *myProps = new MyProps();
  28. QDeclarativeEngine *engine = ...;
  29. engine->rootContext()->setPropertyObject(myProps);

 

Having exposed a QObject through a declarative context, its invokable methods become available. Invokable methods are slots, but also methods marked Q_INVOKABLE.

  1. class MyLogic : public QObject
  2. {
  3.     Q_OBJECT
  4.  
  5. public:
  6.     Q_INVOKABLE setValue(int);
  7.     ...
  8. };

 

This method can now be called from QML.

  1. onSomething : { logic.setValue(42); }

 

It is also possible to invoke QML methods from C++, emit signals from QML to C++ and, if basing your C++ class on the QDeclarativeItem class, emit signals from C++ to QML. All this makes it possible to build a convenient, event-driven, interface between your Qt Quick user interface and C++ business logic.

HTML 5

In some situations Qt Quick competes with HTML 5. Using HTML 5 means that you can gain code re-usability if you have a user interface that you want to deploy both over standard web browsers as well as a customized application.

Using the QtWebKit module, it is fully possible to build a user interface around a web page, i.e. HTML 5 code. The Qt WebKit integration includes not only the HTML renderer and a JavaScript engine, but also a bridge enabling the integration of QObjects into JavaScript.

When rendering web contents, each web page is represented by a QWebPage object. Each page is then represented by one or more frames, QWebFrame. For each frame, a JavaScript context exists. This context is cleared when new URLs are loaded. This results in the emission of the javaScriptWindowObjectCleared signal. When it is emitted, objects can be added to the JavaScript context using the addToJavaScriptWindowObject method as shown below. Also notice that the frame (or frames) of a page is accessed through the mainFrame.

  1. QObject *obj = new MyLogic();
  2. QWebFrame *frame = webPage->mainFrame();
  3. frame->addToJavaScriptWindowObject("logic", obj);

 

There is no way to expose context object in the same way as when integrating C++ and Qt Quick. Instead each object must be added as a separate object to the context.

It is possible to access and modify properties of QObject from JavaScript. You can also call methods from C++ to JavaScript and the other way around, as well as emit signals and connect to signals from both sides.

Compared to the Qt Quick integration, you will face some differences. For instance, property binding is not an inherit part of HTML 5, so instead you actively have to listen for signals and react accordingly. Also, when emitting signals from JavaScript, the signals must be defined by a C++ class. It is not possible to define new signals in JavaScript.

Classic C++

I write C++ in many places in this text, but all really applies to any general purpose language such as Python, Ruby or any other language of your choice with Qt bindings.

I will not try to instruct you on how to build a user interface using C++ and Qt. Instead, let's look at how you can ensure that the user interface and business logic are kept separate.

When relying on Qt Quick or HTML 5, you are forced to define an interface between the business logic and the user interface. When using C++ to build both the logic and the user interface, you must place these restrictions on yourself.

The key here is that any code affecting only the user interface is a part of the user interface, while any code affecting the application state (e.g. the contents of a document, etc) is a part of the business logic.

The business logic needs to be kept in a separate set of classes. To really force yourself to achieve this, a document object for each document window is a good idea. For other types of applications, the context object from a Qt Quick integration can be re-used.

When this is in place, you will have a set of user interface classes. These will probably be more or less graphical or tied to user interaction (e.g. QAction) and a set of logic objects. Now you must ensure that all non-trivial work with the logic is handled in the logic. This means that it is ok to clear and initialize the logic using separate clear and initialize calls from the user interface code. It is not ok to do the same operation by calling clear and then a series of operations to initialize the logic (e.g. setPen, setBrush, setPageProperties, addBoilerplateText, and so on).

As with all these things, there are no clear rules when too much code is in the user interface. It all comes down to a sense of balance. A rule of thumb is that if you find code duplication across user interface implementations, you should probably put that code in the logic layer. But there are no strict criteria.

The Big Picture

Different users expect different user interfaces. In large, multi-user systems, these interfaces may not only differ in looks and capabilities, but also in the way that they are implemented.

Qt supports classic widget based user interfaces, as well as modern device user interfaces through Qt Quick. In addition to these two Qt specific technologies, HTML 5 is also supported through the Qt WebKit integration.

By splitting your application into separate, re-usable parts, you can use the same logic code to create user experiences for all users - all using Qt.

When Johan is not writing about Qt or developing software for MeeGo, he can often be found in the green house watching over his tomatoe and cucumber plants.

8 comments

August 12, 2011

Picture of losaciertos losaciertos


Lab Rat

This article is what I need now!
Thank you very much!

August 13, 2011

Picture of jamestsai jamestsai


Lab Rat

I love this article very much. Thanks.
By the way, it is possible to export C++ class definitions into QML and HTML5 context, instead of class objects?

September 1, 2011

Picture of e8johan e8johan


Lab Rat

@jamestsai you can export C++ class definitions into QML (see writing C++ components).

September 15, 2011

Picture of wiecko wiecko


Lab Rat

“engine->rootContext()->setPropertyObject(myProps);”
should be:
“engine->rootContext()->setContextObject(myProps);”

September 15, 2011

Picture of e8johan e8johan


Lab Rat

@wiecko thanks, you are right

September 23, 2011

Picture of jzellner jzellner


Lab Rat

e8johan:

if you want to just make the class accessable, use:

  1. qmlRegisterType<QtObjectDerivedClass>("QtObjectDerivedClass", 1, 0, "QtObjectDerivedClass");

in qml you can then create instances from qml side:

  1. import QtObjectDerivedClass 1.0
  2.  
  3. QtObjectDerivedClass {
  4.     id: myObjectInstance
  5. }

October 29, 2012

Picture of David Villalobos David Villalobos


Robot Herder

Thanks.

January 30, 2013

Picture of giugiu giugiu


Lab Rat

Hi Johan, I have a lot of experience in programming:

– C++ since about 1992, I bought the first Borland cpp compiler – java since a lot of years as well – some years also using QT and QtQuick + Necessitas but I have very little experience on HTML. Since I would need to write a WEB application running under any HTML 5 browser, how Qt could help me on that? Is there a way to write a WEB application using mostly c++/Qt/QtQuick and avoid as much as possible HTML?

Write a comment

Sorry, you must be logged in to post a comment.