Variablen über Tkinter Frames weitergeben

Fragen zu Tkinter.
Antworten
D3us3x
User
Beiträge: 4
Registriert: Montag 30. September 2019, 18:45

Hallo zusammen!
Ich bin ein kleiner Hobbyprogrammierer und hatte vor, mir ein kleines Tool zu schreiben, welches Commands in der Powershell ausführt.
Und jetzt meine kleine Frage: Wie kann ich Werte zwischen den verschiedenen Tkinter-Frames übergeben? (Ich möchte von Configurewindow die Eingabe von Path in die 2 Funktionen namens "convtom4a/convtomp4" übergeben)
Ich hab schon gegooglet, konnte aber keine Antwort finden, die ich nutzen könnte.
Den Code findet ihr hier:

Code: Alles auswählen

from tkinter import *
from tkinter import filedialog
from tkinter import messagebox
import os

def CallConfigureWindow():
    ConfigureWindow = Tk()
    folder_path = "/Downloads/%(title)s.%(ext)s"
    ConfigureWindow.title("Configuration")
    ConfigureWindow.minsize(430, 110)
    ConfigureWindow.maxsize(430, 110)
    ConfigureWindow.lift()
    Label(ConfigureWindow, text="Download Path:", font="Arial 16") .grid(row=0, column=0, sticky=W, pady=5, padx=5)
    Path = Entry(ConfigureWindow, width=60)
    def selectFolder():
        root = Tk()
        root.attributes("-topmost", True)
        root.withdraw()
        folder_path = filedialog.askdirectory()
        Path.delete(first=0,last=100)
        Path.insert(10, folder_path)
        Path.insert(10, "/%(title)s.%(ext)s")
        ConfigureWindow.lift()
        return folder_path
    def clearPath():
        Path.delete(first=0,last=22)
        Path.insert(10, "/Downloads/%(title)s.%(ext)s")
    Path.insert(10, folder_path)
    Path.grid(row=1, column=0, sticky=W, pady=5, padx=2)
    Button(ConfigureWindow, text="Browse", command=selectFolder) .grid(row=1, column=1, sticky=W, pady=5, padx=5)
    Button(ConfigureWindow, text="Reset Settings", width=18, command=clearPath) .grid(row=2, column=0, sticky=W, pady=5, padx=5)

    ConfigureWindow.mainloop( )
    return folder_path
def main():

    MainWindow = Tk()
    MainWindow.title("YouTube Converter")
    MainWindow.minsize(500, 120)
    MainWindow.maxsize(500, 120)

    Label(MainWindow, text="Link:", font="Arial 22") .grid(row=0, column=0, sticky=W, pady=5, padx=5)
    Link = Entry(MainWindow, width=80)
    Link.grid(row=1, column=0, pady=5, padx=5)

    def convtom4a():
        command=os.system('powershell -command youtube-dl ' + Link.get() + " -f m4a -o " + )
    def convtomp4():
        command=os.system('powershell -command youtube-dl ' + Link.get() + " -f mp4 -o " + )
    Button(MainWindow, text="Configure", command=CallConfigureWindow) .grid(row=2, column=0, sticky=W, pady=5, padx=5)
    Button(MainWindow, text="Convert to Video", command=convtomp4) .grid(row=2, column=0, sticky=E, pady=5, padx=120)
    Button(MainWindow, text="Convert to Audio", command=convtom4a) .grid(row=2, column=0, sticky=E, pady=5, padx=5)

    def on_closing():
        if messagebox.askokcancel("Quit", "Do you want to quit?"):
            MainWindow.destroy()

    MainWindow.protocol("WM_DELETE_WINDOW", on_closing)
    MainWindow.mainloop( )
if __name__ == "__main__":
    main()
Ich freue mich über jede Antwort ^^
MFG
D3us3x
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Keine *-Importe, denn das schaufelt Deinen Namensraum unkontrolliert mit hunderten Namen voll. Tkinter wird üblicherweise als `import tkinter as tk` importiert und alle Namen per tk.xy angesprochen.
Im gesamten Programm darf es nur eine Tk()-Instanz, alle anderen Fenster müssen als TopLevel erzeugt werden. Es darf auch nur einen mainloop-Aufruf geben.
Variablen schreibt man komplett_klein. os.system ist veraltet. Benutze subprocess.run und übergeb den Befehl und die Parameter als Liste.
Vielleicht willst Du auch einen Dialog benutzen?
Für jedes nicht-triviale GUI-Programm braucht man auch Klassendefinitionen. Benutze keine verschachtelten Funktionen.
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@D3us3x: Anmerkungen zum Quelltext:

Sternchen-Importe sind Böse™. Gerade bei `tkinter` holt man sich fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil tatsächlich benutzt wird. Auch Namen die gar nicht im `tkinter`-Modul definiert werden, sondern die dort ihrerseits aus anderen Modulen importiert werden.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Die Fenstergrösse gibt man nicht vor, die ergibt sich aus dem Inhalt des Fensters. Das mag ja bei Dir funktionieren, bei mir sehen die beiden Fenster wie folgt aus, und Du erlaubst ja nicht mal mehr, dass der Anwender selbst das Fenster so gross ziehen kann, dass alles angezeigt wird:
Bild
Bild
Im zweiten Fenster sieht man was passiert wenn man den Button dort drückt. Das sieht nicht gut/richtig aus.

Wenn man das mit der Grössenbegrenzung raus nimmt, sieht das Hauptfenster bei mir so aus:
Bild
Oder mit anderen Worten: drei Buttons in die selbe Grid-Zelle zu setzen und versuchen mit `sticky` und `pad*` und absoluten Pixelwerten die so zu verschieben, dass sie alle sichtbar sind, ist letztlich `place()` mit anderen Mitteln – und funktioniert nicht wie man sieht.

`Entry`-Objekte sollten nicht `link` oder `path` heissen, denn das wären Namen für das was der Benutzer dort eingibt, nicht für das Anzeigeobjekt in dem das eingegeben wird.

Man verschachtelt keine Funktionen in Funktionen. Das macht Sinn wenn man eine Funktion schreibt, die ein Closure an den Aufrufer zurück gibt. Aber hier macht es das Programm einfach nur unübersichtlich und verhindert das man die Funktionen leicht testen kann.

Unübersichtlich ist das unter anderem auch deswegen, weil die Funktionen nicht für sich stehen, man muss immer auch den Inhalt der Funktion kennen in der sie definiert sind, weil sie auf Werte aus diesem umschliessenden Namensraum einfach so zugreifen. Alles was eine Funktion oder Methode ausser Konstanten benötigt, sollte sie aber als Argument(e) übergeben bekommen. Was dann zumindest auch der erste Schritt zur Beantwortung Deiner Frage ist: Die Umwandlungsfunkionen müssen den Pfad als Argument übergeben bekommen.

Fenster werden nicht aufgerufen sondern angezeigt.

Es darf in einem Programm nur *ein* `Tk`-Objekt zur gleichen Zeit geben. Das ist *das* Hauptfenster. Da hängt der Tcl/Tk-Interpreter dran und wenn es davon mehr als einen gibt, können komische Sachen, bis hin zu harten Programmabstürzen passieren. Zusätzliche Fenster erstellt man mit `tkinter.Toplevel`. In `selectFolder()` macht es noch nicht einmal Sinn noch ein `Tk`-Objekt zu erstellen das dann sowieso gleich wieder von der Anzeige zurückgezogen wird.

Das die Funktionen die Du an `command`\s von Buttons bindest Rückgabewerte haben, macht keinen Sinn. Die werden von der GUI-Hauptschleife aufgerufen und die kann mit irgendwelchen Rückgabewerten nichts anfangen und ignoriert die einfach. Hier ist dann auch die Stelle wo man nicht wirklich um objektorientierte Programmierung herum kommt. Bei sehr trivialen Programmen kann man sich noch mit den von `tkinter.Variable` abgeleiteten Typen und `functools.partial()` behelfen, aber das wird relativ schnell unübersichtlich.

Das sie Zeichenkette "/Downloads/%(title)s.%(ext)s" zwei mal im Code steht ist fehleranfällig. So etwas definiert man einmal und als Konstante. Das Dateinamensmuster gehört da auch nicht mit rein.

Warum fügst Du in ein leeres Eingabefeld etwas an Index 10 ein? Man kann da jeden Wert nehmen, die verhalten sich dann alle wie 0, also warum gerde 10 und nicht 23, 42, oder eben die logischste Variante 0‽ Und warum wird bei `clearPath()` der Inhalt von 0 bis Index 22 entfernt? Ich hätte da ja erwartet das *alles* entfernt wird‽ Siehe oben das Bildschirmfoto das den Murks zeigt der durch diese komische Kombination entstehen kann. Entry komplett löschen ist 0 bis `tk.END` und nicht irgendwelche willkürlichen Zahlen von denen man hofft das sie gross genug sind.

Dein Code berücksichtigt nicht, dass der Benutzer den Verzeichnisdialog auch abbrechen kann. Und jedes mal wenn man den Konfigurationsdialog aufruft, fängt der mit dem gleichen Verzeichnis an.

Man sollte keine kryptischen Abkürzungen in Namen verwenden. `convtom4a` und `convtomp4` sind absolut unlesbare Namen.

Der Inhalt der beiden Funktionen ist so ähnlich, dass das eigentlich nur *eine* sein sollte, die halt noch das Zielformat als Argument bekommt. Ausserdem ist `convert_to…()` ein etwas irreführender Name für eine Funktion die Hauptsächlich etwas aus dem Internet herunterlädt und bei der nur als zusätzliche Angabe eine Konvertierung passiert/passieren kann. Zudem sollte diese Funktion komplett unabhängig von der GUI sein.

`os.system()` sollte schon seit Ewigkeiten nicht mehr verwendet werden. Um externe Programme auszuführen, gibt es das `subprocess`-Modul. Da spart man sich viele Probleme, unter anderem auch das man Zeichenketten in den Argumenten verwenden kann, die eine besondere Bedeutung für die Shell in der die PowerShell gestartet wird haben. Wobei an der jetzigen Kombination das in einer PowerShell auszuführen wahrscheinlich immer noch Probleme mit Zeichen entstehen können die für dir PowerShell eine besondere Bedeutung haben. Das so zu machen ist keine gute, robuste Lösung. Warum musst Du da eine PowerShell dazwischen basteln?

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import subprocess
import tkinter as tk
from functools import partial
from pathlib import Path
from tkinter import filedialog, messagebox

DOWNLOAD_PATH = Path("/Downloads")
FILENAME_TEMPLATE = "%(title)s.%(ext)s"  # For ``youtube-dl`` output.
AUDIO_FORMAT = "m4a"
VIDEO_FORMAT = "mp4"


def download(url, target_path, output_format):
    subprocess.run(
        [
            "powershell",
            "-command",
            "youtube-dl",
            "--format",
            output_format,
            "--output",
            str(target_path / FILENAME_TEMPLATE),
            url,
        ],
        check=True,
    )


def on_convert(url_var, path_var, output_format):
    download(url_var.get(), Path(path_var.get()), output_format)


def select_folder(path_var):
    path = filedialog.askdirectory()
    if path:
        path_var.set(path)


def show_configuration_window(master, path_var):
    window = tk.Toplevel(master)
    window.title("Configuration")
    tk.Label(window, text="Download Path:", font="Arial 16").grid(
        row=0, column=0, sticky=tk.W, pady=5, padx=5
    )
    tk.Entry(window, width=60, textvariable=path_var).grid(
        row=1, column=0, sticky=tk.W, pady=5, padx=2
    )
    tk.Button(
        window, text="Browse", command=partial(select_folder, path_var)
    ).grid(row=1, column=1, sticky=tk.W, pady=5, padx=5)
    tk.Button(
        window,
        text="Reset Settings",
        command=partial(path_var.set, DOWNLOAD_PATH),
    ).grid(row=2, column=0, sticky=tk.W, pady=5, padx=5)


def on_closing(widget):
    if messagebox.askokcancel("Quit", "Do you want to quit?"):
        widget.quit()


def main():
    main_window = tk.Tk()
    main_window.title("YouTube Converter")

    path_var = tk.StringVar(value=DOWNLOAD_PATH)

    grid_options = {"padx": 5, "pady": 5}
    tk.Label(main_window, text="Link:", font="Arial 22").grid(
        row=0, column=0, sticky=tk.W, **grid_options
    )
    link_entry = tk.Entry(main_window, width=80)
    link_entry.grid(row=1, column=0, **grid_options)

    button_frame = tk.Frame(main_window)
    tk.Button(
        button_frame,
        text="Configure",
        command=partial(show_configuration_window, main_window, path_var),
    ).grid(row=0, column=0, sticky=tk.W, **grid_options)
    tk.Button(
        button_frame,
        text="Convert to Video",
        command=partial(on_convert, link_entry, path_var, VIDEO_FORMAT),
    ).grid(row=0, column=1, **grid_options)
    tk.Button(
        button_frame,
        text="Convert to Audio",
        command=partial(on_convert, link_entry, path_var, AUDIO_FORMAT),
    ).grid(row=0, column=2, **grid_options)
    button_frame.columnconfigure(0, weight=1)
    button_frame.grid(row=2, column=0, sticky=tk.EW, **grid_options)

    main_window.protocol("WM_DELETE_WINDOW", partial(on_closing, main_window))
    main_window.mainloop()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
D3us3x
User
Beiträge: 4
Registriert: Montag 30. September 2019, 18:45

Danke für eure Tipps ^^
Ich werde sie mir zu Herzen nehmen!
Antworten