Problème de référence de canvas sous Tkinter en Python

Fermé
Heller246 Messages postés 2 Date d'inscription mercredi 18 janvier 2023 Statut Membre Dernière intervention 25 janvier 2023 - Modifié le 18 janv. 2023 à 18:41
mamiemando Messages postés 33081 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 27 avril 2024 - 25 janv. 2023 à 14:24

Bonjour à tous,

Je suis enseignant dans un Collège et je souhaite réaliser un petit programme de démonstration pour mes élèves qui utilise les classes graphiques de Tkinter en Python. 

Le programme consiste à faire apparaitre un archer dans un canevas en 2 dimensions. Cet archer doit pouvoir lancer des flèches dans la direction de l'arc. Si j'arrive bien à déplacer le bras de l'archer avec un coords (dans la classe Archer), lorsque je crée une flèche (dans la classe Fleche), celle-ci se dessine bien, mais lorsque je veux la déplacer avec un coords, elle disparait. 

J'ai constaté que lorsque je suis dans la classe Fleche, je ne parviens plus à dessiner autre chose dans le canevas. La référence au canevas me parait pourtant correcte ???

Quelqu'un peut-il m'aider à comprendre mon erreur ? 

Voici le code :

from Tkinter import *
from math import sin,cos,pi

class Fleche:
    def __init__(self,app,can,alpha,x,y):
        self.app=app
        self.can=can
        self.alpha=alpha
        self.x=x
        self.y=y
        self.f=self.can.create_line(self.x,self.y,self.x+50*cos(self.alpha/180.*pi),self.y-50*sin(self.alpha/180.*pi),arrow="last")
    def mouve(self):
        self.x+=3*cos(self.alpha/180.*pi)
        self.y-=3*sin(self.alpha/180.*pi)
        print self.x,self.y
        self.can.coords(self.f,self.x,self.y,self.x+5*cos(self.alpha/180.*pi),self.y-5*sin(self.alpha/180.*pi))
        if self.y>0 and self.x<500 and self.x>0 and self.y<500:
            self.app.fen.after(500,self.mouve())
class Archer:
    def __init__(self,app,can,x=10,y=400,alpha=0):
        self.app=app
        self.can=can
        self.x=x
        self.y=y
        self.alpha=alpha
        self.listeFleches=[]
        self.can.bind_all("<KeyPress-m>",self.monter)
        self.can.bind_all("<KeyPress-n>",self.descendre)
        self.can.bind_all("<KeyPress-t>",self.tirer)
    def represente(self):
        self.corps=self.can.create_line(self.x,self.y-10,self.x,self.y+30,width=2)
        self.bras=self.can.create_line(self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi),width=2)
        self.jambeG=self.can.create_line(self.x-20,self.y+60,self.x,self.y+30,width=2)
        self.jambeD=self.can.create_line(self.x+20,self.y+60,self.x,self.y+30,width=2)
        self.tete=self.can.create_oval(self.x-10,self.y-30,self.x+10,self.y-10)
    def monter(self,event):
        self.alpha+=1
        self.can.coords(self.bras,self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi))
    def descendre(self,event):
        self.alpha-=1
        self.can.coords(self.bras,self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi))
    def tirer(self,event):
        self.listeFleches.append(Fleche(self.app,self.can,self.alpha,self.x,self.y))
        self.listeFleches[-1].mouve()
class Application:
    def __init__(self):
        self.appli=self
        self.fen= Tk()
        self.can = Canvas(self.fen,width=500,height=500,bg="beige")
        self.can.bind("<Button-1>",self.creerArcher)
        self.can.pack()
        self.b=Button(self.fen,text="Quitter",command=self.fen.quit)
        self.b.pack()
        self.fen.mainloop()
        self.fen.destroy()
    def creerArcher(self,event):
        self.a1=Archer(self.appli,self.can,event.x,event.y)
        self.a1.represente()

a=Application()
A voir également:

8 réponses

Salut, ligne 18 ou à peu près. Tu ne dois pas appeler mouve, mais juste passer la référence de la fonction, et 500 ms, c'est beaucoup trop, mets 10 à 20 ms.

.

self.app.fen.after(10, self.mouve)

.

Sinon, python2 est mort, il serait plus adequat de travailler sur python3.

Sinon, move, il n'y a pas de u ^^

1
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
Modifié le 18 janv. 2023 à 14:54

Bonjour,

Ca ne résout rien, mais déjà, sais tu que pour convertir de degrés en radians,

tu peux utiliser la fonction math.radians() ?

rad = math.radians(deg)

et inversement:

deg = math.degrees(rad)

ca évite tous les self.alpha/180.*pi

pourquoi self.listeFleches[-1].mouve() ?, moi je ferais self.listeFleches[0].mouve()

0
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
Modifié le 18 janv. 2023 à 16:45

OK, mais moi je n'appellerais pas une fonction perso move, car c'est déjà un nom de méthode Python

comme dans 

can.move(obj,dx,dy)

ça donne ça: 

    def mouve(self):
        self.x+=3*cos(self.alpha/180.*pi)
        self.y-=3*sin(self.alpha/180.*pi)
        self.can.coords(self.f,self.x,self.y,self.x+5*cos(self.alpha/180.*pi),self.y-5*sin(self.alpha/180.*pi))
        if 0<self.x<500 and 0<self.y<500: self.can.after(10,self.mouve)

et pas besoin de liste de flèches : 

    def tirer(self,event):
        self.fleche = Fleche(self.app,self.can,self.alpha,self.x,self.y)
        self.fleche.mouve()
0

Salut Phil.

.

> OK, mais moi je n'appellerais pas une fonction perso move, car c'est déjà un nom de méthode Python

.

En quoi ce serait un souci d'avoir une classe perso avec une méthode move et un objet que l'on manipule ayant aussi une méthode move ? Il ne peut y avoir de confusion ici.

1
mamiemando Messages postés 33081 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 27 avril 2024 7 749
Modifié le 20 janv. 2023 à 02:08

Bonjour

Quelques recommandations :

  • Essaye de suivre les conventions d'indentation PEP8 pour rendre le code plus lisible :
    • Un espace derrière les virgules
    • Un espace autour des opérateurs (par notamment *, +=, etc.)
    • Les méthodes et fonctions s'écrivent selon ce style : ma_methode, ma_fonction pas en camel case comme pour creerArcher (qui devient creer_archer)
    • Les classes s'écrivent en camel case.
  • Comme suggéré par Phil
    • travailler en radians allégerait le code
    • python2 est désuet et ce serait mieux d'installer si possible python3 (par exemple anaconda si tu es sous windows), . Si tu le fais, il faut juste corriger ton print, car cela devient une fonction à part entière.
      • Exemple : écris print(x, y, z) au lieu de : print x, y , z
  • Comme suggéré par joubaho, tu devrais renommer mouve en move (il n'y a aucun risque d'ambiguïté)
  • Selon moi le design du programme doit être revu. Ta classe Application devrait stocker une liste (initialement vide) d'archers et appeler en cas d'événement sur une touche une de ses méthodes qui en cascade appelle les méthodes sur les objets créés dans ton application (typiquement les archers et les flèches).
    • Ainsi pas besoin de transporter le canvas dans les classes Archer et Fleche, de toute façon le canvas n'a rien à faire là car il ne caractérise ni un archer ni une flèche.
    • L'appui sur une touche (m, n, t) qui dans le code actuel n'est pas fonctionnel probablement les binds correspondants être déclarés avant de lancer l'application. Il faut donc suivre le même design que ce que tu as fait pour le clic gauche.
from tkinter import *
from math import sin, cos, pi

class OrientedObject:
    def __init__(self, alpha, x, y):
        self.alpha = alpha
        self.x = x
        self.y = y
    def dessiner(self, canvas):
        raise NotImplementedError()

class Fleche(OrientedObject):
    def __init__(self, *cls, **kwargs):
        super().__init__(*cls, **kwargs)
    def dessiner(self, canvas):
        print(f"{self}.dessiner({canvas})")
        canvas.create_line(self.x, self.y, self.x + 50 * cos(self.alpha / 180. * pi), self.y - 50 * sin(self.alpha / 180. * pi), arrow="last")

class Archer(OrientedObject):
    def __init__(self, *cls, **kwargs):
        super().__init__(*cls, **kwargs)
    def monter(self):
        self.alpha += 1
    def descendre(self,event):
        self.alpha -= 1
    def tirer(self,event):
        return Fleche(self.alpha, self.x, self.y)
    def dessiner(self, canvas):
        print(f"{self}.dessiner({canvas})")
        canvas.create_line(self.x, self.y - 10, self.x, self.y + 30, width=2)
        canvas.create_line(self.x, self.y, self.x + 30 * cos(self.alpha / 180. * pi), self.y - 30 * sin(self.alpha / 180. * pi) ,width=2)
        canvas.create_line(self.x - 20, self.y + 60, self.x, self.y + 30, width=2)
        canvas.create_line(self.x + 20, self.y + 60, self.x, self.y + 30, width=2)
        canvas.create_oval(self.x - 10, self.y - 30, self.x + 10, self.y - 10)

class Application:
    def __init__(self):
        self.root = Tk()
        self.canvas = Canvas(self.root, width=500, height=500, bg="beige")
        self.canvas.bind("<Button-1>", self.creer_archer)
        self.canvas.bind("<KeyPress>", self.on_key_press)
        self.canvas.pack()
        self.button = Button(self.root, text="Quitter", command=self.root.destroy)
        self.button.pack()
        self.archers = list()
        self.fleches = list()
    def creer_archer(self, event):
        archer = Archer(0, event.x, event.y)
        self.archers.append(archer)
        archer.dessiner(self.canvas)
    def on_key_press(self, event):
        key = event.char
        print("on_key_press", char)
        if key == "m":
            self.monter()
        elif key == "d":
            self.descendre()
        elif key == "t":
            self.tirer()
    def monter(self):
        for archer in self.archers:
            archer.monter()
    def descendre(self):
        for archer in self.archers:
            archer.descendre()
    def tirer(self):
        for archer in self.archers:
            fleche = archer.tirer()
            fleche.dessiner()
            self.fleches.append(fleche)
    def dessiner(self):
        for archer in self.archers:
            archer.dessiner(self.canvas)
        for fleche in self.fleches:
            fleche.dessiner(self.canvas)

app = Application()
app.root.mainloop()

Pardon, je manque de temps et je donne juste les grandes lignes dans l'extrait ci-dessus. Il reste à :

  • déboguer les key press (chez moi, elles ne se déclenchent pas)
  • reporter la partie permettant d'animer tout

Bonne chance

0

Salut.

.

déboguer les key press (chez moi elle ne se déclenche pas)

.

Vu que chez moi cela fonctionne, je pense que c'est uniquement le canvas qui n'a pas le focus, du moins je pense.

Pour tester, ajouter self.can.focus_set() avant les binds.

0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
19 janv. 2023 à 12:24

Bonjour,

Chez moi, les key press fonctionnent parfaitement sans rien ajouter de plus

La seule modif. que j'ai faite est de remplacer self.can.after(500,self.mouve())

par self.can.after(10,self.mouve)

Donc ceci marche :

# -*- coding:Utf-8 -*-

from tkinter import *
from math import sin,cos,pi

class Fleche:
    def __init__(self,app,can,alpha,x,y):
        self.app=app
        self.can=can
        self.alpha=alpha
        self.x=x
        self.y=y
        self.f=self.can.create_line(self.x,self.y,self.x+50*cos(self.alpha/180.*pi),self.y-50*sin(self.alpha/180.*pi),arrow="last")
    def mouve(self):
        self.x+=3*cos(self.alpha/180.*pi)
        self.y-=3*sin(self.alpha/180.*pi)
        self.can.coords(self.f,self.x,self.y,self.x+5*cos(self.alpha/180.*pi),self.y-5*sin(self.alpha/180.*pi))
        if 0<self.x<500 and 0<self.y<500: self.can.after(10,self.mouve)

class Archer:
    def __init__(self,app,can,x=10,y=400,alpha=0):
        self.app=app
        self.can=can
        self.x=x
        self.y=y
        self.alpha=alpha
        self.listeFleches=[]
        self.can.bind_all("<KeyPress-m>",self.monter)
        self.can.bind_all("<KeyPress-n>",self.descendre)
        self.can.bind_all("<KeyPress-t>",self.tirer)
    def represente(self):
        self.corps=self.can.create_line(self.x,self.y-10,self.x,self.y+30,width=2)
        self.bras=self.can.create_line(self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi),width=2)
        self.jambeG=self.can.create_line(self.x-20,self.y+60,self.x,self.y+30,width=2)
        self.jambeD=self.can.create_line(self.x+20,self.y+60,self.x,self.y+30,width=2)
        self.tete=self.can.create_oval(self.x-10,self.y-30,self.x+10,self.y-10)
    def monter(self,event):
        self.alpha+=1
        self.can.coords(self.bras,self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi))
    def descendre(self,event):
        self.alpha-=1
        self.can.coords(self.bras,self.x,self.y,self.x+30*cos(self.alpha/180.*pi),self.y-30*sin(self.alpha/180.*pi))
    def tirer(self,event):
        self.fleche = Fleche(self.app,self.can,self.alpha,self.x,self.y)
        self.fleche.mouve()

class Application:
    def __init__(self):
        self.appli=self
        self.fen= Tk()
        self.can = Canvas(self.fen,width=500,height=500,bg="beige")
        self.can.bind("<Button-1>",self.creerArcher)
        self.can.pack()
        
        self.b=Button(self.fen,text="Quitter",command=self.fen.quit)
        self.b.pack()
        
        self.fen.mainloop()

    def creerArcher(self,event):
        self.a1=Archer(self.appli,self.can,event.x,event.y)
        self.a1.represente()

a=Application()
0
mamiemando Messages postés 33081 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 27 avril 2024 7 749
Modifié le 20 janv. 2023 à 02:16

Bonjour,

Suite aux messages #6, #7, #8, voici le code que je propose, et qui est fonctionnel :

from tkinter import *
from math import sin, cos, pi

def rad(deg):
    return pi * deg / 180

class OrientedObject:
    def __init__(self, alpha, x, y):
        self.alpha = alpha
        self.x = x
        self.y = y
    def dessiner(self, canvas):
        raise NotImplementedError()

class Fleche(OrientedObject):
    def __init__(self, *cls, longueur = 50, **kwargs):
        if "longueur" in kwargs:
            self.longueur = kwargs.pop("longueur")
        else:
            self.longueur = longueur
        super().__init__(*cls, **kwargs)
        self.line = None
    def dessiner(self, canvas):
        if self.line is None:
            x = self.x
            y = self.y
            alpha = rad(self.alpha)
            self.line = canvas.create_line(
                x, y,
                x + self.longueur * cos(alpha),
                y - self.longueur * sin(alpha),
                arrow="last"
            )
    def deplacer(self, canvas):
        if not self.line:
            self.dessiner(canvas)
        vitesse = 3
        alpha = rad(self.alpha)
        dx = vitesse * cos(alpha)
        dy = - vitesse * sin(alpha)
        canvas.move(self.line, dx, dy)
        return 0 < self.x < 500  and 0 < self.y < 500

class Archer(OrientedObject):
    def __init__(self, *cls, **kwargs):
        super().__init__(*cls, **kwargs)
        self.line = None
    def monter(self):
        self.alpha += 1
        print(f"monter {self.alpha}")
    def descendre(self):
        self.alpha -= 1
        print(f"descendre {self.alpha}")
    def tirer(self):
        print("tirer")
        return Fleche(self.alpha, self.x, self.y)
    def dessiner(self, canvas):
        alpha = rad(self.alpha)
        x = self.x
        y = self.y
        canvas.create_line(x, y - 10, x, y + 30, width=2)
        self.line = canvas.create_line(x, y, x + 30 * cos(alpha), y - 30 * sin(alpha), width=2)
        canvas.create_line(x - 20, y + 60, x, y + 30, width=2)
        canvas.create_line(x + 20, y + 60, x, y + 30, width=2)
        canvas.create_oval(x - 10, y - 30, x + 10, y - 10)
    def deplacer(self, canvas):
        if self.line is None:
            self.dessiner(canvas)
        alpha = rad(self.alpha)
        x = self.x
        y = self.y
        canvas.coords(self.line, x, y, x + 30 * cos(alpha), y - 30 * sin(alpha))

class Application:
    def __init__(self):
        self.root = Tk()
        self.canvas = Canvas(self.root, width=500, height=500, bg="beige")
        self.root.bind("<Button-1>", self.creer_archer)
        self.root.bind("<KeyPress>", self.on_key_press)
        self.canvas.pack()
        self.button = Button(self.root, text="Quitter", command=self.root.destroy)
        self.button.pack()
        self.archers = list()
        self.fleches = list()
    def creer_archer(self, event):
        archer = Archer(0, event.x, event.y)
        self.archers.append(archer)
        archer.dessiner(self.canvas)
    def on_key_press(self, event):
        key = event.char
        if key == "m":
            self.monter()
        elif key == "d":
            self.descendre()
        elif key == "t":
            self.tirer()
    def monter(self):
        for archer in self.archers:
            archer.monter()
            archer.deplacer(self.canvas)
    def descendre(self):
        for archer in self.archers:
            archer.descendre()
            archer.deplacer(self.canvas)
    def tirer(self):
        for archer in self.archers:
            fleche = archer.tirer()
            fleche.dessiner(self.canvas)
            self.fleches.append(fleche)
        self.animer()
    def dessiner(self):
        for archer in self.archers:
            archer.dessiner(self.canvas)
        for fleche in self.fleches:
            fleche.dessiner(self.canvas)
    def animer(self):
        repeat = False
        for fleche in self.fleches:
            repeat |= fleche.deplacer(self.canvas)
        if repeat:
            self.root.after(500, self.animer)

app = Application()
app.root.mainloop()

Parmi les améliorations possibles, on peut envisager les points suivants :

  • utiliser les flèches pour orienter le tir (plutôt que les touches M et D) et la barre espace pour tirer (plutôt que T) ;
  • utiliser des noms anglais plutôt que français ;
  • prévoir une liste de cases à cocher, remplie dynamiquement en fonction des archers créés, permettant d'indiquer quels archers on veut piloter avec le clavier.

Bonne chance

0
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
20 janv. 2023 à 11:07

Bonjour,

Ah ok, Tu introduis carrément de la POO...

Sinon une question bête: pourquoi une fonction rad alors qu'il existe math.radians ?

0
mamiemando Messages postés 33081 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 27 avril 2024 7 749
20 janv. 2023 à 12:31

Bonjour,

Ah ok, Tu introduis carrément de la POO...

Disons que dès le message #1, il y avait des classes. Du coup, de là à faire un héritage, il n'y a qu'un pas. Ça permet de factoriser et de mettre un peu l'intérêt des classes en évidence.

La notion la plus compliquée dans ce que je propose, c'est les *cls et les **kwargs, mais si on prend 5min pour regarder ce que ça signifie, il n'y a rien de bien méchant, c'est juste une syntaxe pour réutiliser la même signature que la méthode mère.

Sinon une question bête: pourquoi une fonction rad alors qu'il existe math.radians ?

Il n'y a pas de question bête, et d'ailleurs c'est une très bonne remarque :-)

J'ai juste oublié l'existence de cette fonction (que tu avais pourtant pointée dans #2). C'est mieux de l'utiliser, ça évite les erreur de programmation. Pour corriger le programme que j'ai proposé #8, il suffit de remplacer :

def rad(deg):
    return pi * deg / 180

... par :

from math import radians as rad

Après, on aurait aussi pu imaginer plus simplement que les angles soient dans tout le code exprimé en radians dans tout le code, car finalement manipuler des degrés n'a pas d'intérêt. Il faut juste corriger en conséquence la valeur qui permet d'incrémenter / décrémenter les angles quand on appelle les méthodes monter et descendre.

0
Heller246 Messages postés 2 Date d'inscription mercredi 18 janvier 2023 Statut Membre Dernière intervention 25 janvier 2023
25 janv. 2023 à 10:48

Merci à Joubaho et Phil_1857 qui ont trouvé mon erreur, super !!!

Merci à Mamiemando pour le cours de programmation et aussi aux autres : je suis débutant en programmation et ça me permet de faire évoluer un peu plus mon code !

0
mamiemando Messages postés 33081 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 27 avril 2024 7 749
25 janv. 2023 à 14:24

Merci pour ton retour positif. As-tu d'autres question en rapport avec le sujet initial. Si ton problème est résolu, merci de suivre ces indications.

0