Seite 1 von 1

Befehl aus TKinter funktioniert nicht?

Verfasst: Dienstag 13. Februar 2024, 18:38
von RandinAndy
Hallo,
ich schreibe gerade mein erstes komplexeres Programm mit GUI
eigentlich relativ simpel, allerdings hänge ich gerade an einer absolut trivialen Stelle :?
In meinem GUI soll standartmäßig ein Text der in ein Feld eingegeben wurde nach Bestätigung in einer Liste ausgegeben werden. (code folgt am Ende)
Leider Wird langer Text fortlaufend und ohne Zeilenumbruch ausgegeben (achja, es sind auch eigentlich 2 Listen, eine im Startfenster und eine in der Alle Daten zusammengefasst ausgegeben werden)
Aus der Tkinter Bibliothek habe ich dafür den Befehl wrap=tk.WORD
Auf der Suche hier im Forum habe ich beim einzigen Beitrag zu dem Thema die Aussage gefunden, dass Tkinter widgets einen Zeilenumbruch automatisch machen...
Naja irgendwie klappt bei mir beides nicht :roll:
Stehe ich gerade so sehr auf dem Schlauch???

Hier mein Code:
1.:

Code: Alles auswählen

    
    # So steht der Abschnitt gerade in meinem Code
    	def create_title_and_description_cells(self):
        self.title_entry = tk.Entry(self.master, font=("Helvetica", 12), fg="grey")
        self.title_entry.insert(0, "Titel")
        self.title_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.title_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.title_entry.pack(pady=5)

        self.description_entry = tk.Entry(self.master, font=("Helvetica", 12), fg="grey")
        self.description_entry.insert(0, "Beschreibung")
        self.description_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.description_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.description_entry.pack(pady=5)
        
2.

Code: Alles auswählen

# so meine ich sollte er aussehen
	def create_title_and_description_cells(self):
        self.title_entry = tk.Entry(self.master, font=("Helvetica", 12), wrap=tk.WORD, fg="grey")
        self.title_entry.insert(0, "Titel")
        self.title_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.title_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.title_entry.pack(pady=5)

        self.description_entry = tk.Entry(self.master, font=("Helvetica", 12), wrap=tk.WORD, fg="grey")
        self.description_entry.insert(0, "Beschreibung")
        self.description_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.description_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.description_entry.pack(pady=5)
        
3.

Code: Alles auswählen

import tkinter as tk
from datetime import datetime


class TimeTrackerApp:
    def __init__(self, master):
        self.master = master
        master.title("Time Tracker")

        window_width = 360
        window_height = 640
        master.geometry(f"{window_width}x{window_height}")

        self.clock_label = tk.Label(master, text="", font=("Helvetica", 24))
        self.clock_label.pack(pady=10)

        button_frame = tk.Frame(master)
        button_frame.pack(pady=10)

        self.start_button = tk.Button(button_frame, text="Start", font=("Helvetica", 14), width=8, height=2,
                                      command=self.start_button_clicked)
        self.start_button.grid(row=0, column=0, padx=10)
        self.start_button.config(bg="green")

        self.save_button = tk.Button(button_frame, text="Save", font=("Helvetica", 14), width=8, height=2,
                                     command=self.save_button_clicked, state=tk.DISABLED)
        self.save_button.grid(row=0, column=1, padx=10)
        self.save_button.config(bg="yellow")

        self.end_button = tk.Button(button_frame, text="Ende", font=("Helvetica", 14), width=8, height=2,
                                    command=self.end_button_clicked, state=tk.DISABLED)
        self.end_button.grid(row=0, column=2, padx=10)
        self.end_button.config(bg="red")

        self.transfer_button = tk.Button(master, text="Bestätigen", font=("Helvetica", 14), width=8, height=2,
                                         command=lambda: [self.transfer_text(), self.transfer_button.pack_forget()])
        self.transfer_button.pack(pady=10)

        self.entries = []
        self.arbeitsbeginn_entries = []
        self.start_button_clicked_once = False

        self.update_clock()


    def start_button_clicked(self):
        if not self.start_button_clicked_once:
            start_time = datetime.now()
            if not any(entry[1] == "Arbeitsbeginn" for entry in self.entries):
                self.arbeitsbeginn_entries.append((start_time, "Arbeitsbeginn", ""))
                self.update_arbeitsbeginn_list_display()
            self.update_time_list_display()
            self.start_button.config(state=tk.DISABLED)
            self.save_button.config(state=tk.NORMAL)
            self.start_button_clicked_once = True

    def save_button_clicked(self):
        end_time = datetime.now()
        elapsed_time = end_time - self.arbeitsbeginn_entries[-1][0]
        self.entries.append((end_time, "Dauer", str(elapsed_time)))
        self.save_button.config(state=tk.DISABLED)
        self.end_button.config(state=tk.NORMAL)
        self.create_title_and_description_cells()
        self.transfer_button.pack()
        self.transfer_button.config(state=tk.NORMAL)

    def end_button_clicked(self):
        popup_window = tk.Toplevel(self.master)
        popup_window.title("Liste der Aktivitäten")

        activity_list = tk.Listbox(popup_window, font=("Helvetica", 12))

        for entry in self.entries:
            if entry[1] != "Dauer":
                if isinstance(entry[0], datetime):
                    activity_list.insert(tk.END, f"{entry[1]}: {entry[2]} ({entry[0].strftime('%H:%M:%S')})")
                else:
                    activity_list.insert(tk.END, f"{entry[1]}: {entry[2]}")

        for i in range(len(self.entries) - 1):
            if self.entries[i][1] != "Dauer" and self.entries[i + 1][1] != "Dauer":
                start_time = self.entries[i][0]
                end_time = self.entries[i + 1][0]
                duration = end_time - start_time
                activity_list.insert(tk.END, f"Zeitspanne: {duration}")

        activity_list.pack(padx=10, pady=10)

    def update_clock(self):
        current_time = datetime.now().strftime("%H:%M:%S")
        self.clock_label.config(text=current_time)
        self.master.after(1000, self.update_clock)

    def update_time_list_display(self):
        if (self.entries):
            entry = self.entries[-1]
            if isinstance(entry[0], datetime):
                time_str = entry[0].strftime("%H:%M")
                text = f"{entry[1]}: {time_str}"
            else:
                text = f"{entry[1]}: {entry[2]}"
            tk.Label(self.master, text=text, font=("Helvetica", 12)).pack()

    def create_title_and_description_cells(self):
        self.title_entry = tk.Entry(self.master, font=("Helvetica", 12), fg="grey")
        self.title_entry.insert(0, "Titel")
        self.title_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.title_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.title_entry.pack(pady=5)

        self.description_entry = tk.Entry(self.master, font=("Helvetica", 12), fg="grey")
        self.description_entry.insert(0, "Beschreibung")
        self.description_entry.bind("<FocusIn>", self.on_entry_focus_in)
        self.description_entry.bind("<FocusOut>", self.on_entry_focus_out)
        self.description_entry.pack(pady=5)

    def transfer_text(self):
        title = self.title_entry.get().strip()
        description = self.description_entry.get().strip()

        if title and description:
            if not any((entry[1], entry[2]) == (title, description) for entry in self.entries):
                self.entries.append(("", title, description))
                self.update_time_list_display()

        self.transfer_button.config(state=tk.DISABLED)
        self.title_entry.pack_forget()
        self.description_entry.pack_forget()
        self.save_button.config(state=tk.NORMAL)

    def on_entry_focus_in(self, event):
        if event.widget == self.title_entry and self.title_entry.get() == "Titel":
            self.title_entry.delete(0, tk.END)
            self.title_entry.config(fg="black")
        elif event.widget == self.description_entry and self.description_entry.get() == "Beschreibung":
            self.description_entry.delete(0, tk.END)
            self.description_entry.config(fg="black")


    def on_entry_focus_out(self, event):
        if event.widget == self.title_entry and self.title_entry.get() == "":
            self.title_entry.insert(0, "Titel")
            self.title_entry.config(fg="grey")
        elif event.widget == self.description_entry and self.description_entry.get() == "":
            self.description_entry.insert(0, "Beschreibung")
            self.description_entry.config(fg="grey")

    def update_arbeitsbeginn_list_display(self):
        for entry in self.arbeitsbeginn_entries:
            if isinstance(entry[0], datetime):
                time_str = entry[0].strftime("%H:%M")
                text = f"{entry[1]}: {time_str}"
            else:
                text = f"{entry[1]}: {entry[2]}"
            tk.Label(self.master, text=text, font=("Helvetica", 12)).pack()


def main():
    root = tk.Tk()
    app = TimeTrackerApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()
    
    
Danke für jeden Hinweis und LG
A

Re: Befehl aus TKinter funktioniert nicht?

Verfasst: Freitag 16. August 2024, 14:19
von Sven_TKLOOP
Du nutzt in deinem Code tk.Entry, Entry ist aber nur für kürze Texte gedacht.
Für richtige Texte gibt es tk.Text.
Tausche tk.Entry einfach gegen tk.Text aus, damit du mehrzeilige Eingaben unterstützen kannst.

tk.Text bietet einfach viel mehr funktionen als tk.Entry

Als Beispiel könntest du folgendes ändern:

Code: Alles auswählen

self.title_entry = tk.Entry(self.master, font=("Helvetica", 12), fg="grey")
self.title_entry.insert(0, "Titel")
zu ->

Code: Alles auswählen

self.title_entry = tk.Text(self.master, height=1, font=("Helvetica", 12), wrap=tk.WORD, fg="grey")
self.title_entry.insert(tk.END, "Titel")

Re: Befehl aus TKinter funktioniert nicht?

Verfasst: Montag 19. August 2024, 15:50
von __blackjack__
@RandinAndy: Anmerkungen zum Quelltext:

`TimeTracker` sollte auf `master` keine Methoden aufrufen die es nur auf Toplevel-Widgets gibt, oder man sollte das nicht einfach so generisch `master` nennen als wenn jedes Container-Widget da funktionieren würde. `title()` kann man auch ausserhalb aufrufen und `geometry()` sollte man gar nicht aufrufen weil…
Bild
… absolute Pixelgrössen funktionieren nicht wirklich.

Wenn die Zeit jede Sekunde aktualisiert werden soll, würde ich das jede halbe Sekunde machen, denn wenn man das jede Sekunde macht, kann es manchmal passieren, dass die Anzeige mal eine Sekunde überspringt wenn die Aktualisierung gerade um die Sekundenwechsel herum ausgelöst wird. `after()` ist da ja nicht 100% genau.

Ich würde weder die Schriftart noch die Schriftgrösse hart vorgeben. So etwas überlässt man dem Benutzer, der das normalerweise auf Systemebene für alle Programme macht. Es ist nervig wenn dann einzelne Programme anfangen das besser wissen zu wollen.

Irgendwie ist das ganze komisch. Ich verstehe nicht was das Programm machen soll, was Start, Save, Ende, und Bestätigen eigentlich machen sollen.

Es gibt eine Liste `self.arbeitsbeginn_entries` wo ich gerade nicht sehe wie da jemals mehr als ein Element drin stehen sollte, also warum ist das eine Liste?

Es wird im Code geprüft ob das erste Element von den Tupeln in `arbeitsbeginn_entries` vom Typ `datetime` ist — ja, ist es. *Immer*. Solche Typtests sollte man aber auch eigentlich gar nicht machen, das riecht komisch in einem objektorientierten Programm.

Die beiden Listen mit den Dreiertupeln und dem magischen Indexzugriffen sollte nicht sein, weil man das nur schwer versteht was in diesen Tupeln eigentlich an Information gespeichert ist. Vor allem wenn dann noch Code da ist der die Typen prüft und dann je nach Typ was anderes mit dem Inhalt macht.

Die Tupel wären besser Objekte mit Attributen die sinnvoll beschreiben was der Wert bedeutet.

Und die Programmlogik könnte man auch von der GUI trennen. Dann kann man die besser erkennen und auch separat testen und/oder verwenden.

Der Code der nach "Arbeitsbeginn" in allen Einträgen sucht ist komisch. Denn "Arbeitsbeginn" kann und muss ja doch immer der erste Eintrag in so einer Liste sein, oder nicht?

Und das mit den Texten wie "Arbeitsbeginn" und "Dauer" auf die reagiert wird, ist problematisch, weil der Benutzer das ja auch als Titel eingeben kann und damit das Programm durcheinanderbringen kann. Oder *muss* der Benutzer da Beispielsweise "Dauer" eingeben, damit das verarbeitet wird? Dann ist es Fehleranfällig, weil man sich ja leicht mal vertippen kann.

`on_entry_focus_in()` und `on_entry_focus_out()` skalieren so nicht. Da müsste man ja für jedes weitere Entry-Widget was man zu so einem Datensatz packen möchte, weiteren Code schreiben, der letztlich immer nach dem gleichen Muster ist. Dafür würde man sich eine eigene Klasse von `Entry` ableiten wo man den Code für den Platzhalter-Text *einmal* schreibt.

Wenn ich das richtig sehe erkennt der Code auch nicht richtig wenn man als Inhalt dort den Platzhalter-Text eintippt.