Begleitfenster

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Zum Auftakt erst einmal zwei ältere Zitate aus einem anderen Themenstrang:
Goswin hat geschrieben: Mein Algorithmus im Hauptmodul macht Berechnungen, die viel Zeit in Anspruch nehmen. Aus diesem Grund ist es wichtig sich laufend über dessen Fortschritt zu informieren und Zwischenergebnisse auszuwerten. Das geht natürlich auch schriftlich über das Terminal, oder durch die Ausgabe verschiedener Dateien, die man einsehen kann. Aber in einigen Fällen ist dieser Fortschritt inuitiver auszuwerten, wenn er grafisch dargestellt wird, und genau das möchte ich manchmal machen.

Das Algorithmus ist eindeutig die Hauptsache, und kann auch ohne grafische Darstellung durchlaufen und vernünftige Ergebnisse liefern. Die grafische Darstellung ist ohne den Algorithmus sinnlos, sie soll ja nur berichten, was dieser gerade macht. Deshalb halte ich es für "gesund" wenn der Algorithmus die grafische Darstellung von Zeit zu Zeit aufruft und nicht umgekehrt. Außerdem könnten je nach Verlauf manchmal verschiedene grafische Darstellungen angezeigt werden.

Für die grafische Darstellung wäre ein Canvas-Widget in Tkinter geqeignet. Aber wie mache ich es, dass Tkinter in einem parallelen Strang läuft, der immer dann reagiert, wenn er vom Algorithmus aufgerufen wird?
BlackJack hat geschrieben: Das Problem ist halt, dass grafische Systeme das schlicht so nicht vorsehen. Die haben eine Hauptschleife und die muss auch laufen, sonst friert die GUI ein und das System fragt den Benutzer ob er die Anwendung killen möchte, weil sie nicht mehr reagiert.

Lang laufende Berechnungen kann man in einem eigenen Thread laufen lassen und ab und zu Daten in eine Queue schreiben lassen. Die kann man dann regelmässig von der GUI-Seite aus abfragen und eventuell vorhandene Daten/Zwischenergebnisse visualisieren. Oder man informiert den GUI-Thread über ein benutzerdefiniertes Ereignis das Daten in der Queue vorliegen. Die Daten selber kann man bei Tkinter leider nicht mit dem Ereignis übertragen.
Ich bin mir nicht sicher, ob ich die Antwort von Blackjack richtig verstanden habe, wenn ich herauslese, dass das was ich ich möchte mit Tkinter so nicht zu machen ist. Insbesondere scheint er zu sagen, dass irgendwo im Programm eine Hauptschleife laufen muss, und laut den Tutorials die ich finde, wird diese mit ".mainloop()" angestoßen. Zumindest das letztere scheint mir ein Mythos zu sein, denn mein Fenster ist auch ohne ".mainloop()"-Anweisung zu sehen. Der folgende Beispielscode vergleicht verschiedene Rundwege zwischen den Besucherorten eines Handelsreisenden-Problems (traveling salesman problem), und zeichnet jedesmal, wenn er einen kürzeren findet, diesen in ein Canvas-Widget eines Tkinter-Begleitfensters, welches während des gesamten Verlaufs offen bleibt. Natürlich ist dieser Algorithmus grottenschlecht und dient nur dazu, die Erzeugung eines Begleitfensters zu illustrieren.

Wie so etwas zu machen ist habe ich großen Teils durch Zufall herausgefunden (indem ich anderswo vergaß, ein "mainloop()" einzufügen). Aber ich verstehe leider immer noch nicht, warum so etwas funktioniert bzw funktionieren soll, und bin für jede Erklärung dankbar. Auch ist es gut möglich, dass das angezeigte Pattern verbessert werden kann oder vielleicht auch Probleme aufweist, die mir nicht aufgefallen sind.

Code: Alles auswählen

import tkinter as t
from time import sleep
#
class Begleitbild(object):
   def __init__(self):
      t.Tk().withdraw()

   def oeffne(self):
      self._fenster = fenster = t.Toplevel()
      (weit,hoch) = (400,400)
      fenster.geometry("{}x{}+{}+{}".format(weit+20,hoch+20,300,0))
      fenster.overrideredirect(True)
      rahmen = t.Frame(fenster,borderwidth=10)
      rahmen.grid()
      self._cv = t.Canvas(rahmen,bg='white',width=weit,height=hoch)
      self._cv.grid()
      self.zeichne_startbild()

   def schliesse(self):
      self._fenster.destroy()

   def zeichne_startbild(self):
      #leeres Startbild
      cv = self._cv
      self._koord = [-1,-1]
      self._weg = self._cv.create_polygon( *self._koord,
         outline='red',fill='white', width=2 )      

   def zeige_weg(self,koord=None):
      #Bild aktualisieren
      if not koord: koord = self._koord
      self._cv.coords(self._weg,*koord)
      self._fenster.update_idletasks()
      sleep(1)


import math
import random as r
#
class Handelsreisender(object):

   def __init__(self,bild):
      koordwerte = list(range(10,400,10))
      #
      ortz = 12
      ort_lst = []
      for _ in range(ortz):
         ort = ( r.choice(koordwerte), r.choice(koordwerte) )
         ort_lst.append(ort)
      print("\nDer Handelsreisender besucht folgende Orte:\n{}"
         .format(ort_lst))

      input("\nZufallswege im Begleitfenster sehen? ")
      bild.oeffne()
      #
      #Schlechtmöglichster Algorithmus für diese Aufgabe;
      #NUR um Begleitbilder zu illustrieren.
      versuchz = 100000
      minlaenge = 10e9
      for versuch in range(versuchz):
         #
         (xx,yy) = ort_lst[-1]
         laenge = 0
         koord = []
         for ort in ort_lst:
            koord.extend(ort)
            laenge += math.hypot(xx-ort[0],yy-ort[1])
            (xx,yy) = ort
         #
         if laenge<minlaenge:
           minlaenge = laenge 
           bild.zeige_weg(koord)
           print("\nbisher kürzeste gefundene Weglänge ist {:.3f}"
              .format(minlaenge))
           #weiter = input("besseren Weg suchen? ")
           #if weiter=='nein': exit()
         r.shuffle(ort_lst)
      print("\nMehr als {} Fälle untersuche ich nicht!".format(versuchz))
      #
      input("Begleitfenster schließen?")
      bild.schliesse()
      input("\ngenug?")


bild = Begleitbild()
Handelsreisender(bild)
Zuletzt geändert von Goswin am Dienstag 28. November 2017, 21:06, insgesamt 3-mal geändert.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du rufst ja selber update_idletasks auf. Damit treibst du die Ereignisschleife. Zumindest bis zu einem gewissen Grade. Wie weit das geht, und ob du zb noch auf Fensterereignisse, Tastendrücke etc reagieren kannst - das muss man ausprobieren oder nachschlagen.

Insofern - no Myth busted.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und hier steht, das Update fehlt, und das es nicht empfohlen wird: https://stackoverflow.com/questions/291 ... g-mainloop
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

__deets__ hat geschrieben:Du rufst ja selber update_idletasks auf. Damit treibst du die Ereignisschleife.
Wenn du die Zeile "update_idletasks()" auskommentierst und durch "input()" ersetzt, dann wird das Fenster immer noch gezeichnet. Wird dann die Erqeignisschleife immer noch angestoßen?
Und hier steht, das Update fehlt, und dass es nicht empfohlen wird: [...]
Ein ".update()" wird nicht empfohlen, und deshalb benutze ich es auch nicht: dann mache ich ja alles bestens! Und zu welchem Zweck sollte ".update()" hier überhaupt verwendet werden?


Falls in einer Widget-Bibliothek wie Tkinter eine so häufige und grundlegende Anwendung wie Begleitfenster "nicht vorgesehen ist" (wie BlackJack sagt), dann ist das ein schwerwiegender Mangel von Tkinter, und man darf sich nicht wundern, wenn sich Benutzer durch Roundabouts auszuhelfen versuchen. Und mögliche Risiken muss man eben eingehen, denn eine dürftig funktionierende Software ist oft immer noch hilfreicher als gar keine.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Goswin: dass man sich mit einem Beil das eigene Bein abhacken kann, ist zwar möglich, aber meist nicht empfehlenswert. Zum Code: einbuchstabige Namen sind unleserlich. tkinter wird üblicherweise mit tk abgekürzt, random überhaupt nicht. Alle Imports stehen am Anfang. Eingerückt wird immer mit 4 Leerzeichen pro Ebene. Ein Tk-Fenster zu erzeugen, um es gleich wegzuwerfen ist unsinnig. So etwas macht man auch nicht in __init__. Dagegen werden in __init__ alle Attribute angelegt.
Ansonsten werden längere Rechnungen in einem eigenen Thread ausgelagert und die Anzeige per Queue in einer Funktion, die regelmäßig per after aufgerufen wird, aktualisiert.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Sirius3 hat geschrieben:Ansonsten werden längere Rechnungen in einem eigenen Thread ausgelagert und die Anzeige per Queue in einer Funktion, die regelmäßig per after aufgerufen wird, aktualisiert.
Wie man so etwas macht, habe ich noch nicht gelernt. Über den Hinweis auf ein Online-Beispiel würde ich mich natürlich freuen.

Sirius3 hat geschrieben:Einbuchstabige Namen sind unleserlich. tkinter wird üblicherweise mit tk abgekürzt, random überhaupt nicht. Alle Imports stehen am Anfang. Eingerückt wird immer mit 4 Leerzeichen pro Ebene. Ein Tk-Fenster zu erzeugen, um es gleich wegzuwerfen ist unsinnig.
@Sirius3:
Ich habe diesen Code im Showcase-Unterforum (und nicht in einem Frage-Unterforum) gepostet, falls er für irgend jemandem hier von Nutzen ist. Ich kann für den Code keine Verantwortung übernehmen, und habe keine Zeit, ihn anderen Benutzern anzupassen. Wer immer ihn verwenden will, tut es auf eigenes Risiko und nimmt ihn "as it is".

Es tut mir leid, dass du behindert bist, einbuchstabige Modulabkürzungen zu lesen. Aber falls du am Code interessierst bist, kannst du ja selber die Abkürzungen verlängern, Imports an den Anfang rücken, und die Einrückungen auf 4_Leerzeichen erweitern. Kopf hoch, du schaffst das! :wink:
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es tut mir leid, das du suchbehindert bist. Wenn dir daran gelegen ist, dein Wissen um Programmierung zu erweitern, kannst du ja selber die entsprechenden Schlagworte in die Suchmaschine deiner Wahl einzufuellen, und dich durch die Ergebnisse wuehlen. Kopf hoch, du schaffst das!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

__deets__ hat geschrieben:Wenn dir daran gelegen ist, dein Wissen um Programmierung zu erweitern, kannst du ja selber die entsprechenden Schlagworte in die Suchmaschine deiner Wahl einzufüllen, und dich durch die Ergebnisse wühlen.
360_Anfragen von mir in über 10_Jahren - das sind weniger als 3_Anfragen im Monat. Was denkst du wohl, lieber _deets_, was ich zwischendurch gemacht habe? Du sagst es! :D
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Goswin hat geschrieben:
__deets__ hat geschrieben:Wenn dir daran gelegen ist, dein Wissen um Programmierung zu erweitern, kannst du ja selber die entsprechenden Schlagworte in die Suchmaschine deiner Wahl einzufüllen, und dich durch die Ergebnisse wühlen.
360_Anfragen von mir in über 10_Jahren - das sind weniger als 3_Anfragen im Monat. Was denkst du wohl, lieber _deets_, was ich zwischendurch gemacht habe? Du sagst es! :D
Bonsai gezüchtet? Die reine Beschäftigungszeit mit einem Thema sagt ja nun nix über Ausmaß oder Tiefe aus. Und angesichts der in diesem Thread dokumentierten Unkenntnis über Threading und Ereignisbehandlung bin ich sicher, das du einen formidablen Miniaturwald dein eigen nennst. Oder etwas äquIvalentes.
Astorek
User
Beiträge: 72
Registriert: Samstag 24. Januar 2009, 15:06
Kontaktdaten:

Puh, da gibts doch einiges zu sagen dazu...
Goswin hat geschrieben:Falls in einer Widget-Bibliothek wie Tkinter eine so häufige und grundlegende Anwendung wie Begleitfenster "nicht vorgesehen ist" (wie BlackJack sagt), dann ist das ein schwerwiegender Mangel von Tkinter, und man darf sich nicht wundern, wenn sich Benutzer durch Roundabouts auszuhelfen versuchen. Und mögliche Risiken muss man eben eingehen, denn eine dürftig funktionierende Software ist oft immer noch hilfreicher als gar keine.
Zunächst mal ist es selbstverständlich möglich, "Begleitfenster" in Tkinter zu erstellen, nur ist die Herangehensweise eigentlich komplett anders. Mit deiner Herangehensweise hätten gefühlt 98% aller Sprachen, die GUIs anbieten, einen schwerwiegenden Mangel (die einzigen Ausnahmen die ich kenne, nennen sich AutoIt und AutoHotKey, beide haben aber dafür andere Mängel...). Vom Konzept her "brauchen" GUIs einfach einen Zustand, in dem sie auf Eingaben reagieren können, selbst die genannten Ausnahmen machen das.

Ich gehe einfach mal davon aus, dass du schonmal über die "after"-Methode von Tkinter gestolpert bist, oder?

In Kombination mit Threading kannst du dann in der GUI prüfen, ob die Berechnung bereits durchgeführt wurde.

Möglichkeit 1:
Mit "after" gibst du eine Methode an, die zu festgelegten Zeiten immer wieder besagte Methode aufruft. Dort kannst du dann nachprüfen, ob besagter Thread noch läuft, und wenn nicht, kannst du wiederum die GUI entsprechend ändern.

Möglichkeit 2 (die ich persönlich bevorzuge, aber eventuell Overengineert ist):
Du baust die Berechnung in eine Klasse um und implementierst Events, die in regelmäßigen Abständen besagte Events "werfen". Diese Events kannst du dann in deiner GUI-Klasse an entsprechende GUI-Methoden binden. Damit hast du dann u.a. eine unabhängige Berechnung-Klasse und eine unabhängige GUI-Klasse. Wenn ich später Zeit habe, bringe ich ein entsprechendes Beispiel; das Ganze hört sich komplizierter an als es eigentlich ist. Wenn man das System dahinter einmal verstanden hat, versteht man auch einen entscheidenden Vorteil von OOP, so gings mir zumindest damals^^...
Es tut mir leid, dass du behindert bist, einbuchstabige Modulabkürzungen zu lesen. Aber falls du am Code interessierst bist, kannst du ja selber die Abkürzungen verlängern, Imports an den Anfang rücken, und die Einrückungen auf 4_Leerzeichen erweitern. Kopf hoch, du schaffst das! :wink:
Zwei Sachen dazu:
  • Wir alle sind hier im Forum während unserer Freizeit, keiner wird dafür entlohnt. Erwarte nicht, dass alle Welt darauf wartet, sich auf neue Threads zu stürzen und stets zu 100% hilfsbereit zur Seite zu stehen. Wir sind nicht die Wohlfahrt, ums mal blöd auszudrücken.
  • Sirius3 hat dir lediglich Tipps zur besseren Übersicht gegeben. Wie du damit umgehst ist natürlich deine Entscheidung, aber ich wage mal stark zu bezweifeln, dass Sirius3 dich persönlich beleidigen wollte. Kein Grund gleich so ruppig zu werden.
Die von Sirius3 genannten Punkte sind einfach Quasi-Standards, weil die sich bewährt haben - und es erhöht ganz nebenbei die Bereitschaft von Programmierern, sich deinen Code anzusehen. Angenommen, dein Quelltext hätte mehrere 1000 Zeilen, du postest eine Methode die mit einem Buchstaben abgekürzt wurde - es würden sich sicher weniger Leute Mühe machen, sich deinen Quelltext anzusehen und dir zu helfen. Und im Endeffekt hilft es dir auch, bessere Strukturen in einen Quelltext zu bekommen. Wenn du nämlich mal einen mehrere tausend Zeilen langen Quellcode schreibst, wirst du nämlich auch froh sein, dass sich die Zahl der einbuchstabigen Variablen in Grenzen hält. Das wird dir jeder Programmierer sagen können, der größere Projekte betreut...

Wie gesagt, du kannst die Vorschläge annehmen oder nicht, deine Entscheidung...
Antworten