tkinter filedialog class mit with-Statement aufrufen?

Fragen zu Tkinter.
Antworten
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

Hallo,
bis jetzt verwende ich tkinter filedialoge mit folgendem Aufruf:

Code: Alles auswählen

  try:
    root = tk.Tk()
    root.withdraw()

    if filetypes is None:
      filetypes = (('All files', '*.*'))

    filename = filedialog.askopenfilename(
      title='Open a panorama file',
      filetypes=filetypes)
  except:
    pass
    return None 
  finally:
    root.withdraw()
    root.destroy()
Ich möchte nun das ganze stattdessen mit einem With-Statement formulieren, analog zu:

Code: Alles auswählen

  class MyApp(tk.Tk):
    def InitLocale(self):
        pass

    def __init__(self, parent):
      tk.Tk.__init__(self, parent)
      self.parent = parent
      self.mainWidgets()
      . . .

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False

    def cancel(self, event=None):
   . . .
        self.destroy()
        self.quit()


    application = MyApp(None)
    with application as app:
      app.mainloop()
Wie formuliere ich das gleiche für die Klasse filedialog von tkinter?
Dieser Klasse fehlen entsprechende Methoden.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso baust du ueberhaupt ein root auf, wenn du es eh sofort wieder kaputt machst? Benutz doch einfach filedialog.askopenfilename fuer sich. So ist es gedacht.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich verstehe nicht, was das "with" im unteren Beispiel bewirken soll? Da sowohl __enter__ als auch __exit__ nichts macht ist der ganze Context unnötig.
Und die selbe Frage bei Deinem file-Dialog: was willst Du mit "with" erreichen?
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

Sirius3 hat geschrieben: Donnerstag 6. April 2023, 14:31 Ich verstehe nicht, was das "with" im unteren Beispiel bewirken soll? Da sowohl __enter__ als auch __exit__ nichts macht ist der ganze Context unnötig.
Und die selbe Frage bei Deinem file-Dialog: was willst Du mit "with" erreichen?
Das untere Beispiel dient lediglich der Anschauung.
Es werden im Übrigen etliche Dinge beim Subclassing gemacht:

Code: Alles auswählen

    def __init__(self, parent):
      tk.Tk.__init__(self, parent)
      self.parent = parent
      self.mainWidgets()
      . . .

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False

    def cancel(self, event=None):
   . . .
        self.destroy()
        self.quit()
Wie man unschwer erkennen kann wird beim init und cancel etwas mehr code (...) und destroy/quit ausgeführt...

In der abgleiteten tk.filedialog-Klasse soll unter anderem das Hauptfenster geschlossen werden und nur der Filedialog angezeigt werden:

Code: Alles auswählen

    root = tk.Tk()
    root.withdraw()
Ein Subclassing ermöglicht zudem, die Klasse in verschiedenen Dialogen (open file, save file, open directory ) gemeinsam recht kurz zu verwenden (weniger Code).
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

__deets__ hat geschrieben: Donnerstag 6. April 2023, 14:23 Wieso baust du ueberhaupt ein root auf, wenn du es eh sofort wieder kaputt machst? Benutz doch einfach filedialog.askopenfilename fuer sich. So ist es gedacht.
Hallo,

zum Aufruf von filedialog benötigt man einen tk-Grunddialog (hier root). Ruft man den filedialog direkt auf, wird zunächst ein root-Fenster kreiert (wenn es noch nicht vorhanden ist) und dann ein zweites Fenster mit dem eigentlichen Filedialog erzeugt. Das sieht gelinde gesagt grottig aus. Daher sorgt man mit dem oben genannten Konstrukt dafür, dass nur der Filedialog angezeigt wird. Dies ist insbesondere der Fall, wenn man nicht in Standard-Desktopumgebungen arbeitet.

Gruß,

Sebastian
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich benoetige keinen solchen "Grunddialog". Da geht nur ein dialog auf.

Wie dem auch sei, wenn das fuer dich da oben mit dem ganzen try/except/widthdrawe funktioniert, kannst du das mit contextlib.contextmanager einfach in einen kleinen contextmanager giessen, der vor und nach dem Dialog die entsprechenden Schritte tut.
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

Hallo,
meine Frage zielt auf das Subclassing von tk.filedialog. Wie geht das?

Das Verhalten von tkinter ist auf den verschiedenen Plattformen recht unterschiedlich. Da ich keine TK-Hauptfenster habe, ist das Verhalten bei mir, exakt wie von mir beschrieben. Du magst ja in einer anderen Umgebung programmieren...

Vielen Dank für den Hinweis auf den contextmanager, das ist jedoch nicht das, was ich suche.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Klassen stehen im Quellcode: https://github.com/python/cpython/blob/ ... log.py#L33 - davon kannst du ableiten.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Die wiedersprichst Dir ja selbst, wenn Du glaubst, dass ›contextmanager‹ nicht das ist, was Du suchst, dann das ist die einfachste Art, wie man einen ContextManager erstellt.
Und dass Du in Deiner Klasse eine Methode __init__ oder cancel hast, hat ja nichts mit dem ContextManager zu tun, denn dafür sind die Methoden __exit__ und __enter__ da, die aber beide nichts machen, und auch innerhalb des with-Blocks machst Du nichts, wofür man einen ContextManager gebrauchen könnte.

In Deinem Fall ist die Lösung eine einfache Funktion:

Code: Alles auswählen

def askopenfilename(title, filetypes=None):
    if filetypes is None:
        filetypes = (('All files', '*.*'))
    try:
        root = tk.Tk()
        root.withdraw()
        return filedialog.askopenfilename(
            title=title, filetypes=filetypes)
    except Exception:
        return None 
    finally:
        root.withdraw()
        root.destroy()


filename = askopenfilename('Open a panorama file')
Übrigens, eingerückt wird immer mit vier Leerzeichen pro Ebene, nicht zwei.
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

Hallo,

der von Dir vorgeschlagene Code entspricht exakt dem, was ich zur Zeit benutze.

Wie ich schon mehrfach sagte, suche ich eine alternative Formulierung mit Hilfe von direktem Subclassing, also in etwa über:

Code: Alles auswählen

calss MyFileDialog(tk.filedialog):
. . . 
Es ist nicht die Frage, ob es in jedem Fall sinnvoll ist, sondern ob und wie es überhaupt geht. Ich suche auch nicht nach einem Wrapper oder einer Methode, sondern nach Subclassing.

Ich habe mir den Quellcode angeschaut, aber er hilft mir nicht weiter.

Beim Versuch eine abgeleitete Klasse zu definieren:

Code: Alles auswählen

import tkinter as tk
from tkinter import filedialog, simpledialog

class MyFileDialog(tk.filedialog):
bekomme ich die Fehlermeldung:
TypeError: module() takes at most 2 arguments (3 given)
Wie formuliere ich das korrekt?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ich finde es ein bisschen seltsam, dass du dich hier ausgiebig darüber auslässt, wie man tkinter korrekt verwendet, dann aber irgendie Code rätst und Gegebenheiten nicht erkennst.

Du redest hier die ganze Zeit von einem "Subclassing". Wenn du von einer Klasse ableiten oder erben möchtest, dann sollte das auch eine Klasse sein. Ist aber tkinter.filedialog gar nicht. __deets__ hat dir doch einen Link zum Quellcode gepostet. Da siehst du die entsprechenden Klassen.

Also entweder bist du schlecht im Erklären was du möchtest, oder du bist auf einem Holzweg. Die vielen Fragezeichen, die dir hier entgegen schlagen, lassen das nach meiner Ansicht vermuten.
Wie man in dem von dir beschriebenen Fall vorgeht, wurde dir gezeigt. Jedenfalls so, wie du den Fall darstellst.
Und ich befürchte, du hast nicht verstanden, wofür das with Statement eigentlich da ist. Und was "Subclassing" ist.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man kann von einem Modul nicht ableiten. Punkt. tk.filedialog ist ein Modul. Kannst du also nicht ableiten von. In dem Modul sind aber Klassen. Von denen kann man ableiten. Hab ich dir gezeigt. Wenn du stattdessen die fertigen Funktionen in dem Modul benutzen willst, brauchst du einen contextmanager. Auch von mir gezeigt. Das sind deine beiden Optionen. Wähle eine, aber dein ausgedachter Weg ist nunmal nicht gangbar 🤷‍♂️
Tester17
User
Beiträge: 9
Registriert: Donnerstag 6. April 2023, 14:06

Vielen Dank für den Hinweis das filedialog nur ein Modul ist. Leider kenne ich den inneren Aufbau von tkinter nicht und auch der Quellcode ist nicht einfach zu lesen.

Da ich auch mit der Klasse FileDialog (Teil von module filedialog) nicht recht zu Rande gekommen bin, habe ich jetzt eine abgeleitete Klasse von der übergeordneten Klasse Tk erstellt und nun klappts:

Code: Alles auswählen

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename, askdirectory
import re
import os
 
class MyFileDialog(tk.Tk):
    def __enter__(self):
        self.withdraw()
        return self


    def __exit__(self, type, value, traceback):
        self.destroy()
        self.quit()


    def get_open_path(self=None, **kwargs):
        if ("filetypes" not in kwargs):
            kwargs["filetypes"] = (("csv files", "*.csv"),("all files", "*.*"))
        if ("title" not in kwargs):
            kwargs["title"] = 'Open Sample Data'
        if ("initialdir" not in kwargs):
            kwargs["initialdir"] = os.getcwd()

        path = askopenfilename(**kwargs)
        return path

 
    def get_save_path(self=None, **kwargs):
        if ("filetypes" not in kwargs):
            kwargs["filetypes"] = (("csv files", "*.csv"),("all files", "*.*"))
        if ("title" not in kwargs):
            kwargs["title"] = 'Save Sample Data'
        if ("initialdir" not in kwargs):
            kwargs["initialdir"] = os.getcwd()
        if ("confirmoverwrite" not in kwargs):
            kwargs["confirmoverwrite"] = True

        path = asksaveasfilename(**kwargs)
        # Make sure extension ".csv" is added
        if path != "":
            path = re.sub('\.csv$', '', path) + ".csv"
        return path


    def get_folder_path(self, **kwargs):
        if ("title" not in kwargs):
            kwargs["title"] = 'Select directory'
        if ("initialdir" not in kwargs):
            kwargs["initialdir"] = os.getcwd()

        folder = askdirectory(**kwargs)
        return folder


if __name__ == '__main__':
    application = MyFileDialog(None)
    with application as app:
        filetypes=(("csv files", "*.csv"),("all files", "*.*"))
        print(app.get_open_path(filetypes=filetypes))
        print(app.get_save_path())
        print(app.get_folder_path())
Damit sind nach der Klassendefinition (oberer Teil) alle weiteren Aufrufe der Filedialoge sehr kurz und können mit With erfolgen. Das ist mein Ziel gewesen.
Ein etwas freundlicherer Ton bei der Beantwortung der Fragen im Forum wäre wünschenswert.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum das jetzt besser ist, als ein Contextmanager, erschliesst sich mir nicht.

Code: Alles auswählen

import tkinter.filedialog

from contextlib import contextmanager

@contextmanager
def hidden_application_window(root):
    root.withdraw()
    try:
        yield
    finally:
      root.destroy()
      root.quit()

def main():
    root = tkinter.Tk()
    with hidden_application_window(root):
        tkinter.filedialog.askopenfilename()

if __name__ == '__main__':
    main()
Denn Ableitung vermeidet man, wenn moeglich, da sie schlecht komponierbar ist.

Was den Ton angeht ... wer mit
Wie man unschwer erkennen kann wird beim init und cancel etwas mehr code (...) und destroy/quit ausgeführt...
wenig subtil und genauso wenig freundlich den Mitlesern vorwirft, das Problem nicht verstanden zu haben, darf da auch gerne selbst eine Schippe drauflegen.
Antworten