Verbesserung der Leistung und Optimierung von QML-Anwendungen – Teil 2

Qt QML-Entwicklung
2023-04-13
12 Minuten
Optimieren von QML-Anwendungen

Wie Sie wissen, ist die Leistung ein kritischer Aspekt jeder Anwendung. Wenn Ihre QML-App langsam oder nicht reaktionsfähig ist, werden die Benutzer schnell das Interesse verlieren und sich anderen Alternativen zuwenden. Daher ist es entscheidend, die Leistung Ihrer App zu optimieren und sie so schnell und reaktionsfähig wie möglich zu machen.

Im vorherigen Beitrag zur Verbesserung der Leistung und Optimierung von QML-Apps haben wir darüber gesprochen, warum die Optimierung Ihres Projekts aus geschäftlicher Sicht wichtig ist. Wir haben uns auch die Vorteile von Effizienz- und Geschwindigkeitssteigerungen angesehen, die durch die Verlagerung der Logik von JavaScript nach C++ erreicht werden können. Zudem haben wir die effiziente Verwaltung von Delegates mit den von Qt bereitgestellten Modell-Schnittstellen thematisiert. Da das letztgenannte Thema bereits den UI-Bereich berührt, wollen wir uns weiteren verwandten Themen widmen, die Ihnen helfen können, die Leistung einer QML-App zu verbessern. Tauchen wir ein!

Sehen Sie, was unsere Experten für Sie im Bereich Qt-Entwicklungsdienstleistungen realisieren können.

 

Dynamisches Erstellen der Benutzeroberfläche

Das dynamische Erstellen von Teilen der Benutzeroberfläche ist ein entscheidender Schritt, um Ihre benutzerorientierte Anwendung effizient zu gestalten. Die Hauptidee hinter diesem Ansatz ist, die Anzahl der Objekte im Speicher auf ein Minimum zu reduzieren. Um dieses Problem zu lösen, können wir Teile der Benutzeroberfläche bei Bedarf instanziieren, basierend auf dem, was der Benutzer im jeweiligen Moment benötigt. Lassen Sie mich beschreiben, welche Techniken Sie dafür einfach anwenden können:

 
Common Techniques for Dynamic UI Creation

 

Behalten, was Sie sehen

Die erste Technik ist allgemeiner Natur, sie sagt also nicht direkt, wie Sie die Benutzeroberfläche laden sollen, sondern wann. Wann immer Sie eine neue Seite, ein komplexes Dialogfenster oder ein anderes aufwändiges UI-Element hinzufügen, sollten Sie sich fragen: „Brauche ich es im Speicher, wenn es nicht sichtbar ist?“ Auch wenn ungenutzte Objekte für den Benutzer unsichtbar oder nicht gerendert sind, können sie unnötige Logik auslösen, wie das ständige Aktualisieren von Eigenschaftsbindungen. Das Beibehalten ungenutzter UI-Elemente erhöht zudem den Speicherbedarf der Anwendung.

Eine Faustregel hierbei ist, nur die UI-Objekte zu instanziieren, die aktuell vom Benutzer sichtbar sind und im Moment nicht anderweitig verwendet werden. Dazu können verschiedene Elemente gehören, wie Unterseiten, Pop-ups oder Panels. Natürlich sollte man dies mit Bedacht anwenden, da ich hier keine nicht-visuellen Elemente (wie Timer oder Verbindungen) oder kritische Teile der Benutzeroberfläche berücksichtige. Falls es keine Kompromisse bei der Zeit geben kann, die zwischen der Anforderung des UI-Elements durch den Benutzer und dessen Anzeige vergeht, ist das Beibehalten eines solchen Objekts im Speicher während der gesamten Laufzeit eine gültige Vorgehensweise. Für alle nicht essenziellen Elemente sollten wir jedoch in Betracht ziehen, sie bei Bedarf zu laden.

 

Neuladen von Benutzeroberflächen

Ein häufiger Fall, bei dem ein deutlicher Leistungsunterschied zugunsten des bedarfsorientierten Ladens von Objekten zu erkennen ist, tritt auf, wenn die Sprache in einer mehrsprachigen Anwendung geändert wird, die die Funktion `qsTr` zur Übersetzung verschiedener UI-Teile verwendet. Ein solches Szenario erfordert das Laden einer neuen .qm-Datei mittels QTranslator, deren Installation und das Aufrufen der retranslate-Methode der QML-Engine. Da dies alle Bindungsausdrücke aktualisiert, die Strings mit Übersetzungsmarkierungen verwenden, dauert es umso länger, je mehr Objekte mit übersetzbaren Strings instanziiert sind.

Dabei wird nicht nur die Anzeige der Strings selbst aktualisiert – alle Bindungen, die diese Strings verwenden, werden ebenfalls neu ausgewertet. Darüber hinaus müssen alle Objekte, deren Größe auf Basis des Inhalts berechnet wird, ihre Dimensionen neu berechnen, wenn sie einen übersetzten String enthalten, der aktualisiert wurde. Das Entladen ungenutzter Teile der Benutzeroberfläche verringert die Anzahl der zu überprüfenden Bindungen, wodurch Übersetzungen deutlich schneller aktualisiert werden können.

Nachdem wir nun wissen, welche Teile der Benutzeroberfläche wir vermeiden sollten, sofort zu instanziieren, und warum wir dies tun sollten, konzentrieren wir uns darauf, wie wir solche Objekte dynamisch erstellen können.

 

Loader vs. StackView in Qt Quick

Die beiden gängigsten Methoden zur dynamischen Erstellung von Teilen der Benutzeroberfläche sind StackView und Loader. Wenn wir Objekte bei Bedarf erstellen, müssen wir bedenken, dass die Erstellung eines Objekts Zeit in Anspruch nehmen kann, insbesondere wenn es komplex ist. Das Hinzufügen komplexer Elemente zu einer StackView kann den Haupt-Thread während der Erstellung blockieren. Deshalb sind Loader weiterhin relevant, da sie Objekte inkubieren können, wenn die Eigenschaft `asynchronous` auf true gesetzt ist, wodurch eine Blockierung des UI-Threads verhindert wird.

QML file

StackView {
id: stack
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: divider.left
}
Button {
id: stackButton
anchors {
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
text: "Push to stack"
onClicked: {
console.time("Stack")
stack.push(nastyComponent)
console.timeEnd("Stack")
}
}
}
Loader {
id: loader
anchors {
top: parent.top
bottom: parent.bottom
left: divider.right
right: parent.right
}
asynchronous: true
active: false
sourceComponent: nastyComponent
Button {
id: loaderButton
anchors {
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
text: "Load component"
onClicked: {
console.time("Loader")
loader.active = true
console.timeEnd("Loader")
}
}
}
Component {
id: nastyComponent
Flickable {
contentHeight: grid.height
Grid {
id: grid
Repeater {
model: 100
delegate: Image {
height: 50
fillMode: Image.PreserveAspectFit
source: "https://upload.wikimedia.org/wikipedia/commons/0/0b/Qt_logo_2016.svg"
sourceSize {
width: 578
height: 424
}
Component.onCompleted: {
let millis = 20
var date = Date.now();
var curDate = null;
do {
curDate = Date.now();
} while (curDate-date < millis);
}
}
}
}
}
}
StackView { id: stack anchors { top: parent.top bottom: parent.bottom left: parent.left right: divider.left } Button { id: stackButton anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter } text: "Push to stack" onClicked: { console.time("Stack") stack.push(nastyComponent) console.timeEnd("Stack") } } } Loader { id: loader anchors { top: parent.top bottom: parent.bottom left: divider.right right: parent.right } asynchronous: true active: false sourceComponent: nastyComponent Button { id: loaderButton anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter } text: "Load component" onClicked: { console.time("Loader") loader.active = true console.timeEnd("Loader") } } } Component { id: nastyComponent Flickable { contentHeight: grid.height Grid { id: grid Repeater { model: 100 delegate: Image { height: 50 fillMode: Image.PreserveAspectFit source: "https://upload.wikimedia.org/wikipedia/commons/0/0b/Qt_logo_2016.svg" sourceSize { width: 578 height: 424 } Component.onCompleted: { let millis = 20 var date = Date.now(); var curDate = null; do { curDate = Date.now(); } while (curDate-date < millis); } } } } } }
StackView {
    id: stack

    anchors {
      top: parent.top
      bottom: parent.bottom
      left: parent.left
      right: divider.left
    }

    Button {
      id: stackButton

      anchors {
        bottom: parent.bottom
        horizontalCenter: parent.horizontalCenter
      }

      text: "Push to stack"

      onClicked: {
        console.time("Stack")
        stack.push(nastyComponent)
        console.timeEnd("Stack")
      }
    }
  }

  Loader {
    id: loader

    anchors {
      top: parent.top
      bottom: parent.bottom
      left: divider.right
      right: parent.right
    }

    asynchronous: true
    active: false
    sourceComponent: nastyComponent

    Button {
      id: loaderButton

      anchors {
        bottom: parent.bottom
        horizontalCenter: parent.horizontalCenter
      }

      text: "Load component"

      onClicked: {
        console.time("Loader")
        loader.active = true
        console.timeEnd("Loader")
      }
    }
  }

  Component {
    id: nastyComponent

    Flickable {
      contentHeight: grid.height

      Grid {
        id: grid

        Repeater {
          model: 100
          delegate:  Image {
            height: 50
            fillMode: Image.PreserveAspectFit
            source: "https://upload.wikimedia.org/wikipedia/commons/0/0b/Qt_logo_2016.svg"
            sourceSize {
              width: 578
              height: 424
            }

            Component.onCompleted: {
              let millis = 20
              var date = Date.now();
              var curDate = null;
              do {
                curDate = Date.now();
              } while (curDate-date < millis);
            }
          }
        }
      }
    }
 }

Wie lange dauert das Laden, nachdem der im obigen QML-Dokument gezeigte Code ausgelöst wurde? Der im Protokoll angezeigte Wert könnte für einige überraschend sein:

 
Instantiate Request Time Benchmark

Warum dauert das Hinzufügen eines Elements zum Stack so lange, während die Funktionalitäten, die nach dem Auslösen des Loaders verwendet werden, sofort funktionieren? Das liegt daran, dass wir einen asynchronen Loader verwendet haben, der beginnt, Quellkomponenten außerhalb des Hauptthreads zu instanziieren. Dadurch wird der Rest der Benutzeroberfläche nicht blockiert, sodass andere Logik verarbeitet werden kann – in diesem Fall wird der Timer unmittelbar nach dem Start gestoppt. Lassen Sie sich davon nicht täuschen: Das Element wird nicht sofort instanziiert, aber das Auslösen des Loaders blockiert den Rest der Logik nicht, sodass andere Anweisungen fortgesetzt werden können. Wie sieht das Timing für die vollständige Instanziierung der Komponente aus? Nach der Modifikation des Codes können wir folgende Ergebnisse sehen:

 
Instantiate Full Component Time Benchmark

In der Realität wird das Erstellen der komplexen Komponente über einen Loader fast genauso lange dauern wie bei der Verwendung von StackView. Das mag auf den ersten Blick nicht beeindruckend wirken, ist jedoch äußerst nützlich. Während der Ladezeit, wenn der asynchrone Loader das Objekt instanziiert, können wir weiterhin andere Logik verarbeiten oder einfach einen Fortschrittsindikator anzeigen. Darüber hinaus können Benutzer weiterhin mit dem Rest der Benutzeroberfläche interagieren, sodass wir ihnen ermöglichen können, andere Funktionen der App zu nutzen, während das komplexe Element geladen wird.

Wir sind jetzt mit Wissen über einige allgemeine Techniken ausgestattet, um die Leistung der UI-Seite zu optimieren. Was halten Sie davon, mehr über spezifische Elemente des Qt-Frameworks zu erfahren, die wir zur Verbesserung der Leistung unserer Anwendung nutzen können?

 

Wie verwendet man Qt.callLater()

Eine der weniger bekannten Funktionen in Qt ist `Qt.callLater()`. Alles, was diese Funktion tut, ist das Vermeiden redundanter Aufrufe einer Funktion oder eines Signals. Dies wird erreicht, indem der Aufruf der Funktion ausgeführt wird, sobald die QML-Engine zur Ereignisschleife zurückkehrt, jedoch nur einmal pro Ereignisschleifeniteration, wenn der Satz von Argumenten derselbe ist. Diese Funktion ist besonders nützlich, wenn Sie vermeiden möchten, dass dieselbe Funktion mehrfach in schneller Folge aufgerufen wird, was zu unnötigem Overhead oder unbeabsichtigtem Verhalten führen könnte. Schauen wir uns an, wie `Qt.callLater()` in der Praxis verwendet wird:

 

Item {
id: example
property int callLaterCounter: 0
property int counter: 0
readonly property int executionTargetCount: 5
function callLaterExample() {
for(let x = 0; x < example.executionTargetCount; x++) {
example.foo()
}
for(let y = 0; y < example.executionTargetCount; y++) {
Qt.callLater(example.bar)
}
console.log("Counter: " + example.counter)
Qt.callLater(()=>{console.log("CallLaterCounter: " + example.callLaterCounter)})
}
function foo() {
console.log("Doing a thing!")
example.counter++
}
function bar() {
console.log("Need to be executed once!")
example.callLaterCounter++
}
Component.onCompleted: {
example.callLaterExample()
}
}
Item { id: example property int callLaterCounter: 0 property int counter: 0 readonly property int executionTargetCount: 5 function callLaterExample() { for(let x = 0; x < example.executionTargetCount; x++) { example.foo() } for(let y = 0; y < example.executionTargetCount; y++) { Qt.callLater(example.bar) } console.log("Counter: " + example.counter) Qt.callLater(()=>{console.log("CallLaterCounter: " + example.callLaterCounter)}) } function foo() { console.log("Doing a thing!") example.counter++ } function bar() { console.log("Need to be executed once!") example.callLaterCounter++ } Component.onCompleted: { example.callLaterExample() } }
Item {
    id: example

    property int callLaterCounter: 0
    property int counter: 0
    readonly property int executionTargetCount: 5

    function callLaterExample() {
      for(let x = 0; x < example.executionTargetCount; x++) {
        example.foo()
      }

      for(let y = 0; y < example.executionTargetCount; y++) {
        Qt.callLater(example.bar)
      }

      console.log("Counter: " + example.counter)
      Qt.callLater(()=>{console.log("CallLaterCounter: " + example.callLaterCounter)})
    }

    function foo() {
      console.log("Doing a thing!")
      example.counter++
    }

    function bar() {
      console.log("Need to be executed once!")
      example.callLaterCounter++
    }

    Component.onCompleted: {
      example.callLaterExample()
    }
 }

In diesem Beispiel haben wir zwei Funktionen, `foo()` und `bar()`. Beide Funktionen werden in einer for-Schleife mehrfach ausgelöst, aber bar() soll in dieser schnellen Abfolge nur einmal ausgeführt werden. Wir können uns vorstellen, dass `bar()` für ressourcenintensive Operationen verantwortlich sein könnte, die keinen Vorteil bringen würden, wenn sie in so kurzer Zeit mehrfach ausgelöst werden.

Nach der Ausführung des Beispiels sehen wir die folgenden Nachrichten in den Protokollen:

 

qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Counter: 5
qml: Need to be executed once!
qml: CallLaterCounter: 1
qml: Doing a thing! qml: Doing a thing! qml: Doing a thing! qml: Doing a thing! qml: Doing a thing! qml: Counter: 5 qml: Need to be executed once! qml: CallLaterCounter: 1
qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Doing a thing!
qml: Counter: 5
qml: Need to be executed once!
qml: CallLaterCounter: 1

Wir können deutlich sehen, dass obwohl `Qt.callLater()` mehrfach ausgelöst wurde, die Funktion `bar()` nur einmal ausgeführt wurde. Die Reduktion der Aufrufe hat somit ihren Hauptzweck erfüllt. Sie haben vielleicht bemerkt, dass wir eine Pfeilfunktion als Parameter mit einem `console.log` übergeben haben, aber es wäre auch möglich, ein JS-Funktionsobjekt zu übergeben. Dies wurde gemacht, um den aktuellen Wert von `callLaterCounter` auszugeben. Hätten wir versucht, ihn sofort auszugeben, wäre er noch nicht aktualisiert worden, da `bar` noch nicht ausgeführt wurde.

Zusätzlich können Sie weitere Argumente an `Qt.callLater()` übergeben. Diese Argumente werden an die aufgerufene Funktion weitergeleitet. Sie müssen jedoch beachten, dass bei der Eliminierung redundanter Aufrufe nur der letzte Satz von Argumenten an die Funktion übergeben wird.

 

Rectangle {
id: backgroundRect
width: 200
height: 200
color: "red"
MouseArea {
anchors.fill: parent
onClicked: {
// Assume multiple clicks happen in quick succession
Qt.callLater(backgroundRect.changeBackgroundColor, "yellow");
Qt.callLater(backgroundRect.changeBackgroundColor, "blue");
Qt.callLater(backgroundRect.changeBackgroundColor, "green");
}
}
function changeBackgroundColor(newColor) {
console.log("Hi! I'm switching to color " + newColor)
backgroundRect.color = newColor;
}
}
Rectangle { id: backgroundRect width: 200 height: 200 color: "red" MouseArea { anchors.fill: parent onClicked: { // Assume multiple clicks happen in quick succession Qt.callLater(backgroundRect.changeBackgroundColor, "yellow"); Qt.callLater(backgroundRect.changeBackgroundColor, "blue"); Qt.callLater(backgroundRect.changeBackgroundColor, "green"); } } function changeBackgroundColor(newColor) { console.log("Hi! I'm switching to color " + newColor) backgroundRect.color = newColor; } }
Rectangle {
      id: backgroundRect
      width: 200
      height: 200
      color: "red"

      MouseArea {
          anchors.fill: parent
          onClicked: {
              // Assume multiple clicks happen in quick succession
              Qt.callLater(backgroundRect.changeBackgroundColor, "yellow");
              Qt.callLater(backgroundRect.changeBackgroundColor, "blue");
              Qt.callLater(backgroundRect.changeBackgroundColor, "green");
          }
      }

      function changeBackgroundColor(newColor) {
        console.log("Hi! I'm switching to color " + newColor)
        backgroundRect.color = newColor;
      }
  }

 

qml example

In diesem Beispiel sehen Sie, dass wir die Funktion `changeBackgroundColor` dreimal in `Qt.callLater` eingewickelt mit verschiedenen Farben aufgerufen haben. Der Rechteckhintergrund hat sich jedoch von Rot zu Grün verändert und dabei die Farben Gelb und Blau durchlaufen.

 

ListView vs. Flickable & Repeater

Wie bei den meisten Features gibt es auch hier mehrere Möglichkeiten, dynamisch erstellte und scrollbare Listen in Ihrer Anwendung zu implementieren. Die Unterschiede zwischen den Ansätzen beschränken sich jedoch nicht nur auf die Einfachheit oder vorhandene Funktionen, sondern können sich auch erheblich auf die Leistung auswirken. Ein solches Beispiel ist der Vergleich zwischen der Verwendung von Flickable mit einem verschachtelten Repeater und ListView. Obwohl beide Ansätze ähnliche Ergebnisse liefern können, gibt es wesentliche Unterschiede hinsichtlich ihrer Auswirkungen auf die Leistung der Anwendung.

 

ListView

ListView ist eine Komponente, die eine komfortable Möglichkeit bietet, eine große Anzahl von Elementen in einer scrollbaren Liste anzuzeigen. Sie erlaubt die Definition eines Delegates, der verwendet wird, um Einträge im bereitgestellten Modell zu visualisieren. Darüber hinaus kann die ListView über angehängte Eigenschaften mit der Liste interagieren. Das Datenmodell selbst kann auf vielen unterstützten Typen basieren, wie z. B. ListModel, XmlListModel oder benutzerdefinierte C++-Modelle, die von QAbstractItemModel erben.

Ein entscheidender Vorteil von ListView ist die automatische Verwaltung von Positionierung, Scrollen und Recycling der instanziierten Delegates. Dies bedeutet, dass selbst bei großen Datensätzen nur eine kleine Anzahl von Elementen gleichzeitig instanziiert bleibt, was die Ressourcennutzung erheblich reduziert.

 

Flickable mit verschachteltem Repeater

Eine alternative Methode zur Anzeige einer Serie von Elementen in einer scrollbaren Liste ist die Kombination mehrerer QML-Typen, um alle notwendigen Funktionen zu einer quasi-ListView bereitzustellen. In diesem Szenario wird Flickable wegen seiner Scroll-Funktionalitäten verwendet, die es Benutzern ermöglichen, Inhalte zu durchblättern, die nicht auf den Bildschirm passen. Obwohl ListView von Flickable abgeleitet ist, bietet der Basistyp keine Mechanismen zur dynamischen Objektinstanziierung oder Verwaltung ihrer Lebensdauer.

Objekte, die die Einträge im Modell repräsentieren, werden mit Repeater instanziiert. Dieser füllt das Flickable basierend auf dem bereitgestellten QML- oder C++-Modell. Ein weiterer wichtiger Bestandteil ist ein Layout-Handler, da weder Repeater noch Flickable die Positionierung ihrer Kinder eigenständig verwalten. Aus diesem Grund wird der Repeater in der Regel in ein Layout wie ColumnLayout für vertikale Listen oder RowLayout für horizontale Listen eingebettet.

Nachdem alle diese Elemente zusammengefügt wurden, hat man eine Liste, die scrollbar ist und ein Modell darstellt, das die Daten enthält. Alles scheint in Ordnung, und die Änderungen werden im Repository gespeichert. Am nächsten Tag bemerken die Entwickler jedoch, dass die Leistung der Anwendung erheblich abnimmt, wenn ein großer Datensatz dem neuen Listenansatz bereitgestellt wird. Warum passiert das?

 

Die richtige Herangehensweise wählen

Während es Szenarien gibt, in denen die Verwendung von Flickable mit Repeater sinnvoll sein könnte, gilt dieser Ansatz als Anti-Pattern für dynamische ListViews. Der Grund dafür liegt in der fehlenden Mechanik von Flickable und Repeater zur Verwaltung der Lebensdauer der enthaltenen Objekte basierend auf ihrer Sichtbarkeit.

Delegates, die vom Repeater instanziiert werden, werden beim Scrollen nicht zerstört, wenn sie sich außerhalb des Flickable-Viewports befinden. Sie können daher nicht bei Bedarf neu geladen werden, wenn sie wieder in den Viewport gelangen. Alle dynamisch erstellten Objekte werden sofort instanziiert, wenn Repeater und Flickable konstruiert werden, und bleiben während der gesamten Lebensdauer ihrer Eltern im Speicher.

Dieses Verhalten ist bei kleinen Datensätzen, bei denen nur wenige Delegates erstellt werden, möglicherweise unauffällig. Je mehr Einträge jedoch im Modell vorhanden sind, desto stärker wird die Leistung beeinträchtigt, da mehr Objekte, Bindungen, Animationen und andere Elemente von der Engine verarbeitet werden müssen.

 

Das Verhalten der gleichen Liste, die mit ListView implementiert wurde, unterscheidet sich grundlegend. Da dieser Typ ein System zur Verwaltung des Lebenszyklus von Delegates basierend auf ihrer Position im Viewport bereitstellt, wird die Leistung nicht von der Anzahl der Einträge im Modell beeinträchtigt. ListView instanziiert nur die Delegates, die sich derzeit im Viewport befinden, sowie einige Delegates außerhalb des Viewports als Puffer. Während der Benutzer durch die App scrollt, werden neue Delegates instanziiert, um sicherzustellen, dass die angezeigten Daten aktualisiert werden. Gleichzeitig werden Elemente, die nicht mehr sichtbar sind und das Ende des Puffers erreichen, de-konstruiert.

Ein solcher Ansatz ermöglicht es, die Anzahl der im Speicher befindlichen Objekte unter Kontrolle zu halten. Mit der cacheBuffer-Eigenschaft können Sie außerdem steuern, wie viele Delegates in den Puffer geladen werden.

 

Zusammenfassung

In diesem Blogpost wurden verschiedene Techniken zur Optimierung der QML-Leistung untersucht. Wir haben die Auswirkungen der dynamischen UI-Erstellung analysiert und die Vor- und Nachteile des asynchronen Loaders und StackView für die bedarfsorientierte Objekterstellung verglichen. Darüber hinaus haben wir untersucht, wie Qt.callLater() redundante Funktionsaufrufe reduzieren und die Gesamtleistung verbessern kann. Schließlich haben wir die Unterschiede zwischen der Implementierung dynamischer Listen mit ListView und Flickable mit einem verschachtelten Repeater betrachtet und die jeweiligen Vorteile der ListView hervorgehoben.

Unabhängig davon, ob Sie ein erfahrener QML-Entwickler sind oder gerade erst anfangen, hoffe ich, dass dieser Blogpost Ihnen wertvolle Einblicke, Techniken und Beispiele geliefert hat, um die Leistung Ihrer App zu optimieren, die richtigen Werkzeuge für die jeweilige Aufgabe auszuwählen und eine bessere Benutzererfahrung zu schaffen. Wir laden Sie herzlich ein, Ihr Feedback zu teilen oder ein paar ermutigende Worte zu hinterlassen, wenn Ihnen dieser Beitrag gefallen hat.

 

Scythe-Studio - Blog Redactors

Scythe Studio Blog Redactors

Brauchen Sie Qt QML-Entwicklungsdienste?

service partner

Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.

Entdecken Sie unsere Fähigkeiten!

Neueste Artikel

[ 156 ]