====== 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);
}
}