Add a QTreeView in QML - c++

I would like to register a QTreeView c++ object to QML.
I tried to register it like this:
main.cpp:
qmlRegisterType<QTreeView>("com.MyApp.QTreeView", 1, 0, "QTreeView");
relevant code in main.qml
import com.MyApp.QTreeView 1.0
QWindow {
QTreeView{
headerHidden: true
}
}
Result: it compiles. headerHidden property is found so it is registered correctly. However I have an error at runtime:
ASSERT: "!d->isWidget" in file kernel\qobject.cpp, line 2090

QWidgets are not directly compatible with QML such that they can be embedded in a QML view. They are two different UI technologies and cannot be used together in that fashion.
You can however embed a QML view inside of a QWidget hierarchy:
https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
Or just use the QML TreeView component instead:
https://doc.qt.io/qttreeview/qml-treeview.html

Related

QML: C++ classes with "bring your own component"

I'm trying to develop a Qt C++ application, with a QML frontend, but I hit a roadblock.
This is what I have so far:
A Factory class that outputs a choice of objects. These objects, that I'm going to call "controllers", control different pieces of hardware.
The Factory would be exposed to the QML layer with setContextProperty.
The controller would be chosen basically with a combo box controlling the factory.
Now, for the tricky bit. I want that the "controllers" behave in a "bring your own component" way. This means that they would have a method returning the respective QML file for their controller. That shouldn't be to hard to do, it's basically biding a Loader to a method of the Factory/Manager saying the file with the component to load into a placeholder.
But the problem is: how can this newly created component and this newly created controller know and talk to each other? This is something I did before with QWidgets, just having pointers between the classes. Quite trivial.
I tried an architecture like this before for QWidgets, but seems to not be ideal for QML.
I made this drawing of what I would ultimately like to happen:
This architecture allows for a very trivial plugin system (at least in the QWidgets world) and I would very much like to keep that. Not a massive singleton and account for every possible action...
I'd appreciate ideas!
I think this is actually very easy, if you return a QQuickItem from the C++ side. If you do so you can create it with a specific context, in which you can set your "specific hardware controller" as a property
QQmlComponent *qml_controller = new QQmlComponent(qengine, "some_file.qml");
QQmlContext *context = new QQmlContext(); //should probably give a pointer to owning object
context->setContextProperty("controller", pointer_to_hw_cont);
return qml_controller->create(context);
The Loader setSource method have additional parameter you could pass to provide initial value for some property. Something like this:
ComboBox {
model: controlerFactory.specificHWListModel
onCurrentTextChanged: {
var specificHWControler = controlerFactory.getObjectFor( currentText );
loader1.setSource(
specificHWControler.qml_file,
{ "controler": specificHWControler }
);
}
}
Loader {
id: loader1
}
The specificHWListModel cold be QStringList or some custom QAbstractListModel.
And getObjectForcould be just a invokable function.
Q_INVOKABLE QObject* getObjectFor(QString hwName);
The object returned from Q_INVOKABLE function will be managed by QQmlEngine by default if you don't set by the QQmlEngine::setObjectOwnership. Remember to register your SpecificHWControler class to QQmlEngine.
The qml_file SpecificView.ui.qml, should have property controler, and could be edited with Designer:
import SpecificHWControlerModule 1.0
Item {
property SpecificHWControler controler
}
https://doc.qt.io/qtcreator/quick-connections-backend.html

Tumbler in C++/Qt

I'm trying to implement a tumbler to set the time for an alarm in C++. Yet I've only seen tumblers in Qt quick and therefore coded in QML. Now I've tried to get QML code in my C++ code by doing:
void SmartAlarm::showTumbler(){
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl(":/files/includes/AlarmTumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
rightLayout->addWidget(quickWidget);
}
My QML file looks like this:
import QtQuick 2.12
import QtQuick.Window 2.2
import QtQuick.Controls 2.12
import QtQuick.Extras 1.4
TumblerColumn{
id: weekdayTumbler
model: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
}
TumblerColumn {
id: hourTumbler
model: 24
}
TumblerColumn {
id: minuteTumbler
model: 60
}
All I get is a blank, white widget in my layout. What am I doing wrong? Is there a way to implement a tumbler in Qt without using QMLs?
I think it doesn't find the tumbler-file. You can check this the easiest by starting the program and check in the Application Output (Bottom Menu in QTCreator) for following message:
":/files/includes/AlarmTumbler.qml: No such file or directory"
If you can't find it, it might be because you use a Shadow Build and the actual execution-files are in a different folder than the QML Files. To solve this, you can go to "Projects" and deactivate "Shadow Build", rebuild and you should see the tumbler.
The implementation itself should work fine. I tested it locally, added everything to the MainWindow though cause I don't know where your "rightLayout" comes from.
ui->setupUi(this);
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl("tumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
this->ui->rightLayout->addWidget(quickWidget);

Include another QML file from a QML file

There's another question on Stackoverflow about this matter but I don't find the accepted solution possible. So I ask again because the old question is out of attention.
The situation is this way. I have application screens defined by 'main.qml', 'feature1.qml', 'feature2.qml'.
These screens share the same toolbar below title bar. The toolbar has multiple items so copy-paste the QML code is like crazy. This question: QML file include - or one monolithic file (structure QML code)? says it's possible to just use QML file name as component name but I can't get it working.
Any solution? with details pls.
Let's assume you have a file called main.qml and a component in another file called MyCustomText.qml. If both files are in the same directory you can directly load the component like this:
// in Main.qml
Rectangle {
id: root
MyCustomText {
text: "This is my custom text element"
}
}
If MyCustomText.qml is in another subdirectory MyComponents for example to group all your custom components together, you first need to import the directory before using the component the same way:
// in Main.qml
import "MyComponents"
Rectangle {
id: root
MyCustomText {
text: "This is my custom text element"
}
}
Another important thing to note is that your QML files should always start with an uppercase letter if you want to be able to use them this way
Of course your Loader solution works too but this is the easiest way to import QML files in other components.
Finally I have dug it out from internet. Let's say the to-be-included file is 'mycomponent.qml' in this directory structure (Qt Quick):
projectdir/
qml/
projectname/
main.qml
mycomponent.qml
The content of 'mycomponent.qml' (for example):
Text {
text:"Hello, Scooby Doo!";
}
We have to load it this way (in 'main.qml'):
Rectangle {
...
Loader {
source:"mycomponent.qml";
}
...
}
See Qt documentation about reuseable components.
The imported QML file defines a type whose name is the same as the filename (capitalized, less the .qml suffix). QML calls the type a reuseable component. You use that type name to instantiate an object in the importing QML document (file.)
Its not like a C language include, where the text of the included file is inserted into the including file. Its more like importing the name of a class in Python, and then instantiating an object of that class in the importing file. Or somewhat similar to Javascript, the imported file is creating a prototype object, and the importing file is prototypically inheriting from it. Except note the discussion about the root object and what properties of the component will be visible (because of QML's document scoping.) You won't be able to access everything in the imported file as if it were a C include, a Python import, or a JS inheritance.
It's easy like that. Put all your file components in a folder like "components". In your case, the name of the file can be Toolbar.qml. Write the QML code for you toolbar, my example will draw a red rectangle.
import QtQuick 2.6
Item {
width: 500
height: 100
Rectangle {
width: 500
height: 100
color: "red"
radius: width * 0.5
}
}
And then, in your screens which you want to use this component (for example, file main.qml), is simple like that:
import "components" as Components
Components.Toolbar {
Layout.fillHeight: true
}
Take care about the location of files, and still all components should start with a Caps letter, in this example:
\main.qml
\components\Toolbar.qml
You can just call the Name of the qml.
for ex.
I have 2 qml file.
The main.qml and Merchant.qml
I just called the Merchant. it should be showed in intellisense.
ApplicationWindow {
id: mainWindow
visible: true
Component{
id: merchantsComponent
Merchant{
id: merchants
width: mainWindow.width
height: mainWindow.height
}
}
}
You can just call that compenent to Loader
You can directly call:
Window {
id: mainWindow
visible: true
Feature1{}
}
like this, to load Feature1.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