Search code examples
pythonpyqt5returnqmlslot

@pyqtslot return value is undefined


I have a QML application that uses settings. So I created a python slot that reads the settings.toml, and returns the value. I've setup the context properties correctly and I can call other functions without a return value from QML without a problem.

...
class Settings(QObject)
    @pyqtSlot(str, str)
    def getSettings(self, category, key):
        try:
            with open("settings.toml", "r") as settings:
                toml_object = toml.load(settings)
            return str(toml_object[category][key])
        except FileNotFoundError:
            self.settingsFileNotFound.emit()
        except toml.TomlDecodeError:
            self.settingsError.emit()
        except BaseException:
            self.fatalError.emit()
...

The settings.toml looks like this.

...
[last-used-font-settings]
font = "Arial"
... 

When printing the function from python, it works as expected.

...
print(Settings.getSettings("last-used-font-settings", "font")) # Returns Arial
...

But when logging from qml:

...   
Component.onCompleted: console.log(Settings.getSettings("last-used-font-settings", "font"))
/* Returns qml: undefined */
...

How to solve this? I'm not very good at creating @pyqtProperty, but if I manage to create one, definitely I do not want to create a property for every value!

And signals wont work for this purpose you see. :(


Solution

  • If you want to return a value from python then you must use "result" in pyqtSlot:

    import os
    
    from PyQt5.QtCore import pyqtProperty, pyqtSlot, QObject, QUrl
    from PyQt5.QtGui import QGuiApplication
    from PyQt5.QtQml import QQmlApplicationEngine
    
    import toml
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    class Settings(QObject):
        def __init__(self, parent=None):
            super().__init__(parent)
            self._error_string = ""
    
        @pyqtProperty(str, constant=True)
        def errorString(self):
            return self._error_string
    
        @pyqtSlot(result=bool)
        def hasError(self):
            return bool(self.errorString)
    
        @pyqtSlot(str, str, result=str)
        def getSettings(self, category, key):
            self._error_string = ""
            error_string = ""
            value = ""
            try:
                with open(os.path.join(CURRENT_DIR, "settings.toml"), "r") as settings:
                    toml_object = toml.load(settings)
                    value = str(toml_object[category][key])
            except FileNotFoundError:
                error_string = "FileNotFoundError"
            except toml.TomlDecodeError:
                error_string = "TomlDecodeError"
            except Exception as e:
                error_string = str(e)
            self._error_string = error_string
            return value
    
    
    if __name__ == "__main__":
        import sys
    
        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()
        settings = Settings()
        engine.rootContext().setContextProperty("Settings", settings)
    
        filename = os.path.join(CURRENT_DIR, "main.qml")
        engine.load(QUrl.fromLocalFile(filename))
    
        if not engine.rootObjects():
            sys.exit(-1)
        sys.exit(app.exec())
    
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        Component.onCompleted: {
            var value = Settings.getSettings("last-used-font-settings", "font")
            if(Settings.hasError()){
                console.error(Settings.errorString)
            }
            else{
                console.log(value)
            }
        }
    }