Konsolenausgabe in tkinter ausgeben

Fragen zu Tkinter.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo zusammen

Es könnte an Stelle von:

Code: Alles auswählen

data = self.queue.get(False)
auch stehen:

Code: Alles auswählen

data = self.queue.get_nowait()
Ist zwar immer noch das gleiche und in Englisch aber schon etwas aussagekräftiger :)

@Nobuddy: Du kannst:

Code: Alles auswählen

def sampler(self):
    if self.queue.qsize():
        # In der Queue sind Daten vorhanden!
        try:
            data = self.queue.get()
            self.text.insert('end', data)
            self.queue.task_done()
        except qu.Empty:
            pass
eventuell auch so schreiben:

Code: Alles auswählen

def sampler(self):
    
    if not self.queue.empty():
        data = self.queue.get_nowait()
        # Please do the job
        self.text.insert('end', data)
        self.queue.task_done()
                            
    self.after(50, self.sampler)
Gruß wuf :wink:
Take it easy Mates!
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen

Danke für Eure Unterstützung!

wuf, habe Deinen Vorschlag genommen.

Was mir aufgefallen ist, daß bei kleinen Modulen, recht flott im Terminal ausgegeben wird.
Bei sehr umfangreichen Modulen (Laufzeit ca. 5 Minuten) erfolgt keine Ausgabe im Terminal.
Hier öffnet sich auch erst relativ spät das Terminalfenster.

Grüße Nobuddy

Nachtrag:
Auch wenn sich das Terminalfenster erst relativ spät sich öffnet, so erfolgt doch am Ende eine Ausgabe darin.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Nobuddy hat geschrieben:Bei sehr umfangreichen Modulen (Laufzeit ca. 5 Minuten) erfolgt keine Ausgabe im Terminal.
Das verstehe ich nich ganz. Was meinst du genau mit Laufzeit? Das ist ja eine Ewigkeit wie das Hochfahren eines sehr bekannten Betriebsystems. Ist von meinem Schiff aus schwierig zu beurteilen da ich nicht sehe was du genau machst. Kannst du den die ganze Terminalgeschichte nicht erst starten, wenn deine Module geladen sind?

Gruß wuf :wink:
Take it easy Mates!
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Naja 5 Minuten war ein wenig übertrieben, aber 2 - 3 Minuten sind es schon.
Das hängt damit zusammen, daß Lieferantendaten abgeglichen werden (ca. 90.000 Datensätze).
Da geht nicht nur um Preise zu aktualisieren, das wäre schnell erledigt.
Es werden die Lieferantendaten zusammengführt. Hersteller, EAN-Code, Herstellernummer, VE- und Inhalt werden überprüft, um nicht Äpfel mit Birnen vergleichen zu müssen. Weiter werden noch die Verkaufspreise aktualisiert, die Produkte den jeweiligen Haupt- und Untergruppen zugeordnet, ein Backup und einen abschließenden Bericht erstellt.
Das wäre das Wesentliche, hoffe ich habe nichts vergessen. :wink:
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen

wufś letzter Post und die Tatsache, daß für die Aktualisierung meiner Lieferantendaten eine Menge Module benötigt werden, die das Demoscript von wuf nicht mehr zeitgleich ablaufen lassen, haben mich auf eine Idee gebracht.

Ich packe alle Module dem Ablauf entsprechend in eine Liste und lasse dann das Demoscript von wuf in einer for-Schleife ablaufen.
Das müßte doch funktionieren, oder?

Grüße Nobuddy
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Nein, ist auch nicht die Lösung, auch hier ist die Ausgabe nicht zeitgleich, sondern kommt erst nach Ablauf des Moduls.

Werde das Projekt mal auf die Seite nehmen, vielleicht kommt noch die zündete Idee. :wink:
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Nobuddy

Es ist natürlich schon so, wenn du Tkinter für dein Terminalfenster und in deiner Applikation noch eine Menge weitere Tkinter-Objekte verwendest, braucht es Zeit bis das Terminal endlich verfügbar wird. Du solltest dein zeitlastigen Modulprozess verzögert starten erst nachdem das Hauptfenster und Terminalfenster initialisiert sind und die mainloop läuft. Zum Beispiel ein verzögerter Aufruf der Module mit:

Code: Alles auswählen

app_win.after(1000, launch_my_modules)
Das ist das nächste was ich versuchen würde.

Gruß wuf :wink:
Take it easy Mates!
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo wuf

Auch der verzögerte Aufruf, bringt nicht das.
Terminalfenster öffnet sich erst nach einigen Minuten, die Ausgabe ist auch extrem langsam.

Ich lasse jetzt in tkinter meine Module ohne Terminalfenster ablaufen.
Die Auswertung des Ablaufes wird in eine Datei geschrieben, welche ich nach Beendigung mir anzeigen lasse und auswerte.

Grüße Nobuddy :wink:
Sirius3
User
Beiträge: 18230
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo Nobuddy,

hab jetzt nochmal den ganzen Thread durchgelesen und entdecke nirgends einen
Hinweis darauf, ob Du Deine Module jetzt im Hintergrund arbeiten läßt oder nicht.

Falls nicht, friert natürlicherweise Dein Terminalfenster so lange ein, bis die Modul-Arbeit
erledigt ist. Dann brauchst Du aber auch nicht den ganzen Umweg über Queues.

Ich vermisse den Aufruf von myVendors in einem eigenen Thread:

Code: Alles auswählen

import threading
from gui_terminal import App

app = App()  # redirect stdout to terminal window
worker = threading.Thread(target=myVendors)
worker.start()  # doing the work in background thread
app.run()  # activate the terminal window
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo Sirius3

Danke für Deine Info!

Die Module laufen im Vordergrund ab.
Das im Hintergrund laufen zu lassen, kenne ich mich noch zu wenig mit Threading, Queue aus.

Ich vermute, daß Du Recht hast, warum das Terminalfenster einfriert.

Um meine Module im Hintergrund laufen zu lassen, müßte ich bei den ganzen Module threading einbauen, ist das richtig?
Oder ist Dein Code, die Antwort darauf?

Grüße Nobuddy
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Nobuddy hat geschrieben: Die Module laufen im Vordergrund ab.
Das im Hintergrund laufen zu lassen, kenne ich mich noch zu wenig mit Threading, Queue aus.
Bereits BlackJack hatte das in seiner Antwort geschrieben - genau lesen oder nachfragen hilft da ;-)

Im übrigen "laufen" Module nirgends ab, denn ein Modul an sich "läuft" nicht ;-)
Nobuddy hat geschrieben: Um meine Module im Hintergrund laufen zu lassen, müßte ich bei den ganzen Module threading einbauen, ist das richtig?
Du musst die Funktionen / Callables, die Du *direkt* aufrufst in einen eigenen Thread packen, ja. Das baut man aber idR *nicht* in die Module ein, in denen die Funktionen zu finden sind, sondern an der Stelle, an der man diese verwendet. Man will solche Funktionen ja auch weiterhin direkt aufrufen können... insofern ist es sinnvoller, das Threading von Funktionalität dort zu implementiern, wo Du dies benötigst.

Ich kenne mich jetzt speziell bei Tk nicht mit Threading aus, weiß aber, dass GUI-Toolkits idR. Probleme mit den nativen Threading-Mechanismen haben. Ist das bei Tk anders? Bzw. wäre eine ``Queue.Queue`` da die Brücke, über die man Daten aus den separaten Threads in den GUI-Thread hineinbekommt?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Hyperion: Module „laufen” wenn man sie so schreibt, das der ganze Code beim importieren ausgeführt wird. AFAIK hat Nobuddy so etwas zumindest am Anfang gemacht.
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo Hyperion, hallo BlackJack

An dieser Materie, werde ich wohl noch eine Weile zu Knabbern haben. :wink:

Dies funktioniert, Modul läuft im Hintergrund.

Code: Alles auswählen

worker = threading.Thread(target=myVendors)
worker.start()  # doing the work in background thread
Leider komme ich aber mit der Übergabe an das Terminalfenster nicht klar.
Normaler Aufruf wäre:

Code: Alles auswählen

App(myVendors).run()
Nur wie bekomme ich es mit diesem Code hin?

Code: Alles auswählen

import threading
from gui_terminal import App

app = App()  # redirect stdout to terminal window
worker = threading.Thread(target=myVendors)
worker.start()  # doing the work in background thread
app.run()  # activate the terminal window
Grüße Nobuddy
Sirius3
User
Beiträge: 18230
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: Deine App braucht gar nichts von myVendor zu wissen.
Da App ja stdout umbiegt und myVendor nur auf stdout schreibt, sind die beiden Teile
voneinander unabhängig.
Der Aufruf

Code: Alles auswählen

self.check = check()
hat ja gerade dazu geführt, dass das Terminal-Fenster stehen blieb.
Also weg damit, da dieser Code ja nun in einem Thread läuft.
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo Sirius3

Danke für Deinen Hinweis, jetzt läuft es wie geschmiert. :wink:

Jetzt habe ich auch die Funktion des Terminals verstanden.
Dachte, daß man das Modul mit übergeben muß, dabei will ja die App nur stdout. :)

Was mich jetzt noch beschäftigt, ist daß die Ausgabe im Terminal, wenn das Fenster voll ist, ich immer mit der Maus herunterscrollen muß, um die aktuelle Ausgabe zu sehen.
Ich habe das Schließen-Button deaktiviert und möchte, daß es erst nach Beendigung des Moduls zum Schließen des Fensters aktiviert wird.
Gibt es dafür eine Lösung?

Grüße Nobuddy
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Nobuddy hat geschrieben:Was mich jetzt noch beschäftigt, ist daß die Ausgabe im Terminal, wenn das Fenster voll ist, ich immer mit der Maus herunterscrollen muß, um die aktuelle Ausgabe zu sehen.
Dies sollte das gewünschte bringen:

Code: Alles auswählen

self.text.insert('end', data)
self.text.see('end')
Gruß wuf :wink:
Take it easy Mates!
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo wuf

Danke, jetzt funktioniert dies auch. :D

Jetzt fehlt nur noch der Punkt mit dem Schließen-Button.

Grüße und einen schönen Abend :wink:
Nobuddy
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Nobuddy

Ich habe hier etwas aus dem was mir von deinem Projekt bekannt ist zusammengewurschtelt. :roll:

Startskript:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import threading
from vendors import MyVendors
from gui_terminal import App

app = App()
MyVendors.queue = app.queue_modules
worker = threading.Thread(target=MyVendors)
worker.start()
app.run()
Terminalskript(gui_terminal.py):

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import sys

try:
    #~~ For Python 2.x
    import Queue as qu
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import queue as qu
    import tkinter as tk
       
class TeeStd(object):
    def __init__(self, queue, type):
       
        self.queue = queue

        if type == 'stderr':
            self.std_obj = sys.stderr
            sys.stderr = self
        else:
            self.std_obj = sys.stdout
            sys.stdout = self
       
    def write(self, data):
        # Ausgabe auf Tk-Textbox
        self.queue.put(data)
       
APP_WIN_XPOS = 50
APP_WIN_YPOS = 50

class App(object):
   
    def __init__(self): #, check):
        self.win = tk.Tk()
        self.win.geometry('+{0}+{1}'.format(APP_WIN_XPOS, APP_WIN_YPOS))
        self.win.protocol("WM_DELETE_WINDOW", self.close)

        self.queue = qu.Queue()
        self.queue_modules = qu.Queue()
       
        self.text = tk.Text(self.win, width=80, height=20, highlightthickness=0,
            bd=0, bg='white', relief='sunken', padx=5, pady=5)
        self.text.pack()

        self.close_button = tk.Button(self.win, text='Fenster schließen',
            command=self.close)
        self.close_button.pack()
        self.close_button['state']="disabled"
               
        self.win.title("My Stdout/Stderr Terminal")

        # Umleitung der Standard-Ausgabe auf meine Textbox
        TeeStd(self.queue, 'stderr')
        TeeStd(self.queue, 'stdout')

        print('Hello')
               
        self.sampler()
   
    def sampler(self):

        if not self.queue.empty():
            data = self.queue.get()
            self.text.insert('end', data)

        if not self.queue_modules.empty():
            data = self.queue_modules.get()
            if data == 'end':
                self.enable_close_button()    
            
        self.win.after(50, self.sampler)
       
    def run(self):
        self.win.mainloop()
   
    def enable_close_button(self):
        self.close_button['state']="normal"
        
    def close(self):
        """Process before shutdown"""
        self.win.destroy()
Vendorskript(vendors.py):

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

class MyVendors(object):
    
    queue = None
    
    def __init__(self):
        
        self.vendors = list()
        self.vendors.append("Vendor-1")
        self.vendors.append("Vendor-2")
        self.vendors.append("Vendor-3")     
        
        self.update_vendors()
        
    def update_vendors(self, new_vendor=None):
        
        if new_vendor != None:
            self.vendors.append('Vendor-{0}: {1}'.format(len(self.vendors)+1,
                new_vendor))
            
        for vendor in self.vendors:
            print(vendor)
        print()
        
        if MyVendors.queue is not None:
            MyVendors.queue.put('end')
Good Luck! Kurze Frage lange Antwort.

Gruß wuf :wink:
Take it easy Mates!
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo wuf

Danke für Deine Lösung! :wink:
Dachte, daß man direkt im Modul gui_terminal.py das Ende abfangen kann, ohne die Module die ablaufen sollen, anpassen zu müssen.

Soweit es keine andere Lösung gibt, werde ich von dem De- und Aktivieren des Schließen-Buttons Abstand nehmen.

Was mir aufgefallen ist.
Nach Beendigung des Terminalfensters, erhalte ich folgende Meldung:
invalid command name "139687964521752callit"
while executing
"139687964521752callit"
("after" script)
Vielleicht kann mir von Euch Jemand erklären, was es damit auf sich hat?

Grüße Nobuddy
Sirius3
User
Beiträge: 18230
Registriert: Sonntag 21. Oktober 2012, 17:20

Ob myVendor fertig ist, kann man das über »not worker.is_active()« abfragen.
Einfach in Deine sampler-Funktion einbauen.
Antworten