Tkinter, Listboxen, Python 3

Hi!
Nach dem ich die Probleme mit dem Modul hinter mir gelassen habe und das Skript in seiner Grundform läuft würde ich nun gerne eine neue Funktion einfügen.

Darum geht es:
Ein einfaches Skript das geschriebenen Text, gesprochen ausgibt (alles in Python 3).

Nun würde ich gerne Standardsätze definieren die mittels klick an die Sprachausgabe übergeben werden. Diese Standardsätze müssen sich irgendwie hübsch in die restliche GUI einfügen. Ich dachte da an Listboxen. Nur leider sind diese anscheinend nicht so einfach ein zu setzen. Darum bitte ich hier noch mal um Hilfe. Evtl. kann mir jemand Listboxen erklären?

Zusammenfassung:

Text in der GUI darstellen und an die Sprachausgabe übergeben.

Vielen Dank für die Hilfe!
 
Okay Die Box mit den Beispielsätzen sieht jetzt gut aus.
Sie besteht aus einer Folge von Buttons die mit den Sätzen beschrieben werden.

Nur wie könnte ich jetzt nach betätigen des Knopfes den Satz an die Sprachausgabe übergeben?
Wenn ich fertig bin (und mich traue) würde ich auch gerne meinen Code hier rein stellen. So zu sagen als ultimativen Test...

Wie immer Danke für die Mühe. Das Forum hat schon zwei mal geholfen (wenn auch anderes als gedacht:p)
 
So wie versprochen möchte ich euch meinen fertigen Code gerne zeigen. Vielleicht hat ja jemand Lust mir zu sagen was falsch ist.
Ich gehe davon aus das es einiges gibt das man besser machen müsste.
Zum Beispiel die letzten Methoden für die Standardsätze find ich selbst hässlich aber leider fällt mir nicht ein wie ich es anders machen könnte.

Code:
from tkinter import *
import win32com.client
"""
Stimme
------------------------------------------------------------
Es greift auf die Windows eigene Stimme zu.
Es ist möglich eine Eingabe von Hand zu tätigen oder einen Standardsatz
aus zu wählen.
"""
class Stimme():
    def __init__(self):

        self.fenster = Tk()
        self.stimme = win32com.client.Dispatch("SAPI.SpVoice")
        self.fenster.title( "Stimme v2.1" )
        self.frame_oben = Frame( master = self.fenster, relief = RIDGE, bd = 3 )
        self.frame_oben.pack(padx = 10, pady = 10)
        
        self.frame_unten = Frame(master = self.fenster, relief = RIDGE, bd = 3 )
        self.frame_unten.pack(padx = 10, pady = 10)
        
        self.eingabe = Entry( master = self.frame_oben, width = 60 )
        self.eingabe.pack(padx = 10, pady = 10)
        self.eingabe.bind( "<Return>", self.sprachausgabe_entry )
        
        self.button = Button( master = self.frame_oben, text = "Sprechen", width = 10, height = 3)
        self.button.pack(padx = 10, pady = 10) 
        self.button.bind( "<Button-1>", self.sprachausgabe_entry )
        
        self.beispiel1 = Button( master = self.frame_unten, text = "Blödmann!", width = 54 )
        self.beispiel1.pack()
        self.beispiel1.bind( "<Button-1>", self.sprachausgabe_beispiel1 )

        self.beispiel2 = Button( master = self.frame_unten, text = "Wie geht es dir?", width = 54 )
        self.beispiel2.pack()
        self.beispiel2.bind( "<Button-1>", self.sprachausgabe_beispiel2 )

        self.beispiel3 = Button( master = self.frame_unten, text = "Wie ist das Wetter heute?", width = 54 )
        self.beispiel3.pack()
        self.beispiel3.bind( "<Button-1>", self.sprachausgabe_beispiel3 )
        
        self.fenster.mainloop()
        
    def sprachausgabe_entry(self, event):
        self.txt = self.eingabe.get()
        self.stimme.speak( self.txt )
        self.eingabe.delete( 0, len(self.txt) )
        
    def sprachausgabe_beispiel1(self, event):
        self.txt = "Blödmann!"
        self.stimme.speak( self.txt )
        
    def sprachausgabe_beispiel2(self, event):
        self.txt = "Wie geht es dir?"
        self.stimme.speak( self.txt )
        
    def sprachausgabe_beispiel3(self, event):
        self.txt = "Wie ist das Wetter heute?"
        self.stimme.speak( self.txt )
        

def main():
    vocal = Stimme()

if __name__ == "__main__":
    main()
 
Ich habe zwei Änderungsvorschläge. Es sind mehr "Schönheitskorrekturen" als Fehlerkorrekturen und dadurch eher Subjektiv. Ich versuche trotzdem jeweils möglichst objektiv zu erläutern, weshalb ich meine Änderung als besser empfinde. Ich bin natürlich auch bereit, meine Meinung zu ändern, falls jemand bessere Argumente hat oder eines meiner Argumente falsch ist (ich kenne Python nicht so gut).

Lokale Variable vs. Instanzvariable
Du weist in den Methoden jeweils den Ausgabetext einer Instanzvariablen zu. Dieser Text wird allerdings nur in der Methode benutzt. Es scheint mir sinnvoller, hier eine lokale Variable zu benutzen. Die Methode sprachausgabe_beispiel1 könntest du also wie folgt abändern:
Code:
def sprachausgabe_beispiel1(self, event):
    output = "Blödmann!"
    self.stimme.speak(output)
Da du die Variable output auch nur als Argument für die speak-Funktion benutzt, könnte sie auch komplett weggelassen werden:
Code:
def sprachausgabe_beispiel1(self, event):
    self.stimme.speak("Blödmann")

Die Funktion sprachausgabe_entry würde dann wie folgt aussehen:
Code:
def sprachausgabe_entry(self, event):
    self.stimme.speak(self.eingabe.get())
    self.eingabe.delete(0, 'end')
Kurzes OT: Python benutzt einen Garbage Collector. Dieser löscht Objekte, die nicht mehr erreicht werden können und gibt so deren Speicher wieder frei (vereinfacht gesagt). Wenn du jetzt den Text in einer Instanzvariable nutzt, bleibt dieser erreichbar, bis er entweder ersetzt wird oder die Instanz unerreichbar wird. In deinem Fall ist es nicht so schlimm, da es ja "nur" Text ist. Stell dir aber vor, du hast etwas grösseres (sagen wir ein Bild): Du gibst dieses am Anfang einmal aus, danach aber nicht mehr bis zum Ende des Programms. Weil du es aber in einer Instanzvariablen gespeichert hast, hat Python das Gefühl, du benötigst es weiterhin und es verbleibt im Speicher.

Zentrale Methode für die Sprachausgabe
Zudem würde ich die Sprachausgabe in eine eigene Methode auslagern. Stell dir vor, du willst deinen Code so ändern, dass er auch auf Nicht-Windows-Systemen läuft oder die Sprachausgabe auf eine andere Art und weise ändern. Im Moment musst du dazu 5 Methoden (und je nachdem noch die __init__) ändern. Das ist jetzt noch nicht so schlimm, aber stell dir vor, das Projekt wächst (ein paar tausend Zeilen, mehrere Dateien). Jetzt wird das ganze schon etwas mühsamer. Vor allem kommen dann schnell einmal Copy-Paste-Lösungen. Falls du einen Bug findest, musst du danach alle diese Methoden wiederfinden. Eventuell vergisst du dann eine, und schon hast du den Dreck.
Schreiben wir also eine Methode, welche den Text ausgibt.
Code:
def speak(self, speech):
    self.stimme.speak(speech)
Die Methoden würden dann folgendermassen aussehen (am Beispiel von sprachausgabe_beispiel1):
Code:
def sprachausgabe_beispiel1(self, event):
    self.speak("Blödmann")

Bemerkung: Es gibt Leute, die Argumentieren, dass es hier besser wäre, den Text in eine Instanzvariable zu geben. Die Idee dahinter ist, dass eine Funktion möglichst wenige Argumente (am besten Null) haben sollte. Ich würde für dieses Beispiel dagegen argumentieren, da der Text nur für die Ausgabe genutzt wird und danach gleich wieder vergessen wird. Entsprechend macht es keinen Sinn, diesen als Instanzvariable zu "speichern" (auch wegen dem GC, wie oben angedeutet). Du könntest natürlich folgendes machen:
Code:
def speak(self):
    self.stimme.speak(self.speech)

def sprachausgabe_beispiel1(self, event):
    self.speech = "Blödmann"
    self.speak()
    self.speech = None
Das ist allerdings mehr Code. Und weniger Code ist immer besser! (In Code, den du nicht schreibst, kann auch kein Bug stecken :wink:)

Code:
from Tkinter import *
#import win32com.client

"""
Stimme
------------------------------------------------------------
Es greift auf die Windows eigene Stimme zu.
Es ist möglich eine Eingabe von Hand zu tätigen oder einen Standardsatz
aus zu wählen.
"""


class Stimme():
    def __init__(self):
        self.fenster = Tk()
        self.stimme = win32com.client.Dispatch("SAPI.SpVoice")
        self.fenster.title("Stimme v2.1")
        self.frame_oben = Frame(master=self.fenster, relief=RIDGE, bd=3)
        self.frame_oben.pack(padx=10, pady=10)

        self.frame_unten = Frame(master=self.fenster, relief=RIDGE, bd=3)
        self.frame_unten.pack(padx=10, pady=10)

        self.eingabe = Entry(master=self.frame_oben, width=60)
        self.eingabe.pack(padx=10, pady=10)
        self.eingabe.bind("<Return>", self.sprachausgabe_entry)

        self.button = Button(master=self.frame_oben, text="Sprechen", width=10, height=3)
        self.button.pack(padx=10, pady=10)
        self.button.bind("<Button-1>", self.sprachausgabe_entry)

        self.beispiel1 = Button(master=self.frame_unten, text="Blödmann!", width=54)
        self.beispiel1.pack()
        self.beispiel1.bind("<Button-1>", self.sprachausgabe_beispiel1)

        self.beispiel2 = Button(master=self.frame_unten, text="Wie geht es dir?", width=54)
        self.beispiel2.pack()
        self.beispiel2.bind("<Button-1>", self.sprachausgabe_beispiel2)

        self.beispiel3 = Button(master=self.frame_unten, text="Wie ist das Wetter heute?", width=54)
        self.beispiel3.pack()
        self.beispiel3.bind("<Button-1>", self.sprachausgabe_beispiel3)

        self.fenster.mainloop()

    def speak(self, speech):
        self.stimme.speak(speech)

    def sprachausgabe_entry(self, event):
        self.speak(self.eingabe.get())
        self.eingabe.delete(0, 'end')

    def sprachausgabe_beispiel1(self, event):
        self.speak("Blödmann!")

    def sprachausgabe_beispiel2(self, event):
        self.speak("Wie geht es dir?")

    def sprachausgabe_beispiel3(self, event):
        self.speak("Wie ist das Wetter heute?")

def main():
    vocal = Stimme()


if __name__ == "__main__":
    main()

Edit: Musste den Code nochmals formatieren (Neuzeilen machen mir irgendwie Probleme...)
 
Zuletzt bearbeitet:
hi!

Danke für die große Mühe die du dir gemacht hast.
Für das Problem mit den Standardsätzen habe ich mittlerweile den Tipp "partial" aus "functools" bekommen. Damit kann ich "command" dann nicht nur eine aus zu führende Funktion übergeben sondern dieser auch ein Argument hinterher schicken. Das löst dann das Problem mit den drei fast identischen Methoden am Ende. Außerdem sind dann die Standartsätze sehr einfach zu erweitern.

Das ganze sieht dann so aus:
Code:
# Bitte erschlagt mich nicht wegen der länge der Zeile.
 for text in ["Blödmann!", "Dusselige Kuh!", "Wie geht es dir?", "Wie ist das Wetter heute?"]:
            tk.Button(master = self.frame_unten, text = text, width = 54, command = partial(self.stimme.speak, text), height = 3, font=("Arial", 15)).pack()
 
Zurück
Oben