Follower

Sonntag, 18. Februar 2018

Menü-Leisten

Oh Mann, das hat mich Nerven gekostet. Da wollte ich mal eben am oberen Fensterrand ein paar Menüpunkte einrichten, aber welche Vorlage ich auch nutzte - nix tat sich. Dachte ich. Bis ich plötzlich feststellte, dass alles funktioniert hatte. Der obere Fensterrand ist tatsächlich der OBERE.

Hier:

Na super. Hab ich einfach nicht gesehen, wollte schon aufgeben. Aber das ist ein Mac-Phänomen - die Pull-Down-Menüs erscheinen am oberen Rand des Bildschirms.

Aber wie geht das überhaupt?

Hier ein einfachstes Beispiel:

from tkinter import *

def ausgeben():
    print("Wurde geklickt")

# wenn man auf die Menüpunkte klickt, wird nur diese eine Funktion ausgeführt - es wird "Wurde geklickt" ausgegeben. Ist natürlich nicht sinnvoll, aber dient der Demonstration.
from tkinter import *

Fenster = Tk()

# Das erzeugt ein Fenster

Menuleiste = Menu(Fenster)

# Menu sorgt für eine Menüleiste beim Fenster

dateiMenu = Menu(Menuleiste)
Menuleiste.add_cascade(label="Datei", menu = dateiMenu)
dateiMenu.add_command(label = "Neues Projekt anlegen", command = ausgeben)
dateiMenu.add_command(label = "Projekt öffnen", command = ausgeben)
dateiMenu.add_command(label = "Speichern", command = ausgeben)
dateiMenu.add_separator()
dateiMenu.add_command(label = "Schließen", command = exit)

# zuerst definiere einen neuen Menüpunkt, wieder mit Menu, namens dateiMenu. Mit add_cascade wird das Drop-Down-Menü erstellt, das mit label seinen namen erhält. mit menu ???
Mit add_command werden die einezelnen Punkte dem Dropdown-Menü hinzugefügt, der command-Befehlt ruft die Funktion "ausgeben" auf - dann wird immer "Wurde geklickt" ausgegeben.
add_separator fügt eine Linie ein.


einstellungenMenu = Menu(Menuleiste)
Menuleiste.add_cascade(label= "Einstellungen", menu = einstellungenMenu)
einstellungenMenu.add_command(label="Aussehen", command = ausgeben)
einstellungenMenu.add_command(label="Grundeinstellungen", command = ausgeben)

Ein weiterer Menüpunkt wird der Menüleiste hinzugefügt: einstellungenMenu mit dem Titel "Einstellungen".

Fenster.config(menu=Menuleiste)

Config sorgt dafür, dass die Sache läuft.

Und wieso muss man kein Fenster.mainloop() ans Ende setzen???



Montag, 12. Februar 2018

Eingabefelder

Mit Entry erzeugt man ein Eingabefeld. Hatte ich schon. Dort konnte ich auch mit eval direkt Funktionen ausführen lassen. Praktisch.

Was aber, wenn mit der Eingabe irgendwas passieren soll? Kommt jetzt:

from tkinter import *

def button_action():
    entry_text = eingabe.get()
    if (entry_text == ""):
        Gruss.config(text="Gib zuerst einen Namen ein.")
    else:
        entry_text = "Welcome " + entry_text + "!"
        Gruss.config(text=entry_text)

# hier wird eine Funktion definiert, die zum Zuge kommt, wenn der Button gedrückt wird. Nämlich dass sie sich den eingegebenen Text aus dem später definierten Eingabe-Feld (eingabe) holen soll. Wenn da nix steht (==""), soll im Fenster "Gib zuerst einen Namen ein." erscheinen. Ansonsten aber soll "Welcome", der eingegebene Name und ein ! erscheinen.

fenster = Tk()
fenster.title("Ich warte auf eine Eingabe von dir.")

Text = Label(fenster, text="Gib deinen Namen ein: ")

Gruss = Label(fenster)

# Hier steht erst mal nix drin, bis der Button gedrückt wird.

eingabe = Entry(fenster, bd=5, width=40)

# Hier wird die Größe des Eingabefeldes bestimmt

welcom_button = Button(fenster, text="Klick me", command=button_action)
exit_button = Button(fenster, text="Beenden", command=fenster.quit)


# Hier werden zwei Button festgelegt. Beim ersten soll die oben definierte Funktion ablaufen, beim unteren das Programm beendet werden.

Text.grid(row = 0, column = 0)
eingabe.grid(row = 0, column = 1)
welcom_button.grid(row = 1, column = 0)
exit_button.grid(row = 1, column = 1)
Gruss.grid(row = 2, column = 0, columnspan = 2)


# Das kenne ich - mit der grid Anweisung werden die Elemente angeordnet.

mainloop()


Ich wäre natürlich nie darauf gekommen, es so zu machen. Aber inzwischen tröste ich mich damit, dass ich den Code zumindest in größeren Teilen nachvollziehen kann.

Sonntag, 11. Februar 2018

Layout mit tkinter

Ich muss einfach ein wenig ausprobieren. Ich lerne, dass es neben pack (da werden die einzelnen Elemente in die zuvor festgelegten Fenster gepackt) und grid (da werden die Elemente in Reihen und Spalten gepackt) auch noch place gibt. Dazu weiter unten mehr. Die drei dürfen aber nicht in einem Fenster gleichzeitig verwendet werden. Dann passiert nix, wie ich eben ausprobiert habe.

Hier mein Versuch mit grid:

from tkinter import *
import sys

def ende():
    sys.exit()

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Text = Entry(Fenster)
Anweisung = Button(Fenster, text="Drück mich", command=Aktion)
Knopf2 = Button(Fenster, text="Ende", command=ende)
Anzeige.grid(row=0, columnspan=2)
Text.grid(row=1,columnspan=2)
Anweisung.grid(row=2, column=0)
Knopf2.grid(row=2, column=1)
Fenster.mainloop()

Ist kein sinnvolles Programm, aber sortiert die Elemente ordentlich, verändert den Text in dem Button "Anweisung", beendet das Programm auf dem Button "Knopf2" und hat sogar ein Textfeld, das sich über die Buttons zieht. Da kann man etwas eingeben, allerdings ohne dass etwas geschieht.

Und wenn man die Elemente noch etwas anders platzieren möchte, kann man den Abstand zwischen ihnen festlegen mit padx (auf der x-Achse, also waagerecht) oder pady (eben senkrecht).

from tkinter import *
import sys

def ende():
    sys.exit()

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Text = Entry(Fenster)
Anweisung = Button(Fenster, text="Drück mich", command=Aktion)
Knopf2 = Button(Fenster, text="Ende", command=ende)
Anzeige.grid(row=0, columnspan=2)
Text.grid(row=1,columnspan=2, padx = 30, pady = 60)
Anweisung.grid(row=2, column=0)
Knopf2.grid(row=2, column=1)
Fenster.mainloop()



Noch mal zurück zu pack. Hier kann man auch die Platzierung ändern, da müssen nicht alle Elemente untereinander stehen wie hier:

from tkinter import *
import sys

def ende():
    sys.exit()

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Text = Entry(Fenster)
Anweisung = Button(Fenster, text="Drück mich", command=Aktion)
Knopf2 = Button(Fenster, text="Ende", command=ende)
Anzeige.pack()
Text.pack()
Anweisung.pack()
Knopf2.pack()
Fenster.mainloop()






Mit side = LEFT in die Klammer hinter pack werden die Elemente alle linksbündig nebeneinander gesetzt. mit side = RIGHT rechtsbündig und mit side = BOTTOM nach unten. side = TOP ist die Voreinstellung (default), muss ich also nicht eigens eingeben.

Sieht dann so aus:

from tkinter import *
import sys

def ende():
    sys.exit()

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Text = Entry(Fenster)
Anweisung = Button(Fenster, text="Drück mich", command=Aktion)
Knopf2 = Button(Fenster, text="Ende", command=ende)
Anzeige.pack()
Text.pack(side=BOTTOM)
Anweisung.pack(side=LEFT)
Knopf2.pack(side=LEFT)
Fenster.mainloop()


Die beiden Buttons sitzen an der linken Seite, das Textfeld unten (BOTTOM). Setze ich den zweiten Button auf side=RIGHT, rutscht er an die rechte Seite.




Und dann gibt es noch die Möglichkeit, die Elemente nach genauen Vorgaben anzuordnen.

from tkinter import *
import sys

def ende():
    sys.exit()

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Text = Entry(Fenster)
Anweisung = Button(Fenster, text="Drück mich", command=Aktion)
Knopf2 = Button(Fenster, text="Ende", command=ende)
# jetzt wird die Fenstergröße festgelegt:

Fenster.geometry("400x200")
Anzeige.place(x = 0, y = 0, width=100, height=30)
Text.place(x = 120, y = 0, width=210, height=50)
Anweisung.place(x = 20, y = 70, width=150, height=40)
Knopf2.place(x = 190, y = 70, width=60, height=40)

# und dann die Lage (Horizontal und vertikal) und die Größe der Elemente. Das ist ziemliche Fummelarbeit.

Fenster.mainloop()



Ich verstehe schon, dass man diese Variante nur nutzen soll, wenn es gar nicht anders geht. Ist wirklich aufwändig.

Samstag, 10. Februar 2018

Buttons belegen

Wieder ein Beispiel, von dem ich etwas lernen sollte, das aber auch noch Fragen aufwirft.

from tkinter import *
import sys

# Ich importiere also alle tkinter Befehel und die sys Bibliothek

def ende():
    sys.exit()

#ich definiere ende, und dazu nutze ich exit aus der sys-Bibliothek, womit offenbar ein Programm beendet wird.

Fenster = Tk()
Anzeige = Label(Fenster, text="Guten Tag")
Knopf = Button(Fenster, text="Ende", command=ende)
Anzeige.pack()
Knopf.pack()
Fenster.mainloop()

Was Neues: mit command wird dem Button die Funktion übergeben, also ihm mitgeteilt, was er beim Anklicken tun soll. Komisch - in den bisherigen Beispielen taten die Buttons doch auch was, ohne die command option. Hier übrigens wird das Programm beendet, wenn man den Button anklickt. Soll ja auch so sein laut Funktion.
Das mit dem command möchte ich näher verstehen. Und finde ein neues Beispiel:

from tkinter import *

def Aktion():
    Anweisung.config(text="Ich wurde geändert!")

# oops, eine neue Funktion: config - Bestandteil von tkinter? offenbar

Fenster = Tk()
Fenster.title("Ich mache nun was.")

change = Button(Fenster, text="Ändern", command=Aktion)
Exit = Button(Fenster, text="Beenden", command=Fenster.quit)

# noch was Neues: quit - damit wird wohl auch ein Programm beendet. Zumindest in tkinter

Anweisung = Label(Fenster, text="Ich bin eine Anweisung:\n\
Klicke auf 'Ändern'.")



# das mit dem \n\ hatte ich schon erklärt - erzwingt einen Zeilenumbruch.

Info = Label(Fenster, text="Ich bin eine Info:\n\
Der Beenden-Button schließt das Programm.")

Anweisung.pack()
change.pack()
Info.pack()
Exit.pack()

Fenster.mainloop()

Tja, offenbar kann man mit command dem Button mitteilen, welche Funktion der ziehen soll

Grid - Einen Taschenrechner programmieren

Noch ein Beispiel für grafische Benutzeroberflächen (GUI) mit tkinter. Diesmal ein Taschenrechner. Wieder aus dem Video-Tutorial, und ich hab erst mal nix anderes gemacht, als es Zeile für Zeile abzutippen. Hatte ständig Fehler drin, unfassbar. Aber tatsächlich lernt man ja aus seinen Fehlern ;-)

Ich kopier mal das ganze Programm hier rein. Und dazwischen das, was ich als Erklärung verstanden haben, mit

from tkinter import *
from math import *

# erinnert Ihr euch? Wenn man alle Elemente aus einer Bibliothek nutzen möchte, kann man diese Form mit dem * wählen, dann muss man den Namen der Bibliothekt nicht jedes Mal vor ein Element schreiben

def calculate(event):
    gleichung = t.get()
    t.delete(0, END)
    try:
            t.insert(0, eval(gleichung))
    except:
            t.insert(0, "invalid syntax")

# Hier wird die Funktion calculate definiert. Zuerst wird mit t.get() das, was in das Textfeld t eingegeben wird, geholt, dann wird das Textfeld geleert (t.delete) und auf 0 gesetzt.
END habe ich noch nicht verstanden.
# Darunter etwas, das ich schon mal hatte: try und except - der Umgang mit Ausnahmen. Also wenn jemand eine Eingabe macht, die unsinnig ist.

top = Tk()

t = Entry(top)
t.grid(row=0,columnspan=3)


# Nun was Neues, nämlich grid. Damit können grafische Elemente in Reihen und Spalten angeordnet werden, dazu nutzt man row und column. Die erste Reihe erhält den Wert 0, weil man ja immer mit 0 anfängt zu zählen. Und columnspan sagt, dass das Textfeld sich über 3 Spalten zieht.

B1 = Button(top, text="1")
B1.grid(row=1,column=0)
B2 = Button(top, text="2")
B2.grid(row=1,column=1)
B3 = Button(top, text="3")
B3.grid(row=1,column=2)
B4 = Button(top, text="4")
B4.grid(row=2,column=0)
B5 = Button(top, text="5")
B5.grid(row=2,column=1)
B6 = Button(top, text="6")
B6.grid(row=2,column=2)
B7 = Button(top, text="7")
B7.grid(row=3,column=0)
B8 = Button(top, text="8")
B8.grid(row=3,column=1)
B9 = Button(top, text="9")
B9.grid(row=3,column=2)
B0 = Button(top, text="0")
B0.grid(row=4,column=1)
Bplus = Button(top, text="+")
Bplus.grid(row=0,column=3)
Bminus = Button(top, text="-")
Bminus.grid(row=1,column=3)
Bmal = Button(top, text="*")
Bmal.grid(row=2,column=3)
Bdurch = Button(top, text="/")
Bdurch.grid(row=3,column=3)
Berg = Button(top, text="=")
Berg.grid(row=4,column=3)
Bdel = Button(top, text="del")
Bdel.grid(row=4,column=2)


# Das ist alles nicht so schwer: Jeder Button wird in das top-Fenster eingebaut und mit einem Text versehen, dann mit .grid einer Reihe und einer Spalte zugeordnet.

B1.bind("<Button-1>", lambda x: t.insert(END,"1"))
B2.bind("<Button-1>", lambda x: t.insert(END,"2"))
B3.bind("<Button-1>", lambda x: t.insert(END,"3"))
B4.bind("<Button-1>", lambda x: t.insert(END,"4"))
B5.bind("<Button-1>", lambda x: t.insert(END,"5"))
B6.bind("<Button-1>", lambda x: t.insert(END,"6"))                       
B7.bind("<Button-1>", lambda x: t.insert(END,"7"))
B8.bind("<Button-1>", lambda x: t.insert(END,"8"))
B9.bind("<Button-1>", lambda x: t.insert(END,"9"))
B0.bind("<Button-1>", lambda x: t.insert(END,"0"))
Bplus.bind("<Button-1>", lambda x: t.insert(END,"+"))
Bminus.bind("<Button-1>", lambda x: t.insert(END,"-"))
Bmal.bind("<Button-1>", lambda x: t.insert(END,"*"))
Bdurch.bind("<Button-1>", lambda x: t.insert(END,"/"))

# Hier werden nun die ganzen Buttons mit Funktionen belegt - d.h. was passiert, wenn man sie mit der linken Maustaste ("<Button-1>") anklickt. Dafür gibt es die lambda Funktion - hab ich schon mal erklärt, auch wenn ich immer noch nicht sicher bin, ob ich das richtig verstanden habe. Und wieder das mit END, was mir auch noch nicht klar ist.

Berg.bind("<Button-1>", calculate)
Bdel.bind("<Button-1>", lambda x: t.delete(0, END))

# Mit dem Button Berg wird die Funktion aufgerufen, die oben definiert wurde, und mit Bdel wird die Eingabe gelöscht.

top.mainloop()

Das Ergebnis? Ein einfacher Taschenrechner. Cool. :-)


Textfeld im Fenster

Weiter geht es mit tkinter.

Diesmal ein kleines Programm, bei dem man etwas eingeben kann, hier eine Rechenaufgabe, die dann gelöst wird bei Knopfdruck.

Mal zuerst das ganze Programm:

import tkinter

def handleButton(event):
    Rahmen['text'] = eval(Textfeld.get())

Fenster = tkinter.Tk()

Fenster.title("Mein Programm")

Textfeld = tkinter.Entry(Fenster)

Rahmen = tkinter.Label(Fenster, text='Start')

Knopf = tkinter.Button(Fenster, text="Berechnen")

Textfeld.pack()

Rahmen.pack()

Knopf.pack()

Knopf.bind('<Button-1>', handleButton)

Fenster.mainloop()

Hier kommen jetzt ein paar neue Dinge hinzu. Die eval Funktion, die ja einen String erwartet, aber diesen dann ausführt je nach Funktion. Damit kann man nun rechnen. Entry erzeugt ein Textfeld. Und mit get() wird der Inhalt aus dem Textfeld gezogen. Mmmmh....




Donnerstag, 8. Februar 2018

Buttons betätigen

Damit auf der grafischen Oberfläche etwas passiert, wenn ich eine Taste bediene oder die Maus, muss man eine Funktion schreiben und dann z.B. dem Button mitteilen, wie die Funktion heißt, die aufgerufen werden soll, wenn der Button gedrückt wird. Klingt logisch.

Und schon komme ich im Buch nicht mehr mit. Da erfahre ich, wie man ein Programm beendet, das verstehe ich aber nicht.

Also zurück zum Video-Tutorial, da wird mir erklärt, wie ich dem Button sage, was er zu tun hat, wenn ich ihn anklicke.

Hier kommt es.

Zuerst sage ich ihm, was passieren soll, wenn ich ihn mit der linken Maustaste anklicke. Der Befehl heißt <button-1>, und das noch in einfachen Anführungszeichen: '<button-1'>. Eingeleitet wird er mit bind, also:
Knopf.bind('<button-1>', handleButton)

handleButton ist die Funktion, die ich noch definieren muss.

Das mache ich am Anfang.

import tkinter
i = 0
def handleButton(event):
    global i
    i = i + 1
    Rahmen['text'] = i

Fenster = tkinter.Tk()

Fenster.title("Mein Programm")

Rahmen = tkinter.Label(Fenster, text='Hallo - drück mich und ich zähle')

Knopf = tkinter.Button(Fenster, text="Drücken")

Rahmen.pack()

Knopf.pack()

Knopf.bind('<Button-1>', handleButton)

Fenster.mainloop()

Ich habe also zu Beginn die Funktion handleButton definiert (die in Klammern event stehen haben muss?) Ein Event kann eben ein Tastendruck oder ein Mausklick sein. Die Funktion, die ich programmiere, sorgt dafür, dass beim Mausklick einfach hochgezählt wird (i = i + 1). Funktioniert, aber fragt mich nicht, wieso....


Dienstag, 6. Februar 2018

Zurück zu Grafiken

Mich hat etwas der Mut verlassen, das mit den Klassen überfordert mich. Also kehre ich zurück zum meinem Lieblingstutorial. Ist ja schon seltsam. Ich fange an einer Ecke an, halte bis zu einem bestimmten Punkt durch, dann verlässt mich der Mut, ich fange was Neues an, bis ich nichts mehr begreife und dann das nächste - um dann wieder zurückzukehren zu einem anderen "Lehrer". Aber was soll's - so lange ich nicht ganz aufgebe...

Also: Ich hatte mir angesehen, wie man einen Button programmiert mit Hilfe des Moduls tkinter. Und der Button konnte sogar zählen, d.h. wenn man draufklickt, zählt er vorwärts.

Also lerne ich erst mal was über die eval-Funktion.

Das ist eine höchst seltsame Geschichte. Sie erwartet einen String, also so:

eval("3+5")  - führt aber dann die funktion in dem String aus.

print(eval("3+5")) führt dann zu 8.

Wohingegen bei

eval("print('Scherz')")

Scherz ausgegeben wird.

mmmh....

Das Video-Tutorial geht mir zu schnell. Ich recherchiere etwas und finde ein Tkinter-Tutorial. Vielleicht hilft mir das zum Verständnis.
Zum Beispiel, was ein Label ist. Aber die Erklärungen sind wie Fremdsprache. Es ist ein Widget, das man nur betrachten, aber nicht irgendwie benutzen (anklicken usw.) kann. Und was ist ein Widget? "eine Komponente eines grafischen Fenstersystems". Da kann ich ein wenig mit anfangen.

Also: Wenn man so etwas einfügt, dann macht ein Fenster auf, und ein Label dient dazu, Texte oder Bilder anzuzeigen. Aber beim Anwenden stelle ich fest, dass die Befehle nicht funktionieren - ich vermute, es basiert auf Python 2. Kann mir also doch nicht helfen. Ich bastle mir was zusammen und komme dazu:

import tkinter

Fenster = tkinter.Tk()

w = tkinter.Label(Fenster, text="Hello Tkinter!")

w.pack()

Fenster.mainloop()
Klappt, es geht ein Fenster auf - yeah. Mit dem Text: Hello Tkinter.

Fenster = tkinter.Tk() ist das Basisfenster, also sorgt dafür, dass überhaupt eins geöffnet wird. Danach können verschiedene Elemente erzeugt werden. In Klammern immer mit dem Parameter Fenster, dem "Eltern"-Fenster.
Z.B. kann ein Button eingebaut werden.

import tkinter

Fenster = tkinter.Tk()

Fenster.title("Mein Programm")

Rahmen = tkinter.Label(Fenster, text="Hello Tkinter! Alles ok?")

Knopf = tkinter.Button(Fenster, text="Drücken")

Rahmen.pack()

Knopf.pack()

Fenster.mainloop()

Mit pack wird das Element dann in das Fenster eingefügt, mit mainloop wird das Fenster aufgerufen. Alles klar? So sieht mein kleines Fenster aus. Cool.


Samstag, 3. Februar 2018

Klassen

Das wird ein weiteres Desaster in Sachen Verständnis, aber was soll's, ich muss ja dadurch. Mal eine erste Erkenntnis: Jetzt geht es um die eigentliche Objektorientierte Programmierung (OOP), das, was Python ja eigentlich ist. Was das eigentlich bedeutet, wird hier ganz anschaulich dargestellt. Versuche ich erst gar nicht, hier wiederzugeben.

Für mich erst mal zum Merken: Klassen sind die Baupläne, sie enthalten die Merkmale der Objekte und die Verfahren bzw. Methoden, wie mit den Merkmalen umgegangen wird.
Die konkreten Objekte werden in auch als Instanzen bezeichnet. Deren Eigenschaften werden auch als Attribute einer Instanz bezeichnet.

Ich erfahre, dass Listen eigentlich eine Klasse darstellen und lerne eine neue Anweisung kennen: pop

x = [3,6,9]

x[1] = 99
x.append(42)
last = x.pop()
print(x)
print(last)

Dient erst mal für mich nur der Wiederholung. Die eckigen Klammern enthalten den Index, d.h. hier also das zweite Element der Liste, das druch 99 ersetzt wird. x.append bedeutet, dass der Liste x ein Element hinzugefügt wird, die 42.
Und x.pop() gibt das letzte Element einer Liste wieder, bzw. das mit dem höchsten Index - auch die 42 und entfernt es von der Liste. Wozu das gut sein soll...
Ausgegeben wird dann:
3 99 9
42
Witzig - stelle grade fest, dass das Element 42 zwar mit pop entfernt wird, aber offenbar nur bei der Ausgabe.