I'm using a QTableView to display some data inside a table. Because no vertical header is given Qt automatically assigns a row-id to a row. The example below displays the following table:
id|data
-------
1 | B
2 | A
3 | D
4 | C
After sorting the table based on the "data"-column:
id|data
-------
2 | A
1 | B
4 | C
3 | D
When a user double-clicks an entry, I want to be able to identify the clicked row by its id (i.e. A=2, B=1, C=4, D=3). Unfortunately, the methods used in "onDoubleClicked" only return the "new" row-id (i.e. A=1, B=2, C=3, D=4).
So how do I retrieve the correct row-id, when a user doubleclicks a row?
table.h
#ifndef TABLE_H
#define TABLE_H
#include <QTableView>
#include <QModelIndex>
#include <QHeaderView>
#include <QDebug>
class Table : public QTableView {
Q_OBJECT
public:
explicit Table() : QTableView() {
setSortingEnabled(true);
connect(this, &Table::doubleClicked, this, &Table::onDoubleClicked);
}
public slots:
void onDoubleClicked(QModelIndex index) {
qDebug() << index.row();
qDebug() << verticalHeader()->logicalIndex(index.row());
qDebug() << verticalHeader()->logicalIndexAt(index.row());
qDebug() << verticalHeader()->visualIndex(index.row());
qDebug() << verticalHeader()->visualIndexAt(index.row());
}
};
#endif // TABLE_H
main.cpp
#include <QApplication>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include "table.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Table table;
QStandardItemModel model;
QList<QStandardItem*> items;
items.append(new QStandardItem("B"));
items.append(new QStandardItem("A"));
items.append(new QStandardItem("D"));
items.append(new QStandardItem("C"));
model.appendColumn(items);
QSortFilterProxyModel proxy;
proxy.setSourceModel(&model);
proxy.sort(0, Qt::AscendingOrder);
table.setModel(&proxy);
table.show();
return a.exec();
}
The model is not having a new row(), you forgot that you are passing them via a proxy model. A proxy model holds different rows() and columns() from the original model because it can rearrange or filter fields.
The fix is easy - you just need to map the index from the proxy model to the original one.
void onDoubleClicked(QModelIndex index) {
QSortFilterProxyModel *m = qobject_cast<QSortFilterProxyModel*>(model());
auto sourceIdx = m->mapToSource(index);
qDebug() << sourceIdx.row();
qDebug() << verticalHeader()->logicalIndex(sourceIdx.row());
qDebug() << verticalHeader()->logicalIndexAt(sourceIdx.row());
qDebug() << verticalHeader()->visualIndex(sourceIdx.row());
qDebug() << verticalHeader()->visualIndexAt(sourceIdx.row());
}
These work for me.
void DeviceData::doubleClicked(QModelIndex cell)
{
cell.column();
cell.row();
}
Which you can then feed into your model: model.at(cell.row()).
Or you create a find() function.
if the value is bind with A you can use the setData method provided by QTableWidgetItem.set the value and get the value back by a specific flag.maybe Qt::UserRole
EDIT:
base on your code.
items.append(new QStandardItem("B"));
then you can get the QStandardItem make the item.setData(1,Qt::UserRole) while 1 is the value which is bind to "B".Later you can get the bind value from item.data(Qt::UserRole).Note: it would return a variant then you need to transfer it toInt
Related
I have the following JSON-file :
{
"users":[
{"nom":"123",
"name":"John",
"family":"ala",
"cash":1000
}
,{"nom":"456",
"name":"Joe",
"family":"ala",
"cash":1000
}
,{"nom":"131",
"name":"David",
"family":"ala",
"cash":1000
}]
}
I would like to change John's cash.
This is how I am trying to achieve this:
QFile f("file address ...");
f.open(QIODevice::ReadOnly|QIODevice::Text|QIODevice::WriteOnly);
QByteArray b=f.readAll();
QJsonDocument d=QJsonDocument::fromJson(b);
QJsonObject o=d.object();
for (int i=0;i<o["users"].toArray().size();i++) {
if(o["users"].toArray()[i].toObject()["name"].toString()=="John")
o["users"].toArray()[i].toObject()["cash"].toInt()=2000;//error unable to assign
}
However, I am getting the following error:
error: unable to assign
How to fix this?
Cause
You get the error, because you are trying to assign a value to the return value of a function (QJsonValue::toInt in this case).
Solution
Assign the value to QJsonValue, as demonstrated in the JSON Save Game Example:
void Character::write(QJsonObject &json) const
{
json["name"] = mName;
json["level"] = mLevel;
json["classType"] = mClassType;
}
Example
Here is an example I have written for you, in order to demonstrate how your code could be changed to implement the proposed solution:
#include <QApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QFile file("data.json");
if (file.open(QFile::ReadOnly | QFile::Text)) {
QJsonObject json = QJsonDocument::fromJson(file.readAll()).object();
QJsonArray users = json["users"].toArray();
file.close();
for (auto user : users) {
QJsonObject userObj = user.toObject();
if (userObj["name"].toString() == "John")
userObj["cash"] = 2000;
user = userObj;
}
qDebug() << users;
}
return a.exec();
}
Result
The given example produces the following result:
QJsonArray([{"cash":2000,"family":"ala","name":"John","nom":"123"},{"cash":1000,"family":"ala","name":"Joe","nom":"456"},{"cash":1000,"family":"ala","name":"David","nom":"131"}])
Please note, that the cash for John is set to 2000.
In Qt, QModelIndex is used to represent an index to my understanding. Officially:
This class is used as an index into item models derived from
QAbstractItemModel. The index is used by item views, delegates, and
selection models to locate an item in the model.
But I see it being used to represent a parent object. For instance, if I want to get an index in a QFileSystemModel object, I need a row, column and a parent:
QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const
I am trying to get a QModelIndex object, but to do that, I need another QModelIndex object? I am merely trying to iterate over the model. I don't have a separate parent object. How do I just create an index from row/column number? I don't understand the role of QModelIndex as a "parent". Shouldn't the model itself know what the parent object is? We passed a pointer to the constructor when creating the model.
Here's a bit of code showing the problem:
#include "MainWindow.hpp"
#include "ui_MainWindow.h"
#include <QFileSystemModel>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* model = new QFileSystemModel{ui->listView};
ui->listView->setModel(model);
ui->listView->setRootIndex(model->setRootPath("C:\\Program Files"));
connect(ui->pushButton, &QPushButton::clicked, [this] {
auto* model = static_cast<QFileSystemModel*>(ui->listView->model());
int row_count = model->rowCount();
for (int i = 0; i != row_count; ++i) {
qDebug() << model->fileName(model->index(i, 0)) << '\n';
}
});
}
Here I have a QListView object (*listView) and a QFileSystemModel object (*model). I would like to iterate over the model and do something, like print the names of the files. The output is
C:
No matter which directory the rootpath is. I assume that is because I did't pass anything as the parent.
You're just accessing the children of the root of the QFileSystemModel when you default the parent node to QModelIndex() in the call model->index(i, 0).
If you also want to list the children of those items, we'll want to iterate them, too:
#include <QApplication>
#include <QDebug>
#include <QFileSystemModel>
void list_files(const QFileSystemModel *model, QModelIndex ix = {},
QString indent = {})
{
auto const row_count = model->rowCount(ix);
for (int i = 0; i < row_count; ++i) {
auto const child = model->index(i, 0, ix);
qDebug() << qPrintable(indent) << model->fileName(child);
list_files(model, child, indent + " ");
}
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QFileSystemModel model;
model.setRootPath(".");
list_files(&model);
}
See how we pass the child index as the new parent when we recurse into list_files()?
Note that the model is likely incomplete at this stage, as it implements lazy reading - so don't expect to see all your files with this simple program.
I'm trying to generate a simple table (2 rows and 2 columns) and write it to a pdf file, using Qt 4.8.0.
So far, I generate the pdf but there is extra space at the bottom of the "printed" table:
I got the same problem with the right side of the table but I managed to get rid of it. But in this case I am clueless.
Here's the code I have now (all of this code is located in main.cpp):
Main
#include <QtGui/QApplication>
#include <QtCore/QDebug>
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtGui/QPrinter>
#include <QtGui/QHeaderView>
#include <QtGui/QPainter>
#include <QtGui/QTableWidget>
#include <QtGui/QTableWidgetItem>
/**/
/* Here are the functions.
/**/
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QMap<QString,int> values;
values.insert("X",7);
values.insert("Y",13);
bool status = TableWidgetToPdf("FromWidget.pdf",values);
return a.exec();
}
TableWidgetToPdf
bool TableWidgetToPdf(const QString& title, const QMap<QString, int>& values) {
QTableWidget* table = GenerateTable(values);
QPrinter printer;
printer.setOutputFileName(title);
printer.setOutputFormat(QPrinter::PdfFormat);
QPainter painter(&printer);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
printer.setPaperSize(QPrinter::A4);
table->render(&painter);
painter.end();
printer.newPage();
delete table;
return true;
};
GenerateTable
QTableWidget* GenerateTable(const QMap<QString,int>& values) {
QTableWidget* table = new QTableWidget;
table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
table->setRowCount(2);
table->setColumnCount(2);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setShowGrid(false);
table->verticalHeader()->hide();
QStringList h_labels;
h_labels << "X" << "Y";
table->setHorizontalHeaderLabels(h_labels);
table->horizontalHeader()->setFont( QFont("Times", 10, QFont::Bold) );
table->horizontalHeader()->setStretchLastSection(true);
QTableWidgetItem* item00 = new QTableWidgetItem( QString::number(values["X"]));
item00->setTextAlignment(Qt::AlignCenter);
table->setItem(0,0, item00 );
QTableWidgetItem* item01 = new QTableWidgetItem( QString::number(values["Y"]) );
item01->setTextAlignment(Qt::AlignCenter);
table->setItem(0,1,item01);
table->setItem(1,0,new QTableWidgetItem("ABCD"));
return table;
};
NOTE:
Putting
table->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
table->verticalHeader()->setResizeMode(QHeaderView::Stretch);
in GenerateTable the space disappears, but the cells are resized and consume too much space than needed for their contents. I would like to avoid that if possible:
EDIT:
OK.
In the end I achieved what I wanted by getting rid of the QTableWidget. I had to create the table using html and feeding it to a QTextEditor. Isn't any way to achieve this with a QTableWidget?
Have you tried the flags for resize content?
Try the following code, I don't have access to Qt right now.
table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
table->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
Hope that works!
I realise that this is an old post but it seems fairly often read.
I tried the same methods that you tried and none worked. Eventually I used a QTableView and added an extra method called by adding/removing rows.
void
TitleView::verticalResizeTableViewToContents()
{
auto count = m_model->rowCount(QModelIndex());
auto scrollBarHeight = horizontalScrollBar()->height();
auto horizontalHeaderHeight = horizontalHeader()->height();
auto rowTotalHeight = scrollBarHeight + (horizontalHeaderHeight * count);
setMinimumHeight(rowTotalHeight);
setMaximumHeight(rowTotalHeight);
}
I am trying to create pop-up menu depending on a variable as follows:
QMenu menu(widget);
for(int i = 1; i <= kmean.getK(); i++)
{
stringstream ss;
ss << i;
string str = ss.str();
string i_str = "Merge with " + str;
QString i_Qstr = QString::fromStdString(i_str);
menu.addAction(i_Qstr, this, SLOT(mergeWith1()));
}
menu.exec(position);
where:
kmean.get(K) returns an int value,
mergeWith1() is some `SLOT()` which works fine
Issue:
The loop creates an action on menu only for i=1 case, and ignores other values of i.
Additional information
When doing the same loop with casual int values (without convert) everything works fine. e.g. if I do in loop only menu.addAction(i, this, SLOT(...))) and my K=4, a menu will be created with four actions in it, named 1, 2, 3, 4 correspondingly.
What can be the problem caused by
I think the issue is in convert part, when I convert i to string using stringstream and after to QString. May be the value is somehow lost. I am not sure.
QESTION:
How to make the loop accept the convert part?
What do I do wrong in convert part?
In Qt code, you shouldn't be using std::stringstream or std::string. It's pointless.
You have a crashing bug by having the menu on the stack and giving it a parent. It'll be double-destructed.
Don't use the synchronous blocking methods like exec(). Show the menu asynchronously using popup().
In order to react to the actions, connect a slot to the menu's triggered(QAction*) signal. That way you can deal with arbitrary number of automatically generated actions.
You can use the Qt property system to mark actions with custom attributes. QAction is a QObject after all, with all the benefits. For example, you can store your index in an "index" property. It's a dynamic property, created on the fly.
Here's a complete example of how to do it.
main.cpp
#include <QApplication>
#include <QAction>
#include <QMenu>
#include <QDebug>
#include <QPushButton>
struct KMean {
int getK() const { return 3; }
};
class Widget : public QPushButton
{
Q_OBJECT
KMean kmean;
Q_SLOT void triggered(QAction* an) {
const QVariant index(an->property("index"));
if (!index.isValid()) return;
const int i = index.toInt();
setText(QString("Clicked %1").arg(i));
}
Q_SLOT void on_clicked() {
QMenu * menu = new QMenu();
int last = kmean.getK();
for(int i = 1; i <= last; i++)
{
QAction * action = new QAction(QString("Merge with %1").arg(i), menu);
action->setProperty("index", i);
menu->addAction(action);
}
connect(menu, SIGNAL(triggered(QAction*)), SLOT(triggered(QAction*)));
menu->popup(mapToGlobal(rect().bottomRight()));
}
public:
Widget(QWidget *parent = 0) : QPushButton("Show Menu ...", parent) {
connect(this, SIGNAL(clicked()), SLOT(on_clicked()));
}
};
int main (int argc, char **argv)
{
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"
I create own widget based on QTableView. It's something like file dialog (list). I want to act intuitively.
a) working with whole rows
b) indicator also worked with whole rows
c) using switched enter the lower level (subdirectory)
d) after run program or moving to a lower level cursor must be on the first row of the table (row 0)
And there is problem. I can not force the program to place the cursor on the first line.
I tried several methods, but none succeeded. setCurrentIndex. selectRow etc. Cursor is always somewhere else. Not highlighted, not on first line, but once it's on 10 position second on 4 position etc. It behaves unpredictably.
How can I do it?
Here my code is:
mFileBoxWidget::mFileBoxWidget(QWidget *parent) :
QTableView(parent)
,model(new QFileSystemModel())
{
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
this->setShowGrid(false);
this->verticalHeader()->setVisible(false);
this->installEventFilter(this);
model->setReadOnly(true);
this->setModel(model);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch );
this->setColumnWidth(1,70);
this->setColumnWidth(2,70);
this->setColumnWidth(3,110);
this->setRootIndex(model->setRootPath("C://"));
this->setSelectionMode(QAbstractItemView::SingleSelection);
this->setSelectionBehavior(QAbstractItemView::SelectRows);
//this->selectRow(0); //Does not work - goto first row
//this->setCurrentIndes(Index); //Does not work - goto index x=0 y=0
}
Thank you with advance for all your responses.
Solved!
The problem is that the model is asynchronous. So reads the data in another thread. When I tried to set the index to the first line, still basically did not exist. The solution is, of course, to wait for loading the thread. At this point signal directoryLoaded(QString) is send. As a result, it is necessary to wait for the signal, and only then set index.
connect(myModel, SIGNAL(directoryLoaded(QString)), this, SLOT(onLoaded()));
void mFileBoxWidget::onLoaded()
{
QModelIndex index = myModel->index(myModel->rootPath());
this->setCurrentIndex(index.child(0, index.column()));
}
You shouldn't name your member variable model. QTableView has function model(), the compiler thinks this->model is meant to be this->model(), therefore you get the error you mentioned.
This is untested code, but I think something like this should work:
QModelIndex firstRow = QTableView::model()->index(0, 0);
QTableView::selectionModel()->select(firstRow,
QItemSelectionModel::ClearAndSelect |
QItemSelectionModel::Rows );
EDIT: (2013-06-19 06:12:58 UTC)
A simple (and ugly) workaround that worked so far for me is triggering a call to m_tableView->selectRow(0); from a QTimer.
Here's the sample code:
Header:
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
class QTableView;
class QFileSystemModel;
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = 0);
~MainWidget();
private:
void layoutWidgets();
QFileSystemModel *m_model;
QTableView *m_tableView;
private slots:
void selectFirstRow();
// for debugging only
void selectionChanged();
};
#endif // MAINWIDGET_H
Implementation:
#include "mainwidget.h"
#include <QTableView>
#include <QHBoxLayout>
#include <QFileSystemModel>
#include <QHeaderView>
#include <QTimer>
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
m_tableView = new QTableView(this);
m_model = new QFileSystemModel(this);
m_model->setReadOnly(true);
m_tableView->setModel(m_model);
m_tableView->setRootIndex(m_model->setRootPath(QDir::homePath()));
m_tableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_tableView->setShowGrid(false);
m_tableView->verticalHeader()->setVisible(false);
m_tableView->setColumnWidth(1,70);
m_tableView->setColumnWidth(2,70);
m_tableView-> setColumnWidth(3,110);
m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
//m_tableView->->setSectionResizeMode(0, QHeaderView::Stretch ); // Qt 5?
layoutWidgets();
connect(m_tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()) );
// This works
QTimer::singleShot(1000, this, SLOT(selectFirstRow()));
// Direct invocation - doesn't works
// selectFirstRow();
}
void MainWidget::layoutWidgets()
{
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(m_tableView);
setLayout(mainLayout);
setFixedSize(500,500);
}
void MainWidget::selectFirstRow()
{
m_tableView->selectRow(0);
}
void MainWidget::selectionChanged()
{
qDebug("Selection changed");
}
MainWidget::~MainWidget()
{}
The weird thing is, if QTimer::singleShot() needs to be triggered with a delay of at least ~25 ms., otherwise it wouldn't work in my system.
Here's the alternative, subclassing QTableView:
#include "mytableview.h"
#include <QFileSystemModel>
#include <QHeaderView>
#include <QTimer>
MyTableView::MyTableView(QWidget *parent) : QTableView(parent)
{
QFileSystemModel *myModel = new QFileSystemModel;
setModel(myModel);
setRootIndex(myModel->setRootPath(QDir::homePath()));
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setShowGrid(false);
verticalHeader()->setVisible(false);
//installEventFilter(this);
myModel->setReadOnly(true);
//setSectionResizeMode(0, QHeaderView::Stretch ); // Qt 5
setColumnWidth(1,70);
setColumnWidth(2,70);
setColumnWidth(3,110);
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
QTimer::singleShot(100, this, SLOT(selectFirstRow()));
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()));
}
void MyTableView::selectFirstRow()
{
// qDebug("Selecting first row");
// QModelIndex firstRow = QTableView::model()->index(0,0);
// if(firstRow.isValid()){
// selectionModel()->select(firstRow, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
// }else{
// qDebug("Invalid index");
// }
selectRow(0);
}
void MyTableView::selectionChanged()
{
qDebug("Selection changed.");
}