Problème de référence de canvas sous Tkinter en Python
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()
- Problème de référence de canvas sous Tkinter en Python
- Citizen code python avis - Accueil - Outils
- Canvas gratuit - Télécharger - Divers Photo & Graphisme
- Reference pto - Accueil - Box & Connexion Internet
- Indice de référence des loyers - Guide
- Python pix ✓ - Forum Python
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 ^^
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()
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()
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
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre questionBonjour,
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()
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
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 ?
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.
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 !
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.