Problem mit pyglet.font und tkinter

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
iainmbc
User
Beiträge: 1
Registriert: Mittwoch 23. November 2022, 12:56

Guten Tag allerseits,

ich arbeite zurzeit an einer Hausaufgabe und dachte schon, ich habe sie fertig, bis ich zu meinen Testszenarien kam.
Seitdem suche ich nach dem Fehler in meinem Code und das ohne Erfolg. Deswegen wende ich mich jetzt an diese Community.
Irgendwas übersehe ich und ich weiß nicht was.

Hier das Problem:
Ich möchte eine nicht installierte ttf-Schrift in meinem Tkinter Programm verwenden. Meine Recherche hat mich zu pyglet font geführt.
Ich habe mir dort das Handbuch und auch die Tipps/Tutorials von Usern angeschaut.

Es hat alles super funktioniert, bis ich testen wollte, ob es wirklich funktioniert, wenn die Schrift nicht installiert ist. Leider funktioniert das Programm danach nicht mehr wie gewünscht.

Funktion des Programmes: In einem Textfenster gibt man Text ein und dieser wird dann in den anderen Textfenster in dem customFont Schriftsatz umgewandelt.
Funktioniert nur solange die Schrift installiert ist.

Dabei gibt pyglet an, dass mit pyglet.font.add_file man auch Fonts nutzen kann, die nicht installiert sind.
Die Font liegt mir als ttf Datei vor und sie liegt im selben Verzeichnis wie meine python Datei.

Da mir mein have_font - Test ein TRUE zurückgibt, würde ich jetzt vermuten, dass ich etwas entweder an der Einrückung, an der Deklaration oder an der Reihenfolge des Codes übersehe.

Hier mein Code:

Code: Alles auswählen

from tkinter import *
import pyglet.font

# Zur Einbindung der customfont
pyglet.font.add_file('WakandaForever_Regular.ttf')
pyglet.font.load('Wakanda Forever')
print(pyglet.font.have_font('Wakanda Forever')) # Gibt TRUE zurück


# Funktion Clear
def clear():
    texteingabe.delete(1.0, END)
    textausgabe.delete(1.0, END)
    pass


# Text übersetzen in wakandanisch
def uebersetzen():
    textausgabe.delete(1.0, END)
    eingabe = texteingabe.get(1.0, END)
    textausgabe.insert(1.0, eingabe)
    pass


root = Tk()
root.title("Translator Wakanda")
root.config(bg="gray17")

# Create outer Frame widget
left_frame = Frame(root, width=300, height=600, bg="MediumPurple1")
left_frame.grid(row=0, column=0, padx=10, pady=5)

right_frame = Frame(root, width=600, height=600, bg="MediumPurple1")
right_frame.grid(row=0, column=1, padx=10, pady=5)

# create left inner Frame widget
# Titel
title_frame = LabelFrame(left_frame, width=250, height=50, bg="snow", relief="flat")
title_frame.grid(row=1, column=0, padx=5, pady=5)
Label(left_frame, text="Übersetzer Wakandanisch", font=("Helvetica", 14, "bold"), background="snow").grid(row=1,
                                                                                                          column=0)

# Wakanda Logo
logo = PhotoImage(file="../test/wakanda.png")
logo_frame = LabelFrame(left_frame, width=250, height=100, bg="snow", relief="flat")
logo_frame.grid(row=2, column=0, padx=5, pady=5)
Label(left_frame, image=logo, background="snow").grid(row=2, column=0)

# Explanation with Aktion Buttons
explanation_frame = LabelFrame(left_frame, width=250, height=415, bg="snow", relief="flat")
explanation_frame.grid(row=3, column=0, padx=5, pady=5)
Label(left_frame, text="Hier könnt ihr euren Text in \nWakandisch übersetzen lassen.", font=("Helvetica", 12),
      background="snow", anchor="n").grid(row=3, column=0, pady=15, sticky='n')

# Buttons
button_frame = Frame(left_frame, background="snow")
button_frame.grid(row=3, column=0)

eingabeButton = Button(button_frame, text="Text übersetzen", command=uebersetzen, bg='goldenrod1', width=20)
eingabeButton.grid(row=3, column=0, pady=5)

clearButton = Button(button_frame, text="Eingabe löschen", command=clear, bg='purple3', width=20)
clearButton.grid(row=4, column=0, pady=5)

# Create right Frame Widgets
texteingabe = Text(right_frame, width=50, height=8, font=("Helvetica", 23))
texteingabe.grid(row=1, column=1, pady=5, padx=5)
texteingabe.insert(END, "Gebe hier deinen Text ein.")

textausgabe = Text(right_frame, width=50, height=15, font=('Wakanda Forever', 16), background="snow")
textausgabe.grid(row=2, column=1, pady=5, padx=5)

root.mainloop()
Vielleicht hat jemand von euch noch eine zündende Idee. Ich würde mich über eine Rückmeldung freuen.

Viele Grüße
Terra
Benutzeravatar
__blackjack__
User
Beiträge: 11265
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@iainmbc: Pyglet hat überhaupt nichts mit Tk oder `tkinter` zu tun. Du hast da eine nicht installierte Schriftart *Pyglet* bekannt gemacht und kannst die in/mit *Pyglet* benutzen. Du benutzt aber gar nicht Pyglet um die GUI zu erstellen, sondern `tkinter`. Die 4—5 Zeilen die mit Pyglet zu tun haben kannst einfach raus löschen, die haben keinen Einfluss auf den Rest des Programms.

Weitere Anmerkungen zum Quelltext: 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.

Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Die beiden Funktionen müssen also `texteingabe` und `textausgabe` als Argumente übergeben bekommen, denn wenn das Hauptprogramm auch in einer Funktion steht, können die da nicht mehr einfach so ”magisch” drauf zugreifen.

Daraus folgt, das eigentlich jede nicht-triviale GUI nicht um objektorientierte Programmierung (OOP) herum kommt, also das schreiben von eigenen Klassen. Bei sehr einfachen GUIs kommt man noch mit `functools.partial()` aus, aber das hat Grenzen.

``pass`` ist eine Anweisung für Fälle wo man keinen Code hat, aber aus syntaktischen Gründen etwas hinschreiben muss. ``pass`` macht also immer nur dann Sinn, wenn es die einzige Anweisung in einem Block ist. Falls dort noch anderer Code steht, dann gehört das da nicht hin.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Bei Funktionen macht es beispielsweise keinen Sinn zu kommentieren das da eine Funktion definiert wird. Und auch den Namen der Funktion muss man nicht noch mal in dem Kommentar stehen haben. ``# Funktion clear`` vor ``def clear(…):`` zu schreiben bringt dem Leser überhaupt nichts.

Der Kommentar bei der anderen Funktion enthält etwas mehr Information und wäre als Docstring geeignet, statt als Kommentar.

`Frame`\s sollte man in der Regel keine Höhe und/oder Breite geben, denn die ergeben sich automatisch aus dem Inhalt. Insgesamt sollte man mit Grössenangaben in Pixeln sehr vorsichtig sein, weil man sich damit in den meisten Fällen Probleme einhandelt falls der Inhalt grösser oder gar deutlich grösser wird. Zum Beispiel weil das Programm auf einem System ausgeführt wird, was eine deutlich abweichende Bildschirmauflösung verwendet.

Es macht keinen Sinn `LabelFrame` zu verwenden und dann keinen Labeltext zu setzen und explizit die `relief`-Option auf FLAT zu setzen, so dass man gar keinen Unterschied zu einem `Frame` mehr sieht.

”Ortsangaben” haben in Namen von Anzeigeelementen in der Regel nichts zu suchen. Wenn die GUI in zwei Bereiche mit unterschiedlichem Inhalt aufgeteilt ist, die in Frames zusammengefasst werden, dann interessiert den Leser nicht ob der Frame rechts oder links angezeigt wird, sondern was die Bedeutung des Inhalts ist. Denn *das* bleibt gleich, auch wenn man sich am Ende entschliesst die beiden Seiten zu vertauschen, oder die Frames untereinander anzuordnen statt nebeneinander.

Man sollte Namen auch nicht zu weit von ihrer Verwendung definieren. `right_frame` wird weit von der Stelle im Code definiert, in der dieser Frame dann endlich mit Inhalt gefüllt ist. Das macht es schwieriger den Zusammenhang zu erkennen, oder auch wenn man die Zeilen liest, wo das verwendet wird, mal schnell zu schauen welche Hintergrundfarbe der Frame hat, weil man dann erst viel weiter oben danach suchen muss. Evenfalls erschwert wird dadurch die Aufteilung von Funktionen die im Laufe der Zeit zu lang geworden sind. Weil man dann nicht einfach einen zusammhängenden Zeilenblock in eine andere Funktion verschieben kann, sondern sich erst einmal über die ganze, zu lang gewordene Funktion die zusammenhörenden Einzelteile zusammensuchen muss. Das macht mehr Arbeit und ist fehleranfälliger.

Einen Frame mit absoluter Grösse und ein Label in die gleiche Grid-Zelle zu packen ist ein unschöner Hack. In aller Regel gehört genau ein Anzeigeelement in eine Grid-Zelle. Den Effekt kann man auch ohne den zusätzlichen Frame in der Zelle erreichen, zumal die absolute Grössenvorgabe da auch nichts zu suchen hat.

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

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial


def clear(texteingabe, textausgabe):
    """
    Leert die Ein- und Ausgabe.
    """
    texteingabe.delete(1.0, tk.END)
    textausgabe.delete(1.0, tk.END)


def uebersetzen(texteingabe, textausgabe):
    """
    Übersetzt den Text aus `texteingabe` in Wakandisch und zeigt ihn in
    `textausgabe` an.
    """
    textausgabe.delete(1.0, tk.END)
    textausgabe.insert(1.0, texteingabe.get(1.0, tk.END))


def main():
    root = tk.Tk()
    root.title("Translator Wakanda")
    root.config(bg="gray17")

    frame = tk.Frame(root, bg="MediumPurple1")
    frame.grid(row=0, column=0, padx=10, pady=5)

    tk.Label(
        frame,
        text="Übersetzer Wakandanisch",
        font=("Helvetica", 14, "bold"),
        background="snow",
    ).grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)

    logo_image = tk.PhotoImage(file="../test/wakanda.png")
    tk.Label(frame, image=logo_image, background="snow").grid(
        row=2, column=0, padx=5, pady=5
    )

    tk.Label(
        frame,
        text="Hier könnt ihr euren Text in\nWakandisch übersetzen lassen.\n",
        font=("Helvetica", 12),
        background="snow",
    ).grid(row=3, column=0, padx=5, pady=15, sticky=tk.NSEW)

    button_frame = tk.Frame(frame, background="snow")
    button_frame.grid(row=4, column=0, padx=5, pady=5, sticky=tk.NSEW)

    translate_button = tk.Button(
        button_frame, text="Text übersetzen", bg="goldenrod1"
    )
    translate_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)

    clear_button = tk.Button(
        button_frame, text="Eingabe löschen", bg="purple3"
    )
    clear_button.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)

    frame = tk.Frame(root, bg="MediumPurple1")
    frame.grid(row=0, column=1, padx=10, pady=5)

    texteingabe = tk.Text(
        frame, width=50, height=8, font=("Helvetica", 23), background="snow"
    )
    texteingabe.grid(row=0, column=0, pady=5, padx=5)
    texteingabe.insert(tk.END, "Gebe hier deinen Text ein.")

    textausgabe = tk.Text(
        frame,
        width=50,
        height=15,
        font=("Wakanda Forever", 16),
        background="snow",
    )
    textausgabe.grid(row=1, column=0, pady=5, padx=5)

    translate_button["command"] = partial(
        uebersetzen, texteingabe, textausgabe
    )
    clear_button["command"] = partial(clear, texteingabe, textausgabe)

    root.mainloop()


if __name__ == "__main__":
    main()
“It should be noted that no ethically-trained software engineer would ever consent to write a `DestroyBaghdad` procedure. Basic professional ethics would instead require him to write a `DestroyCity` procedure, to which `Baghdad` could be given as a parameter.” — Nathaniel Borenstein
__deets__
User
Beiträge: 12829
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: das mit pyglet stimmt so nicht. Ich war da auch zuerst verwirrt, aber letztlich ist das ein workaround der zB in https://stackoverflow.com/questions/119 ... in-tkinter beschrieben ist - weil pyglet im Grunde den Font im System anmeldet, was man sonst zur Not auch ueber ctypes machen koennte. Und dann *sollte* tkinter das finden. Ein Ding koennte der Name sein, laut den Postings hat der nicht unbedingt etwas mit dem Namen der Font-Datei zu tun.

Alternativ ein GUI-Framework benutzen, dass eigene Fonts laden kann, wie pygame oder Qt.
Benutzeravatar
__blackjack__
User
Beiträge: 11265
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Naja, das scheint aber nur unter Windows zu funktionieren und unter Linux nach Kommentaren dort *nicht*. MacOS ist unklar. Ist also in meinen Augen nicht wirklich eine Lösung. Falls doch würde ich da dringend einen Test auf unterstützte Betriebssysteme mit einbauen und das auch deutlich dokumentieren (für den Endbenutzer!), dass es sich um eine Tk-Anwendung handelt die aber *nicht* plattformunabhängig ist.

Edit: Bei den Alternativen hast Du Pyglet vergessen. 🙂
“It should be noted that no ethically-trained software engineer would ever consent to write a `DestroyBaghdad` procedure. Basic professional ethics would instead require him to write a `DestroyCity` procedure, to which `Baghdad` could be given as a parameter.” — Nathaniel Borenstein
Antworten