Reakce na File/open: zobrazime dialogovy box pro vyber jmena souboru.
( demo.cc http://gitlab.fjfi.cvut.cz/culikzde/sos/-/tree/master/graphics04 )
void MainWindow::on_actionOpen_triggered () { QString fileName = QFileDialog::getOpenFileName (this, "Open file"); if (fileName != "") openFile (fileName); }
Pokud si uzivatel vybere jmeno souboru, zavolame openFile.
Pomoci tridy QFile otevreme soubor.
Vytvorime lokalni promennou typu QXmlStreamReader pripojenou na nas soubor.
void MainWindow::openFile (QString fileName) { QFile f (fileName); if (f.open (QFile::ReadOnly)) { QXmlStreamReader r (&f); readXml (r, scene); } refreshTree (); }
Funkci readXml pouzijeme take na konci konstruktoru tridy MainWindow
k nacteni souboru abc.xml pri startu naseho programu.
Soubor abc.xml nalezneme mezi zdrojovymi soubory v adresari graphics04/data.
abc.xml je “zakompilovan” do naseho spustitelneho programu.
V Qt Creatoru nento soubor naleznete ve vetvi “Resources”.
V projektovem souboru graphics04.pro je uveden soubor resources.qrc, ve kterem je uveden abc.xml.
Za behu programu se na “zakompilovane” soubory odvolavame pomoci cest zacinajicich dvojteckou.
openFile (":/data/abc.xml");
Funkce readXml je deklarovana v io.h
Nova data jsou vkladana do scene zadane parametrem scene.
V ramci sceny muzeme prvky vkladat dovnitr prvku target.
Nebo primo do sceny, pokud target je nullptr.
void readXml (QXmlStreamReader & r, QGraphicsScene * scene, QGraphicsItem * target = nullptr);
Pro pripomenuti priklad xml souboru abc.xml.
<?xml version="1.0" encoding="UTF-8"?> <data> <ellipse name="elipsa 1" pen="orange" brush="lime" x="209" y="209" width="100" height="80"/> <ellipse name="elipsa 2" x="325" y="108" width="100" height="80"/> <rectangle name="rectangle 1" x="100" y="100" width="100" height="80"/> <ellipse name="item1" x="40" y="40" pen="#0000ff" brush="#6495ed" width="40" height="40"/> <ellipse name="item2" x="120" y="40" pen="#0000ff" brush="#6495ed" width="40" height="40"/> </rectangle> </data>
Implementace readXml je v souboru io.cc
Napiseme cyklus, ktery vyuziva promennou r typu QXmlStreamReader.
Cyklus pobezi, dokud atEnd neohlasi konec souboru.
Metody isStartElement a isEndElement oznamuji, pokud jsme na zacatku nebo konci XML prvku.
readNext posouva reader na dalsi cast XML souboru.
while (! r.atEnd()) { if (r.isStartElement()) { /* jsme na zacatku nejake <znacky> */ } else if (r.isEndElement()) { /* jsme na konci </znacky> ( nebo /> ) */ } r.readNext(); }
Pokud potkame novou znacku, zjistime jeji jmeno (data, rectangle, ellipse, line).
QString type = r.name().toString();
Korenovou znacku <data> budu ignorovat.
Pro jine znacky zavolam metodu readItem, ktera vrati ukazatel na novy graficky prvek.
Novy prvek pridam primo do sceny, nebo pod prvek target.
A novy prvek budu pouzivat jako novy target, do doby nez potkam zaviraci znacku.
V nasem XML souboru <rectangle> obsahuje dve vnorene elipsy.
void readXml (QXmlStreamReader & r, QGraphicsScene * scene, QGraphicsItem * target) { while (! r.atEnd()) { if (r.isStartElement()) { QString type = r.name().toString(); if (type != "data") // top level element { QGraphicsItem * item = readItem (r); if (item != nullptr) { // add item if (target != nullptr) item->setParentItem (target); else scene->addItem (item); // change target target = item; } } } else if (r.isEndElement()) { if (target != nullptr) target = target->parentItem (); // back to above target } r.readNext(); } }
Pomocna funkce createItem podle zadaneho jmena vytvori graficky prvek.
QGraphicsItem * createItem (QString type) { QGraphicsItem * result = nullptr; if (type == "rectangle") result = new QGraphicsRectItem; else if (type == "ellipse") result = new QGraphicsEllipseItem; else if (type == "line") result = new QGraphicsLineItem; return result; }
Funkce readItem podle jmena znacky vytvori graficky prvek.
Objekt a typu QXmlStreamAttributes obsahuje jednotlive atributy (napr. x=“209” y=“209” z naseho souboru).
Vyzvedneme jednotlive hodnoty atributu z XML souboru a ulozime do grafickeho prvku.
Na zaver nastavime priznaky ItemIsMovable a ItemIsSelectable dovolujici manipulaci pomoci mysi.
QGraphicsItem * readItem (QXmlStreamReader & r) { QString type = r.name().toString(); QGraphicsItem * item = createItem (type); QXmlStreamAttributes a = r.attributes(); QString name = getString (a, "name"); item->setToolTip(name); int x = getNumber (a, "x"); int y = getNumber (a, "y"); item->setPos (x, y); item->setFlag (QGraphicsItem::ItemIsMovable); item->setFlag (QGraphicsItem::ItemIsSelectable); return item; }
Funkce getString a getNumber pro pristup k jednotlivym atributum.
Pokud atribut v XML souboru neni, funkce getString vrati prazdny retezec, funkce getNumber hodnotu zadanou prametrem init.
QString getString (QXmlStreamAttributes & a, QString name) { QString result = ""; if (a.hasAttribute (name)) { result = a.value(name).toString(); } return result; } int getNumber (QXmlStreamAttributes & a, QString name, int init = 0) { int result = init; if (a.hasAttribute (name)) { bool ok; result = a.value(name).toInt(&ok); if (! ok) result = init; } return result; }
Funkce readItem si musi poradit s ruznymi typy grafickych prvku prectenych z XML souboru.
dynamic_cast jsme jiz pouzivali k urceni typu pri zapisu dynamic_cast
Nasledujici konstrukce zkusi pretypovat item na ukazatel na tridu T.
Vysledek ulozi do promenne e opet typu ukazatel na T.
Promenna e existuje jen po dobu existence if prikazu, v nasem pripade jen v if a nasledujicich slozenych zavorkach.
Pokud se pretypovani nepovede, je do promenne e ulozen nullptr, ktery *if* povazuje za false a slozene zavorky se neprovedou ( a else zde nemame )
if (T * e = dynamic_cast < T * > (item)) { }
Zde je funkce readItem i se zpracovanim ruznych typu.
QGraphicsItem * readItem (QXmlStreamReader & r) { QGraphicsItem * item = nullptr; if (r.isStartElement()) { QString type = r.name().toString(); item = createItem (type); QXmlStreamAttributes a = r.attributes(); QString name = getString (a, "name"); item->setToolTip(name); int x = getNumber (a, "x"); int y = getNumber (a, "y"); item->setPos (x, y); if (QAbstractGraphicsShapeItem * shape = dynamic_cast < QAbstractGraphicsShapeItem * > (item)) { QColor c = getColor(a, "pen", QColor ("red")); QColor d = getColor(a, "brush", QColor ("yellow")); shape->setPen (c); shape->setBrush (d); if (QGraphicsRectItem * r = dynamic_cast < QGraphicsRectItem * > (shape)) { int w = getNumber (a, "w", 100); int h = getNumber (a, "h", 80); r->setRect (0, 0, w, h); } if (QGraphicsEllipseItem * e = dynamic_cast < QGraphicsEllipseItem * > (shape)) { int w = getNumber (a, "w", 100); int h = getNumber (a, "h", 80); e->setRect (0, 0, w, h); } } if (QGraphicsLineItem * t = dynamic_cast < QGraphicsLineItem * > (item)) { int w = getNumber (a, "w", 100); int h = getNumber (a, "h", 80); t->setLine (0, 0, w, h); } item->setFlag (QGraphicsItem::ItemIsMovable); item->setFlag (QGraphicsItem::ItemIsSelectable); } return item; }