Model View Programming/ro

From Qt Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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.

Arhitectura Model-View-Controller (MVC)

Generalități

În Qt există un set de clase de vizualizare a datelor cu ajutorul arhitecturii Model-View pentru a gestiona relațiile dintre date și modul în care sunt acestea prezentate utilizatorului. Separația funcțională introdusă de această arhitectură dă programatorului o mai mare flexibilitate în a personaliza prezentarea datelor și oferă un model de interfață care permite o gamă mai largă de surse de date ce pot fi folosite.

Arhitectura Model-View-Controller este un design pattern originar din SmallTalk care este adesea folosit atunci când se construiesc interfețe cu utilizatorul. Model-View-Controller conține trei tipuri de obiecte:

  • Modelul este un obiect de aplicație;
  • View-ul este prezentarea pe ecran;
  • Controller-ul definește modul în care interfața cu utilizatorul reacționează la comenzile date de utilizator.

Înainte de această arhitectură, cele trei obiecte erau de regulă puse laolalta. Arhitectura Model-View-Controller le decuplează pentru a crește flexibilatea codului și să-l facă reutilizabil.

Dacă view-ul și controller-ul sunt combinate, rezultatul va fi o arhitectură model-view. Chiar și în acest caz, datele sunt separate de modul în care ele sunt prezentate, dar oferă niște mecanisme mai ușoare de lucru bazate pe aceleași principii. Această separație face posibilă afișarea a acelorași date în mai multe feluri fără a fi nevoie să schimbăm modul în care sunt structurate datele. Pentru a permite un mod flexibil de gestionare a comenzile utilizatorului, se introduce conceptul de delegat. Delegații ne permit să modificăm modul în care datele sunt afișate și editate.


Clasele unei astfel de arhitecturi sunt clasele de model, de view și delegații. Modelul comunică cu o sursă de date și oferă o interfață de comunicare cu celelalte componente. View-ul obține indexul de la care își va extrage datele din model pentru a le prezenta. Atunci când datele sunt editate delegatul va comunica direct cu modelul folosind indexul. Toate aceste clase vor comunica între ele utilizând semnale și slot-uri. Semnalele de la model vor informa despre schimbările de date. Semnalele de la view vor comunica comenzile date de utilizator. Semnalele de la delegat vor fi folosite în timpul editării datelor să informeze modelul și view-ul în legătură cu starea editorului.

http://doc.qt.nokia.com/4.7-snapshot/images/modelview-overview.png


Toate clasele care implementează modele sunt bazate pe clasa QAbstractItemModel. Această clasă definește o interfață care să poată fi folosită de view-uri și de delegați pentru a accesa date. Datele propriu-zise nu trebuie neapărat reținute într-un model. Ele pot fi păstrate într-o structură de date oferită de o clasă separată, un fișier, o bază de date sau altă componentă de aplicație.


Qt oferă niște modele gata definite care pot fi folosite pentru a gestiona un set de date:

Implementări complete sunt disponibile pentru următoarele view-uri:

  • QListView - pentru o listă de elemente;
  • QTableView - poate a afișa datele dintr-un tabel;
  • QTreeView - pentru a afișa date dintr-un model ierarhic;

Toate clasele menționate mai sus moștenesc clasa QAbstractItemView și pot fi și ele la rândul lor moștenite pentru a le oferi mai multe funcționalități. Delegații moștenesc clasa QAbstractItemDelegate. Delegatul implicit folosit de view-uri este QStyledItemDelegate.

Există două metode de a sorta elementele într-o arhitectură model-view. Dacă modelul este sortabil se poate reimplementa funcția void sort(). QTableView și QTreeView dau posibilitatea modelul să fie sortat printr-un click pe antetul tabelului.

Prezentarea datelor într-un tabel

Clasele QSqlQueryModel, QSqlTableModel, SqlRelationalTableModel pot fi folosite ca surse de date pentru clasele de tip view ca QListView, QTableView, și QTreeView. În practică QTableView este cel mai des folosit, deoarece în esență un rezultat de la o interogare SQL este o structură de date bidimensională.

Înainte de a prezenta diferite date într-un tabel, în arhitectura Model/View este mai întâi nevoie să ne generăm un model pe care să-l asociem mai târziu tabelului. Acest lucru se poate face prin intermediul unor clase precum QSqlQueryModel, QSqlTableModel, SqlRelationalTableModel cum de altfel s-a putut vedea aici .

Putem folosi același model pentru mai multe view-uri. Dacă utilizatorul editează modelul într-una din view-uri aceste schimbări se vor reflecta imediat. De asemenea putem personaliza și antetul unui view prin apelul funcției setHeaderData() a unui model.

Uneori dorim să presortăm datele dintr-un model. Clasa QSortFilterProxyModel poate fi interpusă între un model și un view pentru sortări și filtrări a datelor. Această clasă transformă structura modelului sursă prin maparea indexurilor modelului în noi indexi. Această abordare permite ca un model sursă să fie restructurat fără a efectura transformări asupra modelului sursă și fără a duplica informații în memorie.

Să presupunem că dorim să sortăm și să filtrăm informațiile dintr-un model. Următorul cod va crea un model care va fi dat ca model sursă pentru un model QSortFilterProxyModel :

 model = new QSqlQueryModel(this);
 proxyModel = new MySortFilterProxyModel(this);
 proxyModel->setSourceModel(model);

Iar pentru a permite sortarea trebuie să setăm proprietatea dynamicSortFilter:

 proxyModel->setDynamicSortFilter(true);<code>

Acest lucru ne va permite  sortăm informațiile dintr-un tabel printr-un click pe antetul view-ului.

Pentru a filtra datele ne folosim de funcția [http://doc.qt.nokia.com/4.7-snapshot/qsortfilterproxymodel.html#filterRegExp-prop setFilterRegExp() ] pentru a specifica filtrul folosit și de [http://doc.qt.nokia.com/4.7-snapshot/qsortfilterproxymodel.html#filterKeyColumn-prop setFilterKeyColumn] pentru a specifica coloană pe care aplicăm respectivul filtru.

== Folosirea delegaților în arhitectura Model-View-Controller ==

Un view standard oferit de Qt folosește pentru editare instanțe ale clasei [http://doc.qt.nokia.com/4.7-snapshot/qitemdelegate.html QItemDelegate]. Toate rolurile standard sunt interpretate de delegatul implicit al unui view. În următorul exemplu se va folosi un [http://doc.qt.nokia.com/4.7-snapshot/qdoublespinbox.html QDoubleSpinBox ] pentru a oferi facilități de editare pentru un delegat și vom redefini modul în care afișăm datele dintr-o celulă. Vom moșteni clasa '''QItemDelegate''' și vom reimplementa o serie de funcții :

class CustomSpinBoxDelegate: public QItemDelegate {

Q_OBJECT public:

CustomSpinBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option;,const QModelIndex &index;) const;
void setEditorData(QWidget *editor, const QModelIndex &index;) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index;) const;
void updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option;, const QModelIndex &index;) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option;,

const QModelIndex &index;) const;};

Când un tabel are nevoie de un editor, el îl preia de la delegat. Acest editor se crează prin apelul funcției [http://doc.qt.nokia.com/4.7-snapshot/qitemdelegate.html#createEditor createEditor()] al obiectului de tip delegat:

QWidget *CustomSpinBoxDelegate::createEditor(QWidget parent,

const QStyleOptionViewItem &/ option */, const QModelIndex &/ index */) const {
QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
editor->setAccelerated(true);
editor->setRange(0, 10000);

return editor;}

Nu e nevoie  păstrăm un pointer către editor, deoarece tabelul își asumă responsabilitatea dealocării acestuia când nu va mai fi nevoie de el. Când utilizatorul a terminat de editat, tabelul va apela funcția [http://doc.qt.nokia.com/4.7-snapshot/qitemdelegate.html#setModelData setModelData()] pentru a salva datele:

void CustomSpinBoxDelegate::setModelData(QWidget *editor,

QAbstractItemModel *model, const QModelIndex &index;) const {
QDoubleSpinBox spinBox = static_cast<QDoubleSpinBox> (editor);
spinBox->interpretText();
qreal value = spinBox->value();
model->setData(index, value, Qt::EditRole);

}

De asemenea delegatul trebuie  conțină și funcții prin care copiem datele din model în editor. În acest caz preluăm datele păstrate în model și-l atribuim editorului:

void CustomSpinBoxDelegate::setEditorData(QWidget *editor,

const QModelIndex &index;) const {
qreal value = index.model()->data(index, Qt::EditRole).toDouble();
QDoubleSpinBox spinBox = static_cast<QDoubleSpinBox> (editor);
spinBox->setValue(value);

}

Iar dacă dorim  modificăm modul în care datele sunt afișate trebuie  reimplementăm funcția [http://doc.qt.nokia.com/4.7-snapshot/qitemdelegate.html#paint paint()] :

void CustomSpinBoxDelegate::paint(QPainter *painter,

const QStyleOptionViewItem &option;, const QModelIndex &index;) const {
if (option.state & QStyle::State_Selected) {
painter->setCompositionMode (QPainter:: CompositionMode_SourceAtop);
painter->fillRect(option.rect, option.palette.highlight());
painter->drawText(option.rect, Qt::AlignCenter, QString::number(index.data().toDouble(), 'f', 2));
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
} else {
painter->drawText(option.rect, Qt::AlignCenter, QString::number(index.data().toDouble(), 'f', 2));
}

}