Seite 2 von 3

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 13:02
von sparrow
Alfons Mittelmeyer hat geschrieben:Deswegen soll man aber auch __del__() implementieren für den Fall, dass man vergisst destroy auftzurufen
Ich bin mir ziemlich sicher, dass man einfach von einer vorhandenen Klasse erbt und sich dann um so einen Schabernack gar nicht kümmern muss ;)

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 13:10
von Alfons Mittelmeyer
sparrow hat geschrieben:Ich bin mir ziemlich sicher, dass man einfach von einer vorhandenen Klasse erbt und sich dann um so einen Schabernack gar nicht kümmern muss ;)
Ja so macht man es meistens, von tk.Canvas ableiten, obwohl man gar keine Methoden von tk.Canvas überschreibt und damit gar keine Ableitung bräuchte.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 13:13
von sparrow
Alfons Mittelmeyer hat geschrieben:Ja so macht man es meistens, von tk.Canvas ableiten, obwohl man gar keine Methoden von tk.Canvas überschreibt und damit gar keine Ableitung bräuchte.
Doch. Man braucht sich keine unter anderem keine __del__-Methoden zusammen zu frickeln und kann sich darauf verlassen, dass alle benötigten Methoden vorhanden sind, und bei Bedarf überschrieben werden können.
Das ist ja das tolle an Vererbung in der OOP :wink:

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 13:21
von Alfons Mittelmeyer
sparrow hat geschrieben:Doch. Man braucht sich keine unter anderem keine __del__-Methoden zusammen zu frickeln und kann sich darauf verlassen, dass alle benötigten Methoden vorhanden sind, und bei Bedarf überschrieben werden können. Das ist ja das tolle an Vererbung in der OOP :wink:
Ja, aber nur solange Du nicht selber etwas dazu machst, was der Aufhebung bedarf - da bitte es nicht vergessen. Bei nur Widgets mit GUI Callbacks brauchst Du in der Regel nichts mit __del__() tun.

Und bei Widgets, ist das __del__() auch nicht wichtig. Wenn Du vergessen hast, sie zu löschen, dann siehst Du schon, dass sie noch da sind. Was hier das Problem ist. Aber es gibt auch Vieles andere, das man nicht sieht, und was dann zum Crash führen kann, mitunter verbunden mit aufwendiger Fehlersuche,

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 13:55
von Alfons Mittelmeyer
Sirius3 hat geschrieben:Am besten fängt man mit einer einfachen Klasse an, ohne global, ohne Sternchenimporten und ohne ausführbaren Code auf Modulebene.
Also, das mit der Klasse habe ich nicht richtig verstanden. Ich fange immer ganz sauber an, und mache alles lokal, etwa:

Code: Alles auswählen

def main(parent):
    tk.Label(parent,text='Hello',command = (lambda: print('Hello'))).pack()

main(root)
Ja man kann das auch mit einer Klasse machen:

Code: Alles auswählen

def main(parent):

    class MyClass:
        
        def __init__(self,parent):
            tk.Label(parent,text='Hello',command = (lambda: print('Hello'))).pack()

    MyClass(parent)

main(root)
Aber wozu man das macht, verstehe ich einfach nicht.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 14:03
von Sirius3
@Alfons Mittelmeyer: man macht eine Klasse, damit man globale Variablen wie activeWindow nicht braucht. Die Klassendefinition sollte aus der Funktion main raus.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 14:38
von wuf
Hi tsaG

Kannst du noch verstehen und mitverfolgen was hier in deinem Thread abläuft? Ich nicht. :K

Gruss wuf :wink:

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 15:34
von Alfons Mittelmeyer
Sirius3 hat geschrieben:@Alfons Mittelmeyer: man macht eine Klasse, damit man globale Variablen wie activeWindow nicht braucht. Die Klassendefinition sollte aus der Funktion main raus.
Dann braucht man also doch keine Klasse, denn mit einer Funktion hat man ja auch keine globalen Variablen. Allerdings ist dann der Zugriff nicht schön, weil man dann eine Liste odef etwas Ähnliches nehmen muss. Wer keine Liste nehmen will, der definiert sich eben eine Klasse. Die könnte aber auch innerhalb der Funktion und nur für die Variablen sein.

@tsaG: ich habe es jetzt mit einer Funktion gemacht und nicht mehr verschachtelt. Vielleicht gefällt Dir das ja besser

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 15:44
von sparrow
Alfons Mittelmeyer hat geschrieben:@tsaG: ich habe es jetzt mit einer Funktion gemacht und nicht mehr verschachtelt. Vielleicht gefällt Dir das ja besser
Hoffentlich nicht, denn das ist großer Unsinn.
Lieber Alfons, bitte bedenke, dass durchaus unerfahrene Python-Anwender deine Posts lesen. Und wenn man versucht globale Variablen dadurch zu vermeiden, dass man alles eine Ebene einrückt - also alles in main schreibt - dann sind sie ja trotzdem global, nur weil man das ganze einen Ebene verschiebt ändert sich ja das Problem nicht.
Das hilft dem Anwender also gar nicht und zeugt von falschem Verständnis.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 15:50
von Alfons Mittelmeyer
@sparrow Wieso ist das dann global? Hast Du schon überlegt, wie schnell die Funktion main vorbei ist?

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 15:51
von sparrow
@Alfons: Weißt du warum man globale Variablen vermeiden soll?

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 16:54
von Alfons Mittelmeyer
sparrow hat geschrieben:@Alfons: Weißt du warum man globale Variablen vermeiden soll?
Das brauchst Du mir bestimmt nicht sagen. Aber stimmt, das war schlecht programmiert, denn wir haben eine globale Variable 'root':

Code: Alles auswählen

root = tk.Tk()

def main(parent):
    xxx

main(root)
root.mainloop()
Das können wir natürlich besser machen:

Code: Alles auswählen

def main(parent):
    xxx
    return parent

main(tk.Tk()).mainloop()
Jetzt endlich zufrieden???

Die Funktion main wäre allerdings noch da. Man könnte zwar main mit del löschen, aber dann müssten wir bereits vorher root definieren. Ganz ohne etwas geht es eben leider nicht. Zumindest komme ich nicht drauf.

Man bräuchte da eine Libraryfunktion, welcher man eine Funktion zur Ausführung übergibt und die dabei die Funktion löscht. Sollte aber nicht schwer zu schreiben sein.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 17:08
von BlackJack
@Alfons Mittelmeyer: Du zeigst eine Lösung „ohne verschachtelte Funktionen“ die aus einer Funktion besteht in der lauter Funktionen definiert werden. Womit das verschachtelte Funktionen sind. Dachtest Du das merkt keiner oder merkst Du das selber nicht? ;-)

Und dann bist Du anscheinend nicht mal in der Lage das globale `root` einfach *in* die `main()`-Funktion zu verschieben und fängst schon wieder mit Funktionen löschen wollen an. Dünnes Eis mein Freund…

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 17:34
von Alfons Mittelmeyer
@BlackJack Hör jetzt auf mit dem Unsinn. Das ist in diesem Fall dasselbe, wie wenn Du eine Klasse mit verschiedenen Methoden machst. Sind das dann bei Dir auch verschachtelte Methoden? Zumindest trifft das zu für Funktionen zu, die nur für einmaligen Gebrauch bestimmt sind, wie so eine main Funktion. Da fehlt nur das self gegenüber einer Klassendefinition.

Bei der Klasse ist aber etwas besser. Methoden können auch andere Methoden aufrufen, die nachher definiert sind. Bei der Funktion kann nur aufgerufen werden, was vorher definiert wurde - zumindest ist es so in Python

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 18:29
von BlackJack
@Alfons Mittelmeyer: Das ist nicht dasselbe. Man hat Funktionen bei denen nicht an der Schnittstelle erkennbar ist das die innen etwas verwenden was eben nicht über die Schnittstelle hinein gekommen ist. Das ist unübersichtlich und untestbar. Also genau der Grund warum das ohne die zusätzliche Ebene in der `main()`-Funktion nicht gut ist. Neue Werte zuweisen kann man dem inneren Zustand so auch nicht, wie man das bei einer Klasse könnte. Und selbst wenn es dasselbe wäre, dann sollte es einen offensichtlichen Weg geben und der ist in Python nun mal in der Regel Klassen statt Closures zu verwenden. Nicht zuletzt weil Python's Closures kein vollwertiger Ersatz für Klassen sind, da sich die Namen von inneren Funktionen nicht ohne weiteres neu binden lassen.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 18:30
von Alfons Mittelmeyer
@tsaG Vieleich hast Du inzwischen ja verstanden, weshalb man in Python gerne eine Klasse nimmt, statt einer Funktion, auch wenn man gar kein Objekt braucht.

Globale Variablen sollte man keine nehmen, deshalb eine Funktion oder eine Klasse.

Probleme bei der Funktion:

- wenn Du darin Funktionen definierst, dann können die nur lesend - tricksen würde auch gehen - auf zuvor definierte einfache Variablen zugreifen. Für schreibenden Zugriff bräuchte man dann etwa eine Liste, oder Zugriffsfunktionen etc.

- auch für Funktionen gilt, dass innerhalb derer nur auf das zugegriffen werden kann, was vorher definert wurde. Das heißt, dass Du Bottom Up implementieren müßtest. FORTH Programmiere sind swo etwas gewohnt, andere wohl eher wenig. Und manchmal sind dann auch solche Tricks nötig, wie:

Code: Alles auswählen

# zu Beginn ==========
loadActiveWindow = None

# und später ==========
def do_loadActiveWindow(windowtype):
...

loadActiveWindow = do_loadActiveWindow
Wenn Du Dir Tricks mit Variablenzugriff und Bottom Up Programmierung und Kniffs dabei ersparen wisllst, dann nimm eine Klasse.

Was ich Dir gezeigt hatte, war nicht Python typische Programmierung, sondern wie Du programmieren müßtest, wenn Du keine Klasse nimmst. Durchaus machbar. Aber nicht empfehlenswert für Ungeübte und andere reagieren darauf allergisch. Also nimm eine Klasse, dann tust Du Dich leichter.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 19:13
von BlackJack
@Alfons Mittelmeyer: Den notwendigen ”Trick” habe ich nicht verstanden. Da fehlt irgendwie der Grund warum man den Namen am Anfang an `None` binden muss. Das müsste man doch nur wenn man den Wert, also wirklich das `None` vor der Definition von `do_loadActiveWindow()` irgendwie tatsächlich *benutzt*, aber wofür?

„Bottom up“ zu implementieren ist in allen möglichen Programmiersprache üblich, ich weiss nicht wieso das ungewohnt sein sollte. Übliche Vorgehensweise ist es das Gesamtproblem in kleine Teilprobleme zu zerlegen und damit solange fortzufahren bis die Teilprobleme so klein geworden sind das man sie mit ein paar Zeilen Code lösen kann. Und dann testet man die jeweilige Teillösung und macht erst weiter wenn die funktioniert. Man kann natürlich beim Implementieren auch „top down“ vorgehen, dann braucht man beim Testen aber immer wieder „Mock“-Objekte die die bisher noch nicht implementierten kleineren Teillösungen imitieren. Wobei man Fehler und falsche Annahmen machen so das die sich nicht verhalten wie die spätere tatsächliche Implementierung und man dann Tests macht die eventuell gar nicht aussagekräftig sind. Ausserdem hat man mehr Arbeite „Mock“-Objekte *und* die tatsächlichen Implementierungen statt nur tatsächliche Implementierungen zu schreiben.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 20:42
von Alfons Mittelmeyer
BlackJack hat geschrieben:@Alfons Mittelmeyer: Den notwendigen ”Trick” habe ich nicht verstanden. Da fehlt irgendwie der Grund warum man den Namen am Anfang an `None` binden muss. Das müsste man doch nur wenn man den Wert, also wirklich das `None` vor der Definition von `do_loadActiveWindow()` irgendwie tatsächlich *benutzt*, aber wofür?
Jetzt habe ich nochmals nachgeschaut, und habe gemerkt, man braucht es doch nicht. Ursprünglich wollte ich es aber drawButtons diese Funktion als Parameter übergeben. Und diese Funktion do_loadActiveWindow ist nach Bottom Up die letzte Funktion, Steht daher ür andere Funktionen gar nicht zur Verfügung.

Dann habe ich es aber mit der Parameterübergabe sein lassen. Und jetzt habe ich es nochmals überprüft. In drawButtons sind nicht aufgelöste lambda Expressions und für die braucht die Funktion noch nicht definiert sein. Also kann man in diesem Fall sogar auf den Trick mit loadActiveWindow = None verzichten. Und die Funktion 'do_loadActiveWindow' gleich umbenennen in 'loadActiveWindow'. Nein - da sollte man besser die lambda expression auflösen!

Aber sollte klar sein, wofür man sonst den Trick braucht. Der Compiler braucht zur Compilierzeit die aufzurufende Funktion. Leider kann man die wegen Bottom Up Requirement erst danach definieren. Daher muß man in einer Variablen einen Ersatz liefern - wichtig ist, dass für den Compiler die Variable existiert. Der Inhalt spielt zur Kompilierzeit keine Rolle. Wenn die Funktion aber später ausgeführt wird, dann muß in dieser Variablen der richtige Wert stehen. Und diesen setzt man, nachdem man die zugehörige Funktion definiert hat.

Also, der Compiler braucht eine Variable, damit er kompilieren kann (bei def), aber erst zur Ausführungszeit - wenn die Funktion aufgerufen wird - muss die den richtigen Wert haben.

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 21:49
von wuf
Hi tsaG

Habe mich auch noch mit deinem Skript beschäftigt und kam zu folgender Lösung:
tsaG's Gauges-Panel

Das Skript kann natürlich noch optimiert werden. Ist momentan nur ein Prototyp.

Gruss wuf :wink:

Re: Tkinter Display clearen

Verfasst: Montag 7. September 2015, 21:59
von BlackJack
@Alfons Mittelmeyer: Der Compiler braucht die Variable nicht. Zur Übersetzungszeit existieren ja noch gar keine Variablen, die entsteht erst wenn der Code ausgeführt wird und da ist der Compiler dann schon längst durch mit dem Quelltext.

@tsaG: Hier ist mal ein erster Ansatz den bisherigen Quelltext umzuschreiben:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8 
import Tkinter as tk
from tkFont import Font

FONT_OPTIONS = {'family': 'Helvetica', 'size': 10}


class MeasuredItem(object):

    OK, WARNING, CRITICAL = 'OK', 'WARNING', 'CRITICAL'

    def __init__(self, name, max_value):
        self.name = name
        self.max_value = max_value
        self.value = 2000
        self.state = self.OK

    @property
    def is_ok(self):
        return self.state == self.OK


MEASURED_ITEM_STATE_TO_COLOR = {
    MeasuredItem.OK: 'green',
    MeasuredItem.WARNING: 'yellow',
    MeasuredItem.CRITICAL: 'red',
}


class ErrorFrame(tk.Frame):
    
    def __init__(self, measured_items, parent, **kwargs):
        tk.Frame.__init__(self, parent, **kwargs)
        self.font = Font(**FONT_OPTIONS)
        self.font['size'] = 20
        self.labels = list()
        self.measured_items = measured_items
        self.update()

    def update(self):
        for label in self.labels:
            label.destroy()
        for item in self.measured_items:
            if not item.is_ok:
                label = tk.Label(
                    self,
                    text=item.name,
                    background=self['background'],
                    foreground=MEASURED_ITEM_STATE_TO_COLOR[item.state],
                    font=self.font,
                )
                label.pack(side=tk.TOP)


class MainWindow(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.font = Font(**FONT_OPTIONS)
        self.canvas = tk.Canvas(self, bg='black', height=480, width=320)
        self.canvas.pack()
        self.measured_items = [
            MeasuredItem(name, 6000)
            for name in [
                'RPM', 'Water', 'EGT', 'Fuel Cap.', 'Fuel Flow', 'Oil Temp'
            ]
        ]
        self.error_frame = ErrorFrame(
            self.measured_items, self, width=120, background='black'
        )
        self.error_frame.place(x=190, y=0)

        self._create_main_buttons(
            [
                ('MAIN', self.draw_main_screen),
                ('ENG', self.not_implemented),
                ('NAV', self.not_implemented),
                ('STAT', self.not_implemented),
            ],
            0,
        )
        self._create_main_buttons(
            [
                ('PERF.', self.not_implemented),
                ('MEDIA', self.not_implemented),
                ('NET', self.not_implemented),
                ('SETTINGS', self.not_implemented),
            ],
            290,
        )
        self._draw_boot_screen()

    def _create_main_buttons(self, names_and_callbacks, x_offset):
        for i, (name, callback) in enumerate(names_and_callbacks):
            tk.Button(
                self,
                text=name,
                wraplength=1,
                command=callback,
                pady=6,
                font=self.font,
            ).place(x=x_offset, y=i * 120)

    def draw_gauge(self, x, y, measured_item, scale_factor=0.8):
        gauge_value = measured_item.max_value / float(measured_item.value)
        # 
        # Draw hand.
        # 
        self.canvas.create_arc(
            x,
            y,
            x + 100 * scale_factor,
            y + 100 * scale_factor,
            start=0,
            extent=-(220 / gauge_value),
            fill=MEASURED_ITEM_STATE_TO_COLOR[measured_item.state],
        )
        # 
        # Draw outline.
        # 
        self.canvas.create_arc(
            x - 3,
            y - 3,
            x + 100 * scale_factor + 3,
            y + 100 * scale_factor + 3,
            start=0,
            extent=-220,
            style=tk.ARC,
            outline='white',
            width=2,
        )
        #
        # Draw value box.
        # 
        self.canvas.create_rectangle(
            x + 50 * scale_factor,
            y + 20 * scale_factor,
            x + 100 * scale_factor + 3,
            y + 50 * scale_factor,
            outline='white',
            width=2,
        )
        font = self.font.copy()
        font['size'] = int(14 * scale_factor)
        self.canvas.create_text(
            x + 52 * scale_factor,
            y + 20 * scale_factor,
            anchor=tk.NW,
            text=measured_item.value,
            fill='turquoise',
            font=font,
        )
        font = self.font.copy()
        font['size'] = int(20 * scale_factor)
        self.canvas.create_text(
            x,
            y - 15,
            anchor=tk.NW,
            text=measured_item.name,
            fill='white',
            font=font,
        )
 
    def not_implemented(self):
        pass

    def _draw_boot_screen(self):
        self.canvas.delete(tk.ALL)
        self.draw_gauge(70, 50, self.measured_items[0])
 
    def draw_main_screen(self):
        self.canvas.delete(tk.ALL)
        for i, measured_item in enumerate(self.measured_items[:3]):
            self.draw_gauge(50, 20 + i * 100, measured_item)


def main():
    main_window = MainWindow()
    main_window.mainloop()


if __name__ == '__main__':
    main()
Das ursprüngliche Problem wird dabei komplett umgangen, denn es werden einfach keine Label mehr verwendet sondern der Text wird mit `Canvas.create_text()` erzeugt. Damit wird der automatisch beim ``delete(tk.ALL)`` schon erfasst. Sonst hätte man mit den `Label`-Objekten `create_window()` verwenden müssen um diesen Effekt zu erreichen.

Ich würde als nächstes eine Messanzeige als eigenen Datentyp dort heraus ziehen und zwar auf einem eigenen `Canvas` gezeichnet, und dann Tk's Layout-Mechanismen benutzen statt alles an selber berechneten Positionen zu platzieren. Und dann die einzelnen Bildschirme in Klassen auslagern und wahrscheinlich auch alle am Anfang erstellen und dann beim Wechseln nicht löschen sondern auswechseln. Das kann man Beispielsweise erreichen in dem man einen Bildschirm von `tk.Frame` erben lässt und ihn dann mit einer Layout-Methode im Hauptfenster anordnet und mit der entsprechenden `*_forget()`-Methode wieder aus der Anzeige herausnimmt bevor man den nächsten Bildschirm anzeigt.