Send data from a model to a delegate - c++

I have a tableView that has a column that is using a comboBox. I need to fill the comboBox using the delegate class with the data from the model class. I was using signals and slots for this task but I know there is a method using data.
This is how i create and fill the comboBox. I need to get the file row directly from the model class without already storing it in the delegate.
QWidget *CDelegate :: createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex & index) const
{
if(index.column() == COL_ComboBox)
{
QComboBox *editor = new QComboBox(parent);
for(int i=0; i<file.at(index.row()).size(); i++)
editor -> addItem(file.at(index.row()).at(i))
return editor;
}
...
}

As I understand, you want to fill QComboBox with data from model of your QTableView. As you see, const QModelIndex & index parameter in createEditor function provides you access to this model. Look for method model of QModelIndex class. Thats' why, your createEditor function may be like this:
QWidget *CDelegate :: createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex & index) const
{
if(index.column() == COL_ComboBox)
{
QComboBox *editor = new QComboBox(parent);
const QAbstractItemModel *model = index.model();
while(/*condition*/)
{
// take data from model
// QVariant dt = model->data(...);
// fill editor with data from dt
// editor->addItem(...)
}
return editor;
}
...
}

Related

Why there is Qt::UserRole in delegate in this example?

I read a tutorial book about delegates in QT. There is a simple example. We create QComboBox as editor with 3 values: "", "Pan", "Pani". We reimplement 3 methods: createEditor, setEditorData, setModelData in TitleDelegate class. I understand the first and the second one.
QWidget * TitleDelegate :: createEditor (QWidget *parent ,const QStyleOptionViewItem &,const QModelIndex &) const
{
QComboBox *widget=new QComboBox (parent);
QStringList itemList; itemList <<""<<"Pan"<<"Pani";
widget ->addItems(itemList);
return widget;
}
void TitleDelegate :: setEditorData (QWidget *editor , const QModelIndex &index) const
{
QComboBox *widget=static_cast < QComboBox *>( editor);
int value=index.model () ->data(index ,Qt::EditRole).toInt ();
widget -> setCurrentIndex (value);
}
void TitleDelegate :: setModelData (QWidget *editor , QAbstractItemModel *model , const QModelIndex &index) const
{
QComboBox *widget = static_cast < QComboBox *>( editor);
int value = widget -> currentIndex ();
model ->setData(index , widget -> currentText (), Qt::DisplayRole );
model ->setData(index , value , Qt::UserRole);
}
But I don't understand line from setModelData:
model ->setData(index , value , Qt::UserRole);
In the book there is explanation: We have to save value to can use it in setEditorData method to know active list's element.
Why we use Qt::UserRole? I think we should use Qt::EditRole.
Look at this Documentation about Qt::ItemDataRole:
But For User roles:
As It says :
For user roles, it is up to the developer to decide which types to use
and ensure that components use the correct types when accessing and
setting data.

C++ model for QML TreeView

Due to lack of any other Qt demo, I am making use of Qt widgets's SimpleTreeModeldemo to implement C++ model for my QML TreeView. I have defined roles so QML can use it but I am having trouble connecting them with actual model data.
What I also find interesting is that widgets (C++) demo works fine but TreeModeldoesn't seem to store the data as its member variable..leaves me scratching my head. I figured this out, every TreeItem store all its child items and TreeModel has only one rootItem which in turn stores all data as its child.
The TreeItem class
class TreeItem
{
public:
explicit TreeItem(const QList<QVariant> &data, TreeItem *parentItem = 0);
~TreeItem();
void appendChild(TreeItem *child);
TreeItem *child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
TreeItem *parentItem();
private:
QList<TreeItem*> m_childItems;
QList<QVariant> m_itemData;
TreeItem *m_parentItem;
};
The TreeModel class
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum DisplayRoles {
TitleRole = Qt::UserRole + 1,
SummaryRole
};
explicit TreeModel(const QString &data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> TreeModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TitleRole] = "title";
roles[SummaryRole] = "summary";
return roles;
}
private:
void setupModelData(const QStringList &lines, TreeItem *parent);
TreeItem *rootItem;
};
The model loads the data from a default.txt
TreeModel::TreeModel(const QString &data, QObject *parent)
: QAbstractItemModel(parent)
{
QList<QVariant> rootData;
rootData << "Title" << "Summary";
rootItem = new TreeItem(rootData);
setupModelData(data.split(QString("\n")), rootItem);
}
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
QList<TreeItem*> parents;
QList<int> indentations;
parents << parent;
indentations << 0;
int number = 0;
while (number < lines.count()) {
int position = 0;
while (position < lines[number].length()) {
if (lines[number].mid(position, 1) != " ")
break;
position++;
}
QString lineData = lines[number].mid(position).trimmed();
if (!lineData.isEmpty()) {
// Read the column data from the rest of the line.
QStringList columnStrings = lineData.split("\t", QString::SkipEmptyParts);
QList<QVariant> columnData;
for (int column = 0; column < columnStrings.count(); ++column)
columnData << columnStrings[column];
if (position > indentations.last()) {
// The last child of the current parent is now the new parent
// unless the current parent has no children.
if (parents.last()->childCount() > 0) {
parents << parents.last()->child(parents.last()->childCount()-1);
indentations << position;
}
} else {
while (position < indentations.last() && parents.count() > 0) {
parents.pop_back();
indentations.pop_back();
}
}
// Append a new item to the current parent's list of children.
parents.last()->appendChild(new TreeItem(columnData, parents.last()));
}
++number;
}
}
My problem is in this function, how do I connect the roles with the data which is stored in rootItem?. Note titleString and summaryStringare possible proposed functions (if needed) but I don't know what to write in them get access the data!
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.isValid() && role >= TitleRole) {
switch (role) {
case TitleRole:
return QVariant(titleString(rootItem(index))); // get title through rootItem?
case SummaryRole:
return QVariant(summaryString(rootItem(index))); // get summary through rootItem?
}
}
if (role != Qt::DisplayRole)
return QVariant();
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
return item->data(index.column());
}
The default.txthas the following data but the same is available in the Qt Creator demo itself as well.
Getting Started How to familiarize yourself with Qt Designer
Launching Designer Running the Qt Designer application
The User Interface How to interact with Qt Designer
Designing a Component Creating a GUI for your application
Creating a Dialog How to create a dialog
Composing the Dialog Putting widgets into the dialog example
Creating a Layout Arranging widgets on a form
Signal and Slot Connections Making widget communicate with each other
Using a Component in Your Application Generating code from forms
The Direct Approach Using a form without any adjustments
The Single Inheritance Approach Subclassing a form's base class
The Multiple Inheritance Approach Subclassing the form itself
Automatic Connections Connecting widgets using a naming scheme
A Dialog Without Auto-Connect How to connect widgets without a naming scheme
A Dialog With Auto-Connect Using automatic connections
Form Editing Mode How to edit a form in Qt Designer
Managing Forms Loading and saving forms
Editing a Form Basic editing techniques
The Property Editor Changing widget properties
The Object Inspector Examining the hierarchy of objects on a form
Layouts Objects that arrange widgets on a form
Applying and Breaking Layouts Managing widgets in layouts
Horizontal and Vertical Layouts Standard row and column layouts
The Grid Layout Arranging widgets in a matrix
Previewing Forms Checking that the design works
Using Containers How to group widgets together
General Features Common container features
Frames QFrame
Group Boxes QGroupBox
Stacked Widgets QStackedWidget
Tab Widgets QTabWidget
Toolbox Widgets QToolBox
Connection Editing Mode Connecting widgets together with signals and slots
Connecting Objects Making connections in Qt Designer
Editing Connections Changing existing connections
My output shows the same number of lines as in widgets demo except that there is no text. It seems like it is just not connected to the roles properly or roles are not connected to day. I am attaching the screen shot of my output.
Just so if anyone is trying to do the same thing, I figured it out and here is the answer. The data method of the model should read the following.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if (index.isValid() && role >= TitleRole) {
switch (role) {
case TitleRole:
return item->data(0);
case SummaryRole:
return item->data(1);
}
}
if (role != Qt::DisplayRole)
return QVariant();
return item->data(index.column());
}
TreeItem could be changed in real application to store specific data in which case the above method will point to that data instead of column number approach.

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 to build the Layout from the Properties frame in QtDesigner?

How can I build the Layout used by QtCreator in Tools->Options for my own application? It should look like this, but with another content:
How can I do this using C++/Qt using the QtDesigner integrated in QtCreator?
I just found the solution! I needed to use a QListView with a QStringListModel in it. To set the Item Size and Icons manually, I needed to subclass QStringListModel and QStyledItemDelegate:
// ItemDelegate to set item size manually
class MyItemDelegate : public QStyledItemDelegate
{
public:
virtual QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize suggestion = QStyledItemDelegate::sizeHint(option, index);
return QSize(suggestion.width(), 30);
}
};
// StringListModel to allow setting images
class MyStringListModel : public QStringListModel
{
public:
virtual QVariant data (const QModelIndex &index, int role) const
{
if (role == Qt::DecorationRole)
{
QString value = stringList().at(index.row());
if (value == "new")
return add;
else
return db;
}
else
return QStringListModel::data(index, role);
}
private:
QIcon db = QIcon::fromTheme("server-database");
QIcon add = QIcon::fromTheme("list-add");
};
Then, I could update my QListView (that was created by Qt):
MyStringListModel * model = new MyStringListModel();
model->setStringList(QStringList() << "test" << "new");
ui->databases->setModel(model);
ui->databases->setItemDelegate(new MyItemDelegate());

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