Funktion abrechen/pausieren durch drücken eines Buttons der GUI

Fragen zu Tkinter.
Antworten
Ch4iss
User
Beiträge: 3
Registriert: Donnerstag 28. Februar 2019, 15:42

Hallo,

ich habe ein Python Skript/Programm geschrieben, dass eine GUI entält über die Funktion starten lasse, die Werte über I2C in einen DAC schreiben. Der DAC steuert Laser Scanner.
Eine Funktion lässt eine Schleife ablaufen, die zwei While-Schleifen enthält welche ich gerne durch drücken einen anderen Buttons abrechen bzw. im Ideal-Fall nur pausieren und danach weiterführen möchte.
Ich habe mir ein kleines Programm gebastelt, dass eine GUI mit zwei Buttons enthält. Der eine Button startet eine einfache Zähl-Schleife und der zweite soll diese Anhalten.
Wie stelle ich das am besten an? Ach ja ich beschäftige mich erst seit gut zwei Wochen mit Python und meine letzten Programmiererfahrung sind locker 5 Jahre her.

Code: Alles auswählen

import time
from tkinter import *
from _thread import start_new_thread

def p1():
    k=6000
    while k <=56001:
        if B2 is True: # das klappt nicht aber keine AHnung wie sonst
            break
        print ("k=",k)
        k=k+10000
        time.sleep(1.5)
        i=6000
        
def th():
    start_new_thread(p1, (), {})
    
def nope():
    quit(p1)   # beendet die GUI

root = Tk() 
rightFrame = Frame(root, width=600, height = 600, bg="#000000")
rightFrame.grid(row=0, column=3, padx=10, pady=10)
buttonFrame = Frame(rightFrame)
buttonFrame.grid(row=1, column=0, padx=10, pady=3)
 
B1 = Button(buttonFrame, text="start count", bg="#32cd32", width=15, command=th)
B1.grid(row=0, column=0, padx=10, pady=3)

 
B2 = Button(buttonFrame, text="break", bg="#32cd32", width=15, command=nope)
B2.grid(row=0, column=10, padx=10, pady=3)

root.mainloop()
Ich bin für einfache Antworten sehr dankbar!
Ich bin seit gut zwei Tagen am googlen und testen aber noch keine Lösung gefunden
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich hoffe, du willst durch so ein Programm keinen Not-Aus fuer deinen Laser realisieren. Dazu ist sowas viel zu unzuverlaessig!

Was dein Problem angeht: dazu musst du mit timern arbeiten, und in tkinter macht man das mit der after-Method. Zu diesem Begriff mal hier im Forum oder in den Weiten des Internets suchen, dann finden sich da unmengen an Beitraegen.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Ch4iss: Ich denke Du willst zu schnell zu viel. Der gezeigte Quelltext ist so ziemlich komplett unrettbar.

Das `_thread`-Modul ist nicht Teil der öffentlichen API, das darfst Du nicht verwenden. Für Threads gibt es das `threading`-Modul in der Standardbibliothek. Aber Threads an sich sind nicht einfach und im Zusammenhang mit GUIs wird es nicht einfacher.

Sternchen-Importe sind Böse™. Bei `tkinter` holt man sich damit mehr als 100 Namen in das Modul von denen nur ein Bruchteil verwendet wird. Es wird schwerer nachzuvollziehen wo was her kommt, und es besteht die Gefahr von Namenskollisionen.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Dann gibt es keine globalen Variablen mehr, was auch gut ist, denn Funktionen und Methoden sollten nicht einfach so auf magische Weise auf Werte zugreifen, sondern alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen.

In Verbindung mit GUIs und deren ereignisbasierter Programmierung bedeutet das, dass man für jede nicht-triviele GUI nicht um objectorientierte Programmierung (OOP) herum kommt. Das sollte man können bevor man mit GUI-Programmierung anfängt, sonst muss man das alles auf einmal lernen.

`quit()` sollte gar nicht zur Verfügung stehen. Du verwechselst das eventuell mit der `Tk.quit()`-Methode, die wesentlich mehr Sinn macht. Wobei es bei keiner der beiden Sinn macht die `p1()`-Funktion als Argument zu übergeben.

Zum Problem des Themas: So funktioniert GUI-Programmierung nicht. Nicht Du fragst die GUI ab, sondern die GUI ruft bei bestimmten Ereignissen die Funktionen/Methoden die Du für diese Ereignisse registriert hast auf. Und die dürfen dann *kurz* etwas machen, und kehren dann zur GUI-Hauptschleife zurück.

Ein GUI-Ereignis kann auch verstrichene Zeit sein. Und das wäre mit der `after()`-Methode ein Ansatz Deine ``while``-Schleife zu ersetzen durch mehrere Methoden-Aufrufe die in 1.5 Sekunden-Abständen von der GUI-Hauptschleife getätigt werden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Ohne das es hübsch aussieht, habe ich deine Frames und grids übernommen und einfach mal drauf los gehackt.
Wie gesagt, die Größen und Weiten sind von mir nicht angepasst oder betrachtet worden.

Code: Alles auswählen

import tkinter as tk

KONSTANTE = 6000  # ich weiß nicht was das sein soll, ich hab es mal als Konstante gesetzt


class Notaus(object):
    def __init__(self):
        self.fenster = tk.Tk()
        self.right_frame = tk.Frame(self.fenster, width=600, height=600, bg="#000000")
        self.right_frame.grid(row=0, column=3, padx=10, pady=10)
        self.label = tk.Label(self.right_frame, font=('Arial', 40), width=6, text="Werbung")
        self.button = tk.Button(self.right_frame, text='Exit Tkinter', bg="#32cd32", width=15,
                                command=self.shutdown)
        self.notaus_button = tk.Button(self.right_frame, text="Notaus", bg="#32cd32", width=15,
                                       command=self.power_off)
        self.right_frame.pack(pady=10)
        self.label.pack(pady=5)) # die pack() haben vorher komplett gefehlt
        self.notaus_button.pack(pady=5)
        self.button.pack()

        self.fenster.mainloop()

    def power_off(self): # ehemals func(p1)
        k = KONSTANTE + 10000  # Wieso? Einfach Konstante +10000 ?
        self.label.after(1000)  # 1 sekunde warten
        i = 6000  # Was ist i?
        self.label.configure(text=f"k={k}") # wozu print(k) wenn man eine Gui hat? Konstante wird in der GUI angezeigt

    def shutdown(self):
        self.fenster.quit() # ehemals func(nope)
        
    def th(self):
        pass # entfernt, hab den Sinn nicht erkannt

if __name__ == "__main__":
    test = Notaus()
@all
Ich weiß, dass das Ausbaufähig ist. Die Darstellung stimmt nicht, die Funktionen power_off wird mir selbst nicht ganz klar. Weil ich weder k (jetzt Konstante), noch i verstehe. Aber das ".after" ist hier Funktionsfähig dargestellt.
Aber der Ch4iss wird das jetzt nach besten Gewissen verarbeiten.
Ch4iss
User
Beiträge: 3
Registriert: Donnerstag 28. Februar 2019, 15:42

__blackjack__ hat geschrieben: Donnerstag 28. Februar 2019, 16:48 @Ch4iss: Ich denke Du willst zu schnell zu viel. Der gezeigte Quelltext ist so ziemlich komplett unrettbar.
Da hast du den Nagel auf den Kopf getroffen. Das ist dem Umständen zu verdanken: Es müssen halt erst ein mal Ergebnisse geliefert werden.
__deets__ hat geschrieben: Donnerstag 28. Februar 2019, 16:33 Ich hoffe, du willst durch so ein Programm keinen Not-Aus fuer deinen Laser realisieren. Dazu ist sowas viel zu unzuverlaessig!
Oh Gott nein! Das ist alles Über einen Interlock geregelt.

Danke an alle für die Hilfe und Vorschläge. Ich habe jetzt erstmal gut zu lesen, verstehen, schreiben, lernen und ausprobieren.
Meine Erfahrung ist in diesem Bereich gering aber ich habe die Chance wahrgenommen auf Arbeit dieses Projekt zu übernehmen, um mich im Bereich Programmierung/Python weiterzubilden.
Ich werde euch auf dem laufenden halten, welche Lösung ich gefunden habe! Evtl. komme ich nochmal mit Fragen zurück :)
Ch4iss
User
Beiträge: 3
Registriert: Donnerstag 28. Februar 2019, 15:42

Sooo ich habe mich mal hingesetzt und zugesehen, das ich hinbekomme was ich haben mag und siehe da es klappt!

Code: Alles auswählen

import time
from _thread import start_new_thread # ja ist ungern gesehen aber ich brauche es
import tkinter as tk

class Pause(object):
    def __init__(self):
        self.window = tk.Tk()
        #GUI
        self.right_frame= tk.Frame(self.window, width=600, height=600, bg= "#000000")
        self.right_frame.grid(row=0, column=10, padx=10, pady=10)
        self.label = tk.Label(self.right_frame, font=('Arial', 40), width=6, text="Werbung")
        self.button = tk.Button(self.right_frame, text='start', bg="#32cd32", width=15,
                                command=self.th)
        self.abbruch_button = tk.Button(self.right_frame, text="stop", bg="#32cd32", width=15,
                                       command=self.abbruch)
        self.start_button = tk.Button(self.right_frame, text="continue", bg="#32cd32", width=15,
                                       command=self.start)
        self.right_frame.pack(pady=10)
        self.label.pack(pady=5)
        self.button.pack()
        self.abbruch_button.pack(pady=5)
        self.start_button.pack(pady=5)
        

        self.window.mainloop()

    def counter(self, aus=0): # Zähl Methode, simulliert im kleinen was das eigentliche Programm macht
        k=1000
        self.aus=aus
        while k <= 56000-1:
            self.button['state'] = 'disabled'
            if self.aus is 0:
            
                k=k+1000
                #print(k)
                self.label.configure(text=f"k={k}")
                time.sleep(0.1)
                if k ==56000:
                    self.button['state'] = 'normal'
                    self.abbruch_button['state']='normal'
        if self.aus is 1:
            pass
    
        

    def th(self):
        start_new_thread(self.counter, (), {}) # ruft counter als thread auf, damit die GUI nicht einfrieht

    def abbruch(self):                          
        self.aus = 1
        self.abbruch_button['state'] = 'disabled'
       

    def start(self):
        self.aus = 0
        self.abbruch_button['state'] = 'normal'
    
                
test = Pause()
Ich habe mich am Anfang evtl etwas falsch ausgedrückt.
Das System sieht wie folgt aus:
Raspberry Pi steuert DAC über I2C an.
Der gibt eine Spannung aus. Diese wandle ich mit einer OPV-Schaltung so um, dass der Scanner damit angesteuert werden kann. Auf dieses Scanner schießt ein Laser.
Es wird der Wert verändert (hochgezählt), den ich in den DAC schreibe(0-65535; 16Bit). Es verändert sich insgesamt die Spannung die an der Scanner-Regelung(+10V bis -10V differenziell) anliegt. Durch unterschiedliche Schleifen, kann man unterschiedliche Muster erzeugen, die der Laser abfährt.

Ich habe nur den kleinen Teil aus dem Code genommen und auch den nochmal gekürzt. In diesem Teil wird ein Muster erzeugt. Der Prozess wird über ein Button-Klick in der GUI gestartet.
Jetzt war die Anforderung, dass über einen Klick in der GUI, der Zählprozess pausiert wird und dann auch wieder fortgesetzt werden kann.
Mit dem oben angebenden Code funktioniert das aber er muss noch überführt werden.

Ich musste _thread verwenden, damit die GUI nicht einfriert und auch Stop drücken kann.
Bis jetzt habe ich noch keine bessere Lösung gefunden.

Print habe ich nur für mich als Kontrolle verwendet.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Ch4iss: da sind noch einige Fehler drin:
1. start_new_thread nicht verwenden, statt dessen threading.Thread
2. in einem Thread die GUI nicht verändern, statt dessen Zustandsänderung per Queue an GUI übergeben.
3. Vergleiche von Zahlen nicht mit `is`
4. Statt 0 und 1 True und False verwenden.
Ein if-Block der nur aus `pass` besteht ist ziemlich unsinnig. Wenn eine Schleife nur noch sinnlos Strom verbrät, wenn aus = 1 ist, dann ist das ein Programmierfehler.
Und das, was Du hier per Thread löst, läßt sich viel einfacher per `after` lösen.
Antworten