PDA

View Full Version : QTableView with a checkbox



maxpower
24th October 2006, 19:19
Is it possible for a QTableView showing a model from a MySQL DB to have a column with checkboxes that get turned into text (Y,N) in the DB? In other words, I would like a column that contains checkboxes instead of having the user have to enter Y or N.

mAx

wysota
24th October 2006, 21:57
Yes. You need to implement a custom delegate which will handle that.

KingFish
25th October 2006, 11:04
You don't need a new delegate for that. In your model, simply react to the Qt::CheckStateRole (in data() and setData() for the appropiate columns).

maxpower
25th October 2006, 16:05
What do you mean by "react to ...?" I have tried this:

model->setData(model->index(0, 7), "Y", Qt::CheckStateRole)

to no avail. I want the checkbox in column 7 which is recorded in the DB as Y or N. Is the above sorta right or do I have to subclass QSqlTableModel and reimplement the data and setdata functions?

Thanks
mAx

wysota
25th October 2006, 17:04
I'd stick with the delegate thing. Otherwise you'll have to reimplement the model without really needing it. The delegate will give you much more flexibility.

maxpower
27th October 2006, 17:51
Okay, so I see in the docs how to subclass ItemDelegate and make my own. However, it appears you assign the delegate for the whole view. I only want to change the delegate for one column. How is this accomplished?

mAx

jpn
27th October 2006, 17:56
Qt 4.2 introduces new functions:

QAbstractItemView::setItemDelegateForRow()
QAbstractItemView::setItemDelegateForColumn()


In Qt 4.1 you'll just have to check the column of the passed model index:


void MyItemDelegate::delegateFunction(const QModelIndex& index, ...)
{
if (index.isValid() && index.column() == INTERESTING_COLUMN)
{
// appropriate column in question, do the custom thing
}
else
{
// pass to the base class implementation
QItemDelegate::delegateFunction(index, ...);
}
}

maxpower
27th October 2006, 18:50
Okay, thanks for the help I am getting close now. I converted the included exmaple for the sponbox delegate to a checkbox delegate and compiled it and it worked great. However, once I changed the code fome setItemDelegate to setItemDelegateForColumn, it no longer worked at all. Do I need to change something in the example code for the custom delegate to work with just one column? I am using 4.2.

Thanks
mAx

maxpower
31st October 2006, 20:16
Anybody have an idea? I just tried 4.2.1 but no love there either.

mAx

jpn
31st October 2006, 21:03
Looks like QAbstractItemView::itemDelegateForRow/Column are not used pretty much for anything (yet?)... Anyway, I already gave you a solution which works with old good "normal" delegates.. and it has been discussed earlier (http://www.qtcentre.org/forum/f-qt-programming-2/t-delegates-and-table-1941.html#4).. :)

maxpower
1st November 2006, 16:24
Okay, I edited the spinbox example again and with your code it properly displays a checkbox in the first column. However, I tried to use the exact same code in my program and it doesn't work. The column and row headers are present but there is no data, nor checkboxes or even grid lines. What did i do wring?

Thanks
mAx

jpn
1st November 2006, 18:13
Does everything work correctly without the custom delegate? Which QItemDelegate functions have you actaully overridden? Did you by any chance override QItemDelegate::paint()?

maxpower
1st November 2006, 19:03
Yes, everything works fine if I do not use setItemDelegate. I overrode createEditor, setEditor, setModelData, and updateModelGeometry. As I said before, my code works fine by itself, just not inside my db program.

Applying the delegate:


if(viewInit)
{
model->submitAll();
delete model;
}
int rowCount = 0;
model = new QSqlTableModel();
model->setTable(tableName);
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
model->removeColumn(8);
rowCount = model->rowCount();
ui.tableTV->setModel(model);
checkboxDelegate delegate;
ui.tableTV->setItemDelegate(&delegate);
ui.tableTV->resizeColumnToContents(2);
ui.tableTV->hideColumn(0);
ui.tableTV->setEditTriggers(QAbstractItemView::CurrentChanged) ;
ui.tableTV->setAlternatingRowColors(true);
ui.tableTV->scrollToBottom();


The delegate:


checkboxDelegate::checkboxDelegate(QObject *parent)
: QItemDelegate(parent)
{
}

QWidget *checkboxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if(index.isValid() && index.column() == checkboxCol)
{
QCheckBox *editor = new QCheckBox(parent);
editor->installEventFilter(const_cast<checkboxDelegate*>(this));
return editor;
}
else
{
return QItemDelegate::createEditor(parent, option, index);
}
}

void checkboxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
if(index.isValid() && index.column() == checkboxCol)
{
QString value = index.model()->data(index, Qt::DisplayRole).toString();

QCheckBox *checkBox = static_cast<QCheckBox*>(editor);
if(value == "Y")
checkBox->setCheckState(Qt::Checked);
else
checkBox->setCheckState(Qt::Unchecked);
}
else
{
QItemDelegate::setEditorData(editor, index);
}
}

void checkboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
if(index.isValid() && index.column() == checkboxCol)
{
QCheckBox *checkBox = static_cast<QCheckBox*>(editor);
QString value;
if(checkBox->checkState() == Qt::Checked)
value = "Y";
else
value = "N";

model->setData(index, value);
}
else
{
QItemDelegate::setModelData(editor, model, index);
}
}

void checkboxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.isValid() && index.column() == checkboxCol)
editor->setGeometry(option.rect);
else
QItemDelegate::updateEditorGeometry(editor, option, index);

}

jpn
1st November 2006, 19:12
Allocate the delegate object on the heap so it won't get destroyed as soon as it gets out of scope:


checkboxDelegate* delegate = new checkboxDelegate(this);
ui.tableTV->setItemDelegate(delegate);


Edit: oops, forgot the important keyword new ;)

maxpower
1st November 2006, 19:32
That did it, thanks alot!!!!

mAx

Georgest
18th February 2007, 00:41
Is it possible for a QTableView showing a model from a MySQL DB to have a column with checkboxes that get turned into text (Y,N) in the DB? In other words, I would like a column that contains checkboxes instead of having the user have to enter Y or N.

mAx
Without delegates:


class MyModel : public QSqlQueryModel {
Q_OBJECT
public:
MyModel(QObject *parent = 0);
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
........
};

Qt::ItemFlags MyModel::flags(const QModelIndex &index) const {
Qt::ItemFlags flags = QSqlQueryModel::flags(index);
if (index.column() == aColWithCheckbox)
flags |= Qt::ItemIsUserCheckable;
else
flags |= Qt::ItemIsEditable;
return flags;
}

QVariant MyModel::data(const QModelIndex &index, int role) const {
QVariant value = QSqlQueryModel::data(index, role);
if (role == Qt::CheckStateRole && index.column() == aColWithCheckbox)
return (QSqlQueryModel::data(index).toInt() != 0) ? Qt::Checked : Qt::Unchecked;
else
return value;
}

wysota
18th February 2007, 01:14
You're changing the model here which is... well... at least controversial when it comes to the MVC paradigm.

BTW. The model is to be editable so you should subclass QSqlTableModel and also reimplement setData(). Now imagine someone wants to have another view (or access the database through the model) without the checkboxes... Or the column might allow NULLs :)

Of course I don't say that you can't do it this way, I only say you should do it with a delegate, as you'll be modifying only the user interaction this way. I wrote about it earlier in the thread.

Just for completeness - a third way would be to use a proxy model that would translate between the bool and the checkstate role. That would be my second best suggestion (as it doesn't change the source model).

Georgest
18th February 2007, 09:45
The bounds between View and Model are the conventionalities... Qt4 provides for changing font, color, alignment in model, so it can be useful.

For NULLs there is Qt::ItemIsTristate.

setData needs to be reimplemented of course:


bool MyModel::setData(const QModelIndex &index, const QVariant &value, int /* role */) {
if (index.column() ......)
return false;

QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);
int id = QSqlQueryModel::data(primaryKeyIndex).toInt();
//clear();
bool ok;
QSqlQuery query;
if (index.column() == 1) {
query.prepare("update employee set name = ? where id = ?");
query.addBindValue(value.toString());
query.addBindValue(id);
.......
}else if(index.column() == aColWithCheck) {
query.prepare("update employee set married = ? where id = ?");
query.addBindValue(value.toInt());
query.addBindValue(id);
}
ok = query.exec();
refresh();
return ok;
}


ItemIsUserCheckable in flags() and CheckStateRole in data() achieve the interesting feature: checkbox is clicked -> text label is changed in cell.

Proxy model 'Bool to CheckStateRole' is realy the best i think ;)

What about 'Qt Cookbook'?