I have created a library to store custom QML components that I am making for an application. I am using Pyside6 with python. This is what the basic directory looks like:
library
| |__CustomComponents
| |__Components1
| |__CustomComponent1.qml
|
|__test
|__MainComponent
|__main.py
|__Main.qml
library and test are on the same level. I want to be able to access the CustomComponent1.qml file for use in the Main.qml, but how would I go about writing an import statement for that in Python? Also, to be able to import Components1 as a module, is the qmldir file all I need to have there? It would be neat if there is a command I can use to generate the files I need to make a module.
Edit: Possible to use QQuickView and QQmlApplicationEngine together?
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView);
newWidget = newWidget()
view.rootContext().setContextProperty("headerWidget", headerWidget)
qml_file = os.fspath(Path(__file__).resolve().parent / 'Main.qml')
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
view.show()
You have to use qmldir to indicate that there is a module in that folder and for it to be used it must be in the list of imports of the engine and for this you must use the addImportPath method.
├── library
│ └── CustomComponents
│ └── Components1
│ ├── CustomComponent1.qml
│ └── qmldir
└── test
└── MainComponent
├── main.py
└── main.qml
CustomComponent1.qml
import QtQuick
Rectangle{
id: root
width: 100
height: 100
color: "salmon"
}
qmldir
module Components1
CustomComponent1 CustomComponent1.qml
main.py
import os
from pathlib import Path
import sys
from PySide6.QtCore import QCoreApplication, Qt, QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
LIBRARY_DIR = CURRENT_DIRECTORY.parents[1] / "library" / "CustomComponents"
def main():
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.addImportPath(os.fspath(LIBRARY_DIR))
url = QUrl.fromLocalFile(os.fspath(CURRENT_DIRECTORY / "main.qml"))
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(
handle_object_created, Qt.ConnectionType.QueuedConnection
)
engine.load(url)
sys.exit(app.exec())
if __name__ == "__main__":
main()
main.qml
import QtQuick
import QtQuick.Window
import Components1
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
CustomComponent1{
}
}
QQuickView
In the case of QQuickView, it has an engine so you must use that one, so the changes are:
main.py
import os
from pathlib import Path
import sys
from PySide6.QtCore import QCoreApplication, Qt, QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView
CURRENT_DIRECTORY = Path(__file__).resolve().parent
LIBRARY_DIR = CURRENT_DIRECTORY.parents[1] / "library" / "CustomComponents"
def main():
app = QGuiApplication(sys.argv)
view = QQuickView()
view.engine().addImportPath(os.fspath(LIBRARY_DIR))
url = QUrl.fromLocalFile(os.fspath(CURRENT_DIRECTORY / "main.qml"))
def handle_status_changed(status):
if status == QQuickView.Error:
QCoreApplication.exit(-1)
view.statusChanged.connect(
handle_status_changed, Qt.ConnectionType.QueuedConnection
)
view.setSource(url)
view.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
main.qml
import QtQuick
import Components1
Rectangle {
width: 640
height: 480
CustomComponent1{
}
}
Related
I have a repo at https://github.com/jh3010-qt-questions/qml_location/tree/loader_with_another_component
I am using Loader to load the MyDeeperComponent into main.qml's Column. However, MyDeeperComponent references another internal component called MySquare. When it loads, I get the error MySquare is not a type and the program quits. Of course, if I comment out MySquare from MyDeeperComponentForm.ui.qml, everything works and MyDeeperComponent loads successfully.
What needs to be changed so MyDeeperComponent can use MySquare and be dynamically loaded?
The directory structure is:
$ tree qml_location/
qml_location/
├── MySquare.qml
├── MySquareForm.ui.qml
├── main.cpp
├── main.qml
├── qml
│ └── more
│ ├── MyDeeperComponent.qml
│ └── MyDeeperComponentForm.ui.qml
├── qml.qrc
├── qml_location.pro
└── qml_location.pro.user
qml_location.pro
QT += quick
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = $$PWD/qml
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH = $$PWD/qml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>qml/more/MyDeeperComponent.qml</file>
<file>qml/more/MyDeeperComponentForm.ui.qml</file>
<file>MySquare.qml</file>
<file>MySquareForm.ui.qml</file>
</qresource>
</RCC>
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column
{
MySquare {}
Loader {
source: "qrc:/qml/more/MyDeeperComponent.qml"
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImportPath( "qrc:/qml" );
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
MySquare.qml
import QtQuick 2.4
MySquareForm {
}
MySquareForm.ui.qml
import QtQuick 2.4
Rectangle {
width: 40
height: 40
color: "blue"
}
Alternatively edit MyDeeperComponentForm.ui.qml by changing
MySquare {}
to
Loader {
source: "../../MySquare.qml"
}
or by adding (as already mentioned in the comments)
import "../.."
Note however, that your use case looks pretty weird from the dependency point of view... I suppose you are already familiar with different import mechanisms in QML but adding the link anyway for reference.
I have a sample project at https://github.com/jh3010-qt-questions/qml_location
If my hierarchy looks like:
$ tree qml_location/
qml_location/
├── MyDeepComponent.qml
├── MyDeepComponentForm.ui.qml
├── main.cpp
├── main.qml
├── qml.qrc
└── qml_location.pro
Then I can write main.qml like:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MyDeepComponent {}
}
and it will work.
However, I would like to organize some QML files into a folder hierarchy and not have them all at the same level.
For example, if I move to:
$ tree qml_location/
qml_location/
├── main.cpp
├── main.qml
├── qml
│ ├── MyDeepComponent.qml
│ ├── MyDeepComponentForm.ui.qml
│ └── more
│ ├── MyDeeperComponent.qml
│ └── MyDeeperComponentForm.ui.qml
├── qml.qrc
└── qml_location.pro
and have a main.qml that looks like:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column
{
MyDeepComponent {}
MyDeeperComponent {}
}
}
Qt Creator tells me that MyDeepComponent and MyDeeperComponent are Unknown.
When I try to run, I get the error: MyDeepComponent is not a type
What can I do so this will work?
One caveat, I do not want to place a special or additional import at the top of main.qml. Is this still possible?
qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>qml/MyDeepComponent.qml</file>
<file>qml/MyDeepComponentForm.ui.qml</file>
<file>qml/more/MyDeeperComponent.qml</file>
<file>qml/more/MyDeeperComponentForm.ui.qml</file>
</qresource>
</RCC>
qml_location.pro
QT += quick
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
This answer doesn't meet the requirement of no imports, but it is worth mentioning anyway that it can be solved with a couple of imports at the top of main.qml.
https://github.com/jh3010-qt-questions/qml_location/tree/import_solution
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import "qml"
import "qml/more"
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column
{
MyDeepComponent
{
}
MyDeeperComponent
{
}
}
}
You can do something like this:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column
{
Loader {
source: "qrc:/qml/MyDeepComponent.qml"
}
Loader {
source: "qrc:/qml/more/MyDeeperComponent.qml"
}
}
}
Though I'm not quite sure why you would want to. Might help to describe the problem you are trying to solve?
https://github.com/jh3010-qt-questions/qml_location/tree/create_object_solution
createObject can be used to load the component directly into the Column.
main.qml looks like:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property var componentNames: [ "qml/more/MyDeeperComponent.qml", "qml/MyDeepComponent.qml" ]
function generateObjects()
{
function generateOneObject( name )
{
var component
var componentObject
function finishCreation()
{
componentObject = component.createObject( contentColumn );
}
component = Qt.createComponent( `qrc:/${name}` )
if ( component.status === Component.Ready )
{
finishCreation()
}
else
{
component.statusChanged.connect( finishCreation );
}
}
for ( var index in componentNames )
{
generateOneObject( componentNames[ index ] )
}
}
Component.onCompleted: {
generateObjects()
}
Column
{
id: contentColumn
}
}
Another solution is to move main.qml into the qml folder. This allows main.qml to find MyDeepComponent because they are siblings. To find MyDeeperComponent, main.qml can import the "more" directory.
This solution in represented in the all_in_one_solution branch.
directory structure
$ tree qml_location/
qml_location/
├── main.cpp
├── qml
│ ├── MyDeepComponent.qml
│ ├── MyDeepComponentForm.ui.qml
│ ├── main.qml
│ └── more
│ ├── MyDeeperComponent.qml
│ └── MyDeeperComponentForm.ui.qml
├── qml.qrc
├── qml_location.pro
└── qml_location.pro.user
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
qml.qrc
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/MyDeepComponent.qml</file>
<file>qml/MyDeepComponentForm.ui.qml</file>
<file>qml/more/MyDeeperComponent.qml</file>
<file>qml/more/MyDeeperComponentForm.ui.qml</file>
</qresource>
</RCC>
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import "more"
Window
{
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column
{
MyDeepComponent
{
}
MyDeeperComponent
{
}
}
}
EDIT: I'm reading your part of the question where you state that you don't want an import at the top. I don't think this is possible, without throwing a bunch of addImportPath in your main (for each folder that you are to create) or heavy fiddling with qrc-aliases. Leaving the rest of the answer for a future visitor or for if you change your mind ;-)
You can use a qmldir file to make them available in a namespace of choice. But there is a bit of a learning curve. The qmldir file has to be situated in folder which resembles the namespace.
For example if you want to use MyApplication as namespace, you have to put the qmldir file in a folder "MyApplication" (within the qml import path). If you want MyOrg.application1 as namespace, you have to put qmldir in "MyOrg/application1".
For your situation, I choose the first option. I would make a separate qrc file for the importable qml files, which is also needed if you want to easily put all qml files in a "qml" folder:
qml/qml.qrc:
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/qml/MyApplication">
<file>MyDeepComponent.qml</file>
<file>MyDeepComponentForm.ui.qml</file>
<file>more/MyDeeperComponent.qml</file>
<file>more/MyDeeperComponentForm.ui.qml</file>
<file>qmldir</file>
</qresource>
</RCC>
A simple qmldir would look something like this:
module MyApplication
MyDeepComponent 1.0 MyDeepComponent.qml
MyDeeperComponent 1.0 more/MyDeeperComponent.qml
You can see that I used a prefix, such that the files don't have to move. If you are to go to great heights with the app and want a MyApplication 2.0 namespace, you can use the prefix /qml/MyApplication.2
I have a 2 qml files in a lib and I want to export those to another project and was unable to import in my QuickQmlTest project. I want to use those qml files from QmlPlugin lib in QuickQmlTest project. Is there any additional step I need to do ? I have a folder structure
QMLProject
|-- QmlPlugin -(it is lib)
|--QML
|--CustomButton.qml
|-- CustomPolygon.qml
|-- QuickQmlTest - (app)
My QmPlugins.pro looks like this
TEMPLATE = lib
TARGET = QmlPlugin
QT += qml quick
CONFIG += plugin c++11
TARGET = $$qtLibraryTarget($$TARGET)
HEADERS += \
MyPlugin.h
DISTFILES = qmldir \
CustomButton.qml \
CustomPolygon.qml
DESTDIR = $$PWD/lib
And I linked the generated dlls into to the QuickQmlTest.pro using
LIBS += -L$$PWD/../QmlPlugin/lib -lQmlPlugin
INCLUDEPATH += $$PWD/../QmlPlugin
MyPlugin.h
#include <QQmlExtensionPlugin>
#include <QtQml/qqml.h>
class MyItemModel: public QObject
{
Q_OBJECT
Q_PROPERTY(qreal roundingRadius READ roundingRadius WRITE setRoundingRadius)
public:
void setRoundingRadius(const qreal rounding)
{
roundingRadius_ = rounding;
}
qreal roundingRadius() const
{
return roundingRadius_;
}
private:
qreal roundingRadius_{8.0};
};
class MyPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char* uri)override
{
Q_ASSERT(uri == QLatin1String("Components"));
qmlRegisterType<MyItemModel>(uri, 1, 0, "MyItemModel");
}
};
in qmldir I have
module Components
Components 1.0 CustomButton.qml CustomPolygon.qml
plugin QmlPlugin
In the QuickQmlTest (app)
I want to use CustomButton.qml and CustomPolygon.qml in my main.qml
So my main.qml in QuickQmlTest app
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import Components 1.0 //To import the dll from lib
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
CustomPolygon
{
width:60
height:30
}
CustomButton
{
width:60
height:30
}
}
And also I want to see my Custom qml types(CustomButton and CustomPolygon) in Quick Design mode which I am unable to achieve.
Suppose I have the following structure in my qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>Style.qml</file>
</qresource>
<qresource prefix="/components">
<file>Test.qml</file>
</qresource>
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.0
import "components"
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Test {
color: "red"
}
}
Test.qml
import QtQuick 2.0
import ".."
Rectangle {
width: Style.test * 200
height: 200
}
Style.qml
import QtQuick 2.0
pragma Singleton
QtObject {
property real test: 1.0
}
I have for some time tried to import Style.qml from Test.qml with import "../" but I keep getting ReferenceError: Style is not defined
I know the import statement is the cause for this and I have tried a few different variants of what "might" work but the docs are lacking on this and I am stuck. Help appreciated.
With the following code, I have no errors, and see the expected visual result. I'd suggest including a more complete example, if you can't spot your problem from this working sample. Note that all paths I give here are relative from the "root" project directory (so e.g. main.cpp is a file in the "root", components/Test.qml is located in a "components" subdirectory):
main.cpp:
#include <QQmlApplicationEngine>
#include <QGuiApplication>
int main(int argc, char **argv) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine qAppEngine(QUrl("qrc:/main.qml"));
return app.exec();
}
main.qml
import "components"
Test {
}
components/Test.qml:
import ".."
Style {
}
Style.qml:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 500
height: 500
color: "red"
}
Built with the following:
test.pro:
QT += quick qml
RESOURCES += test.qrc
SOURCES += main.cpp
test.qrc:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>main.qml</file>
<file>Style.qml</file>
<file>components/Test.qml</file>
</qresource>
</RCC>
How can a python method/slot be connected to a QML signal? It looks like QtObject.connect() used to work in PyQt4 but it's no longer available in PyQt5.
#Sample QML File (stack.qml)
import QtQuick 2.0
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
// relay this to python
}
}
}
--
#Sample Python File
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
if __name__ == '__main__':
import os
import sys
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setWidth(500)
view.setHeight(500)
view.setTitle('Hello PyQt')
view.setResizeMode(QQuickView.SizeRootObjectToView)
view.setSource(QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__),'stack.qml')))
def on_qml_mouse_clicked(mouse_event):
print 'mouse clicked'
view.show()
qml_rectangle = view.rootObject()
# this technique doesn't work #############################
qml_rectangle.mousePressEvent.connect(on_qml_mouse_clicked)
sys.exit(app.exec_())
Some of the PyQT examples pass an object into the QML context via "setContextProperty" and then relay QML events to slots on that object but that approach seems roundabout. Is there a better way?
qml_rectangle.mousePressEvent is not a signal, it's an event handler that is called on mouse events, so you can't connect to it. You could just replace it with your handler function (qml_rectangle.mousePressEvent = on_qml_mouse_clicked), but that's not a very clean way of working with Qt.
The better way would be to define a signal in your qml file and emit it from the rectangle's onClicked handler:
import QtQuick 2.0
Rectangle {
signal clicked()
MouseArea {
anchors.fill: parent
onClicked: {
parent.clicked() // emit the parent's signal
}
}
}
Then you can just connect to it from your python code:
...
def on_qml_mouse_clicked():
print('mouse clicked')
qml_rectangle.clicked.connect(on_qml_mouse_clicked)
...
I would recommend sub classing QQuickView and setting a property on its root context, for example MainWindow. Now all you need to do is adding functions in that class with decorations such as #pyqtSlot('QString'), and then you can set the event handler with onClicked: MainWindow.FunctionName(Arguments_According_To_Decoration)
Then your main.py would look like this
#!/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QUrl
from PyQt5.QtQuick import QQuickView
from PyQt5.QtWidgets import QApplication
import sys
class MainWindow(QQuickView):
def __init__(self):
super().__init__()
self.setSource(QUrl('sample.qml'))
self.rootContext().setContextProperty("MainWindow", self)
self.show()
#pyqtSlot('QString')
def Print(self, value):
print(value)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
And sample.qml like so
import QtQuick 2.0
import QtQuick.Controls 2.2
Rectangle {
width: 200; height: 200
Button {
text: "print Hello World"
onClicked: MainWindow.Print('hello world')
}
}
You can find more information in the docs
http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html