====== Tabulka s parametry ====== {{qt:property-table.png}} 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 ()); } } } 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 (); 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 ()**. 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 (); 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 ===== {{qt:color-editor.png?400}} 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 (); 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); } }