Seite 1 von 1

tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 14:09
von Tester17
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 14:23
von __deets__
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 14:31
von Sirius3
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?

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 14:56
von Tester17
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).

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 15:36
von Tester17
__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

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 15:55
von __deets__
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 16:33
von Tester17
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 16:48
von __deets__
Die Klassen stehen im Quellcode: https://github.com/python/cpython/blob/ ... log.py#L33 - davon kannst du ableiten.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Donnerstag 6. April 2023, 18:38
von Sirius3
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Freitag 7. April 2023, 10:50
von Tester17
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?

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Freitag 7. April 2023, 11:08
von sparrow
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Freitag 7. April 2023, 11:34
von __deets__
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 🤷‍♂️

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Montag 10. April 2023, 17:06
von Tester17
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.

Re: tkinter filedialog class mit with-Statement aufrufen?

Verfasst: Montag 10. April 2023, 18:12
von __deets__
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.