Label wird nicht angezeigt

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
DJ1NG
User
Beiträge: 4
Registriert: Dienstag 29. Oktober 2024, 19:31

Servus,
  • ich habe an sich eine ganz einfache Geschichte:
  • Ein Fenster wird geöffnet
  • ein label wird erstellt
  • das label wird platziert
  • ein Systembefehl wird ausgeführt (zur Demo durch "pwd" ersetzt
  • es wird 3 Sekunden gewartet
  • Das Fenster schliesst sich selbst
Sinn und Zweck: Eine Sounddatei wird abgespielt über den Systembefehl und solange soll das Fenster anzeigen, dass diese abgespielt wird.

Code: Alles auswählen

from tkinter import *
import os
import time

fenster = Tk()
fenster.config(background = "#00a")
#fenster.attributes("-fullscreen", True)
#fenster.fullScreenState = True
fenster.lift()
label = Label(fenster, text="Soundfile wird abgespielt", fg='white', bg="#00a", font=("Arial", 20))
label.grid(row=0, column=0, padx=0, pady=0)
os.system('pwd') # Stellvertretend für den Aufruf eines Shell-Scripts, nur zur Demo
time.sleep(3) 
fenster.destroy()
Ergebnis:
Das Fenster geht auf (später mal in Fullscreen), wird blau - und KEIN Text wird angezeigt :oops:
Und obwohl der Code aussieht wie andere funktionierende Codes bei mir, wird das Label nicht angezeigt - und ich weiss nicht warum!!!

Kann mir bitte jemand auf die Sprünge helfen?

Danke, Guido
Benutzeravatar
__blackjack__
User
Beiträge: 13535
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Damit eine GUI läuft, muss deren Hauptschleife laufen. Bei Dir ist nicht einmal ein Aufruf von der Tk-Hauptschleife. Und `time.sleep()` geht in dem Kontext auch nicht, denn solange blockiert die GUI. Auch wenn die nichts machen soll, kann man das nicht einfach machen, weil dann das Betriebssystem merkt, dass die Anwendung nicht mehr reagiert, und daraufhin aktiv werden kann. Beispielsweise mit einem Fenster das den Benutzer darauf hinweist, dass die Anwendung nicht mehr reagiert, mit der Nachfrage ob sie beendet werden soll.

Weitere Anmerkungen: Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 140 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

`os.system()` sollte nicht verwendet werden. Die Dokumentation zu der Funktion verweist auf das `subprocess`-Modul. Da kann man dann auch gleich eine Variante programmieren die nicht einfach 3 Sekunden wartet, sondern regelmässig nachsieht, ob der externe Prozess fertig ist. Das macht man mit der `after()`-Methode die es auf jedem Tk-Widget gibt.

GUI-Programmierung setzt für jedes nicht-triviale Programm objektorientierte Programmierung voraus. Also eigene Klassen schreiben zu können, weil man sich Zustand über Aufrufe hinweg merken muss. In einfachen Fällen kann man den noch bei den Aufrufen an die Rückruffunktionen durchreichen — da ist `functools.partial()` oft hilfreich — aber dieses Vorgehen hat Grenzen.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

dir fehlt die `mainloop`, die die GUI-Elemente immer wieder aktualisiert. Dann erreichst du aber deine `destroy()` - Methode nicht mehr. Man darf bei GUI nicht einen linearen Programmablauf im Kopf haben, Änderungen passieren durch Ereignisse.

`tkinter` kennt eine `after` - Methode, die erlaubt in vorgegebenen Zeitabständen Funktionen auszuführen. Im ersten Moment würde ich das vermutlich nutzen um das Fenster nach einer Zeit wieder zu schließen. Allerdings ist es notwendig, dass du weist wie Klassen und Funktionen funktionieren.

Code: Alles auswählen

import tkinter as tk

# in ms
TIME_UNTIL_CLOSE = 3000


class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.config(background="#00a")
        tk.Label(
            self,
            text="Soundfile wird abgespielt",
            fg="black",
            bg="#00a",
            font=("Arial", 20),
        ).grid(row=0, column=0, padx=0, pady=0)
        self.exit = False
        self._countdown_to_close()

    def _countdown_to_close(self):
        if self.exit:
            self.master.destroy()
        else:
            self.exit = True
        self.after(TIME_UNTIL_CLOSE, self._countdown_to_close)


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


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
DJ1NG
User
Beiträge: 4
Registriert: Dienstag 29. Oktober 2024, 19:31

Ich versuche gerade Beispiel aus dem Netz nachzuvollziehen, und habe dabei gefunden, dass man eine Loop nach der Ausführung eines "idleTask" beenden könne.

Daher sieht mein Code jetzt so aus:

Code: Alles auswählen

from tkinter import *
import os
import time

def idleTask():
    os.system('aplay morse_sos.wav')

fenster = Tk()
fenster.config(background = "#00a")
#fenster.attributes("-fullscreen", True)
#fenster.fullScreenState = True
fenster.lift()
label = Label(fenster, text="HILFERUF läuft!!!", fg='white', bg="#00a", font=("Arial", 20))
label.grid(row=0, column=0, padx=0, pady=0)

fenster.after_idle(idleTask)
fenster.mainloop()
Das Ergebnis:
1. Das Fenster wird blau und SOS wird abgespielt.
2. Danach wird der Text vom Label angezeigt und ich muss das Fenster doch wieder gewaltsam schliessen.

Nochmal zur Info, mein gewünschter Ablauf:
1. Fenster öffnen
2. Label "Hilferuf läuft!!!" anzeigen
3. Sound abspielen
4. Fenster schliesst sich von selbst

Für heute muss ich leider aufgeben, da meine Augen gerade schlapp machen. Aber ich würde mich extrem freuen, wenn mir jemand eine Hilfestellung gibt in dieser Angelegenheit.

Have a gud n8,
Guido
DJ1NG
User
Beiträge: 4
Registriert: Dienstag 29. Oktober 2024, 19:31

Dennis89 hat geschrieben: Dienstag 29. Oktober 2024, 20:49 `tkinter` kennt eine `after` - Methode, die erlaubt in vorgegebenen Zeitabständen Funktionen auszuführen. Im ersten Moment würde ich das vermutlich nutzen um das Fenster nach einer Zeit wieder zu schließen. Allerdings ist es notwendig, dass du weist wie Klassen und Funktionen funktionieren.
Gerade noch gesehen: Danke

Aber leider, wenn ich nach dem Label einfüge

Code: Alles auswählen

os.system('aplay morse_sos.wav')
dann wird zuerst die WAV-Datei abgespielt und DANN das Fenster eröffnet mit der Nachricht "Soundfile läuft".

Der Text soll aber unbedingt während das Abspielens zu lesen sein und danach verschwinden - quasi als Blockade für den User, damit er weiss: Jetzt läuft's. Denn das Soundfile wird später über ein Funkgerät ausgestrahlt, und der Benutzer bekommt das nicht mit. Daher brauche ich eine "optische Signalisierung und Sperre", solange der Sound gesendet wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13535
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DJ1NG: `os.system()` ist halt doppelt falsch. Man benutzt das nicht, wie ich schon geschrieben habe, *und* es blockiert, genau wie `time.sleep()`, was in einer GUI nicht passieren darf, solange die GUI nicht reagieren kann.

Auf die anderen Anmerkungen könntest Du auch mal eingehen. Also Sternchen-Import, `main()`-Funktion, und so weiter.

Ich würde da auch nicht eine Feste Zeit warten, sondern auf das Ende des externen Programms warten, indem in regelmässigen Abständen per `after()` eine Funktion/Methode aufgerufen wird, die testet ob das externe Programm noch läuft. Im `subprocess`-Modul gibt es einen Datentyp der einen externen Prozess starten kann, und wo man das Objekt dann fragen kann ob der noch läuft, oder schon fertig ist.

`after_idle()` kann man verwenden um das anzustossen, und wenn man keine Klasse schreiben will, kann man sowohl `after_idle()` als auch `after()` noch weitere Argumente mitgeben, die dann beim Rückruf an die Rückruffunktion durchgereicht werden.

Namen werden in Python übrigens per Konvention klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
__blackjack__
User
Beiträge: 13535
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from subprocess import Popen

BACKGROUND_COLOUR = "#00a"


def check_for_end(widget, audio_process):
    if audio_process.poll() is None:
        widget.after(500, check_for_end, widget, audio_process)
    else:
        widget.quit()


def main():
    fenster = tk.Tk()
    fenster.config(background=BACKGROUND_COLOUR)
    # fenster.attributes("-fullscreen", True)
    fenster.lift()
    tk.Label(
        fenster,
        text="HILFERUF läuft!!!",
        foreground="white",
        background=BACKGROUND_COLOUR,
        font=("Arial", 20),
    ).grid(row=0, column=0)
    fenster.after_idle(
        lambda: check_for_end(fenster, Popen(["aplay", "morse_sos.wav"]))
    )
    fenster.mainloop()


if __name__ == "__main__":
    main()

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
DJ1NG
User
Beiträge: 4
Registriert: Dienstag 29. Oktober 2024, 19:31

__blackjack__ hat geschrieben: Mittwoch 30. Oktober 2024, 08:33 Ungetestet:
Yippiejahjahyippiehyippieyeah

Mercie vielmohls!
Antworten