Access QML WebView from C++ - c++

I have qml file with a webview in it.
I get a reference to that object in c++ but is QObject*
Can i cast it to something similar to QWebView from which i can get the QWebPage object and so?
qobject_cast<> returns NULL if i try QWebView.
It works with cast to QQuickItem but that doesn't really help me much.
Thanks

You can't. The QML WebView element doesn't have a public C++ API. You have two ways to work around this:
Use private API. First, add this to the .pro file:
QT += quick quick-private webkit-private
Then include the private headers:
#include <private/qquickwebview_p.h>
Now you can access the WebView, as long as you get a pointer to it (for example by using QObject::findChildren or by passing the QObject* from QML to C++. Here I simply assume the WebView is the root object, for simplicity):
QQuickWebView * webView = dynamic_cast<QQuickWebView*>(viewer.rootObject());
qDebug() << webView->url();
Use the generic QObject API to access the WebView's properties:
QObject * webView = viewer.rootObject();
qDebug() << webView->property("url");

Related

Mock a C++ class instantiated in QML

I am trying to write a plugin that contains some QML files and some C++ classes that provide lower-level functionalities and communicate with another application. They are used by the QML components.
I want to be able to manage the life time of these C++ objects from QML (i.e. they should be created when the QML file is loaded and destroyed when QML is destroyed), while still being able to mock the C++ objects.
I tried a few different approaches so far. Ideally, the result will be that I can use qmlscene on the QML file I want to edit and have a dummydata folder next to that file which contains the mock for the instantiated C++ class.
If I try that by using qmlRegisterType in a plugin class that inherits from QQmlExtensionPlugin (similar to the example in https://qmlbook.github.io/ch17-extensions/extensions.html), and I pass the resulting library to qmlscene, the QML file will not use the mock, but instantiate a C++ object instead. This means that sometimes, I need to start up a fair bit of logic to get some mocked data into my QML file.
It seems like the example in the "QML Book" suggests to completely design the QML component with a mock before introducing any C++ to QML. Is there a way to do that more sustainable? I guess, I could avoid using qmlRegisterType for a C++ class that I want to mock for a while, by commenting out the according line, but I would like to not have to do that.
The other approach I tried was using QQMLContext::setContextProperty from a central C++ controller class. That enables me to pass the C++ object to QML from C++ and also use the dummydata, however the object's lifetime will not be managed by the QML component, but from C++. Also, each class should potentially be instantiated multiple times and connecting signals properly is pretty error-prone. This is what I found so far:
auto proxy = std::make_shared<Proxy>();
//make `proxy` object known in QML realm
_qmlEngine.rootContext()->setContextProperty("proxy", proxy.get());
connect(&_qmlEngine, &QQmlApplicationEngine::objectCreated,
[&proxy](QObject *object, const QUrl &url) {
if (url == QUrl("qrc:/imports/Common/TestWindow.qml")) {
// make sure the proxy is not destroyed when leaving scope of this function
connect(qobject_cast<QQuickWindow *>(object),
&QWindow::visibilityChanged, // as a dirty workaround for missing QWindow::closing signal
[proxy]() mutable { proxy.reset(); }); // delete proxy when closing TestWindow
}
});
_qmlEngine.load(QUrl("qrc:/imports/Common/TestWindow.qml"));
Is there a "comfortable" way to mock data instantiated in QML and originally coming from C++, or is there at least a good way to attach the life time of such a C++ object to the life time of the QML object?
The way I solved this issue is as follows:
The actual production application will use a C++ plugin, containing only C++ files and no QML.
For mocking, there is a QML module with the same name as the C++ plugin, containing QML files which provide the same interface as the equivalent C++ classes. This module is passed to qmlscene in addition to the general QML includes.
If the C++ class header looks like this:
class Proxy : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int foo)
Q_INVOKABLE void start();
signals:
void started();
}
And this class is made available to QML like this:
qmlRegisterType<Proxy>("Logic", 1, 0, "Proxy");
The QML mock (in file Proxy.qml) can look like this:
import QtQml 2.12
QtObject {
signal started()
property var foo: 42
function start() { console.log("start") }
}
And be importable in QML with a qmldir file that looks like this:
module Logic
Proxy 1.0 Proxy.qml
The final call to qmlscene would be
qmlscene [path/to/prototype/qml] -I [path/to/folder/containing/proxy/mock/]

Passing QObject pointer from a QML object to C++

I have a publisher/subscriber key-value DB class in Qt/C++. The subscribers can connect by passing the key ( string ) , their QObject pointer and the property.
Whenever a value of the subscribed key changes, the properties of the subscribed QObject changes to the new value. Works in Qt/C++ fine.
Now I want to make a view in QML. Is it possible to pass from QML to C++ an object with 3 parameters:
QObject pointer of the QML object
property as string
DB-key as string
?
The preferable solution were, as if the property connects to another property:
Item{ myQmlProp: MyCppInst("myDBKey") }
EDIT
What currently works is this solution:
Item{
id:myqmlitem
myQmlProp: MyCppInst("myDBKey","myQmlProp",myqmlitem)
}
or like this:
Item{
id:myqmlitem
Component.onCompleted:{
MyCppPublisher.subscribe("myDBKey1","myQmlProp1",myqmlitem)
MyCppPublisher.subscribe("myDBKey2","myQmlProp2",myqmlitem)
}
}
Compared to the preferable solution, I have to pass the connected property name and the QML item instance explicitly. But it is ok, many thanks for the answers!
I've hoped to use QML's this-Keyword but have learned, that it is currently undefined in QML :-(
Just give the object an id and pass that id to the function, it will become a QObject * on the C++ side. Then you can use the meta system to access properties by name:
// qml
Item {
id: someitem
...
CppObj.cppFoo(someitem)
}
// c++
void cppFoo(QObject * obj) {
...obj->property("myDBKey")...
}
A reference would do as well, for example children[index].
What you could do is a function taking just your dbkey as a parameter, and return a QObject* exposing a Q_PROPERTY with a READ function and NOTIFY signal.
This way, you just have to tell with the notify signal the value has changed, and the QML will call the read function automatically.
It could be implemented like that Item{ myQmlProp: MyCppInst("myDBKey").value }.
If you know the db keys at compile time you could just add a property for each of them in your MyCppInst directly, or if you know them at the creation of your cpp class you could put them in a QQmlPropertyMap.
Usage would be like that : Item { myQmlProp: MyCppInst.myDbKey } (or MyCppInst["myDbKey"] if you need to be dynamic in the QML side).

Load property from QML-file in C++

I'm building a plugin-system for my QML+C++ application. The plugins are QML-files.
A qml-file could look like this:
Item {
title: "Sexy Plugin"
version: "1.0"
}
How can I read title and version within C++?
Every QML item inherits QObject directly or indirectly, so you can use the meta system to read and write properties "dynamically".
QVariant QObject::property(const char * name) const
Returns the value of the object's name property. If no such
property exists, the returned variant is invalid.
If the item happens to be the root item, you can use QQuickItem * QQuickView::rootObject() const to get it, if not, you will have to set the objectName : QString property, avaiable for every QObject derived object and call findChild<QObject*>("name") from the root object.
The easiest wold be, to write a QuickItem in C++. Even when its just a small component, that just holds title and version. Then it is easily accessible within C++ and its the cleanest way, to decouple view and logic.Then you can just define properties with the well-known Q_PROPERTY Macro.
If you actually want to use only Qml-written components and don't want to write anything in C++, there is a solution mentioned at this page: http://qt-project.org/doc/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
Quoting the relevant part from "Accessing Members of a QML Object Type from C++":
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100);
So QQmlProperty might help you.
As I don't know, what your goal is, I won't recommend anything. Anyway in my opinion, the first way of just writing the Item in C++ is in many cases much cleaner then trying to get the properties from Qml.

Clearing WebView Cache from withing QML

I open website with QDeclarativeView and use JavaScript to load next pages in same view.
After each website loaded, my program occupy 20mb more of memory. How do i clean the cache or otherwise release the memory after new website is loaded?
I tried:
decView->engine()->rootContext()->setContextProperty("myEngine", decView->engine());
and then in qml
myEngine.clearComponentCache()
but i get
TypeError: Result of expression 'myEngine.clearComponentCache' [undefined] is not a function.
What i should do?
EDIT: here is what i got sofar:
aws.cpp
void Aws::openQMLWindowSlot(){
QDeclarativeView *decView= new QDeclarativeView();
decView->engine()->rootContext()->setContextProperty("myAws",this);
decView->setSource(QUrl("qrc:/inc/firstqml.qml"));
decView->show();
}
void Aws::clearCacheQMLSlot(){
//HERE I GOT PROBLEM
}
firstqml.qml
import QtQuick 1.1
import QtWebKit 1.0
WebView {
id: webView
objectName: "myWebView"
url:"http://example.com"
onLoadFinished: {myAws.clearCacheQMLSlot();}
}
There two reasons why your code doesn't work as intended. First, to be able to access slots and invokable methods of QObject descendants, you have to register them:
qmlRegisterType<QDeclarativeEngine>("MyApp", 1, 0, "QDeclarativeEngine");
And second, QDeclarativeEngine::clearComponentCache is neither a slot nor an invokable method, so it would still not work. It is simply impossible to call normal C++ methods from QML.
What you actually have to do is to implement an own QObject based class wrapping the call to QDeclarativeEngine::clearComponentCache in a slot, registering the class like above, set an instance of that class as an context property like you did with the declarative engine and finally call the slot from QML.

Connecting qml-signals to Qt

I'm trying to use a qml-grid view in my code. I'm trying to couple it with my C++ code.
I've dynamically created a list view model and passed across the qml file. It works fine.
However, I'm facing trouble when I want to connect a Qml signal to Qt/c++ code. I've handled mouseArea in my Qml-rectangle and emitting a signal from there.
I'm trying to connect to the signal as follows:
QDeclarativeView *pQMLContainer = NULL;
TempWidget *pTemp = new TempWidget();
pQMLContainer = new QDeclarativeView(pTemp);
pQMLContainer->setResizeMode(QDeclarativeView::SizeRootObjectToView);
pQMLContainer->rootContext()->setContextProperty("imgModel", createModel() );
pQMLContainer->setSource(QUrl("../Temp/qml/gridview-example.qml"));
QObject *rootObject = dynamic_cast<QObject*>pQMLContainer->rootObject();
QObject::connect(rootObject, SIGNAL(keyPressed()), pTemp, SLOT(onKeyPressed()));
When the connect statement runs, I get an error: cannot connect to "null" object.
On debugging, I found I could never get "rootObject" as a valid pointer.
Where am I going wrong?
Thanks
Can you try this ? (it is example code from Qt Docs)
QObject *item = pQMLContainer->rootObject();
QObject::connect(item, SIGNAL(keyPressed()),
pTemp, SLOT(onKeyPressed()));
The code is pretty much straight:
in .cpp file:
ui->declarativeView->setSource(QUrl("qrc:/Resources/main.qml"));
QGraphicsObject *obj = ui->declarativeView->rootObject();
connect ( obj, SIGNAL(clicked()), this, SLOT(itemClicked()));
and QML File:
import Qt 4.7
Rectangle {
width: 100
height: 100
id: rect
signal clicked
Text {
text: "Hello World"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
rect.clicked();
}
}
}
one more thing, check the location of your qml file, it should be accessible to the binary.
Perhaps you should use qobject_cast instead of dynamic_cast? See e.g. question
dynamic_cast returns NULL but it shouldn't
QGraphicsObject is a QObject so no cast should be required. If your compiler complains, try adding #include <QGraphicsObject>.
Just casting without the compiler knowing the classes is asking for trouble. (Especially as there is multiple inheritance involved.)
I could finally get this working. I'm not sure if this is the real solution to the problem, but finally this got it working:
I was setting the qml path as a relative path to my working folder. And yes the path was indeed correct, as I could see the qml and its contents. I just happened to change the qml path from relative to the working folder to relative to "qrc" as:
pQMLContainer->setSource(QUrl("qrc:/gridview-example.qml"));
instead of:
pQMLContainer->setSource(QUrl("../Temp/qml/gridview-example.qml"));
and it started working. I'm not sure if I had to add the qml to the qrc (I've just started using qml).
Thanks everyone for your support!
Mots