PDA

View Full Version : Table and combo with, almost, the same data



panoss
24th February 2017, 21:24
I have a db table named 'clients' with fields 'firstname' and 'lastname'.
It's first row is empty.

I want this table to 'feed' a QTableView and a QComboBox.

In the table I want the first row (the empty) not to be shown.
Also the table to be editable.
I need changes made in the model of the table to be filled back in the database.
And the combo 's model to be automatically updated.

In the combo I want all the rows (and the first one) to be shown. But I also want the 2 fields concatenated.

I thought of using a proxy, because:
1. I 'feed' two widgets from one db table.
2. I want the second model to be updated when the first changes.
3. their data, slightly, differ (by the first row).

The first row in the db table is empty because I want the combo to have an option of 'nothing', no option, Null.
But I don't want the table to have an empty row.
So I thought of a model 'feeding' all the data to the combo, and a filter (here I thought of QSortFilterProxyModel) that removes the first row for the table.

How can I accomplish this without using 'external' libraries like KDE?

Santosh Reddy
27th February 2017, 05:24
You are in the right path, keep going the same way.

Just one suggestion, empty row in DB sounds strange and is redundant, better have a main model (QSqlTableModel) and connect two QSortFilterProxyModel to it one for Table, another for Combo box.

panoss
27th February 2017, 08:11
You are in the right path, keep going the same way.
The problem is I 'm in no way!:D
I haven't done anything yet, I 'm trying to become familiar with models and their subclassing, I can't say I understand much, they seem to me very complicated.


Just one suggestion, empty row in DB sounds strange and is redundant, better have a main model (QSqlTableModel) and connect two QSortFilterProxyModel to it one for Table, another for Combo box.
So you 're suggesting:
Source model: a QSqlTableModel.
Table model: a QSortFilterProxyModel.
Combo model: another QSortFilterProxyModel.

If I remove the empty first row from my db table, I shall add it in the combo 's model with model->insertRow()?
If I do this, it will also be added in the source model and in the table 's model, right?

Or:
1. Remove empty row from db table.
2. Insert an empty row in the source model.
3. The table 's model filters out the empty row.
4. Result: table doesn't contain the empty row, but the combo does contain it.

How shall I concatenate the 2 fields for the combo?
FirstName + LastName to be replaced with FullName?

Santosh Reddy
27th February 2017, 11:18
I haven't done anything yet, I 'm trying to become familiar with models and their subclassing,...
Without trying anything, this is how far you can get familiar with Qt MVC. I suggest to start implementing some basic working model / view, you will get a more clarity.


If I remove the empty first row from my db table, I shall add it in the combo 's model with model->insertRow()?
If I do this, it will also be added in the source model and in the table 's model, right?
No, it should taken care by overriding
QSortFilterProxyModel::rowCount()


How shall I concatenate the 2 fields for the combo?
FirstName + LastName to be replaced with FullName?
Override the
QSortFilterProxyModel::data()

Added after 1 51 minutes:

For a head start, this is how ComboBox model will look like (working code)



class ComboModel : public QAbstractProxyModel
{
public:
ComboModel(QObject * parent = 0)
: QAbstractProxyModel(parent)
{ }

QModelIndex mapFromSource(const QModelIndex & sourceIndex) const
{
if(!sourceModel())
return QModelIndex();

if(!sourceIndex.isValid())
return QModelIndex();

return index(sourceIndex.row() + 1, sourceIndex.column());
}

QModelIndex mapToSource(const QModelIndex & proxyIndex) const
{
if(!proxyIndex.isValid())
return QModelIndex();

if(sourceModel())
{
if(proxyIndex.row() == 0)
return QModelIndex();
else
return sourceModel()->index(proxyIndex.row() - 1, proxyIndex.column());
}

return QModelIndex();
}

protected:
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const
{
if(!parent.isValid())
return createIndex(row, column);
return QModelIndex();
}

QModelIndex parent(const QModelIndex & /*child*/) const
{
return QModelIndex();
}

int rowCount(const QModelIndex & parent) const
{
if(!parent.isValid() && sourceModel())
return sourceModel()->rowCount(QModelIndex()) + 1;

return 0;
}

int columnCount(const QModelIndex & parent) const
{
if(!parent.isValid() && sourceModel())
return sourceModel()->columnCount(QModelIndex());

return 0;
}

Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.row() == 0)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;

return sourceModel()->flags(mapToSource(index));
}

QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if(!index.isValid() || !sourceModel())
return QVariant();

int row = index.row();

if(row > 0)
{
if(role == Qt::DisplayRole)
{
QModelIndex firstNameIndex = mapToSource(this->index(row, 0));
QModelIndex lastNameIndex = mapToSource(this->index(row, 1));

return QString("%1, %2")
.arg(firstNameIndex.data().toString())
.arg(lastNameIndex.data().toString());
}
else
return mapToSource(this->index(row, 0)).data(role);
}

if(role == Qt::DisplayRole)
return QString("--no selection--");
if(role == Qt::ForegroundRole)
return QVariant(QColor(Qt::lightGray));

return QVariant();
}
};

panoss
27th February 2017, 15:13
Thank you very much Santosh.
Works perfectly, but you shouldn't give me ready - made code, you 'll spoil me and make me lazy! :D
I only had to comment out 2 lines in data function:


// if(role == Qt::ForegroundRole)
// return QVariant(QColor(Qt::lightGray));

The would raise an error: "C2440: '<function-style-cast>' : cannot convert from 'Qt::GlobalColor' to 'QColor'
Source or target has incomplete type".

Added after 1 53 minutes:

I can't beleive this, I 'm trying to achieve this:


QModelIndex firstNameIndex = mapToSource(this->index(row, 0));
QModelIndex lastNameIndex = mapToSource(this->index(row, 1));

return QString("%1, %2")
.arg(firstNameIndex.data().toString())
.arg(lastNameIndex.data().toString());


for 4 days now...and it was so simple (and logical) now that you posted it...
Thank you so much.

anda_skoa
28th February 2017, 08:27
Removing the first row could have also be done by simply deriving from QSortFilterProxyModel and rejecting the first row in the override of filterAcceptsRow().

Cheers,
_