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);
   }
}
 
qt/graphics_property_table.txt · Last modified: 2020/09/30 12:02 by 88.103.111.44
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki