[Development] On QML and keeping data alive.
Rene Jensen
rene at catatonic.dk
Thu Aug 15 15:07:32 CEST 2013
Hi Qt developers
A little over a year ago I raised a question on this list about
QSharedPointers and QML, and got a lot of debate started, which turned out
to be very illuminating. (
http://lists.qt-project.org/pipermail/development/2012-May/004049.html)
What I did not reveal was the motive behind asking, since it seemed
irrelevant for the discussion. But now I start to feel a huge need to
clarify the deeper problems. While developing an Android app using QML, I
have had the pleasure of experiencing the C++/QML interaction as it was
probably supposed to work all along. I was positively shocked, as it in no
way compared to the desktop disaster project I am still working on.
This email is about how QML fits into a traditional software design process
and what exact role it is playing in a model/view/controller universe.
I find it difficult to explain, so please bear with me as I do my best.
Starting this discussion with the traditional Model/View/Controller
approach:
THE MODEL
===========================
Typically done in C++, we are looking at two scenarios.
1) Small applications that run for short spans of time and typically
exposes a single document in a single screen space, and hence are easily
resource managed.
NB: The data structure is hierarchical and not very large.
2) Large applications that run for one or more days and are true
multidocument/database applications with many windows - one per top level
document. They are not easily resource managed.
NB: The data structure is like a network graph with clusters of
hierarchies and all sorts of interconnections.
Clearly I am talking about the difficulties of a database application. A
database is a gigantic collection of data which cannot be loaded all at
once. Hence resources must be tightly controlled, leading to this
observation:
Solution for (1): The document tree is easily managed using a simple
QObject parent/child ownership tree.
Solution for (2): One solution would be to use shared pointers. A clever
system of QSharedPointer/QWeakPointer must be deviced to make sure cycles
aren't locking the objects loaded etc.
Now we are getting closer. As you may observe, most C++ database layers
(ODB, QDjango) support if not indeed urges to use shared/weak pointers. I
take that as a hint that such a design is typical and well-thought off in
the C++ world. Much akin to references in Java and C#.
The key is that SHARED OWNERSHIP must take effect during the use of the
objects in question.
Example: In a CRM system, let's say we have a Company with Persons being
hired into the company. But of course people can work two places at once. So
CompanyX has in its human stock: PersonAA, PersonBB and PersonCC
CompanyY has in its human stock: PersonAA and PersonGG
View 1 (showing the first company) lays claim to CompanyX and Person AA,BB
and CC
View 2 (showing the second company) lays claim to CompanyY and Person AA
and GG
It is unknown whether view 1 or 2 will be closed first by the user - if at
all.
Now the punch line...
THE CONTROLLER
===========================
While I see is as the job of the model to query the database and serialize
the SQL response into Q_PROPERTIES, I am quite reluctant to wish that
objects in the model domain should store QSharedPointers and hence be
responsible for resources being locked in memory as long as their (shared)
parent keeps them stored.
Instead I see it as the job of the View/Controller to lay claim to resouces
while they are in use. Why? Because the controller ultimately is
responsible for actually using the database resource for as long as the
view exists.
So far I have surprised very few programmers. The MODEL queries and
serializes data and the CONTROLLER does the resource management deciding
when it is time to let go of database resources again.
THE VIEW
===========================
This is inherently a good explanation to why the old widget system lends
itself so formidably to a more evolved MVC architecture: The QWidget-based
view is written in C++, which deals just fine with shared and weak pointers
or whatever other solution we can come up with. QML did not back then, and
possibly still doesn't. So basically a widget-based form can in this
scenario take on both the role of the view and the controller.
Regarding QML, the common perception is that if you want to feed
QSharedPointers into Quick views, you unpack them with the .data()
function, and makes sure that the lifetime of the object in question
outlives the Quick view.
SUMMING UP
===========================
By now I think you get the problem (and sorry for being overly verbose).
QtQuick items makes good views but bad controllers. QWidget based forms are
fairly good for both. The form can store QSharePointers during its lifetime
which are easily accessed by the view code.
Lifetime of an app using widgets:
Form (view/controller): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Object (model): ~~~ ~~~~~~
To be able to do the same thing using qml, we need to embed the view in a
C++ controller: QQuickView (or something derived from or containing that
class), which then can handle the resources. The form holds the resources
and feeds them to the quick view, outliving both the quick view and the C++
models.
Lifetime of an app using qml:
QQuickView (controller): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
QObjects (model): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
QML file (view): ~~~~~~~~~~~~~~~
Hmm, good luck with the ascii graphics :-)
But that still leaves out difficult decisions. Continuing on our example,
let's see how a simple CompanyView.qml could work out, using typical QML
idioms:
COMPANYVIEW.QML:
Item {
property Company company
Column {
Text { text: company.name; .... }
ListView {
model: company.employees <<<<<<<<< PROBLEM: What does this mean?
....
}
}
}
PERSONVIEW.QML:
Text {
property Person person
text: person.name
}
Look at the place where the ListView is bound to the companys employee
list. What exactly is being asked of the model here? The semantics are
unclear. Is QML asking the C++ model to load the employees and then forget
about the references? I.e. can the C++ model trust the controller to keep
and store references itself? Clearly the view is accessing the model
directly, and that leaves out the controller, which should have helped
store the reference in a guaranteed way.
In all fairness, the problem would also exist in a QWidget scenario. But
QML is extra difficult, because MVC usage patterns in my opinion aren't
really clear yet. My current instinct commands that me to make the most
beautiful interface, i.e. one using QQuickListProperty, which is not very
intuitive when it comes to custom types, and doesn't give a lot of room for
dealing with the resource management situation.
Just like last year, I wish for an enriching debate, hoping I have
demonstrated the need to think usage patterns into the API design.
Best regards,
Rene Jensen
P.S.: You can always design your way out of most problems, and in this case
I will probably end up doing just that. But I still wish to hear your take
on the problem, because I imagine that these issues have been solved before.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20130815/64a952e5/attachment.html>
More information about the Development
mailing list