Best Practices in Qt Quick/QML - Part IV

54
Qt Quick Best Practices Part 4 Justin Noel Senior Consulting Engineer ICS, Inc.

Transcript of Best Practices in Qt Quick/QML - Part IV

Page 1: Best Practices in Qt Quick/QML - Part IV

Qt Quick Best PracticesPart 4

Justin NoelSenior Consulting Engineer

ICS, Inc.

Page 2: Best Practices in Qt Quick/QML - Part IV

Agenda• QML Data Models• Keyboard Navigation and Focus• Performance Tips

Page 3: Best Practices in Qt Quick/QML - Part IV

Data Models

Page 4: Best Practices in Qt Quick/QML - Part IV

Model – View – Delegate Pattern• Views in QML are Model-View-Delegate• Model is an interface to data• View manages item geometries• Delegate implements item UI

• Drawing graphics• Editing data

Page 5: Best Practices in Qt Quick/QML - Part IV

Models in QML• All models are lists in QML

• No tables• Can be implemented using roles

• No trees• Can be implemented using QSortFilterProxyModel

Page 6: Best Practices in Qt Quick/QML - Part IV

Model Roles• Roles are like a “3rd Dimension” to cells

• Can be use to apply extra attributes• Visible and non-visible

• These roles in basic QML are used to make complex cells• Can be used to emulate a table

Page 7: Best Practices in Qt Quick/QML - Part IV

Model Roles• Consider this ContactsListModel

• One item in the list can be very complex

Name RolePhone Number Role

Address Role

Image RoleJustin Noel54 B Middlesex TpkeBedford, MA(617 ) 621 - 0060

Page 8: Best Practices in Qt Quick/QML - Part IV

Model Types in QML• QML ListModel Item• QML list<> property• JavaScript JSON• QQmlListProperty<Type>• QList<QObject*>• QAbstractItemModel*

Page 9: Best Practices in Qt Quick/QML - Part IV

QML List Model• ListModel is a list of ListElement Items

• ListElement is a list of Key/Value pairs• Key names are arbitrary

• Use whatever is convenientListView {

model: contactModel}

ListModel {id: contactModelListElement { name: “Justin Noel”; phoneNumber: “(617) 621-0060” }ListElement { name: “John Doe”; phoneNumber: “(555) 555-5555” }

}

Page 10: Best Practices in Qt Quick/QML - Part IV

Delegates• Roles appear as attached properties in a

DelegateListView {

model: contactModeldelegate: Rectangle {

Column {Text { text: name }Text { text: phoneNumber }

}}

ListModel {id: contactModelListElement { name: “Justin Noel”; phoneNumber: “(617) 621-0060” }ListElement { name: “John Doe”; phoneNumber: “(555) 555-5555” }

}

Page 11: Best Practices in Qt Quick/QML - Part IV

QML Specialty Models• XmlListModel

• Create a model from XML• Using XPath and XQuery statements

• FolderListModel• Lists a directory on the disk• Not a tree

Page 12: Best Practices in Qt Quick/QML - Part IV

QML List Property Model//ContactList.qmlItem {

property list<Contact> contactModel: undefined

ListView {model: contactModeldelegate: Rectangle {

Column {Text { text: name }Text { text: phoneNumer }

}}

}

//Main.qmlContactList {contactModel: [

Contact{ name: “Justin Noel”; phoneNumber: “(617) 621-0060” }, Contact{ name:” John Doe”; phoneNumber: “(555) 555-5555” }

]}

Page 13: Best Practices in Qt Quick/QML - Part IV

JSON ModelItem {

property var json: [{ name:”Justin Noel” phoneNumber:”(617) 621-0060” },{ name:” John Doe” phoneNumber “(555) 555-5555” }

]

ListView {model: json

}

Page 14: Best Practices in Qt Quick/QML - Part IV

QList<QObject*> Modelclass Alarm : public QObject{

Q_OBJECTQ_PROPERTY(Severity severity...)Q_PROPERTY(QString description...)[...]

};QML_DECLARE_METATYPE(Alarm*);

class CoffeeMaker : public QObject{Q_OBJECTQ_PROPERTY(QList<Alarm*> alarms READ alarms NOTIFY alarmsChanged)

public:QList<Alarm*> alarms() const{

return m_alarms;}

};

Page 15: Best Practices in Qt Quick/QML - Part IV

QList<QObject*> Modelimport MrCoffee 1.0

Rectangle {

CoffeeMaker {id: maker

}

ListView {anchors.fill: parentmodel: maker.alarms

}}

Page 16: Best Practices in Qt Quick/QML - Part IV

QAbstractItemModel• Data model interface from Qt Interview

Framework• Originally designed for QWidgets

• QListView, QTableView, QTreeView

• QAbstractItemModel is a tree interface w/ roles• Remember: QML Doesn’t support Tables or

Trees• Makes the interface a little confusing for those not

familiar with the QWidget views

Page 17: Best Practices in Qt Quick/QML - Part IV

QAbstractListModel• QAbstractListModel is a specialized QAIM

• Implements some of the tree interface• Makes it easier to implement a list

• Data models should wrap data rather than store data

• Simple interface

Page 18: Best Practices in Qt Quick/QML - Part IV

Alarm Model Implementationclass AlarmModel : public QAbstractListModel{Q_OBJECT

public:enum Roles { SeverityRole = Qt::UserRole,

DescriptionRole };

AlarmModel(DataModel& data);

QHash<int, QByteArray> roleNames() const;int rowCount(const QModelIndex& parent = QModelIndex()) const;QVariant data(const QModelIndex& index, int role) const;

private slots:void handleAlarmAppened();void handleAlarmRemoved(int alarm);

private:DataModel& m_data;

};

Page 19: Best Practices in Qt Quick/QML - Part IV

Alarm Model ImplementationAlarmModel::AlarmModel(DataModel& data) :

m_data(data){connect(&data, SINGAL(alarmsAppened()),

this, SLOT(handleAlarmAppened()));

connect(&data, SINGAL(alarmsRemoved(int)),this, SLOT(handleAlarmRemoved(int)));

}

QHash<int, QByteArray> AlarmModel::roleNames() const{static QHash<int, QByteArray> roles;if(roles.isEmpty) {roles.insert(SeverityRole, “severity”);roles.insert(DescriptionRole, “description”);

}return roles;

}

Page 20: Best Practices in Qt Quick/QML - Part IV

Alarm Model Implementationint AlarmModel::rowCount(const QModelIndex& parent) const{if(!parent.isValid())return m_data.alarms().size();

elsereturn 0;

}

QVariant AlarmModel::data(const QModelIndex& index, int role) const{if(!index.isValid() || index.column() != 0)return QVariant();

else if(role == SeverityRole)return m_data.alarms()[index.row()].severity();

else if(role == DescriptionRole)return m_data.alarms()[index.row()].description();

}

Page 21: Best Practices in Qt Quick/QML - Part IV

Alarm Model Implementationvoid AlarmModel::handleAlarmAppened(){

beginInsertRows(QModelIndex(), rowCount(), rowCount());endInsertRows();

}

void AlarmModel::handleAlarmRemoved(int alarm){beginRemoveRows(QModelIndex(), alarm, alarm);endRemoveRows();

}

Page 22: Best Practices in Qt Quick/QML - Part IV

Which Model Is Right For Me?• Use Case! Use Case! Use Case!

• Web Services based app• Use JSON or XmlListModel

• C++ based app• Use QAbstractItemModel or QList<QObject*>

• Composite QML items like BarChart• Consists of N Bar items• property list<Type>

Page 23: Best Practices in Qt Quick/QML - Part IV

Keyboard Handling

Page 24: Best Practices in Qt Quick/QML - Part IV

Focus Order• Tab order works like a linked list• KeyNavigation is an attached property

• Executed only when this Item has focusTextInput {

id: firstFieldfocus: trueKeyNavigation.tab: secondField

}

TextInput {id: secondFieldfocus: trueKeyNavigation.backTab: firstField

}

Page 25: Best Practices in Qt Quick/QML - Part IV

KeyNavigation• Navigation Possibilities

• Tab – Backtab• Right - Left• Down - Up• Qt will automatically reverse a singly linked tab

order• If you only specify (Tab, Right, Down)• Qt will automatically do (Backtab, Left, Up)

Page 26: Best Practices in Qt Quick/QML - Part IV

Focus Property• Only one item in a qml file can have focus =

true• Many QML files can be composed together

• This results in more than one item with focus = true• This is actual a good thing as focus will be stored

Item { //Main.qmlOptionsBox {

model: firstOptions}OptionsBox {

model: otherOptions}

}

Item { //OptionsBox.qmlproperty alias model:

listView.model

ListView {focus: truedelegate: ...

}}

Page 27: Best Practices in Qt Quick/QML - Part IV

Active Focus• Only one item in a QmlEngine will have

activeFocus = true• When drawing focus effects use activeFocus• When taking focus call forceActiveFocus()

• Set focus = true• Sets focus on all FocusScope items in parent

hierarchy

Page 28: Best Practices in Qt Quick/QML - Part IV

FocusScope• Proxies Focus to a child item

• Can be use directly as a wrapper item• More often it is the base class for QML files

Item { //Main.qmlOptionsBox {

model: firstOptionsfocus: true

}OptionsBox {

model: otherOptions}

}

FocusScope{ //OptionsBox.qmlproperty alias model:

listView.model

ListView {focus: truedelegate: ...

}}

Page 29: Best Practices in Qt Quick/QML - Part IV

FocusScope• When a child of a FocusScope has focus

• The FocusScope will also report focus = true• FocusScopes can be nested

•If your application needs keyboard input• Use FocusScope as the base class for all your

custom items• ListViews, Loader, etc have FocusScope built in

Page 30: Best Practices in Qt Quick/QML - Part IV

Handling Keyboard Input• Handling actual key stroke is easy

• Keys is an attached property• Key specific handlers• Handlers for all keys

• Like how QWidgets work

Page 31: Best Practices in Qt Quick/QML - Part IV

Key Specific HandlersRectangle {

color: “black”focus: true

Image {id: rocketx: 150, y:150source: “../images/rocket.svg”transformOrigin: Item.Center

}

Keys.onLeftPressed: rocket.rotation = (rocket.rotation - 10) %360Keys.onRightPressed: rocket.rotation = (rocket.rotation + 10) %360//Note there are only a few specific key handler available for use

}

Page 32: Best Practices in Qt Quick/QML - Part IV

All Keys HandlerRectangle {

color: “black”focus: true

Image {id: rocketx: 150, y:150source: “../images/rocket.svg”transformOrigin: Item.Center

}

Keys.onPressed: {if(event.key == Qt.Key_Left)

rocket.rotation = (rocket.rotation - 10) %360else if (event.key == Qt.Key_Right)

rocket.rotation = (rocket.rotation + 10) %360 }

}

Page 33: Best Practices in Qt Quick/QML - Part IV

Key Event Propagation• Key Events will propagate

• From child to parent up to the root item• Propagation can be stopped with

• event.accepted = true• Key event will stop at the accepted handler

Page 34: Best Practices in Qt Quick/QML - Part IV

Event Propagation is ConvenientRectangle {

color: “black”focus: true

Keys.onVolumeDownPressed: cppMixer.decrementVolume()Keys.onVolumeUpPressed: cppMixer.incrementVolume()

Mp3Player {//Internally handles Key_MediaNext, Key_MediaPreviousvisible: falsefocus: visible

}

AmFmRadio {//Internally handles Key_MediaNext, Key_MediaPreviousvisible: falsefocus: visible

}}

Page 35: Best Practices in Qt Quick/QML - Part IV

Performance Tips

Page 36: Best Practices in Qt Quick/QML - Part IV

Be Asynchronous• Never spend more than a couple of

milliseconds within blocking functions• 60Hz drawing leaves 16ms to get work done

• Or frames get dropped!• User worker threads to do heavy lifting

• QThread or QML WorkerScript

• Never manually spin the event loop• QCoreApplication::processEvents()

• This was sorta-kinda acceptable for with widgets

Page 37: Best Practices in Qt Quick/QML - Part IV

Optimized Binding Evaluator• QML Engine has a binding optimizer

• Doesn’t need to invoke the full JS interpreter• If bindings fit certain criteria

• Help it out by following some guidelines

•Don’t go overboard enforcing these as rules

• Micro-optimized unreadable code could ensue• Use the QML Profiler to examine JS performance

Page 38: Best Practices in Qt Quick/QML - Part IV

Binding Optimizer• Make sure types are known at compile time

• Avoid using var as property type• Qt Creator will underline this yellow

• Avoid assigning properties to derivative types• Avoid caching intermediate vars in bindings

• However, this can be beneficial with full JS interpeter

Page 39: Best Practices in Qt Quick/QML - Part IV

Binding Optimizer• Avoid calling JavaScript functions

• Even though code may be easier to read• Includes anonymous functions

• value: function() {…}

• Avoid using symbols from JavaScript imports

Page 40: Best Practices in Qt Quick/QML - Part IV

Binding Optimizer• Only access Properties in “Component”

Scope• Properties of this item• Ids of any objects in the containing component• Properties of the root item of the component

ListView {id: viewproperty cellHeight: 50 // Bad. Move to inside delegate delegate:

Rectangle {

height: view.cellHeight}

} }

Page 41: Best Practices in Qt Quick/QML - Part IV

Maximize Binding Optimizer• Avoid writing to other properties in a property

binding• This should be obvious.

property int other: 0value: { other = foo } //Also happens to bind value to foo

• QML_COMPILER_STATS env var• Print statistics about how many bindings were

able to be optimized / not optimized.

Page 42: Best Practices in Qt Quick/QML - Part IV

C++ Type Conversions• Avoid variant type QML properties

• Marked as deprecated• Use var instead

• Assigning list types can be expensive• Optimizations implemented are made for

• QString, QUrl, int, bool, qreal, pointer types

Page 43: Best Practices in Qt Quick/QML - Part IV

Cache results• In non-optimized bindings cache results

• Save lookup times.• Especially on objects not in “Component Scope”

Page 44: Best Practices in Qt Quick/QML - Part IV

Don’t Over Update Propeties• Don’t a update property more than one in a

binding or signal handler

Page 45: Best Practices in Qt Quick/QML - Part IV

Delegates• Keep it short. Keep it Simple• Avoid Loader• Avoid Shader Effects• Avoid clip: true• Increase cacheBuffer property for smoother

scrolling• At the cost of memory

Page 46: Best Practices in Qt Quick/QML - Part IV

Animations• Animating properties will cause bindings to

update• Usually what is wanted

• If not use PropertyAction to “unbind” temporarily• Or create a second animatedValue property

• See Bar Chart Example

Page 47: Best Practices in Qt Quick/QML - Part IV

Painting• Avoid Clipping

• Very expensive

• Hide non-visible items (visible = false)• Off screen items• Completely obscured items• QtQuick will call rendering methods for all

visible items

Page 48: Best Practices in Qt Quick/QML - Part IV

Startup Performance• Load as little QML as possible at startup

• main.qml loads a splash screen• main.qml uses async loader to show 1st screen

• Connect loader.progress to an indicator• main.qml hides splash screen when

• loader.status === Loader.Ready

• From here load the screens as the user finds them• Using Loader or component.createObject()

Page 49: Best Practices in Qt Quick/QML - Part IV

Runtime Performance• Use lazy loading to load screens on demand

• Cache screens as they are found• Or at least common screens

• Caching screens causes two side effects• Increase in memory footprint• Processing of bindings for items not on the screen

Page 50: Best Practices in Qt Quick/QML - Part IV

Processing Bindings Off Screen• Bindings are re-calculated when property

NOTIFY signals are emitted• On screen or not• This might not be a bad thing

• If your system is mostly idle• Might as well update bindings while system is idle• Rather than fetch all the data and re-calc when switching screens

which might be animated

• Use case dependent. YMMV.

Page 51: Best Practices in Qt Quick/QML - Part IV

Memory Usage• QML uses quite a bit of memory

• Typical app is around 80MB resident• Qt internals is making this better

• Delete items made with Component createObject• Use destroy()

• Delete uncommon dialogs after the user is done with them• Trading memory for screen reload performance

Page 52: Best Practices in Qt Quick/QML - Part IV

Processor Performance• QtQuick 2 is OpenGL ES 2.0 based

• But some things still need to be run on the main processor

• Animations @ 60 Hz require about 30% of the lowend TI AM3358 CPU*• Code from event handlers can only block for 16ms

max• Or frames will be dropped• User will notice if it’s bad enough

Page 53: Best Practices in Qt Quick/QML - Part IV

Fake Animations• If you just need small animated indicators

• Consider AnimatedImage• Takes an animated GIF• QtQuick has some optimizations for AnimatedImage

• 15fps is around 5 percent CPU• User won’t notice

Page 54: Best Practices in Qt Quick/QML - Part IV

Thank You!

Justin NoelSenior Consulting Engineer

ICS, Inc.