Passing a QObject to an Script function with QJSEngine? - qt

I'm trying to call a function in an external script while passing a QObject as a parameter.
My QObject is defined as this:
#ifndef INSERTVALUES_H
#define INSERTVALUES_H
#include <QObject>
struct insertValueDef
{
QString name;
QString xmlCode;
QString value;
bool key;
bool insert;
};
typedef insertValueDef TinsertValueDef;
class insertValues : public QObject
{
Q_OBJECT
public:
explicit insertValues(QObject *parent = 0);
~insertValues();
void insertValue(TinsertValueDef value);
int count();
void setItemName(int index, QString name);
void setItemXMLCode(int index, QString xmlCode);
void setItemValue(int index, QString value);
void setItemIsKey(int index, bool isKey);
void setItemToInsert(int index, bool toInsert);
QString itemName(int index);
QString itemXMLCode(int index);
QString itemValue(int index);
bool itemIsKey(int index);
bool itemToInsert(int index);
bool valueIsNumber(int index);
int getIndexByColumnName(QString name);
private:
QList<TinsertValueDef> m_insertList;
};
#endif // INSERTVALUES_H
My JS Script function is this:
function beforeInsert(table,data)
{
if (table == "tmpTable")
{
var index = data.getIndexByColumnName("tmpfield");
if (index >= 0)
{
data.setItemValue(index,"Carlos Quiros");
}
}
}
The code that runs runs the script is the following:
QFile scriptFile(javaScript);
if (!scriptFile.open(QIODevice::ReadOnly))
{
log("Error: Script file defined but cannot be opened");
return 1;
}
JSEngine.evaluate(scriptFile.readAll(), javaScript);
scriptFile.close();
insertValues insertObject;
TinsertValueDef tfield;
tfield.key = false;
tfield.name = "tmpfield";
tfield.xmlCode = "tmpCode";
tfield.value = "tmpValue";
tfield.insert = true;
insertObject.insertValue(tfield);
QString error;
beforeInsertFunction = JSEngine.evaluate("beforeInsert",error);
if (!beforeInsertFunction.isError())
{
QJSValue insertListObj = JSEngine.newQObject(&insertObject);
QJSValue result = beforeInsertFunction.call(QJSValueList() << "tmpTable" << insertListObj);
if (result.isError())
{
log("Error calling BeforInsert JS function.");
return 1;
}
else
{
log("JS function seems to be ok");
for (int pos = 0; pos < insertObject.count(); pos++)
{
log(insertObject.itemName(pos) + "-" + insertObject.itemValue(pos));
}
return 1;
}
}
else
{
log("Error evaluating BeforInsert JS function. [" + error + "]");
return 1;
}
I can see that the parameter "table" is passing properly but the rest of the code is not working. I guess I cannot do:
var index = data.getIndexByColumnName("tmpfield");
Any idea what am I doing wrong? and what else should I do to make it work?
Thanks,

In order to access properties or invoke methods of QObjects passed to QJSEngine (or to QML), you need to declare them using Q_PROPERTY and Q_INVOKABLE macros in your QObject-derived class declaration.
Please see the Qt documentation for more details: Exposing Attributes of C++ Types to QML

Related

How to create a QVariant-based generic model?

Quite often I find myself in need of some custom scheme model, mandating the implementation of more and more models, made even more tedious by the inability of QObject derived classes to be templates.
Qt has the QStandardItemModel but that seems a bit verbose and inconvenient to use, especially from the qml side, and total overkill for a basic list model.
There is also the basic qml ListModel, but that is limited and not elegant to use on the C++ side, and I do suspect a tad more bloated than it needs to be.
Qt has QVariant, which is what its model/view architecture uses internally, so it is surprising that the framework doesn't provide something as simple as:
// qml code
VarMod {
roles: ["name", "age", "weight"]
Component.onCompleted: {
insert(["Jack", 34, 88.5], -1) // qml doesn't support
insert(["Mary", 26, 55.3], -1) // default arg values
}
}
// cpp code
VarMod vm { "name", "age", "weight" }; // member declaration
vm.insert({ "Jack", 34, 88.5 });
vm.insert({ "Mary", 26, 55.3 });
And here it is.
Note that you do have to be responsible with the parameters, as there is no type safety, in fact it has implicit analog to ListModel's dynamicRoles - that is, it will accept and work with any QVariant compatible value on every role slot.
As for memory efficiency, consider that QVariant has 8 bytes for data, plus 4 bytes for type id, plus another 4 bytes of padding, for a total of 16 bytes. That is not insignificant if you are using it for small data types, like say bool, so in case you have a data scheme that has a lot of small (1 - 4 bytes) fields and a scores of items, implementing a full model will still be the better option. It is still a lot better than the generic object model I am using, which has to carry the bloat of QObject, and even more significant in the case of qml objects.
Additionally, QVariant being 16 bytes, I opted to not use the convenience of QVariantList for data storage, which has an underlying QList, making the situation worse than it needs to be. Although that is fixed in Qt 6, which gets rid of QList as it is, and replaces it with an alias of QVector. Still, std::vector helps to avoid that in any case, plus it might actually be a tad faster, since it doesn't have to deal with COW and atomic ref counters. There are several auxiliary methods to help with pre-allocation and release of memory as well.
The model has a safeguard against the change the roles for obvious reasons, the latter is primarily intended to be initialized just once, but there is reset() that is intended to be used in a more dynamic qml context, making it possible to redefine the model schema on the fly and provide a compatible delegate. For the sake of certainty, the roles can only be redefined after the model has been explicitly reset.
There is a minute difference in inserting, on the c++ side, the parameter pack is passed wrapped in {}, in qml it is wrapped in [], both leveraging implicit conversion in the context specific way. Also, note that qml currently doesn't support omitting parameters with default values provided on the c++ side, so for appending you do have to provide an invalid index. Naturally, it would be trivial to add convenience methods for appending and prepending if needed.
In addition to the syntax example of the question, it is also possible to add multiple items at once, from "declarative-y" qml structure such as:
let v = [["Jack", 34, 88.5],
["Mary", 26, 55.3],
["Sue", 22, 69.6]]
vm.insertList(v, -1)
Finally, type safety is possible to implement if the scenario really calls for it, then each role can be specified with the expected type to go with it, such as:
VarMod vm {{"name", QMetaType::QString},
{"age", QMetaType::Int},
{"weight", QMetaType::QReal}};
and then iterating and making the necessary checks to ensure type safety when inserting.
Update: I also added serialization, and save/load from disk features, note that this will serialize the data together with the mode schema.
class VarMod : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
QVariantList vroles;
QVariantList roles() const { return vroles; }
QHash<int, QByteArray> _roles;
std::vector<std::vector<QVariant>> _data;
inline bool checkArgs(int rc) const {
if (rc == _roles.size()) return true;
qWarning() << "arg size mismatch, got / expected" << rc << _roles.size();
return false;
}
inline bool inBounds(int i, bool ok = false) const {
if (i > -1 && i < (int)_data.size()) return true;
if (!ok) qWarning() << "out of bounds" << i; // do not warn if intentionally appending
return false;
}
inline bool validRole(int r) const { return (r > -1 && r < _roles.size()); }
protected:
QHash<int, QByteArray> roleNames() const override { return _roles; }
int rowCount(const QModelIndex &) const override { return _data.size(); }
QVariant data(const QModelIndex &index, int r) const override {
r = r - Qt::UserRole - 1;
if (inBounds(index.row()) && validRole(r)) return _data[index.row()][r];
return QVariant();
}
public:
VarMod() {} // for qml
VarMod(std::initializer_list<QByteArray> r) {
int rc = Qt::UserRole + 1;
for (const auto & ri : r) {
_roles.insert(rc++, ri);
vroles << QString::fromLatin1(ri);
}
rolesChanged();
}
inline void insert(std::initializer_list<QVariant> s, int i = -1) {
if (!checkArgs(s.size())) return;
insert(QVariantList(s), i);
}
inline bool setItem(int i, std::initializer_list<QVariant> s) {
if (checkArgs(s.size())) return setItem(i, QVariantList(s));
return false;
}
void setRoles(QVariantList r) {
if (_roles.empty()) {
int rc = Qt::UserRole + 1;
for (const auto & vi : r) _roles.insert(rc++, vi.toByteArray());
vroles = r;
rolesChanged();
} else qWarning() << "roles are already initialized";
}
void read(QDataStream & d) {
reset();
QVariantList vr;
d >> vr;
quint32 s;
d >> s;
_data.resize(s);
for (uint i = 0; i < s; ++i) {
_data[i].reserve(vr.size());
for (int c = 0; c < vr.size(); ++c) {
QVariant var;
d >> var;
_data[i].push_back(std::move(var));
}
}
setRoles(vr);
beginResetModel();
endResetModel();
}
void write(QDataStream & d) const {
d << vroles;
d << (quint32)_data.size();
for (const auto & v : _data) {
for (const auto & i : v) d << i;
}
}
public slots:
void insert(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
if (!checkArgs(s.size())) return;
beginInsertRows(QModelIndex(), i, i);
_data.insert(_data.begin() + i, { s.cbegin(), s.cend() });
endInsertRows();
}
void insertList(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
int added = 0;
for (const auto & il : s) {
QVariantList ll = il.value<QVariantList>();
if (checkArgs(ll.size())) {
_data.insert(_data.begin() + i + added++, { ll.cbegin(), ll.cend() });
}
}
if (added) {
beginInsertRows(QModelIndex(), i, i + added - 1);
endInsertRows();
}
}
bool setData(int i, int r, QVariant d) {
if (!inBounds(i) || !validRole(r)) return false;
_data[i][r] = d;
dataChanged(index(i), index(i));
return true;
}
bool setDataStr(int i, QString rs, QVariant d) { // a tad slower
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return setData(i, r - Qt::UserRole - 1, d);
qWarning() << "invalid role" << rs;
return false;
}
bool setItem(int i, QVariantList d) {
if (!inBounds(i) || !checkArgs(d.size())) return false;
_data[i] = { d.cbegin(), d.cend() };
dataChanged(index(i), index(i));
return true;
}
QVariantList item(int i) const {
if (!inBounds(i)) return QVariantList();
const auto & v = _data[i];
return { v.begin(), v.end() };
}
QVariant getData(int i, int r) const {
if (inBounds(i) && validRole(r)) return _data[i][r];
return QVariant();
}
QVariant getDataStr(int i, QString rs) const {
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return getData(i, r);
qWarning() << "invalid role" << rs;
return QVariant();
}
QVariantList take(int i) {
QVariantList res = item(i);
if (res.size()) remove(i);
return res;
}
bool swap(int i1, int i2) {
if (!inBounds(i1) || !inBounds(i2)) return false;
std::iter_swap(_data.begin() + i1, _data.begin() + i2);
dataChanged(index(i1), index(i1));
dataChanged(index(i2), index(i2));
return true;
}
bool remove(int i) {
if (!inBounds(i)) return false;
beginRemoveRows(QModelIndex(), i, i);
_data.erase(_data.begin() + i);
endRemoveRows();
return true;
}
void clear() {
beginResetModel();
_data.clear();
_data.shrink_to_fit();
endResetModel();
}
void reset() {
clear();
_roles.clear();
vroles.clear();
rolesChanged();
}
void reserve(int c) { _data.reserve(c); }
int size() const { return _data.size(); }
int capacity() const { return _data.capacity(); }
void squeeze() { _data.shrink_to_fit(); }
bool fromFile(QString path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return false;
QDataStream d(&f);
read(d); // assumes correct data
return true;
}
bool toFile(QString path) const {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return false;
QDataStream d(&f);
write(d);
return true;
}
signals:
void rolesChanged();
};
I also created this sorting/filtering view to supplement the model:
class View : public QSortFilterProxyModel {
Q_OBJECT
Q_PROPERTY(QJSValue filter READ filter WRITE set_filter NOTIFY filterChanged)
Q_PROPERTY(bool reverse READ reverse WRITE setReverse NOTIFY reverseChanged)
bool reverse() const { return _reverse; }
void setReverse(bool v) {
if (v == _reverse) return;
_reverse = v;
reverseChanged();
sort(0, (Qt::SortOrder)_reverse);
}
bool _reverse = false;
mutable QJSValue m_filter;
QJSValue & filter() const { return m_filter; }
void set_filter(QJSValue & f) {
if (!m_filter.equals(f))
m_filter = f;
filterChanged();
invalidateFilter();
}
}
public:
View(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0, (Qt::SortOrder)_reverse); }
signals:
void filterChanged();
void reverseChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override {
if (!m_filter.isCallable()) return true;
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return true;
}
return m_filter.call({_engine->toScriptValue(vm->item(sourceRow))}).toBool();
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return false;
}
return vm->getData(left.row(), sortRole()) < vm->getData(right.row(), sortRole());
}
};
For sorting, you just have to specify the sorting role, note that it is the index of the "column" rather than the int value from the roles hash. For filtering it works via a qml functor that receives the model item as a JS array, and expects to return a bool, a c++ functor can be easily added via std::function if needed. Also note that it needs a pointer to the actual qml engine.
View {
id: vv
sourceModel: vm
sortRole: sr.value
reverse: rev.checked
filter: { sa.value; o => o[1] < sa.value } // "capturing" sa.value to react to value changes
}

Q_GADGET Unknown method return type

I have a class MyWindow. This class call
MyWindow.h
class MyWindow : public QObject
{
Q_OBJECT
Q_PROPERTY(int nbMatch READ GetNbMatch NOTIFY matchChangedQMLL)
public:
explicit MyWindow(QObject *parent = nullptr);
explicit MyWindow(AsyncCalendarGetter& calendar, QObject *parent = nullptr);
~MyWindow();
Q_INVOKABLE QString getFirstMatch() {
return QString::fromUtf8(calendar->GetCalendar().front().GetDate().toString().c_str());
}
Q_INVOKABLE Date getFirstDate() {
return calendar->GetCalendar().front().GetDate();
}
// ...
}
Date.h
#pragma once
#include <string>
#include <sstream>
#include <QObject>
#include <iostream>
class Date
{
Q_GADGET
Q_PROPERTY(std::string dateStr READ toString)
public:
Date(std::string&& str);
Date();
Date(int day, int month, int year, int h, int m);
friend std::ostream& operator<<(std::ostream& os, const Date& obj);
Q_INVOKABLE std::string toString() const {
std::stringstream ss;
ss << *this;
return ss.str();
}
private:
int day = 1;
int month = 1;
int year = 1970;
int h = 0;
int m = 0;
};
When I call the first function getFirstMatch in my QML, it works.
But, the second function gtFirstDate does not work, I have an error message :
qrc:/main.qml:27: Error: Unknown method return type: Date
My QML
Connections {
target: mainmywindow
onMatchChangedQMLL: {
lbl0.text = "" + Number(mainmywindow.nbMatch) + " -> " + qsTr(mainmywindow.getFirstMatch()) // WORKS
lbl1.text = "" + Number(mainmywindow.nbMatch) + " -> " + qsTr(mainmywindow.getFirstDate().toString()) // DOES NOT WORK
}
}
Someone have an idea ?
Thanks
You can find information about Q_DECLARE_METATYPE here:
https://doc.qt.io/qt-5/qmetatype.html#Q_DECLARE_METATYPE
According to it, you should do these steps to resolve your problem:
Add Q_DECLARE_METATYPE(Date) after declaration of Date
Add qRegisterMetaType<Date>(); somewhere before engine.load(url);
(I assume you have QQmlApplicationEngine engine; in main() to load and run QML)
Update: Also std::string not supported directly by QML, you should use QString

Qt - Create WriteOnly and ReadOnly socket

I have a socketDescriptor.
I need to two sockets. One of them just for ReadOnly and another one for WriteOnly.
Is it impossible? If so, how?
Qt's sockets don't support having more than one socket object per native socket, but you can approximate this by creating appropriate unidirectional interfaces.
In the example below, the interfaces don't perform any buffering, thus they enforce that there is only once of each kind of interface per socket. Once you use the interface, you shouldn't be using the socket directly.
// https://github.com/KubaO/stackoverflown/tree/master/questions/socket-split-43409221
#include <QtNetwork>
class SocketBase : public QIODevice {
Q_OBJECT
public:
explicit SocketBase(QAbstractSocket * parent) : QIODevice{parent} {
connect(parent, &QAbstractSocket::connected, this, &SocketBase::connected);
connect(parent, &QAbstractSocket::disconnected, this, &SocketBase::disconnected);
connect(parent, &QAbstractSocket::stateChanged, this, [this](QAbstractSocket::SocketState state){
emit stateChanged(state);
setOpenMode(m_dev->openMode());
});
connect(parent,
static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
this, [this](QAbstractSocket::SocketError error){
setErrorString(m_dev->errorString());
emit this->error(error);
});
setOpenMode(m_dev->openMode());
}
QAbstractSocket::SocketError error() const {
return m_dev->error();
}
QAbstractSocket::SocketState state() const {
return m_dev->state();
}
Q_SIGNAL void connected();
Q_SIGNAL void disconnected();
Q_SIGNAL void error(QAbstractSocket::SocketError);
Q_SIGNAL void stateChanged(QAbstractSocket::SocketState);
bool isSequential() const override { return true; }
protected:
QAbstractSocket * const m_dev = static_cast<QAbstractSocket*>(parent());
};
class ReadSocket : public SocketBase {
Q_OBJECT
public:
explicit ReadSocket(QAbstractSocket * parent) : SocketBase(parent) {
for (auto proxy : parent->findChildren<ReadSocket*>())
Q_ASSERT(proxy == this);
connect(m_dev, &QIODevice::readyRead, this, &QIODevice::readyRead);
}
bool atEnd() const override {
return QIODevice::atEnd() && m_dev->atEnd();
}
qint64 bytesAvailable() const override {
return m_dev->bytesAvailable();
}
bool canReadLine() const override {
return m_dev->canReadLine();
}
protected:
qint64 readData(char * data, qint64 maxLength) override {
return m_dev->read(data, maxLength);
}
qint64 readLineData(char *data, qint64 maxLength) override {
return m_dev->readLine(data, maxLength);
}
qint64 writeData(const char *, qint64) override {
return -1;
}
};
class WriteSocket : public SocketBase {
Q_OBJECT
public:
explicit WriteSocket(QAbstractSocket * parent) : SocketBase(parent) {
for (auto proxy : parent->findChildren<WriteSocket*>())
Q_ASSERT(proxy == this);
connect(m_dev, &QIODevice::bytesWritten, this, &QIODevice::bytesWritten);
}
qint64 bytesToWrite() const override {
return m_dev->bytesToWrite();
}
bool flush() {
return m_dev->flush();
}
protected:
qint64 readData(char *, qint64) override {
return -1;
}
qint64 writeData(const char * data, qint64 length) override {
return m_dev->write(data, length);
}
};
int main(int argc, char *argv[])
{
QCoreApplication app{argc, argv};
QHostAddress addr{"127.0.0.1"};
quint16 port{9341};
QTcpServer server;
if (! server.listen(addr, port)) qFatal("can't listen");
QObject::connect(&server, &QTcpServer::newConnection, &server, [&]{
auto s = server.nextPendingConnection();
QObject::connect(s, &QTcpSocket::readyRead, s, [s]{
s->write(s->readAll());
});
QObject::connect(s, &QTcpSocket::disconnected, s, &QObject::deleteLater);
});
const char data_[] = "dhfalksjdfhaklsdhfklasdfs";
auto const data = QByteArray::fromRawData(data_, sizeof(data_));
QTcpSocket client;
WriteSocket writer(&client);
ReadSocket reader(&client);
QObject::connect(&writer, &WriteSocket::connected, [&]{
writer.write(data);
});
QObject::connect(&reader, &ReadSocket::readyRead, [&]{
if (reader.bytesAvailable() >= data.size()) {
auto const read = reader.read(data.size());
Q_ASSERT(read == data);
qApp->quit();
}
});
client.connectToHost(addr, port);
return app.exec();
}
#include "main.moc"

QDoubleSpinBox with leading zeros (always 4 digits)

I have a QDoubleSpinBox in range 0-7000, but want the value always displayed as 4 digits
(0-> 0000, 1 -> 0001 , 30 -> 0030, 3333 -> 3333).
I understand I can add a prefix, but a prefix is always added.
What are my options?
If you use integers, then QSpinBox will be enough.
You can simply inherit from QSpinBox and re-implement the textFromValue function:
class MySpinBox: public QSpinBox
{
Q_OBJECT
public:
MySpinBox( QWidget * parent = 0) :
QSpinBox(parent)
{
}
virtual QString textFromValue ( int value ) const
{
/* 4 - number of digits, 10 - base of number, '0' - pad character*/
return QString("%1").arg(value, 4 , 10, QChar('0'));
}
};
Filling QString this way does the trick.
Since prefix is not an option solution if you consider negative values, in my opinion the best and most elegant solution is defining your own custom spin box by deriving QAbstractSpinBox. Here is a small example:
Note that it is far from perfection and it serves just as an example on what could be done:
q4digitspinbox.h:
#ifndef Q4DIGITSPINBOX_H
#define Q4DIGITSPINBOX_H
#include <QAbstractSpinBox>
#include <QLineEdit>
class Q4DigitSpinBox : public QAbstractSpinBox
{
Q_OBJECT
public:
explicit Q4DigitSpinBox(QWidget *parent = 0);
StepEnabled stepEnabled() const;
double maximum() const;
double minimum() const;
void setMaximum(double max);
void setMinimum(double min);
void setRange(double minimum, double maximum);
double value() const;
public slots:
virtual void stepBy(int steps);
void setValue(double val);
signals:
void valueChanged(double i);
void valueChanged(const QString & text);
private:
double m_value;
double m_minimum;
double m_maximum;
QLineEdit m_lineEdit;
};
#endif // Q4DIGITSPINBOX_H
q4digitspinbox.h:
#include "q4digitspinbox.h"
Q4DigitSpinBox::Q4DigitSpinBox(QWidget *parent) :
QAbstractSpinBox(parent),
m_value(0),
m_minimum(-99),
m_maximum(99)
{
setLineEdit(&m_lineEdit);
setValue(0.0);
}
QAbstractSpinBox::StepEnabled Q4DigitSpinBox::stepEnabled() const
{
return StepUpEnabled | StepDownEnabled;
}
double Q4DigitSpinBox::maximum() const
{
return m_maximum;
}
double Q4DigitSpinBox::minimum() const
{
return m_minimum;
}
void Q4DigitSpinBox::setMaximum(double max)
{
m_maximum = max;
}
void Q4DigitSpinBox::setMinimum(double min)
{
m_minimum = min;
}
void Q4DigitSpinBox::setRange(double minimum, double maximum)
{
m_minimum = minimum;
m_maximum = maximum;
}
double Q4DigitSpinBox::value() const
{
return m_value;
}
void Q4DigitSpinBox::stepBy(int steps)
{
m_value += (double)steps / 10;
if (fabs(m_value - 0) < 0.00001)
{
m_value = 0;
}
if(m_value < m_minimum || m_value > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)m_value);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(m_value < 0)
{
value = QString("-%1%2").arg(prefix).arg(-m_value);
}
else
{
value = QString("%1%2").arg(prefix).arg(m_value);
}
m_lineEdit.setText(value);
emit valueChanged(m_value);
emit valueChanged(value);
}
void Q4DigitSpinBox::setValue(double val)
{
if(val < m_minimum || val > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)val);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(val < 0)
{
value = QString("-%1%2").arg(prefix).arg(-val);
}
else
{
value = QString("%1%2").arg(prefix).arg(val);
}
m_lineEdit.setText(value);
emit valueChanged(val);
emit valueChanged(value);
}
I didn't provide any commentary since I considered it pretty straight forward, but if needed I can add a few more explanations.
I hope this helps.

HowTo restore QTreeView last expanded state?

What I have:
QTreeView class with table data
And connected QAbstractTableModel model
Question: how to save expanded state of items? Is some one have finished solutions?
PS: I know, that I can do this code by myself, but I don't have much time, and this is not the major problem of our project, but still we need it, because app contain a lot of such tables, and every time expanding tree items is annoyed process...
First, thanks to Razi for persistentIndexList and isExpanded way.
Second, here is the code which works for me just fine :-)
dialog.h file:
class Dialog : public QDialog
{
Q_OBJECT;
TreeModel *model;
TreeView *view;
public:
Dialog(QWidget *parent = 0);
~Dialog(void);
void reload(void);
protected:
void createGUI(void);
void closeEvent(QCloseEvent *);
void saveState(void);
void restoreState(void);
};
dialog.cpp file:
Dialog::Dialog(QWidget *parent)
{
createGUI();
reload();
}
Dialog::~Dialog(void) {};
void Dialog::reload(void)
{
restoreState();
}
void Dialog::createGUI(void)
{
QFile file(":/Resources/default.txt");
file.open(QIODevice::ReadOnly);
model = new TreeModel(file.readAll());
file.close();
view = new TreeView(this);
view->setModel(model);
QVBoxLayout *mainVLayout = new QVBoxLayout;
mainVLayout->addWidget(view);
setLayout(mainVLayout);
}
void Dialog::closeEvent(QCloseEvent *event_)
{
saveState();
}
void Dialog::saveState(void)
{
QStringList List;
// prepare list
// PS: getPersistentIndexList() function is a simple `return this->persistentIndexList()` from TreeModel model class
foreach (QModelIndex index, model->getPersistentIndexList())
{
if (view->isExpanded(index))
{
List << index.data(Qt::DisplayRole).toString();
}
}
// save list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
settings.setValue("ExpandedItems", QVariant::fromValue(List));
settings.endGroup();
}
void Dialog::restoreState(void)
{
QStringList List;
// get list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
List = settings.value("ExpandedItems").toStringList();
settings.endGroup();
foreach (QString item, List)
{
// search `item` text in model
QModelIndexList Items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(item));
if (!Items.isEmpty())
{
// Information: with this code, expands ONLY first level in QTreeView
view->setExpanded(Items.first(), true);
}
}
}
Have a nice day!)
PS: this example based on C:\Qt\4.6.3\examples\itemviews\simpletreemodel code.
Thanks to Razi and mosg I was able to get this working. I made it restore the expanded state recursively so I thought I would share that part.
void applyExpandState_sub(QStringList& expandedItems,
QTreeView* treeView,
QAbstractItemModel* model,
QModelIndex startIndex)
{
foreach (QString item, expandedItems)
{
QModelIndexList matches = model->match( startIndex, Qt::UserRole, item );
foreach (QModelIndex index, matches)
{
treeView->setExpanded( index, true );
applyExpandState_sub(expandedItems,
treeView,
model,
model->index( 0, 0, index ) );
}
}
}
Then use like:
void myclass::applyExpandState()
{
m_treeView->setUpdatesEnabled(false);
applyExpandState_sub( m_expandedItems,
m_treeView,
m_model,
m_model->index( 0, 0, QModelIndex() ) );
m_treeView->setUpdatesEnabled(true);
}
I am using the Qt::UserRole here because multiple items in my model can have the same display name which would mess up the expand state restoration, so the UserRole provides a unique identifier for each item to avoid that problem.
These two function by using a loop should do that for you:
QModelIndexList QAbstractItemModel::persistentIndexList () const
bool isExpanded ( const QModelIndex & index ) const
Here is a general approach that should work with any QTreeView based widget, that uses some sort of ID system to identify elements (I am assuming the ID is an int, which is stored inside the Qt::UserRole):
void MyWidget::saveExpandedState()
{
for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
saveExpandedOnLevel(tree_view_->model()->index(row,0));
}
void Widget::restoreExpandedState()
{
tree_view_->setUpdatesEnabled(false);
for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
restoreExpandedOnLevel(tree_view_->model()->index(row,0));
tree_view_->setUpdatesEnabled(true);
}
void MyWidget::saveExpandedOnLevel(const QModelIndex& index)
{
if(tree_view_->isExpanded(index)) {
if(index.isValid())
expanded_ids_.insert(index.data(Qt::UserRole).toInt());
for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
saveExpandedOnLevel(index.child(row,0));
}
}
void MyWidget::restoreExpandedOnLevel(const QModelIndex& index)
{
if(expanded_ids_.contains(index.data(Qt::UserRole).toInt())) {
tree_view_->setExpanded(index, true);
for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
restoreExpandedOnLevel(index.child(row,0));
}
}
Instead of MyWidget::saveExpandedState() and MyWidget::saveExpandedState() one could also directly call MyWidget::saveExpandedOnLevel(tree_view_->rootIndex()) and MyWidget::restoreExpandedOnLevel(tree_view_->rootIndex()). I only used the above implementation because the for loop will be called anyway and MyWidget::saveExpandedState() and MyWidget::saveExpandedState() looked cleaner with my SIGNAL and SLOT design.
I have reworked iforce2d's solution into this:
void ApplyExpandState(QStringList & nodes,
QTreeView * view,
QAbstractItemModel * model,
const QModelIndex startIndex,
QString path)
{
path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
for(int i(0); i < model->rowCount(startIndex); ++i)
{
QModelIndex nextIndex = model->index(i, 0, startIndex);
QString nextPath = path + QString::number(nextIndex.row()) + QString::number(nextIndex.column());
if(!nodes.contains(nextPath))
continue;
ApplyExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
}
if(nodes.contains(path))
view->setExpanded( startIndex.sibling(startIndex.row(), 0), true );
}
void StoreExpandState(QStringList & nodes,
QTreeView * view,
QAbstractItemModel * model,
const QModelIndex startIndex,
QString path)
{
path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
for(int i(0); i < model->rowCount(startIndex); ++i)
{
if(!view->isExpanded(model->index(i, 0, startIndex)))
continue;
StoreExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
}
if(view->isExpanded(startIndex))
nodes << path;
}
This way there is no need to match data. Obviously - for this approach to work, tree needs to stay relatively unchanged. If you somehow change the order of tree items - it will expand wrong nodes.
Here is a version which doesn't rely on nodes having a unique Qt::UserRole or Qt::DisplayRole - it just serialises the entire QModelIndex
header:
#pragma once
#include <QTreeView>
class TreeView : public QTreeView
{
Q_OBJECT
public:
using QTreeView::QTreeView;
QStringList saveExpandedState(const QModelIndexList&) const;
void restoreExpandedState(const QStringList&);
};
source:
#include "tree_view.h"
#include <QAbstractItemModel>
namespace
{
std::string toString(const QModelIndex& index)
{
std::string parent = index.parent().isValid() ? toString(index.parent()) : "X";
char buf[512];
sprintf(buf, "%d:%d[%s]", index.row(), index.column(), parent.c_str());
return buf;
}
QModelIndex fromString(const std::string& string, QAbstractItemModel& model)
{
int row, column;
char parent_str[512];
sscanf(string.c_str(), "%d:%d[%s]", &row, &column, parent_str);
QModelIndex parent = *parent_str == 'X' ? QModelIndex() : fromString(parent_str, model);
return model.index(row, column, parent);
}
}
QStringList TreeView::saveExpandedState(const QModelIndexList& indices) const
{
QStringList list;
for (const QModelIndex& index : indices)
{
if (isExpanded(index))
{
list << QString::fromStdString(toString(index));
}
}
return list;
}
void TreeView::restoreExpandedState(const QStringList& list)
{
setUpdatesEnabled(false);
for (const QString& string : list)
{
QModelIndex index = fromString(string.toStdString(), *model());
setExpanded(index, true);
}
setUpdatesEnabled(true);
};
For a QFileSystemModel, you can't use persistentIndexList().
Here is my work around. It works pretty well, even if I do say so myself. I haven't tested to see what happens if you have a slow loading filesystem, or if you remove the file or path.
// scrolling code connection in constructor
model = new QFileSystemModel();
QObject::connect(ui->treeView, &QTreeView::expanded, [=](const QModelIndex &index)
{
ui->treeView->scrollTo(index, QAbstractItemView::PositionAtTop);//PositionAtCenter);
});
// save state, probably in your closeEvent()
QSettings s;
s.setValue("header_state",ui->treeView->header()->saveState());
s.setValue("header_geometry",ui->treeView->header()->saveGeometry());
if(ui->treeView->currentIndex().isValid())
{
QFileInfo info = model->fileInfo(ui->treeView->currentIndex());
QString filename = info.absoluteFilePath();
s.setValue("last_directory",filename);
}
// restore state, probably in your showEvent()
QSettings s;
ui->treeView->header()->restoreState(s.value("header_state").toByteArray());
ui->treeView->header()->restoreGeometry(s.value("header_geometry").toByteArray());
QTimer::singleShot(1000, [=]() {
QSettings s;
QString filename = s.value("last_directory").toString();
QModelIndex index = model->index(filename);
if(index.isValid())
{
ui->treeView->expand(index);
ui->treeView->setCurrentIndex(index);
ui->treeView->scrollTo(index, QAbstractItemView::PositionAtCenter);
qDebug() << "Expanded" << filename;
}
else
qDebug() << "Invalid index" << filename;
} );
Hope that helps someone.
My approach was to save the list of expanded items (as pointers) and when restoring, only set as expanded only the items in this list.
In order to use the code below, you may need to replace TreeItem * to a constant pointer to your object (that doesn't change after a refresh).
.h
protected slots:
void restoreTreeViewState();
void saveTreeViewState();
protected:
QList<TargetObject*> expandedTreeViewItems;
.cpp
connect(view->model(), SIGNAL(modelAboutToBeReset()), this, SLOT(saveTreeViewState()));
connect(view->model(), SIGNAL(modelReset()), this, SLOT(restoreTreeViewState()));
...
void iterateTreeView(const QModelIndex & index, const QAbstractItemModel * model,
const std::function<void(const QModelIndex&, int)> & fun,
int depth=0)
{
if (index.isValid())
fun(index, depth);
if (!model->hasChildren(index) || (index.flags() & Qt::ItemNeverHasChildren)) return;
auto rows = model->rowCount(index);
auto cols = model->columnCount(index);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
iterateTreeView(model->index(i, j, index), model, fun, depth+1);
}
void MainWindow::saveTreeViewState()
{
expandedTreeViewItems.clear();
iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
if (!view->isExpanded(index))
{
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(item && item->getTarget())
expandedTreeViewItems.append(item->getTarget());
}
});
}
void MainWindow::restoreTreeViewState()
{
iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(item && item->getTarget())
view->setExpanded(index, expandedTreeViewItems.contains(item->getTarget()));
});
}
I think this implementation gives extra flexibility compared to some of the others here. At least, I could not make it work with my custom model.
If you want to keep new items expanded, change the code to save the collapsed items instead.

Resources