Why does the following code crash when I call idx.data()?
QVariant ApplicantTableModel::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid()) return QVariant();
if (idx.column() == 10 && role == Qt::DisplayRole)
if(idx.data() == "0")
return "-";
else return "+";
else return QSqlTableModel::data(idx,role);
}
If idx is an index of the same ApplicantTableModel instance (which it should be, otherwise the usage is incorrect) idx.data() will call idx.model()->data(), i.e. the very same ApplicantTableModel::data() function we’re looking at => infinite recursion, which leads to stack overflow/crash.
From your code I guess what you want might be
QVariant ApplicantTableModel::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid())
return QVariant();
if (idx.column() == 10 && role == Qt::DisplayRole) {
if(QSqlTableModel::data(idx,role).toString() == “0")
return "-";
else
return "+";
}
return QSqlTableModel::data(idx, role);
}
I.e. call get the value of data from the base class implementation and change that in this special case.
Related
My question about model in Qt MVC pattern and std::string type.
For example, i have an object of struct generated from other library (boost, for example):
struct Foo{
int age;
std::string name;
}
And QAbstractListModel data implementation
QVariant FooListModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() > fooList.count())
return QVariant();
const Foo & foo = contacts[fooList.row()];
if (role == AgeRole) //AgeRole = 0
return foo.age;
else if (role == NameRole) //NameRole = 1
return QString::fromStdString(foo.name);
return QVariant();
}
The problem is that name data doesn't work. Now i see only one solution - convert all Foo class objects to FooQt objects:
struct FooQt{
int age;
QString name;
}
Is there a more clean solution?
The problem is with your usage of Qt's View model. The roles describe differnet aspects of a single item, like displayed text, font, color, decoration, etc.
Your AgeRole (which is 0) equals Qt::DisplayRole. That's why the age seems to work. Your NameRole (which is 1) equals Qt::DecorationRole. You would use that to specify an icon or background color.
You probably want to provide the data for different columns of your model:
QVariant FooListModel::data(const QModelIndex &index, int role) const
{
// Only provide data for valid indices and display role
if (index.row() < 0 || index.row() >= fooList.count() || role != Qt::DisplayRole)
return QVariant();
const Foo & foo = contacts[index.row()];
// Check which column to display
if (index.column() == AgeRole) //AgeRole = 0
return foo.age;
else if (index.column() == NameRole) //NameRole = 1
return QString::fromStdString(foo.name);
return QVariant();
}
I am learning the Model-View in Qt by the AddressBook example. https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-example.html
And I find something interesting. The code construct a TableModel class besed on QAbstractTableModel. In the override setData function, it emit the dataChanged signal. But, there is no signal emit in removeRows/insertRows. Then, how can these function update the View.
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
listOfPairs.removeAt(position);
}
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
QPair<QString, QString> p = listOfPairs.value(row);
if (index.column() == 0)
p.first = value.toString();
else if (index.column() == 1)
p.second = value.toString();
else
return false;
listOfPairs.replace(row, p);
emit(dataChanged(index, index));
return true;
}
return false;
}
Make note of function calls beginRemoveRows() and endRemoveRows() in the function removeRows() of your posted code.
The beginRemoveRows() function, emits a signal rowsAboutToBeRemoved(). This is how connected views can know about the deletion and the underlying connected views must handle before data is removed.
Look the note in below documentation:
https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
I have extended QSqlTableModel in the past, but currently I'm stuck (still a beginner after all):
QVariant MyChild::data(const QModelIndex &index, int role) const
{
// Check if (soft)deleted / cancelled
if(index.column() == 3)
{
if(QSqlTableModel::data(index, Qt::DisplayRole).toString() == "1")
{
if(role == Qt::DisplayRole)
{
return "Deleted";
}
if(role == Qt::BackgroundColorRole)
{
return QVariant(QColor(Qt::yellow));
}
// QSqlTableModel::setData(index, QVariant(QColor(Qt::red)), Qt::BackgroundColorRole);
// setData(index, QColor(Qt::red), Qt::BackgroundColorRole);
}
}
return QSqlTableModel::data(index, role);
}
Now this works great for what it does. It colors the field in the table yellow, but instead I'd love to have the whole row being colored. So you can see at one glimpse that this record was (soft) deleted already.
As you can see, I already tried with calling setData, but to no avail. (Of course I would do this for each index in the this very row, but as it doesn't work to begin with I stopped there.)
Any ideas on this? I searched the web a lot, but I can't seem to find a way to color the whole row.
I would do something like that (its not testet at all so adaptions may be needed):
QVariant MyChild::data(const QModelIndex &index, int role)
{
// Check if (soft)deleted / cancelled
if(role == Qt::DisplayRole && index.column() == 3 && QSqlTableModel::data(index, Qt::DisplayRole).toString() == "1")
{
return "Deleted";
}
else if(role == Qt::BackgroundColorRole)
{
QModelIndex tmpIdx = QSqlTableModel::index(index.row(), 3, index.parent());
if(QSqlTableModel::data(tmpIdx, Qt::DisplayRole).toString() == "1")
{
return QVariant(QColor(Qt::yellow));
}
}
return data(index, role);
}
So check for each modelindex if the specified column says that the row is deleted. There would also be no harm in checking if "tmpIdx" is valid...
I wanted to have a tree view which shows the item name, the item description, and two related Boolean values in respective columns. I started by modifying the Editable Tree Mode example, so there's a TreeModel that keeps track of a group of TreeItems, each of which not only has a list of child TreeItems, but also a list of QVariants which stores a set of values that can later be displayed in columns in the QTreeView.
I managed to add two more columns for two Boolean values. I also searched through the net on how to add checkboxes for QTreeView and QAbstractItemModel. I managed to have the checkboxes on the two Boolean columns working okay, as well as the rest of the tree hierarchy. Yet all the items in each column renders a checkbox and a line of text now.
Here's the parts where I've modified from the example, mainly within TreeModel.
treemodel.cpp:
bool TreeModel::isBooleanColumn( const QModelIndex &index ) const
{
bool bRet = false;
if ( !index.isValid() )
{
}
else
{
bRet = ( index.column() == COLUMN_BOL1 ) || ( index.column() == COLUMN_ BOL2 );
}
return bRet;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
if ( isBooleanColumn( index ) )
{
return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
}
else
{
return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole )
return QVariant();
TreeItem *item = getItem(index);
if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
{
Qt::CheckState eChkState = ( item->data( index.column() ).toBool() ) ? Qt::Checked : Qt::Unchecked;
return eChkState;
}
return item->data(index.column());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value,
int role)
{
if (role != Qt::EditRole && role != Qt::CheckStateRole )
return false;
TreeItem *item = getItem(index);
bool result;
if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
{
Qt::CheckState eChecked = static_cast< Qt::CheckState >( value.toInt() );
bool bNewValue = eChecked == Qt::Checked;
result = item->setData( index.column(), bNewValue );
}
else
{
result = item->setData(index.column(), value);
}
if (result)
emit dataChanged(index, index);
return result;
}
mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
…
QStringList headers;
headers << tr("Title") << tr("Description") << tr("Hide") << tr("Lock");
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
TreeModel *model = new TreeModel(headers, file.readAll());
file.close();
…
}
The checkboxes under the non-Boolean columns don't respond to user input, and the text under the Boolean columns aren't editable. So functionality-wise there's nothing wrong, but it's still bothersome as far as UI goes.
I'm moving onto having QTreeWidget do the same thing. Meanwhile, I'm couldn't help but wonder if there's something else I'm missing here. I heard one solution is to have a custom delegate; is that the only option?
If there's anyone who can point out what else I need to do, or provide a similar example, I will greatly appreciate it.
I think the problem is in the Data method. You should return QVariant() When the role is CheckStateRole but the column is not boolean.
I had this problem. It occurred in TreeModel::parent() method due to passing child.column() value to createIndex() method. It should be 0 instead. So, instead of
createIndex(parentItem->childNumber(), child.column(), parentItem);
should be
createIndex(parentItem->childNumber(), 0, parentItem);
The reason this is happening is related to a "bug" in peoples implementation of the models data method.
In the example below, only column 2 should show a checkbox.
Problem code:
if role == Qt.CheckStateRole:
if index.column() == 2:
if item.checked:
return Qt.Checked
else:
return Qt.Unchecked
Correct code:
if role == Qt.CheckStateRole:
if index.column() == 2:
if item.checked:
return Qt.Checked
else:
return Qt.Unchecked
return None
In the problematic code, table cells that should not have a checkbox decorator were getting missed and processed by a catch all role handler farther down in the code.
I have a matrix of data, I simply stored it as a QList of QStringLists, all containing an equal number of QStrings. In this way, the data looks almost like a spreadsheet.
I use a QTableView to present this data to the user:
void DialogwitQTableView::setData(QList<QStringList> s)
{
/* Create the data model */
// 1. give it some headers
QStandardItemModel model = new QStandardItemModel(s.count(),25,this); //x Rows and 25 Columns
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Column 1")));
model->setHorizontalHeaderItem(1, new QStandardItem(QString("Column 2")));
// ...
model->setHorizontalHeaderItem(24, new QStandardItem(QString("Column 25")));
// 2. populate the model with the data
for(int i = 0; i < s.count() ; i++)
{
for(int j = 0; j < s[i].count() ; j++)
model->setItem(i,j,new QStandardItem(QString(s[i][j])));
}
ui->NameOfTheTableView->setModel(model);
}
Now, if the user wants to change some of the data, he will just doubleclick in the QTableView in the Dialogbox and edits what he wants.
How do I get that edit also in the data? How can I adapt the QStringList with that new information?
If I search for documentation, I mostly find QTableViews linked to databases, but I don't see how this will work with a simple datastructure in memory. If I go to QtDesigner and click on "go to slots" for the TableView, I also do not see a slot called "datachanged" or anything similar.
Any thoughts? I feel pretty stuck and I am probably overviewing something, any tip is very welcome.
Looking at the doco, a QTableView inherits 6 signals from QAbstractItemView
http://doc.qt.digia.com/qt/qabstractitemview.html#signals
This class has all sorts of functionality for capturing edits, and edit triggers.
Once you can catch when the data is changed you can recommit it back to your model if you are using an MVC view. I am sure there are a lot of examples.
Hope that helps.
I think that for more complicated cases it's always best to use the abstract classes, more specifically QAbstractTableModel in this case.
Looking at this file, I just replaced Contact with StringList and changed the getters and setters. Check it out:
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-cpp.html
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
}
TableModel::TableModel(QList<QStringList> stringLists, QObject *parent) :
QAbstractTableModel(parent),
stringLists(stringLists)
{
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return stringLists.size();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2; // put the amount of columns here
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() >= stringLists.size() || index.row() < 0) return QVariant();
if (role == Qt::DisplayRole) {
const auto &strings = stringLists.at(index.row());
if (index.column() == 0)
return strings[0];
else if (index.column() == 1)
return strings[1];
}
return QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("String 1");
case 1:
return tr("String 2");
default:
return QVariant();
}
}
return QVariant();
}
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginInsertRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.insert(position, { QString(), QString() });
endInsertRows();
return true;
}
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.removeAt(position);
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
auto strings = stringLists.at(row);
if (index.column() == 0)
strings[0] = value.toString();
else if (index.column() == 1)
contact[1] = value.toString();
else
return false;
stringLists.replace(row, contact);
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QStringList> TableModel::getStringLists() const
{
return stringLists;
}
I also highly recommend that you read this:
https://doc.qt.io/qt-5/modelview.html
Hope it helps.
If I search for documentation, I mostly find QTableViews linked to
databases, but I don't see how this will work with a simple
datastructure in memory.
QTableView is a part of Qt's Model/View framework. There are bunch of examples of model-views.
How do I get that edit also in the data? How can I adapt the
QStringList with that new information?
At least these solutions exists:
You can grab all data from QStandardItemModel via item method.
Connect to QStandardItemModel::itemChanged signal.
You can make your own model via subclassing (and I suggest to base on QAbstractTableModel) and implement several methods (data, setData + several utility methods).