Convert QSqlQueryModel Data into QVector(s) - c++

If we get data from an SQL database using the following QT functionality
QSqlQueryModel *model = new QSqlQueryModel;
model->setQuery("SELECT name, salary FROM employee");
it states in the accompanying material we can access individual elements using either of the following commands
int salary = model.record(4).value("salary").toInt();
or
int salary = model.data(model.index(4, 2)).toInt();
and that we can insert this data into a QTableView using
QTableView *view = new QTableView;
view->setModel(model);
And that this data can also be However, I can see anywhere how to extract a whole column of data or range of columns / rows (or as in the terminology of QSqlQueryModel index & roles).
Ultimately what I would like to do is efficiently extract parts of the data into either QVectors e.g column 3 of the data. or possibly into a matrix library such as armadillo or Eigen using some sort of getDataFunc(QVector rows, QVector columns).
The data I am working is can be huge, so efficiently is important and thus using a naive set of for loops seems a very bad idea.
Any help much appreciated.

I would suggest to have a look on QAbstractProxyModel. Especially considering that you've got a huge datasets, so copying them in between different places doesn't look good to me.
So in the code language I think I would create something like:
class RowsInColumnProxyModel: public QAbstractProxyModel {
....
void setTargetColumn( int column );
void setRowsRange(int minRow, int maxRow);
int rowCount(..);
int columnCount(..);
... whatever else on your taste from QAbstractItemModel ...
virtual QModelIndex mapFromSource(const QModelIndex & sourceIndex) const;
virtual QModelIndex mapToSource(const QModelIndex & proxyIndex) const;
}
you might want to have a look in the Qt documentation, since there are plenty examples available. Idea is that inside mapFrom and mapTo functions you provide a logic which maps
index in your column into index in full dataset, as well as proper values for rows() and columns().. rest is done automatically for you and as soon as ProxyModel is just a model you can use it in any UI controls or any other way you like. I use proxy models a lot and it's very convenient thing once you get used to it.
I will try to make some example, hope it will make things clear for you...
A model in Qt (whatever type it has), is a set of data. Depending on type it could be either a table with rows and columns (obviously model with one column is a list), or something more advanced like tree where you could have more complex hierarchical structures, but let's leave it for now.
So, QSqlQueryModel is a flat table with rows and columns, where rows - is a number of records in result set and columns are number of columns you fetched with your query..
Let's say you've got something like
Col1 Col2 Col3
1 2 a
4 3 v
5 f f
from math prospective point of view you can call it a matrix.
QModelIndex - is a class which represents a "position" in model. To make things simple you can think of it as something representing pair { row, column }
so you can fetch data from model using method
QVariant data( QModelIndex, DisplayRole)
this method will return you a content of a cell located at position described by QModelIndex. To convert physical row and column you should use method
QModelIndex index(int row, int column, QModelIndex & parent = QModelIndex());
you can ignore last parameter for now (it's only used on tree models), so to get any cell from the flat table model (which QSqlRecordModel is) you can use code like:
QVariant value = model.data( model.index( row, column) );
int intValue = value.toInt();
.. or in case you expect a string
QString strValue = value.toString()
.. etc
so, getting close to subject... you task is to extract a vector, so lets say a part of any matrix column...so you define a class like:
class Vector: public QAbstractProxyModel {
protected:
// make them =0 in constructor
int m_column;
int m_minRow;
int m_maxRow;
...
void setTargetColumn( int column ) {
// emitting signals important to let rest of the world now about changes
emit beginResetModel();
m_column = column;
emit endResetModel();
}
void setRowsRange( int minRow, int maxRow ) {
// emitting signals important to let rest of the world now about changes
emit beginResetModel();
m_minRow = minRow;
m_maxRow = maxRow;
emit endResetModel();
}
virtual int columnCount(const QModelIndex & parent = QModelIndex()) {
return 1; // since it's vector
}
virtual int rowCount(const QModelIndex & parent = QModelIndex()) {
return m_maxRow - m_minRow;
}
.. now most exciting part..
virtual QModelIndex mapFromSource(const QModelIndex & sourceIndex) const {
int sourceRow = sourceIndex.row();
int sourceColumn = sourceIndex.column();
int targetColumn = 0; // only one in vector
int targetRow = sourceRow - m_minRow;
return modelIndex( targetRow, targetColumn );
}
virtual QModelIndex mapToSource(const QModelIndex & proxyIndex) const {
// same as above but a bit shorter
return index( proxyIndex.row()+m_minRow, m_column );
}
}
ok, we've got a class let's use it
QSqlRecordModel * sourceModel = ... (so, it's something)
Vector * myVector = new Vector();
myVector->setSourceModel( sourceModel );
myVector->setTargetColumn(3);
myVector->setRowsRange(5, 10);
...
for (int i=0;i<myVector->rowCount();i++) {
int vectorItem = myVector->data( myVector->index(i,0) ).toInt();
}
hope it explains a bit more, what I meant with my answer

Related

How to select a specific entire colum of a QTableView

I have a QTableView with three columns like the example below:
| Id | name | ACoord |
I am trying to highlight the entire ACoord column no matter which cell I am clicking on in the ACoord only.
I have tried several examples but nothing is helpful.
The most promising (also from official QT documentation) seems to be setSelectionBehavior(QAbstractItemView::SelectColumns) but didn't work exactly as I need.
Here is the snipped of code:
connect(mTurnIntoExcelData, &QAction::triggered, [&]() {
int row = -1, column = -1;
QString reference;
QString type;
QModelIndex index;
int rowModel = index.row();
SelectionData currentData;
for(int i = 0; i < ui->tableViewLeft->model()->columnCount(); i++)
{
if(ui->tableViewLeft->model()->headerData(i, Qt::Horizontal).toString() == "ACoord") {
column = i;
ui->tableViewLeft->setSelectionBehavior(QAbstractItemView::SelectColumns);
ui->tableViewLeft->setSelectionMode(QAbstractItemView::SingleSelection);
type = "ACoord";
}
The expected result is: I click on any cell of the ACoord and the entire column becomes selectable.
However, the actual result is that if I click on any cell of ACoord column I am not able to select the entire column, but only the single cell.
Thanks for any insight.
I don't know if this is the most elegant way to do it, but I was able to get (what I believe is) the behavior you want by modifying Qt's "FrozenColumn" example program (in $QTDIR/qtbase/examples/widgets/itemviews/frozencolumn) in the following way:
In freezetablewidget.h, add the following declarations inside the private slots: section:
void currentColumnChanged(const QModelIndex &);
void autoSelectMagicColumn();
In freezetablewidget.cpp, add a #include <QTimer> to the includes-section at the top, then add the following lines to the end of the FreezeTableWidget::init() method:
connect(frozenTableView->selectionModel(), SIGNAL(currentColumnChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentColumnChanged(const QModelIndex &)));
connect(frozenTableView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentColumnChanged(const QModelIndex &))); // optional
... and finally, add the following new lines into freezetablewidget.cpp:
const int magicColumnIndex = 2; // or whichever column you want the auto-selecting to happen on
void FreezeTableWidget::currentColumnChanged(const QModelIndex & mi)
{
const int col = mi.column();
if (col == magicColumnIndex)
{
// Auto-select the entire column! (Gotta schedule it be done later
// since it doesn't work if I just call selectColumn() directly at this point)
QTimer::singleShot(100, this, SLOT(autoSelectMagicColumn()));
}
}
void FreezeTableWidget::autoSelectMagicColumn()
{
// Double-check that the focus is still on the magic column index, in case the user moves fast
if (selectionModel()->currentIndex().column() == magicColumnIndex) frozenTableView->selectColumn(magicColumnIndex);
}
With the above changes, the FrozenColumn example app will auto-select the entire "YDS" column whenever I click on any cell in that column (or navigate to any cell in that column via the arrow keys). Perhaps you can do something similar in your program.

QTreeView Model of a plain array

I have a very big plain array of data with numbers. I want to use QTreeView and Model/View approach to show the data grouped in tree nodes with specific parameters, ex. data value < 10, data value < 100, data value < 1000.
Official Tree Model example shows how to use hierarchical data structure for item nodes that is bad option for me.
I tried to write the model myself from QAbstractItemModel but I can't even realize how to write parent() method for group nodes (<10, <100, <1000) and their child nodes, for example.
Is it event possible to write such a model?
You need to create "artificial" parents, which are model indexes with no value but just correspond to the parent of items with Data Value < 100.
Each item with a data value < 100 will refer to the same parent. And this parent will need to give all those items when asked for its children (and the number of children with rowCount() applied on it).
bool MyModel::isCategory(const QModelIndex &index) const
{
/* Based on your internal tracking of indexes, return if this is
a category (with children), false if not */
}
int MyModel::rowCount(const QModelIndex & parent) const
{
if (!parent.isValid()) {
/* This is top level */
/* Return the number of different categories at top level */
}
if (isCategory(parent)) {
/* This is a parent category (for example items with data value < 100 */
/* Return the number of indexes under it */
}
/* This is a child element with just data in it, no children */
return 0;
}
QModelIndex MyModel::index(int row, int column, const QModelIndex & parent) const
{
if (!parent.isValid()) {
/* Return QModelIndex corresponding to the nth global category, with n = row */
}
/* return the nth sub-element of the category represented by parent, with n = row */
}
QModelIndex QAbstractItemModel::parent(const QModelIndex & index) const
{
/* If index is a top level category, return an empty QModelIndex */
/* Otherwise return the QModelIndex corresponding to the bigger category */
}
Provide a minimal code sample on a simplified case where your code is not working if this is still not enough.

Qt checkboxes in QTableView

I'm using this code to query sqlite and put the results in a QTableView.
//MainWindow.cpp
void MainWindow::on_pushButton_clicked()
{
QSqlQueryModel * modal=new QSqlQueryModel();
connOpen();
QSqlQuery* qry=new QSqlQuery(mydb);
qry->prepare("select * from database");
qry->exec();
modal->setQuery(*qry);
//from stack
modal->insertColumn(0);
ui->tableView->setModel(modal);
//from stack
ui->tableView->resizeColumnsToContents();
int p;
for(p=0; p<modal->rowCount(); p++)
{
ui->tableView->setIndexWidget(modal->index(p,0),new QCheckBox());
}
connClose();
qDebug() <<(modal->rowCount());
}
I've seen several examples of the web for adding checkboxes to a column, but I'm not quite sure what to use for my simple example.
This answer suggests a few lines that doesn't seem standard.
There are more examples like this and this one that appear to outline what I need, but it's unclear to where you place the code.
What I intend to do is to have column 1 checkable. On next btn press, If checked those rows of data get written to a file.
I still need to understand how to loop thru the selected data, or perhaps I need to get the ids of the checked rows and do another query.
Questions:
How do you add 1 column of editable checkboxes to QTableView?
How do you loop through values in the QTableView data, so values of the checked rows can be accessed?
How do you check all/none?
I think the best way to have a column of checkable cells is to create your item model, e.g. by subclassing the QSqlQueryModel.
You must reimplement the flags() method to make checkable the cells.
Also you need to reimplement the data() method to return the check state and the setData() method and to set the check state. You must implement your own logic to keep track of the check state of every rows (e.g. using an array of Qt::CheckState that you must initialize and resize when the model data changes).
Yuo can start with something like this:
class MyModel : public QSqlQueryModel
{
public:
Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.column() == 0)
return QSqlQueryModel::flags(index) | Qt::ItemIsUserCheckable;
return QSqlQueryModel::flags(index);
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to return the check state
//....
}
else
return QSqlQueryModel::data(index, role);
}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to set the check state
//....
}
else
QSqlQueryModel::setData(index, value, role);
}
};
Se also:
Model Subclassing
QAbstractItemModel documentation

QSortFilterProxyModel and lazily populated treeviews

I have implemented a lazily populated treeview by subclassing QAbstractItemModel. The implementation looks something like:
https://gist.github.com/gnufied/db9c4d805e2bb24d8c23
(I am not pasting code inline, so as to mess with messaging)
It is basically a tree representation of hierarchical data stored in table. Now, I want users to be able to sort the rows based on columns. Where columns are, "count" or "reference count". These values are basically integers.
The implementation on its own works, until I throw in QSortFilterProxyModel and I start to get lots of empty rows in the view. The hard problem is, this tends to happen only when I have lots of rows (like thousands or so).
The code for implementing sorting proxy is:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
ui->treeView->setModel(proxyModel);
ui->treeView->setSortingEnabled(true);
I have subclassed QSortFilterProxyModel class and subclass implementation is really simple:
https://gist.github.com/gnufied/115f1a4fae3538534511
The documentation does say -
"This simple proxying mechanism may need to be overridden for source models with more complex behavior; for example, if the source model provides a custom hasChildren() implementation, you should also provide one in the proxy model."
But beyond that, I am not sure - what I am missing.
So, I think I have found the solution and the fix appears to be reimplementing fetchMore in proxy model subclass, because inserted rows reported by source model, do not match place where rows actually exist in view (Rows in view are owned by proxy model), so this seems to have fixed it for me:
#include "sortobjectproxymodel.h"
SortObjectProxyModel::SortObjectProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
}
bool SortObjectProxyModel::hasChildren(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->hasChildren(sourceIndex);
}
int SortObjectProxyModel::rowCount(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->rowCount(sourceIndex);
}
bool SortObjectProxyModel::canFetchMore(const QModelIndex &parent) const
{
if(!parent.isValid())
return true;
else {
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->canFetchMore(sourceIndex);
}
}
void SortObjectProxyModel::fetchMore(const QModelIndex &parent)
{
if (parent.isValid() && parent.column() == 0) {
int row = parent.row();
int startRow = row + 1 ;
const QModelIndex sourceIndex = mapToSource(parent);
RBKit::HeapItem *item = static_cast<RBKit::HeapItem *>(sourceIndex.internalPointer());
if (!item->childrenFetched) {
qDebug() << "Insert New Rows at :" << startRow << " ending at : " << startRow + item->childrenCount();
beginInsertRows(parent, startRow, startRow + item->childrenCount());
item->fetchChildren();
endInsertRows();
}
}
}
Thank you for responding. At this point I really don't care about resorting rows that were lazy loaded (when a node was expanded), so I went ahead and disabled sortingEnabled and disabled dynamicSortFiltertoo.
The new code looks like:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(2, Qt::DescendingOrder);
proxyModel->setDynamicSortFilter(false);
ui->treeView->setModel(proxyModel);
That still leaves with empty rows though.
In my oppinion You don't need to subclass QSortFilterProxyModel for sorting on the top layer. If sortingEnabled == true for your view then the view will perform sorting on the proxy model, which is not desirable as the model should sort itself. What you need is to to call proxyModel->sort(desiredColumn) and that will display your model sorted in the view without altering your data. By default the QSortFilterProxyModel has its dynamicSortFilter property on, which will cause the proxy model to automatically re-sort when data changes or row is inserted or removed. I didn't see emitting dataChanged signal anywhere in your HeapDataModel, so maybe that could be a hint for you to get dynamically sorted rows. If you need to sort subitems then it goes little more complicated and then maybe you'll need to subclasss QSortFilterProxyModel. These model-view abstractions are hard to learn but once you get it you can do miracles rapidly.
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(column);
ui->treeView->setModel(proxyModel);

QTableView and unique IDs

I'm new to Qt and coming from C# .Net. I am trying to replicate a fairly simple program I wrote in C# in Qt as a learning tool. I have a data model that inherits QAbstractTableModel and implements:
rowCount,
columnCount,
data,
setData,
headerData
flags
My data structure is a map
std::map<int, CBDataRow>
So the idea was that each row would have a unique int ID and a struct containing the rest of the row information.
What I am stuck on now is how to update my data model when the user makes an edit in the QTableView object. The setData function does get called. Here it is:
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole) {
success = m_data.UpdateRow(index, value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
Now you see that the UpdateRow() function gets called here on an edit. That function should find the unique id in the map and update the appropriate members of its CBDataRow struct. My problem is that I have no idea how to get the unique ID out of the QModelIndex object that gets passed into the edit function.
For example:
User edits the "CB Name" cell of row 3. The data in row three has a unique ID of 100. That value of 100 is in the QTableView in a hidden column, column index 0. So what I need to do is simply:
(Psuedo code)
it = m_data.find(unique_id);
it->second.cb_name = value.toString();
Since the user was editing column 1, how do i find the unique ID that is contained in column 0?
I would recommend to reimplement index() method of your model and there create indexes by using the call createIndex(row,col, unique_id);
Then in any place where you got QModelIndex, you can always extract unique_id = model_index.internalId();
In my opinion you can store your data in an array and index your element simply accessing through index.row():
QVector<CBDataRow> m_data;
....
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole && index.row() < m_data.size()) {
success = m_data.at(index.row()).UpdateRow(index.column(), value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
if you are worrying about element sorting you can derive your model from a QSortFilterProxyModel (instead of a QAbstractTableModel) and then reimplement
bool CBDatabaseModel::lessThan(const QModelIndex &left,
const QModelIndex &right) const
without define a internal id by yourself.
I hope this can help you.