QTreemodel multiple QVariant role - qt

I am using this Example http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html and need to pass a Color as Forgoundroll to the data, but cant figure it out.
In the treemodel.cpp i have altered the data as following..
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ForegroundRole)
return QVariant();
TreeItem *item = getItem(index);
if(role==Qt::ForegroundRole) {
QBrush redBackground(QColor(Qt::red));
return redBackground;
} else
return item->data(index.column());
}
... which works (items get the red color, but need to control the color from the mainwindow.cpp and let user set it and have different colors per column/row. Apparently i need to alter the Treemodel:setdata method, but cant figure it out.
So seeking for the setdata method..
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole && role != Qt::ForegroundRole )
return false;
TreeItem *item = getItem(index);
bool result;
if(role==Qt::ForegroundRole ) {
//do what ???????
} else {
result= item->setData(index.column(), value);
}
if (result) emit dataChanged(index, index);
return result;
}
From mainwindow.cpp i need to set it like ..
model->setData(child, QVariant(rowdata.at(column)), Qt::EditRole); // this provides the text of the inserted row
model->setData(child, QVariant(QBrush(Qt::red)), Qt::ForegroundRole); // this to provide its color
... but i get the text of the color #ffffff instead (in red color though).
Any help would be appreciated. Thanks

You have to save the color somewhere. One option is to add it to the TreeItem:
class TreeItem
{
public:
...
void setColor(const QColor& color) { this->color = color; }
const QColor& getColor() const { return color; }
private:
...
QColor color;
};
And in the model, you simply set the color if it's the appropriate role, something along these lines:
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = getItem(index);
bool result = false;
switch (role)
{
case Qt::EditRole:
result = item->setData(index.column(), value);
break;
case Qt::ForegroundRole:
item->setColor(value.value<QColor>());
result = true;
break;
default:
break;
}
if (result)
emit dataChanged(index, index);
return result;
}
And similarly in getData(), you return something like item->getColor().
Also, you don't have to use QBrush, AFAIK you can simply return QColor as a ForegroundRole.

Related

Qt QSortfilterproxy model virtual column

A related question has been asked by ymoreau here - but there is no decisive solution. I have subclassed QSortFilterProxyModel with the purpose to display some data in the virtual column on addition to QSqlRelationalTableModel (sourceModel) whose data is from MYSQL database.
Below is my code:
class vModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit vModel(QObject *parent): QSortFilterProxyModel(parent)
{
}
virtual QModelIndex index(int row, int column) const
{
if(column >= columnCount()-1){
return createIndex(row,column);
}
else
return QSortFilterProxyModel::index(row, column);
}
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const
{
Q_UNUSED(parent);
return sourceModel() ? (sourceModel()->columnCount() + 1) : 0;
}
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
{
if (index.isValid() && role == Qt::EditRole)
{
QSortFilterProxyModel::setData(index,value,role);
emit dataChanged(index,index);
return true;
}
else
return false;
}
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
{
int amtCol = columnCount()-1;
if (orientation == Qt::Horizontal
&& section == amtCol
&& role == Qt::DisplayRole){
return QString("Amount");
}
else
return QSortFilterProxyModel::headerData(section, orientation, role);
}
virtual QModelIndex parent(const QModelIndex &index) const {
Q_UNUSED(index);
return QModelIndex();
}
virtual QVariant data(const QModelIndex &index, int role) const
{
int amtCol = columnCount()-1;
if(index.column() == amtCol ){
if (role != Qt::DisplayRole)
return QSortFilterProxyModel::data(index, role);
else{
QString val = QString("Amount Index(%1,%2)").arg(index.row()).arg(index.column());
return val;
}
}
return QSortFilterProxyModel::data(index, role);
}
virtual QModelIndex mapFromSource(const QModelIndex &source) const
{
return index(source.row(), source.column());
}
virtual QModelIndex mapToSource(const QModelIndex &proxy) const
{
return (sourceModel()&&proxy.isValid())
? sourceModel()->index(proxy.row(), proxy.column(), proxy.parent())
: QModelIndex();
}
protected:
Qt::ItemFlags flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
if (index.isValid())
{
flags |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
return flags;
}
};
The view draws the extra column with correct header as I specified, but the content is empty even alternating row background is not painted. The model returns the correct data when I query the extra column indexes.The delegates returns false when I check the validity of any index in the virtual column yet the my subclassed model returns true.
Where's my problem...Is it the model or the delegates? Is there any additional function that I need to include?
Subclassing the Sourcemodel yielded my expectations so easily. I believe QIdentityProxyModel or any other proxy can do the same task but you have to man-handle so many issues. My Sourcemodel (the QSqlRelationalTableModel) responds to data changes (calculated values in more than 4 virtual columns) and communicates with the default relational-delegate so easily.
If there's away to make proxymodels do this task, I still welcome those suggestions. Am using Qt5.9

Creating custom QAbstractItemModel from comma seperated text file

I have this issue that my Qtableview gets extreme slowly after inserting 100.000 rows and specially using multiple select.
So after some reasearch I have decided to use my own model that inherits from QAbstractItemModel. But the issue is I have no clue how to use it since, the text file can change. fx. I can load a file with 5 columns and 50.000 rows, and after sometime it can have 15 columns and 10.000 rows.
the data does not need to be changed, so its only read only.
Anyone can help me with this problem?
My "empty" custom model is here
#include "customabstractmodel.h"
CustomAbstractModel::CustomAbstractModel(QObject *parent)
: QAbstractItemModel(parent)
{
}
QVariant CustomAbstractModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: Implement me!
}
bool CustomAbstractModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (value != headerData(section, orientation, role)) {
// FIXME: Implement me!
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
QModelIndex CustomAbstractModel::index(int row, int column, const QModelIndex &parent) const
{
// FIXME: Implement me!
}
QModelIndex CustomAbstractModel::parent(const QModelIndex &index) const
{
// FIXME: Implement me!
}
int CustomAbstractModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return 0;
// FIXME: Implement me!
}
int CustomAbstractModel::columnCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return 0;
// FIXME: Implement me!
}
QVariant CustomAbstractModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
// FIXME: Implement me!
return QVariant();
}
bool CustomAbstractModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (data(index, role) != value) {
// FIXME: Implement me!
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags CustomAbstractModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable; // FIXME: Implement me!
}
bool CustomAbstractModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row + count - 1);
// FIXME: Implement me!
endInsertRows();
}
bool CustomAbstractModel::insertColumns(int column, int count, const QModelIndex &parent)
{
beginInsertColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endInsertColumns();
}
bool CustomAbstractModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
// FIXME: Implement me!
endRemoveRows();
}
bool CustomAbstractModel::removeColumns(int column, int count, const QModelIndex &parent)
{
beginRemoveColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endRemoveColumns();
}
Edit #1 Csv data (Not real information, randomized)
name,phone,email,address,city,zip
Chavez, Lisandra F.,1-498-913-8181,ac.fermentum.vel#semperrutrumFusce.org,Ap #123-1044 Sed Rd.,Drayton Valley,21833
Humphrey, Briar W.,1-583-466-4027,Morbi.accumsan.laoreet#Loremipsumdolor.net,Ap #642-6497 Id Rd.,Lochgilphead,16394
Benson, Tasha H.,1-898-918-7872,consequat#enimEtiam.ca,P.O. Box 197, 4720 Ipsum. St.,Telford,56688
Emerson, Susan P.,1-190-818-1919,dignissim#liberoatauctor.ca,P.O. Box 482, 7813 Dolor. Ave,San Antonio,M8C 7F6
Dunn, Alexander U.,1-222-379-2231,libero.Donec.consectetuer#nonegestasa.ca,803-958 Lectus Rd.,Raleigh,74078
Assuming that we read the whole file into memory, I would implement the model (subclass from QAbstractTableModel) in the following way:
CustomAbstractModel::CustomAbstractModel(const QString &filePath,
QObject *parent)
:
QAbstractTableModel(parent)
{
// Parse the file and store its data in an internal data structure
QFile file(filePath);
if (file.open(QIODevice::ReadOnly))
{
QTextStream in(&file);
while (!in.atEnd())
{
// Assuming that m_data is a std::vector<QString>
m_data.emplace_back(in.readLine());
}
}
}
QVariant CustomAbstractModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole &&
!m_data.empty())
{
// Read the column names from the first line
const QString &firstLine = m_data.front();
QStringList headers = firstLine.split(',');
return headers.at(section);
}
}
int CustomAbstractModel::rowCount(const QModelIndex &parent) const
{
if (!m_data.empty())
{
return m_data.size() - 1; // First line is the column names data
}
return 0;
}
int CustomAbstractModel::columnCount(const QModelIndex &parent) const
{
if(!m_data.empty())
{
const QString &firstLine = m_data.front();
return firstLine.count(",") + 1;
}
return 0;
}
QVariant CustomAbstractModel::data(const QModelIndex &index, int role) const
{
if (m_data.empty() && role == Qt::DisplayRole)
{
// First line is the header data.
const QString &line = m_data[index.row() + 1];
QStringList columnData = firstLine.split(',');
return columnData.at(index.column());
}
return QVariant();
}

Qt signal emit in addressbook

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

Editing data in QTableModel not changed after editing

I have inherited QAbstractTableModel as described here. I have coded setData as:
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role == Qt::EditRole)
{
Data[index.row()][index.column()]= value.toString();
qDebug()<<Data[index.row()][index.column()];//to check entered data passed or not
}
return true;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable|Qt::ItemIsEnabled|Qt::ItemIsSelectable;
}
and in mainwindow I have connected model and view as:
connect(model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),ui->tableView,SLOT(dataChanged(QModelIndex,QModelIndex)));
but after editing data is not updated in view. I tried with emitting dataChanged signal from setData function but it is not working. What I missed in my code?
data function:
QVariant TableModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::DisplayRole:
if(index.column()==0)
{
return index.row()+1;
}
break;
case Qt::DecorationRole:
if(index.column()==1)
{
QColor c;
c.setRgb(0,200,200,200);
return c;
}
break;
case Qt::ToolTipRole:
if(index.column()==2)
{
return "colum 3";
}
break;
case Qt::StatusTipRole:
return "Not editable";
break;
case Qt::FontRole:
{
QFont f;
f.setFamily("Times");
f.setBold(true);
f.setKerning(true);
return f;
}
break;
case Qt::TextAlignmentRole:
{
return Qt::AlignCenter;
}
break;
case Qt::BackgroundRole:
if(index.column()==2)
{
QColor b;
b.setRgb(100,100,250,200);
return b;
}
break;
case Qt::ForegroundRole:
{
QColor b;
b.setRgb(0,100,250,200);
return b;
}
case Qt::CheckStateRole:
if(index.column()==3)
{
if(index.row()==1) return true;
return false;
}
break;
case Qt::InitialSortOrderRole:
return Qt::AscendingOrder;// use not clear
}
return QVariant();
}
There are some notes here that can be taken.
You should emit the dataChanged() signal once the data is updated properly.
You do not seem to use the 2D array variable called "Data" in your data() function that you set up properly in the setData() method. You need to use the (same) variable at both places.
You should not connect the dataChanged() signal to any slot. It will work out automatically for you.

using QList<QStringList> to populate a QTableView. When something gets changed in the View, how do I get it back in the data?

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).

Resources