BreakButton aus Thread wegnehmen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

hi
ich habe einen breakbutton, den ich versuche zu löschen.
entweder soll er aus einer threadanweisung (nach getaner arbeit) gelöscht werden oder
wenn er selbst betätigt wird (dann soll eine schleife gestoppt und der breakbutton weggenommen werden)
den code habe ich aus einem bestehenden projekt zur vereinfachung herausgebastelt...

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import Tkinter as tk
from threading import Thread
import time


class MyThirdGUI(tk.Toplevel):
    run_string = True

    def __init__(self, first):
        tk.Toplevel.__init__(self)
        self.first = first
        # Toplevel 1
        self.title("MyThirdGUI")
        self.geometry("%dx%d+%d+%d" % (350, 275, 600, 250))
        self.frame_third = tk.Frame(self)
        self.frame_third.pack(expand=True, fill=tk.BOTH)
        self.break_button = tk.Button(self.frame_third, text="ABBRECHEN", font=('Arial', 8, 'bold'),
                                      width=15, command=self.breaking)
        self.break_button.place(relx=.5, rely=.86, anchor="c")
        # Threading
        self.work = Thread(target=self.working)
        self.work.start()

    def working(self):
        for value in range(10):
            print value
            time.sleep(0.5)
            if self.run_string is False:
                break
        if self.run_string is False:
            self.breaking()
        else:
            self.closing()

    def breaking(self):
        global run_string
        self.run_string = False

    def stopping(self):
        self.break_button.destroy()

    def closing(self):
        self.break_button.destroy()


class MyFirstGUI(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.geometry("%dx%d+%d+%d" % (300, 150, 600, 250))
        self.title("MyFirstGUI")
        self.frame = tk.Frame(self)
        self.frame.grid_propagate(False)
        self.label = tk.Label(self.frame, text="Start Page!", width=15)
        self.start_button = tk.Button(self.frame, text="STARTEN", width=15, command=self.start)
        self.close_button = tk.Button(self.frame, text="SCHLIESSEN", width=15, command=self.quit)
        self.frame.pack(expand=True, fill=tk.BOTH)
        self.label.place(relx=.5, rely=.2, anchor=tk.CENTER)
        self.start_button.place(relx=.5, rely=.5, anchor=tk.CENTER)
        self.close_button.place(relx=.5, rely=.75, anchor=tk.CENTER)
        self.mainloop()

    def start(self):
        self.withdraw()
        MyThirdGUI(self)


if __name__ == '__main__':
    first = MyFirstGUI()
das beenden der schleife per click auf den button funktioniert, aber in keinem der beiden
fälle wird er entfernt.
kann mir da jemand helfen, warum das so nicht funktioniert?
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Innerhalb von Threads darf man nichts an der GUI ändern. Booleans vergleicht man weder mit `is` noch mit `==` sondern mit `not`. `global` sollte man nicht benutzen und hier ist es zudem völlig nutzlos. Klassenattribute sollten Klassenattribute sein, und nicht dafür benutzt werden, Default-Werte für Instanzvariablen sein. Mit Threads kommuniziert man über Events, Queues, Semaphoren, etc. nicht über Instanzvariablen direkt.
__init__ ist dazu da, Instanzen zu initialisieren, nicht um ewig in einem mainloop zu hängen.
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

ok, thanks, werd ich versuchen zu ändern.
Mit Threads kommuniziert man über Events, Queues, Semaphoren, etc.
nicht über Instanzvariablen direkt.
muß ich dann den breakbutton in ein queue setzen, um den später destroyen zu können?
__init__ ist dazu da, Instanzen zu initialisieren, nicht um ewig in einem mainloop zu hängen.'''
versteh ich nicht. ich darf doch nur ein mainloop benutzen. die klasse MyThirdGUI ist doch ein toplevel.
Klassenattribute sollten Klassenattribute sein,
und nicht dafür benutzt werden, Default-Werte für Instanzvariablen sein
heißt das, first muß nicht mit übergeben werden?
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

ich hoffe, so ist es besser (obwohl ich nicht genau weiß, was in der antwort oben genau gemeint war).

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import Tkinter as tk
from threading import Thread
import time


class MyThirdGUI(tk.Toplevel):

    def __init__(self, first):
        tk.Toplevel.__init__(self)
        self.first = first
        self.run_string = True
        self.frame_third = tk.Frame(self)
        self.label = tk.Label(self.frame_third, text="Der imageButton ist weg?")
        self.break_button = tk.Button(self.frame_third, text="ABBRECHEN", font=('Arial', 8, 'bold'), width=15, command=self.breaking)
        self.work = Thread(target=self.working)
        self.layout()

    def layout(self):
        self.title("MyThirdGUI")
        self.geometry("%dx%d+%d+%d" % (350, 275, 600, 250))
        self.frame_third.pack(expand=True, fill=tk.BOTH)
        self.break_button.place(relx=.5, rely=.86, anchor="c")
        self.work.start()

    def working(self):
        for value in range(5):
            print value
            time.sleep(0.5)
            if not self.run_string:
                break
        if self.run_string:
            self.closing()

    def breaking(self):
        self.run_string = False
        self.break_button.destroy()
        self.label.pack()

    def closing(self):
        self.break_button.destroy()
        self.label.pack()


class MyFirstGUI(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.geometry("%dx%d+%d+%d" % (300, 150, 600, 250))
        self.title("MyFirstGUI")
        self.frame = tk.Frame(self)
        self.start_button = tk.Button(self.frame, text="STARTEN", width=15, command=self.start)
        self.close_button = tk.Button(self.frame, text="SCHLIESSEN", width=15, command=self.quit)
        self.layout()

    def layout(self):
        self.frame.pack(expand=True, fill=tk.BOTH)
        self.start_button.place(relx=.5, rely=.5, anchor=tk.CENTER)
        self.close_button.place(relx=.5, rely=.75, anchor=tk.CENTER)
        self.mainloop()

    def start(self):
        self.withdraw()
        MyThirdGUI(self)


if __name__ == '__main__':
    first = MyFirstGUI()
ich bräuchte aber noch etwas hilfe, um das hinzubekommen.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Es ist nicht besser, wenn mainloop statt in __init__ in einer Methode steht, die von __init__ aufgerufen wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DMD-OL: Du zerstörst den Button immer noch von dem Thread aus.

Eine `__init__()`-Methode soll ein Objekt initialisieren, damit der Aufrufer dann etwas mit dem Objekt machen kann. Das geht aber nicht wenn die `__init__()` die Tkinter-Hauptschleife aufruft und deshalb gar nicht zum Aufrufer zurück kehrt bevor die GUI wieder geschlossen wurde.

Die `layout()`-Methoden sind auch etwas überflüssig. Das kann mit in die `__init__()`.

Nichts was in der `MyFirstGUI.__init__()` an das Objekt gebunden wird, muss da wirklich an das Objekt gebunden werden.

`place()` ist auch mit relativen Angaben nicht wirklich eine gute Idee. Es kann immer noch passieren das die Kombination aus Systemeinstellung und Anzeigehardware die GUI unbrauchbar macht, und man müsste auch die Geometrie für Fenster nicht vorgeben wenn man `pack()` und/oder `grid()` verwenden würde und die GUI damit weiss wie gross das Fenster sein muss.

In `MyThirdGUI` wird das `first`-Argument/-Attribut nicht verwendet.

Bei dem komisch benannten `run_string` hast Du eine „race condition“: Es ist nicht ausgeschlossen das der Benutzer 'ABBRECHEN' drückt während der Thread zwischen dem Prüfen nach der Schleife und dem Aufruf von `closing()` ist.

Der Thread sollte zudem ein `daemon`-Thread sein, sonst wird es schwierig das Programm zu beenden solange der noch läuft. Der Benutzer kann ja nicht nur die Schaltflächen drücken, sondern auch das X in der Fensterleiste. Darauf muss man auch entsprechend reagieren.

Code: Alles auswählen

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from threading import Event, Thread


class MyThirdGUI(tk.Toplevel):

    def __init__(self):
        tk.Toplevel.__init__(self)
        self.title('MyThirdGUI')
        self.protocol('WM_DELETE_WINDOW', self.quit)
        
        self.work_finished = Event()
        self.work = Thread(target=self.working)
        self.work.daemon = True
        
        frame = tk.Frame(self)
        self.label = tk.Label(frame, text='Der ImageButton ist weg?')
        self.cancel_button = tk.Button(
            frame,
            text='ABBRECHEN',
            font=('Arial', 8, 'bold'),
            width=15,
            command=self.work_finished.set,
        )
        self.cancel_button.pack(side=tk.BOTTOM)
        frame.pack(expand=True, fill=tk.BOTH)
        
        self.work.start()
        self.check_work_thread()

    def check_work_thread(self):
        if self.work_finished.is_set():
            self.cancel_button.destroy()
            self.label.pack()
        else:
            self.after(100, self.check_work_thread)

    def working(self):
        try:
            for value in range(5):
                print(value)
                if self.work_finished.wait(0.5):
                    break
        finally:
            self.work_finished.set()


class MyFirstGUI(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.title('MyFirstGUI')
        frame = tk.Frame(self)
        tk.Button(
            frame, text='STARTEN', width=15, command=self.start
        ).pack()
        tk.Button(
            frame, text='SCHLIESSEN', width=15, command=self.quit
        ).pack()
        frame.pack(expand=True, fill=tk.BOTH)

    def start(self):
        self.withdraw()
        MyThirdGUI()


def main():
    gui = MyFirstGUI()
    gui.mainloop()
    

if __name__ == '__main__':
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

vielen dank.
hab aber noch eine frage, ist
self.work_finished = Event() automatisch an den thread gebunden, so dass man dafür keinen neuen thread öffnen muß?
ich könnte mir nämlich denken, daß self.work_finished ja sonst im gui teil läufen würde, was ja wohl dann ein problem wäre.
???
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@DMD-OL: work_finished ist ein Event, ein Fähnchen das anzeigt, ob etwas passiert ist, oder noch nicht. Da läuft nichts. Sondern das, was läuft (der Thread) hebt oder senkt das Fähnchen.
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

thx :)
Antworten