Wie nutzte ich die Variabel von einer anderen Funktion weiter?

Fragen zu Tkinter.
Antworten
Frederick
User
Beiträge: 3
Registriert: Dienstag 24. März 2020, 18:46

Hallo Zusammen, ich bin ein Anfänger beim Thema Python. Ich habe momentan ein Projekt bei dem ich probieren möchte soviel wie es geht bei turtle zu ergänzen. Bisher lässt sich die "Turtle" mit den Pfeiltasten steuern und man kann die Geschwindigkeit dieser mit einem Slider anpassen. Zudem ist es möglich die Form zu ändern. Nun zu meiner Frage:
Ich wollte es so machen, dass man die Farbe per Klick anpassen kann. Dies funktioniert auch schon aber wenn man in dieses Hellgraue Feld drückt um den Farbcode einzugeben kann ich den zwar speichern aber weiß nicht ganz wie ich den wert aus der Funktion "Aussuchen" in die Funktion "Farbwechsel" bekomme. Ich bin mir relativ sicher das es daran liegt, weil es wenn man den Code aus der Funktion "Farbwechsel" nimmt und ihn "einfach so" dahin schreibt funktioniert es.

PS: Der Code ist eher auf die Funktion ausgelegt desshalb ist dort wahrscheinlich auch viel Luft nach oben desshalb würde ich mich auch hier über Feedback freuen. Vielen Dank schonmal im Voraus für die Mühe.

Code: Alles auswählen

from turtle import *        #Alles von turtle importieren
from tkinter import*        #Alles von tkinter importieren
import time


farbcode = "#000000"
delai=50


def Schildkröte():          
    shape("turtle")
def Kreis():              
    shape("circle")
def Dreieck():              
    shape("triangle")
def Pfeil():                
    shape("arrow")
def Quadrat():             
    shape("square")
def Classic():             
    shape("classic")

    
def Schwarz():              
    pencolor("black")
def Rot():           
    pencolor("red")
def Grün():            
    pencolor("green")
def Weiß():             
    pencolor("white")
def Cyan():              
    pencolor("cyan")
def Magenta():              
    pencolor("magenta")
def Gelb():              
    pencolor("yellow")
def Blau():              
    pencolor("blue")

def Aussuchen():            
    aussuchen = Tk()
    Label(aussuchen, text="Farbcode: ").grid(row=0)
    Eingabe = Entry(aussuchen)
    Eingabe.grid(row=0, column=1)
    buttonFarbcode=Button(aussuchen, text='anwenden',command=Farbwechsel)
    buttonFarbcode.grid(row=1, column=1, sticky="w", pady=4)
    farbcode = Eingabe.get()
    return farbcode
def Farbwechsel():
    
    pencolor(farbcode)
       
def left():                 #Methode left definieren
    speed(regler.get())     #speed auf regler Variabel anpassen
    setheading(180)         #Auf 180° drehen
    fd(100)                 #100 nach vorne gehen
    delay(delai)            
    
def right():                #Methode right definieren
    speed(regler.get())     #speed auf regler Variabel anpassen
    setheading(0)           #Auf 0° drehen
    fd(100)                 #100 nach vorne gehen
    delay(delai)            
    
def up():                   #Methode up definieren
    speed(regler.get())     #speed auf regler Variabel anpassen
    setheading(90)          #Auf 90° drehen
    fd(100)                 #100 nach vorne gehen
    delay(delai)            
    
def down():                 #Methode down definieren
    speed(regler.get())     #speed auf regler Variabel anpassen
    setheading(270)         #Auf 270° drehen
    fd(100)                 #100 nach vorne gehen
    delay(delai)            
    
def back():                 #Methode back definieren
    undo()                  #letzte Aktion löschen
    delay(delai)            
main = Tk()                 #Fenster erstellen/öffnen


main.geometry("175x400")
main.title("test")

lab1 = Label(main, text="Geschwindigkeit", width=20, bg="#BBBBBB")   #Label erstellen darauf steht Geschwindigkeit weite von 20 und Hintergrundfarbe "#BBBBBB"
lab2 = Label(main, text="Form", width=20, bg="#BBBBBB")
lab3 = Label(main, text="Farbe", width=20, bg="#BBBBBB")
regler = Scale(main, from_=1, to=10, troughcolor="green", orient=HORIZONTAL) #regler erstellen von 1-10 mit der Farbe "green" und horizontal ausgerichtet
regler.set(10)              #regler auf 10 setzen
       

ikon = IntVar()
ikon.set(0)
rb0 = Radiobutton(main, text="Schildkröte", variable = ikon, value = 0,command=Schildkröte)
rb1 = Radiobutton(main, text="Kreis",variable = ikon, value = 1,command=Kreis)
rb2 = Radiobutton(main, text="Dreieck",variable = ikon, value = 2,command=Dreieck)
rb3 = Radiobutton(main, text="Pfeil",variable = ikon, value = 3,command=Pfeil)
rb4 = Radiobutton(main, text="Quadrat",variable = ikon, value = 4,command=Quadrat)
rb5 = Radiobutton(main, text="Classic",variable = ikon, value = 5,command=Classic)

buttonSchwarz = Button(main,text="    ",activebackground="black",bg="black",command=Schwarz)
buttonRot = Button(main,text="    ",activebackground="red",bg="red",command=Rot)
buttonGrün = Button(main,text="    ",activebackground="green",bg="green",command=Grün)
buttonBlau = Button(main,text="    ",activebackground="blue",bg="blue",command=Blau)
buttonCyan = Button(main,text="    ",activebackground="cyan",bg="cyan",command=Cyan)
buttonMagenta = Button(main,text="    ",activebackground="magenta",bg="magenta",command=Magenta)
buttonGelb = Button(main,text="    ",activebackground="yellow",bg="yellow",command=Gelb)
buttonWeiß = Button(main,text="    ",activebackground="white",bg="white",command=Weiß)
buttonkp = Button(main,text="    ",activebackground="#BBBBBB",bg="#BBBBBB",command=Aussuchen)


    
rb0.grid(row=4,column=0,columnspan=5, sticky= "w")
rb1.grid(row=5,column=0,columnspan=5, sticky= "w")
rb2.grid(row=6,column=0,columnspan=5, sticky= "w")
rb3.grid(row=7,column=0,columnspan=5 ,sticky= "w")
rb4.grid(row=8,column=0,columnspan=5, sticky= "w")
rb5.grid(row=9,column=0,columnspan=5, sticky= "w")

regler.grid(row=2,column=0,columnspan=5,pady=5)               #regler auf seite darstellen

lab1.grid(row = 1,column=0,columnspan=5)                 #label auf Seite darstellen
lab2.grid(row=3,column=0,columnspan=5)
lab3.grid(row=10,column=0,columnspan=5)

buttonSchwarz.grid(row=11,column=0,padx=5,pady=5,sticky="nsew")
buttonWeiß.grid(row=11,column=1,padx=5,pady=5,sticky="nsew")
buttonGrün.grid(row=11,column=2,padx=5,pady=5,sticky="nsew")
buttonMagenta.grid(row=12,column=0,padx=5,pady=5,sticky="nsew")
buttonBlau.grid(row=12,column=1,padx=5,pady=5,sticky="nsew")
buttonCyan.grid(row=12,column=2,padx=5,pady=5,sticky="nsew")
buttonGelb.grid(row=13,column=0,padx=5,pady=5,sticky="nsew")
buttonRot.grid(row=13,column=1,padx=5,pady=5,sticky="nsew")
buttonkp.grid(row=13,column=2,padx=5,pady=5,sticky="nsew")


  
shape("turtle")

listen()                    #System soll auf Eingaben reagieren

onkey(left, "Left")         #Wenn ich "Left" drücke soll die Methode left abgerufen werden
onkey(right, "Right")       #Wenn ich "Right" drücke soll die Methode right abgerufen werden
onkey(up, "Up")             #Wenn ich "Up" drücke soll die Methode up abgerufen werden
onkey(down, "Down")         #Wenn ich "Down" drücke soll die Methode down abgerufen werden
onkey(back, "BackSpace")    #Wenn ich "BackSpace" drücke soll die Methode back abgerufen werden

main.mainloop() 

Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

`return` kann man mit GUI-Callbacks nicht benutzen, was soll die GUI auch mit dem Rückgabewert anfangen?

Du mußt in `Farbwechsel` schon die `Eingabe` verarbeiten. Da Turtle schon ein Tk-Fenster benutzt, müssen weitere Fenster per TopLevel erzeugt werden.

Code: Alles auswählen

def Aussuchen():            
    aussuchen = TopLevel()
    Label(aussuchen, text="Farbcode: ").grid(row=0)
    Eingabe = Entry(aussuchen)
    Eingabe.grid(row=0, column=1)
    buttonFarbcode=Button(aussuchen, text='anwenden',command=lambda: Farbwechsel(Eingabe))
    buttonFarbcode.grid(row=1, column=1, sticky="w", pady=4)

def Farbwechsel(Eingabe):
    farbcode = Eingabe.get()
    pencolor(farbcode)
Frederick
User
Beiträge: 3
Registriert: Dienstag 24. März 2020, 18:46

Vielen Danke für die schnelle Antwort. Es funktioniert zwar aber ich habe noch zwei Fragen dazu:

Was macht dieses lambda, sorgt das nur dafür das ich sagen kann Farbwechsel "von" Eingabe?

Was ist der unterschied zwischen Toplevel() und Tk() weil ich kann mit beiden Fenster erzeugen.

Danke für die Mühe.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Frederick: Dass das für Dich überhaupt funktioniert grenzt an ein Wunder, denn Du erstellst ein `Tk`-Objekt, das `turtle`-Modul erstellt seinerseits aber auch eines. Es darf aber nur ein solches Objekt geben, denn das ist *das* Hauptfenster. Weitere Fenster kann man mit `tkinter.Toplevel` erstellen.

Sternchen-Importe sind Böse™. Bei `tkinter` holt man sich so fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Wenn Du dann noch aus einem anderen Modul per Sternchen importierst, wird es schwierig zu sehen wo eigentlich welcher Name her kommt. Noch unübersichtlicher wird es wenn Du per * Namen importierst und dann selbst Funktionen mit diesen Namen definierst. Kannst Du auf Anhieb sagen welche das bei Dir sind?

Kommentare sind nicht dazu da zu beschreiben *was* der Code macht, denn das steht da ja bereits als Code, sondern warum der das so macht. Sofern das nicht offensichtlich ist.

`time` wird importiert, aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Womit `main` für das Fenster mit den Einstellungen kein guter Name ist.

Variablen haben auf Modulebene nichts zu suchen. Alles was eine Funktion oder Methode ausser Konstanten benötigt wird als Argument(e) übergeben.

Etwas `delai` nennen weil es schon `delay` gibt ist so gar keine gute Idee. Da es sich um eine Konstante handelt ist es kein Problem diesen Namenskonflikt ohne Rechtschreibfehler aufzulösen: Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Die Fenstergrösse sollte man nicht selbst vorgeben. Das Fenster ist automatisch gross genug um alle mit `grid()` angeordneten Anzeigeelemente aufzunehmen.

Die Reihenfolge des Codes der den Fensterinhalt aufbaut ist unübersichtlich, weil das über die ganze Funktion verteilt ist. Man kann daraus nicht leicht ersehen wie die GUI aufgebaut ist und falls man beispielsweise die Abschnitte umordnen möchte oder in einzelne Funktionen oder `Frame`\s herausziehen, muss man sich das über viele Zeilen verteilt zusammensuchen.

Wenn man Namen nummeriert möchte man entweder bessere Namen, oder eine Datenstruktur statt Einzelwerte. Oft eine Liste. Einige Namen in dem Code kann man aber auch ganz weg lassen wenn man sie nicht wirklich braucht/verwendet.

Bei den Radiobuttons möchte man beispielsweise gar keine Namen haben und das alles auch nicht sechs mal hinschreiben. Für so etwas gibt es Schleifen. Was man auch nicht braucht sind die einzelnen Funktionen für die Formen die jeweils nur `turtle.shape()` mit einem anderen Argument aufrufen. Da reicht es statt numerischen Werten in `ikon` zu speichern, eine `tkinter.StringVar` zu erstellen, die den jeweiligen Formnamen für `turtle.shape()` hält, und als `command` dann einen Lambda-Ausdruck der sich diesen Wert holt und `turtle.shape()` damit aufruft.

Auch die Buttons für die Farben braucht man keine trivialen Funktionen zum Setzen der Farbe und auch die ganzen `Button`-Objekte muss man nicht an individuelle Namen binden. Denn auch hier kann man das mit einer Schleife über die gemeinsamen Daten, in diesem Fall der Farbname, ohne die ganzen Code-Wiederholungen lösen.

`Aussuchen()` sagt nichts darüber aus *was* die Funktion aussucht.

Einen Farbauswahldialog gibt es schon fertig in `tkinter.colorchooser`, den muss man sich nicht selber schreiben.

Die Richtungsfunktionen machen fast das gleiche, einzig die Ausrichtung der Schildkröte unterscheidet sich. Das sollte also nur *eine* Funktion sein, die diese Ausrichtung als Argument bekommt (und `regler`). Für die Richtungswerte bietet es sich an Konstanten zu definieren und die mit Hilfe des `enum`-Moduls zu einem eigenen Typ zusammenzufassen.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
import turtle
from enum import Enum
from functools import partial
from itertools import count
from tkinter.colorchooser import askcolor

DELAY = 50  # In milliseconds.
MOVE_DISTANCE = 100
HEADING_BACKGROUND_COLOR = "#BBBBBB"


class Heading(Enum):  # Values in degrees.
    RIGHT = 0
    UP = 90
    LEFT = 180
    DOWN = 270


def choose_color():
    _, color = askcolor()
    if color is not None:
        turtle.pencolor(color)


def move_pen(speed_var, heading):
    turtle.speed(speed_var.get())
    turtle.setheading(heading.value)
    turtle.forward(MOVE_DISTANCE)
    turtle.delay(DELAY)


def move_back():
    turtle.undo()
    turtle.delay(DELAY)


def main():
    turtle.shape("turtle")

    tool_window = tk.Toplevel()
    tool_window.title("test")

    buttons_per_row = 3
    row_counter = count()

    tk.Label(
        tool_window, text="Geschwindigkeit", bg=HEADING_BACKGROUND_COLOR
    ).grid(
        row=next(row_counter),
        column=0,
        columnspan=buttons_per_row,
        sticky=tk.EW,
    )

    speed_scale = tk.Scale(
        tool_window, from_=1, to=10, troughcolor="green", orient=tk.HORIZONTAL
    )
    speed_scale.set(10)
    speed_scale.grid(
        row=next(row_counter), column=0, columnspan=buttons_per_row, pady=5
    )

    tk.Label(tool_window, text="Form", bg=HEADING_BACKGROUND_COLOR).grid(
        row=next(row_counter),
        column=0,
        columnspan=buttons_per_row,
        sticky=tk.EW,
    )

    shape_data = [
        ("Schildkröte", "turtle"),
        ("Kreis", "circle"),
        ("Dreieck", "triangle"),
        ("Pfeil", "arrow"),
        ("Quadrat", "square"),
        ("Classic", "classic"),
    ]
    shape_name_var = tk.StringVar(value=shape_data[0][1])
    for row_offset, (text, shape_name) in enumerate(
        shape_data, next(row_counter)
    ):
        tk.Radiobutton(
            tool_window,
            text=text,
            variable=shape_name_var,
            value=shape_name,
            command=lambda: turtle.shape(shape_name_var.get()),
        ).grid(
            row=next(row_counter),
            column=0,
            columnspan=buttons_per_row,
            sticky=tk.W,
        )

    tk.Label(tool_window, text="Farbe", bg=HEADING_BACKGROUND_COLOR).grid(
        row=next(row_counter),
        column=0,
        columnspan=buttons_per_row,
        sticky=tk.EW,
    )

    button_text = " " * 4
    grid_options = {"padx": 5, "pady": 5, "sticky": tk.NSEW}
    row_offset = next(row_counter)
    i = 0
    for i, color in enumerate(
        ["black", "white", "green", "magenta", "blue", "cyan", "yellow", "red"]
    ):
        row_index, column_index = divmod(i, buttons_per_row)
        tk.Button(
            tool_window,
            text=button_text,
            activebackground=color,
            bg=color,
            command=partial(turtle.pencolor, color),
        ).grid(row=row_offset + row_index, column=column_index, **grid_options)

    row_index, column_index = divmod(i + 1, buttons_per_row)
    tk.Button(
        tool_window,
        text=button_text,
        activebackground="#BBBBBB",
        bg="#BBBBBB",
        command=choose_color,
    ).grid(row=row_offset + row_index, column=column_index, **grid_options)

    for key_name, heading in [
        ("Left", Heading.LEFT),
        ("Right", Heading.RIGHT),
        ("Up", Heading.UP),
        ("Down", Heading.DOWN),
    ]:
        turtle.onkey(partial(move_pen, speed_scale, heading), key_name)

    turtle.onkey(move_back, "BackSpace")

    turtle.listen()
    turtle.done()


if __name__ == "__main__":
    main()
Die `main()`-Funktion ist für meinen Geschmack zu umfangreich, die würde ich sinnvoll aufteilen. Den Fensterinhalt vielleicht auch auf Frames. Dann brauchen all die oberen Elemente auch nichts davon wissen das unten ein Grid ist das drei Zellen breit ist. Das ist momentan unschön gelöst.

Wenn man das Werkzeugfenster schliesst, sollte sich vielleicht auch das Hauptfenster schliessen. Sonst ärgert sich der Anwender das es weg ist wenn man es einmal geschlossen hat, und das man es auch nicht wiederbekommt. Oder man nimmt die objektorientierte API vom `turtle`-Modul und erstellt nur *ein* Fenster mit einem eigenen `Canvas`, auf den man eine Schildkröte zeichnen lässt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frederick
User
Beiträge: 3
Registriert: Dienstag 24. März 2020, 18:46

@__blackjack__: Danke für dein Feedback und sogar das erstellen eines mehr als verbesserten Codes von dem ich viel lernen kann. Ich werde versuchen einiges davon weiter zu verwenden aber das wird wahrscheinlich nicht immer möglich sein da ich bisher gefühlt die hälfte der Befehle nicht kenne. Aber ich habe schon bei meiner Suche im Internet zu bestimmten Problemen gemerkt das es definitiv viel schlauere Wege gibt z.B. mehrere Buttons zu erstellen.
Antworten