Quantcast
Channel: meetmeego.org
Viewing all articles
Browse latest Browse all 10

Tutorial: Qt, QML und App Entwicklung – Models und ListView

$
0
0

Seit unserem letzten Tutorial ist sehr viel mehr Zeit ins Land gegangen als ich ursprünglich geplant hatte. Auf der anderen Seite ist mir erst später klar geworden wie aufwendig die Übung war – zumindest wenn man wirklich beide Varianten implementieren wollte. Dabei habe ich mir auch überlegt den eigentlichen Plan zu kippen und heute die ListView einzuführen anstelle des optischen Tunings. Keine Angst, die Optik machen wir vielleicht auch noch ausführlich. Wie dem auch sei, heute geht es weiter und zunächst werden wir die Lösung für die “Hausaufgabe” angehen. Nach dem Klick gehts los.

Wie ein Settings-Menü entsteht

Bevor wir richtig loslegen ein Hinweis: Der QtCreator, bzw. das gesamte SDK hat ein Update erfahren, welches es sich zu beziehen wirklich lohnt, vor allem in Hinblick auf Harmattan soll eine Menge verbessert worden sein. Auch wer sein Hausaufgabe nicht gemacht hat, sollte sich den Teil durchlesen – es gibt neues zu lernen. :D

Wie oben schon geschrieben entpuppte sich die Hausaufgabe etwas aufwendiger als zunächst angenommen. Jedenfalls dürfte der Anfang noch völlig einfach gewesen sein. Zunächst brachen wir wie gewohnt einen Button, nämlich den der uns zum Einstellungs-Menü bringt. Hier kann man eigentlich wunderbar schnell mit copy&paste arbeiten, schnell den Reset-Button genommen, editiert und die Sache passt:

Button {

onButtonClicked: {
container.state = “settings_dialog”
}

Wirklich Interessant ist eigentlich nur, was bei einem Klick auf einen Button (der übrigens wiedermal unser angefertigtes Element ist) passieren soll. Hier wird, wie wir dank der letzten Sitzung erkennen können, ein State aufgerufen, nämlich jener mit dem Namen “settings_dialog”. Damit dieser Aufruf später auch ohne Probleme funktioniert, werden wir ihn jetzt erstmal erstellen. Dazu wechseln wir in den oberen Bereich unserer main.qml wo wir schon unser Reset-Menü als state beschrieben haben und ergänzen die states Eigentschaft in eckigen Klammern bis sie so aussieht:

states: [
State {
name: "reset_dialog"
PropertyChanges {
target: reset_dialog
x: 0
}

PropertyChanges {
target: counter
enabled: false
}
},

State {
name: "settings_dialog"
PropertyChanges {
target: settings_dialog
x: 0
}
PropertyChanges {
target: counter
enabled: false
}
}
]

Auch hier reicht fast copy&paste aus. Einige Details müssen angepasst werden, außerdem braucht es nach der schließenden } Klammer des ersten State-Elements ein kleines Komma, damit der zweite State auch akzeptiert wird. Wer nicht selbst darauf gekommen ist, dem hat/hätte die QtCreator Hilfe mal wieder wunderbar helfen können. ;) Soweit so gut, was jetzt noch fehlt ist das Dialog-Fenster an und für sich. Dazu erstellen wir uns zunächst wie schon vorher (Rechtsklick-Hinzufügen) eine neue QML Datei und benennen sie – bei mir ist es ein “Dialog2″ geworden – leider nicht sonderlich clever, später sollten eure Elemente möglichst Namen tragen mit denen ihr sie auch “wieder erkennt” bzw. die ihre Funktion ordentlich beschreiben. Die extra QML Datei erstellen wir, da wir es ja als QtComponent umsetzen wollten, wie bereits mehrfach erwähnt ist die Arbeitsweise für Harmattan ganz ähnlich. Den Dialog selbst können wir zunächst an unserem Reset-Dialog orientieren. Wichtig ist eigentlich nur den x-Wert so zu ändern, dass das Fenster wieder “am Rand anliegt”. Die eigentlich Kür dürfte jetzt gekommen sein, wer Schwierigkeit 1 schaffen wollte musste einen Weg finden die Schritte 5 – 10 darzustellen. Die mit Sicherheit schnellste Variante ist folgende: 5 Buttons! Genau…ganz schlicht und einfach hab ich 5 Buttons nebeneinander angeordnet und mit den Schritten 10,20,30,40,50 beschriftet.

Dabei bietet sich eine wunderbare Möglichkeit zum einen das Ankern zu üben, auf der anderen Seite kann ich euch so auch noch einen sehr schönen Kniff zeigen – selbsterstellte properties/Eigenschaften. Ihr habt in QML nämlich die Möglichkeit Eigenschaften selbst zu erstellen, egal welcher Typ und welcher Wert! Also lasst uns gleich zu Beginn des neuen Dialogs folgende Zeile schreiben:

property int buttontopMargin: 275

Was die Macht? Ganz einfach: zunächst sagen wir QML es soll ein property erstellen, anschließend geben wir den Typen an, hier ein Integer – also ein runder Zahlenwert, gefolgt wird das Ganze von seinem Namen und nach dem Doppelpunkt folgt sein Wert. Im übrigen ist es nicht notwendig einen Wert zu vergeben – vielleicht soll eure Eigenschaft ja erst später einen annehmen. Bei den Typen habt ihr ebenfalls eine große Auswahl zur Hand, in der Hilfe könnt ihr euch unter “QML Basic Types” einen Überblick verschaffen.

Wer sich nun fragt wozu wir dieses Property festgelegt haben – damit können wir ganz bequem die fünf Buttons positionieren. Denn wenn man als  Wert für deren topMargin den Wert buttontopMargin angibt landen alle Buttons auf einer Höhe von 275 Pixeln, der Vorteil besteht darin, dass wir zukünftig nur noch den Wert oben, also 1x ändern müssen, aber alle Buttons verrückt werden. Andere Lösungen sind auch denkbar, z.B. mit Ankern, oder zusammengefasst als Item, oder was euch sonst noch einfällt – hier geht es vor allem um das Beispiel.

Der Rest ist keine große Zauberei mehr, erstellt einfach die fünf Buttons und bringt sie in Reih und Glied. Was nun noch fehlt ist, dass die Buttons auch wirklich die Zahl unseres Hauptfensters ändert. So sieht meine Lösung im onClicked Bereich dafür aus:

onButtonClicked: {zahl.text = set50.text}

Zur Erklärung, set50 ist der Name des Buttons – der Text des Buttons ist ja bekanntlich “50″ und zahl.text ist als property alias umgesetzt. Wer sich daran nicht mehr erinnern kann sollte einfach nochmal die Lektion zu den QtComponents ansehen. Den gesamten Code findet ihr auch wie immer im Anhang, also könnt Ihr dort nochmal vergleichen, euch inspirieren lassen oder eben abgucken. ;)

Die Lösung für Schwierigkeit 2 oder 3, wollen wir nun auch noch besprechen. Aller Beginn ist wieder mal einfach, denn zunächst erstellen wir ein Rectangle Element, welches das Aussehen einer Eingabe-Zeile hat. Nun wird es Interessant, wie in meinem Tipp schon angedeutet: TextInput ist jenes Element das wir nun benötigen. Bei mir ist es dem Rectangle untergeordnet und  mein Code sieht so aus:

TextInput {
id: input
anchors.fill: parent
font.pixelSize: 50
maximumLength: 10
validator: IntValidator()
}

Insgesamt ist das Element super einfach zu verwenden. Es muss aber beachtet werden, dass es unsichtbar ist, also brauchen wir eine “Zeile” – unser Rectangle, welches wir aber selbst erstellen müssen. Da wir das TextInput dem Rectangle untergeordnet haben, können wir nun mit einem anchors.fill: parent das gesamte Rectangle ausfüllen und schon haben wir unsere Eingabezeile zusammen. Wie zu sehen wurde die Schriftgröße angepasst, außerdem habe ich eine maximale Länge festgelegt von 10 Zeichen, mehr dürfte wohl niemand zu Zählen haben.Weiterhin könnt Ihr eine validator Eigenschaft entdecken, sie stellt sicher, dass die Eingabezeile auch nur Zahlen akzeptiert – denn man sollte bedenken das User immer alles ausprobieren, oder auch mal Fehler machen. Also wären Buchstaben in unserer Zahl ziemlich blöd.

Bleibt nur noch eine Sache offen: Die Zahl aus unserer Zeile als Zählerzahl zu setzen. Dafür hab ich einfach einen weiteren Button erstellt, der direkt an unsere Input-Zeile grenzt. Dazu der folgende Code:

Button {
id: inputOK
anchors.top: parent.bottom
anchors.topMargin: 5
height: 50
width: 100
text : “Ok”

onButtonClicked: {zahl.text = input.text}
}

Wie man sehen kann ist die Lösung fast schon “billig”. Durch unseren validator wird die Eingabe auch nur übernommen, wenn ausschließlich Zahlen auf/in unserer Input-Zeile stehen. Das war es eigentlich schon. Was dem Menü nun noch fehlt ist ein “Ok” Button, der den State wieder auf unser normales “App-Fenster” ändert. Wie erwähnt – im Anhang ist der gesamte Code, also werft ruhig mal einen Blick drauf/darüber.

Eine Anmerkung sei noch gemacht: es empfiehlt sich die States aus Performance Gründen unsichtbar zu machen, z.B. mit dem bekannten opacity. Die “Sichtbarwerdung” könntet ihr dann mit dem/der State-auslösenden Button/Fläche verbinden, oder als PropertyChanges, wenn Ihr die States gestaltet.

ListView und Model – unsere neuen QML Freunde

Wenn man mal sein Smartphone, z.B. ein N9 in die Hand nimmt und sich so die Apps ansieht, wird man feststellen, dass einem eigentlich wie am Fließband Listen über den weg laufen. Ganze egal ob nun Kontakte, Musik, Anruf-Historie, SMS, Mails, Suche oder sonst irgendwas, jedes mal braucht man Listen, also wollen auch wir uns damit befassen. In Harmattan wird euch zumindest die Arbeit abgenommen, wenn es darum geht Auswahl-Menüs zu erstellen. Aber jetzt schlag ich vor: “in medias res”, wie es die alten Römer gern ausdrückten. ;)

Das ListView-Element

Alles beginnt mit einem ListView Element. Wie der Name schon andeutet wird es einfach an jene Stelle gesetzt an der, die Liste später erscheinen soll. Die ListView ist, wie auch das TextInput-Element unsichtbar, wer also einen Hintergrund möchte, muss sich also wieder etwas einfallen lassen.

Also wollen wir uns ein neues Projekt erstellen und das ganze doch einfach mal austesten. Dafür ist aber zunächst ein kurzer theoretischer Exkurs notwendig. Wenn ihr nämlich eine ListView einsetzen wollt braucht es ein außerdem ein “Model”. In dem sogenannten Model stehen eigentlich nur jene Elemente, welche später in eurer Liste auftauchen sollen!

Also erstellen wir uns dazu zunächst ein Model, also erstellt eine QML Datei die “Rennfahrer.qml” heißt. Der Inhalt sieht folgender Maßen aus:

import QtQuick 1.0

ListModel {
ListElement {
name: “Sebastian Vettel”
team: “Red Bull”
}
ListElement {
name: “Mark Webber”
team: “Red Bull”
}

ListElement {
name: “Lewis Hamilton”
team: “McLaren”
}

ListElement {
name: “Jenson Button”
team: “McLaren”
}

ListElement {
name: “Fernando Alonso”
team: “Ferrari”
}

ListElement {
name: “Felipe Massa”
team: “Ferrari”
}
}

Auf den ersten Blick wirkt das ganze vielleicht etwas verwirrend, doch eigentlich ist es nicht so schlimm. Unserer QML Datei startet wie bekannt mit einem Import, anschließend ist das grundlegende Element ein ListModel Element. Wir erstellen wie der Name schon angibt ein Model für unsere Liste. Untergeordnet sind nun ListElement Elemente, also die Elemente unseres Models und damit unserer späteren Liste. Diese verfügen über Eigenschaften, die nicht vorgegeben sind sondern frei benannt werden können, also neben name und team könnten wir auch die Eigenschaften Größe, Gewicht, Augenfarbe, Nationalität, gestriges Mittag oder was auch immer festhalten. Natürlich sollten die Eigenschaften sinnvoll benannt werden um den Code a) lesbar zu halten b) um sich gut/besser daran erinnern zu können.

Wir speichern unsere neue QML ab und wechseln zurück zur main.qml, dort schreiben wir folgenden Code:

import QtQuick 1.0

Rectangle {
width: 480
height: 800

ListView {
id: rennfahrerListe
anchors.fill: parent
model: Rennfahrer{}
delegate: Text {text: name+”<br>”+team}
}
}

Wie immer import von QtQuick, anschließend, ein Rectangle als Hauptelement mit der Display-Größe des Nokia N900 – jede andere ist natürlich auch möglich. ;) Und im Anschluss folgt unsere ListView, man kann wie immer eine id vergeben, außerdem kann sie mit anchor beliebig platziert werden. Anschließend müssen wir das model wählen in unserem Fall das eben erstellte. Wir fügen es als Wert der Eigenschaft ein wie wir auch vorher schon immer entsprechende QML Files eingefügt haben: ihren Namen gefolgt von {}. Der nun folgende Punkt ist sehr wichtig, es handelt sich um die Eigenschaft delegate, diese “gestaltet” quasi unsere ListView im Beispiel hier habe ich mich dafür entschieden sie soll das ganze als Text-Element darstellen deren text Eigenschaft

name+”<br>”+team

lautet. Dem einen oder anderen geht das Licht sicher schon auf: name und team stehen für die Werte in unserem Model, daher werden diese Teile auch nicht in Anführungsstrichen angegeben, sondern einfach so. Der Part dazwischen ist gleich mehrfach interessant, zunächst sehen wir + Zeichen, diese verbinden wie in JavaScript z.B. Strings miteinander. Also würde “Hallo”+”Welt” ebenso HalloWelt ergeben, als würde man es zusammen schreiben. Ein weiterer Vorteil von QML bzw. Qt allgemein ist,dass es HTML Code ohne weiteres in Texten zulässt und damit die Möglichkeit gibt diesen zu gestalten. Wer HTML kennt weiß das <br> für einen Zeilenumbruch sorgt. Übrigens gibt die ListView einfach alle Elemente wieder die in dem Model stehen. Immer nach der Gestaltung die durch Delegate vorgegeben ist. :)

Das heißt wenn man den Code jetzt ausführt erhält man einfach jeden Fahrer und sein Team direkt hintereinander weg aufgelistet.

Listen-Design? – delegate hilft!

Das sieht natürlich ziemlich langweilig aus, daher wollen wir unsere delegate-Eigenschaft mal etwas tunen. ;) Wir fügen daher zunächst in unserem Model zum Namen und Team noch die Farbe des Teams hinzu, also im etwa so:

import QtQuick 1.0

ListModel {
ListElement {
name: “Sebastian Vettel”
team: “Red Bull”
farbe: “blue”
}

Hier empfiehlt sich jedoch, nicht “color” als Bezeichnung zu nutzen, denn QML scheint dann Probleme zu bekommen, da der Name color ja schon als Eigenschaft für Elemente “reserviert” ist. Anschließend wechseln wir in unsere main.qml und tunen dort den delegate Bereich. Ziel ist es um jeden Fahrer ein Rechteck zu erzeugen, welches die Farbe des Teams trägt! Ihr dürft nun gern selber loslegen bevor ich euch meine Lösung zeige.

Ich habe es so gelöst:
Mit die rechteckigen Boxen etwas besser wirken, habe ich den Hintergrund des Fensters erstmal auf schwarz geändert, anschließend den folgenden Code als delegate geschrieben:

delegate:
Rectangle {
id: delegated_elements_rec
width: parent.width
height: 80
                color: farbe

Text {
id: delegated_elements
width: parent.width
text: name+”<br>”+team
}
}

Den spannenden Bereich habe ich euch fett gedruckt: color: farbe ! Soll heißen auch hier haben wir wieder auf die Eigenschaft aus dem Model zurückgegriffen, auf diese Art kann man auch beliebige weitere Teams hinzufügen, die dann ebenfalls dank dem delegate korrekt eingesetzt/angezeigt werden. Auf diese Weise können Listen in fast jeder erdenklichen Form mit fast jedem Zweck angezeigt werden.

Listenelemente – klickbar machen und Elemente hinzufügen

Das Anzeigen verschiedener Dinge, die in einer Liste auftauchen sollen ist natürlich sehr praktisch. Oft ist es aber auch nötig, dass das Element einer Liste anklickbar oder auswählbar gemacht werden kann. Hier werden einige sicher schon eine Idee haben – eine MouseArea! Und genau die offenbart sich als sehr einfache Möglichkeit, folglich ergänzen wir unter dem delegated_elements_rec einfach eine MouseArea:

 MouseArea {
id: elements_click_check
anchors.fill: parent
onClicked: { console.log(name+” “+team) }
}

Wie dem Code klar zu entnehmen ist, spannt sich die MouseArea nun über das gesamte (farbige) Rechteck und gibt bei einem Klick den Namen und das Team zurück. Wenn ihr das im Simulator testet, könnt ihr in der unteren Zeile des QtCreator, in der auch Fehler gemeldet werden, nun ganz eindeutig lesen was ihr angeklickt habt. Auf diese Weise könnte man das angeklickte Element nun weiterverarbeiten.

Einen wichtigen Aspekt den man sich vor Augen führen sollte ist der folgende: Bisher sorgt die ListView lediglich dafür, dass die Elemente eines Models auf der Oberfläche auftauchen. Im Moment können wir folglich weder Objekte anhängen noch entfernen. Doch ein Blick in die Dokumentation des ListModel verrät uns, dass das auch möglich ist, wie wollen wir jetzt klären.

Anhängen von Objekten:

Um Objekte anhängen zu können sprechen wir unser Model an, damit das Problem frei klappt, sollten wir es mit einer id unseres Wunsches in der main.qml einbinden, z.B. folgender Maßen:

Rennfahrer {id: rennfahrerliste}

für Fälle in denen nur eine einzige Eigenschaft angebunden wird, ist so eine Kurzschreibweise völlig in Ordnung, möglich ist natürlich auch mehr, aber davon sollte man aus Gründen der Code-Lesbarkeit absehen. Eine Ausnahme bilden bestimmte Eigenschaften einer “Familie”, z.B. die anchors oder font Eigenschaften, diese kann man/es empfiehlt sich, sie verkürzt zu schreiben, dies sieht dann so aus:

  anchors {top: parent.top; topMargin: 10; left: parent.left; leftMargin: 10}

Anschließend sollten wir unser ListView Element ein Stück nach untern versetzen, damit wir darüber etwas Platz haben. Macht das wie gehabt mit anchors, versucht dabei die Kurzschreibweise. Über der ListView wollen wir nun einen TextInput erstellen und einen “Button” mit dem wir unserem Model etwas hinzufügen können. Ich habe den folgenden Code verwendet:

Rectangle {
id: inputline_rec
anchors {top: parent.top; topMargin: 10; left: parent.left; leftMargin: 10}

width: 300
height: 50
color: “white”

TextInput {
id: inputline
anchors.fill: parent
//text: “”
}
}

Rectangle {
id: add_button
anchors {top: parent.top; topMargin: 10; left: inputline_rec.right; leftMargin: 30}

width:  50
height: 50
color: “lightgrey”

MouseArea {
id: input_button_click
anchors.fill: parent

onClicked: {
var a = inputline.text
rennfahrerliste.append({“name”:a, “team”: “Lotus Renault”, “farbe”: “green”})
}
}

}

Wie zu sehen ist, habe ich (aus Faulheit) meinem Button keinen Text mehr verpasst, er ist schlicht eine hässliche kleine graue Fläche. Wer möchte könnte dafür natürlich auch unseren dereinst erstellten QtComponent Button wiederverwenden. ;)

Dennoch ist gerade unser “Button” hier ein interessanter Fall. Denn im onClicked Bereich ist das ganze Hinzufügen-Geheimnis enthalten. Mit dem ersten Teil, also:

var a = inputline.text

Wird eine eine Variable erzeugt, var ist dabei ein reseviertes “Codewort”, darauf folgt die Bezeichnung der Variablen, hier ein schnödes a, es könnte aber auch alles andere sein, z.B. inhalt oder neuerName oder wie auch immer. Wie das = Symbol verrät bekommt es den aktuellen Text unseres TextInput Elements verpasst, also das was später eingeben wird.

Die nächste Zeile zeigt dann, wie man Dinge hinzufügt:

rennfahrerliste.append({“name”:a, “team”: “Lotus Renault”, “farbe”: “yellow”})

Man spricht das Model an und gibt ihm mit .append() den Auftrag etwas einzufügen. Dann folgt quasi ein weiteres ListModel dazu verwendet man {} Klammern und eben jene Positionen die auch die anderen Elemente tragen. Bei name folgt als Wert dann unser a, welches wir ja gerade davor definiert haben. Die anderen Elemente, nämlich team und farbe habe ich hier  schon vorgegeben. Möglich wäre es aber selbstverständlich diese ebenfalls mit ihren (eigenen) TextInput Elementen einzulesen und so zu bestimmen.

Solltet ihr z.B. die Farbe einfach weglassen, wird euch die Konsole zwar vollmeckern, dass sie keine Farbe setzen konnte, mehr sollte aber nicht passieren.

Fazit und Übungen

Ihr habt heute eine Menge neues lernen können, dabei ist es Anfangs nur schwer zu verdauen und nicht ganz einfach. Daher will ich euch mit diesen neuen Erkenntnissen für heute entlassen. Einen wichtigen Punkt der noch fehlt ist das entfernen von Objekten. Die Speicherung der Listeninhalte empfiehlt sich in die eigentliche Programm Logik zu packen, folglich mit C++ / Python u.ä. zu realisieren. Denn dort sind die Mittel und Wege recht einfach (zumindest für Python kann ich das mit Bestimmtheit sagen).

Natürlich haben wir wie immer ein paar Hausaufgaben bzw. Übungen die ihr erfüllen könnt, bzw. solltet:

1. Schreibt die Anwendung so um, dass es möglich ist auch die anderen Werte, also den Team-Namen und die Farbe zu bestimmen.

2. Sorgt dafür, dass bei ausbleibender Vorgabe (z.B. kein Name, keine Farbe) ein Standard-Wert geladen wird.

3. Schreibt eine eigene kleine Anwendung, die auf der linken Seite eine Liste anbietet mit Farben. Diese sollen durch Text und ein kleines Rechteck mit dieser Farbe dargestellt werden. Auf der rechten Seite der Anwendung findet sich ein großes Rechteck, welches je nach Wahl entsprechend gefärbt werden soll!

4. Wer möchte versucht sich daran Elemente wieder entfernbar zu machen.

Ich wünsche Viel Spaß, über Feedback jeder Art würde ich mich freuen. Fragen kommen in die Kommentare. ;)

————————————–

Anbei wieder der finale Code (nur main.qml):

import QtQuick 1.0

Rectangle {
id: appWindow
width: 480
height: 800
color: “black”

Rennfahrer {id: rennfahrerliste}

Rectangle {
id: inputline_rec
anchors {top: parent.top; topMargin: 10; left: parent.left; leftMargin: 10}

width: 300
height: 50
color: “white”

TextInput {
id: inputline
anchors.fill: parent
}
}

Rectangle {
id: add_button
anchors {top: parent.top; topMargin: 10; left: inputline_rec.right; leftMargin: 30}

width:  50
height: 50
color: “lightgrey”

MouseArea {
id: input_button_click
anchors.fill: parent

onClicked: {
var a = inputline.text
rennfahrerliste.append({“name”:a, “team”: “Lotus Renault”, “farbe”: “yellow”})
}
}

}

ListView {
id: rennfahrerListe
anchors.top: parent.top
anchors.topMargin: 100
width: parent.width
height: 500
model: rennfahrerliste

delegate:
Rectangle {
id: delegated_elements_rec
width: parent.width
height: 80
color: farbe

Text {
id: delegated_elements
width: appWindow.width
text: name+”<br>”+team
}

MouseArea {
id: elements_click_check
anchors.fill: parent
onClicked: {console.log(name+” “+team)}
}
}
}
}

 


Viewing all articles
Browse latest Browse all 10

Latest Images

Trending Articles





Latest Images