Seite 1 von 1

BreakButton aus Thread wegnehmen

Verfasst: Samstag 22. September 2018, 14:50
von DMD-OL
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?

Re: BreakButton aus Thread wegnehmen

Verfasst: Samstag 22. September 2018, 15:48
von Sirius3
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.

Re: BreakButton aus Thread wegnehmen

Verfasst: Samstag 22. September 2018, 16:12
von DMD-OL
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?

Re: BreakButton aus Thread wegnehmen

Verfasst: Montag 24. September 2018, 10:28
von DMD-OL
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.

Re: BreakButton aus Thread wegnehmen

Verfasst: Montag 24. September 2018, 11:03
von Sirius3
Es ist nicht besser, wenn mainloop statt in __init__ in einer Methode steht, die von __init__ aufgerufen wird.

Re: BreakButton aus Thread wegnehmen

Verfasst: Montag 24. September 2018, 11:29
von __blackjack__
@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()

Re: BreakButton aus Thread wegnehmen

Verfasst: Dienstag 25. September 2018, 12:59
von DMD-OL
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.
???

Re: BreakButton aus Thread wegnehmen

Verfasst: Dienstag 25. September 2018, 14:01
von Sirius3
@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.

Re: BreakButton aus Thread wegnehmen

Verfasst: Donnerstag 27. September 2018, 15:52
von DMD-OL
thx :)