Table of Contents
Tabulka s parametry
Zdrojové texty
http://gitlab.fjfi.cvut.cz/culikzde/sos/-/blob/master/graphics04/property.h
http://gitlab.fjfi.cvut.cz/culikzde/sos/-/blob/master/graphics04/property.cc
Vytvoříme si třídu PropertyTable odvozenou od QTableWidget.
Funkce displayProperties zobrazí parametry našeho grafického prvku.
class PropertyTable : public QTableWidget { private: QTableWidgetItem * addTableLine (QString name, QString value); void storeProperty (QGraphicsItem * item, QString name, QVariant value); void addBool (QString name, bool value); void addNum (QString name, int value); void addReal (QString name, double value); void addText (QString name, int value); void addColor (QString name, QColor value); void addList (QString name, QStringList value); public: PropertyTable (QWidget * parent = nullptr); void displayProperties (QGraphicsItem * item); public slots: void onItemChanged (QTableWidgetItem * item); private: Win * win; QGraphicsItem * graphics_item; public: void setWin (Win * p_win) { win = p_win; } };
Zobrazení jedné řádky
V konstruktoru si nastavíme počet sloupů a jejich nadpisy.
Poslednímu sloupeci nastavíme “roztažnost”.
setColumnCount(2); QStringList labels; labels << "Name" << "Value"; setHorizontalHeaderLabels (labels); horizontalHeader ()->setStretchLastSection (true);
Funkce addTableLine přidá novou řádku a
vytvoří dvě buňky typu QTableWidgetItem pro jméno a hodnotu,
výsledkem funkce je ukazatel na druhou buňku.
Pokud buňky vyvořím bez new,
(ne jako ukazatel, ale jako obyčejné QTableWidgetItem),
destruktor je na konci funkce z tabulky vymaže.
QTableWidgetItem * PropertyTable::addTableLine (QString name, QString value) { int cnt = rowCount (); setRowCount (cnt+1); QTableWidgetItem * cell = new QTableWidgetItem; cell->setText (name); setItem (cnt, 0, cell); cell = new QTableWidgetItem; cell->setText (value); setItem (cnt, 1, cell); return cell; }
Jednotlivé buňky skladují data pro různé příležitosti (role).
Funkce addText, addColor, … zobrazovanou hodnotu uloží jako data pro editační roli.
Data jsou typu QVariant, který může skladovat celá čísla, texty a jiné běžné hodnoty.
Tento typ se také používá pro přenos dat z databází.
Příklademdalší role je DecorationRole zobrazující barevný obdlélníček nebo ikonku na začátky řádky.
void PropertyTable::addNum (QString name, int value) { QTableWidgetItem * cell = addTableLine (name, ""); cell->setData (Qt::EditRole, value); } void PropertyTable::addText (QString name, int value) { QTableWidgetItem * cell = addTableLine (name, ""); cell->setData (Qt::EditRole, value); } void PropertyTable::addColor (QString name, QColor value) { QTableWidgetItem * cell = addTableLine (name, ""); cell->setData (Qt::EditRole, value); // cell->setData (Qt::DecorationRole, value); } void PropertyTable::addList (QString name, QStringList value) { QTableWidgetItem * cell = addTableLine (name, ""); cell->setData (Qt::EditRole, value); }
Funkce displayProperties vymaže původnítabilku a
přidá jednoltivé hodnoty grafického prvku.
void PropertyTable::displayProperties (QGraphicsItem * item) { graphics_item = item; setRowCount (0); if (item != nullptr) { addTableLine ("tooltip", item->toolTip()); addNum ("x", item->x()); addNum ("y", item->y()); if (QAbstractGraphicsShapeItem * shape = dynamic_cast < QAbstractGraphicsShapeItem * > (item)) { addColor ("pen", shape->pen().color() ); addColor ("brush", shape->brush().color() ); if (QGraphicsRectItem * r = dynamic_cast < QGraphicsRectItem * > (shape)) { addNum ("width", r->rect().width() ); addNum ("height", r->rect().height() ); } if (QGraphicsEllipseItem * e = dynamic_cast < QGraphicsEllipseItem * > (shape)) { addNum ("width", e->rect().width() ); addNum ("height", e->rect().height() ); } } if (QGraphicsLineItem * e = dynamic_cast < QGraphicsLineItem * > (item)) { addColor ("pen", e->pen().color() ); addNum ("width", e->line().dx() ); addNum ("height", e->line().dy() ); } addBool ("boolean", true); addReal ("real", 3.14); addList ("list", QStringList () << "abc" << "def" << "klm"); } }
Na konci jsou pro ilustraci přidány řádky s booleovskou hodnotou, desetinným číslem a
seznamem několika hodnot (zobrazovaných v “roletce”).
Editování
V kostruktoru propojíme signál itemChanged naší tabulky a
metodu onItemChanged v naší třídě.
connect (this, &QTableWidget::itemChanged, this, &PropertyTable::onItemChanged);
Zjistíme si číslo řádky a sloupce (indexované od 0)
a pokud byl změněn sloupec s hodnotami (sloupec 1),
tak hodnotu uložíme do proměnné value typu QVariant.
void PropertyTable::onItemChanged (QTableWidgetItem * cell) { int line = cell->row (); int col = cell->column (); if (col == 1) { QString name = item (line, 0)->text (); QVariant value = cell->text (); storeProperty (graphics_item, name, value); if (name == "tooltip") { win->refreshTreeName (graphics_item, value.toString ()); } if (name == "pen" || name == "brush") { cell->setData (Qt::DecorationRole, value.value <QColor> ()); } } }
Funkce storeProperty dostane název parametru name, hodnotu value a uloží hodnotu do grafického prvku item.
Hodnotu type QVariant převedeme na vhodný typ fnkcemi toString, toInt, …
Barvu QColor vyzvedneme pomocí výrazu
QColor color = value.value <QColor> ();
Proměnná value obsahuje šablonovou funkci téhož jména.
Parametr šablony určuje i typ výsledku funkce.
Funkce nemá žádný parametr, který by překladači dovolil odvodit parametr šablony.
Tak musíme šablonový parametr zadat sami value <QColor> ().
void PropertyTable::storeProperty (QGraphicsItem * item, QString name, QVariant value) { if (name == "tooltip") item->setToolTip (value.toString ()); if (name == "x") item->setX (value.toInt ()); if (name == "y") item->setY (value.toInt ()); if (QAbstractGraphicsShapeItem * shape = dynamic_cast < QAbstractGraphicsShapeItem * > (item)) { if (name == "pen") { QColor color = value.value <QColor> (); shape->setPen (color); } /* ... */ } /* ... */ }
Propojení s hlavním oknem
Pokud se změnila řádka tooltip, kterou používáme jako jméno grafického prvku,
překreslíme položku ve stromu.
Proměnná win obsahuje ukazatel na rozhranní (abstraktní třídu) s funkcemi hlavního okna
dostupnými v “okolních” třídách.
class Win // MainWindow interface { public: virtual void refreshTree () = 0; virtual void refreshProperties (QGraphicsItem * item) = 0; virtual void refreshTreeName (QGraphicsItem * item, QString name) = 0; virtual void refreshNewTreeItem (QGraphicsItem * item) = 0; };
Třída MainWindow potom tyto funkce implementuje
class MainWindow : public QMainWindow, public Win { /* ... */ };
a ukazatel na hlavní okno je předán naší tabulce.
Naše tabulka tak má přístup jen k vybraným funkcím hlavnímo okna a
také nepotřebuje znát kompletní deklaraci hlavního okna.
Vlastní editor pro jednu položku
Jako doplněk můžeme vytvořit vlastní editor pro jednu položku
a například přidat tlačítko zobrazující editor barev,
musíme vytvořit objekt odvozený od třídy QAbstractItemDelegate a
v konstruktoru předáme tento objekt naší tabulce.
setItemDelegate (new CustomDelegate (this));
Tento objekt předefinovává tři virtuální funkce pro
vytvoření vlastního editoru a předávání dat do editoru a ukládání dat zpět z editoru.
class CustomDelegate : public QStyledItemDelegate { public: virtual QWidget * createEditor (QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; virtual void setEditorData (QWidget * param_editor, const QModelIndex & index) const override; virtual void setModelData (QWidget * param_editor, QAbstractItemModel * model, const QModelIndex & index) const override; PropertyTable * table; CustomDelegate ( PropertyTable * p_table) : table (p_table) { } };
Třída obsahující editační prvky pro jednu řádku
Třída CustomEditor obsahuje například editační kolonku QLineEdit a tlačítko QPushButton
class CustomEditor : public QWidget { public: bool enable_checkbox; bool enable_list; bool enable_text; bool enable_numeric; bool enable_real; bool enable_dialog; QStringList list_values; QCheckBox * check_box; QLineEdit * line_edit; QSpinBox * numeric_edit; QDoubleSpinBox * real_edit; QComboBox * combo_box; QPushButton * button; QTableWidgetItem * cell; void onDialogClick (); CustomEditor (QWidget * parent); };
Tlačítko po stisknutí zavolá metodu onDialogClick,
která zobrazí dialogový box pro výběr barvy.
void CustomEditor::onDialogClick () { QVariant value = cell->data (Qt::EditRole); QColor color = value.value <QColor> (); color = QColorDialog::getColor (color); if (color.isValid ()) { cell->setData (Qt::EditRole, color); } }
Metoda createEditor
Metoda createEditor vytvoří na začátku editace jedné buňky
vytvoří editační prvky a srovná je pomocí QHBoxLayout.
Editační prvky vytvoří podle typu skutečné hodnoty uvnitř QVariant.
QWidget * CustomDelegate::createEditor (QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { CustomEditor * editor = new CustomEditor (parent); if (index.column () == 1) { QVariant value = index.data (Qt::EditRole); QVariant::Type type = value.type (); if (type == QVariant::Bool || index.data(Qt::UserRole).toString () == "boolean") { editor->enable_checkbox = true; } else if (type == QVariant::Int) { editor->enable_numeric = true; } else if (type == QVariant::Double) { editor->enable_real = true; } else if (type == QVariant::String) { editor->enable_text = true; } else if (type == QVariant::Bool) { editor->enable_checkbox = true; } else if (type == QVariant::StringList) { editor->enable_list = true; editor->list_values = value.toStringList (); } else if (type == QVariant::Color) { editor->enable_dialog = true; } QHBoxLayout * layout = new QHBoxLayout (editor); layout->setMargin (0); layout->setSpacing (0); editor->setLayout (layout); if (editor->enable_list) { editor->combo_box = new QComboBox (editor); layout->addWidget (editor->combo_box); int cnt = editor->list_values.count (); for (int inx = 0; inx < cnt; inx++) editor->combo_box->addItem (editor->list_values [inx]); if (cnt > 0) editor->combo_box->setCurrentIndex (0); } if (editor->enable_checkbox) { editor->check_box = new QCheckBox (editor); layout->addWidget (editor->check_box); } if (editor->enable_text) { editor->line_edit = new QLineEdit (editor); layout->addWidget (editor->line_edit); } if (editor->enable_numeric) { editor->numeric_edit = new QSpinBox (editor); editor->numeric_edit->setMaximum (1000); layout->addWidget (editor->numeric_edit); } if (editor->enable_real) { editor->real_edit = new QDoubleSpinBox (editor); layout->addWidget (editor->real_edit); } if (editor->enable_dialog) { editor->button = new QPushButton (editor); editor->button->setText ("..."); editor->button->setMaximumWidth (32); layout->addWidget (editor->button); connect (editor->button, &QPushButton::clicked, editor, &CustomEditor::onDialogClick); } } return editor; }
Metoda setEditorData
Metoda setEditorData vyzvedne data z jedné buňku tabulky a to z editační role,
uschová hodnotu v proměnné typu QVariant
a nastaví parametry malých editačních prvků.
void CustomDelegate::setEditorData (QWidget * param_editor, const QModelIndex & index) const { CustomEditor * editor = dynamic_cast < CustomEditor * > (param_editor); editor->cell = table->item (index.row(), index.column ()); QVariant value = index.data (Qt::EditRole); if (editor->check_box != nullptr) { editor->check_box->setChecked (value.toBool ()); } if (editor->line_edit != nullptr) { editor->line_edit->setText (value.toString ()); } if (editor->numeric_edit != nullptr) { editor->numeric_edit->setValue (value.toInt ()); } if (editor->real_edit != nullptr) { editor->real_edit->setValue (value.toDouble ()); } }
Metoda setModelData
Metoda setModelData na konci editace jedné řádky
přenáší hodnoty z ediatčních prvků a ukládáje do naší tabulky.
void CustomDelegate::setModelData (QWidget * param_editor, QAbstractItemModel * model, const QModelIndex & index) const { CustomEditor * editor = dynamic_cast < CustomEditor * > (param_editor); // assert (editor != nullptr); if (editor->check_box != nullptr) { bool val = editor->check_box->isChecked (); model->setData (index, val); } if (editor->line_edit != nullptr) { QString txt = editor->line_edit->text (); model->setData (index, txt); } if (editor->numeric_edit != nullptr) { int val = editor->numeric_edit->value (); model->setData (index, val); } if (editor->real_edit != nullptr) { double val = editor->real_edit->value (); model->setData (index, val); } }