├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The content that you see here is mostly based on my experience working with QML 4 | in a large project with a diverse team of developers and designers. I update 5 | the document as my opinions about certain things become validated by real life 6 | experience. 7 | 8 | You may not agree with some of the ideas laid out here, in those cases please 9 | create an issue to discuss and update accordingly. I'll keep updating this guide 10 | as I learn new things. Contributions are vital to this document because it needs 11 | to reflect tried and validated ideas of more people to make sense in a general 12 | sense. It's likely that I may not have done a good job at explaining a concept. 13 | I would appreciate any contributions to improve it. 14 | 15 | Please don't hesitate to raise issues and submit PRs. Even the tiniest 16 | contribution matters. 17 | 18 | # Table of Contents 19 | 20 | - [Code Style](#code-style) 21 | - [CS-1: Signal Handler Ordering](#cs-1-signal-handler-ordering) 22 | - [CS-2: Property Ordering](#cs-2-property-ordering) 23 | - [CS-3: Function Ordering](#cs-3-function-ordering) 24 | - [CS-4: Animations](#cs-4-animations) 25 | - [CS-5: Giving Components `id`s](#cs-5-giving-components-ids) 26 | - [CS-6: Property Assignments](#cs-6-property-assignments) 27 | - [CS-7: Import Statements](#cs-7-import-statements) 28 | - [Full Example](#full-example) 29 | - [Bindings](#bindings) 30 | - [B-1: Prefer Bindings over Imperative Assignments](#b-1-prefer-bindings-over-imperative-assignments) 31 | - [B-2: Making `Connections`](#b-2-making-connections) 32 | - [B-3: Use `Binding` Object](#b-3-use-binding-object) 33 | + [Transient Bindings](#transient-bindings) 34 | - [B-4: KISS It](#b-4-kiss-it) 35 | - [B-5: Be Lazy](#b-5-be-lazy) 36 | - [B-6: Avoid Unnecessary Re-Evaluations](#b-6-avoid-unnecessary-re-evaluations) 37 | - [C++ Integration](#c-integration) 38 | - [CI-1: Avoid Context Properties](#ci-1-avoid-context-properties) 39 | - [CI-2: Use Singleton for Common API Access](#ci-2-use-singleton-for-common-api-access) 40 | - [CI-3: Prefer Instantiated Types Over Singletons For Data](#ci-3-prefer-instantiated-types-over-singletons-for-data) 41 | - [CI-4: Watch Out for Object Ownership Rules](#ci-4-watch-out-for-object-ownership-rules) 42 | - [Performance and Memory](#performance-and-memory) 43 | - [PM-1: Reduce the Number of Implicit Types](#pm-1-reduce-the-number-of-implicit-types) 44 | - [Signal Handling](#signal-handling) 45 | - [SH-1: Try to Avoid Using connect Function in Models](#sh-1-try-to-avoid-using-connect-function-in-models) 46 | - [SH-2: When to use Functions and Signals](#sh-2-when-to-use-functions-and-signals) 47 | - [JavaScript](#javascript) 48 | - [JS-1: Use Arrow Functions](#js-1-use-arrow-functions) 49 | - [JS-2: Use the Modern Way of Declaring Variables](#js-2-use-the-modern-way-of-declaring-variables) 50 | - [States and Transitions](#states-and-transitions) 51 | - [ST-1: Don't Define Top Level States](#st-1-dont-define-top-level-states) 52 | - [Visual Items](#visual-items) 53 | - [VI-1: Distinguish Between Different Types of Sizes](#vi-1-distinguish-between-different-types-of-sizes) 54 | - [VI-2: Be Careful with a Transparent `Rectangle`](#vi-2-be-careful-with-a-transparent-rectangle) 55 | 56 | 57 | # Code Style 58 | 59 | This section provides details about how to format the order of properties, signals, 60 | and functions to make things easy on the eyes and quickly switch to related code block. 61 | 62 | [QML object attributes](https://doc.qt.io/qt-5/qtqml-syntax-objectattributes.html) 63 | are always structured in the following order: 64 | 65 | - id 66 | - Property declarations 67 | - Signal declarations 68 | - Object properties 69 | - Attached properties and signal handlers 70 | - States 71 | - Transitions 72 | - Signal handlers 73 | - Child objects 74 | + Visual Items 75 | + Qt provided non-visual items 76 | + Custom non-visual items 77 | - `QtObject` for encapsulating private members[1](https://bugreports.qt.io/browse/QTBUG-11984) 78 | - JavaScript functions 79 | 80 | The main purpose for this order is to make sure that the most intrinsic properties of a type is 81 | always the most visible one in order to make the interface easier to digest at a first glance. 82 | Although it could be argued that the JavaScript functions are also part of the interface, the ideal 83 | is to have no functions at all. 84 | 85 | ## CS-1: Signal Handler Ordering 86 | 87 | When handling the signals attached to an `Item`, make sure to always leave 88 | `Component.onCompleted` to the last line. 89 | 90 | ```qml 91 | // Wrong 92 | Item { 93 | Component.onCompleted: { 94 | } 95 | onSomethingHappened: { 96 | } 97 | } 98 | 99 | // Correct 100 | Item { 101 | onSomethingHappened: { 102 | } 103 | Component.onCompleted: { 104 | } 105 | } 106 | ``` 107 | 108 | This is because it mentally makes for a better picture because 109 | `Component.onCompleted` is expected to be fired when the components construction 110 | is complete. 111 | 112 | ------ 113 | 114 | If there are multiple signal handlers in an `Item`, then the ones with least amount 115 | of lines may be placed at the top. As the implementation lines increases, the handler 116 | also moves down. The only exception to this is `Component.onCompleted` signal, it 117 | is always placed at the bottom. 118 | 119 | ```qml 120 | // Wrong 121 | Item { 122 | onOtherEvent: { 123 | // Line 1 124 | // Line 2 125 | // Line 3 126 | // Line 4 127 | } 128 | onSomethingHappened: { 129 | // Line 1 130 | // Line 2 131 | } 132 | } 133 | 134 | // Correct 135 | Item { 136 | onSomethingHappened: { 137 | // Line 1 138 | // Line 2 139 | } 140 | onOtherEvent: { 141 | // Line 1 142 | // Line 2 143 | // Line 3 144 | // Line 4 145 | } 146 | } 147 | ``` 148 | 149 | ## CS-2: Property Ordering 150 | 151 | The first property assignment must always be the `id` of the component. If you 152 | want to declare custom properties for a component, the declarations are always 153 | above the first property assignment. 154 | 155 | ```qml 156 | // Wrong 157 | Item { 158 | someProperty: false 159 | property int otherProperty: -1 160 | id: myItem 161 | } 162 | 163 | // Correct 164 | Item { 165 | id: myItem 166 | 167 | property int otherProperty: -1 168 | 169 | someProperty: false 170 | } 171 | ``` 172 | 173 | There's also a bit of predefined order for property assignments. The order goes 174 | as follows: 175 | 176 | - id 177 | - x 178 | - y 179 | - width 180 | - height 181 | - anchors 182 | 183 | The goal here is to put the most obvious and defining properties at the top for 184 | easy access and visibility. For example, for an `Image` you may decide to also 185 | put `sourceSize` above `anchors`. 186 | 187 | ------ 188 | 189 | If there are also property assignments along with signal handlers, make sure to 190 | always put property assignments above the signal handlers. 191 | 192 | ```qml 193 | // Wrong 194 | Item { 195 | onOtherEvent: { 196 | } 197 | someProperty: true 198 | onSomethingHappened: { 199 | } 200 | x: 23 201 | y: 32 202 | } 203 | 204 | // Correct 205 | Item { 206 | x: 23 207 | y: 32 208 | someProperty: true 209 | onOtherEvent: { 210 | } 211 | onSomethingHappened: { 212 | } 213 | } 214 | ``` 215 | 216 | It is usually harder to see the property assignments If they are mixed with 217 | signal handlers. That's why we are putting the assignments above the signal 218 | handlers. 219 | 220 | ### CS-3: Function Ordering 221 | 222 | Although there are no private and public functions in QML, you can provide a 223 | similar mechanism by wrapping the properties and functions that are only supposed 224 | to be used internally in `QtObject `. 225 | 226 | Public function implementations are always put at the very bottom of the file. 227 | 228 | ```qml 229 | // Wrong 230 | Item { 231 | 232 | function someFunction() { 233 | } 234 | 235 | someProperty: true 236 | } 237 | 238 | // Correct 239 | Item { 240 | someProperty: true 241 | onOtherEvent: { 242 | } 243 | onSomethingHappened: { 244 | } 245 | 246 | function someFunction() { 247 | } 248 | } 249 | ``` 250 | 251 | ### CS-4: Animations 252 | 253 | When using any subclass of `Animation`, especially nested ones like 254 | `SequentialAnimation`, try to reduce the number of properties in one line. 255 | More than 2-3 assignments on the same line becomes harder to reason with after 256 | a while. Or maybe you can keep the one line assignments to whatever line length 257 | convention you have set up for your project. 258 | 259 | Since animations are harder to imagine in your mind, you will benefit from 260 | keeping the animations as simple as possible. 261 | 262 | ```qml 263 | // Bad 264 | NumberAnimation { target: root; property: "opacity"; duration: root.animationDuration; from: 0; to: 1 } 265 | 266 | // Depends on your convention. The line does not exceed 80 characters. 267 | PropertyAction { target: root; property: "visible"; value: true } 268 | 269 | // Good. 270 | SequentialAnimation { 271 | PropertyAction { 272 | target: root 273 | property: "visible" 274 | value: true 275 | } 276 | 277 | NumberAnimation { 278 | target: root 279 | property: "opacity" 280 | duration: root.animationDuration 281 | from: 0 282 | to: 1 283 | } 284 | } 285 | ``` 286 | 287 | ### CS-5: Giving Components `id`s 288 | 289 | If a component does not need to be accessed for a functionality, avoid setting 290 | the `id` property. This way you don't clutter the namespace with unused `id`s and 291 | you'll be less likely to run into duplicate `id` problem. Also, having an id for 292 | a type puts additional cognitive stress because it now means that there's 293 | additional relationships that we need to care for. 294 | 295 | If you want to mark the type with a descriptor but you don't intend to reference 296 | the type, you can use `objectName` instead old just plain old comments. 297 | 298 | Make sure that the top most component in the file always has `root` as its `id`. 299 | Qt will make unqualified name look up deprecated in QML 3, so it's better to 300 | start giving IDs to your components now and use qualified look up. 301 | 302 | See [QTBUG-71578](https://bugreports.qt.io/browse/QTBUG-71578) and 303 | [QTBUG-76016](https://bugreports.qt.io/browse/QTBUG-76016) for more details 304 | on this. 305 | 306 | ### CS-6: Property Assignments 307 | 308 | When assigning grouped properties, always prefer the dot notation If you are only 309 | altering just one property. Otherwise, always use the group notation. 310 | 311 | ```qml 312 | Image { 313 | anchors.left: parent.left // Dot notation 314 | sourceSize { // Group notation 315 | width: 32 316 | height: 32 317 | } 318 | } 319 | ``` 320 | 321 | When you are assigning the component to a `Loader`'s `sourceComponent` in different 322 | places in the same file, consider using the same implementation. For example, in 323 | the following example there are two instances of the same component. If both of 324 | those `SomeSpecialComponent` are meant to be identical it is a better idea to 325 | wrap `SomeSpecialComponent` in a `Component`. 326 | 327 | ```qml 328 | // BEGIN bad. 329 | Loader { 330 | id: loaderOne 331 | sourceComponent: SomeSpecialComponent { 332 | text: "Some Component" 333 | } 334 | } 335 | 336 | Loader { 337 | id: loaderTwo 338 | sourceComponent: SomeSpecialComponent { 339 | text: "Some Component" 340 | } 341 | } 342 | // END bad. 343 | 344 | // BEGIN good. 345 | Loader { 346 | id: loaderOne 347 | sourceComponent: specialComponent 348 | } 349 | 350 | Loader { 351 | id: loaderTwo 352 | sourceComponent: specialComponent 353 | } 354 | 355 | Component { 356 | id: specialComponent 357 | 358 | SomeSpecialComponent { 359 | text: "Some Component" 360 | } 361 | } 362 | // END good. 363 | ``` 364 | 365 | This ensures that whenever you make a change to `specialComponent` it will take 366 | effect in all of the `Loader`s. In the bad example, you would have to duplicate 367 | the same change. 368 | 369 | When in a similar situation without the use of `Loader`, you can use inline 370 | components. 371 | 372 | ```qml 373 | component SomeSpecialComponent: Rectangle { 374 | 375 | } 376 | ``` 377 | 378 | ### CS-7: Import Statements 379 | 380 | If you are importing a JavaScript file, make sure to not include the same 381 | module in both the QML file and the JavaScript file. JavaScript files share the 382 | imports from the QML file so you can take advantage of that. If the JavaScript 383 | file is meant as a library, this does not apply. 384 | 385 | If you are not making use of the imported module in the QML file, consider moving 386 | the import statement to the JavaScript file. But note that once you import something 387 | in the JavaScript file, the imports will no longer be shared. For the complete 388 | rules see [here](https://doc.qt.io/qt-5/qtqml-javascript-imports.html#imports-within-javascript-resources). 389 | 390 | Alternatively, you can use `Qt.include()` which copies the contents of the 391 | included file and you will not have to worry about the import sharing rules. 392 | 393 | As a general rule, you should avoid having unused import statements. 394 | 395 | #### Import Order 396 | 397 | When importing other modules, use the following order; 398 | 399 | - Qt modules 400 | - Third party modules 401 | - Local C++ module imports 402 | - QML folder imports 403 | 404 | ### Full Example 405 | 406 | ```qml 407 | // First Qt imports 408 | import QtQuick 2.15 409 | import QtQuick.Controls 2.15 410 | // Then custom imports 411 | import my.library 1.0 412 | 413 | Item { 414 | id: root 415 | 416 | // ----- Property Declarations 417 | 418 | // Required properties should be at the top. 419 | required property int radius: 0 420 | 421 | property int radius: 0 422 | property color borderColor: "blue" 423 | 424 | // ----- Signal declarations 425 | 426 | signal clicked() 427 | signal doubleClicked() 428 | 429 | // ----- In this section, we group the size and position information together. 430 | 431 | x: 0 432 | y: 0 433 | z: 0 434 | width: 100 435 | height: 100 436 | anchors.top: parent.top // If a single assignment, dot notation can be used. 437 | // If the item is an image, sourceSize is also set here. 438 | // sourceSize: Qt.size(12, 12) 439 | 440 | // ----- Then comes the other properties. There's no predefined order to these. 441 | 442 | // Do not use empty lines to separate the assignments. Empty lines are reserved 443 | // for separating type declarations. 444 | enabled: true 445 | layer.enabled: true 446 | 447 | // ----- Then attached properties and attached signal handlers. 448 | 449 | Layout.fillWidth: true 450 | Drag.active: false 451 | Drag.onActiveChanged: { 452 | 453 | } 454 | 455 | // ----- States and transitions. 456 | 457 | states: [ 458 | State { 459 | 460 | } 461 | ] 462 | transitions: [ 463 | Transitions { 464 | 465 | } 466 | ] 467 | 468 | // ----- Signal handlers 469 | 470 | onWidthChanged: { // Always use curly braces. 471 | 472 | } 473 | // onCompleted and onDestruction signal handlers are always the last in 474 | // the order. 475 | Component.onCompleted: { 476 | 477 | } 478 | Component.onDestruction: { 479 | 480 | } 481 | 482 | // ----- Visual children. 483 | 484 | Rectangle { 485 | height: 50 486 | anchors: { // For multiple assignments, use group notation. 487 | top: parent.top 488 | left: parent.left 489 | right: parent.right 490 | } 491 | color: "red" 492 | layer: { 493 | enabled: true 494 | samples: 4 495 | } 496 | } 497 | 498 | Rectangle { 499 | width: parent.width 500 | height: 1 501 | color: "green" 502 | } 503 | 504 | // ----- Qt provided non-visual children 505 | 506 | Timer { 507 | 508 | } 509 | 510 | // ----- Custom non-visual children 511 | 512 | MyCustomNonVisualType { 513 | 514 | } 515 | 516 | QtObject { 517 | id: privates 518 | 519 | property int diameter: 0 520 | } 521 | 522 | // ----- JavaScript functions 523 | 524 | function collapse() { 525 | 526 | } 527 | 528 | function setCollapsed(value: bool) { 529 | if (value === true) { 530 | } 531 | else { 532 | } 533 | } 534 | } 535 | ``` 536 | 537 | # Bindings 538 | 539 | Bindings are a powerful tool when used responsibly. Bindings are evaluated 540 | whenever a property it depends on changes and this may result in poor performance 541 | or unexpected behaviors. Even when the binding is simple, its consequence can be 542 | expensive. For instance, a binding can cause the position of an item to change 543 | and every other item that depends on the position of that item or is anchored to 544 | it will also update its position. 545 | 546 | So consider the following rules when you are using bindings. 547 | 548 | ## B-1: Prefer Bindings over Imperative Assignments 549 | 550 | See the related section on [Qt Documentation](https://doc.qt.io/qt-5/qtquick-bestpractices.html#prefer-declarative-bindings-over-imperative-assignments). 551 | 552 | The official documentation explains things well, but it is also important to 553 | understand the performance complications of bindings and understand where the 554 | bottlenecks can be. 555 | 556 | If you suspect that the performance issue you are having is related to 557 | excessive evaluations of bindings, then use the QML profiler to confirm your 558 | suspicion and then opt-in to use imperative option. 559 | 560 | Refer to the [official documentation](https://doc.qt.io/qtcreator/creator-qml-performance-monitor.html) 561 | on how to use QML profiler. 562 | 563 | ## B-2: Making `Connections` 564 | 565 | A `Connections` object is used to handle signals from arbitrary `QObject` derived 566 | classes in QML. One thing to keep in mind when using connections is the default 567 | value of `target` property of the `Connections` is its parent if not explicitly 568 | set to something else. If you are setting the target after dynamically creating 569 | a QML object, you might want to set the `target` to `null` otherwise you might 570 | get signals that are not meant to be handled. 571 | 572 | ```qml 573 | // Bad 574 | Item { 575 | id: root 576 | onSomethingHappened: { 577 | // Set the target of the Connections. 578 | } 579 | 580 | Connections { 581 | // Notice that target is not set so it's implicitly set to root. 582 | onWidthChanged: { 583 | // Do something. But since Item also has a width property we may 584 | // handle the change for root until the target is set explicitly. 585 | } 586 | } 587 | } 588 | 589 | // Good 590 | Item { 591 | id: root 592 | onSomethingHappened: { 593 | // Set the target of the Connections. 594 | } 595 | 596 | Connections { 597 | target: null // Good. Now we won't have the same problem. 598 | onWidthChanged: { 599 | // Do something. Only handles the changes for the intended target. 600 | } 601 | } 602 | } 603 | ``` 604 | 605 | ## B-3: Use `Binding` Object 606 | 607 | `Binding`'s `when` property can be used to enable or disable a binding expression 608 | depending on a condition. If the binding that you are using is complex and does 609 | not need to be executed every time a property changes, this is a good idea to 610 | reduce the binding execution count. 611 | 612 | Using the same example above, we can rewrite it as follows using a `Binding` object. 613 | 614 | ```qml 615 | Rectangle { 616 | id: root 617 | 618 | Binding on color { 619 | when: mouseArea.pressed 620 | value: mouseArea.pressed ? "red" : "yellow" 621 | } 622 | 623 | MouseArea { 624 | id: mouseArea 625 | anchors.fill: parent 626 | } 627 | } 628 | ``` 629 | 630 | Again, this is a really simple example to get the point out. In a real life 631 | situation, you would not get more benefit from using `Binding` object in this 632 | case unless the binding expression is expensive (e.g It changes the item's 633 | `anchor` which causes a whole chain reaction and causes other items to be 634 | repositioned.). 635 | 636 | `Binding` objects can also be used to provide bidirectional binding for 637 | properties without the risk of breaking the bindings. Consider the following 638 | example: 639 | 640 | ```qml 641 | Rectangle { 642 | id: rect 643 | width: 50 644 | height: 50 645 | anchors.centerIn: parent 646 | color: cd.color 647 | 648 | MouseArea { 649 | anchors.fill: parent 650 | acceptedButtons: Qt.LeftButton | Qt.RightButton 651 | onClicked: { 652 | if (mouse.button === Qt.LeftButton) { 653 | cd.visible = true; 654 | } 655 | else { 656 | parent.color = "black" 657 | } 658 | } 659 | } 660 | } 661 | 662 | ColorDialog { 663 | id: cd 664 | color: rect.color 665 | } 666 | ``` 667 | 668 | The binding to `color` properties of `ColorDialog` and `Rectangle` will be broken 669 | once those proeprties are set from outside. If you play around with the example, 670 | you'll see that `parent.color = "black"` breaks the binding. 671 | 672 | Now, see the following example and you'll find that bindings are not broken. 673 | 674 | ```qml 675 | Rectangle { 676 | id: rect 677 | width: 50 678 | height: 50 679 | anchors.centerIn: parent 680 | color: "red" 681 | 682 | Binding on color { 683 | value: cd.color 684 | } 685 | 686 | MouseArea { 687 | anchors.fill: parent 688 | acceptedButtons: Qt.LeftButton | Qt.RightButton 689 | onClicked: { 690 | if (mouse.button === Qt.LeftButton) { 691 | cd.visible = true; 692 | } 693 | else { 694 | parent.color = "black" 695 | } 696 | } 697 | } 698 | } 699 | 700 | ColorDialog { 701 | id: cd 702 | 703 | Binding on color { 704 | value: rect.color 705 | } 706 | } 707 | ``` 708 | 709 | ### Transient Bindings 710 | 711 | There may be cases where you have to end up using an imperative assignment. But 712 | naturally this will break the binding. In that case, you can create transient 713 | `Binding` objects to safely set the new property without breaking the existing 714 | binding. 715 | 716 | ```qml 717 | Item { 718 | property var contentItem 719 | 720 | onContentItemChanged: { 721 | contentItem.width = 100 // This will break the binding. 722 | // ----- 723 | const temp = cmpBinding.createObject(root, { 724 | "target": contentItem, 725 | "property": "width", 726 | "value": 100 727 | }) 728 | // Now the width property is safely updated to 100 without breaking 729 | // any existing bindings. 730 | temp.destroy() // Don't forget to destroy it. 731 | } 732 | 733 | Component { 734 | id: cmpBinding 735 | 736 | Binding { } 737 | } 738 | } 739 | ``` 740 | 741 | ## B-4: KISS It 742 | 743 | You are probably already familiar with the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle). 744 | QML supports optimization of binding expressions. Optimized bindings do not require 745 | a JavaScript environment hence it runs faster. The basic requirement for optimization 746 | of bindings is that the type information of every symbol accessed must be known at 747 | compile time. 748 | 749 | So, avoid accessing `var` properties. You can see the full list of prerequisites 750 | of optimized bindings [here](https://doc.qt.io/qt-5/qtquick-performance.html#bindings). 751 | 752 | ## B-5: Be Lazy 753 | 754 | There may be cases where you don't need the binding immediately but when a certain 755 | condition is met. By lazily creating a binding, you can avoid unnecessary executions. 756 | To create a binding during runtime, you can use `Qt.binding()`. 757 | 758 | ```qml 759 | Item { 760 | property int edgePosition: 0 761 | 762 | Component.onCompleted: { 763 | if (checkForSomeCondition() == true) { 764 | // bind to the result of the binding expression passed to Qt.binding() 765 | edgePosition = Qt.binding(function() { return x + width }) 766 | } 767 | } 768 | } 769 | ``` 770 | 771 | You can also use `Qt.callLater` to reduce the redundant calls to a function. 772 | 773 | ## B-6: Avoid Unnecessary Re-Evaluations 774 | 775 | If you have a loop or process where you update the value of the property, you may 776 | want to use a temporary local variable where you accumulate those changes and only 777 | report the last value to the property. This way you can avoid triggering re-evaluation 778 | of binding expressions during the intermediate stages of accumulation. 779 | 780 | Here's a bad example straight from Qt documentation: 781 | 782 | ```qml 783 | import QtQuick 2.3 784 | 785 | Item { 786 | id: root 787 | 788 | property int accumulatedValue: 0 789 | 790 | width: 200 791 | height: 200 792 | Component.onCompleted: { 793 | const someData = [ 1, 2, 3, 4, 5, 20 ]; 794 | for (let i = 0; i < someData.length; ++i) { 795 | accumulatedValue = accumulatedValue + someData[i]; 796 | } 797 | } 798 | 799 | Text { 800 | anchors.fill: parent 801 | text: root.accumulatedValue.toString() 802 | onTextChanged: console.log("text binding re-evaluated") 803 | } 804 | } 805 | ``` 806 | 807 | And here is the proper way of doing it: 808 | 809 | ```qml 810 | import QtQuick 2.3 811 | 812 | Item { 813 | id: root 814 | 815 | property int accumulatedValue: 0 816 | 817 | width: 200 818 | height: 200 819 | Component.onCompleted: { 820 | const someData = [ 1, 2, 3, 4, 5, 20 ]; 821 | let temp = accumulatedValue; 822 | for (let i = 0; i < someData.length; ++i) { 823 | temp = temp + someData[i]; 824 | } 825 | 826 | accumulatedValue = temp; 827 | } 828 | 829 | Text { 830 | anchors.fill: parent 831 | text: root.accumulatedValue.toString() 832 | onTextChanged: console.log("text binding re-evaluated") 833 | } 834 | } 835 | ``` 836 | 837 | # C++ Integration 838 | 839 | QML can be extended with C++ by exposing the `QObject` classes using the `Q_OBJECT` 840 | macro or custom data types using `Q_GADGET` macro. 841 | It always should be preferred to use C++ to add functionality to a QML application. 842 | But it is important to know which is the best way to expose your C++ classes, and 843 | it depends on your use case. 844 | 845 | ## CI-1: Avoid Context Properties 846 | 847 | Context properties are registered using 848 | 849 | ```cpp 850 | rootContext()->setContextProperty("someProperty", QVariant()); 851 | ``` 852 | 853 | Context properties always takes in a `QVariant`, which means that whenever you access the property 854 | it is re-evaluated because in between each access the property may be changed as 855 | `setContextProperty()` can be used at any moment in time. 856 | 857 | Context properties are expensive to access, and hard to reason with. When you are writing QML code, 858 | you should strive to reduce the use of contextual variables (A variable that doesn't exist in the 859 | immediate scope, but the one above it.) and global state. Each QML document should be able to run 860 | with QML scene provided that the required properties are set. 861 | 862 | See [QTBUG-73064](https://bugreports.qt.io/browse/QTBUG-73064). 863 | 864 | ## CI-2: Use Singleton for Common API Access 865 | 866 | There are bound to be cases where you have to provide a single instance for a 867 | functionality or common data access. In this situation, resort to using a singleton 868 | as it will have a better performance and be easier to read. Singletons are also 869 | a good option to expose enums to QML. 870 | 871 | ```cpp 872 | class MySingletonClass : public QObject 873 | { 874 | public: 875 | static QObject *singletonProvider(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 876 | { 877 | if (m_Instance == nullptr) { 878 | m_Instance = new MySingletonClass(qmlEngine); 879 | } 880 | 881 | Q_UNUSED(jsEngine); 882 | return m_Instance; 883 | } 884 | }; 885 | 886 | // In main.cpp 887 | qmlRegisterSingletonType("MyNameSpace", 1, 0, "MySingletonClass", 888 | MySingletonClass::singletonProvider); 889 | ``` 890 | 891 | You should strive to not use singletons for shared data access. Reusable components are especially 892 | a bad place to access singletons. Ideally, all QML documents should rely on the customization 893 | through properties to change its content. 894 | 895 | Let's imagine a scenario where we are creating a paint app where we can change the currently 896 | selected color on the palette. We only have one instance of the palette, and the data from this is 897 | accessed throughout our C++ code. So we decided that it makes sense to expose it as a singleton to 898 | QML side. 899 | 900 | ```qml 901 | // ColorViewer.qml 902 | Row { 903 | id: root 904 | 905 | Rectangle { 906 | color: Palette.selectedColor 907 | } 908 | 909 | Text { 910 | text: Palette.selectedColorName 911 | } 912 | } 913 | ``` 914 | 915 | With this code, we bind our component to `Palette` singleton. Who ever wants to use our `ColorViewer` 916 | they won't be able to change it so they can show some other selected color. 917 | 918 | ```qml 919 | // ColorViewer_2.qml 920 | Row { 921 | id: root 922 | 923 | property alias selectedColor: colorIndicator.color 924 | property alias selectedColorName: colorLabel.color 925 | 926 | Rectangle { 927 | id: colorIndicator 928 | color: Palette.selectedColor 929 | } 930 | 931 | Text { 932 | id: colorLabel 933 | text: Palette.selectedColorName 934 | } 935 | } 936 | ``` 937 | 938 | This would allow the users of this component to set the color and the name from outside, but we 939 | still have a dependency on the singleton. 940 | 941 | ```qml 942 | // ColorViewer_3.qml 943 | Row { 944 | id: root 945 | 946 | property alias selectedColor: colorIndicator.color 947 | property alias selectedColorName: colorLabel.color 948 | 949 | Rectangle { 950 | id: colorIndicator 951 | } 952 | 953 | Text { 954 | id: colorLabel 955 | } 956 | } 957 | ``` 958 | 959 | This version allows you to de-couple from the singleton, enable it to be resuable in any context 960 | that wants to show a selected color, and you could easily run this through `qmlscene` and inspect 961 | its behavior. 962 | 963 | ## CI-3: Prefer Instantiated Types Over Singletons For Data 964 | 965 | Instantiated types are exposed to QML using: 966 | 967 | ```cpp 968 | // In main.cpp 969 | qmlRegisterType("MyNameSpace", 1, 0, "ColorModel"); 970 | ``` 971 | 972 | Instantiated types have the benefit of having everything available to you to understand and digest 973 | in the same document. They are easier to change at run-time without creating side effects, and easy 974 | to reason with because when looking at a document, you don't need to worry about any global state 975 | but the state of the type that you are dealing with at hand. 976 | 977 | ```qml 978 | // ColorsWindow.qml 979 | Window { 980 | id: root 981 | 982 | Column { 983 | Repeater { 984 | model: Palette.selectedColors 985 | delegate: ColorViewer { 986 | required property color color 987 | required property string colorName 988 | 989 | selectedColor: color 990 | selectedColorName: colorName 991 | } 992 | } 993 | } 994 | } 995 | ``` 996 | 997 | The code above is a perfectly valid QML code. We'll get our model from the singleton, and display it 998 | with the reusable component we created in CI-2. However, there's still a problem here. `ColorsWindow` 999 | is now bound to the model from `Palette` singleton. And If I wanted to have the user select two 1000 | different sets of colors, I would need to create another file with the same contents and use that. 1001 | Now we have 2 components doing basically the same thing. And those two components need to be 1002 | maintained. 1003 | 1004 | This also makes it hard to prototype. If I wanted to see two different versions of this window with 1005 | different colors at the same time, I can't do it because I'm using a singleton. Or, If I wanted to 1006 | pop up a new window that shows the users the variants of a color set, I can't do it because the data 1007 | is bound to the singleton. 1008 | 1009 | A better approach here is to either use an instantiated type or expect the model as a property. 1010 | 1011 | ```qml 1012 | // ColorsWindow.qml 1013 | Window { 1014 | id: root 1015 | 1016 | property PaletteColorsModel model 1017 | 1018 | Column { 1019 | Repeater { 1020 | model: root.model 1021 | // Alternatively 1022 | model: PaletteColorsModel { } 1023 | delegate: ColorViewer { 1024 | required property color color 1025 | required property string colorName 1026 | 1027 | selectedColor: color 1028 | selectedColorName: colorName 1029 | } 1030 | } 1031 | } 1032 | } 1033 | ``` 1034 | 1035 | Now, I can have the same window up at the same time with different color sets because they are not 1036 | bound to a singleton. During prototyping, I can provide a dummy data easily by adding 1037 | `PaletteColorElement` types to the model, or by requesting test dataset with something like: 1038 | 1039 | ```qml 1040 | PaletteColorsModel { 1041 | testData: "prototype_1" 1042 | } 1043 | ``` 1044 | 1045 | This test data could be auto-generated, or it could be provided by a JSON file. The beauty is that 1046 | I'm no longer bound to a singleton, that I have the freedom to instantiate as many of these windows 1047 | as I want. 1048 | 1049 | There may be cases where you actually truly want the data to be the same every where. In these 1050 | cases, you should still provide an instantiated type instead of a singleton. You can still access 1051 | the same resource in the C++ implementation of your model and provide that to QML. And you would 1052 | still retain the freedom of making your data easily pluggable in different context and it would 1053 | increase the re-usability of your code. 1054 | 1055 | ```cpp 1056 | class PaletteColorsModel 1057 | { 1058 | explicit PaletteColorsModel(QObject* parent = nullptr) 1059 | { 1060 | initializeModel(MyColorPaletteSingleton::instance().selectedColors()); 1061 | } 1062 | }; 1063 | ``` 1064 | 1065 | ## CI-4: Watch Out for Object Ownership Rules 1066 | 1067 | When you are exposing data to QML from C++, you are likely to pass around custom 1068 | data types as well. It is important to realize the implications of ownership when 1069 | you are passing data to QML. Otherwise you might end up scratching your head trying 1070 | to figure out why your app crashes. 1071 | 1072 | If you are exposing custom data type, prefer to set the parent of that data to the 1073 | C++ class that transmits it to QML. This way, when the C++ class gets destroyed 1074 | the custom data type also gets destroyed and you won't have to worry about releasing 1075 | memory manually. 1076 | 1077 | There might also be cases where you expose data from a singleton class without a 1078 | parent and the data gets destroyed because QML object that receives it will take 1079 | ownership and destroy it. And you will end up accessing data that doesn't exist. 1080 | Ownership is **not** transferred as the result of a property access. For data 1081 | ownership rules see [here](https://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership). 1082 | 1083 | To learn more about the real life implications of this read [this blog post](https://www.embeddeduse.com/2018/04/02/qml-engine-deletes-c-objects-still-in-use/). 1084 | 1085 | # Performance and Memory 1086 | 1087 | Most applications are not likely to have memory limitations. But in case you are 1088 | working on a memory limited hardware or you just really care about memory allocations, 1089 | follow these steps to reduce your memory usage. 1090 | 1091 | ## PM-1: Reduce the Number of Implicit Types 1092 | 1093 | If a type defines custom properties, that type becomes an implicit type to the JS 1094 | engine and additional type information has to be stored. 1095 | 1096 | ```qml 1097 | Rectangle { } // Explicit type because it doesn't contain any custom properties 1098 | 1099 | Rectangle { 1100 | // The deceleration of this property makes this Rectangle an implicit type. 1101 | property int meaningOfLife: 42 1102 | } 1103 | ``` 1104 | 1105 | You should follow the advice from the [official documentation](http://doc.qt.io/qt-5/qtquick-performance.html#avoid-defining-multiple-identical-implicit-types) 1106 | and split the type into its own component If it's used in more than one place. 1107 | But sometimes, that might not make sense for your case. If you are using a lot of 1108 | custom properties in your QML file, consider wrapping the custom properties of 1109 | types in a `QtObject`. Obviously, JS engine will still need to allocate memory 1110 | for those types, but you already gain the memory efficiency by avoiding the 1111 | implicit types. Additionally, wrapping the properties in a `QtObject` uses less 1112 | memory than scattering those properties to different types. 1113 | 1114 | Consider the following example: 1115 | 1116 | ```qml 1117 | Window { 1118 | Rectangle { id: r1 } // Explicit type. Memory 64b, 1 allocation. 1119 | 1120 | // Implicit type. Memory 128b, 3 allocations. 1121 | Rectangle { id: r2; property string nameTwo: "" } 1122 | 1123 | QtObject { // Implicit type. Memory 128b, 3 allocations. 1124 | id: privates 1125 | property string name: "" 1126 | } 1127 | } 1128 | ``` 1129 | 1130 | In this example, the introduction of a custom property to added additional 64b 1131 | of memory and 2 more allocations. Along with `privates`, memory usage adds up to 1132 | 256b. The total memory usage is 320b. 1133 | 1134 | You can use the QML profiler to see the allocations and memory usage for each 1135 | type. If we change that example to the following, you'll see that both memory 1136 | usage and number of allocations are reduced. 1137 | 1138 | ```qml 1139 | Window { 1140 | Rectangle { id: r1 } // Explicit type. Memory 64b, 1 allocation. 1141 | 1142 | Rectangle { id: r2 } // Explicit type. Memory 64b, 1 allocation. 1143 | 1144 | QtObject { // Implicit type. Memory 160b, 4 allocations. 1145 | id: privates 1146 | 1147 | property string name: "" 1148 | property string nameTwo: "" 1149 | } 1150 | } 1151 | ``` 1152 | 1153 | In the second example, total memory usage is 288b. This is really a minute 1154 | difference in this context, but as the number of components increase in a 1155 | project with memory constrained hardware, it can start to make a difference. 1156 | 1157 | # Signal Handling 1158 | 1159 | Signals are a very powerful mechanism in Qt/QML. And the fact that you can 1160 | connect to signals from C++ makes it even better. But in some situations, If you 1161 | don't handle them correctly you might end up scratching your head. 1162 | 1163 | ## SH-1: Try to Avoid Using connect Function in Models 1164 | 1165 | You can have signals in the QML side, and the C++ side. Here's an example for 1166 | both cases. 1167 | 1168 | QML Example. 1169 | 1170 | ```qml 1171 | // MyButton.qml 1172 | import QtQuick.Controls 2.3 1173 | 1174 | Button { 1175 | id: root 1176 | 1177 | signal rightClicked() 1178 | } 1179 | ``` 1180 | 1181 | C++ Example: 1182 | 1183 | ```cpp 1184 | class MyButton 1185 | { 1186 | Q_OBJECT 1187 | 1188 | signals: 1189 | void rightClicked(); 1190 | }; 1191 | ``` 1192 | 1193 | The way you connect to signals is using the syntax 1194 | 1195 | ```qml 1196 | item.somethingChanged.connect(function() {}) 1197 | ``` 1198 | 1199 | When this method is used, you create a function that is connected to the 1200 | `somethingChanged` signal. 1201 | 1202 | Consider the following example: 1203 | 1204 | ```qml 1205 | // MyItem.qml 1206 | Item { 1207 | id: root 1208 | 1209 | property QtObject customObject 1210 | 1211 | objectName: "my_item_is_alive" 1212 | onCustomObjectChanged: { 1213 | customObject.somethingChanged.connect(() => { 1214 | console.log(root.objectName) 1215 | }) 1216 | } 1217 | } 1218 | ``` 1219 | 1220 | This is a perfectly legal code. And it would most likely work in most scenarios. 1221 | But, if the life time of the `customObject` is not managed in `MyItem`, meaning 1222 | if the `customObject` can keep on living when the `MyItem` instance is destroyed, 1223 | you run into problems. 1224 | 1225 | The connection is created in the context of `MyItem`, and the function naturally 1226 | has access to its enclosing context. So, as long as we have the instance of 1227 | `MyItem`, whenever `somethingChanged` is emitted we'd get a log saying 1228 | `my_item_is_alive`. 1229 | 1230 | Here's a quote directly from [Qt documentation](https://doc.qt.io/qt-5/qml-qtquick-listview.html): 1231 | 1232 | > Delegates are instantiated as needed and may be destroyed at any time. They 1233 | > are parented to `ListView`'s `contentItem`, not to the view itself. State 1234 | > should never be stored in a delegate. 1235 | 1236 | So you might be making use of an external object to store state. But what If 1237 | `MyItem` is used in a `ListView`, and it went out of view and it was destroyed 1238 | by `ListView`? 1239 | 1240 | Let's examine what happens with a more concrete example. 1241 | 1242 | ```qml 1243 | ApplicationWindow { 1244 | id: root 1245 | 1246 | property list myObjects: [ 1247 | QtObject { 1248 | signal somethingHappened() 1249 | }, 1250 | QtObject { 1251 | signal somethingHappened() 1252 | }, 1253 | QtObject { 1254 | signal somethingHappened() 1255 | }, 1256 | QtObject { 1257 | signal somethingHappened() 1258 | }, 1259 | QtObject { 1260 | signal somethingHappened() 1261 | }, 1262 | QtObject { 1263 | signal somethingHappened() 1264 | }, 1265 | QtObject { 1266 | signal somethingHappened() 1267 | }, 1268 | QtObject { 1269 | signal somethingHappened() 1270 | } 1271 | ] 1272 | 1273 | width: 640 1274 | height: 480 1275 | 1276 | ListView { 1277 | anchors { 1278 | top: parent.top 1279 | left: parent.left 1280 | right: parent.right 1281 | bottom: btn.top 1282 | } 1283 | // Low enough we can resize the window to destroy buttons. 1284 | cacheBuffer: 1 1285 | model: root.myObjects.length 1286 | delegate: Button { 1287 | id: self 1288 | 1289 | readonly property string name: "Button #" + index 1290 | 1291 | text: "Button " + index 1292 | onClicked: { 1293 | root.myObjects[index].somethingHappened() 1294 | } 1295 | Component.onCompleted: { 1296 | root.myObjects[index].somethingHappened.connect(() => { 1297 | // When the button is destroyed, this will cause the following 1298 | // error: TypeError: Type error 1299 | console.log(self.name) 1300 | }) 1301 | } 1302 | Component.onDestruction: { 1303 | console.log("Destroyed #", index) 1304 | } 1305 | } 1306 | } 1307 | 1308 | Button { 1309 | id: btn 1310 | anchors { 1311 | bottom: parent.bottom 1312 | horizontalCenter: parent.horizontalCenter 1313 | } 1314 | text: "Emit Last Signal" 1315 | onClicked: { 1316 | root.myObjects[root.myObjects.length - 1].somethingHappened() 1317 | } 1318 | } 1319 | } 1320 | ``` 1321 | 1322 | In this example, once one of the buttons is destroyed we still have the object 1323 | instance. And then object instance still contains the connection we made in 1324 | `Component.onCompleted`. So, when we click on `btn`, we get an error: 1325 | `TypeError: Type error`. But once we expand the window so that the button is 1326 | created again, we don't get that error. That is, we don't get that error for the 1327 | newly created button. But the previous connection still exists and still causes 1328 | error. But now that a new one is created, we end up with two connections on the 1329 | same object. 1330 | 1331 | This is obviously not ideal and should be avoided. But how do you do it? 1332 | 1333 | The simplest and most elegant solution (That I have found) is to simply use a 1334 | `Connections` object and handle the signal there. So, If we change the code to 1335 | this: 1336 | 1337 | ```qml 1338 | delegate: Button { 1339 | id: self 1340 | 1341 | readonly property string name: "Button #" + index 1342 | 1343 | text: "Button " + index 1344 | onClicked: { 1345 | root.myObjects[index].somethingHappened() 1346 | } 1347 | 1348 | Connections { 1349 | target: root.myObjects[index] 1350 | onSomethingHappened: { 1351 | console.log(self.name) 1352 | } 1353 | } 1354 | } 1355 | ``` 1356 | 1357 | Now, whenever the delegate is destroyed so is the connection. This method can 1358 | be used even for multiple objects. You can simply put the `Connections` in a 1359 | `Component` and use `createObject` to instantiate it for a specific object. 1360 | 1361 | ```qml 1362 | Item { 1363 | id: root 1364 | onObjectAdded: { 1365 | cmp.createObject(root, {"target": newObject}) 1366 | } 1367 | 1368 | Component { 1369 | id: cmp 1370 | 1371 | Connections { 1372 | target: root.myObjects[index] 1373 | onSomethingHappened: { 1374 | console.log(self.name) 1375 | } 1376 | } 1377 | } 1378 | } 1379 | ``` 1380 | 1381 | ## SH-2: When to use Functions and Signals 1382 | 1383 | When coming from imperative programming, it might be very tempting to use signals 1384 | very similar to functions. Resist this temptation. Especially when communicating 1385 | between the C++ layer of your application, misusing signals can be very confusing 1386 | down the line. 1387 | 1388 | Let's first clearly define what a signal should be doing. Here's how 1389 | [Qt](https://doc.qt.io/qt-5/signalsandslots.html#signals) defines it. 1390 | 1391 | > Signals are emitted by an object when its internal state has changed in some 1392 | > way that might be interesting to the object's client or owner. 1393 | 1394 | This means that whatever happens in the signal handler is a reaction to an 1395 | internal state change of an object. The signal handler should not be changing 1396 | something else in the same object. 1397 | 1398 | See the following example. We have a `ColorPicker` component that we want to use 1399 | to show the user a message when the color is picked. As far as component design 1400 | goes, the fact that the customer sees a message is not `ColorPicker`'s job. 1401 | Its job is to present a dialog and change the color it represents. 1402 | 1403 | ```qml 1404 | // ColorPicker.qml 1405 | Rectangle { 1406 | id: root 1407 | 1408 | signal colorPicked() 1409 | 1410 | ColorDialog { 1411 | onColorChanged: { 1412 | root.color = color 1413 | root.colorPicked() 1414 | } 1415 | } 1416 | } 1417 | 1418 | // main.qml 1419 | Window { 1420 | ColorPicker { 1421 | onColorPicked: { 1422 | label.text = "Color Changed" 1423 | } 1424 | } 1425 | 1426 | Label { 1427 | id: label 1428 | } 1429 | } 1430 | ``` 1431 | 1432 | The above example is pretty straightforward, the signal handler only reacts to 1433 | a change and does something with that information after which the `ColorPicker` 1434 | object is not affected. 1435 | 1436 | ```qml 1437 | // ColorPicker.qml 1438 | Rectangle { 1439 | id: root 1440 | 1441 | signal colorPicked(color pickedColor) 1442 | 1443 | ColorDialog { 1444 | onColorChanged: { 1445 | root.colorPicked(color) 1446 | } 1447 | } 1448 | } 1449 | 1450 | // main.qml 1451 | Window { 1452 | ColorPicker { 1453 | onColorPicked: { 1454 | color = pickedColor 1455 | label.text = "Color Changed" 1456 | } 1457 | } 1458 | 1459 | Label { 1460 | id: label 1461 | } 1462 | } 1463 | ``` 1464 | 1465 | In this example, the signal handler not only reacts to an internal state but it 1466 | also changes it. This is a very simple example, and it'll be easy to spot an 1467 | error. However complex your application is, you will always benefit from 1468 | making the distinction clear. Otherwise what you think to be a function at first 1469 | glance might end up being a signal and it loses its semantics of an internal 1470 | state change. 1471 | 1472 | Here's a general principle to follow: 1473 | 1474 | 1. When communicating up, use signals. 1475 | 2. When communicating down, use functions. 1476 | 1477 | ### Communicating with C++ Using Signals 1478 | 1479 | When you have a model that you use in the QML side, it's very possible that you 1480 | are going to run into cases where something that happens in the QML side needs 1481 | to trigger an action in the C++ side. 1482 | 1483 | In these cases, prefer not to invoke any C++ signals from QML side. Instead, 1484 | use a function call or better a property assignment. The C++ object then should 1485 | make the decision whether to fire a signal or not. 1486 | 1487 | If you are using a C++ type instantiated in QML, the same rules apply. You should 1488 | not be emitting signals from QML side. 1489 | 1490 | # JavaScript 1491 | 1492 | It is the prevalent advice that you should avoid using JavaScript as much as possible 1493 | in your QML code and have the C++ side handle all the logic. This is a sound advice 1494 | and should be followed, but there are cases where you can't avoid having JavaScript 1495 | code for your UI. In those cases, follow these guidelines to ensure a good use of 1496 | JavaScript in QML. 1497 | 1498 | ## JS-1: Use Arrow Functions 1499 | 1500 | Arrow functions were introduced in ES6. Its syntax is pretty close to C++ lambdas 1501 | and they have a pretty neat feature that makes them most comfortable to use 1502 | when you are using the `connect()` function to create a binding. If there's no 1503 | block within the arrow function, it has an implicit return statement. 1504 | 1505 | Let's compare the arrow function version with the old way. 1506 | 1507 | ```qml 1508 | Item { 1509 | property int value: -1 1510 | 1511 | Component.onCompelted: { 1512 | // Arrow function 1513 | root.value = Qt.binding(() => root.someOtherValue) 1514 | // The old way. 1515 | root.value = Qt.binding(function() { return root.someOtherValue }) 1516 | } 1517 | } 1518 | ``` 1519 | 1520 | The arrow function version is easier on the eyes and cleaner to write. 1521 | For more information about arrow functions, head over to the [MDN Blog](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 1522 | 1523 | ## JS-2: Use the Modern Way of Declaring Variables 1524 | 1525 | With ES6, there are 3 ways of delcaring a variable: `var`, `let`, and `const`. 1526 | 1527 | You should leverage `let` and `const` in your codebase and avoid using `var`. 1528 | `let` and `const` enables a scope based naming wheras `var` only knows about one 1529 | scope. 1530 | 1531 | ```qml 1532 | Item { 1533 | onClicked: { 1534 | const value = 32; 1535 | let valueTwo = 42; 1536 | { 1537 | // Valid assignment since we are in a different scope. 1538 | const value = 32; 1539 | let valueTwo = 42; 1540 | } 1541 | } 1542 | } 1543 | ``` 1544 | 1545 | Much like in C++, prefer using `const` If you don't want the variable to be assigned. 1546 | But keep in mind that `const` variables in JavaScript are not immutable. It just 1547 | means they can't be reassigned, but their contents can be changed. 1548 | 1549 | ```javascript 1550 | const value = 32; 1551 | value = 42; // ERROR! 1552 | 1553 | const obj = {value: 32}; 1554 | obj.value = 42; // Valid. 1555 | ``` 1556 | 1557 | See the MDN posts on [const](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) 1558 | and [let](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) 1559 | 1560 | # States and Transitions 1561 | 1562 | States and transitions are a powerful way to create dynamic UIs. Here are some things to keep in 1563 | mind when you are using them in your projects. 1564 | 1565 | ## ST-1: Don't Define Top Level States 1566 | 1567 | Defining states at the top-level of a reusable component can cause breakages if the user of your 1568 | components also define their own states for their specific use case. 1569 | 1570 | ```qml 1571 | // MyButton.qml 1572 | Rectangle { 1573 | id: root 1574 | 1575 | property alias text: lb.text 1576 | property alias hovered: ma.containsMouse 1577 | 1578 | color: "red" 1579 | states: [ 1580 | State { 1581 | when: ma.containsMouse 1582 | 1583 | PropertyChanges { 1584 | target: root 1585 | color: "yellow" 1586 | } 1587 | } 1588 | ] 1589 | 1590 | MouseArea { 1591 | id: ma 1592 | anchors.fill: parent 1593 | hoverEnabled: true 1594 | } 1595 | 1596 | Label { 1597 | id: lb 1598 | anchors.centerIn: parent 1599 | } 1600 | } 1601 | 1602 | // MyItem.qml 1603 | Item { 1604 | MyButton { 1605 | id: btn 1606 | text: "Not Hovering" 1607 | // The states of the original component are not actually overwritten. 1608 | // The new state is added to the existing states. 1609 | states: [ 1610 | State { 1611 | when: btn.hovered 1612 | 1613 | PropertyChanges { 1614 | target: btn 1615 | text: "Hovering" 1616 | } 1617 | } 1618 | ] 1619 | } 1620 | } 1621 | ``` 1622 | When you assign a new value to `states` or any other `QQmlListProperty`, the new value does not 1623 | overwrite the existing one but adds to it. In the example above, the new state is added to the 1624 | existing list of states that we already have in `MyButton.qml`. Since we can only have one active 1625 | state in an item, our hover state will be messed up. 1626 | 1627 | In order to avoid this problem, create your top-level state in a separate item or use a 1628 | `StateGroup`. 1629 | 1630 | ```qml 1631 | Rectangle { 1632 | id: root 1633 | 1634 | property alias text: lb.text 1635 | property alias hovered: ma.containsMouse 1636 | 1637 | color: "red" 1638 | 1639 | MouseArea { 1640 | id: ma 1641 | anchors.fill: parent 1642 | hoverEnabled: true 1643 | } 1644 | 1645 | Label { 1646 | id: lb 1647 | anchors.centerIn: parent 1648 | } 1649 | 1650 | // A State group or 1651 | StateGroup { 1652 | states: [ 1653 | State { 1654 | when: ma.containsMouse 1655 | 1656 | PropertyChanges { 1657 | target: root 1658 | color: "yellow" 1659 | } 1660 | } 1661 | ] 1662 | } 1663 | 1664 | // another item 1665 | Item { 1666 | states: [ 1667 | State { 1668 | when: ma.containsMouse 1669 | 1670 | PropertyChanges { 1671 | target: root 1672 | color: "yellow" 1673 | } 1674 | } 1675 | ] 1676 | } 1677 | } 1678 | ``` 1679 | 1680 | With this change, the button will both change its color and text when the mouse is hovered above it. 1681 | 1682 | 1683 | # Visual Items 1684 | 1685 | Visual items are at the core of QML, anything that you see in the window (or don't see because of 1686 | transparency) are visual items. Having a good understanding of the visual items, their relationship 1687 | to each other, sizing, and positioning will help you create a more robust UI for your application. 1688 | 1689 | ## VI-1: Distinguish Between Different Types of Sizes 1690 | 1691 | When thinking about geometry, we think in terms of `x`, `y`, `width` and `height`. This defines 1692 | where our items shows up in the scene and how big it is. `x` and `y` are pretty straightforward but 1693 | we can't really say the same about the size information in QML. 1694 | 1695 | There's 2 different types of size information that you get from various visual items: 1696 | 1697 | 1. Explicit size: `width`, `height` 1698 | 2. Implicit size: `implicitWidth`, `implicitHeight` 1699 | 1700 | A good understanding of these different types is important to building a reusable library of 1701 | components. 1702 | 1703 | ### Explicit Size 1704 | 1705 | It's in the name. This is the size that you explicitly assign to an `Item`. By default, `Item`s do 1706 | not have an explicit size and its size will always be `Qt.size(0, 0)`. 1707 | 1708 | ```qml 1709 | // No explicit size is set. You won't see this in your window. 1710 | Rectangle { 1711 | color: "red" 1712 | } 1713 | 1714 | // Explicit size is set. You'll see a yellow rectangle. 1715 | Rectangle { 1716 | width: 100 1717 | height: 100 1718 | color: "yellow" 1719 | } 1720 | ``` 1721 | 1722 | ### Implicit Size 1723 | 1724 | Implicit size refers to the size that an `Item` occupies by default to display itself properly. 1725 | This size is not set automatically for any `Item`. You, as a component designer, need to make a 1726 | decision about this size and set it to your component. 1727 | 1728 | The other thing to note is that [Qt internally 1729 | knows](https://github.com/qt/qtdeclarative/blob/dev/src/quick/items/qquickitem.h#L418) if it has an 1730 | explicit size or not. So, when an explicit size is not set, it will use the implicit size. 1731 | 1732 | ```qml 1733 | // Even though there's no explicit size, it will have a size of Qt.size(100, 100) 1734 | Rectangle { 1735 | implicitWidth: 100 1736 | implicitHeight: 100 1737 | color: "red" 1738 | } 1739 | ``` 1740 | 1741 | ----- 1742 | 1743 | Whenever you are building a reusable component, never set an explicit size within the component but 1744 | instead choose to provide a sensible implicit size. This way, the user of your components can freely 1745 | manipulate its size and when they need to return to a default size, they can always default to the 1746 | implicit size so they don't have to store a different default size for the component. This feature 1747 | is also very useful if you want to implement a resize-to-fit feature. 1748 | 1749 | When a user is using your component, they may not bother to set a size for it. 1750 | 1751 | ```qml 1752 | CheckBox { 1753 | text: "Check Me Out" 1754 | } 1755 | ``` 1756 | 1757 | In the example above, the check box would only be visible If there was a sensible implicit size for 1758 | it. This implicit size needs to take into account its visual components (the box, the label etc.) so 1759 | that we can see the component properly. If this is not provided, it's difficult for the user of your 1760 | component to set a proper size for it. 1761 | 1762 | ## VI-2: Be Careful with a Transparent `Rectangle` 1763 | 1764 | `Rectangle` should never be used with a transparent color except when you need to draw a border. 1765 | This is especially true if you are using a `Rectangle` as part of a delegate that's supposed to be 1766 | created in a batch. 1767 | 1768 | Drawing transparent/translucent content takes more time because translucency requires blending. 1769 | Opaque content is optimized better by the renderer. 1770 | 1771 | In order to avoid paying the penalty, look for ways that you can defer the use of a transparent 1772 | `Rectangle`. Maybe you can show it on hover, or during certain events and set it to invisible when 1773 | it's no longer needed. Alternatively, you can put the `Rectangles` in an asynchronous `Loader`. 1774 | 1775 | Here's a sample QML code to demonstrate the difference between using an opaque rectangle and a 1776 | transparent one when it comes to the creation time of these components. 1777 | 1778 | ```qml 1779 | Window { 1780 | visible: true 1781 | 1782 | Row { 1783 | Button { 1784 | text: "Rect" 1785 | onClicked: { 1786 | console.time("Rect") 1787 | rprect.model = rprect.model + 10000 1788 | console.timeEnd("Rect") 1789 | } 1790 | } 1791 | 1792 | Button { 1793 | text: "Transparent" 1794 | onClicked: { 1795 | console.time("Transparent") 1796 | rptrans.model = rptrans.model + 10000 1797 | console.timeEnd("Transparent") 1798 | } 1799 | } 1800 | 1801 | Button { 1802 | text: "Transparent Loader" 1803 | onClicked: { 1804 | console.time("Transparent Loader") 1805 | rploader.model = rploader.model + 10000 1806 | console.timeEnd("Transparent Loader") 1807 | } 1808 | } 1809 | 1810 | Button { 1811 | text: "Translucent" 1812 | onClicked: { 1813 | console.time("Translucent") 1814 | rptransl.model = rptransl.model + 10000 1815 | console.timeEnd("Translucent") 1816 | } 1817 | } 1818 | 1819 | Button { 1820 | text: "Reset" 1821 | onClicked: { 1822 | rprect.model = 0 1823 | rptrans.model = 0 1824 | rptransl.model = 0 1825 | rploader.model = 0 1826 | } 1827 | } 1828 | } 1829 | 1830 | Repeater { 1831 | id: rptrans 1832 | model: 0 1833 | delegate: Rectangle { 1834 | width: 10 1835 | height: 10 1836 | color: "transparent" 1837 | } 1838 | } 1839 | 1840 | Repeater { 1841 | id: rptransl 1842 | model: 0 1843 | delegate: Rectangle { 1844 | width: 10 1845 | height: 10 1846 | opacity: 0.5 1847 | color: "red" 1848 | } 1849 | } 1850 | 1851 | Repeater { 1852 | id: rprect 1853 | model: 0 1854 | delegate: Rectangle { 1855 | width: 10 1856 | height: 10 1857 | color: "red" 1858 | } 1859 | } 1860 | 1861 | Repeater { 1862 | id: rploader 1863 | model: 0 1864 | // This will speed things up. You can defer the creation of the rectangle to when it makes 1865 | // sense and since it's asynchronous it won't block the UI thread. 1866 | delegate: Loader { 1867 | asynchronous: true 1868 | sourceComponent: Rectangle { 1869 | width: 10 1870 | height: 10 1871 | opacity: 0.5 1872 | color: "transparent" 1873 | } 1874 | } 1875 | } 1876 | } 1877 | ``` 1878 | 1879 | When you run this example for the first time and create solid rectangles, you'll notice that the 1880 | creation is pretty fast. If you close it and run it again, but this time create transparent or 1881 | translucent ones you'll see that the time reported does not actually differ that much from the 1882 | solid rectangle. 1883 | 1884 | The real problem starts presenting itself when you are creating new transparent items when there's 1885 | already rectangles on the scene. Try creating first the solid ones and then the transparent ones. 1886 | You'll see that the time difference is very noticeable. 1887 | 1888 | Please note that this will not matter that much when you are drawing a few rectangles here and 1889 | there. The problem will present itself when you are using translucency in the context of a delegate 1890 | because there can potentially be creating thousands of these rectangles. 1891 | 1892 | See also: [Translucent vs Opaque](https://doc.qt.io/qt-5/qtquick-performance.html#translucent-vs-opaque) 1893 | --------------------------------------------------------------------------------