Ich bin mir ziemlich sicher, dass man einfach von einer vorhandenen Klasse erbt und sich dann um so einen Schabernack gar nicht kümmern mussAlfons Mittelmeyer hat geschrieben:Deswegen soll man aber auch __del__() implementieren für den Fall, dass man vergisst destroy auftzurufen
Tkinter Display clearen
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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
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.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.
Das ist ja das tolle an Vererbung in der OOP
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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
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,
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also, das mit der Klasse habe ich nicht richtig verstanden. Ich fange immer ganz sauber an, und mache alles lokal, etwa:Sirius3 hat geschrieben:Am besten fängt man mit einer einfachen Klasse an, ohne global, ohne Sternchenimporten und ohne ausführbaren Code auf Modulebene.
Code: Alles auswählen
def main(parent):
tk.Label(parent,text='Hello',command = (lambda: print('Hello'))).pack()
main(root)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)-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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.
@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.Alfons Mittelmeyer hat geschrieben:@tsaG: ich habe es jetzt mit einer Funktion gemacht und nicht mehr verschachtelt. Vielleicht gefällt Dir das ja besser
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.
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@sparrow Wieso ist das dann global? Hast Du schon überlegt, wie schnell die Funktion main vorbei ist?
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Das brauchst Du mir bestimmt nicht sagen. Aber stimmt, das war schlecht programmiert, denn wir haben eine globale Variable 'root':sparrow hat geschrieben:@Alfons: Weißt du warum man globale Variablen vermeiden soll?
Code: Alles auswählen
root = tk.Tk()
def main(parent):
xxx
main(root)
root.mainloop()Code: Alles auswählen
def main(parent):
xxx
return parent
main(tk.Tk()).mainloop()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.
-
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…
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…
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@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
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
-
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.
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@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: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.
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_loadActiveWindowWas 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.
-
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.
„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.
-
Alfons Mittelmeyer
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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?
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.
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
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
Take it easy Mates!
-
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:
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.
@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()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.
