QML Listview and c++: ReferenceError Model not defined - c++

I want to display my C++ class as a ListView in QML. I have an empty vector, which will later be filled:
QList<QObject*> _myList;
and set the context
QQmlContext *ctxt = _eng->rootContext();
ctxt->setContextProperty("modelName",QVariant::fromValue(_myList));
In the qml file I have
ListView {
id: listView
model: modelName
delegate: myDelegate {}
}
But when starting the application I get the following error
qrc:/screen2.qml:252: ReferenceError: modelName is not defined
What am I doing wrong? Strangly, the error does not prevent the list from being correctly displayed once it is filled.

Call setContextProperty before loading your QML file.
When you load your QML file, the engine evaluates its bindings, since you haven't yet set the context property for modelName, it outputs a warning.
When you set it afterwards, this binding is reevaluated and that's why the list is eventually correctly displayed in your case.

Related

How to use a QML Combobox to lookup data from a c++ model and insert it into another one

after creating some database applications using Qt and widgets, I am exploring Quick Controls and QML. I want to mimic the use of a QComboBox mapped to a QSqlRelationalTableModel to populate one table out of another one. Something similar to:
ui->cmbArea->setModel(model->relationModel(areaIdx));
ui->cmbArea->setModelColumn(model->relationModel(areaIdx)->fieldIndex("description"));
mapper->addMapping(ui->cmbArea, areaIdx);
(Using widgets, cmbArea is a QComboBox, model is a QSqlRelationalTableModel, and mapper a QDataWidgetMapper)
I managed to get something working like:
property ModeloCodificador modeloCodificador: ModeloCodificador{}
property ModeloTabla modeloTabla: ModeloTabla{}
ComboBox {
model: modeloCodificador
textRole: "elemento"
valueRole: "id"
Component.onCompleted: {
currentIndex = find(modeloTabla.get(0, modeloTabla.codigoIdx), Qt.MatchExactly)
}
onActivated: {
modeloTabla.set(0, modeloTabla.codigoIdx, currentValue)
}
}
Is there a better way to accomplish this?

QML TableView Model Silently Failing?

edit: added some clarification
Using QML 5.14
It seems the model attribute of TableView does not want to display a QList<int>, or any variation of int, be it qint8, qint32, etc. I can make it work with unsigned QList<uint>, however I need to keep the negative range of values in my application.
I've found that the information is making it to the qml layer, because when i call:
console.log("cfs.amounts is " + cfs.amounts)
console.log("model is " + model)
console.log("modellength is " + model.length)
I actually am getting the expected console output of:
qml: cfs.amounts is 11,12
qml: model is 11,12
qml: modellength is 2
I've ensured the TableView is functional by directly passing it data, i.e. model: [11, 22] and it displays correctly, i.e. it displays the indexes 0, 1. However I can't get it to display anything at all when I pass it the cfs.amounts, which is a QList<int> in c++. So according to the console.log, the model data is there, it is correct, it's getting passed from c++ to qml without issues, and the length is good -- the TableView is just failing to display it.
The only thing I can think of, is that the TableView is silently failing to display arrays of signed integers. However I may also be completely wrong, because I can't get a Repeater item to recognize it in its model, neither. I've searched but I can't find any bug reports on this subject. Does anyone have any suggestion on how to get the qml model to recognize the passed QList<int> ? This is all in QML 5.14.
cashflowschedule.h
#ifndef CASHFLOWSCHEDULE_H
#define CASHFLOWSCHEDULE_H
#include "QObject"
#include "QList"
class CashFlowSchedule : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<int> amounts READ amounts)
public:
CashFlowSchedule() {};
QList<int> amounts() { return {11,12}; }
};
#endif // CASHFLOWSCHEDULE_H
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import cpps 1.0
Window {
visible: true
width: 640
height: 480
CashFlowSchedule { id: cfs }
TableView {
anchors.fill: parent
model: cfs.amounts
delegate: Text { text: index }
Component.onCompleted: {
console.log("cfs.amounts is " + cfs.amounts)
console.log("model is " + model)
}
}
}
included in the main.cpp
#include "cashflowschedule.h"
...
qmlRegisterType<CashFlowSchedule>("cpps", 1, 0, "CashFlowSchedule");
...
QList<int> is not one of the official supported C++ types used for models (see the list here). A bug report exists to clarify the documentation on that point. A QVariantList is a good alternative to use.

QML global access to C++ class

My app has 3 level:
- Main Form
- Settings
- Device searching
From the main form I open others using:
var component = Qt.createComponent("qrc:/touch/content/SettingsMain.qml");
win = component.createObject(rootWindow2);
In main form I created object Network (it is C++ class)
Network{
id: net1
}
Object "net1" is accesible by other QML objects which were not invoked by above code creating component. Unfortunately, all QML objects created by using the code above do not see "net1".I need something like global object for all QML files. Any ideas?
You should use a singleton for that, it exists for that exact purpose:
// in main.cpp
qmlRegisterSingletonType(QUrl(QStringLiteral("qrc:/touch/content/SettingsMain.qml")), "Core", 1, 0, "Settings");
Then you can access that from every QML file by importing:
import Core 1.0
//.. and use it
Settings.someProperty
Settings.someFoo()
You would also have to add a pragma Singleton line in the beginning of SettingsMain.
You can also skip registering the singleton from C++ if you implement a qmldir file, but IMO registering in C++ is better when the singleton is an integral part of the application.
When using qml singletons, you don't need to create the instance yourself, it will be automatically created.
Your question is ambigious as to what you actually want to be "global", I assume settings is one thing you would like to be global.
You can also register C++ objects as singletons in QML, for example:
qmlRegisterSingletonType<Network>("Core", 1, 0, "Network", someFooReturningValidNetworkPtr);
Singleton is not the only way. QML provides lots of ways to get same results.
Another way is to pass the net1 id in as a property when calling Qt.createObject(). Example below:
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Item {
id: rootWindow2
property Item settingsMain
Network{
id: net1
}
Component.onCompleted: {
var component = Qt.createComponent("qrc:/touch/content/SettingsMain.qml");
settingsMain = component.createObject(rootWindow2, {"net1": net1});
}
}
}
SettingsMain.qml
import QtQuick 2.0
Item {
property Item net1
Component.onCompleted: {
console.log("SettingsMain.qml: can I see net1? %1".arg(net1 ? "yes" : "no"))
}
}

Implementing an active item function in QAbstractListModel subclass for QML

I have created a ListView in QML, and I want to be able to implement something like an active item, using an QAbstractListModel as the model that the QML uses. To be more specific, I am using a generic object model, as described in the answer to this question. However, in my QML delegate I have something like this:
Component
{
id: itemDlgt
Rectangle
{
id: rec
width: 50
height: 50
color: "#645357"
property bool itemActive: false
AbstractItem //AbstractItem is the class that my object model uses. Its only property is a boolean value
{
id: s
}
MouseArea
{
anchors.fill: parent
onClicked:
{
s.status = !s.status
itemActive= s.status // using this to trigger onItemActiveChanged
console.log(s.status)
console.log(index)
}
}
onItemActiveChanged:
{
if (itemActive == true)
rec.color = "#823234"
else
rec.color = "#645357"
}
}
}
What I want to do, is have only one item in the ListView to hold a true value at a time. As soon as another item is clicked, I want to set the AbstractItem of the previously selected item to false, then set the AbstractItem of the new item to true.
Of course, I could use something like this:
ListView
{
id: view
anchors.fill: parent
clip: true
model: myAbstractModel
delegate: itemDlgt
spacing: 5
focus: true //using focus could allow to highlight currently selected item,
//by using ListView.isCurrentItem ? "blue": "red"
}
But this doesn't seem to work with a QAbstractListModel, since neither arrow keys, nor clicking on an item seems to highlight the current item.
In addition, I want that item to be highlighted again, in the event that the ListView is forced to reset itself from the c++ side, when I use beginResetModel() and endResetModel(). If I were using a QAbstractListModel as described in Qt Documentation, I would be able to do that easily, by saving the index of the selected item, and storing it until a new item was selected. In other words, something like this:
//somewhere in QAbstractListModel's subclass .h file
int x; // temporary storage for keeping currently selected item
//QAbstractListModel's subclass .cpp file
changeCurrentItem(int index) // function that gets called when user selects an item
{
//...
//Checking if x has a value, If not, I simply set the
//needed item's value to true, and then set x to the value of index.
//Else do the following...
m_ItemList.at(x).setToFalse();
m_ItemList.at(index).setToTrue();
x = index;
}
But I was facing several issues when I was using that, which is the reason why I decided to use a generic object model, which seems to be more flexible.
Finally, I want to be able to send a signal to the c++ side of the code whenever the currently selected item changes, which is trivial with a MouseArea, but I know not of a method to do that using the ListView's focus property, should that be an option.
To make long things short, this is my question in a few words:
Am I missing something in regards to QML code, that would allow me to highlight the currently selected item, while also being able to keep it active after reseting ListView, and being able to send a signal to c++ when it changes?
If not, is there a way to implement a function inside my generic object model, that keeps track of the currently selected item, so that I can highlight it?
If using the ListView's currentIndex property is the goto approach.
Simply set the color in the delegate via:
color: index == view.currentIndex ? "blue": "red"
And don't forget that in order for this to work, you must set the active index, it doesn't work by magic, and by default it will be -1, so no item will be highlighted:
//in the delegate mouse area
onClicked: view.currentIndex = index
The same applies to keyboard events too, you will have to tell the view what to do with the events, so you can have the up and down keys increment and decrements the current index.
There is another approach, which is more applicable if you want to detach the active item functionality from any view, and have it at item level:
property ItemType activeItem: null
function setActiveItem(item) {
if (activeItem) activeItem.active = false
activeItem = item
activeItem.active = true
}
The focus property only specifies whether an item has keyboard event focus or not.
If you want to signal on the the index change, use onCurrentIndexChanged

Get webview document title when HTML is loaded

I have the following Qt webview:
QWebView *view = new QWebView();
view->load(QUrl("http://example.com"));
I want to get the title of document when load is finished, and use it to set the main window title.
From what I suppose view->loadFinished() returns true if page was loaded or not.
For setting the window title I use webView->setWindowTitle(newTitle). So, I need that newTitle variable that I want to be the document title.
How can I do this?
QWebView::loadFinished is a signal. You can subscribe to it to know when the page is loaded:
connect(view, SIGNAL(loadFinished(bool)), this, SLOT(onLoaded()));
To access HTML title you can use QWebView::title property.
void onLoaded()
{
window->setWindowTitle(view->title());
}
Rather then using loadFinished it may be more appropriate to use signal titleChanged(const QString& title) to apply a new title to the window:
connect(view, SIGNAL(titleChanged(QString)), this, SLOT(setWindowTitle(QString)));
EDIT:
Example:
QWebView* webView = new QWebView();
connect(webView, SIGNAL(titleChanged(QString)), webView, SLOT(setWindowTitle(QString)));
webView->load(QUrl("http://yahoo.com"));
webView->show();