Getting Started Programming with QML/ro

From Qt Wiki
Revision as of 16:12, 4 May 2015 by AutoSpider (talk | contribs) (AutoSpider moved page GettingStartedQMLRomanian to Getting Started Programming with QML/ro: Localisation)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.


Introducere în programarea cu QML

Bun venit in lumea QML, limbajul declarativ pentru interfațe utilizator. În aceast ghid, vom crea un editor de texte folosing QML. După parcurgerea acestui ghid, veți fi gata sa dezvoltați propriile aplicații folosind QML și Qt C+.

Construirea interfeței cu utilizatorul folosind QML

Aplicația pe care o construim este un editor de texte care va încarca, salva și realiza manipulări de text. Acest ghid are două părți. Prima parte implică proiectarea aspectului aplicației și a comportamentului său folosind limbajul declarativ QML. În partea a doua, încarcarea si salvarea fișierelor va fi implementată folosing Qt C. Folosind sistemul Qt Meta-Object, putem expune funcții C+ ca proprietăți pe care elementele QML le pot folosi. Folosind QML și Qt C+, putem decupla logica interfeței grafice de logica aplicației.

http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png

Pentru a rula exemplul de cod QML, porniți qmlviewer oferindu-i fișierul QML drept argument. Porțiunea C+ a acestui tutorial presupune că cititorul are cunoștiințe de bază despre procedurile de compilare Qt.

Definirea unui buton și a unui meniu

Componenta de bază: un buton

Începem editorul de texte prin construirea unui buton. Din punct de vedere funcțional, un buton are o zonă sensibilă la acțiunile mouse-ului și o etichetă. Butoanele execută acțiuni atunci când utilizatorul le apasă.

În QML, elemenul vizual de bază este elementul Rectangle. Elementul Rectangle are proprietăți care controlează aspectul și locația sa.

În primul rând, import QtQuick 1.0 determină qmlviewer să importe elementele QML ce vor fi folosite mai târziu. Aceasta linie trebuie să existe în orice fișier QML. Observați că versiunea modulelor Qt e inclusă in propoziția import.

Acest simplu dreptunghi are un identificator unic, simplebutton, care este asociat proprietății id. Proprietăților elementului Rectangle le sunt asociate valori prin enunțarea proprietății, urmata de două puncte și apoi de valoarea proprietății. În exemplul de cod, culoarea gri este asociată proprietății color a dreptunghiului. Similar, asociem valori lățimii și înălțimii dreptunghiului.

Elementul Text este un câmp text needitabil. Numim acest element Text buttonLabel. Pentru a stabili conținutul câmpului Text ca șir de caractere, asociem o valoare proprietății text. Eticheta este inclusa în elementul Rectangle și pentru a o centra în mijlocul acestuia, atribuim ancorele elementului Text părintelui său, numit simplebutton. Ancorele pot fi asociate cu ancorele altor elemente, permițând simplificarea atribuirilor.

Vom salva codul actual ca SimpleButton.qml. Rularea qmlviewer cu fișierul salvat ca argument va afisa dreptunghiul gri cu o etichetă text.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png

Pentru a implementa funcționalitatea apasării butonului, putem folosi manipularea evenimentelor QML. Manipularea evenimentelor QML este foarte asemanatoare cu mecanismul Qt signal and slot.

Rectangle{
 id:simplebutton
 

MouseArea{
 id: buttonMouseArea

anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
 //onClicked handles valid mouse button clicks
 onClicked: console.log(buttonLabel.text + " clicked" )
 }
}

Includem un element MouseArea în simplebutton. Elementele MouseArea descriu zona în care mișcările mouse-ului sunt detectate. Pentru butonul nostru, ancorăm întregul MouseArea de părintele său care e simplebutton. Sintaxa anchors.fill este un mod de a accesa o proprietate numită fill, în cadrul grupului de proprietăți numit anchors. QML folosește ancore pentru a specifica amplasarea elementelor; elementele se ancorează de alte elemente, creînd astfel amplasări robuste.

MouseArea are multe handlere pentru semnale care sunt chemate în timpul mișcărilor mouse-ului în cadrul granițelor sale. Unul dintre ele este onClicked care este chemat atunci când butonul acceptat al mouse-ului este apăsat, butonul acceptat implicit fiind cel stâng. Putem atribui acțiuni handler-ului onClicked. În exemplul nostru, console.log() tipărește text atunci când se dă click pe elementul MouseArea. Funcția console.log() este folositoare pentru depanarea programelor și pentru tipărirea de text.

Codul din SimpleButton.qml e suficient pentru a afișa un buton pe ecran și a tipărește text atunci când este apăsat cu mouse-ul.

Rectangle {
 id:Button
 

property color buttonColor: "lightblue"
 property color onHoverColor: "gold"
 property color borderColor: "white"

signal buttonClick()
 onButtonClick: {
 console.log(buttonLabel.text + " clicked" )
 }

MouseArea{
 onClicked: buttonClick()
 hoverEnabled: true
 onEntered: parent.border.color = onHoverColor
 onExited: parent.border.color = borderColor
 }

//determines the color of the button by using the conditional operator
 color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}

Un buton complet funcțional este prezentat in Button.qml. Exemplele de cod din acest articol nu conțin anumite părți din cod, care sunt înlocuite de … fie pentru că au fost deja prezentate în secțiunile anterioare sau pentru că nu sunt relevante pentru codul discutat.

Proprietățile custom sunt declarate folosind sintaxa: property type name. În cod, proprietatea buttonColor, de tipul color, este declarată si atribuită valorii "lightblue". buttonColor e folosită mai târziu într-o operație condițională pentru a determina culoarea cu care butonul este umplut. Observați că atribuirea valorilor unei proprietăți e posibilă folosind semnul =, în afară de atribuirea folosind caracterul :. Proprietățile custom permit ca elementele interne să fie accesibile în afara domeniului elementului Rectangle. Există tipuri QML de bază, ca int, string, real, precum și un tip numit variant.

Prin atribuirea de culori handlerelor pentru semnalele onEntered și onExited, bordura butonului va deveni galbenă când mouse-ul este deasupra butonului și culoarea va reveni la cea anterioară atunci când mouse-ul iese de pe suprafața butonului.

Un semnal buttonClick() este declarat în Button.qml plasând cuvântul cheie signal în fața numelui semnalului. Toate semnalele au handlere create automat, numele lor începând cu on. Drept consecință, onButtonClick este handlerul pentru buttonClick. Lui onButtonClick îi este atribuită o acțiune. În exemplul nostru, handlerul onClicked va chema onButtonClick, care afișează text. onButtonClick permite obiectelor externe să acceseze ușor zona butonului. De exemplu, elementele pot avea mai mult de o declarație MouseArea si un semnal buttonClick poate face mai bine distincția între mai multe handlere pentru semnalele MouseArea.

Avem acum cunoștiințele de bază pentru a implementa elemente în QML care pot gestiona mișcări simple ale mouse-ului. Am creat o etichetă Text în interiorul unui Rectangle, definit proprietăți custom, și implementat comportamente ca răspuns la mișcările mouse-ului. Idea de a crea elemente în interiorul altor elemente este repetată de-a lungul aplicației editor de texte.

Butonul nu este folositor dacă nu este folosit ca o componentă pentru a îndeplini o acțiune. În secțiunea următoare, vom crea un meniu ce conține mai multe butoane.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.png

Crearea unui Menu Page

Până acum, am văzut cum să cream elemente și să atribuim comportamente acestora în cadrul unui singur fișier QML. În această secțiune, vom arăta cum să importăm elemente QML și cum să reutilizăm componentele deja create pentru a construi alte componente.

Meniurile afișează conținutul unei liste, fiecare element având posibilitatea de a realiza o acțiune. În QML putem crea un meniu în mai multe moduri. Mai întâi vom crea un menu care conține butoane care în final vor realiza diverse acțiuni. Codul pentru meniu se găsește in fișierul FileMenu.qml.

import QtQuick 1.0 import the main Qt QML module
import "folderName" import the contents of the folder
import "script.js" as Script import a Javascript file and name it as Script

Sintaxa de mai sus arată cum să folosiți cuvântul cheie import. Acest cuvânt este necesar pentru a folosi fișiere Javascript, sau fișiere QML care nu se află în același director. Deoarece Button.qml e în același director cu FileMenu.qml, nu e nevoie să importăm fișierul Button.qml pentru a-l folosi. Putem crea un buton folosind o declarație Button{}, similar cu o declarație Rectangle{}.

În FileMenu.qml:

Row{
 anchors.centerIn: parent
 spacing: parent.width/6

Button{
 id: loadButton
 buttonColor: "lightgrey"
 label: "Load"
 }
 Button{
 buttonColor: "grey"
 id: saveButton
 label: "Save"
 }
 Button{
 id: exitButton
 label: "Exit"
 buttonColor: "darkgrey"

onButtonClick: Qt.quit()
 }
 }

În FileMenu.qml declarăm trei elemente Button. Ele sunt declarate în interiorul unui element Row, un element care poziționează copii săi de-a lungul unui rând vertical. Declarația elementului Button se găsește în Button.qml, același Button.qml pe care l-am folosit în secțiunea anterioară. Noi atribuiri ale proprietăților pot fi declarate în cadrul butoanelor nou create, ele suprascriu proprietățile din Button.qml. Butonul numit exitButton va închide aplicația și fereastra sa atunci când este apăsat. Observați că handlerul pentru semnal onButtonClick din Button.qml va fi chemat, alături de handlerul onButtonClick din exitButton.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png

Declarația Row se află în cadrul unui Rectangle, creând un container dreptunghi pentru rândul de butoane. Acest nou dreptunghi crează un mod indirect de a organiza rândul de butoane într-un meniu.

Declarația meniului pentru editare e foarte asemănătoare în această etapă. Meniul are butoane cu etichetele: Copy, Paste și Select All.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png

Folosind cunoștințele legate de importarea și personalizarea componentelor create anterior, putem combina aceste pagini cu meniuri pentru a crea o bară de meniuri ce constă în butoane pentru a selecta meniul și putem urmări cum sa structurăm date folosind QML.

Implementarea unei bare de meniuri

Editorul de texte are nevoie de o modalitate de a afișa meniuri folosind o bară de meniuri. Aceasta comută între diferite meniuri, iar utilizatorul poate alege care dintre ele e afișat. Pentru a putea comuta între meniuri, acestea trebuie să fie structurate și nu doar afișate la rând. QML folosește modele și vizualizări pentru a structura datele și pentru a le vizualiza.

Folosirea modelelor de date și a vizualizărilor

QML folosește diverse vizualizări de date pentru a afișa modele de date. Bara de meniuri va afișa meniurile într-o listă, având un antet pentru a afișa numele meniurilor. Lista cu meniuri e declarată în cadrul unui VisualItemModel. Elementul VisualItemModel conține itemi care au deja vizualizări, precum elemente Rectangle și elemente UI importate. Alte tipuri de modele precum ListModel au nevoie de un delegat pentru a-și afișa datele.

Declarăm două elemente vizuale în menuListModel, FileMenu și EditMenu. Personalizăm cele două meniuri și le afișăm folosind un ListView. Fișierul MenuBar.qml conține declarațiile QML, iar un meniu simplu pentru editare e definit în EditMenu.qml.

VisualItemModel{
 id: menuListModel
 FileMenu{
 width: menuListView.width
 height: menuBar.height
 color: fileColor
 }
 EditMenu{
 color: editColor
 width: menuListView.width
 height: menuBar.height
 }
 }

Elementul ListView afișează un model conform unui delegat. Delegatul poate declara itemii modelului ce trebuie afișați într-un element Row sau poate afișa itemii într-o grilă. menuListModel are deja elemente vizibile deci nu este nevoie să declarăm un delegat.

ListView{
 id: menuListView

//Anchors are set to react to window anchors
 anchors.fill:parent
 anchors.bottom: parent.bottom
 width:parent.width
 height: parent.height

//the model contains the data
 model: menuListModel

//control the movement of the menu switching
 snapMode: ListView.SnapOneItem
 orientation: ListView.Horizontal
 boundsBehavior: Flickable.StopAtBounds
 flickDeceleration: 5000
 highlightFollowsCurrentItem: true
 highlightMoveDuration:240
 highlightRangeMode: ListView.StrictlyEnforceRange
 }

În plus, ListView moștenește Flickable ceea ce face ca lista sa raspundă la "trageri" cu mouse-ul și alte gesturi. Ultima parte a codului de mai sus setează proprietăți Flickable pentru a crea mișcarea dorită vizualizării noastre. Mai precis, proprietatea highlightMoveDuration schimbă durata tranziției flick. O valoare mai mare pentru highlightMoveDuration rezultă într-o schimbare mai lentă a meniului.

ListView păstrează itemii modelului prin intermediul unui index și fiecare item vizual al modelului este accesibil prin intermediul indexului, în ordinea declarației. Schimbarea lui currentIndex schimbă practic elementul evidențiat în ListView. Header-ul barei de meniuri exemplifică acest efect. El conține două butoane într-un rând, fiecare din ele schimbând meniul curent când este apăsat. Apăsarea fileButton face ca meniul curent să fie meniul file, indexul fiind 0 deoarece FileMenu e declarat primul în cadrul menuListModel. Similar, apăsarea editButton face ca meniul curent sa fie EditMenu.

Dreptunghiul labelList are valoarea z egala cu 1, ceea ce face ca el să fie afișat în fața barei de meniuri. Elementele cu o valoare z mai mare sunt afișate în fața elementelor cu valoare z mai mica. Implicit, valoarea z este 0.

Rectangle{
 id: labelList
 
 z: 1
 Row{
 anchors.centerIn: parent
 spacing:40
 Button{
 label: "File"
 id: fileButton
 
 onButtonClick: menuListView.currentIndex = 0
 }
 Button{
 id: editButton
 label: "Edit"
 
 onButtonClick: menuListView.currentIndex = 1
 }
 }
 }

Bara de meniuri creată folosește poate folosi efectul "flick" sau apăsarea pe numele meniurilor pentru a accesa meniurile. Schimbarea meniurilor e intuitivă și răspunde acțiunilor utilizatorului.

http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png

Construirea unui editor de texte

Declararea unei zone pentru text

Editorul nostru de texte nu ar fi un editor dacă nu ar conține o zonă editabilă de text. Elementul TextEdit din QML permite declararea unei zone de text formată din mai multe linii. Elementul TextEdit e diferit de elementul Text, acesta din urmă nu permite utilizatorului să editeze textul.

TextEdit{
 id: textEditor
 anchors.fill:parent
 width:parent.width; height:parent.height
 color:"midnightblue"
 focus: true

wrapMode: TextEdit.Wrap

onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
 }

Editorul are culoarea font-ului setată și este setat sa continue textul pe linia următoare. Zona TextEdit se afla în cadrul unei zone "flickable" care va face scroll textului dacă cursorul este în afara zonei vizibile. Funcția ensureVisible() verifică dacă cursorul este în afara limitelor vizibile caz în care zona de text e mutată corespunzător. QML folosește sintaxa Javascript pentru script-urile sale; așa cum s-a menționat, fișiere Javascript pot fi importate și folosite în cadrul unui fișier QML.

function ensureVisible®{
 if (contentX >= r.x)
 contentX = r.x;
 else if (contentX+width <= r.x+r.width)
 contentX = r.x+r.width-width;
 if (contentY >= r.y)
 contentY = r.y;
 else if (contentY+height <= r.y+r.height)
 contentY = r.y+r.height-height;
 }

Combinarea componentelor pentru editorul de texte

Putem acum să creăm amplasarea elementelor editorului nostru de texte folosind QML. Editorul de texte are două componente, bara de meniuri pe care am creat-o și zona de text. QML ne permite să reutilizăm componente, și deci să simplificăm codul, prin importarea componentelor și personalizarea lor, atunci când aceasta este necesară. Editorul de texte împarte fereastra în două: o treime a ecranului e dedicată barei de meniuri și două treimi afișează zona de text. Bara de meniuri este afișată în fața oricăror alte elemente.

Rectangle{

id: screen
 width: 1000; height: 1000

//the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the MenuBar
 property int partition: height/3

MenuBar{
 id:menuBar
 height: partition
 width:parent.width
 z: 1
 }

TextArea{
 id:textArea
 anchors.bottom:parent.bottom
 y: partition
 color: "white"
 height: partition*2
 width:parent.width
 }
 }

Prin importarea componentelor reutilizabile, codul pentru editorul de texte este mult mai simplu. În plus, putem personaliza aplicația principală, fără a ne preocupa de proprietățile care au deja definite comportamente. Folosind această abordare, amplasarea elementelor unei aplicații și a componentelor interfeței grafice pot fi ușor realizate.

http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png

Înfrumusețarea editorului de texte

Implementarea interfeței Drawer

Editorul de texte e încă destul de simplu și poate fi îmbunătățit. Folosind QML putem declara tranziții și animații în editor. Bara de meniuri ocupă o treime din ecran și ar fi frumos dacă ar apărea doar la cerere.

Putem adăuga o interfață drawer (sertar) care va compacta sau expanda bara de meniuri la apăsare. În implementarea curentă există un dreptunghi îngust ce răspunde la apăsări cu mouse-ul. Ca și aplicația, sertarul are două stări: "sertar deschis" și "sertar închis". Obiectul sertar e o fâșie de dreptunghi de înălțime mică. În interior se găsește un element de tip Image specificând că o iconiță de tip săgeată e centrată în cadrul sertarului. Obiectul sertar atribuie o stare întregii aplicații, în cadrul ecranului identificator, de fiecare dată când un utilizator apasă în zona mouse-ului.

Rectangle{
 id:drawer
 height:15

Image{
 id: arrowIcon
 source: "images/arrow.png"
 anchors.horizontalCenter: parent.horizontalCenter
 }

MouseArea{
 id: drawerMouseArea
 anchors.fill:parent
 onClicked:{
 if (screen.state  "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state  "DRAWER_OPEN"){
 screen.state = "DRAWER_CLOSED"
 }
 }
 
 }
 }

O stare e pur și simplu o colecție de configurații și se declară printr-un element State. O listă de stări poate fi enumerată și legată de proprietatea states. În aplicația noastră, cele două stări sunt numite DRAWER_CLOSED și DRAWER_OPEN. Configurațiile elementelor sunt declarate în elemente PropertyChanges. În starea DRAWER_OPEN există patru elemente ale căror proprietăți se schimbă. Primul dintre ele, menuBar, își va schimba proprietatea y în 0. Similar, textArea va coborî într-o poziție nouă când starea e DRAWER_OPEN. Elementele textArea, drawer și iconița obiectului drawer vor suferi schimbări ale proprietăților pentru a se conforma stării curente.

 states:[
 State {
 name: "DRAWER_OPEN"
 PropertyChanges { target: menuBar; y: 0}
 PropertyChanges { target: textArea; y: partition + drawer.height}
 PropertyChanges { target: drawer; y: partition}
 PropertyChanges { target: arrowIcon; rotation: 180}
 },
 State {
 name: "DRAWER_CLOSED"
 PropertyChanges { target: menuBar; y:-height; }
 PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height }
 PropertyChanges { target: drawer; y: 0 }
 PropertyChanges { target: arrowIcon; rotation: 0 }
 }
 ]

Schimbările de stare sunt bruște și necesită tranziții mai fine. Tranzițiile între stări sunt definite cu ajutorul elementului Transition, care poate fi conectat de proprietatea transitions a obiectului. Editorul nostru de texte suferă o tranziție de fiecare dată când starea se schimbă fie în DRAWER_OPEN, fie în DRAWER_CLOSED. De remarcat e faptul că tranziția necesită o stare from și o stare to; însă, pentru tranzițiile noastre, putem folosi simbolul wildcard * pentru a preciza că tranziția se aplică tuturo schimbărilor de stare.

În timpul tranzițiilor putem asigna animații schimbărilor de proprietăți. Elementul menuBar își comută poziția de la y:0 la y:-partition și putem anima această tranziție folosind NumberAnimation. Specificăm că proprietățile elementelor țintă vor fi animate pentru o anumită durată de timp și că vor folosi o curbă de atenuare. O curbă de atenuare reglementează gradul de animare și interpolare în timpul tranzițiilor între stări. Alegem curba de atenuare Easing.OutQuint, ce încetinește mișcarea către sfârșitul animației. Pentru detalii suplimentare, vă rugăm să citiți articolul QML Animation

 transitions: [
 Transition {
 to: "*"
 NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
 NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
 NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
 }
 ]

O altă modalitate de a anima schimbările proprietăților e de a declara un element Behavior. O tranziție apare doar în timpul schimbărilor de stare, iar Behavior poate stabili o animație pentru o schimbare generală a unei proprietăți. În editorul de texte, săgeata are un element NumberAnimation care îi animează proprietatea rotation de fiecare dată când aceasta se schimbă.

 In TextEditor.qml:

Behavior{
 NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
 }

Revenind la componentele noastre după ce ne-am familiarizat cu conceptele de stare și animație, putem îmbunătăți aspectul acestora. În Button.qml putem adăuga schimbări ale proprietăților color și scale de fiecare dată când butonul e apăsat. Culorile se animează folosind ColorAnimation, iar numerele se animează cu ajutorul NumberAnimation. Sintaxa on-numele-proprietății prezentată mai jos e folositoare când avem de-a face cu o singură proprietate.

 In Button.qml:
 

color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
 Behavior on color { ColorAnimation{ duration: 55} }

scale: buttonMouseArea.pressed ? 1.1 : 1.00
 Behavior on scale { NumberAnimation{ duration: 55} }

În plus, putem îmbunătăți aspectul componentelor QML adăugând efecte coloristice precum gradienți și opacitate. Declararea unui element Gradient suprascrie proprietatea color a elementului respectiv. În cadrul gradientului, o culoare poate fi declarată folosind un element GradientStop. Gradientul e precizat folosind o scală între 0.0 și 1.0.

In MenuBar.qml
 gradient: Gradient {
 GradientStop { position: 0.0; color: "#8C8F8C" }
 GradientStop { position: 0.17; color: "#6A6D6A" }
 GradientStop { position: 0.98;color: "#3F3F3F" }
 GradientStop { position: 1.0; color: "#0e1B20" }
 }

Acest gradient e folosit de către bara de meniuri pentru a afișa un gradient ce simulează adâncimea. Prima culoare începe la 0.0, iar ultima culoare e la 1.0.

Cum să continuați

Suntem pe punctul de a termina construirea unei interfețe utilizator pentru un editor de texte foarte simplu. Interfața utilizator e completă; putem, deci, să implementăm logica aplicației folosind Qt și C+. QML poate fi folosit destul de bine ca instrument pentru crearea de prototipuri, separând logica aplicației de proiectarea interfeței utilizator.

http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png

Extinderea QML folosind Qt C+

Acum că avem amplasarea elementelor editorului de texte, putem implementa funcționalitățile în C+. Folosirea QML împreună cu C+ permite crearea logicii aplicației folosind Qt. Putem crea un context QML într-o aplicație C++ folosind clasele din Qt Declarative și putem afișa elementele QML folosind Graphics Scene. Ca alternativă, putem exporta codul C++ ca un plugin pe care qmlviewer îl poate folosi. Pentru aplicația noastră, vom implementa funcțiile de încarcare si salvare a textului în C++ și le vom exporta ca plugin. În acest fel, e nevoie doar să încărcăm fișierul QML, în loc să rulăm un executabil.

Expunerea claselor C++ către QML

Vom implementa încărcarea și salvarea fișierelor folosind Qt și C+. Clasele și funcțiile C+ pot fi folosite în QML dacă sunt înregistrate ca atare. Clasa trebuie de asemenea compilată ca un plugin Qt și fișierul QML trebuie să știe unde se află plugin-ul.

Pentru aplicația noastră, avem nevoie de următoarele:

  1. clasa Directory care se va ocupa de operațiile legate de directoare
  2. clasa File care este un QObject și simulează lista de fișiere dintr-un director
  3. o clasa plugin care va înregistra clasa în context QML
  4. un fișier proiect Qt care va compila plugin-ul
  5. un fișier qmldir care va indica qmlviewer unde se găsește plugin-ul

Compilarea unui plugin pentru Qt

Pentru a compila un plugin, e nevoie de următoarele într-un fișier proiect Qt. Mai întâi, sursele, header-ele și modulele Qt necesare trebuie adăugate fișierului proiect. Toate fișierele ce conțin cod C++ și fișierele proiect se găsesc în directorul filedialog.

In cppPlugins.pro:

TEMPLATE = lib
 CONFIG ''= qt plugin
 QT''= declarative

DESTDIR ''= ../plugins
 OBJECTS_DIR = tmp
 MOC_DIR = tmp

 TARGET = FileDialog

 HEADERS''= directory.h  file.h  dialogPlugin.h

SOURCES += directory.cpp  file.cpp  dialogPlugin.cpp

Mai precis, compilăm Qt cu modulul declarativ și configurăm proiectul ca plugin; pentru aceasta e nevoie template-ul lib. Plugin-ul compilat va fi pus în directorul plugins al directorului părinte.

Înregistrarea unei clase către QML

In dialogPlugin.h:

#include <QDeclarativeExtensionPlugin>

class DialogPlugin : public QDeclarativeExtensionPlugin
 {
 Q_OBJECT

public:
 void registerTypes(const char *uri);

};

Clasa plugin, DialogPlugin, e o clasa derivată din QDeclarativeExtensionPlugin. Trebuie să implementăm funcția moștenită registerTypes(). Fișierul dialogPlugin.cpp arată ca mai jos:

DialogPlugin.cpp:

#include "dialogPlugin.h"
 #include "directory.h"
 #include "file.h"
 #include <qdeclarative.h>

void DialogPlugin::registerTypes(const char '''uri){

 qmlRegisterType<Directory>(uri, 1, 0, "Directory");
 qmlRegisterType<File>(uri, 1, 0,"File");
 }

 Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

Funcția registerTypes() înregistrează clasele File și Directory către QML. Aceasta funcție are nevoie de numele clasei pentru template, un număr de versiune major, un număr de versiune minor și un nume pentru clasele noastre.

Trebuie să exportăm plugin-ul folosind macro-ul Q_EXPORT_PLUGIN2. Observați că în fișierul dialogPlugin.h, folosim macro-ul Q_OBJECT la începutul clasei. De asemenea, trebuie să rulăm qmake pe fișierul proiect pentru a genera codul meta-object necesar.

Crearea de proprietăți QML într-o clasă C++

Putem crea elemente și proprietăți QML folosind C++ și sistemul Qt Meta-Object. Putem implementa proprietățile folosind slots și signals; în acest fel Qt recunoaște aceste proprietăți. Proprietățile pot fi apoi folosite în QML.

Pentru editorul de texte, avem nevoie să încărcăm și salvăm fișiere. De obicei, aceste funcționalități sunt conținute de un dialog pentru fișiere. Din fericire, putem folosi QDir, QFile și QTextStream pentru a implementa citirea directorului și a fluxulurilor de intrare/ieșire.

class Directory : public QObject{

 Q_OBJECT

 Q_PROPERTY(int filesCount READ filesCount CONSTANT)
 Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
 Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
 Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

 

Clasa Directory folosește sistemul Qt Meta-Object pentru a înregistra proprietățile de care are nevoie pentru a realiza manipularea fișierelor. Clasa Directory este exportată ca plugin și poate fi folosită în QML ca elementul Directory. Fiecare din proprietățile declarate folosind macro-ul Q_PROPERTY este o proprietate QML.

Macro-ul Q_PROPERTY declară o proprietate, precum și funcțiile pentru citirea și scrierea ei către sistemul Qt Meta-Object. De exemplu, proprietatea filename, de tipul QString, poate fi citită folosind functia filename() și scrisă folosind funcția setFilename(). De asemenea, există un semnal asociat cu proprietatea filename numit filenameChanged(), semnal care e emis atunci cand proprietatea se schimbă. Funcțiile pentru citire și scriere sunt declarate publice în fișierul header.

Similar, avem celelalte proprietăți declarate conform cu modul în care vor fi folosite. Proprietatea filesCount indică numărul de fișiere dintr-un director. Proprietatea filename conține numele fișierului selectat în acest moment, iar conținutul încărcat/salvat e reținut în proprietatea fileContent.

Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>

Proprietatea files e o listă ce conține toate fișiere ce ramân în urma filtrării unui director. Clasa Directory e implementata astfel încât  filtreze fișierele text nevalide; numai fișierele cu extensia .txt sunt valide. Mai mult, listele de tipul [http://doc.qt.nokia.com/4.7/qlist.html QList] pot fi folosite în fișiere QML dacă le declarăm ca o [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html QDeclarativeListProperty] în C+''. Obiectul cu care este instanțiat template-ul trebuie sa moștenească [http://doc.qt.nokia.com/4.7/qobject.html QObject], deci clasa File trebuie să moștenească [http://doc.qt.nokia.com/4.7/qobject.html QObject]. În clasa Directory lista de obiecte File e reținută într-un [http://doc.qt.nokia.com/4.7/qlist.html QList] numit m_fileList.

class File : public QObject{

Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

};

Proprietățile pot fi folosite în QML ca parte a proprietăților elementului Directory. Observați  nu e necesar  cream o proprietate id în codul C.

Directory{

id: directory
filesCount
filename
fileContent
files
files[0].name

}

Deoarece QML folosește sintaxa și structura Javascript, putem itera lista de fișiere și accesa proprietățile lor. Pentru a obține proprietatea name a primului fișier, folosim files[0].name.

Și funcțiile obișnuite C''+ sunt accesibile din QML. Funcțiile pentru încărcarea și salvarea fișierelor sunt implementate în C++ și declarate folosind macro-ul [http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE Q_INVOKABLE]. Ca alternativă, puteam declara funcțiile ca slot și atunci ele ar fi de asemenea accesibile din QML.

In Directory.h:

Q_INVOKABLE void saveFile();

Q_INVOKABLE void loadFile();

Clasa Directory trebuie  și notifice alte obiecte atunci când conținutul directorului se schimbă. Această funcționalitate e implementată folosind un semnal. După cum s-a precizat, semnalele QML au un handler al cărui nume este numele semnalului prefixat cu on. Semnalul este numit directoryChanged și este emis atunci când conținutul directorului este reîmprospătat. Reîmprospătarea pur și simplu recitește conținutul directorului și actualizează lista de fișiere valide din director. Elementele QML pot fi notificate prin atașarea unei acțiuni handler-ului de semnal onDirectoryChanged. 

Proprietățile listei trebuie examinate mai îndeaproape. E necesar  facem asta pentru  proprietățile listei folosesc callback-uri pentru a accesa și modifica conținutul listei. Tipul proprietății listă de fișiere este QDeclarativeListProperty<File>. De fiecare data când lista este accesată, funcția accesor trebuie  returneze un object QDeclarativeListProperty<File>. Obiectul folosit pentru instanțierea template-ului, File, trebuie  moștenească QObject. Mai mult, pentru a crea obiectul [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html QDeclarativeListProperty], funcțiile ce vor accesa și modifica lista trebuie trimise constructorului ca pointeri la funcții. Lista, de tipul QList în cazul nostru, trebuie de asemenea să fie o lista de pointeri la File.

Constructorul tipului [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html QDeclarativeListProperty] și implementarea Directory:

QDeclarativeListProperty ( QObject object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 ) QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );

Constructorul primește pointeri la funcții care vor adăuga elemente în lista, număra câte elemente sunt în listă, returna un element al listei folosind un index și goli lista. Doar funcția de adăugare în lista este obligatorie. Observați  pointerii la funcții trebuie  corespundă definițiilor pentru [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AppendFunction-typedef AppendFunction], [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#CountFunction-typedef CountFunction], [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AtFunction-typedef AtFunction], și [http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#ClearFunction-typedef ClearFunction].

void appendFiles(QDeclarativeListProperty<File> * property, File * file)

File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)

void clearFilesPtr(QDeclarativeListProperty<File> *property)

Pentru a simplifica dialogul pentru fișiere, clasa Directory filtrează fișierele text nevalide, care sunt fișierele ce nu au extensia txt. Dacă numele unui fișier nu are extensia .txt, acel fișier nu va apărea în dialogul pentru fișiere. De asemenea, implementarea se asigură  fișierele salvate au extensia .txt. Directory folosește [http://doc.qt.nokia.com/4.7/qtextstream.html QTextStream] pentru a citi și scrie text din și în fișier.

Folosind elementul Directory, putem obține fișierele ca o listă, ști cate fișiere text sunt în directorul aplicației, obține numele fișierului și conținutul său ca șir de caractere și putem fi notificați când sunt schimbări în conținutul directorului.

Pentru a compila plugin-ul, rulați qmake pe fișierul proiect cppPlugins.pro, apoi rulați make pentru a compila și transfera plugin-ul în directorul plugins.

==== Importarea unui Plugin in QML ====

qmlviewer importă fișiere care sunt în același director cu aplicația. Putem și sa creăm un fișier qmldir ce conține locațiile fișierelor QML pe care dorim  le importăm. Fișierul qmldir poate reține și locații ale plugin-urilor sau alte resurse.

In qmldir:

Button ./Button.qml

FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml

plugin FileDialog plugins

Plugin-ul pe care tocmai l-am creat e numit FileDialog, după cum indică câmpul TARGET din fișierul proiect. Plugin-ul compilat se găsește în directorul plugins.

==== Integrarea unui dialogului de fișiere în meniul de fișiere ====

Elementul FileMenu trebuie  afișeze elementul FileDialog ce conține o listă de fișiere text dintr-un director, permițând astfel utilizatorului  selecteze fișierul dând click pe listă. De asemenea, trebuie  atribuim butoanelor pentru salvare, încărcare și fișier nou acțiunile corespunzătoare. Elementul FileMenu conține un element pentru introducere de text care permite utilizatorului  introducă un nume de fișier folosind tastatura.

Elementul Directory e folosit în fișierul FileMenu.qml și notifică elementul FileDialog  directorul și-a reîmprospătat conținutul. Această notificare e efectuată în handler-ul de semnal onDirectoryChanged.

In FileMenu.qml:

Directory{

id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()

}

În conformitate cu simplitatea aplicației noastre, dialogul pentru fișiere va fi mereu vizibil și nu va afișa fișierele text nevalide (cele care nu au extensia .txt).

In FileDialog.qml:

signal notifyRefresh()

onNotifyRefresh: dirView.model = directory.files

Elementul FileDialog va afișa conținutul unui director citind proprietatea sa de tip listă numită files. Această proprietate e folosită ca modelul unui element [http://doc.qt.nokia.com/4.7/qml-gridview.html GridView], care afișează itemi de date într-un tabel, conform unui delegat. Delegatul gestionează aspectul modelului și dialogul nostru de fișiere va crea un simplu tabel cu text centrat în mijlocul său. Un click pe numele fișierului va rezulta în apariția unui dreptunghi pentru a evidenția numele fișierului. FileDialog este notificat atunci când semnalul notifyRefresh este emis, ceea ce cauzează reîmprospătarea fișierelor din director.

In FileMenu.qml:

Button{

id: newButton
label: "New"
onButtonClick:{
textArea.textContent = ""
}
}
Button{
id: loadButton
label: "Load"
onButtonClick:{
directory.filename = textInput.text
directory.loadFile()
textArea.textContent = directory.fileContent
}
}
Button{
id: saveButton
label: "Save"
onButtonClick:{
directory.fileContent = textArea.textContent
directory.filename = textInput.text
directory.saveFile()
}
}
Button{
id: exitButton
label: "Exit"
onButtonClick:{
Qt.quit()
}
}

Acum, FileMenu se poate conecta la acțiunile corespunzătoare. Butonul saveButton va transfera textul din TextEdit în proprietatea fileContent a directorului, apoi va copia numele fișierului din zona editabilă de text. În sfârșit, butonul cheamă funcția saveFile() care salvează fișierul. Execuția butonului loadButton e similară. De asemenea, acțiunea New va goli conținutul zonei editabile de text.

În plus, butoanele meniului de editare sunt conectate la funcțiile TextEdit pentru copiere, lipire și selectare a textului din editor.

http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png

Finalizarea editorului de texte

http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png

Aplicația poate funcționa ca un simplu editor de texte, poate accepta text pe care îl poate salva într-un fișier. Editorul de texte poate de asemenea încărca text dintr-un fișier și manipula text.