Open file dialog from PyQt5 connected to QML

Viewed 618

I have defined a button (actually it is a MouseArea) in QML inside an ApplicationWindow. I managed to connect to its clicked event from PyQt5. Now I am trying to show a save file dialog, but I get the error:

QWidget: Cannot create a QWidget without QApplication

My code looks like this:

from PyQt5.QtCore import QUrl, QObject   # pylint: disable-msg=E0611
from PyQt5.QtGui import QGuiApplication, QIcon # pylint: disable-msg=E0611
from PyQt5.QtQml import QQmlApplicationEngine # pylint: disable-msg=E0611
from PyQt5.QtWidgets import QFileDialog # pylint: disable-msg=E0611

def openFile():
    options = QFileDialog.Options()
    options |= QFileDialog.DontUseNativeDialog
    filename = QFileDialog.getOpenFileName(None,"QFileDialog.getOpenFileName()", "","All Files (*);;Python Files (*.py)", options=options)
    print(filename)

def run():
    app = QGuiApplication(sys.argv)
    app.setWindowIcon(QIcon(resource_path("assets\\images\\icon.ico")))

    engine = QQmlApplicationEngine()
    engine.load(resource_path("qml\\Window.qml"))
    engine.quit.connect(app.quit)

    if not engine.rootObjects():
        return -1

    button = engine.rootObjects()[0].findChild(QObject, "openButton")
    button.clicked.connect(openFile)

    return app.exec_()

if __name__ == '__main__':
    sys.exit(run())

I have also tried to pass the ApplicationWindow instead of None, but then I get a type error:

TypeError: getOpenFileName(parent: QWidget = None, caption: str = '', directory: str = '', filter: str = '', initialFilter: str = '', options: Union[QFileDialog.Options, QFileDialog.Option] = 0): argument 1 has unexpected type 'QWindow'

which does not make a lot of sense in my opinion, because QMainWindow inherits QWidget.

How can I show the dialog from my openFile() function?

EDIT: For the sake of completeness here is a stripped down version of my .qml file

import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick 2.3

ApplicationWindow {
    id: mainWindow
    visible: true
    width: 600  
    height: 400

    Item {
        anchors.top: titleBar.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        property bool isMainButtonFocused: false
        objectName: "openButton"
        signal clicked()

        Label {
            padding: 5
            text: "<b><font color='#fefefe'>Hello World</font></b>"
            anchors.verticalCenter: parent.verticalCenter
            anchors.horizontalCenter: parent.horizontalCenter
            font.family: "Helvetica"
            font.pointSize: 9
            background: Rectangle {
                color: mouseAreaOpenFolderButton.containsMouse ? "#777777" : "#333333"
                border.width: isMainButtonFocused ? 2 : 1
                border.color: "#ffffff"
                radius: 5
            }

            MouseArea {
                id: mouseAreaOpenFolderButton
                anchors.fill: parent
                hoverEnabled: true
                onClicked: {
                    isMainButtonFocused = true
                    parent.parent.clicked()
                }
            }
        }
    }
}
1 Answers

The errors are very clear:

  • If you are going to use a QWidget like QFileDialog then you must create a QApplication.
  • If you are going to pass a parent a QWidget, as QFileDialog is, then that parent must be another QWidget but ApplicationWindow is not a QWidget but a QWindow causing that error.

Other additional but more important error is that you should not access the QML object from Python (or C++) since its life cycle is different so it could have problems (See this answer for example), instead create a QObject where implement the logic and expose it as an item (with qmlRegisterType) or a context property (via setContextProperty).

Considering the above then you should use FileDialog and expose a QObject as context property:

# ...
class Helper(QObject):
    @pyqtSlot(QUrl)
    def read_file(self, url):
        filename = url.toLocalFile()
        print(filename)


def run():
    app = QGuiApplication(sys.argv)
    app.setWindowIcon(QIcon(resource_path("assets\\images\\icon.ico")))

    engine = QQmlApplicationEngine()
    helper = Helper()
    engine.rootContext().setContextProperty("helper", helper)
    # ...
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick 2.3
import QtQuick.Dialogs 1.3

ApplicationWindow {
    id: mainWindow
    visible: true
    width: 600  
    height: 400

    Item {
        anchors.top: titleBar.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        property bool isMainButtonFocused: false

        Label {
            padding: 5
            text: "<b><font color='#fefefe'>Hello World</font></b>"
            anchors.verticalCenter: parent.verticalCenter
            anchors.horizontalCenter: parent.horizontalCenter
            font.family: "Helvetica"
            font.pointSize: 9
            background: Rectangle {
                color: mouseAreaOpenFolderButton.containsMouse ? "#777777" : "#333333"
                border.width: isMainButtonFocused ? 2 : 1
                border.color: "#ffffff"
                radius: 5
            }

            MouseArea {
                id: mouseAreaOpenFolderButton
                anchors.fill: parent
                hoverEnabled: true
                onClicked: {
                    // isMainButtonFocused = true
                    fileDialog.visible = true
                    
                }
            }
        }
    }
    FileDialog {
        id: fileDialog
        title: "Please choose a file"
        selectedNameFilter: "All Files (*);;Python Files (*.py)"
        onAccepted: {
            helper.read_file(fileDialog.fileUrls[0])
        }
    }
}
Related