after() methode

Fragen zu Tkinter.
Antworten
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Ich versuche gerade zu erreichen, dass sich in einem tkinter grid einkästen bewegen lässt und das ähnlich wie in Snake.

Alles funktioniert aber ich will das Event, dass startet wenn man die Leertaste drückt durch eine After Funktion ersetzen aber wenn ich schreibe

Code: Alles auswählen

self.root,after(1000, self.move)
funktioniert das nicht,aber ich bekomme keine Fehlermeldung ich habe das selbe versucht aber habe eine einfache Funktion aufgerufen und es hat funktioniert. Nun habe ich keine Ahnung woran es liegt.

Code: Alles auswählen

from tkinter import *
# Mir ist klar dass ein Sternchen-import nicht gut ist

class Controller(object):

    def __init__(self, parent):
        # class variables
        self.parent = parent
        # events
        self.parent.bind("<w>", self.up)
        self.parent.bind("<s>", self.down)
        self.parent.bind("<a>", self.left)
        self.parent.bind("<d>", self.right)
        # direction var
        #    0
        #  3 X 1
        #    2
        #
        self.direction = " "

    def up(self, event):
        self.direction = "up"
        self.get_direction()

    def right(self, event):
        self.direction = "right"
        self.get_direction()
        
    def down(self, event):
        self.direction = "down"
        self.get_direction()
        
    def left(self, event):
        self.direction = "left"
        self.get_direction()

    def get_direction(self):
        print(self.direction)
        return self.direction

class Field(object):

    def __init__(self, parent, field_len):
        # class Variables
        self.parent = parent
        self.field_len = field_len
        self.cont = Controller(self.parent)
        # image
        self.alpha = PhotoImage(file="pixel.gif")
        # widget list
        self.widget_list = []
        # logic list, 1 = shown
        self.pos = {"x":3, "y":3}

        # create list depht of 2
        for _ in range(0, self.field_len):
            self.widget_list.append([0] * self.field_len)
        #assign widgets to each cell of one list
        for x in range(0, self.field_len):
            for y in range(0, self.field_len):

                self.widget_list[x][y] = Label(self.parent, image=self.alpha, bg="grey", width=10, height=10)
                self.widget_list[x][y].grid(row=x, column=y, padx=0, pady=0)

        self.parent.bind("<space>", self.move)

    def move(self, event):
        print("move !")
 
        if self.cont.get_direction() == "up": self.pos["x"] -= 1
        elif self.cont.get_direction() == "right": self.pos["y"] += 1
        elif self.cont.get_direction() == "down": self.pos["x"] += 1
        elif self.cont.get_direction() == "left": self.pos["y"] -= 1
        
        else: print("fail")

        # get all cells
        for x in range(0, self.field_len):
            for y in range(0, self.field_len):

                # highlight the actual active cell
                if self.pos["x"] == x and self.pos["y"] == y:
                    self.widget_list[x][y].config(bg="gold")
                # set the color to defaut
                else:
                    self.widget_list[x][y].config(bg="grey")
                    

        
                    

        

class Main(object):

    def __init__(self, root, field):
        # class variables
        self.root = root
        # setup window
        self.root.title("Snake")
        # create game field
        self.field = field
        self.root.mainloop()



if __name__ == "__main__":
    
    root = Tk()
    field = Field(root, 20)

    Main(root, field)
BlackJack

@misterjosu: Also *da* sollte es aber eine Ausnahme geben, denn der Name `after` ist lokal nicht definiert. Und nach dem Komma sollte man auch ein Leerzeichen setzen wegen der Lesbarkeit. Dann fällt einem auch eher auf, dass man da gar kein Tupel erzeugen wollte. ;-)
Sirius3
User
Beiträge: 17752
Registriert: Sonntag 21. Oktober 2012, 17:20

BlackJack hat geschrieben:[…] denn der Name `after` ist lokal nicht definiert.
wer weiß, was sonst alles noch per '*' importiert wurde :P
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

@BlackJack

Hatt mich vertippt, das sollt so aussehen:

Code: Alles auswählen

self.root.after(1000, self.move)
Müsste das nicht funktionieren ?
BlackJack

@misterjosu: So grundsätzlich ja. Du hast das auch in die `move()`-Methode selber eingebaut, damit es nicht nur *einmal* ausgeführt wird?
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Wie meinst du dass mit dem "eingebaut"
?
BlackJack

@misterjosu: Na wenn `move()` *regelmässig* ausgeführt werden soll und nicht nur *einmal* muss die `move()`-Methode dafür sorgen, dass sie nach einer Sekunde wieder aufgerufen wird.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Also sollte ich "self.parent.after(1000, self.move)" nicht in __init__ sondern direkt in move schreiben und move dann einmal aufrufen ?
BlackJack

@misterjosu: Ja so könnte man das machen.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Ok. Ich versuchs nacher und ooste dann das ergebnis. DANKE !
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Was würdet ihr am code nochanderst machen ? einfacher ?

Code: Alles auswählen

from tkinter import *
# Mir istklar dass ein Sternchen-import nicht gut ist

class Controller(object):

    def __init__(self, parent):
        # class variables
        self.parent = parent
        # events
        self.parent.bind("<w>", self.up)
        self.parent.bind("<s>", self.down)
        self.parent.bind("<a>", self.left)
        self.parent.bind("<d>", self.right)
        # direction var
        self.direction = " "

    def up(self, event):
        self.direction = "up"
        self.get_direction()

    def right(self, event):
        self.direction = "right"
        self.get_direction()
        
    def down(self, event):
        self.direction = "down"
        self.get_direction()
        
    def left(self, event):
        self.direction = "left"
        self.get_direction()

    def get_direction(self):
        return self.direction

class Field(object):

    def __init__(self, parent, field_len):
        # class Variables
        self.parent = parent
        self.field_len = field_len
        self.cont = Controller(self.parent)
        # image
        self.alpha = PhotoImage(file="pixel.gif")
        # widget list
        self.widget_list = []
        # logic list, 1 = shown
        self.pos = {"x":3, "y":3}

        # create list depht of 2
        for _ in range(0, self.field_len):
            self.widget_list.append([0] * self.field_len)
        #assign widgets to each cell of one list
        for x in range(0, self.field_len):
            for y in range(0, self.field_len):

                self.widget_list[x][y] = Label(self.parent, image=self.alpha, bg="grey", width=10, height=10)
                self.widget_list[x][y].grid(row=x, column=y, padx=0, pady=0)


    def move(self):

        self.parent.after(500, self.move)
 
        if self.cont.get_direction() == "up": self.pos["x"] -= 1
        elif self.cont.get_direction() == "right": self.pos["y"] += 1
        elif self.cont.get_direction() == "down": self.pos["x"] += 1
        elif self.cont.get_direction() == "left": self.pos["y"] -= 1
        
        else: print("fail")

        # get all cells
        for x in range(0, self.field_len):
            for y in range(0, self.field_len):

                # highlight the actual active cell
                if self.pos["x"] == x and self.pos["y"] == y:
                    self.widget_list[x][y].config(bg="gold")
                # set the color to defaut
                else:
                    self.widget_list[x][y].config(bg="grey")
                    

        
                    

        

class Main(object):

    def __init__(self):
        # class variables
        self.root = Tk()
        # setup window
        self.root.title("Snake")
        # create game field
        self.field = Field(self.root, 12)
        self.field.move()
        mainloop()



if __name__ == "__main__":
    Main()
BlackJack

@misterjosu: Der Sternchen-Import ist ja schon entsprechend kommentiert.

Der `Controller` enthält nicht wirklich etwas den könnte man soweit zusammen streichen, dass fraglich wird ob man dafür überhaupt eine Klasse braucht. Der `get_direction()`-Aufruf in den Richtungsmethoden ist sinnfrei, wie die `get_direction()`-Methode an sich. Die Richtungsmethoden machen im Grunde auch alle das selbe, nur mit einem anderen Wert, das könnte man also in einer Methode zusammenfassen in der das `direction()`-Attribut gesetzt wird. Dann hätte man aber einen sinnfreien Setter, den man auch wieder einsparen könnte. Wird das `parent`-Attribut vom `Controller` eigentlich irgendwo benutzt?

Die Indirektion über Zeichenketten die für Richtungen stehen, könnte man dagegen durch einen eigenen Datentyp ersetzen. Dann braucht man die ``if``/``elif``-Kaskaden nicht, die oft ein Zeichen für einen nicht so guten objektorientierten Entwurf sind. Man könnte zum Beispiel einen Datentyp für (auch relative) Positionen einführen auf dem die Addition von Positionen definiert ist und die Bewegungsrichtungen dann durch entsprechende relative Positionsangaben repräsentieren die man nur noch auf die aktuelle Position addieren muss um die neue Position zu berechnen.

Bei den Namen sollte man Abkürzungen vermeiden und auch den Datentyp nicht in den Namen schreiben. Bei `cont` muss man beispielsweise immer erst überlegen was dass denn heissen mag. Und wenn man `widget_list` durch eine andere Datenstdruktur ersetzen möchste, zum Beispiel ein Wörterbuch das Positionsobjekte auf `Label` abbildet, muss man entweder überall den Namen ändern oder hat einen sehr irreführenden Namen im Programm.

Der Kommentar ``# logic list, 1 = shown`` macht für mich keinen Sinn. Wo ist da eine Liste?

Das erstellen einer Liste mit Dummywerten, in diesem Falle 0en, also ganze Zahlen, die dann in einer Schleife über Indexvariablen durch die tatsächlich gewünschten Werte, in diesem Falle `Label`-Objekte, also etwas ganz anderes als Zahlen, ersetzt werden, ist zutiefst „unpythonisch”. Man kann die verschachtelte Listenstruktur in einem Schritt dynamisch aufbauen, ohne irgendwelche Dummywerte.

Nach dem ``:`` bei Syntaxkonstrukten bei denen damit ein Block eingeleitet wird, sollte man auch wenn der Block nur aus einer Zeile besteht einen Zeilenumbruch und einen eingerückten Block schreiben. Das lässt sich leichter lesen.

Die `Main`-Klasse ist keine echte Klasse. Das liesse sich problemlos als Funktion schreiben.

Edit: Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk
from functools import partial


class Position(object):

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return '{0}({1!r}, {2!r})'.format(
            self.__class__.__name__, self.x, self.y
        )

    def __cmp__(self, other):
        return cmp(self.as_tuple(), other.as_tuple())

    def __hash__(self):
        return hash(self.as_tuple())

    def __add__(self, other):
        return Position(self.x + other.x, self.y + other.y)

    def as_tuple(self):
        return (self.x, self.y)


UP, DOWN, LEFT, RIGHT, NONE = [
    Position(0, -1), Position(0, 1), Position(-1, 0), Position(1, 0), Position()
]


class Controller(object):

    def __init__(self, parent):
        self.direction = NONE
        for key, direction in [
            ('w', UP), ('s', DOWN), ('a', LEFT), ('d', RIGHT)
        ]:
            parent.bind(
                '<{0}>'.format(key),
                partial(setattr, self, 'direction', direction)
            )


class Field(object):

    def __init__(self, parent, size):
        self.parent = parent
        self.controller = Controller(self.parent)
        self.alpha = tk.PhotoImage(file='pixel.gif')
        self.position = Position(3, 3)
        self.cells = dict()
        for y in xrange(size):
            for x in xrange(size):
                label = tk.Label(
                    self.parent,
                    image=self.alpha,
                    bg='grey',
                    width=10,
                    height=10
                )
                label.grid(row=y, column=x, padx=0, pady=0)
                self.cells[Position(x, y)] = label

    def move(self):
        self.parent.after(500, self.move)
        self.position += self.controller.direction
        for position, cell in self.cells.iteritems():
            cell['bg'] = 'gold' if self.position == position else 'grey'
        

def main():
    root = tk.Tk()
    root.title('Snake')
    field = Field(root, 12)
    field.move()
    root.mainloop()


if __name__ == "__main__":
    main()
Antworten