[Interest] Structuring of QML app as set of interlinked screens for maximum code reuse

Elvis Stansvik elvstone at gmail.com
Wed Mar 30 16:52:32 CEST 2016


2016-03-30 16:16 GMT+02:00 Martin Leutelt <martin.leutelt at basyskom.com>:
> From: Elvis Stansvik <elvstone at gmail.com>
> To: Martin Leutelt <martin.leutelt at basyskom.com>
> Cc: "interest at qt-project.org Interest" <interest at qt-project.org>
> Sent: 3/30/2016 3:17 PM
> Subject: Re: [Interest] Structuring of QML app as set of interlinked screens
> for maximum code reuse
>
> 2016-03-22 22:38 GMT+01:00 Martin Leutelt <martin.leutelt at basyskom.com>:
>> Von: Elvis Stansvik <elvstone at gmail.com>
>> An: "interest at qt-project.org Interest" <interest at qt-project.org>
>> Gesendet: 22.03.2016 20:19
>> Betreff: Re: [Interest] Structuring of QML app as set of interlinked
>> screens
>> for maximum code reuse
>>
>> 2016-03-22 20:13 GMT+01:00 Elvis Stansvik <elvstone at gmail.com>:
>>> Hi all,
>>>
>>> I'm working on a fullscreen Qt Quick/QML app (for machine control)
>>> which will consist of a set of interlinked screens, with key presses
>>> as the only interaction method.
>>>
>>> Each screen will have roughly the following QML structure:
>>>
>>> Rectangle {
>>>
>>>     NavBar {
>>>         id: topBar
>>>         ...
>>>         controls for navigation and information stuff,
>>>         different depending on which screen
>>>         ...
>>>     }
>>>
>>>     Rectangle {
>>>         id: topSeparator
>>>         ...
>>>         aesthetic divider rectangle between top nav bar and content.
>>>         ...
>>>     }
>>>
>>>     Rectangle {
>>>         id: content
>>>         ...
>>>         main content here, different depending on which screen.
>>>         ...
>>>     }
>>>
>>>     Rectangle {
>>>         id: bottomSeparator
>>>         ...
>>>         aesthetic divider rectangle between top nav bar and content.
>>>         ...
>>>     }
>>>
>>>     NavBar {
>>>         id: bottomBar
>>>         ...
>>>         controls for navigation and information stuff,
>>>         different depending on which screen
>>>         ...
>>>     }
>>> }
>>
>> To clarify, think of NavBar as just another Rectangle in the example
>> above. It's just a custom item with some common visual properties.
>>
>> Elvis
>>
>>>
>>> And I'm now trying to find a good structure that will minimize
>>> repetition of QML code.
>>
>>
>> Since you already know that every screen will have two NavBars and
>> separators
>>
>> why don't you use your current QML structure as a skeleton for the whole
>> application?
>>
>> The content area could then be a thing that loads the 'pages' of your
>> application. Take a
>>
>> look at elements like StackView, TabView and ListView (in combination with
>> Loader as a delegate) which
>>
>> already provide a mechanism to navigate between (dynamic) content...
>
> I've now done some experimentation with StackView and ListView.
>
> I used a structure like this (only showing the gist of it) for main.qml:
>
> Window {
>    property WelcomePage welcomePage: WelcomePage {}
>    property SettingsPage settingsPage: SettingsPage {}
>    ....
>
>    ListView {
>        id: topBar
>        model: ListModel {}
>        orientation: ListView.Horizontal
>        ...
>    }
>
>    StackView {
>        initialPage: welcomePage
>        ...
>    }
>
>    ListView {
>        id: bottomBar
>        model: ListModel {}
>        orientation: ListView.Horizontal
>        ...
>    }
> }
>
> Each of the pages (WelcomePage, ...) has a Page as root item, where
> Page is (for now) simply:
>
> import QtQuick 2.4
>
> FocusScope {
>    property ListModel topBarModel: ListModel {}
>    property ListModel bottomBarModel: ListModel {}
> }
>
> For example, WelcomePage.qml is roughly:
>
> Page {
>    bottomBarModel: ListModel {
>        ListElement {
>            name: "Power Off"
>            target: "powerOff"
>        }
>        ListElement {
>            name: "Begin Scan"
>            target: "beginScan"
>        }
>        ListElement {
>            name: "Settings"
>            target: "settings"
>        }
>    }
>
>    Text {
>        text: "Welcome!"
>    }
> }
>
> I then handle the currentItemChanged signal in the central StackView using:
>
>        onCurrentItemChanged: {
>            if (currentItem) {
>                topBar.model = currentItem.topBarModel
>                bottomBar.model = currentItem.bottomBarModel
>            }
>        }
>
>
> You can also use the Binding object and do something like:
>
> Binding {
>    target: topBar
>    property: "model"
>    value: stackView.currentItem.topBarModel
>    when: stackView.currentItem
> }
>
> That's a bit more declarative but your version works just as well.

Nice. It's good to train my brain in some declarative thinking :)

>
>
>
> That is, by setting the model for the top and bottom bar to the ones
> provided by the current page.
>
> This sort of works, but the problems I have right now are:
>
> * ListView seems like it's not such a good fit for my use case. In my
> case, I always want either one, two or three buttons in the top/bottom
> bars. Each button should take up one third of the horizontal screen
> space. If it's one button, it should sometimes be to the left,
> sometimes in the middle and sometimes on the right. If it's two
> buttons, they should always be to the left and right. Given this, do
> you think I should make a more static arrangement, where each page
> provides a topLeftItem, topItem, topRightItem, bottomLeftItem,
> bottomItem and a bottomRightItem? If so, how can I insert these items,
> provided as a properties of the current page, into whatever layout
> element I use for the bars (Row? RowLayout?).
>
>
> Sounds like ListView is not the right choice here. Try a RowLayout in
> combination with
> Repeater that uses the models to instantiate an appropriate delegate. Maybe
> you have
> to experiment with spacer elements in the layout to get the positioning
> right.

Ah, Repeater was the missing link here I couldn't see. Thanks.

And yes, I think I'll have to use some kind of "empty" item in the
model, which will get special treatment by the delegate..

>
>
>
> * Focus handling: Since navigation is by Left/Right/Enter only, I need
> to be able to move focus through all the navigational items in the top
> bar, and then across to the current page and through any focusable
> items there, and finally across to the bottom bar (and perhaps cycle
> back again to the top bar, haven't decided yet). How can I handle the
> "jump" across from navigation bars to the current page?
>
>
> Take a look at the KeyNavigation item which allows specifying the item which
> should
> take focus when the arrow keys are pressed.

Right, I've also been reading about FocusScopes, and I think (but I'm
not sure) that the right thing to do is to wrap the top bar, the
content and the bottom bar each in its own FocusScope, and then a
FocusScope around it all. I could then statically request focus for
each item within these three scopes which should receive focus when
the enclosing scope receives focus, and use KeyNavigation for the
items within each scope.

It's the "jumping across" focus scopes that I'm a little worried
about. I've found these slides from KDAB from QTDD13:

    https://www.kdab.com/wp-content/uploads/stories/slides/DD13/qtdd13_practical_qml.pdf

On slide 9-18, a quite complicated solution to keyboard navigation
across FocusScopes is described, and also how to handle shortcomings
in ListViews regarding key navigation (I'm sure I'll use ListViews in
the main content on a few of my pages).

I'm not sure how many of the problems described there are still
present, but it's a little disheartening if you really have to build
your own elaborate "cursor" system on top of the existing mechanism,
like described in the slides :/

>
>
>
> * Like I mentioned, on some pages, the top and bottom bar should
> contain informational items instead of navigational items on some of
> the spots. In fact, most of the time, the top-right position should
> contain a fixed informational item. These items should not be
> focusable and are quite distinct from the navigational buttons. All
> they have in common is that they occupy the same spot on the screen
> (though obviously not at the same time). How would I best handle this?
> Looking at delegates, I can't quite see a way to make something
> appear/behave so completely different without making quite a
> convoluted sort of "chameleon" delegate. I saw that there's an
> ObjectModel that could have been handy, but it's Qt 5.6+ only (while
> our product will use Qt 5.5.1 since it's based on current Ubuntu).
>
>
> Using the RowLayout allows you to place the informational elements at the
> positions
> where you need them to be, although it might require some tweaking when
> using Repeater
> to create the other elements like buttons.

Yea, I'm afraid the delegate I use for the Repeater will be quite complicated.

I'll see which way I'll go, perhaps considering the small number of
pages and the fact that my navbars won't really have homogenous
content all the time, I should just live with some code repetition and
make each of my page more "standalone".

Thanks a lot for the input!

Elvis

>
>
>
> Much grateful for any tips regarding this, and still looking for a
> good example of a well structured page based application that is not
> making use of the various mobile QML frameworks (Silica, BlackBerry
> et.c.) (since those naturally come with a certain look).
>
> Regards,
> Elvis
>
>>
>>
>>>
>>> I understand that QMLs main model for code reuse is composition, but
>>> that it also has a form of "inheritance": by defining a new Item in a
>>> separate file using another Item as the top level item and redefining
>>> some of its properties, I'm sort of inheriting that item.
>>>
>>> I could save the above general structure in a Screen.qml, and in
>>> FooScreen.qml, BarScreen et.c. do:
>>>
>>> Screen {
>>>     ...
>>>     override some properties
>>>     ...
>>> }
>>>
>>> But this will only allow me to redefine properties, and add new child
>>> items. How would I then be able to define both which content goes in
>>> the main area (the content Rectangle in the "base" item) and in the
>>> two navigation bars (topBar and bottomBar Rectangles)?
>>>
>>
>>
>> The content of your NavBars could be defined by models (maybe either
>> ListModel or
>>
>> something more advanced), at least that's what I would suggest. Either
>> each
>> 'page' of
>>
>> your application defines a model for both the upper and the lower NavBar
>> and
>> these
>>
>> models are then used to dynamically create the appropriate content OR your
>>
>> application switches the content of the NavBars based on whatever state
>> the
>> application
>>
>> is currently in.
>>
>>
>>> It seems QML is not really meant to be used this way, and I'd have to
>>> essentially redefine these things in each of my screens, even if
>>> they'll all have the same general structure? There's no "template"
>>> mechanism so to speak?
>>>
>>
>>
>> I think your example fits QML rather fine. Your application seems to be
>> the
>> typical
>>
>> page-based mobile application, only limited to key interaction instead of
>> touch/mouse.
>>
>> The initial conception of the architecture might require a bit of thinking
>> but as soon
>>
>> as you have that figured out the actual content of the application can be
>> added very easily.
>>
>>
>>> I'm very thankful for any tips from people more experienced with Quick /
>>> QML.
>>>
>>> And if you know of a well designed full screen QML app modeled as a
>>> set of interlinked screens with keyboard navigation, I'm idle ears.
>>>
>>
>>
>> You might find several applications using the page-based approach for
>> basically every
>>
>> mobile platform out there. For more code take a look at the examples that
>> qt
>> already provides,
>>
>> you'll be able to find bits and pieces that you can easily adapt to fit
>> your
>> needs (for example the
>>
>> qt quick controls gallery).
>>
>>
>>> Cheers,
>>> Elvis
>> _______________________________________________
>> Interest mailing list
>> Interest at qt-project.org
>> http://lists.qt-project.org/mailman/listinfo/interest



More information about the Interest mailing list