Search code examples
pythonqmlpyside2

How to access QML\QtQuick controls from PySide?


I'm trying to access a FileDialog control from the python file that starts the QQmlApplication engine in order to retrieve the file path property. I have set up a signal in the .qml file, however I cannot access the file dialog by id in the python file to set up the slot. The findChild method in application.py returns None. Here is the code:

application.py

import sys
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, QQmlFileSelector

sys_argv = sys.argv
sys_argv += ['--style', 'material']
app = QGuiApplication(sys_argv)

window = QQmlApplicationEngine()
window.load("QML/main.qml")

fileDialog = window.findChild(QQmlFileSelector, "fileDialog")
print(fileDialog)

app.exec_()

Page1.qml

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.2

Page {
    width: 600
    height: 400

    header: Label {
        text: qsTr("Prepare Data")
        horizontalAlignment: Text.AlignHCenter
        font.pixelSize: Qt.application.font.pixelSize * 2
        padding: 10
    }

    Button {
        text: qsTr("Load data")
        anchors.centerIn: parent
        onClicked: fileDialog.visible = true
        padding: 10
    }

    signal folderSelected()

    FileDialog {
        id: fileDialog
        selectFolder: true
        title: qsTr("Select the data directory")
        folder: shortcuts.home
        onAccepted: {
            parent.folderSelected()
        }
    }
}

main.qml

import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12

ApplicationWindow{
    visible: true
    title: qsTr("Main window")
    width: 1000
    height: 800

    Material.theme: Material.Light
    Material.accent: Material.Orange

    SwipeView {
        id: swipeView
        anchors.fill: parent

        Page1 {
        }

        Page2 {
        }

        Page3 {
        }

    }
}

Solution

  • In an old answer explain in the section Pushing References to QML how to update some python object from QML, that methodology is the one recommended by Qt and it is the one that should be used now. With your current method you need to establish an objectname that can be problematic in many cases.

    So the solution is to create a QObject that we export to QML and update the qproperty, this will emit a signal that we connect to a slot where we can do the logic that we want. On the other hand FileDialog returns a url, so the property must be a QUrl:

    main.qml

    import os
    import sys
    from PySide2 import QtCore, QtGui, QtQml
    
    class FileManager(QtCore.QObject):
        file_url_Changed = QtCore.Signal(QtCore.QUrl)
    
        def __init__(self, parent=None):
            super(FileManager, self).__init__(parent)
            self._file_url = QtCore.QUrl()
    
        def get_file_url(self):
            return self._file_url
    
        def set_file_url(self, file_url):
            if self._file_url != file_url:
                self._file_url = file_url
                self.file_url_Changed.emit(self._file_url)
    
        file_url = QtCore.Property(QtCore.QUrl, fget=get_file_url, fset=set_file_url, notify=file_url_Changed)
    
    @QtCore.Slot(QtCore.QUrl)
    def on_file_url_changed(file_url):
        print(file_url.toLocalFile())
    
    if __name__ == '__main__':
        sys.argv += ['--style', 'material']
        app = QtGui.QGuiApplication(sys.argv)
    
        file_manager = FileManager()
        file_manager.file_url_Changed.connect(on_file_url_changed)
    
        engine = QtQml.QQmlApplicationEngine()
        engine.rootContext().setContextProperty("file_manager", file_manager)
        file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "QML", "main.qml")
        engine.load(QtCore.QUrl.fromLocalFile(file))
        if not engine.rootObjects():
            sys.exit(-1)
        sys.exit(app.exec_())
    

    Page1.qml

    import QtQuick 2.9
    import QtQuick.Controls 2.2
    import QtQuick.Dialogs 1.2
    
    Page {
        width: 600
        height: 400
    
        header: Label {
            text: qsTr("Prepare Data")
            horizontalAlignment: Text.AlignHCenter
            font.pixelSize: Qt.application.font.pixelSize * 2
            padding: 10
        }
    
        Button {
            text: qsTr("Load data")
            anchors.centerIn: parent
            onClicked: fileDialog.visible = true
            padding: 10
        }
    
        FileDialog {
            id: fileDialog
            selectFolder: true
            title: qsTr("Select the data directory")
            folder: shortcuts.home
            onAccepted: {
                file_manager.file_url = fileDialog.fileUrl // <---
            }
        }
    }