qt - clickable checkbox for boolean column in QTableView - c++

I wanted to have a checkbox in one of the columns in my Table view. To be specific - in one of the rows, because the view is using a transposing proxy model. I did it using a QItemDelegate derivate class, as described in the doc, and here:
.h
class checkBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
checkBoxDelegate(QAbstractItemView* parentView = NULL, QObject *parent = NULL);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const Q_DECL_OVERRIDE;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
//QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
~checkBoxDelegate();
private:
QAbstractItemView* parentView;
};
.cpp
checkBoxDelegate::checkBoxDelegate(QAbstractItemView* parentView, QObject *parent)
: QItemDelegate(parent), parentView(parentView)
{
}
checkBoxDelegate::~checkBoxDelegate()
{
}
QWidget *checkBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QCheckBox *editor = new QCheckBox(parent);
editor->setTristate(false);
return editor;
}
void checkBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
bool value = index.model()->data(index, Qt::EditRole).toBool();
QCheckBox *locEdit = static_cast<QCheckBox*>(editor);
locEdit->setChecked(value);
}
void checkBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
QCheckBox *locEdit = static_cast<QCheckBox*>(editor);
bool value = locEdit->isChecked();
model->setData(index, value, Qt::EditRole);
}
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 {
QPalette::ColorGroup cg;
if (option.state & QStyle::State_Enabled) {
cg = (option.state & QStyle::State_Active) ? QPalette::Normal : QPalette::Inactive;
}
else
cg = QPalette::Disabled;
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.color(cg, QPalette::Highlight));
//if (! (parentView->editTriggers() > QAbstractItemView::NoEditTriggers && option.state & QStyle::State_Selected) )
drawCheck(painter, option, option.rect, index.data().toBool() ? Qt::Checked : Qt::Unchecked);
drawFocus(painter, option, option.rect);
}
//QSize checkBoxDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const {
// return QItemDelegate::sizeHint ( option, index );
//}
The delegete is bound to a row in my view
checkBoxDelegate* cBD = new checkBoxDelegate(ui.personalData);
ui.personalData->setItemDelegateForRow(4, cBD);
It "kind of" works, the check box appears - centered:
But when I edit that cell, the control returned by createEditor(...) is drawn next to original checkbox:
,
I achieved what I wanted by adding this line:
if (! (parentView->editTriggers() > QAbstractItemView::NoEditTriggers && option.state & QStyle::State_Selected) ) //this one
drawCheck(painter, option, option.rect, index.data().toBool() ? Qt::Checked : Qt::Unchecked);
in the paint(...) method .
I ended up with something like that:
if (! (option.state & QStyle::State_Selected) || option.state & QStyle::State_HasFocus)
drawCheck(painter, option, option.rect, index.data().toBool() ? Qt::Checked : Qt::Unchecked);
May seem strange, but I could't make it better. It appears, that Qt behaves kind of strange in this piece of code. When the table cell is selected (not in edit mode), it has option.state = State( "Active | Enabled | HasFocus | Selected" ). And when in edit mode, it has State( "Enabled | Selected" ) . Why no longer "Active" ? Why no longer HasFocus ? Ok, focus is probably passed to the QCheckBox. I expected, to have QStyle::State_Editing state - but it didn't
I'm still not sure if this is the best way to do it?

Related

How do I horizontally center DecorationRole icon in QTableView cell? [duplicate]

NetworkPageForm::NetworkPageForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::NetworkPageForm),
devicesModel(NULL)
{
ui->setupUi(this);
devicesModel = new QStandardItemModel(0, 4, parent);
devicesModel->setHeaderData(0, Qt::Horizontal, QObject::tr("IP"));
devicesModel->setHeaderData(1, Qt::Horizontal, QObject::tr("Name"));
devicesModel->setHeaderData(2, Qt::Horizontal, QObject::tr("Last Online"));
devicesModel->setHeaderData(3, Qt::Horizontal, QObject::tr("Status"));
ui->devicesTableView->setModel(devicesModel);
ui->devicesTableView->resizeColumnsToContents();
}
void NetworkPageForm::addDevice(const QString &ip, int device_type) {
bool haveSameItem = false;
for(int i=0; i<devicesModel->rowCount(); i++) {
QStandardItem * ipItem = devicesModel->item(i, 0);
QStandardItem * nameItem = devicesModel->item(i, 1);
if(QString::compare(ipItem->text(), ip)== 0 && QString::compare(nameItem->text(), deviceStr)==0) {
devicesModel->setData(devicesModel->index(i, 2), BaseModel::now());
haveSameItem = true;
}
}
if(!haveSameItem)
{
int last = devicesModel->rowCount();
devicesModel->insertRow(last);
devicesModel->setData(devicesModel->index(last, 0), ip);
devicesModel->setData(devicesModel->index(last, 1), device_type);
devicesModel->setData(devicesModel->index(last, 2), BaseModel::now());
devicesModel->setData(devicesModel->index(last, 3), QIcon(":/res/images/online_icon.png"), Qt::DecorationRole);
// This function does not work, the icon is algin left.
// devicesModel->item(last, 3)->setTextAlignment(Qt::AlignCenter);
}
ui->devicesTableView->resizeColumnsToContents();
}
Is there a way to set QIcon item center in QTableView?
Update:
I create my own QStyledItemDelegate sub class as #RazrFalcon answered.
#include <QStyledItemDelegate>
class MyDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private slots:
};
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
if(index.column() == 3) {
// TODO
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
QSize MyDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
if(index.column() == 3) {
// TODO
} else {
return QStyledItemDelegate::sizeHint(option, index);
}
}
void MyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
QStyledItemDelegate::setModelData(editor, model, index);
}
And set ui->devicesTableView->setItemDelegate(new MyDelegate);
Could someone help me how to set icon column center in QTableView?
There is no default way. You should implement your own QStyledItemDelegate.
UPD: example added
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
// disable default icon
opt.icon = QIcon();
// draw default item
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0);
const QRect r = option.rect;
// get pixmap
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
QPixmap pix = icon.pixmap(r.size());
// draw pixmap at center of item
const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2);
painter->drawPixmap(r.topLeft() + p, pix);
}
It's no need to do it in delegate. The delegate will make your style sheet unavailable.
You can paint the icon in the middle of a rectangle pixmap which is as same size as the cell, and return it in the QAbstractItemModel::data() function with Qt::DecorationRole. Like this:
Qt::DecoratoinRole:
QPixmap pixmap(w, h); //w=cell width, h=cell
pixmap.fill(Qt::transparent); // draw a transparent rectangle
QPixmap iconPixmap(":/xx.png");
QPainter painter(&pixmap);
//Calculate the center coordinate x,y for iconPixmap
painter.draw(x, y, iconWidth, iconHeight, iconPixmap);
return pixmap;

Override Text in QStyledItemDelegate for QTreeView

I'm having an issue with overriding the text displayed for a QTreeView using a QStyledItemDelegate. When some condition is met following code is executed:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
.
.
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
QString text = opt.text;
text = text + QString("TEST");
opt.text = text;
QStyledItemDelegate::paint(painter, opt, index);
}
I confirmed in the debbugger that TEST is added to opt.text.
However, when I run my program and look at the TreeVuew it is still displaying the original text without the TEST string appended.
It seems that when I call QStyledItemDelegate::paint(painter, opt, index), it's ignoring the change I've made to the opt parameter.
The default implementation of the QStyledItemDelegate::paint() method uses it's own QStyleOptionViewItem instance initialized with data from the model.
From the Qt 5.4.0 source code:
void QStyledItemDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
const QWidget *widget = QStyledItemDelegatePrivate::widget(option);
QStyle *style = widget ? widget->style() : QApplication::style();
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}
Solution:
Do not call the default implementation and implement your delegate's paint() method like this:
void MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyleOptionViewItem itemOption(option);
initStyleOption(&itemOption, index);
itemOption.text = "Test Text"; // override text
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter, nullptr);
}
The alternative solution, if you want to change the displayed text in a view, is to override displayText() method.
Example for Qt5:
mydelegate.h
virtual QString displayText(const QVariant &value,
const QLocale &locale) const override;
mydelegate.cpp
QString MyDelegate::displayText(const QVariant &value,
const QLocale &locale) const
{
Q_UNUSED(locale)
QString result = value.toString() + "TEST";
return result;
}
doc link: https://doc.qt.io/qt-5/qstyleditemdelegate.html#displayText
Depending what type of delegates it is, I would also try to override the setEditorData() method or even the createEditor() (where you can add values different from your model). It is less time consuming than doing such operation in paint.
Otherwise, you can use something like that to draw your text where you want:
painter->drawText(option.rect, Qt::AlignJustify, text + "_test");
You probably have a reason for doing so, but it seems like something is wrong in your design if you want to add extra text on the fly?
Possible QStyledItemDelegate::paint picks a text directly from index.data( Qt::DisplayRole ).toString(). That's why text is not changed. You may debug through Qt sources to be sure.
I propose you to use QIdentityProxyModel to do such things. Delegates are not designed for such solutions. You just need to override 1 method. So your code should look like this:
class MyProxyModel : public QIdentityProxyModel
{
// ...
};
QVariant MyProxyModel::data(const QModelIndex &index, int role) const override
{
if ( /*Conditions when you don't want to change source text*/ )
return QIdentityProxyModel::data( index, role );
// Extra check for editors or other roles to return original data
if ( role == Qt::EditRole || role != Qt::DisplayRole )
return QIdentityProxyModel::data( index, role );
const auto sourceIndex = mapToSource( index );
const auto originalText = sourceModel()->data( sourceIndex, Qt::DisplayRole ).toString();
const auto newText = QString( "%1 [TEST]" ).arg( originalText );
return newText;
}
// Usage
auto yourModel = YourOriginalModel( this );
auto proxy = MyProxyModel( this );
proxy->setSourceModel( yourModel );
view->setModel( proxy );

How can I set the background color of some special row in QTableView?

I read an older post however that is not work for me.
I would like to set the background color of every row whose 6th argument is true.
I tried to overwrite the Paint method in my subclass of QSqlRelationalDelegate but apparently it does not do anything.
MoviesDelegate::MoviesDelegate(QObject *parent)
: QSqlRelationalDelegate(parent)
{ }
void MoviesDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if( index.sibling( index.row(), 6 ).data().toBool() )
{
QStyleOptionViewItemV4 optionViewItem = option;
optionViewItem.backgroundBrush = QBrush( Qt::yellow );
drawDisplay( painter, optionViewItem,
optionViewItem.rect,index.data().toString() );
drawFocus( painter, optionViewItem, optionViewItem.rect);
}
else
QSqlRelationalDelegate::paint(painter, option, index);
}
How can I fix it?
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// Grab cell value and cast it to boolean
bool boolValue = index.model()->data(index).toBool();
// Paint cell background depending on the bool value
if(boolValue)
painter->fillRect(option.rect, QColor(179, 229, 255));
else
painter->fillRect(option.rect, Qt::red);
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}

QItemDelegate setModelData in QTreeWidget is not called

I am trying to set delegate to my QTreeWidget. The problem is that delegate setModelData is never called. createEditor and setEditorData are called.
Since the editor that I create is simple QLineEdit, commitData() signal doesn't have to be emitted. Also I tried to emit this signal just in case is needed , when editLine editingFinished() was emitted, but that doesn't solve the problem.
As I understand documentaion say that for simple widgets like QLineEdit setModelData should be called without emitting commitData signal, so the following code should work :
MyDlg::MyDlg()
{
mTreeWdg->setItemDelegate(new TestDelegate( this ));
}
MyDlg::OnTreeItemDoubleCliked(QTreeeWidget* item,int column)
{
if(column != 1) return;
item->setFlags(Qt::ItemIsEditable);
mTreeWdg->editItem(item,column);
}
TestDelegate::TestDelegate(QObject *parent )
:QItemDelegate(parent)
{
}
QWidget* TestDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if(index.column() == 1) // value column
{
QLineEdit* edit = new QLineEdit(parent);
return edit;
}
else return 0; // no editor attached
}
void TestDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(index.column() == 1)
{
QLineEdit* edit = static_cast<QLineEdit*> (editor);
edit->setText("damn");
}
}
void TestDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index)
{
if(index.column()!= 1)
return;
}
void TestDelegate::updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
void TestDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) must be const:
void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const
Look at the declaration above.

Mystery: In Qt, why would editorEvent be called, but not createEditor?

I'm subclassing QAbstractItemDelegate. This is my code. Suggestions are welcome:
QWidget *ParmDelegate::createWidget(Parm *p, const QModelIndex &index) const {
QWidget *w;
if (index.column() == 0) {
w = new QLabel(p->getName().c_str());
} else {
if (p->isSection())
return NULL;
w = p->createControl();
}
return w;
}
QWidget *ParmDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
cout << "createEditor called" << endl;
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
QWidget *retval = createWidget(p, index);
retval->setFocusPolicy(Qt::StrongFocus);
retval->setParent(parent);
return retval;
}
void ParmDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QRect rect(option.rect);
editor->setGeometry(QRect(QPoint(0,0), rect.size()));
}
void ParmDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
scoped_ptr<QWidget> w(createWidget(p, index));
if (!w)
return;
QRect rect(option.rect);
w->setGeometry(QRect(QPoint(0,0), rect.size()));
w->render(painter, rect.topLeft());
}
QSize ParmDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
scoped_ptr<QWidget> w(createWidget(p, index));
if (!w)
return QSize(0,0);
return w->sizeHint();
}
bool ParmDelegate::editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) {
cout << "editorEvent called" << endl;
return false;
}
When this is run, I only see that editorEvent gets called twice for every edit event -- no createEditor!
From Qt's AbstractItemDelegate documentation:
To provide custom editing, there are two approaches that can be used. The first approach is to create an editor widget and display it directly on top of the item. To do this you must reimplement createEditor() to provide an editor widget, setEditorData() to populate the editor with the data from the model, and setModelData() so that the delegate can update the model with data from the editor.
The second approach is to handle user events directly by reimplementing editorEvent().
This appears to say that you are missing something to trigger the first approach. My guess is that your model's data() function isn't returning the proper value for the Qt::EditRole option.
I had implemented a TableView which i had inhertied from QItemDelegate. Then i had similar problem. I tracked it down to not calling 'return QItemDelegate::editorEvent(event, model, option, index);' in the editorEvent(...) method.
You can try this. Maybe it helps.