Qt: Space between individual cells in QTableWidget - c++

(Using Qt 5.5.1 on Windows 8.1)
I have a table displaying images selected by the user.
I know their are many other ways to display multiple images on a GUI, but I'm new to Qt, so I didn't understand how to use QGraphicsView and found the following to be the easiest way.
But this forms a table in which images are not separated. I want some space b/w them. See how the next images starts right where the first ends.
How can I do it so that next image starts after leaving some space?
I need those checkboxes too (they came bu default by using QTableWidget) because after adding images, I want user to select them too for further processing.
main.cpp
QFileDialog dialog(this);
dialog.setNameFilter(tr("Images (*.jpg)"));
dialog.setFileMode(QFileDialog::ExistingFiles);
QStringList all_filenames = dialog.selectedFiles();
int maxCol = 3;
int maxRows = all_filenames.size() / maxCol;
ui->tableWidget->setColumnCount(maxCol);
ui->tableWidget->setRowCount(maxRows);
int remainder = all_filenames.size() % maxCol;
if (remainder != 0)
{
maxRows +=1;
}
ui->tableWidget->horizontalHeader()->setDefaultSectionSize(200);
ui->tableWidget->verticalHeader()->setDefaultSectionSize(200);
if(all_filenames.isEmpty() == 0)
{
for( int i = 0; i < all_filenames.size() ; ++i)
{
QPixmap map(all_filenames.at(i));
map = map.scaled(200,200,Qt::IgnoreAspectRatio,Q::FastTransformation);
QBrush brush(map);
QTableWidgetItem* item = new QTableWidgetItem();
item->setCheckState(Qt::CheckState());
item->setBackground(brush);
ui->tableWidget->setItem(j,k,item);
k++;
if ( k == maxCol )
{
j++;
k = 0;
}
}
}
EDIT
cmargindelegate.cpp
#ifndef CMARGINDELEGATE_H
#define CMARGINDELEGATE_H
#include <QItemDelegate>
class CMarginDelegate : public QItemDelegate
{
public:
explicit CMarginDelegate(int margin, QObject* parent);
~CMarginDelegate();
private:
int m_margin;
public slots:
void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index);
};
#endif // CMARGINDELEGATE_H
cmargindelegate.cpp
#include "cmargindelegate.h"
#include <QItemDelegate>
CMarginDelegate::CMarginDelegate(int margin, QObject*parent):QItemDelegate(parent),m_margin(margin)
{}
CMarginDelegate ::~CMarginDelegate()
{}
void CMarginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
QStyleOptionViewItem itemOption(option);
// Make the 'drawing rectangle' smaller.
itemOption.rect.adjust(m_margin, m_margin, -m_margin, -m_margin);
QItemDelegate::paint(painter, itemOption, index);
}

Implement a custom delegate to draw your table cells. You can start with this one:
class CMarginDelegate : public QItemDelegate
{
public:
CMarginDelegate(int margin, QObject* parent)
: QItemDelegate(parent),
m_margin(margin)
{}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem itemOption(option);
// Make the 'drawing rectangle' smaller.
itemOption.rect.adjust(m_margin, m_margin, -m_margin, -m_margin);
QItemDelegate::paint(painter, itemOption, index);
}
private:
int m_margin;
};
And this is how to use it:
ui->tableWidget->setItemDelegate(new CMarginDelegate(5, ui->tableWidget));

Related

Customizing QTableView grid style

I'm wondering several things. I have subclassed QTableView to make a custom table. I'd like to be able to do several things.
First of all, I wanted the selected cells not to all have the "selected" color (blue by default) but instead have a frame around the selected cells (just like in Excel). To do this, I've used the following (in my custom QItemDelegate):
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QModelIndex upIndex = index.sibling(index.row() - 1, index.column());
QModelIndex downIndex = index.sibling(index.row() + 1, index.column());
QModelIndex rightIndex = index.sibling(index.row(), index.column() + 1);
QModelIndex leftIndex = index.sibling(index.row(), index.column() - 1);
auto newOption = option;
if (option.state.testFlag(QStyle::State_Selected))
{
painter->save();
auto selIndexes = selM->selectedIndexes().toSet();
painter->setPen(QPen(Qt::red, 5));
if (!selIndexes.contains(rightIndex))
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
if (!selIndexes.contains(upIndex))
painter->drawLine(option.rect.topLeft(), option.rect.topRight());
if (!selIndexes.contains(downIndex))
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
if (!selIndexes.contains(leftIndex))
painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
painter->restore();
// newOption.palette.setBrush(QPalette::Normal, QPalette::Highlight, index.data(Qt::BackgroundRole).value<QColor>());
newOption.palette.setBrush(QPalette::Normal, QPalette::Highlight, Qt::gray);
}
QStyledItemDelegate::paint(painter, newOption, index);
}
This is probably not optimal, but I'd like to have something that works, before anything else.
Now it seems to be working, but it unfortunately isn't automatically repainted. What happens when I select cells is the following:
Which is not what I'm looking for at all. I guess it's not being repainted because (1) The points inside the zone are still red and (2) If I resize my window, I get the following:
Which is way closer to what I'm trying to achieve.
I've already tried to do this in my QTableView:
// selModel is my selection model
connect(selModel, &QItemSelectionModel::selectionChanged, [this]() {
for(const auto & selected : selModel->selectedIndexes())
{
update(visualRect(selected));
repaint(visualRect(selected));
}
}
(Before, I actually used setDirtyRegion but it didn't work either, so I figured I'd do something more... brutal.)
Finally, I have another question: why do I get those weird red "small lines" in my cell corners? Even on the "kind of" correct screenshot I get these lines that I can't explain:
Please suggest if you have any ideas for any of the issues.
The problem can be easily explained as follows.
Assume that the cell (0,0) is selected. Now the user selects the additional
cells (0,1), (1,0) and (1,1). Qt correctly repaints the additional 3 cells, that
became selected. But, it doesn't repaint the cell (0,0), as it was neither
selected nor deselected. Of course for your desired behavior you still need to redraw this cell.
This can easily be achieved by just redrawing all indices of your current selection.
MyDelegate.h
#pragma once
#include <QStyledItemDelegate>
#include <QItemSelectionModel>
class MyDelegate : public QStyledItemDelegate {
public:
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setSelectionModel(QItemSelectionModel* selectionModel);
private:
QItemSelectionModel* mSelectionModel{ nullptr };
};
MyDelegate.cpp
#include "MyDelegate.h"
#include <QPainter>
#include <QDebug>
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (!mSelectionModel) return;
auto newOption = option;
auto normalText = newOption.palette.brush(QPalette::ColorGroup::Normal, QPalette::ColorRole::Text);
newOption.palette.setBrush(QPalette::ColorGroup::Normal, QPalette::ColorRole::Highlight, QBrush(Qt::GlobalColor::blue, Qt::BrushStyle::NoBrush));
newOption.palette.setBrush(QPalette::ColorGroup::Normal, QPalette::ColorRole::HighlightedText, normalText);
QStyledItemDelegate::paint(painter, newOption, index);
QModelIndex upIndex = index.sibling(index.row() - 1, index.column());
QModelIndex downIndex = index.sibling(index.row() + 1, index.column());
QModelIndex rightIndex = index.sibling(index.row(), index.column() + 1);
QModelIndex leftIndex = index.sibling(index.row(), index.column() - 1);
//auto newOption = option;
//newOption.palette.setBrush(QPalette::Normal, QPalette::Highlight, Qt::transparent);
if (option.state.testFlag(QStyle::State_Selected))
{
painter->save();
painter->setClipRect(option.rect);
auto selIndexes = mSelectionModel->selectedIndexes().toSet();
painter->setPen(QPen(Qt::red, 5));
if (!selIndexes.contains(rightIndex))
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
if (!selIndexes.contains(upIndex))
painter->drawLine(option.rect.topLeft(), option.rect.topRight());
if (!selIndexes.contains(downIndex))
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
if (!selIndexes.contains(leftIndex))
painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
painter->restore();
}
}
void MyDelegate::setSelectionModel(QItemSelectionModel* selectionModel)
{
mSelectionModel=selectionModel;
}
main.cpp
#include <QApplication>
#include <QDebug>
#include <QStandardItemModel>
#include <QTableWidget>
#include "MyDelegate.h"
int main(int argc, char** args) {
QApplication app(argc, args);
auto widget = new QTableView;
QStandardItemModel model;
model.setRowCount(10);
model.setColumnCount(10);
for (auto i = 0; i < 10; i++) {
for (auto j = 0; j < 10; j++) {
model.setItem(i, j, new QStandardItem("Test"));
}
}
auto selModel = new QItemSelectionModel;
selModel->setModel(&model);
widget->setModel(&model);
widget->setSelectionModel(selModel);
auto delegate = new MyDelegate;
delegate->setSelectionModel(selModel);
widget->setItemDelegate(delegate);
// Ensures that also items are repainted, that where neither selected nor deselect, but will stay selected
// This solutions eventually paints elements twice
QObject::connect(selModel, &QItemSelectionModel::selectionChanged, [widget,selModel](auto &selected, auto& deselected) {
for (auto item : selModel->selectedIndexes()) {
widget->update(item);
}
});
widget->show();
app.exec();
}
As for the strange red line artifacts, you should paint the red line inside the allowed rectangle I guess. This is easily achieved by clipping to the item boundaries.

How to pass images on QGraphicsView to QTableView programmatically using QPushButton

I have been researching for a while how to store images loaded on a QGraphicsView into rows of a QTableView using a QPushButton in a programmatic way but the information I found so far are not that many.
I have 1 QGraphicsView, a QPushButton (Send button) and a QTableView and a QLineEdit. When I upload images using the load button I show them both on the QGraphicsView and on the QLineEidt (I show the path of the image), if I click the Send button, the text of the QLineEdit should be added in the first row of the QTableView (which is happening) and the image should be stored inside the QTableView.
However, the image on the QGraphicsView is not being stored to the QTableView and nothing is being passed.
Currently this is what happens:
The expected behavior would be:
I created an ItemDelegate class that takes care of the resizing of the image on the QGraphicsView to be stored inside the QTableView
That part is shown below:
This is the mainwindow.h
#include <QGraphicsView>
#include <QGraphicsScene>
#include "imagedelegate.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void addData();
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
private slots:
void on_sendBtn_clicked();
void on_loadBtn_clicked();
private:
Ui::MainWindow *ui;
QStandardItemModel *model;
QGraphicsScene *leftScene;
};
#endif // MAINWINDOW_H
and here is the mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "imagedelegate.h"
#include <QGraphicsPixmapItem>
#include <QBuffer>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
leftScene = new QGraphicsScene(this);
ui->graphicsView->setScene(leftScene);
ui->graphicsView->show();
model = new QStandardItemModel();
ui->tableView->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addData()
{
// Path on the first column
QStandardItem *pathAItem = new QStandardItem(ui->lineEdit->text());
// Image on the second column - not working yet
//QStandardItem *image1 = new QStandardItem(/*ui->graphicsView->*/);
QPixmap image1;
QByteArray img1Array;
QBuffer buffer1(&img1Array);
buffer1.open(QIODevice::WriteOnly);
image1.save(&buffer1, "PNG");
QList<QStandardItem*> row;
row << pathAItem;
model->setColumnCount(1);
model->appendRow(row);
}
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
// QStyledItemDelegate::paint(painter, option, index);
}
}
The entire code will compile if you copy and paste so that you can see the issue I have.
Please shed light on this matter.
I wanted to answer to this question hoping that could also be useful to others. As suggested by Jeremy Friesner, the best (and fast in comparison to a QItemDelegate) way to send images into a QTableView using a QPushButton is to modify the void MainWindow::addData() function by using a QImage and pass it to a setData(QVariant(QPixmap::fromImage), Qt::DecorationRole) so that the entire function can be written as follows:
FIRST OPTION:
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::addData()
{
QStandardItem *pathAItem = new QStandardItem(ui->pathLineEdit_A->text());
QStandardItem *pathBItem = new QStandardItem(ui->pathLineEdit_B->text());
QImage image1(ui->graphicsViewLeft->grab().toImage());
QStandardItem *item1 = new QStandardItem();
item1->setData(QVariant(QPixmap::fromImage(image1.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QImage image2(ui->graphicsViewRight->grab().toImage());
QStandardItem *item2 = new QStandardItem();
item2->setData(QVariant(QPixmap::fromImage(image2.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QList<QStandardItem*> row;
row << pathAItem << pathBItem << item1 << item2;
model->appendRow(row);
}
SECOND OPTION
If it is necessary to use a QItemDelgate I am posting that part of the code too (it is working as I already tried it):
In the imagedelegate.h is necessary to provide a QSize as follows:
class ImageDelegate : public QStyledItemDelegate
{
public:
ImageDelegate(QObject * parent = nullptr);
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
After that on your imagedelegate.cpp the implementation is:
#include "imagedelegate.h"
ImageDelegate::ImageDelegate(QObject * parent) : QStyledItemDelegate(parent)
{}
QSize ImageDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return QSize(32,32);
Q_UNUSED(option);
Q_UNUSED(index);
}
void ImageDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
qDebug() << (index.model()->headerData(index.column(), Qt::Horizontal).toString());
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1" || colName == "image2")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
In my case I had two columns in which I needed to save the images, so you can expand it for how many columns as you like and I also set a QSize of (32,32) but this is up to the developer.
I hope this will save your programming time and this is the final result! :)

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;

Qt Using Custom QItemDelegate for QTableView

I followed the Spin Box Delegate tutorial, which Qt provides, to try to implement my own QItemDelegate. It would be used to specify a QComboBox to represent data in a QTableView cell but it is not working.
My biggest problem is that I don't know when my QItemDelegate is going to be utilized.
when itemModel->setData() is used or when itemModel->setItem(). I would suspect setItem() because I reimplemented a QItemDelegate (emphasis on the "Item") but the tutorial uses setData() and it works fine.
I know that if the specified QItemDelegate does not work it uses the default one but how do I now that the one I specified did not work?
when should I suspect for QTableView to use my delegate. I would like to specify which delegates to use for each cell. Is this possible or does the QTableView only use one delegate throughout?
How would I specify the items to populate the QComboBox once it gets displayed by the QTableView?
I implemented QItemDelegate here:
the part where I try to add the cell which is suppose to use the QComboBox is under the comment "Enabled" in mainwindow.cpp further down this post.
qcomboboxitemdelegate.h
#ifndef QCOMBOBOXITEMDELEGATE_H
#define QCOMBOBOXITEMDELEGATE_H
#include <QItemDelegate>
#include <QComboBox>
class QComboBoxItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit QComboBoxItemDelegate(QObject *parent = 0);
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index);
void setEditorData(QWidget *editor, const QModelIndex &index);
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index);
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index);
signals:
private:
};
#endif // QCOMBOBOXITEMDELEGATE_H
qcomboboxitemdelegate.cpp
#include "qcomboboxitemdelegate.h"
#include <QDebug>
QComboBoxItemDelegate::QComboBoxItemDelegate(QObject *parent)
: QItemDelegate(parent)
{
}
QWidget* QComboBoxItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) {
// create widget for use
QComboBox* comboBox = new QComboBox(parent);
return comboBox;
}
void QComboBoxItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) {
// update model widget
QString value = index.model()->data(index, Qt::EditRole).toString();
qDebug() << "Value:" << value;
QComboBox* comboBox = static_cast<QComboBox*>(editor);
comboBox->setCurrentIndex(comboBox->findText(value));
}
void QComboBoxItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) {
// store edited model data to model
QComboBox* comboBox = static_cast<QComboBox*>(editor);
QString value = comboBox->currentText();
model->setData(index, value, Qt::EditRole);
}
void QComboBoxItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) {
editor->setGeometry(option.rect);
}
mainwindow.cpp : this is where I initialize the QStandardItemModel
void MainWindow::init() {
itemModel = new QStandardItemModel(this);
}
void MainWindow::setupUi() {
this->setWindowTitle("QAlarmClock");
QStringList labelList;
labelList << "Alarm Name" << "Time" << "Enabled";
itemModel->setHorizontalHeaderLabels(labelList);
ui->tableView->setModel(itemModel);
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->tableView->setItemDelegate(comboBoxItemDelegate);
}
void MainWindow::on_actionNew_triggered() {
alarmDialog = new AlarmDialog(this);
connect(alarmDialog, SIGNAL(on_close()), this, SLOT(on_alarmDialog_close()));
alarmDialog->exec();
}
mainwindow.cpp : this is where I update QStandardItemModel
void MainWindow::on_alarmDialog_close() {
QString alarmName = alarmDialog->getAlarmName();
QDateTime alarmDateTime = alarmDialog->getDateTime();
itemModel->insertRow(itemModel->rowCount());
int rowCount = itemModel->rowCount();
// Alarm Name
QStandardItem* alarmItem = new QStandardItem(QIcon("res/alarmclock.ico"), alarmName);
itemModel->setItem(rowCount - 1 , 0, alarmItem);
// Date Time
QStandardItem* dateTimeItem = new QStandardItem();
dateTimeItem->setText(alarmDateTime.toString());
dateTimeItem->setEditable(false);
itemModel->setItem(rowCount - 1, 1, dateTimeItem);
// Enabled
QStandardItem* enabledItem = new QStandardItem();
QList<QStandardItem*> optionList;
optionList << new QStandardItem("Enabled") << new QStandardItem("Disabled");
enabledItem->appendRows(optionList);
itemModel->setItem(rowCount - 1, 2, enabledItem);
}
Edit 1
qcomboboxdelegate.cpp
QWidget* QComboBoxItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) {
// create widget for use
qDebug() << "Column: " << index.column();
if (index.column() == 2) {
QComboBox* comboBox = new QComboBox(parent);
QStringList values;
values << "Enabled" << "Disabled";
comboBox->addItems(values);
return comboBox;
} else {
return QItemDelegate::createEditor(parent, option, index);
}
}
mainwindow.cpp
void MainWindow::on_alarmDialog_close() {
QList<QStandardItem*> row;
QString alarmName = alarmDialog->getAlarmName();
QDateTime alarmDateTime = alarmDialog->getDateTime();
QString status = "Enabled";
// Alarm Name
QStandardItem* alarmItem = new QStandardItem(QIcon("res/alarmclock.ico"), alarmName);
row << alarmItem;
// Date Time
QStandardItem* dateTimeItem = new QStandardItem();
dateTimeItem->setText(alarmDateTime.toString());
dateTimeItem->setEditable(false);
row << dateTimeItem;
// Enabled
QStandardItem* statusItem = new QStandardItem(status);
row << statusItem;
itemModel->appendRow(row);
}
First, you should have a description of your model columns:
enum Columns
{
COL_NAME,
COL_TIME,
COL_STATUS
}
Your delegate should only work for the last column.
Here is an example of how you can populate your model:
for (int i = 0; i < 5; ++i)
{
QStandardItem *itemName = new QStandardItem(QString("name %1").arg(i));
QStandardItem *itemTime = new QStandardItem(QString("time %1").arg(i));
QString status;
if (i % 2 == 0)
{
status = "Enabled";
}
else
{
status = "Disabled";
}
QStandardItem *itemStatus = new QStandardItem(status);
QList<QStandardItem*> row;
row << itemName << itemTime << itemStatus;
model->appendRow(row);
}
As I said, your delegate should only work for the last column. So all methods you have reimplemented should have a column check like this:
QWidget* QComboBoxItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if (index.column() == COL_STATUS)
{
QStringList values;
values << "Enabled" << "Disabled";
QComboBox* comboBox = new QComboBox(parent);
comboBox->addItems(values);
return comboBox;
}
else
{
return QItemDelegate::createEditor(parent, option, index);
}
}
You should add this check to the other methods: if the current column is not the status column, the base class (QItemDelegate) implementation should be used.
Then you set your delegate to your view:
ui->tableView->setItemDelegate(new ComboBoxDelegate);
If you do everything right, a combo Box will appear in the last column if you try to edit its values.
Even more simply; I found QTableView::setItemDelegateForColumn() to work admirably for a column. For example, in your MainWindow, you could make a member:
QComboBoxItemDelegate dgtComboDelegate;
Then, in your ctor, or init(), you could have
ui->tableView->setItemDelegateForColumn(2, dgtComboDelegate);
If you wanted that to happen for a single cell, that's when you need to test on the index.column() and index.row().
You know, you don't have to create a QTableView to do this either. E.g., see the ?:
Qt - Centering a checkbox in a QTable
The OP doesn't give the declaration for a table widget or view; but it does have the QTableView tag. It should work equally well for either.
In the former case, you can do ui->tableWidget->setItemDelegateForColumn(2, dgtComboDelegate); and never have to make your own model. Just use setData() on the items you create (or even later, for that matter,) to initialize their values.
So I figured out that I did not override the correct function prototypes..! I forgot that they
had const in the prototype meaning that I was not overriding any functions so it was using the default ones. Here are the correct virtual functions that have to be re-implemented: http://qt-project.org/doc/qt-5.0/qtwidgets/qitemdelegate.html

How to interact with checkbox actions ? (QTableView with QStandardItemModel)

I'm using QTableView and QStandardItemModel to show some data.
For each row, there is a column which has a check Box, this check box is inserted by setItem, the code is as follows:
int rowNum;
QStandardItemModel *tableModel;
QStandardItem* __tmpItem = new QStandardItem();
__tmpItem->setCheckable(true);
__tmpItem->setCheckState(Qt::Unchecked);
tableModel->setItem(rowNum,0,__tmpItem);
Now I want to interact with the check box. If a check box changes its state by user (from checked to unchecked or vice versa), I want to do something on the corresponding data row.
I know I can use signal-slot to catch the change of checkbox, but since there are lots of data row, I don't want to connect each row one by one.
Is there anyway to interact with the check action more effectively? Thanks :)
I don't deal with QTableView+QStandardItemModel, but may be example below will help you:
1). table.h file:
#ifndef TABLE__H
#define TABLE__H
#include <QtGui>
class ItemDelegate : public QItemDelegate
{
public:
ItemDelegate(QObject *parent = 0)
: QItemDelegate(parent)
{
}
virtual void drawCheck(QPainter *painter, const QStyleOptionViewItem &option,
const QRect &, Qt::CheckState state) const
{
const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
QRect checkRect = QStyle::alignedRect(option.direction, Qt::AlignCenter,
check(option, option.rect, Qt::Checked).size(),
QRect(option.rect.x() + textMargin, option.rect.y(),
option.rect.width() - (textMargin * 2), option.rect.height()));
QItemDelegate::drawCheck(painter, option, checkRect, state);
}
virtual bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_ASSERT(event);
Q_ASSERT(model);
// make sure that the item is checkable
Qt::ItemFlags flags = model->flags(index);
if (!(flags & Qt::ItemIsUserCheckable) || !(flags & Qt::ItemIsEnabled))
return false;
// make sure that we have a check state
QVariant value = index.data(Qt::CheckStateRole);
if (!value.isValid())
return false;
// make sure that we have the right event type
if (event->type() == QEvent::MouseButtonRelease) {
const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
QRect checkRect = QStyle::alignedRect(option.direction, Qt::AlignCenter,
check(option, option.rect, Qt::Checked).size(),
QRect(option.rect.x() + textMargin, option.rect.y(),
option.rect.width() - (2 * textMargin), option.rect.height()));
if (!checkRect.contains(static_cast<QMouseEvent*>(event)->pos()))
return false;
} else if (event->type() == QEvent::KeyPress) {
if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space
&& static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
return false;
} else {
return false;
}
Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked
? Qt::Unchecked : Qt::Checked);
QMessageBox::information(0,
QString((state == Qt::Checked) ? "Qt::Checked" : "Qt::Unchecked"),
QString("[%1/%2]").arg(index.row()).arg(index.column()));
return model->setData(index, state, Qt::CheckStateRole);
}
virtual void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const
{
QItemDelegate::drawFocus(painter, option, option.rect);
}
};
static int ROWS = 3;
static int COLS = 1;
class Table : public QTableWidget
{
Q_OBJECT
public:
Table(QWidget *parent = 0)
: QTableWidget(ROWS, COLS, parent)
{
setItemDelegate(new ItemDelegate(this));
QTableWidgetItem *item = 0;
for (int i=0; i<rowCount(); ++i) {
for (int j=0; j<columnCount(); ++j) {
setItem(i, j, item = new QTableWidgetItem);
item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsUserCheckable);
item->setCheckState((i+j) % 2 == 0 ? Qt::Checked : Qt::Unchecked);
}
}
}
};
#endif
2). main.cpp file:
#include <QApplication>
#include "table.h"
int main(int argc, char **argv)
{
QApplication a(argc, argv);
Table w;
w.show();
return a.exec();
}
Good luck.
PS: here is the original text.
handle the click event, there you will get the modelindex, get the data and modify the same
if you are going to insert more than one text or icon, then you need to set the delegate for your listview
Here is another similar idea of the example suggested by mosg using a QStyleItemDelegate instead. http://developer.qt.nokia.com/faq/answer/how_can_i_align_the_checkboxes_in_a_view
Use on clicked slot and index.data(Qt::CheckStateRole) :
void MainWindow::on_tableView_clicked(const QModelIndex &index)
{
if(index.column() == 2){
if(index.data(Qt::CheckStateRole) == Qt::Checked){
//your code
}else if(index.data(Qt::CheckStateRole) == Qt::Unchecked){
//your code
}
}
//get other infos
QVariant value = index.sibling(index.row(),0).data();
QString selectedMessageName = value.toString();
}