0

I made a QtQuick Window Gui Application for Python 3.8 on Windows. The last thing I cant figure out is how to display Python print() in the Gui Text Area. What i want is, that wherever in my Python code a print statement is and gets executed during runtime, i want to output it into the TextArea in my Gui app

I read the following post, but failed to implemet it, different errors occured and am more confused then before:

the closest and most usefull was this one:

and some others:

working Sample Code to send a string from Python into QML TextArea

main.py

import os
from pathlib import Path
import sys
from vantage import daily

# load GUI libs
from PySide2.QtGui import QGuiApplication
from PySide2.QtCore import QSettings, QObject, Signal, Slot
from PySide2.QtQml import QQmlApplicationEngine


# load app
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))



class Backend(QObject):
     textwritten = Signal(str, arguments=['writen'])   


     def __init__(self):
         super().__init__()

         self.timer = QTimer()
         self.timer.setInterval(100)  
         self.timer.timeout.connect(self.writer)
         self.timer.start()

        
         # console output write function
         def writer(self):
             towrite = 'i am writing'
             self.textwritten.emit(str(towrite))


# create an instance of the Python object (Backend class)
back_end = Backend()


# give data back to QML
engine.rootObjects()[0].setProperty('writen', back_end)

# close app
sys.exit(app.exec_())

main.qml

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.15



Window {
    width: 640
    height: 480
    visible: true
    color: "#2f2f2f"
    title: qsTr("alpha")

    /*print out console text*/
    property string texted: "Console Text"
    property QtObject writen
    ScrollView {
        id: scrollViewCon
        x: 58
        y: 306
        width: 507
        height: 100
        ScrollBar.vertical.verticalPadding: 4
        ScrollBar.vertical.minimumSize: 0.4
        ScrollBar.vertical.contentItem: Rectangle {
             implicitWidth: 6
             implicitHeight: 100
             radius: width / 2
             color: control.pressed ? "#81e889" : "#f9930b"
        }

    TextArea {
        font.family: control.font
        font.pointSize: 8
        color:"#f9930b"
        wrapMode: TextEdit.Wrap
        KeyNavigation.priority: KeyNavigation.BeforeItem
        KeyNavigation.tab: textField
        placeholderTextColor : "#f9930b"
        opacity: 1
        
        text: texted
        placeholderText: texted //qsTr("Console")
        readOnly: true
        background: Rectangle {
            radius: 12
            border.width: 2
            border.color: "#f9930b"
        }
    }
}
Connections {
    target: writen

    function onTextwritten(msg) {
        texted = msg;
    }
  }
}

i think what needs to happen is that everytime sys.stdout is called by print() it emits a signal with itself?

leaving main.qml as is and only changing main.py

main.py

...

class Backend(QObject):
     textwritten = Signal(str, arguments=['writen'])   


     def __init__(self):
         super().__init__()

         sys.stdout = self.writer(str(sys.stdout))


         def writer(self, message):
             #towrite = 'i am writing'
             self.textwritten.emit(message)

         ...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
chubaka
  • 51
  • 8
  • Are you stick to print() or just need a way to have text output from any place in your code? If latter I would propose to take a look on qDebug and/or python logging library. I personally redirected logging ouput to QPlainTextEdit and it works pretty well. – StarterKit Aug 17 '21 at 10:45
  • yes, basically just printing out the stdout stream and the stderr, i want to package the app later without a console window so that the user can get some feedback what is currently happening in the code – chubaka Aug 19 '21 at 09:55

1 Answers1

1

The print function writes over sys.stdout so the solution is to assign some QObject that has a write method that emits a signal. For this you can use contextlib.redirect_stdout:

import os
import sys
from contextlib import redirect_stdout
from functools import cached_property
from pathlib import Path

from PySide2.QtCore import (
    QCoreApplication,
    QDateTime,
    QObject,
    Qt,
    QTimer,
    QUrl,
    Signal,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

CURRENT_DIRECTORY = Path(__file__).resolve().parent


class RedirectHelper(QObject):
    stream_changed = Signal(str, name="streamChanged", arguments=["stream"])

    def write(self, message):
        self.stream_changed.emit(message)


class TimerTest(QObject):
    @cached_property
    def timer(self):
        return QTimer(interval=1000, timeout=self.handle_timeout)

    def handle_timeout(self):
        print(QDateTime.currentDateTime().toString())

    def start(self):
        self.timer.start()


def main():
    ret = 0
    redirect_helper = RedirectHelper()
    with redirect_stdout(redirect_helper):

        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()

        engine.rootContext().setContextProperty("redirectHelper", redirect_helper)
        filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
        url = QUrl.fromLocalFile(filename)

        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.QueuedConnection)
        engine.load(url)

        timer_test = TimerTest()
        timer_test.start()

        ret = app.exec_()

    sys.exit(ret)


if __name__ == "__main__":
    main()
import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    id: root

    width: 640
    height: 480
    visible: true

    Flickable {
        id: flickable

        flickableDirection: Flickable.VerticalFlick
        anchors.fill: parent

        TextArea.flickable: TextArea {
            id: textArea

            anchors.fill: parent
            readOnly: true
            font.pointSize: 8
            color: "#f9930b"
            wrapMode: TextEdit.Wrap
            placeholderTextColor: "#f9930b"
            opacity: 1
            placeholderText: qsTr("Console")

            background: Rectangle {
                radius: 12
                border.width: 2
                border.color: "#f9930b"
            }

        }

        ScrollBar.vertical: ScrollBar {
        }

    }

    Connections {
        function onStreamChanged(stream) {
            textArea.insert(textArea.length, stream);
        }

        target: redirectHelper
    }

}
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • thank you eyllanesc, as far as i have tested it, it does what i wanted. works pretty good. Any specific reason you changed Scrollview to flickable, i mean it works with both. – chubaka Aug 19 '21 at 09:52