PDA

View Full Version : Model/View, Delegates for my Data



kahlenberg
27th September 2018, 16:36
Hi,

I am quite new tho Model/View approach and I need your help. I am reading a text file and want to display data in a tableview. I already looked at example codes but they are very basic and my requirements are very different.

Text from file looks like:


Page Address Name
....
1 0000abda _CpuTimer1
1 0000abe2 _CpuTimer2
1 0000abea _CpuTimer0
1 0000abf2 _msg
1 0000abf4 _res
....

I store those information to a QVector of following typedef:


typedef struct St_Register
{
QString name;
quint16 page;
quint16 address;
quint32 currValue;
quint32 wrValue;
bool wrProtect;
} Register;
...
QVector<Register *> Registers;

But as soon as I store the data into Registers vector, It should update the table automatically.
I want to show Name, Page, Address, currValue information in the table as not editable strings. I want to show wrProtect as editable checkbox and wrValue as editable spinbox.
When I check the checkbox, the wrValue should be editable, otherwise not editable.
So, Is my data-model only this QVector? QVector<Register *> Registers;
Do I need customized checkbox delegate and spinbox delegate? If yes, how? How can I "connect" those specialized delegates to tableView (or to model)?
I need a step-by-step instruction for implementing of creating all required classes (pseude-code is enough).

Thanks.

d_stranz
28th September 2018, 00:10
First, unless you have some specific need, do not use QVector< pointer to thing >. If you do, it means you are responsible for maunally creating and deleting the items in your vector each time the vector content changes. Simply use QVector< thing > (in other words, QVector< Register >) and the Register instances will automatically be created, copied, and go in and out of scope without you having to worry about memory leaks.

You can use Qt::CheckStateRole to return an enum (as a QVariant) indicating whether the item should be checked or not. If you want the user to be able to edit the check state, then implement the QAbstractItemModel::flags() method to return Qt:ItemFlags that have the Qt::ItemIsUserCheckable bit set.

For the spin box, you will have to create a custom delegate. The Qt documentation contains an example (https://doc.qt.io/qt-5/qtwidgets-itemviews-spinboxdelegate-example.html). You may have to modify this example to support hexadecimal and to disable the ability for a user to type into it, although you could also set it up to allow typing, but then also implement a QValidator to prohibit any non-hex input. See here (https://stackoverflow.com/questions/16860490/qt-check-qstring-to-see-if-it-is-a-valid-hex-value) and QRegularExpressionValidator.

You will also need to implement QAbstractItemModel::setData() so that these changes are put back into your model.

kahlenberg
1st October 2018, 17:06
Thanks for reply.
I try checkbox column without a delegate. The column is shown with checkbox but with "true" "false" text as well, as you can see in the attachment. It is only double-clickable.
I also want to edit chekcbox with only one click. If I click double, it turns to false as in the attachment.
What I want is: Single-clickable checkbox without true-false text.
12976
12977


Registermodel.cpp


.....
QVariant RegisterModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();

switch (role) {
case Qt::DisplayRole:

switch (col) {
case 0:
return Registers[row].name; // QString
break;
case 1:
return Registers[row].page; // quint16
break;
case 2:
return QString("%1").arg(Registers[row].address, 8, 16, QChar('0')).toUpper(); // quint32
break;
case 3:
return QString("%1").arg(Registers[row].currValue, 8, 16, QChar('0')).toUpper(); // quint32
break;
case 4:
return Registers[row].wrProtect; // bool
break;
case 5:
return QString("%1").arg(Registers[row].wrValue, 8, 16, QChar('0')).toUpper(); // quint32
break;
default:
return QVariant();
break;
}

break;

case Qt::TextAlignmentRole:
if(col == 0)
return Qt::AlignLeft;
else
return Qt::AlignRight;
break;

case Qt::CheckStateRole:

if(col == 4)
{
if(Registers[row].wrProtect == true)
return Qt::Checked;
else
return Qt::Unchecked;
}

break;

case Qt::FontRole:
if(col != 0)
return QFont("Courier New", 10, QFont::Normal);
break;
default:
return QVariant();
break;
}

return QVariant();
}

bool RegisterModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role == Qt::EditRole)
{
setElement(index.row(), index.column(), value);
}

else if (role == Qt::CheckStateRole)
{

if((Qt::CheckState)value.toInt() == Qt::Checked)
{
setElement(index.row(), index.column(), value);
}
}
return true;

}

Qt::ItemFlags RegisterModel::flags(const QModelIndex &index) const
{
int col = index.column();
if (col == 5)
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
else if (col == 4)
return Qt::ItemIsUserCheckable | Qt::ItemIsEditable | QAbstractTableModel::flags(index);
else
return QAbstractTableModel::flags(index);
}

....



Do I need custom delegate? If yes how?

d_stranz
1st October 2018, 18:22
If you don't want the words "true" or "false", then you need to return QVariant() for the column 4 DisplayRole instead of "wrProtect", which the table will convert to "true" or "false".

The reason a double-click is needed is that for the table, the first click selects the item and activates the editor, the second click changes its state. You might be able to change this behavior by retrieving the QItemSelectionModel for the table view and connecting a slot to the QItemSelectionModel::currentChanged() signal. If the new current index is for column 4, change the state of the check box by changing the model and emitting dataChanged().

kahlenberg
2nd October 2018, 13:43
I added QVariant to the code but the table still displays boolean as ture or false.



QVariant RegisterModel::data(const QModelIndex &index, int role) const
{
...
case 4:
return QVariant(Registers[row].wrProtect); // bool
break;
...

case Qt::CheckStateRole:

if(col == 4)
{

if(Registers[row].wrProtect == true)
{
return QVariant(Qt::Checked);
}
else
{
return QVariant(Qt::Unchecked);
}
}
...
}


I want to use delegate. When I double-click the cell a new checkbox appers on the left in the cell. Checkbox is not displayed when I don't click.

checkboxdelegate.h


#ifndef CHECKBOXDELEGATE_H
#define CHECKBOXDELEGATE_H

#include <QStyledItemDelegate>
#include <QTableView>
#include <QObject>
#include <QDebug>
#include <QCheckBox>

#define DEB qDebug().noquote() <<

class CheckBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit CheckBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
QRect CheckBoxRect(const QStyleOptionViewItem &view_item_style_options) const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;

mutable QCheckBox *checkBox;

private slots:
void setData(bool value);
};

#endif // CHECKBOXDELEGATE_H


checkboxdelegate.cpp


#include "checkboxdelegate.h"
#include <QComboBox>
#include <QCheckBox>
#include <QRect>
#include <QPainter>
#include <QSpinBox>
#include <QApplication>
#include <QSignalMapper>
#include "booleanwidget.h"

CheckBoxDelegate::CheckBoxDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}


QWidget* CheckBoxDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{

checkBox = new QCheckBox(parent);
QObject::connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(setData(bool)));
return checkBox;
}

void CheckBoxDelegate::setEditorData ( QWidget *editor, const QModelIndex &index ) const
{

if(QCheckBox *cb = static_cast<QCheckBox *>(editor)) {

bool checked = index.model()->data(index, Qt::DisplayRole /* Qt::EditRole */).toBool();
DEB "Checked: " << checked;
cb->setChecked(checked);
}
}

void CheckBoxDelegate::setModelData ( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
{

if(QCheckBox *cb = static_cast<QCheckBox *>(editor))
// save the current text of the combo box as the current value of the item
model->setData(index, cb->isChecked());
else
QStyledItemDelegate::setModelData(editor, model, index);
}


void CheckBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {

editor->setGeometry(option.rect);
}

void CheckBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
}

void CheckBoxDelegate::setData(bool value)
{
emit commitData(checkBox);
}



What am I missing?

d_stranz
2nd October 2018, 17:09
I added QVariant to the code but the table still displays boolean as ture or false.

No, I meant return literally "QVariant()" (an empty QVariant instance). Returning QVariant( Qt::Checked ) still results in the table seeing a Boolean value instead of nothing.


Checkbox is not displayed when I don't click.

I think you are misunderstanding what a delegate does. Delegates are -only- used when editing an item in a table or tree view. When the editing is finished, the editor is destroyed and the model is updated. So if you create a checkbox delegate, it will be displayed only when you are actually editing the cell with the Boolean item, otherwise it will not be visible.

If you want something to be visible all of the time, you will have to implement the paint() method to draw the checkbox -image-.

Personally, I think you are chasing this too far. You don't need a delegate for checkboxes, since QTableView already provides one. Did you try my suggestion of connecting a slot to the QItemSelectionModel?