Hallo,
ich will in einer (einem?) GUI ein Entry anbieten, in das ein Spielername eingegeben werden kann. Zur Zeit wird auf 8 Zeichen begrenzt. Soweit klappt auch alles.
Allerdings hat das Ausgabefeld nur eine bestimmte Breite. Das Feld sollte möglichst gut ausgenutzt werden können.
Auf Grund unterschiedlicher Buchstabenbreiten kann ja nun eine unterschiedliche Stringlänge dargestellt werden. Wie ermittle ich die grafische Breite des Strings?
Im Ergebnis gäbe es zwei Möglichkeiten zu reagieren. Entweder die Schriftgröße soweit verringern, dass der String in das Ausgabefeld passt oder die Anzahl der Zeichen in Abhängigkeit von den verwendeten Buchstaben soweit begrenzen, dass mit einer festen Schriftgröße der eingegeben String vollständig dargestellt werden kann.
Hat jemand eine Idee?
Schriftbreite im Eingabefeld ermitteln
Die aktuell 8 Zeichen habe ich nur eingebaut, um erstmal die meisten Eingaben abfangen und den eingegebenen String im Ausgabefeld halbwegs vernünftig darstellen zu können. Das klappt auch in wahrscheinlich 90 % der Fälle. Extremabweichungen wie "WWWWWWWW" oder "iiiiiiii" sehen aber unschön aus bzw. passen dann doch nicht.
Im Idealfall würde ich wahrscheinlich eine feste Anzahl Zeichen zulassen und den String dann über Scalierung der Schriftgröße an das Ausgabefeld anpassen wollen.
Ich habe inzwischen auch schon was dazu gefunden, mit dem man wohl die Breite in Pixeln ermitteln kann. Stichwort font.measure(text). Ich muss jetzt halt nur noch schauen, wie man das am besten anwendet.
Das mit dem rumzappeln würde ich hinnehmen, bzw. ist denke ich überschaubar.
Im Idealfall würde ich wahrscheinlich eine feste Anzahl Zeichen zulassen und den String dann über Scalierung der Schriftgröße an das Ausgabefeld anpassen wollen.
Ich habe inzwischen auch schon was dazu gefunden, mit dem man wohl die Breite in Pixeln ermitteln kann. Stichwort font.measure(text). Ich muss jetzt halt nur noch schauen, wie man das am besten anwendet.
Das mit dem rumzappeln würde ich hinnehmen, bzw. ist denke ich überschaubar.
Tu was du nicht lassen kannst. Als Anwender (besonders mit mobilen Geräten) kotze ich im Strahl. Du legst hier einen seltsamen Sinn für Ästhetik an. Ein Eingabeelement, das sich bei Benutzung verändert ist in meinen Augen ein no Go.
Wie bereits beschrieben, werde ich wahrscheinlich nicht die Eingabe, sondern die Ausgabe anpassen. Bei der Eingabe wird nur die Länge begrenzt bzw. die Eingabe weiterer Zeichen verhindert. Soll auch keine App fürs Handy werden, sondern wird für eine Anwendung auf einem Rasp genutzt werden.
Hätt ja sein können, dass jemand schon eine Idee bzw. ein Beispiel hätte, wie man die Pixelbreite eines Strings ermittelt.
Hätt ja sein können, dass jemand schon eine Idee bzw. ein Beispiel hätte, wie man die Pixelbreite eines Strings ermittelt.
Hi suk
Habe hier etwas zusammengebastelt:Gruss wuf
Habe hier etwas zusammengebastelt:
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.font as fnt
APP_TITLE = "String Pixle Length"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 700
APP_HEIGHT = 200
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title(APP_TITLE)
self.build()
def build(self):
self.entry_font = fnt.Font(family='Helvetica', size=10, weight='bold')
self.label_font = fnt.Font(family='Helvetica', size=18, weight='bold')
self.protocol("WM_DELETE_WINDOW", self.close_app)
self.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
self.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
self.option_add("*Button.highlightThickness", 0)
self.main_frame = tk.Frame(self, bg='yellow', bd=0)
self.main_frame.pack(side='left', fill='both', expand=True)
self.entry_container = tk.Frame(self.main_frame, bd=0)
self.entry_container.pack(expand=True)
self.entry_container.propagate(False)
self.entry_var = tk.StringVar()
self.entry = tk.Entry(self.entry_container, font=self.entry_font,
textvariable=self.entry_var, highlightthickness=0, bd=0)
self.entry.pack(fill='x')
self.label_container = tk.Frame(self.main_frame, bd=0)
self.label_container.pack(expand=True)
self.label_container.propagate(False)
self.label_var = tk.StringVar()
self.label = tk.Label(self.label_container, textvariable=self.entry_var,
font=self.label_font, bg='white')
self.label.pack()
self.entry_var.trace('w', self.var_callback)
self.entry_string = "WWWWWWWW" #"iiiiiiii" #
self.entry_var.set(self.entry_string)
def var_callback(self, *args):
string_length = self.entry_font.measure(self.entry_var.get())
string_height = self.entry_font.metrics('linespace')
self.entry_container.config(width=string_length+2, height=string_height)
string_length = self.label_font.measure(self.entry_var.get())
string_height = self.label_font.metrics('linespace')
self.label_container.config(width=string_length+2, height=string_height)
def close_app(self):
# Here do something before apps shutdown
print("Good Bye!")
self.destroy()
Application().mainloop()
Take it easy Mates!
Danke wuf ...
Habe inzwischen auch ein *schnipplet* gefunden und mit diesem mal selber ein bisschen probiert.
Ist zwar sicher nicht sehr sauber programmiert, sollte jedoch die Funktion etwas veranschaulichen.
Habe inzwischen auch ein *schnipplet* gefunden und mit diesem mal selber ein bisschen probiert.
Ist zwar sicher nicht sehr sauber programmiert, sollte jedoch die Funktion etwas veranschaulichen.
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
### Import der erforderlichen Module ###
try:
# Tkinter for Python 2.xx
import Tkinter as tk
except ImportError:
# Tkinter for Python 3.xx
import tkinter as tk
from functools import partial
import tkinter.font as tkf
### Fensterdefinition ###
APP_TITLE = "Template_Canvas"
APP_WIDTH = 600
APP_HEIGHT = 300
### Inhalt Canvas-Applikation ###
class App_Canvas():
def __init__(self, canvasmaster):
self.mycanvas = canvasmaster
# print(self.mycanvas)
self.measure_beispiel("yellow world")
self.measure_anwendung()
### Beispiel für eine Berechnung pixelgenaue Textbreite und -höhe
def measure_beispiel(self, text):
(x,y) = (20,20)
fonts = []
for (family, size) in [("Arial", 20), ("Times", 20)]:
font = tkf.Font(family=family, size=size)
(breite, hoehe) = (font.measure(text), font.metrics("linespace"))
print("%s %s: (%s,%s)" % (family, size, breite, hoehe))
self.mycanvas.create_text(x,y,text="_%s_" % (text), font=(family, size),anchor="nw")
self.mycanvas.create_text(x,y + hoehe + 5,text="%s: BxH=%s" % ((family, size), (breite, hoehe)),font=("Arial", 10),anchor="nw")
y += hoehe + 25
### und ein Beispiel für die Nutzung in einer Anwendung
def measure_anwendung(self):
self.text = "Name 1"
self.a_x = 150
self.a_y = 100
self.a_font = "Arial"
self.a_font_size = 15
obj_frame = tk.Frame(self.mycanvas, background="gray", bd=3, relief="groove", width=300, height=150)
self.eingabefeld = tk.Entry(obj_frame, relief = "sunken", borderwidth=2, font=("Arial", 15))
self.eingabefeld.insert(0, self.text)
self.eingabefeld.select_range(0, len(self.eingabefeld.get()))
self.eingabefeld.focus()
self.eingabefeld.place(x=10, y=5, width=270, height=30)
self.eingabefeld.bind("<KeyRelease>", self.event_keyrelease)
self.eingabefeld.bind("<FocusOut>", self.event_lostfocus)
self.ausgabefeld = tk.Label(obj_frame, relief = "groove", font=(self.a_font, self.a_font_size), bg="lightgray")
self.ausgabefeld.place(x=10, y=40, width=self.a_x, height=self.a_y)
self.mycanvas.create_window(20, 150, window=obj_frame, anchor="nw")
def event_keyrelease(self, event):
# print(event.keycode)
maxchar = 20
if (int(len(self.eingabefeld.get()) > maxchar)):
# Länge auf maximale Anzahl Zeichen zurücksetzen
print("maximale Anzahl Zeichen erreicht")
self.eingabefeld.delete(maxchar, len(self.eingabefeld.get()))
# self.eingabefeld.xview_moveto(0)
if (event.keycode == 9):
# ESC-Taste
print("Abbruch durch Escape")
self.ausgabefeld.config(text = "")
self.eingabefeld.config(text = self.eingabefeld.get())
if (event.keycode == 36):
# Enter-Taste
print("\neingegebener Text ist: %s" % (self.eingabefeld.get()))
# anpassen der Schriftgröße
var_font = tkf.Font(family=self.a_font, size=int(self.a_font_size))
var_text = self.eingabefeld.get()
# textmaße ermitteln
var_textlaenge = var_font.measure(var_text)
var_texthoehe = var_font.metrics("linespace")
# Verhältnis zum ausgabefeld ermitteln
var_verhaeltnis = self.a_x / var_textlaenge
print("Verhältnis Textfeldbreite zu Textbreite: {} / {} = {}".format(self.a_x, var_textlaenge, var_verhaeltnis))
# maximale schriftgröße ermitteln, so dass der Text in das Ausgabefeld passen würde
var_font_size_neu = int(self.a_font_size * var_verhaeltnis * 0.95)
print("Schriftgröße alt: {}, neu: {}".format(self.a_font_size, var_font_size_neu))
var_font_neu = tkf.Font(family=self.a_font, size=var_font_size_neu)
print("neu: (Textbreite, Textfeldbreite) (Texthöhe, Textfeldhöhe): ({}, {}) ({}, {})".format(var_font_neu.measure(var_text), self.a_x, var_font_neu.metrics("linespace"), self.a_y))
# wenn Ergenbis Texthöhe zu hoch, Schriftgröße nochmal verringern
if (var_font_neu.metrics("linespace") > self.a_y):
var_verhaeltnis = self.a_y / var_font_neu.metrics("linespace")
print("Verhältnis Textfeldhöhe zu Texthöhe: {} / {} = {}".format(self.a_y, var_font_neu.metrics("linespace"), var_verhaeltnis))
var_font_size_neu = int(var_font_size_neu * var_verhaeltnis)
print("Schriftgröße alt: {}, neu: {}".format(self.a_font_size, var_font_size_neu))
var_font_neu = tkf.Font(family=self.a_font, size=var_font_size_neu)
print("neu: (Textbreite, Textfeldbreite) (Texthöhe, Textfeldhöhe): ({}, {}) ({}, {})".format(var_font_neu.measure(var_text), self.a_x, var_font_neu.metrics("linespace"), self.a_y))
self.ausgabefeld.config(text = self.eingabefeld.get(), font = var_font_neu)
self.text = self.eingabefeld.get()
def event_lostfocus(self, event):
# Eingabefeld hat Focus verloren
print("Abruch durch Focusverlust")
self.ausgabefeld.config(text = "")
### Inhalt Frame-Applikation ###
class App_Frame1():
def __init__(self, obj_frame):
self.myframe = obj_frame
### Applikationsstart ###
def main():
win_master = tk.Tk()
win_master.title(APP_TITLE)
app_canvas = tk.Canvas(win_master, width=APP_WIDTH, height=APP_HEIGHT, relief="groove", bg="white", bd=5)
app_canvas.pack()
# app_frame = tk.Frame(win_master, width=APP_WIDTH, height=APP_HEIGHT, relief="sunken", bg="gray", bd=3)
# app_frame.pack()
button1 = tk.Button(win_master, text="close", command=win_master.destroy)
button1.pack()
App_Canvas(app_canvas)
# App_Frame(app_frame)
win_master.mainloop()
if __name__ == '__main__':
main()
Hi suk
Dein Skript war sehr schwer zu lesen obwohl es in Python geschrieben ist. Konnte aber mit Hilfe des Skripts besser verstehen was du eigentlich erreichen möchtest. Habe hier meine eigene Variante erstellt:Gruss wuf
Dein Skript war sehr schwer zu lesen obwohl es in Python geschrieben ist. Konnte aber mit Hilfe des Skripts besser verstehen was du eigentlich erreichen möchtest. Habe hier meine eigene Variante erstellt:
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.font as tkf
APP_TITLE = "TkTemplate_Small"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 600
APP_HEIGHT = 310
class Application(tk.Canvas):
def __init__(self, app_win, **kwargs):
self.app_win = app_win
app_win.protocol("WM_DELETE_WINDOW", self.close_app)
tk.Canvas.__init__(self, app_win, **kwargs)
def build(self):
self.measure_samples("yellow world")
self.output_font = tkf.Font(family='Helvetica', size=10)
# Subframe
self.entry_frame = tk.Frame(self, bg='gray', bd=3, relief="groove",
width=300, height=150)
# Eingabefeld
self.entry_var = tk.StringVar()
self.max_char = 8
self.entry_var.trace("w", self.entry_callback)
self.entry = tk.Entry(self.entry_frame, relief = "sunken", bd=2,
textvariable=self.entry_var, font=("Arial", 15))
self.entry.place(x=10, y=5, width=270, height=30)
self.entry_var.set("Name1")
self.entry.select_range(0, len(self.entry.get()))
self.entry.focus()
self.entry.bind("<KeyRelease>", self.event_key_release)
self.entry.bind("<FocusOut>", self.event_lost_focus)
# Ausgabefeld
label_width = 150
label_height = 100
self.label_var = tk.StringVar()
self.label_display = tk.Label(self.entry_frame, relief="groove", bd=2,
font=('Arial', 15), bg="lightgray",
textvariable=self.label_var)
self.label_display.place(x=10, y=40, width=label_width,
height=label_height)
self.create_window(20, 150, window=self.entry_frame, anchor="nw")
tk.Button(self.app_win, text="close",
command=self.app_win.destroy).pack()
def entry_callback(self, *args):
char = self.entry_var.get()[0:self.max_char]
print("c=" , char)
self.entry_var.set(char)
def event_key_release(self, event):
if event.keysym == 'Return':
label_width = self.label_display.winfo_width()
entry_text = self.entry_var.get()
output_text_width = self.output_font.measure(entry_text)
if output_text_width < label_width:
adjust = 'larger'
else:
adjust = 'smaller'
while True:
output_text_width = self.output_font.measure(entry_text)
font_size = self.output_font.cget('size')
if adjust == 'larger':
if output_text_width < label_width:
font_size += 1
else:
break
elif adjust == 'smaller':
if output_text_width > label_width:
font_size -= 1
else:
break
self.output_font.configure(size=font_size)
print(label_width, output_text_width, font_size)
self.label_display.config(font=self.output_font)
self.label_var.set(entry_text)
def event_lost_focus(self, event):
print('Lost Focus')
def measure_samples(self, text):
xpos, ypos = (20,20)
fonts = (
tkf.Font(family='Helvetica', size=20),
tkf.Font(family='Times', size=20))
for font in fonts:
font_fam, size, breite, height = (
font.cget('family'),
font.cget('size'),
font.measure(text),
font.metrics("linespace"),
)
self.create_text(xpos, ypos, text="_{}_".format(text), font=font,
anchor="nw")
self.create_text(xpos, ypos + height + 5,
text="{} {}: BxH=({},{})".format(font_fam, size, breite, height),
font=("Arial", 10),anchor="nw")
ypos += height + 25
def close_app(self):
# Here do something before apps shutdown
print("Good Bye!")
self.app_win.destroy()
def main():
app_win = tk.Tk()
app_win.title(APP_TITLE)
app_win.option_add("*highlightThickness", 0)
app = Application(app_win, width=APP_WIDTH, height=APP_HEIGHT,
relief="groove", bg="white", bd=2)
app.pack()
app.build()
app_win.mainloop()
if __name__ == '__main__':
main()
Take it easy Mates!
Hallo wuf,
Schon mal Danke für den Vorschlag zum Umbau.
Die while-Schleife scheint mir jedoch bedenklich zu sein.
Zum einen ist sie recht langsam und außerdem hängt sie sich auf, wenn keine Eingabe erfolgt.
Dem werde ich wahrscheinlich die Berechnung per Verhältnis vorziehen.
Die Berücksichtigung der Höhe müsste man auch noch beachten.
Da ich noch recht neu mit Python unterwegs bin, habe ich noch einige Wissenslücken.
Was macht self.entry_var.trace ? Wie bekommst Du es hin, dass ein eingegebenes Zeichen größer max_char gar nicht erst angezeigt wird?
Ich habe außerdem noch ein Verständnisproblem zu Euren Programmstrukturen ...
Wenn ich es richtig verstanden habe, ist app_win eine Instanz von tk.Tk und sollte alle Module kennen.
self.app_win ist eine Referenz auf app_win.
Warum muss tk.Canvas über die Klassendefinition noch zusätzlich vererbt werden? Eigentlich müsste die Klasse doch Canvas über app_win bereits kennen?!
Schon mal Danke für den Vorschlag zum Umbau.
Die while-Schleife scheint mir jedoch bedenklich zu sein.
Zum einen ist sie recht langsam und außerdem hängt sie sich auf, wenn keine Eingabe erfolgt.
Dem werde ich wahrscheinlich die Berechnung per Verhältnis vorziehen.
Die Berücksichtigung der Höhe müsste man auch noch beachten.
Da ich noch recht neu mit Python unterwegs bin, habe ich noch einige Wissenslücken.
Was macht self.entry_var.trace ? Wie bekommst Du es hin, dass ein eingegebenes Zeichen größer max_char gar nicht erst angezeigt wird?
Ich habe außerdem noch ein Verständnisproblem zu Euren Programmstrukturen ...
Wenn ich es richtig verstanden habe, ist app_win eine Instanz von tk.Tk und sollte alle Module kennen.
self.app_win ist eine Referenz auf app_win.
Warum muss tk.Canvas über die Klassendefinition noch zusätzlich vererbt werden? Eigentlich müsste die Klasse doch Canvas über app_win bereits kennen?!
Hi suk
if len(var_text) == 0: return
Übrigens wirft dieser Fall bei deinem Skript in Zeile 82 die Exception:
var_verhaeltnis = self.a_x / var_textlaenge
ZeroDivisionError: division by zero
Du könntest dies auch erreichen durch auslösen eines:
self.entry.bind("<KeyPress>", self.event_key_press)Hiermit wird der Text direkt bei der Eingabe über die Tastatur gestutzt. Es passiert so schnell, dass du das Zeichen, welches die maximale Länge übersteigt im Texteingabefeld gar nicht siehst.
Der Unterschied zwischen diesen beinden Varianten ist folgender:
Fall-1: Beim setzen der Kontrollvariablen wird ein überlanger Textstring sofort gestutzt und erst dann im Texteingabefeld angezeigt.
Fall-2: Beim setzen des Texteingabefeldeigenen 'set' Methode wird aber ein überlanger Textstring mit seine voller Länge angezeigt und erst beim drücken einer Tastaturtaste gestutzt.
Noch ein Tipp. In der Methode event_keyrelease würde ich an stelle von keycode eher keysym verwenden. Dann würde der Kode wie folgt lesbarer:
if (event.keycode == 9): = Escape
if (event.keycode == 36): = Return
if (event.keysym == 'Escape'):
if (event.keysym == 'Return'):
Was bezweckst du in Zeile 71 mit dieser Textrückkopplung?
self.eingabefeld.config(text = self.eingabefeld.get())
Noch weiter Tipps:
a) Versuche die PEP8 Richtlinen zu befolgen
b) Codezeilenlänge möglichts maximal auf Seitenbreite begrenzen (Seite im Hochformat).
Überlange Kodezeilen können mit einem \ gekürzt werden
c) Ein Skript möglichts ohne Überlange print-Anweisungen ins Forum platzieren
d) Im Forum möglichst die nummerierten Code Tags benutzen
Habe für die Bestimmung der Textlänge in meinem Skript den Kodeabschnitt aus deinem Skript (weil er besser ist) abgeändert übernommen. Um zu verstehen was in diesem Kodabschnitt abläuft decodierte ich in von:um in besser lesen zu können:
Daraus entstand mein Skript wie folgt:
Gruss wuf
Vor allem was eventuell die Abarbeitungszeit anbelangt. Die Texthöhe habe ich leider nicht berücksichtigt. Der 'while' Hänger kann behoben werden mit:Die while-Schleife scheint mir jedoch bedenklich zu sein.
if len(var_text) == 0: return
Übrigens wirft dieser Fall bei deinem Skript in Zeile 82 die Exception:
var_verhaeltnis = self.a_x / var_textlaenge
ZeroDivisionError: division by zero
Bei Verwendung einer Kontrollvariabel wird schon ein Event ausgelöst, wenn sie verändert wird bevor die Änderung ins Textfeld übertragen wird. Mit der Methode 'trace'kannst du somit etwas abfangen bevor es im Texteingabefeld erscheint. In unserem Fall wird der Eingabestring auf die vorgegebene Länge MAX_ENTRY_CHAR gestutzt mit dem callback:Was macht self.entry_var.trace ? Wie bekommst Du es hin, dass ein eingegebenes Zeichen größer max_char gar nicht erst angezeigt wird?
Code: Alles auswählen
def entry_trace_callback(self, *args):
char = self.entry_var.get()[0:MAX_ENTRY_CHAR]
self.entry_var.set(char)
self.entry.bind("<KeyPress>", self.event_key_press)
Code: Alles auswählen
def event_key_press(self, event):
char = self.entry_var.get()[0:self.max_char]
self.entry_var.set(char)
Der Unterschied zwischen diesen beinden Varianten ist folgender:
Fall-1: Beim setzen der Kontrollvariablen wird ein überlanger Textstring sofort gestutzt und erst dann im Texteingabefeld angezeigt.
Fall-2: Beim setzen des Texteingabefeldeigenen 'set' Methode wird aber ein überlanger Textstring mit seine voller Länge angezeigt und erst beim drücken einer Tastaturtaste gestutzt.
Noch ein Tipp. In der Methode event_keyrelease würde ich an stelle von keycode eher keysym verwenden. Dann würde der Kode wie folgt lesbarer:
if (event.keycode == 9): = Escape
if (event.keycode == 36): = Return
if (event.keysym == 'Escape'):
if (event.keysym == 'Return'):
Was bezweckst du in Zeile 71 mit dieser Textrückkopplung?
self.eingabefeld.config(text = self.eingabefeld.get())
Noch weiter Tipps:
a) Versuche die PEP8 Richtlinen zu befolgen
b) Codezeilenlänge möglichts maximal auf Seitenbreite begrenzen (Seite im Hochformat).
Überlange Kodezeilen können mit einem \ gekürzt werden
c) Ein Skript möglichts ohne Überlange print-Anweisungen ins Forum platzieren
d) Im Forum möglichst die nummerierten Code Tags benutzen
Habe für die Bestimmung der Textlänge in meinem Skript den Kodeabschnitt aus deinem Skript (weil er besser ist) abgeändert übernommen. Um zu verstehen was in diesem Kodabschnitt abläuft decodierte ich in von:
Code: Alles auswählen
def event_keyrelease(self, event):
# print(event.keycode)
maxchar = 8
if (int(len(self.eingabefeld.get()) > maxchar)):
# Länge auf maximale Anzahl Zeichen zurücksetzen
print("maximale Anzahl Zeichen erreicht")
self.eingabefeld.delete(maxchar, len(self.eingabefeld.get()))
# self.eingabefeld.xview_moveto(0)
if (event.keycode == 9):
# ESC-Taste
print("Abbruch durch Escape")
self.ausgabefeld.config(text = "")
self.eingabefeld.config(text = self.eingabefeld.get())
if (event.keycode == 36):
# Enter-Taste
print("\neingegebener Text ist: %s" % (self.eingabefeld.get()))
# anpassen der Schriftgröße
var_font = tkf.Font(family=self.a_font, size=int(self.a_font_size))
var_text = self.eingabefeld.get()
# textmaße ermitteln
var_textlaenge = var_font.measure(var_text)
var_texthoehe = var_font.metrics("linespace")
# Verhältnis zum ausgabefeld ermitteln
var_verhaeltnis = self.a_x / var_textlaenge
print("Verhältnis Textfeldbreite zu Textbreite: {} / {} = {}".format(self.a_x, var_textlaenge, var_verhaeltnis))
# maximale schriftgröße ermitteln, so dass der Text in das Ausgabefeld passen würde
var_font_size_neu = int(self.a_font_size * var_verhaeltnis * 0.95)
print("Schriftgröße alt: {}, neu: {}".format(self.a_font_size, var_font_size_neu))
var_font_neu = tkf.Font(family=self.a_font, size=var_font_size_neu)
print("neu: (Textbreite, Textfeldbreite) (Texthöhe, Textfeldhöhe): ({}, {}) ({}, {})".format(var_font_neu.measure(var_text), self.a_x, var_font_neu.metrics("linespace"), self.a_y))
# wenn Ergenbis Texthöhe zu hoch, Schriftgröße nochmal verringern
if (var_font_neu.metrics("linespace") > self.a_y):
var_verhaeltnis = self.a_y / var_font_neu.metrics("linespace")
print("Verhältnis Textfeldhöhe zu Texthöhe: {} / {} = {}".format(self.a_y, var_font_neu.metrics("linespace"), var_verhaeltnis))
var_font_size_neu = int(var_font_size_neu * var_verhaeltnis)
print("Schriftgröße alt: {}, neu: {}".format(self.a_font_size, var_font_size_neu))
var_font_neu = tkf.Font(family=self.a_font, size=var_font_size_neu)
print("neu: (Textbreite, Textfeldbreite) (Texthöhe, Textfeldhöhe): ({}, {}) ({}, {})".format(var_font_neu.measure(var_text), self.a_x, var_font_neu.metrics("linespace"), self.a_y))
self.ausgabefeld.config(text = self.eingabefeld.get(), font = var_font_neu)
self.text = self.eingabefeld.get()
Code: Alles auswählen
self.text = "Name 1"
self.output_field.width = 150
self.output_field.height = 100
self.output_field_font = "Arial"
self.output_field_font_size = 15
if (event.keycode == 36):
# anpassen der Schriftgröße
var_font = tkf.Font(family=self.output_field_font, size=int(
self.output_field_font_size))
var_text = self.eingabefeld.get()
# textmaße ermitteln
var_textlaenge = var_font.measure(var_text)
var_texthoehe = var_font.metrics("linespace")
# Verhältnis zum ausgabefeld ermitteln
var_verhaeltnis = self.output_field.width / var_textlaenge
# maximale schriftgröße ermitteln, so dass der Text in das
# Ausgabefeld passen würde
var_font_size_neu = int(
self.output_field_font_size * var_verhaeltnis * 0.95)
var_font_neu = tkf.Font(
family=self.output_field_font, size=var_font_size_neu)
# wenn Ergenbis Texthöhe zu hoch, Schriftgröße nochmal verringern
if (var_font_neu.metrics("linespace") > self.output_field.height):
var_font_size_neu = int(var_font_size_neu * var_verhaeltnis)
var_font_neu = tkf.Font(family=self.output_field_font,
size=var_font_size_neu)
self.output_fieldusgabefeld.config(text = self.eingabefeld.get(),
font=var_font_neu)
self.text = self.eingabefeld.get()
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.font as tkf
APP_TITLE = "Text Fitter"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 600
APP_HEIGHT = 310
MAX_ENTRY_CHAR = 8
OUTPUT_WIN_WIDTH = 150
OUTPUT_WIN_HEIGHT = 100
class Application(tk.Canvas):
def __init__(self, app_win, **kwargs):
self.app_win = app_win
app_win.protocol("WM_DELETE_WINDOW", self.close_app)
tk.Canvas.__init__(self, app_win, **kwargs)
def build(self):
self.measure_samples("yellow world")
self.entry_frame = tk.Frame(self, bg='gray', bd=3, relief="groove",
width=300, height=150)
# Eingabefeld
self.entry_var = tk.StringVar()
self.entry_var.trace("w", self.entry_trace_callback)
self.entry = tk.Entry(self.entry_frame, relief = "sunken", bd=2,
textvariable=self.entry_var, font=("Arial", 15))
self.entry.place(x=10, y=5, width=270, height=30)
self.entry_var.set("Name1")
self.entry.select_range(0, len(self.entry.get()))
self.entry.focus()
self.entry.bind("<KeyRelease>", self.event_key_release)
# Ausgabefeld
self.output_font = tkf.Font(family="Arial", size=15)
self.output_var = tk.StringVar()
self.output = tk.Label(self.entry_frame, relief="groove", bd=2,
font=self.output_font, bg="lightgray", textvariable=self.output_var)
self.output.place(x=10, y=40, width=OUTPUT_WIN_WIDTH,
height=OUTPUT_WIN_HEIGHT)
self.create_window(20, 150, window=self.entry_frame, anchor="nw")
tk.Button(self.app_win, text="close",
command=self.app_win.destroy).pack(pady=2)
def entry_trace_callback(self, *args):
char = self.entry_var.get()[0:MAX_ENTRY_CHAR]
self.entry_var.set(char)
def event_key_release(self, event):
if event.keysym == 'Escape':
self.output_var.set("")
if event.keysym == 'Return':
#--- Anpassen der Schriftgröße ---
# Texteingabe kontrollieren
var_text = self.entry_var.get()
if len(var_text) == 0: return
# Textlänge ermitteln
var_text_length = self.output_font.measure(var_text)
# Verhältnis zum Ausgabefeld ermitteln
var_ratio = OUTPUT_WIN_WIDTH / var_text_length
# Maximale Schriftgröße ermitteln, so dass der Text in das
# Ausgabefeld passen würde
old_font_size = self.output_font.cget('size')
new_font_size = int(old_font_size * var_ratio * 0.95)
self.output_font.configure(size=new_font_size)
# Wenn Ergenbis Texthöhe zu hoch, Schriftgröße nochmal verringern
text_height = self.output_font.metrics("linespace")
if text_height > OUTPUT_WIN_HEIGHT:
var_ratio = OUTPUT_WIN_HEIGHT / text_height
new_font_size = int(new_font_size * var_ratio)
self.output_font.configure(size=new_font_size)
# Ausgabefeld aktualisieren
self.output.config(font=self.output_font)
self.output_var.set(var_text)
def measure_samples(self, text):
xpos, ypos = (20,20)
fonts = (
tkf.Font(family='Helvetica', size=20),
tkf.Font(family='Times', size=20))
for font in fonts:
font_fam, size, text_length, height = (
font.cget('family'),
font.cget('size'),
font.measure(text),
font.metrics("linespace"),
)
self.create_text(xpos, ypos, text="_{}_".format(text), font=font,
anchor="nw")
self.create_text(xpos, ypos + height + 5,
text="{} {}: BxH=({},{})".format(font_fam, size, text_length,
height), font=("Arial", 10),anchor="nw")
ypos += height + 25
def close_app(self):
# Here do something before apps shutdown
print("Good Bye!")
self.app_win.destroy()
def main():
app_win = tk.Tk()
app_win.title(APP_TITLE)
app_win.option_add("*highlightThickness", 0)
app = Application(app_win, width=APP_WIDTH, height=APP_HEIGHT,
relief="groove", bg="white", bd=2)
app.pack()
app.build()
app_win.mainloop()
if __name__ == '__main__':
main()
Darüber können dir unsere versierten und allzeit hilfsbereiten Forumsmitglieder __deets__ und Sirius3 schneller und besser Auskunft geben.Ich habe außerdem noch ein Verständnisproblem zu Euren Programmstrukturen ...
Wenn ich es richtig verstanden habe, ist app_win eine Instanz von tk.Tk und sollte alle Module kennen.
self.app_win ist eine Referenz auf app_win.
Warum muss tk.Canvas über die Klassendefinition noch zusätzlich vererbt werden? Eigentlich müsste die Klasse doch Canvas über app_win bereits kennen?!
Gruss wuf
Take it easy Mates!
@suk: noch etwas zu den Coding-Richtlinien: Deine if haben zu viele Klammern, das sind keine Funktionen, also sind Klammern um den gesamten Ausdruck unnötig. Bei `if (int(len(self.eingabefeld.get()) > maxchar)):` wandelst zu zudem noch den Wahrheitswert des >-Vergleichs in eine Zahl um!
Statt überlange Zeilen mit \ umzubrechen, ist es besser, innerhalb von Klammern umzubrechen, dann weiß Python automatisch, dass es in der nächsten Zeile weitergeht:
Zu Deinen Fragen:
Statt überlange Zeilen mit \ umzubrechen, ist es besser, innerhalb von Klammern umzubrechen, dann weiß Python automatisch, dass es in der nächsten Zeile weitergeht:
Code: Alles auswählen
print("neu: (Textbreite, Textfeldbreite) (Texthöhe, Textfeldhöhe): ({}, {}) ({}, {})".format(
var_font_neu.measure(var_text), self.a_x, var_font_neu.metrics("linespace"), self.a_y))
`app_win` ist eine Instanz von tk.Tk, kennt also alle Methoden.suk hat geschrieben:Wenn ich es richtig verstanden habe, ist app_win eine Instanz von tk.Tk und sollte alle Module kennen.
`app` dagegen ist die eigentliche Applikation; wuf hat die Designentscheidung getroffen, dass `app` ein Canvas IST. Damit hat app alle Eigenschaften von Canvas und zusätzlich noch das, was in der Klasse definiert wurde.suk hat geschrieben:Warum muss tk.Canvas über die Klassendefinition noch zusätzlich vererbt werden? Eigentlich müsste die Klasse doch Canvas über app_win bereits kennen?!
Danke für die guten Tips. Werde ich so übernehmen.
@wuf
zu a) Wo finde ich die PEP8 Richtlinien?
zu d) Ich benutze immer "Code". Mit der Nummerierung ist natürlich besser. Welche Formatierungsfunktion müsste ich denn nutzen?
@Sirius3
Meine Klammersetzung war in dem genannten Fall nicht so gedacht bzw. falsch. Int sollte eigentlich nur das Ergebnis von len(self.eingabefeld.get()) definitiv als Integer umwandeln. Wäre aber wahrscheinlich nicht notwendig gewesen. Kommt daher, dass ich schon mal eine Fehlermeldung hatte, dass String und Integer nicht verglichen werden konnten. Ich tue mich manchmal noch recht schwer damit einzuschätzen, welchen Typ die Rückgabewerte haben.
@wuf
.. hatte ich auch schon bemerkt. Da war jedoch schon eingestellt.Übrigens wirft dieser Fall bei deinem Skript in Zeile 82 die Exception:
var_verhaeltnis = self.a_x / var_textlaenge
ZeroDivisionError: division by zero
.. war wohl schon ein bisschen spät Sollte eigentlich ein Rücksetzen auf den Ursprungswert werden.Was bezweckst du in Zeile 71 mit dieser Textrückkopplung?
self.eingabefeld.config(text = self.eingabefeld.get())
..Noch weiter Tipps:
a) Versuche die PEP8 Richtlinen zu befolgen
b) Codezeilenlänge möglichts maximal auf Seitenbreite begrenzen (Seite im Hochformat).
Überlange Kodezeilen können mit einem \ gekürzt werden
c) Ein Skript möglichts ohne Überlange print-Anweisungen ins Forum platzieren
d) Im Forum möglichst die nummerierten Code Tags benutzen
zu a) Wo finde ich die PEP8 Richtlinien?
zu d) Ich benutze immer "Code". Mit der Nummerierung ist natürlich besser. Welche Formatierungsfunktion müsste ich denn nutzen?
@Sirius3
.. da ich öfters auch mehrere Bedingungen verknüpfe, finde ich es lesbarer, die Einzelbedingungen zu klammern. Bei konsequenter Umsetzung sind dann halt auch einfache Bedingungen geklammert. Kann dies zu Problemen führen oder ist es nur ein Design-Thema?@suk: noch etwas zu den Coding-Richtlinien: Deine if haben zu viele Klammern, das sind keine Funktionen, also sind Klammern um den gesamten Ausdruck unnötig. Bei `if (int(len(self.eingabefeld.get()) > maxchar)):` wandelst zu zudem noch den Wahrheitswert des >-Vergleichs in eine Zahl um!
Meine Klammersetzung war in dem genannten Fall nicht so gedacht bzw. falsch. Int sollte eigentlich nur das Ergebnis von len(self.eingabefeld.get()) definitiv als Integer umwandeln. Wäre aber wahrscheinlich nicht notwendig gewesen. Kommt daher, dass ich schon mal eine Fehlermeldung hatte, dass String und Integer nicht verglichen werden konnten. Ich tue mich manchmal noch recht schwer damit einzuschätzen, welchen Typ die Rückgabewerte haben.
@suk: PEP8 findet man hier. Das hätte übrigens auch eine kurze Google-Suche ergeben. Und die nummerierten Code-Tags verbergen sich hinter dem Dropdown-Menü „Code auswählen“.
Ad Klammerung: Für jeden beliebigen Ausdruck `x` ist `(x)` genau das selbe. Von daher macht das für Python keinen Unterschied. Es liest sich einfach nur komisch, weil die Klammern eben überflüssig sind. Das Argument, dass du das aus Konsistenz machst, weil du bei mehreren verknüpften Bedingungen auch klammerst, ist ähnlich sinnvoll wie das Argument, dass man ja bei der Addition zweier Zahlen ein Pluszeichen und links und rechts jeweils ein Objekt hat und deswegen jeden ganzzahligen Ausdruck immer mit `+ 0` abschließt, weil man bei der Addition ja auch ein Plus braucht.
Das Klammerargument und das Ergebnis von `len` in einen `int` umzuwandeln, hat ein wenig was von Cargo-Kult-Programmierung. `len` gibt immer einen `int` zurück. Wie sollte eine Sequenz auch 42,5 Werte enthalten? Oder `"foo"` viele Werte‽
Ad Klammerung: Für jeden beliebigen Ausdruck `x` ist `(x)` genau das selbe. Von daher macht das für Python keinen Unterschied. Es liest sich einfach nur komisch, weil die Klammern eben überflüssig sind. Das Argument, dass du das aus Konsistenz machst, weil du bei mehreren verknüpften Bedingungen auch klammerst, ist ähnlich sinnvoll wie das Argument, dass man ja bei der Addition zweier Zahlen ein Pluszeichen und links und rechts jeweils ein Objekt hat und deswegen jeden ganzzahligen Ausdruck immer mit `+ 0` abschließt, weil man bei der Addition ja auch ein Plus braucht.
Das Klammerargument und das Ergebnis von `len` in einen `int` umzuwandeln, hat ein wenig was von Cargo-Kult-Programmierung. `len` gibt immer einen `int` zurück. Wie sollte eine Sequenz auch 42,5 Werte enthalten? Oder `"foo"` viele Werte‽
Mit der Zeit bekommt man ein Gefühl dafür. Bis dahin hilft die Dokumentation.suk hat geschrieben:Ich tue mich manchmal noch recht schwer damit einzuschätzen, welchen Typ die Rückgabewerte haben.
Zum Glück muss man gar nicht schätzen, sondern Python bietet eine interaktive Konsole, wo man so leichte Fragen leicht prüfen und selbst beantworten lassen kann, hier z.B. mit dem Befehl type():narpfel hat geschrieben:Mit der Zeit bekommt man ein Gefühl dafür. Bis dahin hilft die Dokumentation.suk hat geschrieben:Ich tue mich manchmal noch recht schwer damit einzuschätzen, welchen Typ die Rückgabewerte haben.
Code: Alles auswählen
>>> type(len("Test"))
<class 'int'>
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Habe mir mal gestern PEP8 durchgelesen.narpfel hat geschrieben:@suk: PEP8 findet man hier. Das hätte übrigens auch eine kurze Google-Suche ergeben. Und die nummerierten Code-Tags verbergen sich hinter dem Dropdown-Menü „Code auswählen“.
Da freut man sich, wenn man Fortschritte in der Syntax macht ... und dann ist noch soviel Design zu beachten. Macht ja alles sicher Sinn, ist jedoch sehr umfangreich.
Mit der Zeilenbegrenzung wird man m.E. irgendwann an Grenzen stoßen ...
Wie müsste ich denn folgenden Code umbrechen, um nach PEP8 die Zeilenlänge von 72? Zeichen noch einhalten zu können?
Code: Alles auswählen
if var_font.measure(self.mydata.player_name[self.mydata.playerqueue[position]]) >= var_label_width_max:
var_font_size = int(var_font_size * (var_label_width_max / var_font.measure(self.mydata.player_name[self.mydata.playerqueue[position]])))
.. Cargo-Kult nehme ich mal als KomplimentDas Klammerargument und das Ergebnis von `len` in einen `int` umzuwandeln, hat ein wenig was von Cargo-Kult-Programmierung. `len` gibt immer einen `int` zurück. Wie sollte eine Sequenz auch 42,5 Werte enthalten? Oder `"foo"` viele Werte‽
Hast aber Recht, mit meinen bisher zwei Monaten Python habe ich mir noch viel zu erarbeiten. Aber dazu hoffe ich ja im Forum Anregungen und Hinweise zu bekommen.
@suk: die 72 Zeichen ist einer der wenigen Punkte, die nicht so streng gesehen werden. Bei Deinem Code sollte man ein paar Variablen einführen:
Code: Alles auswählen
player_name = self.mydata.player_name[self.mydata.playerqueue[position]]
width = var_font.measure(player_name)
if width >= var_label_width_max:
var_font_size = int(var_font_size * (var_label_width_max / width))
@suk: Die 72 Zeichen beziehen sich auf Fließtext, also Docstrings und längere Kommentare. Da ist es im Prinzip egal, wo man umbricht, also sollte man die Zeilenlänge so beschränken, dass es gut lesbar bleibt.
Bei der Zeilenlänge für Code halte ich es auch eher mit Raymond Hettinger: Es ist besser, die 79 Zeichen ein wenig zu überschreiten, als dafür Lesbarkeit zu opfern, indem man Zeilen an schlechten Stellen umbricht oder Variablennamen verkürzt. Über die alternativ erlaubten 99 Zeichen würde ich aber nicht herausgehen.
Deinen Code könnte man verbessern, indem man Werte, die mehrfach berechnet werden, an Namen bindet:
Alternativ finde ich auch so etwas in Ordnung:
Sonstige Anmerkungen: Ein `my`-Präfix vor Namen ist überflüssig, weil es keinerlei Aussagekraft hat. `data` ist auch ein sehr generischer Name, für den man eventuell etwas besseres finden könnte. Das `var_`-Präfix sieht auch komisch aus. Wozu ist das gut?
Eventuell macht es auch Sinn, eine `Player`-Klasse (oder ein `namedtuple`) zu schreiben, weil du in `mydata.playerqueue` anscheinend Indizes in andere Listen speicherst. Es ist sinnvoller, zusammengehörige Daten auch zusammen zu speichern. `playerqueue` ist als Name für eine Liste (?) auch suboptimal, weil es den Datentypen `Queue` gibt.
Bei der Zeilenlänge für Code halte ich es auch eher mit Raymond Hettinger: Es ist besser, die 79 Zeichen ein wenig zu überschreiten, als dafür Lesbarkeit zu opfern, indem man Zeilen an schlechten Stellen umbricht oder Variablennamen verkürzt. Über die alternativ erlaubten 99 Zeichen würde ich aber nicht herausgehen.
Deinen Code könnte man verbessern, indem man Werte, die mehrfach berechnet werden, an Namen bindet:
Code: Alles auswählen
player_name = self.mydata.player_name[self.mydata.playerqueue[position]]
player_name_width = var_font.measure(player_name)
if player_name_width >= var_label_width_max:
var_font_size = int(var_font_size * var_label_width_max / player_name_width)
Code: Alles auswählen
player_name_width = var_font.measure(
self.mydata.player_name[self.mydata.playerqueue[position]]
)
if player_name_width >= var_label_width_max:
var_font_size = int(var_font_size * var_label_width_max / player_name_width)
Eventuell macht es auch Sinn, eine `Player`-Klasse (oder ein `namedtuple`) zu schreiben, weil du in `mydata.playerqueue` anscheinend Indizes in andere Listen speicherst. Es ist sinnvoller, zusammengehörige Daten auch zusammen zu speichern. `playerqueue` ist als Name für eine Liste (?) auch suboptimal, weil es den Datentypen `Queue` gibt.