Size of editor in QItemDelegate - c++

I have a custom Delegate, subclassed from QItemDelegate, that provides a QComboBox in the very first column and a QLineEdit in all other columns.
SensorDisplayDelegate::SensorDisplayDelegate(QObject *parent) :
QItemDelegate(parent)
{}
QWidget *SensorDisplayDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = new QComboBox(parent);
connect(comboBox, SIGNAL(activated(int)), this, SLOT(setData(int)));
comboBox->setEditable(false);
//comboBox->setMaximumSize(editorSize);
comboBox->setInsertPolicy(QComboBox::NoInsert);
currentComboBox = comboBox;
return comboBox;
}
else
{
QLineEdit *lineEdit = new QLineEdit(parent);
return lineEdit;
}
return NULL;
}
void SensorDisplayDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = static_cast<QComboBox*>(editor);
QStringList comboItems = index.data(Qt::EditRole).toStringList();
comboBox->addItem("Add New Sensor");
comboBox->addItems(comboItems);
QCompleter *completer = new QCompleter(comboItems);
completer->setCaseSensitivity(Qt::CaseInsensitive);
comboBox->setCompleter(completer);
comboBox->showPopup();
}
else
{
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
lineEdit->setText(index.data(Qt::EditRole).toString());
lineEdit->show();
}
}
void SensorDisplayDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = static_cast<QComboBox*>(editor);
if(comboBox->currentIndex() == 0)
emit addNewSensor();
else
emit populateSensorView(comboBox->currentText());
}
else
{
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
model->setData(index, QVariant(lineEdit->text()));
}
}
void SensorDisplayDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
QSize SensorDisplayDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
return editorSize;
}
void SensorDisplayDelegate::setData(int option)
{
emit commitData(currentComboBox);
emit closeEditor(currentComboBox);
}
The editTrigger has been set to selectClicked. I want the combo box to cover the entire cell in the QTableView. However, now it just appears as blip on the left hand corner. I tried setting the minimum size by passing the cell size through an event filter that listens for mousePressed on QTableView. However, the corresponding slot in the delegate is never called. Here's the code:
MultiEventFilter.cpp :
bool MultiEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
{
if(obj->objectName() == "sensorlocationTableView")
{
QTableView *sensorView = static_cast<QTableView*>(obj);
QModelIndexList idxs = sensorView->selectionModel()->selectedIndexes();
if(!idxs.empty())
{
QModelIndex idx = idxs.at(0);
emit passCellSize(QSize(sensorView->columnWidth(idx.column()),
sensorView->rowHeight(idx.row())));
}
}
}
return false;
}
installed on qApp.
MainWindow.cpp:
eFilter = new MultiEventFilter();
connect(eFilter, SIGNAL(passCellSize(QSize)),
sensor_display_delegate, SLOT(setEditorSize(QSize)));
SensorDisplayDelegate.cpp slot:
void SensorDisplayDelegate::setEditorSize(const QSize &size)
{
editorSize = size;
}
where QSize editorSize is a private member.
How can I set the size of the editor correctly? I need something general that can be applied to the QLineEdit editors as well.
Also, is it necessary to explicitly emit commitData() when the editor is closed? I have not seen this done in any example codes involving QComboBox.

I suspect your eventFilter is intercepting the click events before the selection indexes have been set. So, you're effectively always hitting an empty idxs IndexList?
Try replacing that loop with something like:
bool MultiEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
{
if(obj->objectName() == "sensorlocationTableView")
{
emit locationTableViewClicked();
}
}
return false;
}
....
connect(eFilter, SIGNAL(locationTableViewClicked()),
sensor_display_delegate, SLOT(setEditorSize()));
...
void SensorDisplayDelegate::setEditorSize()
{
QModelIndexList idxs = sensorView->selectionModel()->selectedIndexes();
if(!idxs.empty())
{
QModelIndex idx = idxs.at(0);
editorSize = QSize(sensorView->columnWidth(idx.column()),
sensorView->rowHeight(idx.row()));
}
}

Related

Customization of user editable checkboxes implemented via QAbstractItemModel in QTreeView

I have QTreeView with QAbstractItemModel. Some particular columns are supposed to have user defined checkboxes. I have done so by overriding QAbstractItemModel::data() function and by sending check state for Qt::CheckStateRole role as shown in the code.
I am getting checkboxes and am able to check and uncheck them successfully.
But the requirement is to customize some of these checkboxes. Basically I need to differentiate some checkboxes from the others by any method for eg: fill the checkbox with blue, make the boundary of the checkbox blue or any other method. But I am not sure how to change checkbox styling as I am creating checkbox via model.
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::CheckStateRole && index.column() == COLUMN_WITH_CHECKBOX)
{
//return Qt::Checked or Qt::Unchecked here
}
//...
}
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
if (role == Qt::CheckStateRole)
{
if ((Qt::CheckState)value.toInt() == Qt::Checked)
{
//user has checked item
return true;
}
else
{
//user has unchecked item
return true;
}
}
return false;
}
First you need is implement your own ItemDelegate
class CheckedDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
CheckedDelegate(QObject *parent = nullptr);
~CheckedDelegate();
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;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const;
};
In this delegate you must implement custom editor and custom item painting. To create custom editor:
QWidget *CheckedDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QCheckBox *chBox = new QCheckBox(parent);
//customize editor checkbox
QString strQss = "QCheckBox::indicator:checked { image: url(:/icons/pic/checkboxChecked.png); } ";
strQss.append("QCheckBox::indicator:unchecked { image: url(:/icons/pic/checkboxUnchecked.png); }");
chBox->setStyleSheet(strQss);
return chBox;
}
void CheckedDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked)
{
chBox->setChecked(true);
}
else
{
chBox->setChecked(false);
}
}
void CheckedDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
model->setData(index, chBox->isChecked() ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
}
void CheckedDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(GetCheckboxRect(option));
}
To calculate checkbox geometry use
QRect GetCheckboxRect(const QStyleOptionViewItem &option)
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
double dx = (r.width() - sz.width()) / 2;
double dy = (r.height()- sz.height()) / 2;
r.setTopLeft(r.topLeft() + QPoint(qRound(dx),qRound(dy)));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
Then implement custom painting. In this example I use pixmaps to customize checkbox so I also paint only pixmaps.
void CheckedDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked) {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxChecked.png"));
} else {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxUnchecked.png"));
}
}
And set your delegate (in my example I have TableTiew not TreeView)
CheckedDelegate *chDel = new CheckedDelegate(this);
ui->tableView->setItemDelegateForColumn(1, chDel);

Cannot check item of QListView

I'm working on a dialog class with a QListView and a customized model inheriting QAbstractListModel. Items on my list are custom widgets with several labels.
I managed to make a checkbox displayed for each item by reimplementing data(), setData() and flags() methods of my model, but when I run my code and click on a checkbox associated to one of the item, the checkbox doesn't appear as checked (remains unchecked).
Here's my code:
mymodel.h
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
MyModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE ;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex & index) const Q_DECL_OVERRIDE ;
QSet<QPersistentModelIndex> checkedItems;
};
mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::CheckStateRole)
{
if(checkedItems.contains(index))
return Qt::Checked;
else
return Qt::Unchecked;
}
return QVariant();
}
bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
checkedItems.insert(index);
else
checkedItems.remove(index);
emit dataChanged(index, index);
}
return true;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}
mydelegate.h
class MyDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
mydelegate.cpp
QSize MyDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return QSize(400, 120);
}
In the constructor of mydialog.cpp
model = new MyModel(this);
ui->list->setModel(model);
ui->list->setItemDelegate(new MyDelegate(this));
I've tried adding flags Qt::ItemIsEnabled and Qt::ItemIsEditable but it didn't change anything.
I'm not very familiar with view/model implementation so far, although I've read Qt docs.
Thx for the help !
To be more precise about my comment, here is what I had in mind
bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
checkedItems.insert(index);
self.checkBoxList[index.row()][index.column()].setChecked(True)
else
checkedItems.remove(index);
self.checkBoxList[index.row()][index.column()].setChecked(False)
emit dataChanged(index, index);
}
return true;
}
I just do not have all of your code so I do not know how your self.checkBoxList is done. Are the checkBox child of the table?

How to close an editor created by a custom QItemDelegate::createEditor()

I have created a custom item delegate which lets users edit a list of file paths:
I have achieved this through a custom class DirEdit. Now the selected path is commited and the editor is closed when the user presses enter, but I would like to add two cases where the editor should be closed without the user having to press enter:
When the user selects a file by activating a combo box entry(by clicking or pressing return)
When the user selects a file by clicking the "ellipsis" tool button.
I have been exprimenting with clearFocus() and other methods, but nothing seems to work. Below is a complete example:
#include <QtWidgets>
class DirEdit : public QWidget
{
QLineEdit* lineEdit=nullptr;
public:
DirEdit(QWidget* parent=nullptr)
: QWidget(parent)
{
new QHBoxLayout(this);
layout()->setMargin(0);
layout()->addWidget(lineEdit=new QLineEdit(this));
QCompleter *completer = new QCompleter(this);
auto model = new QDirModel(completer);
model->setFilter(QDir::AllDirs|QDir::NoDotAndDotDot);
completer->setModel(model);
lineEdit->setCompleter(completer);
connect(completer, static_cast<void (QCompleter::*)(const QString&)>(&QCompleter::activated), [this](const QString& text)
{
// >>>>>>>>>>>>>>>>>>>>>>> TODO: Make the editor close here <<<<<<<<<<<<<<<<<<<<<<<<<<<<
});
QToolButton* dotDotDot;
layout()->addWidget(dotDotDot=new QToolButton(this));
dotDotDot->setText("...");
connect(dotDotDot, &QToolButton::clicked, this, [this]()
{
QString dir = QFileDialog::getExistingDirectory(window(), "Select Directory", lineEdit->text());
if(dir!="")
{
lineEdit->setText(dir);
// >>>>>>>>>>>>>>>>>>>>>>> TODO: Make the editor close here <<<<<<<<<<<<<<<<<<<<<<<<<<<<
}
});
setFocusProxy(lineEdit);
}
void setPath(const QString& path)
{
lineEdit->setText(path);
}
QString path()const
{
return lineEdit->text();
}
};
class MyDelegate : public QItemDelegate
{
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{
return new DirEdit(parent);
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem& option, const QModelIndex &)const
{
editor->setGeometry(option.rect);
}
void setEditorData(QWidget *editor, const QModelIndex &index) const
{
QVariant value = index.model()->data(index, Qt::DisplayRole);
if (DirEdit *dirEdit = dynamic_cast<DirEdit *>(editor))
dirEdit->setPath(value.toString());
}
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if (DirEdit *dirEdit = dynamic_cast<DirEdit *>(editor))
model->setData(index, dirEdit->path());
}
};
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QListWidget listWidget;
listWidget.setItemDelegate(new MyDelegate);
listWidget.addItem(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
listWidget.addItem(QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
listWidget.addItem(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
listWidget.addItem(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
for (int i = 0; i<listWidget.count(); i++)
listWidget.item(i)->setFlags(listWidget.item(0)->flags()|Qt::ItemIsEditable);
listWidget.show();
return app.exec();
}
TL;DR
Replace the TODOs with
QApplication::postEvent(this, new QKeyEvent(QKeyEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier));
Motivation:
I found the key to the answer here: Why pressing of "Tab" key emits only QEvent::ShortcutOverride event?
There is an event filter in place looking for certain events so I only have to trigger one of those:
// Edited for brevity.
bool QItemDelegate::eventFilter(QObject *object, QEvent *event)
{
QWidget *editor = qobject_cast<QWidget*>(object);
if (event->type() == QEvent::KeyPress) {
switch (static_cast<QKeyEvent *>(event)->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor",
Qt::QueuedConnection, Q_ARG(QWidget*, editor));
return false;
}
}
I tried first posting a FocusOut event like #fasked suggests in that other post, but that didn't work in this case
I solved a similar problem by setCurrentIndex(QModelIndex()).
QTreeView *tree;
// ...
// ...
QObject::connect(outsideButton, &QPushButton::clicked, [tree](){
tree->setCurrentIndex(QModelIndex());
});

Bind CheckBox checked-state in TableView to custom model attribute

I've got a QML-application containing a TableView with two columns. One of them is a CheckBox. Now I created a model derived from QAbstractTableModel. Reading data for the ordinary text-column already works but how do I sync the checked-property for my CheckBox with the model?
Currently I can't even set it checked via model. You find the relevant code below.
tablemodel.cpp
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
list.append("test1");
list.append("test2");
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return list.count();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant TableModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= list.count())
return QVariant();
if (role == NameRole)
return list[index.row()]
else if (role== EnabledRole){
//list is not QList<QString>, its a custom class saving a String and a boolean for the checked state
return list[index.row()].isEnabled();
}
else {
return QVariant();
}
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[EnabledRole] = "enabled";
return roles;
}
tablemodel.hpp
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole + 1,
EnabledRole
};
explicit TableModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex & parent = QModelIndex()) const;
Q_INVOKABLE QVariant data (const QModelIndex & index, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> list;
main.qml
TableView {
id: Table
model: TableModel
TableViewColumn {
role: "name"
}
TableViewColumn {
role: "enabled"
delegate: CheckBox {
//how to get the right state from the model
//checked: ??
}
}
}
main.cpp
QQmlApplicationEngine engine;
QQmlContext * context = new QQmlContext(engine.rootContext());
TableModel tableModel;
context->setContextProperty("tableModel",&tableModel);
QQmlComponent component(&engine, QUrl("qrc:///main.qml"));
QQuickWindow * window = qobject_cast<QQuickWindow*>(component.create(context));
window->show();
You can emit signal from qml, when clicked on checkbox; connect this signal to your model slot and do something
main.qml
TableView {
id: table
objectName: "myTable"
signal checkedChanged(int index, bool cheked)
TableViewColumn {
role: "enabled"
delegate: CheckBox {
onClicked: table.checkedChanged(styleData.row, checked);
}
}
main.cpp
QQuickItem *obj = engine.rootObjects().at(0)->findChild<QQuickItem*>(QStringLiteral("myTable"));
QObject::connect(obj,SIGNAL(checkedChanged(int,bool)),tableModel,SLOT(mySlot(int,bool)));

C++ Qt: set a active widget in QStyledItemDelegate::paint method

I would like to set a widget in a treeviews child row using a QStyledItemDelegate.
The widget is shown as intended but not clickable. It seems like it is not "active".
This is my paint method:
ProjectSpecificDelegate::ProjectSpecificDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}
void ProjectSpecificDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
if(index.data(SpecialTreatmentsArchiv::ParentRowRol).toString() != "parent")
{
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
QPaintDevice* original_pdev_ptr = painter->device();
QList<ProjectSpecificArchivItem> item_list = index.data(SpecialTreatmentsArchiv::ChildRowRole).value<QList<ProjectSpecificArchivItem> >();
ProjectSpecificArchiv expand_widget(item_list);
painter->end();
expand_widget.render(painter->device(), QPoint(option.rect.x(), option.rect.y()), QRegion(0, 0, option.rect.width(), option.rect.height()), QWidget::DrawChildren);
painter->begin(original_pdev_ptr);
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
My question is: What do I have to change so that it is possible to to interact with the widget?