Tkinter in einem weiteren Modul

Fragen zu Tkinter.
Antworten
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Hallo,

ich habe ein Programm erstellt, welches via Bluetooth Daten von einem Sensor ausliest und mit diesen Berechnungen durchführt.
Jetzt möchte ich ein weiteres Programm erstellen, welches diese in einer grafischen Oberfläche darstellt. Dazu habe ich folgendes erstellt:


gui.py:

Code: Alles auswählen

import tkinter as tk
import threading
from Main import i

class App(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.start()

    def callback(self):
        self.root.quit()

    def run(self, i):
        self.root = tk.Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.callback)

        w = tk.Scale(self.root, from_=0, to=200)
        w.pack()
        w.set(i)

        self.root.mainloop()


app = App()

in der main.py sollen jetzt Berechnungen durchgeführt werden und dies ein der GUI visualisiert werden. Darüber wird auch alles gestartet:

main.py:

Code: Alles auswählen

import gui
import time

i = 1

def counter():
    global i
    while i < 20:
        i = i + 1
        time.sleep(1)
        print(i)
        
counter()
Jetzt läuft nur der Timer ab, aber die GUI wird erst gestartet, wenn i = 20 ist und danach läuft der Timer erneut ab, aber die GUI aktualisiert sich nicht.
Ich hätte gerne, dass diese laufend aktualisert wird und sich gleich zu beginn startet. Wo ist mein Denkfehler?

Danke und Gruß
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der erste Denkfehler besteht darin, daass du planst die GUI in einem weiteren thread zu starten. Das geht nicht. Das MUSS der Maintgread sein, aus technischen Gründen. Der zweite besteht darin das du denst, da würde irgendwie magisch ein i in der run-Methode deines Threads auftauchen.

Die Lösung für dein Problem sind entweder Timer oder GUI-Variablen in tkinter. Erstere kannst du mit der after-Methode starten, und darin zB periodisch diene Abfrage &Berechnung einfach sofort durchführen. Zweitere kannst du benutzen um Werte in zB einem Label darstellen zu lassen, und den Wert aus einem Thread der die BT Kommunikation macht zu setzen.
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Danke für die Antwort. Das hilft mir weiter!

Ich habe ein wenig recherchiert und nun funktioniert ist. Ist das jetzt der richtige weg? Ein Problem habe ich noch. Ich kann das Programm nicht mehr abbrechen, da dies nun durch die update Schleife geblockt wird. Ist das jetzt der richtige weg? Gruß

Code: Alles auswählen

import tkinter as tk
import threading
import time


class App(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.start()

    def callback(self):
        self.root.quit()

    def run(self):
        self.root = tk.Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.callback)
        
        w = tk.Scale(self.root, from_=0, to=200)
        w.pack()
        
        while True:
            w.set(i)
            self.root.update()
        
        self.root.mainloop()



app = App()

print("Weiter gehts")



i = 0
def calculator():
    global i
    while i < 50:
        i = i + 1
        time.sleep(1)
        print(i)
calculator()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, das ist immer noch der komplett falsche Weg. Die GUI im Thread ist so nicht richtig. Die Dauerschleife mit Update ist so nicht richtig, da sie sowohl zu viel CPU Zeit verbrät, als auch eine nicht empfohlenes Vorgehen darstellt.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

https://wiki.tcl-lang.org/page/Update+c ... ed+harmful

Das ist aus der Dokumentation zu der unterliegenden Technologie für die GUI.
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Okay vielen Dank nochmal!

1) keine Schleife
2) GUI nicht in einem Thread
3) mit after den Wert in der GUI neu setzen

Das probiere ich morgen mal aus.
Gruß,
Henry
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Hallo,

ich habe nun viele verschiedene Dinge ausprobiert und komme leider zu keinem Ergebnis.

Ich habe es jetzt wie folgt zum laufen gebracht, mit einer Auslastung von 70%. Mein Mainthread Listener Empfängt Werte via Bluetooth und übergibt diese an einen Rechner (Calculator). Die GUI soll anhand der berechneten Werte aktualisiert werden. Recherchen zum Thema after haben mich leider nicht zum Erfolg gebracht. Hat noch jemand neue Ideen/Anreize oder Beispiele wie ich das ganze sauber realisieren kann?

Code: Alles auswählen

import myo
from _thread import start_new_thread
import tkinter
import time


gyr_y = 0

def gui():
    
    main = tkinter.Tk()
    w = tkinter.Scale(main, from_=0, to=100)
    w.pack()
    
    LOOP_ACTIVE = True
    
    while LOOP_ACTIVE == True:
        w.set(gyr_y)
        main.update()
        time.sleep(0.1)
        

def calculator(gyr):
    global gyr_y
    
    gyr_y += gyr.x * 0.02


class Listener(myo.DeviceListener):
    def on_paired(self, event):

        print("Guten Tag, {}!".format(event.device_name))

        event.device.vibrate(myo.VibrationType.short)
        
        start_new_thread(gui,())
        
    def on_unpaired(self, event):

        return False  # Stop the hub

    def on_orientation(self, event):
        
        orientation = event.orientation

        acceleration = event.acceleration

        gyroscope = event.gyroscope
        
        print(gyroscope.x)
        calculator(gyroscope)

 
if __name__ == '__main__':

    myo.init(sdk_path='C:/Python_SDKs/myo-sdk-win-0.9.0/')

    hub = myo.Hub()
    
    listener = Listener()
    

    
    while hub.run(listener.on_event, 500):
        
        pass
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da du inzwischen zum dritten mal deine GUI in einem extra Thread startest, immer noch update verwendest, und damit alles ignorierst, was ich dir bis dato erzaehlt habe, bin ich raus. Viel Erfolg!
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Hallo,
deine Ansätze habe ich auch schon verfolgt. Ich werde diese morgen noch einmal raussuchen und hochladen.
Gruß Henry
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

der Unterstrich bei `_thread` soll Dir sagen, dass das Modul nur für interne Zwecke verwendet werden soll. Jede zweite Zeile eine Leerzeile, macht den Code so gut wie unlesbar.

So sollte das aussehen:

Code: Alles auswählen

import myo
import tkinter as tk
from queue import Queue, Empty

class Listener(myo.DeviceListener):
    def __init__(self, queue):
        super().__init__()
        self.queue = queue

    def on_paired(self, event):
        print("Guten Tag, {}!".format(event.device_name))
        event.device.vibrate(myo.VibrationType.short)
        
    def on_unpaired(self, event):
        return False  # Stop the hub

    def on_orientation(self, event):
        gyroscope = event.gyroscope
        print(gyroscope.x)
        queue.put(gyroscope.x)


class Gui(tk.Tk):
    def __init__(self, queue):
        super().__init__()
        self.scale = tk.Scale(main, from_=0, to=100)
        self.scale.pack()
        self.queue = queue
        self.gyr_y = 0
        self.after(500, self.loop)

    def loop(self):
        try:
            while True:
                v = self.queue.get_nowait()
                self.gyr_y += v * 0.02
                self.scale.set(self.gyr_y)
        except Empty:
            pass
        self.after(500, self.loop)

def main():
    myo.init(sdk_path='C:/Python_SDKs/myo-sdk-win-0.9.0/')
    hub = myo.Hub()
    queue = Queue()
    gui = Gui(queue)
    listener = Listener(queue)
    with hub.run_in_background(listener.on_event):
        gui.mainloop()

if __name__ == '__main__':
    main()
henry06
User
Beiträge: 6
Registriert: Sonntag 2. Dezember 2018, 19:14

Hallo,

ich habe es jetzt mit der After-Methode selber hinbekommen:

Code: Alles auswählen

import myo
import tkinter


gyr_x = 0

root = tkinter.Tk()
w = tkinter.Scale(root, from_=-100, to=100)
w.pack()

def requery():
    w.set(gyr_x)
    root.after(1, requery)

def calculator(gyr):
    global gyr_x
    gyr_x += gyr.x * 0.02
    print(gyr_x)
    
class Listener(myo.DeviceListener):

        
    def on_paired(self, event):
        print("Guten Tag, {}!".format(event.device_name))
        event.device.vibrate(myo.VibrationType.short)
    def on_unpaired(self, event):
        return False  # Stop the hub
    def on_orientation(self, event):
        gyroscope = event.gyroscope
        calculator(gyroscope)
       
    
def main():
    myo.init(sdk_path='C:/Python_SDKs/myo-sdk-win-0.9.0/')
    hub = myo.Hub()
    listener = Listener()
    
    with hub.run_in_background(listener.on_event, 500):
        root.after(1000, requery)
        root.mainloop()
    
if __name__ == '__main__':
    main()
Danke auch dir Sirius, denn ohne das ändern von

Code: Alles auswählen

    while hub.run(listener.on_event, 500):
        pass
auf

Code: Alles auswählen

 with hub.run_in_background(listener.on_event):
hat es nicht funktioniert. Warum weiß ich nicht. Dein Programm werde ich mir morgen einmal ansehen, aber ich habe es lieber selber ausprobiert, da ich es noch weiter entwickeln will und viele deiner verwendeten Funktionen mir fremd sind (queue, super() etc.)

Nun habe ich versucht, es mithilfe einer Klasse zu realisieren, aber das funktioniert noch nicht ganz, weil ich auf keine Werte zugreifen kann, aber da arbeite ich mich erstmal rein. Danke
Antworten