Qt Model/View Editing Tutorial

In my last tutorial, I explained how to use Qt’s model/view architecture as well as provided an implementation of a custom table-model that is specialized for displaying data that is organized into rows. The code for my last tutorial, which we will be building on, is on github.

However, we have not examined how to alter the editing process, and so we face some limitations with our editing. The main limitation that we face is that we cannot set a maximum and minimum value for the QSpinBox editor, and this kind of limitation applies to many other editors. Another limitation is that we cannot choose the QWidget that is will be used for certain types of dat.

So in this tutorial, we will be creating a delegate for use with DataRow, and will also examine QItemEditorFactory. The complete code example for this tutorial is available on github.

QItemEditorFactory

QItemEditorFactory is used by the basic delegate, and will provide the widget required for editing data in the view if you have not customized the delegates createEditor function. Here is the list of all default editors.

The following code shows how to set the editor used by QItemEditorFactory to a QLineEdit for QString, and to a QSpinBox for integers.

void ChangeQItemEditorFactory()
{
    QItemEditorFactory * factory = new QItemEditorFactory;

    QItemEditorCreatorBase * stringEditor = new QStandardItemEditorCreator<QLineEdit>();
    factory->registerEditor(QVariant::String, stringEditor);

    QItemEditorCreatorBase * intEditor = new QStandardItemEditorCreator<QSpinBox>();
    factory->registerEditor(QVariant::Int, intEditor);

    QItemEditorFactory::setDefaultFactory(factory);
}

There are two different ways that you can register an editor for use with QItemEditorFactory. The first is to have the widget inherit from QItemEditorCreatorBase.

The other, and easier, way is to use QStandardItemEditorCreator (like I did) because it allows you to use a class that does not inherit from QItemEditorCreatorBase; instead, all you need to do is ensure the editor has a Q_PROPERTY that uses the “USER” keyword. Most, if not all, of the Qt widgets that could be used for editing already do this, so you just need to do this for your custom editors. Here is an explanation of Q_PROPERTY and the Color Editor Factory example created by Qt project.

Limitation of QItemEditorFactory without a delegate

Unfortunately, while we can change the editor used for specific data types, we are not guaranteed to be able to set all of the possible data in an editor: for example, we cannot change the minimum and maximum value of the QSpinBox for our numbers. This means that if you want to set this additional information, you will still need to use a delegate.

Qt Delegate

Within Qt, the delegate is responsible for painting the data items, setting up the editor, giving the editor the data it needs, and getting the model to update based on changes in the editor. All delegates must have QAbstractItemDelegate as their base class. There are two concrete delegate classes created by Qt: QStyledItemDelegate and QItemDelegate. The default delegate used by Qt is QStyledItemDelegate because it uses the current style to paint the items, but you can inherit from any of these three class.

Which class to inherit from?

It is generally recommended to inherit from QStyledItemDelegate because it follows the current style used when drawing the data items, and will thus fit in better with the program. Additionally, the code required to use QStyledItemDelegate or QItemDelegate is approximately the same, and is less than that required for QAbstractItemDelegate in most cases. So our delegate, which I will call MyDelegate, will inherit from QStyledItemDelegate.

Qt Delegate: Paint

The purpose of the paint function in QAbstractItemDelegate is to draw the actual data items inside of the view. This is not for when the data is being edited (when they are, the editor is responsible for drawing), but is for when the data is just being displayed normally. For an example of how to customize this, see Qt’s Pixelator Example.

MyDelegate Setup

What we want to do with MyDelegate is allow the DataRow that is about to be edited set the data of the editor itself. Furthermore, we also want DataRow to get the edited data from the editor once the user is finished with the editor. Here is the complete cpp file and the complete header file for MyDelegate.

The declaration of MyDelegate is quite simple:

// Note: This class will only work when the used model is MyModel
class MyDelegate : public QStyledItemDelegate
{
public:
    explicit MyDelegate(QObject *parent = 0);

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;

    void setModelData(QWidget *editor, QAbstractItemModel *model,
        const QModelIndex &index) const override;
};

The constructors code is quite simple:

MyDelegate::MyDelegate(QObject *parent /*= 0*/)
    : QStyledItemDelegate(parent)
{
}
MyDelegate::setEditorData

The setEditorData function is called immediately after the editor is created, and this function is to provide the editor with the needed data.

void MyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    const MyModel* model = static_cast<const MyModel*>(index.model());

    model->SetEditorData(editor, index);
}

As you can see, setEditorData will simply pass the editor to the used instance of MyModel using the newly added function SetEditorData.

void MyModel::SetEditorData(QWidget *editor, const QModelIndex &index) const
{
    if (IndexIsValid(index))
        rowsInfo[index.row()]->SetEditorData(editor, index.column());
}

IndexIsValid is a convenience function that ensures the index is valid for use in the model: makes sure the index is itself valid, and that the row is inside of the number of rows. The signature of DataRow‘s SetEditorData function is:

virtual void SetEditorData(QWidget *editor, const int & column) const = 0;
Changes to NameNumberInfo

To better show off the abilities of using a delegate, NameNumberInfo will now store a changeable name, and it will also store a maximum and minimum value for the number.

class NameNumberInfo : public DataRow
{
public:
    NameNumberInfo(const QString & name, const int & number, const int & minimum, const int & maximum)
        : name(name),
        number(number),
        minimum(minimum),
        maximum(maximum)
        {}

    QVariant GetData(int column) const override;

    // Only two columns
    int GetColumnCount() const override { return 2; }

    void DataChanged(const int & column, const QVariant & value) override;

    bool CanBeChanged(const int & column) const override;

private:
    QString name;
    int number;

    const int maximum, minimum;

    static const int NAME_COLUMN = 0, NUMBER_COLUMN = 1;

    NameNumberInfo & operator=(NameNumberInfo & rhs) { return rhs; }
};

The only function that is changed (other than the constructor) is NameNumberInfo‘s CanBeChanged, this is because the NAME_COLUMN can now also be changed:

bool NameNumberInfo::CanBeChanged(const int & column) const
{
    return (column == NAME_COLUMN || column == NUMBER_COLUMN);
}

Now, we need to add the SetEditorData function to NameNumberInfo. Its declaration is

void SetEditorData(QWidget *editor, const int & column) const override;

What it needs to do is set the editor data based on which column the editor is being displayed in:

void NameNumberInfo::SetEditorData(QWidget *editor, const int & column) const
{
    switch (column)
    {
    case NAME_COLUMN:
    {
        QLineEdit * lineEdit = static_cast<QLineEdit *>(editor);
        lineEdit->setText(name);
        break;
    }

    case NUMBER_COLUMN:
    {
        QSpinBox * spinBox = static_cast<QSpinBox *>(editor);
        spinBox->setValue(number);
        spinBox->setMinimum(minimum);
        spinBox->setMaximum(maximum);
        break;
    }

    default:
        // Do nothing
        break;
    }
}

There are two different cases for the editor: one where it is in the NAME_COLUMN and thus assumed to be a QLineEdit, and one where it is in the NUMBER_COLUMN and thus assumed to be a QSpinBox: the editor will be set up with the essential information corresponding to the column it belongs to.

MyDelegate::setModelData

setModelData is actually an optional function to override for MyDelegate, and will depend upon the editor(s) that you use.

One reason you would need to override it is if you need to change how the data is received from the editor. For example QTextEdit will, by default, give the HTML version of its text (from toHtml) to the model, so you would want to instead use the toPlainText function of QTextEdit, which will be done by DataRow.

This function behaves nearly identically to setEditorData, but it will call different functions in MyModel and DataRow:

void MyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    MyModel* myModel = static_cast<MyModel*>(model);

    myModel->SetDataFromEditor(editor, index);
}
void MyModel::SetDataFromEditor(QWidget * editor, const QModelIndex & index)
{
    if (IndexIsValid(index))
        rowsInfo[index.row()]->SetDataFromEditor(editor, index.column());
}

setModelData will forward the editor and index to the instance of MyModel, and then MyModel will forward the editor and column to DataRow. We will need to add the function declaration

virtual void SetDataFromEditor(QWidget * editor, int column) = 0;

to DataRow.

Then, the implementation for NameNumberPair will just cast the editor to the type corresponding to the column, and update its information:

void NameNumberInfo::SetDataFromEditor(QWidget * editor, int column)
{
    switch (column)
    {
    case NAME_COLUMN:
    {
        QLineEdit * lineEdit = static_cast<QLineEdit *>(editor);
        DataChanged(column, lineEdit->toPlainText());
        break;
    }

    case NUMBER_COLUMN:
    {
        QSpinBox * spinBox = static_cast<QSpinBox *>(editor);
        DataChanged(column, spinBox->value());
        break;
    }
    }
}

MyDelegate createEditor

If you looked at Qt’s page on QStyledItemDelegate, you may have noticed that one of the virtual functions it has is createEditor.

The reason why we do not override the createEditor function of QStyledItemDelegate is because this implementation uses QItemEditorFactory to customize the created editor. However, if you wanted to override createEditor, you would just need to set up a similar architecture to setModelData. There are some advantages and disadvantages for both methods. A branch of this project that implements this change is also available on my github.

QItemEditorFactory vs MyDelegate createEditor

One of the problems with using QItemEditorFactory to change the editor created for certain data types (like QString) is that this change is program wide: you cannot change just one specific view. In addition, it may not always be obvious that QItemEditorFactory is being used in a large program and, if you use multiple different factories, then it may be difficult to determine which factory is actually being used. However, this issue of determining the factory being used can be alleviated with good naming and such. And finally, when changing QItemEditorFactory, you need to add all of the editors that may be used each time you want to change even one editor.

On the other hand, the code required to set up QItemEditorFactory is less than what is required to use createEditor with MyModel, and it reduces the number of functions in all the classes related to MyModel.

Deciding between using QItemEditorFactory or using createEditor basically depends on exactly what you need within your project and your personal preference.

Once again, all of the code for this project is available on my github.

If you still have any questions, please ask them in the comments. And if you have any suggestions, please comment or email me at mredshaw@diusrex.com.

Leave a Reply

Your email address will not be published. Required fields are marked *

[TOP]