Fonts in Canvas, Eigenschaften bleiben nicht konstant

Fragen zu Tkinter.
Antworten
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo Python-Forum,

mein Problem ist, dass ich in ein Canvas Texte (c.create_text...) einfügen will. Die Textoptionen wie Schriftart, Höhe, fett, kursiv, und unterstrichenan können vor dem Einfügen in einem anderen Fenster ausgewählt werden. Das alles funktioniert, aber nachdem ein paar unterschiedliche Schriftarten eingefügt wurden, übernehmen die "älteren" Texte die Eigenschaften der neu eingefügten Schrift. Also die Schriftart wechselt von Helvetica zu Bettsy Flanagan, der text wird kursiv oder fett und oder auch unterstrichen.
Ich hab herausgefunden, dass die Option Unterstrichen den Fehler verursacht. Lass ich diese Option weg, also übergebe die Schriftart ohne diese Option, verhält sich alles wie gewünscht.
Bem durchforsten des Forums hab ich festgestellt, dass einige ein Script suchen, welches die verfügbaren Schriften ausgibt. Das Script "test_schriftauswahl" darf gerne dazu verwendet werden.

So nun folgen die zwei Module test_Schrift in Canvas.py und test_schriftauswahl.py
Da dies mein erster Beitrag ist, hoffe ich, dass es ob dem vielen Code keine Beschwerden gibt.

Gruss
Peter

Code: Alles auswählen

#!/usr/bin/env python -u
#-*- coding: utf-8 -*-

# test_Schrift_in Canvas.py

# Python 2.7.3

"""
Das Modul erzeugt ein Canvas und ruft das Modul schriftauswahl auf.
Im Canvas kann mit der rechten Maustaste die ausgewählte Schrift dargestellt
werden.
"""

from Tkinter import Tk, Canvas, Button
import tkFont
from schriftauswahl import Grafik, Datenspeicher_S


class Texteinfuege(object):
    def __init__(self):
        self.flaeche()
        self.zaehler = 1
        self.hole()
        self.schlaufe()

    def flaeche(self):
        self.rahmen = Tk(className = " Test")

        self.flaeche = Canvas(self.rahmen, width = "200m", height = "100m",\
                              bg = "white")
        self.flaeche.pack()
 
        self.flaeche.bind(sequence = "<Button-3>", func=self.einfuege)

    def hole(self):

        self.schriften = Grafik()
        self.flaeche.lift()
        
        
    def einfuege(self, event):
        
        self.schrift = getattr(Datenspeicher_S,"schriftoptionen")
        attr = self.schrift.configure()
        self.fam = attr["family"]
        self.dicke = attr["weight"]
        self.stil = attr["slant"]
        self.unterstrich = attr["underline"]
        self.hoehe = attr["size"]
        self.inhalt = "Text Nummer " + str(self.zaehler) +" Schrift "+str(self.fam)
        self.koordinaten1 = event.x, event.y
        print "Koordinaten 1 = " +str(self.koordinaten1)
        self.koord_x = self.koordinaten1[0]
        self.koord_y = self.koordinaten1[1]


        self.schreibe()
        
    def schreibe(self):

        self.schreibe_nummer = self.flaeche.create_text(self.koord_x, self.koord_y,\
                                                        text = str(self.inhalt), \
                                                        anchor ="sw", \
                                                        font = tkFont.Font(family = self.fam, \
                                                        weight = self.dicke, slant = self.stil,\
                                                        underline = self.unterstrich, \
                                                        size = self.hoehe))
                
        self.zaehler += 1
        
        

    def schlaufe(self):
        self.rahmen.mainloop()


Texteinfuege()

Code: Alles auswählen

#!/usr/bin/env python -u
#-*- coding: utf-8 -*-

#test_schriftauswahl.py

# Python 2.7.3

"""
___________________________________________________________
Die Klasse Schriften listet die verfügbaren Schriften in
einem Auswahlfeld auf und ermöglicht die Schriftoptionen
anzupassen und die Schrift mit den gewählten Eigenschaften
von einer andern Klasse zu übernehmen.

Die Klasse Grafik erzeugt die GUI und enthält die Logik

In der Klasse Datenspeicher_S, Methode Schriftoptionen,
wird der Schriftsatz zur Übernahme durch eine andere Klasse abgelegt.

<<In der untersten Zeile den Aufruf Grafik() und in der Methode ablaufsteuerung den Aufruf
self.schlaufe() aktivieren und das Modul ist alleine lauffähig.>>

<<Wenn in der Methode ablaufsteuerung der Aufruf self.unterstrich() aktiviert wird, steht auch unterstrichener
Text zur Auswahl, bietet aber Probleme mit dem Übernehmen in ein Canvas>>

19.02.2013 
____________________________________________________________
"""

from Tkinter import Listbox, Button, StringVar, IntVar, Label, Radiobutton,\
     Entry, Checkbutton, Toplevel
import tkFont

class Grafik(object):
    def __init__(self):

        
        self.rahmen = Toplevel(class_ = " Schrift Auswahl")
        self.rahmen.title(" Schriftauswahl")
        self.schriftsatz = {}
        
        self.anzeige = StringVar()
        self.gewaehlte_Schrift = ""
        self.knopf = Button
        self.rb = Radiobutton
        self.cb = Checkbutton
        self.texterscheinung = IntVar()
        self.slanterscheinung = IntVar()
        self.underline = IntVar()
        self.texthoehe = StringVar()
        self.textdicke = StringVar()
        self.gewaehlt = ""
        self.unterstrichen = 0
        self.ablaufsteuerung()
        self.schlaufe()
        
        
       

    def ablaufsteuerung(self):
        
        #die Methode unterstrich ist  für meine Anwendung nicht brauchbar
        self.schriften()
        self.auswahlfeld()
        self.anzeigefeld()
        self.loesche()
        self.normal_fett()
        self.hoehe()
        self.normal_kursiv()
        self.dick_duenn()
        self.slant_auswahl()
        #self.unterstrich()# bietet Probleme mit der Uebernahme in ein Canvas
        
        
        
        
        
    def auswahlfeld(self):
        
        
        self.auswahlfenster = Listbox(self.rahmen, width = 30, height = 20,\
                               bg = "white", \
                               selectbackground = "cyan")
        self.auswahlfenster.grid(row = 0,rowspan = 10, column = 0, padx = 10)
        
        anzahl = len(self.schriften)

        for schriftart in self.schriften:
            self.auswahlfenster.insert("end", schriftart)

        self.auswahlfenster.bind("<ButtonRelease -1>", self.auswahl_event)



    def anzeigefeld(self):

        self.anzeigefenster = Label(self.rahmen, width = 30, height = 3,\
                                    bg = "white", text = self.gewaehlte_Schrift, \
                                    font = self.schriftsatz)
        self.anzeigefenster.grid(row = 13, column = 0, padx = 10, pady = 10,\
                                 columnspan = 2)

    def loesche(self):
        loeschtaste = self.knopf(self.rahmen,bg = "red",text = "Fenster loeschen",\
                   command = self.loeschen)
        loeschtaste.grid(row = 14, columnspan = 2, padx = 10, pady =10)


        
    def normal_fett(self):
        
        self.auswahlweight = self.rb(self.rahmen,text = "normal", value = 1,\
                               variable = self.texterscheinung,\
                               anchor = "w", width = 8, command = self.normal)
        self.auswahlweight.grid(row = 0, column = 1)

        self.auswahlweight.select()
        

        self.auswahlweight = self.rb(self.rahmen,text = "fett", value = 2,\
                               variable = self.texterscheinung,\
                               anchor = "w", width = 8, command = self.fett)
        self.auswahlweight.grid(row = 1, column = 1)

  
        self.text_dick_duenn = self.texterscheinung.get()


    def normal_kursiv(self):
        
        
        self.auswahlslant = self.rb(self.rahmen,text = "Normal", value = 1,\
                               variable = self.slanterscheinung,\
                               anchor = "w", width = 8, command = self.gerade)
        self.auswahlslant.grid(row = 4, column = 1)

        self.auswahlslant.select()

        self.auswahlslant = self.rb(self.rahmen,text = "Kursiv", value = 2,\
                               variable = self.slanterscheinung,\
                               anchor = "w", width = 8, command = self.kursiv)
        self.auswahlslant.grid(row = 5, column = 1)

        self.slant_wahl = self.slanterscheinung.get()
   

    def gerade(self):
        self.slant = "roman"
        
        self.slant_wahl = self.slanterscheinung.get()
        
        self.anzeigefenster.update()
        self.auswahl()
 
        self.slant_auswahl()

    def kursiv(self):
        self.slant = "italic"
 
        self.slant_wahl = self.slanterscheinung.get()
 
        
        self.auswahl()
 
        self.slant_auswahl()

    def slant_auswahl(self):
        
        if self.slant_wahl == 1:
            self.slant = "roman"
        else:
            self.slant = "italic"
 
        self.aktualisiere()
            

    def hoehe(self):
        self.textfeldhoehe = Label(self.rahmen,text = "Schrifthöhe",bd = 5, anchor = "w")
        self.textfeldhoehe.grid(row = 2, column = 1)
        self.schrifthoehe = Entry(self.rahmen, text = "4", bg = "white",\
                                  textvariable = self.texthoehe, width = 2,\
                                  takefocus = 1)

                                  
        self.schrifthoehe.grid(row = 3, column = 1,sticky = "w"+"e")
        if self.schrifthoehe > "1":
            print "Schrifthoehe > 1"
            pass
        else:
            print "schrifthoehe <= 1"
            print self.schrifthoehe
            self.schrifthoehe.insert(1,"10")
        
       

    def unterstrich(self):
        
        self.unterstreichen = self.cb(self.rahmen,text = "Unter-\nstreichen",\
                               variable = self.underline,\
                               anchor = "w", width = 8, command = self.under_line)
        self.unterstreichen.grid(row = 6, column = 1)

    def under_line(self):
        
        self.unterstreich = self.underline.get()
 
        if self.unterstreich == 1:
            self.unterstrichen = 1
        else:
            self.unterstrichen = 0
        self.auswahl()
        

    def normal(self):
       
        self.textdicke = "normal"
        self.auswahl()
        self.aktualisiere()
 

    def fett(self):
 
        self.textdicke = "bold"
 
        self.auswahl()
        self.aktualisiere()

    def dick_duenn(self):
 
        if self.text_dick_duenn == 1:
            self.textdicke = "normal"
        else:
            self.textdicke = "bold"
         
        self.aktualisiere()

    def auswahl_event(self, Event):
        self.auswahl()

    def auswahl(self):
        
        auswahl = self.auswahlfenster.curselection()
 
        if str(auswahl) == "()":
            auswahl = ("0",)
 
        hoehe = self.schrifthoehe.get()
        self.hoehe = int(hoehe)
               
        wahl = str(auswahl[0])# Nr. in der Liste
 
        self.gewaehlt = self.schriften[int(wahl)]
        try:
            self.gewaehlte_Schrift = str(self.gewaehlt)
        except:
            self.gewaehlte_Schrift = "Fehler im Schriftnamen"
            
        try:               
            schrift = tkFont.Font(family = str(self.gewaehlt), size = int(hoehe))
        except:
            schrift = tkFont.Font(family = "Fehler im Schriftnamen", size = int(hoehe))
            
        schriftattribute = schrift.configure() #Dictionary mit den Merkmalen

        self.fam = str(schriftattribute["family"])#Schrift Name
          
        self.fam = self.fam.replace(" ","")
        print "Schrift ausgewählt"
        
        self.schrift_erstellen()

    def schrift_erstellen(self):
 
        self.schriftsatz["family"] = self.fam
        self.schriftsatz["weight"] = self.textdicke
        self.schriftsatz["slant"] = self.slant
        self.schriftsatz["underline"] = self.unterstrichen
        self.schriftsatz["size"] = int(self.hoehe)
        
              
        self.schriftsatz = tkFont.Font(family = self.fam, weight = self.textdicke,\
                                       size = int(self.hoehe), underline = self.unterstrichen,\
                                       slant = self.slant)
        self.aktualisiere()

    def aktualisiere(self):

        print "Schriftwahl aktualisiert"
        self.anzeigefenster.config(text=self.gewaehlte_Schrift)
        self.anzeigefenster.config(font=self.schriftsatz)
        self.schrifthoehe.focus_set()
        self.uebernommen()
        
    def schriften(self):
        #holt die verfügbaren Schriften aus dem Speicher
        schrift = Schriften()
        self.schriften = getattr(Datenspeicher_S, "schriften")

    def loeschen(self):
        self.rahmen.destroy()

    def uebernommen(self):
        #speichert die gewählte Schrift im Datenspeicher_S ab
        setattr(Datenspeicher_S, "schriftoptionen", self.schriftsatz)
        

    def schlaufe(self):
        self.rahmen.mainloop()


class Schriften(object):
    
    def __init__(self):
        
        self.schriften = tkFont.families()
        self.verfuegbare_Schriften()

    def verfuegbare_Schriften(self):
        self.ls_schriften = list(self.schriften)
        self.ls_schriften.sort()
        setattr(Datenspeicher_S,"schriften",self.ls_schriften)
        
class Datenspeicher_S(object):

    def schriften(self):
        return(ls_schriften)

    def schriftoptionen(self):
        return(schriftsatz)

#Grafik()
Zuletzt geändert von Anonymous am Freitag 15. März 2013, 15:25, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@peterpy: Einige Zeilen sind zu lang und einige Namen und die Leerzeichensetzung halten sich nicht an den Style Guide for Python Code.

``except:`` ohne konkrete Ausnahme(n) ist ein sicheres Rezept sich die Fehlersuche unnötig zu erschweren. Das behandelt *alle* Ausnahmen, auch solche die durch falsch geschriebene Namen oder Speichermangel ausgelöst werden. Es gibt nur ganz wenig Code der sich tatsächlich eignet *alle* Ausnahmen ordentlich zu behandeln, und selbst bei dem würde man den allgemeinsten Typ angeben und die Ausnahme an einen Namen binden, damit diese Information nicht verloren geht.

`Grafik` erklärt nicht wirklich was der Datentyp ist. `schriftsatz` sollte eher `schriftart` heissen. Funktionen und Methoden sollten nach Tätigkeiten benannt werden. Dadurch kann man sie einfacher von Datenattributen unterscheiden und Kollisionen mit solchen Namen sind weniger wahrscheinlich.

Warum werden Widget-Typen als Attribute an das Objekt gebunden? Teilweise auch noch an kryptische kurze Namen wie `rb` und `cb`? Das macht den Quelltext unnötig komplexer und schwerer zu verstehen.

Die Klasse ist viel zu überfrachtet mit Methoden und Attributen. Das Du da selber schon durcheinander kommst sieht man daran, dass mindestens zwei Attribute sowohl als Methode und als Datenattribut verwendet werden, wobei das Datenattribut die Methode dann natürlich überschreibt. Ein Grund warum man Attribute nicht ausserhalb der `__init__()`-Methode neu einführen sollte. Denn wenn man dort schon eine Methode mit einem Attribut überdeckt, fällt das sofort beim ersten Aufruf der Methode auf. Ausserdem ist es sonst sehr schwer festzustellen welche Attribute ein Objekt hat, wenn man sich das erst aus allen Methoden zusammen suchen muss.

Einiges scheint redundant, wie die Methoden die jeweils eine Möglichkeit setzen und dann eine Methode die eine von beiden Auswählt. Beispiel: `normal()`, `fett()`, und `dick_duenn()`. Ebenso scheint `auswahl_event()` überflüssig zu sein.

Das hier ist gruseliger Code um ein leeres Tupel zu erkennen:

Code: Alles auswählen

        if str(auswahl) == "()":
            auswahl = ("0",)
Selbst ein Vergleich mit einem leeren Tupel wäre suboptimal, denn der Typ ist ja gar nicht von Interesse, sondern der Umstand, dass nichts drin ist.

Code: Alles auswählen

        if len(auswahl) == 0:
# oder nur
        if not auswahl:
Die `Datenspeicher_S`-Klasse ist ``global``, nur umständlicher geschrieben. Globale Variablen sind schlechter Stil weil sie unnötige und undurchsichtige Abhängigkeiten schaffen. Die beiden ”Methoden” sinnfrei und fehlerhaft. Den Rückgabewert gibt es doch gar nicht als lokalen Namen. Und auf Modulebene auch nicht. Ausserdem ist ``return`` keine Funktion — man sollte es deshalb auch nicht schreiben als wäre es eine.

Die `Schriften`-Klasse macht als Klasse keinen Sinn und der Ablauf von `Grafik.schriften()` im Zusammenhang mit `Schriften` und `Datenspeicher_S` ist vorsichtig ausgedrückt abenteuerlich.

Die ganze UI in *ein* Grid zu packen ist nicht besonders wartungsfreundlich.

'\' zur Zeilenfortsetzung braucht man nur, wenn keine Klammern ”offen” sind, denn dann weiss der Compiler schon, dass der Ausdruck noch nicht zuende sein kann.

Ändern der Zahl in dem Eingabefeld für die Schriftgrösse ändern nichts bis man nicht irgend etwas anderes gemacht hat was die Schriftanzeige beeinflusst.

Ein Doppelklick auf die Zahl um sie zu selektieren, deselektiert den Eintrag in der Listbox und damit ist die Schriftartauswahl verloren.
Antworten