QML ListModel file read/write and good practices - qt

I am just starting to learn QML and I am kinda lost at what should I do when I want to read ListModel from settings.
My dilemma is:
1) If I define the model in C++ code I have no problems loading it (I have done similar stuff loads of times) but I am sacrificing my time to actually write (and later update) model code and then recompile each time I need to do it.
2) My other idea is to read settings file into QList of QVariantMap and create the model in QML file reading this list using javascript. This way I will only need 2 C++ functions, one to read the file section by section and one to write it. But as I said - I am only starting QML programming and not sure if it is sane or discouraged.
Can someone comment on good practices when one needs dynamic QML ListModel?
UPD: I seem to need to clarify the question:
Do I even need C++ data model if the stuff is simple enough to read from settings and then parse directly into ListModel via Javascript? Or are there pitfalls I am not aware of that make C++ way the only reasonable choice?
UPD2 After some mroe researching I am tempted to go with LocalStorage and forgo c++ altogether

Making an existing C++ model editable is quit easy, since you get everything from Qt.
You have the following in .h
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum MyRoles {
SomeRole = Qt::UserRole,
// ...
}
// ...
bool setData(const QModelIndex &index, const QVariant &value, int role);
in .cpp
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
switch (role) {
case SomeRole:
// your writer code
emit dataChanged(index, index, (QVector<int>(0) << SomeRole));
return true;
case SomeOtherRole:
// ...
return true;
default:
qCritical() << "MyModel.setData: Unknown role:" << role;
return false;
}
}
Now your can use QML internals to perfrom a write.
MyModelDelegate {
Button {
text: somerole
onClicked: {
// this will call setData with the correct row index and SomeRole
somerole = "some other value"
}
}
}
This C++ code is only recompiled when you add new roles, which should not happen very often or your write method changed.

Related

How to use QKeySequence or QKeySequenceEdit from QML?

Is it possible to use a QKeySequence or QKeySequenceEdit in QML? I only see the documentation for C++ https://doc.qt.io/qt-5/qkeysequence.html#details
To provide context, I want a QKeySequence to be input by the user of the application so that I can pass it to my C++ backend so that I can hook into native OS APIs and also serialize it to file.
I do not want to actually establish the shortcut within Qt.
I created a new object that wraps the QKeySequence::toString and makes it available from QML so I wouldn't have to re-implement a massive switch-case in QML.
#ifndef QMLUTIL_H
#define QMLUTIL_H
#include <QObject>
#include <QKeySequence>
// A singleton object to implement C++ functions that can be called from QML
class QmlUtil : public QObject{
Q_OBJECT
public:
Q_INVOKABLE bool isKeyUnknown(const int key) {
// weird key codes that appear when modifiers
// are pressed without accompanying standard keys
constexpr int NO_KEY_LOW = 16777248;
constexpr int NO_KEY_HIGH = 16777251;
if (NO_KEY_LOW <= key && key <= NO_KEY_HIGH) {
return true;
}
if (key == Qt::Key_unknown) {
return true;
}
return false;
}
Q_INVOKABLE QString keyToString(const int key, const int modifiers){
if (!isKeyUnknown(key)) {
return QKeySequence(key | modifiers).toString();
} else {
// Change to "Ctrl+[garbage]" to "Ctrl+_"
QString modifierOnlyString = QKeySequence(Qt::Key_Underscore | modifiers).toString();
// Change "Ctrl+_" to "Ctrl+..."
modifierOnlyString.replace("_", "...");
return modifierOnlyString;
}
}
};
To expose this in QML, you have to say engine.rootContext()->setContextProperty("qmlUtil", new QmlUtil()); in your main.cpp where you are setting up your QQmlEngine.
Then you can type qmlUtil.keyToString(event.key, event.modifiers) in QML to turn a keyboard event to a string.
You can combine that with the solution here https://stackoverflow.com/a/64839234/353407 replacing the individual cases with a single function call to qmlUtil.keyToString
You can set a string to the sequence property from Shortcut, see Qt docs.
For example if you want to chain Ctrl+M and Ctrl+T, you specify the following:
Shortcut {
sequence: "cltr+m,ctrl+t"
onActivated: console.log("you activated turbo mode")
}
It's even possible to assign multiple keyboard shortcuts to the same action, using the sequences (plural) property: Qt docs

If I call a setValue() or similar function of a Qt widget, when is its valueChanged() slot guaranteed to be executed?

Let's say I have multiple input widgets to set up the same parameter. For example, there is a QSlider and a QSpinBox which need to show the same value. In the valueChanged() slot of one of them I call the setValue() of the other one.
Obviously, this would result in an endless loop of them calling each other.
A similar problem arises when this input widget controls some external resource or device. If the user changes the value, it will send the new value to the external device. But if the external device changes the value (or it is read from a settings file, etc) then I have to update the widget, which in turn will send the value, which in turn will update the widget, and so on.
A third scenario is when I save the values into a file or database, but I have to initialize the widgets to some value at the beginning, possibly before I got all the values from the database. But by initializing the widgets at the beginning of my program, they will write that dummy value into the database, overwriting the real values.
The obvious solution for these problems is to just have a bool which allows or forbids the side effects of the valueChanged() functions.
For example, if I want to change the value of my slider, I use
editing = true;
slider.setValue(value);
editing = false;
While I have if (editing) return; at the beginning of my valueChanged() function.
Assuming I didn't fiddle with setting up the signals and slots manually, but they were done by QtCreator, is there a danger of the slot being called later, for example after the editing flag is set to false again? I tried it, and it works, but I am unsure how guaranteed it is.
If you use direct connection (the default for objects in the same thread), the slot is called as soon as the signal is emitted, that is before the setValue method returns.
If you use Qt::QueuedConnection, the slot is invoked when control returns to the event loop of the receiver's thread.
See Qt::ConnectionType
The way I would go about solving this problem is by having another QObject that will be your data model. Your data will be centralized in your model, and will gotten/set via the model. This way your widgets wouldn't need to know about one another and can be created in separate places in your code as long as they can access your model.
Your model will have a method setValue and a signal valueChanged, so it will look some thing like this:
class Model : public QObject {
Q_OBJECT
public:
void setValue(const QVariant& value) {
if (_value != value) {
_value = value;
emit valueChanged(_value);
}
}
const QVariant& getValue() const {
return _value;
}
public signals:
void valueChanged(QVariant& value);
private:
QVariant _value;
}
Then your widgets can take the same instance of Model as a dependency and listen to its valueChanged signal and update themselves. The widgets will also listen to user input, and when the user changes the value then they will change the value in the model. That way the other widgets will get notified about the change.
Your widgets will look like this:
class MySlider : public QSlider {
Q_OBJECT
public:
explicit MySlider(QSharedPointer<Model> model, QWidget *parent=nullptr)
: QSlider(parent), _model(model) {
connect(this, &QSlider::valueChanged, this, [this](int value){
_model->setValue(value);
});
connect(_model.data(), &Model::valueChanged, this, &MySlider::onValueChanged);
//this is to update the widget with the latest value upon creation
onValueChanged(_model->getValue());
}
private slots:
void onValueChanged(const QVariant& value) {
if (value.toInt() != value()) {
//this is calling QSlider::setValue
setValue(value.toInt());
}
}
}
Before you create all your widgets you can create your model with the default value, so let's assume it's in main:
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
//note that your model doesn't have a parent, it's a shared pointer
auto model = QSharedPointer<Model>::create();
auto mySlider1 = new MySlider(model, &w);
auto mySlider2 = new MySlider(model, &w);
return a.exec();
}
P.S. You can also look into QDataWidgetMapper and see if it can accomplish what you're looking for.

QTableView with QStandardItemModel: How to perform live updates during editing a cell?

Recently, I made the switch to QT. It has taken some time, but I am starting to find my way around. However, one issue remains:
I want to port a program, that responds to every key press while editing a cell in a table view (QTableView with QStandardItemModel). The idea is to show and update a list of possibilities on a separate form while the user is entering a text in a table view's cell. After every key stroke, the list needs to be updated according to the current text in the edit field of some cell.
Using QTableView::installEventFilter and QEvent::KeyPress, I can get every key press while the table view is in focus, but the cell text / model is only updated after editing, which prohibits live updates of the list.
The model's dataChanged signal is only emitted after editing has finished and not during the user's input.
Any ideas on how to solve this?
Should I use a QItemDelegate?
Or should a QLineEdit be connected to a cell somehow and can this be done without it visually being apparent, so the user still appears to be working directly inside a cell?
Thank you for any help
It works (that is, until the main window is resized...)
Perhaps not the best solution, but at least i found a way to make it work.
I put the slot in the source file that contains the main window, because that is what I have always been used to (C++ Builder)...
GLiveEdit.H - Subclass QStyledItemDelegate
#ifndef GLIVEEDIT_H
#define GLIVEEDIT_H
#include <QStyledItemDelegate>
class GLiveEdit : public QStyledItemDelegate {
public:
GLiveEdit (QObject *_ParentWindow = 0, const char *_Slot = 0);
protected:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
protected:
mutable QLineEdit *Editor;
const char *Slot;
QWidget *ParentWindow;
};
#endif // GLIVEEDIT_H
GLiveEdit.CPP
#include "gliveedit.h"
#include <QLineEdit>
GLiveEdit::GLiveEdit (QObject *_ParentWindow, const char *_Slot)
: QStyledItemDelegate (_ParentWindow)
{
Editor = 0;
Slot = _Slot;
ParentWindow = (QWidget *) _ParentWindow;
}
QWidget *GLiveEdit::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Editor = (QLineEdit *) QStyledItemDelegate::createEditor (parent, option, index);
connect (Editor, SIGNAL (textChanged (const QString &)), ParentWindow, Slot);
return Editor;
}
MainWindow.CPP - Install Subclassed QStyledItemDelegate ("GLiveEdit")
void MainWindow::SetUp()
{
// Instantiate subclassed QStyledItemDelegate "GLiveEdit" as "Live"
// Also pass the slot "OnEditChanged" that is to be called during editing
Live = new GLiveEdit (this, SLOT (OnEditChanged (const QString &)));
// Tell the table about the instantiated subclassed delegate "Live"
ui->tvOverview->setItemDelegate (Live);
}
void MainWindow::OnEditChanged (const QString &NewText)
{
// NewText contains the up to date text that is currently being edited
}
Anyone any ideas on having it also work after the window has been resized?
Calling the SetUp function from within QMainWindow::resizeEvent does not seem to work, unfortunately.
Also, I suppose QTableView deletes the item delegate from memory itself?
Edit: The item delegate only stops working if the window is resized during the editing... It keeps functioning if the edit is finished first.
It works! Problems after resizing were due to a bug of mine :-) (located elsewhere in the source code)
Any suggestions for improvement still welcome!

QTableView - set the first column as "read only"

I have a QTableView based on a QStandardItemModel.
I want to set the first column as "read only" and all the others columns editable.
I'm not an expert of QT and OOP, i searched around the web and in the QT-documentation and I've understand that I need to reimplement the flags(const QModelIndex &index) function of my model, but I don't know how and where do the re-implementation.
Thanks in advance!
You should create a new class inherited from QStandardItemModel, reimplement method flags and use your new class instead of the standard one.
class MyModel : public QStandardItemModel
{
public:
virtual Qt::ItemFlags flags(const QModelIndex& index) const override
{
Qt::ItemFlags result = QStandardItemModel::flags(index);
if (index.column() == 0) //0 is the first column!
{
result &= ~Qt::ItemIsEditable;
}
return result;
}
}
Another way to do the same:
- create a new class inherited from QStandardItem,
- reimplement flags in the same way
- call QStandardItemModel::setItemPrototype with an instance of the new class
This way is a little bit more complicated because you will need to reimplement method QStandardItem::clone as well.

In Qt how to sort the immediate child indexes of a QModelIndex

I'm writing a C++ application that uses Qt classes to work with certain data models. For that purpose I inherited from QAbstractItemModel:
// the following is a class that represents the actual data used in my application
class EventFragment
{
....
private:
qint32 address;
QString memo;
QDateTime dateCreated;
QVector<EventFragment*> _children;
....
};
// the following is the model representation that used by my application to display the actual details to the user
class EventModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit EventModel (QObject *parent = 0);
....
private:
// the following is the root item within the model - I use a tree-like presentation to show my data
EventFragment* _rootFragment;
};
At some point I needed a sort/filter option in my application so I also created a class that inherits from QSortFilterProxyModel
class EventProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit EventProxyModel (QObject *parent = 0);
...
public:
// I had to add my custom implementation in the 'lessThan' method to achieve a
// more complex sort logic (not just comparing the actual values but using
// additional conditions to compare the two indexes)
virtual bool lessThan ( const QModelIndex & left, const QModelIndex & right ) const;
...
};
To achieve sorting, I used the default QSortFilterProxyModel::sort() method (I haven't reimplemented it in my proxy model class) and for a time it seemed to work.
At some point though, I noticed that the actual QSortFilterProxyModel::sort() method sorts the entire model and what I need is to sort only the immediate children of a certain index.
I tried to reimplement the sort() method of the EventModel class, but after a while I realized that QSortFilterProxyModel::sort() is not referring to it at all. On the other hand, I'm not sure how to rearrange the indexes in a safe way so that the view which displays the model does not crash.
I think there must be a way to sort only the immediate children of a certain QModelIndex, but I haven't found it yet.
Is there any tutorial/example that demonstrates a possible solution to my case, or some guidelines on how to do it?
Regards
If you want an optimized solution that doesn't do comparisons at all for the indexes you don't want to sort, I think you'd have to reimeplement your own QAbstractProxyModel, which is a non-trivial task. However, if you're fine with a non-optimized solution, I'd try this:
bool EventProxyModel::lessThan( const QModelIndex & left, const QModelIndex & right ) const {
if ( left.parent() == isTheOneToSortChildrenFor ) {
...apply custom comparison
} else {
return left.row() < right.row();
}
}
Comparing the rows in the source should leave everything other then indexes with that specific parent as they are.

Resources