Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

changer d'image de fond tkinter dynamiquement

+3 votes

Edit : Poste initial édité en fonction de vos remarques ( très pertinentes ! mais le problème en lui-même n'est toujours pas réglé)

j'aimerai changer d'image de fond de ma petite appli tkinter (le lien contient le code github complet), j'ai commencé à coder qq chose ^^:
voici les points du code important

from os import chdir
# On se place dans le répertoire d'images s'il existe
try:
    chdir("images_gif/")
except:
    pass

def create_canvas(root):
    """ creer la zone de dessin qui va contenir l'entrelacs ainsi que le quadrillage"""
    mycanvas = tk.Canvas(root, bg='white', width=600, height=500)
    return mycanvas

Ensuite j'aimerai proposé une liste de theme à choisir avec une listbox :

def create_listbox(root):
    listbox = tk.Listbox(root, font=('times', 13))
    listbox.place(x=100, y=100)
    listbox.pack(side=tk.LEFT)
    # items contenus dans la listbox
    for item in ["theme blue", "theme tourbi", "theme nature", "theme cube",
                 "theme time","theme giphy"]:
        listbox.insert(tk.END, item)
    return listbox

ce qui permet de déclenché l'evt :

listbox = ui.create_listbox(root)
listbox.bind('<<ListboxSelect>>', ui.change_im)

et ici l'evenement déclanché :

def change_im(evt):
    # En fonction du choix de l'utilisateur dans la listbox, on charge l'image appropriée
    """ Gest° de l'evt selection dans la listbox """
    value = str((main.listbox.get(main.listbox.curselection())))
    main.current_theme = value
    print(main.current_theme)
    print("evt change_im enclanché")
    if main.current_theme == "theme tourbi":
        tof = tk.PhotoImage(file="tourbi.jpg")
    elif main.current_theme == "theme planet":
        tof = tk.PhotoImage(file="theme4.gif")
    elif main.current_theme == "theme nature":
        tof = tk.PhotoImage(file="nature.gif")
    elif main.current_theme == "theme cube":
        tof = tk.PhotoImage(file="cubes.gif")
    elif main.current_theme == "theme time":
        tof = tk.PhotoImage(file="time.gif")
    elif main.current_theme == "theme giphy":
        tof = tk.PhotoImage(file="giphy.gif")
    else:
        tof = tk.PhotoImage(file="theme4.gif")
    # on supprime d'abord l'image courante, repérée avec le tag 'background-image'
    main.my_canvas.delete('background-image')
    main.image = main.my_canvas.create_image(0, 0, image=tof)
    main.my_canvas.im = tof
    # Ensuite on configure la nouvelle image avec le tag 'background-image'
    main.my_canvas.itemconfigure(main.image, tags=('background-image', ))

Si je mets une image par dessus elle ne risque pas d'écraser tout l'affichage ? (et c'est ce qu'elel fait)
Pour donner une petite idée : pour l'instant ça ressemble à ça :

image

(PS : toutes les remarques ne répondant pas forcément à la question mais plutôt d'ordre général que soit sur ma façon de coder, l'interface elle-même etc... sont toujours les bienvenues ;)
Edit : à l'execution j'ai bien :

theme green
evt change_im enclanché

Edit2 : Pour tester le code directement, faire : (necessite d'avoir git/python3/tkinter)

git clone https://github.com/boblinux/tkinter_project
cd tkinter_project
python3 libui_fork.py

Donc grâce à l'affichage du print, je peux constater que l'evenement est bien déclenché, je peux choisir entre plusieurs themes mais ces derniers écrasent tout l'affichage courant =/ et ne se contentent pas de s'afficher en fond d'image (comme un fond d'écran quoi x) ).

demandé 11-Avr-2015 par boblinux (3,092 points)
edité 14-Avr-2015 par boblinux
  • soit consistent dans le format de nomage de tes méthodes (CurSelet vs create_listbox : la premiere forme est pour les le nommage des classes). Ca facilite la lecture

  • j'ai remarqué que ton depot github contient les pyc et les __pycache__ (c'est pas pas propre et ca pollue ton repo pour rien). Utilise un .gitinore a la racine de ton projet (tu peu en générer sur gitignore.io comme celui-ci pour python)

  • dans l'absolu, evite les print et utilise le module de log (c'est pas critique, mais c'est une best practice)

(PS : toutes les remarques ne répondant pas forcément à la question mais plutôt d'ordre général que soit sur ma façon de coder, l'interface elle-même etc... sont toujours les bienvenues ;)

T'as tendu la perche :)

Pout ta question, je passe, je ne connais pas tkinter.

Edit :
nommage Ok
.gitignore Ok
l'histoire des logs je n'ai pas encore bien digéré l'article de sam&max, dès que j'aurai digéré ça je m'y penche ^^
/kiss

En vrac (parce que je suis passé par là y'a pas deux semaines) :

Un tuto pour du Tk, en Py3 en plus, si je me souviens bien, ET qui inclus un comment-utiliser-le-module-de-LOG ainsi que le préconisait JC un peu avant lui-même.

http://blog.agupieware.com/2015/03/pygest-python-tkinter-tutorial-part-1.html

Sinon, toujours en vrac et parce qu'on s'y perd pas trop, deux liens :

http://www.tkdocs.com/tutorial/
http://slav0nic.org.ua/static/books/python/tkinter.pdf

La page parlant du tag dont traite Sam plus bas.


Note1:
Beaucoup utilisent encore pack dans leur code parce qu'habitués, sinon, selon les bons conseils glanés çà et là, il faut mieux passer sur grid. J'ai jamais essayé pack, cela dit, quand j'ai vu que grid était très simple.


Note2:
Note : tu verras, mais le logging indiqué plus haut dans le tuto est très euh ... logorrhéique. Je préfère le simple print(), voire :

def dbg(arg):
    '''Imprime tout'''
    for key in arg:
        print(key)

def dbge(arg):
    '''Imprime et s'arrête.'''
    for key in arg:
        print(key)
    exit()

# tout plein de code    
dbg(("pouet pouet tagada", 2, ['aa', 'bb'], type(42)))
# tout plein de code
dbge(("ça plante ici, je cr <<-- famous last word",))  # "," <-- ne pas oublier
# tout plein de code encore

2 Réponses

+3 votes
 
Meilleure réponse

Dans une autre de tes questions on a parlé des tags. C'est le moment de s'en servir.

# supprimer l'image précédente
main.my_canvas.delete('background-image')
# ajouter la nouvelle image
image = main.my_canvas.create_image(0, 0, image=tof)
# tagger la nouvelle image pour pour la manipuler plus tard
main.my_canvas.itemconfig(image, tags=('background-image',))
# mettre l'image en arrière plan
main.my_canvas.tag_lower('background-image')
répondu 12-Avr-2015 par Sam (4,974 points)
sélectionné 15-Avr-2015 par boblinux

A bien y regarder, la réponse de Sam est tout à fait celle qui fonctionne.

L'image de fond est effacée, les motifs (les points) sont conservés, et la nouvelle image de fond est ajoutée puis envoyée au fond.

Pour info, voici ta fonction change_im légèrement modifiée :

def change_im(canv):
    # Charge image 'validation.gif' et l'enregistre dans la classe
    # pour conserver sa référence.
    tof = tk.PhotoImage(file="validation.gif")
    canv.img = tof

    # Effacement de l'image de fond, uniquement, qui est tagguée
    # 'background-image'
    canv.delete('background-image')

    # On affiche 'validation.gif' et on lui configure un tag (le
    # même nom en l'occurence : 'background-image')
    image = canv.create_image(55, 45, image=tof)
    canv.itemconfigure(image, tags=('background-image',))

    # On remet la nouvelle image tagguée 'background-image' en dessous.
    canv.lower('background-image')

C'est qu'en fait j'avais pas compris comment était organisé ton programme et je cherchais pourquoi le bouton 'delete' effaçait tout et pas kuste l'image de fond. Ce n'est que ce soir que j'ai vu qu'il lançait un :

bouton_delete = tk.Button(root, text='Delete',
                         command=lambda: my_canvas.delete(tk.ALL))

C'est le bouton 'Valider' qui permet actuellement de changer l'image de fond et c'est lui qui appelle change_im :

bouton_theme = tk.Button(root,
                         text='Valider le thème',
                         command=lambda: change_im(my_canvas))

Je pense que ça ne marchait pas après l'intervention (divine ?) de Sam car ton code était assez euh ... pas ... hein ? oui, pas très lisible. Pour peu que tu ais interverti deux ou trois trucs et pouf ! ça marche point.

Le lien pour le code en entier : http://paste.debian.net/166968/

Il fonctionne 'presque' : 1. DRAW, 2. Valider le thème : les points sont toujours là et le "check" est derrière.
Les thèmes ne font rien pour le moment vu qu'ils ne faisaient déjà rien quand j'ai git cloné.

Si c'est pas déjà fait, oublie pas de voter pour Sam.

@agagax Merci pour le retour mais t'arrive un peu tard :P
Lis mes derniers com's, le problème est résolu x)
Mais je me posais la question sur un autre problème dans mon dernier commentaire, car can.lower met l'item "tout derriere" mais si je veux mettre un objet graphique A derrier un objet graphique B, t'as une idée de comment je peux me la jouer?

Oui, mais ça aurait pu faire l'objet d'une autre question.

Résultat voulu : soit A au dessus de B au dessus de C au dessus de D, ces lettres désignant, par exemple, des tags.
Actuel : tout mélangé.

Il faut procéder à une inversion matricielle de niveau 1, autrement appelée "méthode d'Agagax" :

canv.lower('A')
canv.lower('B')
canv.lower('C')
canv.lower('D')

Tu colles tout ça dans une fonction / méthode que t'appelles quand tu modifies l'ordre d'une manière ou d'une autre, par exemple quand tu effaces une couche référencée par son tag et que tu en ajoutes un (ou que tu le remplaces).

Oui pour ce genre de chose mieux vaut une autre question.

Désolé je pensais que cette petite question ne méritait pas un post x).
Fixed : à la creation des create_line je fais un juste un :

main.my_canvas.tag_lower('line')
main.my_canvas.tag_lower('background-image')
+1 vote

Très rapidement, quelques trucs qui permettent :
1. effacer l'image du fond mais ça fonctionnait déjà,
2. afficher 'validation.gif' (ça ne fonctionnait pas avant et c'est la question) ; c'est pas bien "centré"
3. corrige le 'chdir'
4. ça efface tout : l'image de fond ET le motif quand il y en a un. Et pour ça, je ne sais pas comment ne PAS effacer le motif de fond. Lui assigner un tag aussi ?

Mes commentaires en '#'.

def change_im():
    # Faut un path absolu. J'ai pris la liberté de pirater ton pc
    # pour savoir où c'était localisé ^^
    chdir("/home/etudiants/arouani277/school/L3/S6/IHM/tkinter_project/images_gif/")
    print("evt change_im enclenché")
    # Maintenant ça peut charger le fichier MAIS c'est pas terminé.
    tof = tk.PhotoImage(file="validation.gif")
    main.my_canvas.delete('background-image')
    main.image = main.my_canvas.create_image(0, 0, image=tof)
    # tof est une variable locale qui disparaît quand tu sors de la
    # fonction 'change_im' => l'image 'validation' n'apparaît pas.
    # il faut donc la sauvegarder dans un attribut du widget. Ici,
    # j'en ai créé un à la volée (j'suis comme ça, moi :-) ).
    main.my_canvas.img = tof
    main.my_canvas.itemconfigure(main.image,tags=('background-image',))

Pour le chemin complet, j'ai rien piraté, c'tune blague évidemment, y'a le chemin dans le log de l'Exception.

Ah, oui : http://tkinter.unpythonic.net/wiki/PhotoImage.

répondu 14-Avr-2015 par Agagax (182 points)

Merci pour ce retour,
1)
chdir("/home/etudiants/arouani277/school/L3/S6/IHM/tkinterproject/imagesgif/")
Je ne suis pas d'accord là x), en effet, il suffit que mon fichier py soit à la racine d'un dossier x et que (toutes) mes images soient dans un sous-dossier du dossier x et je n'ai pas besoin de mettre le chemin complet x), en deux mots :

chdir("images_gif/")

fonctionne la preuve? j'arrive tout de meme à afficher un fond d'image à la base (qui lui est présent dans le sous-dossier images_gif/) (celui présent sur le screen

2)

tof est une variable locale qui disparaît quand tu sors de la
fonction 'change_im' => l'image 'validation' n'apparaît pas.

D'accord à 100% avec toi, d'ailleurs j'avais agis comme ça à la création du canvas :

def create_canvas(root):
    main.current_im = tk.PhotoImage(file="theme4.gif")
    im = "theme4.gif"
    #print(main.current_theme)
    main.imdict[im] = main.current_im  # instance pour maintenir l'image en vie !
    """ creer la zone de dessin qui va contenir l'entrelacs ainsi que le quadrillage"""
    mycanvas = tk.Canvas(root, bg='white')
    main.image = mycanvas.create_image(0, 0, image=main.current_im)
    return mycanvas

Je vais éditer mon premier poste en fonction de tes remarques mais (et oui il y a toujours un Mais) quand bien même je n'aurai pas réussi à afficher ma nouvelle image, j'aurais quand même dû réussir à supprimer l'image courante non ??
Edit : ça l'air de fonctionner niveau affichage, mais en fait ça affiche par dessus l'image courante x), donc apparement c'est la suppression de l'image courant qui pose problème =/

Pour info, j'ai, par la suite, regardé ton script de plus près (rapidement pendant le café ce matin). Il y a un chdir appelé en premier lieu pour la création du canvas. Or tu ne fais pas de chdir("..") par la suite --> le programme se trouve(rait) donc déjà dans le répertoire des images. Refaire un chdir vers ce répertoire par la suite dans change_im n'aboutira pas puisque le programme y est déjà. Doc chdir. Regarde la doc, avant de faire un chdir, tu pourrais vérifier si tu n'y es pas déjà. Voire, appelles tes images par des xxx = 'images_gif/validation.gif' (pas sûr que ce soit compris par tkinter, mais essaye pour voir).

Oui, j'ai remarqué que dans create_canvas la variable était bien mémorisée.

[.../...]j'aurais quand même dû réussir à supprimer l'image courante, non ??

Dans l'état d'hier soir du git clone, je pouvais effacer l'image de fond (tout, en fait, même le motif quand il y en avait un) sans rien modifier au préalable. Bizarre que de ton côté ça ne fonctionne pas.


Hors-sujet : plusieurs points qui ressortent à la lecture de ton code après un git clone.

EN-TETE

Mets ça dans les deux premières lignes de codes (tout sauf les guillemets) :

"#!/usr/bin/python3.4"
"# -*- coding: utf-8 -*-"

C'est mieux d'indiquer l'interpréteur python à utiliser ce qui permet en ligne de commande de faire :

(chmod +x mon_script.py)
./mon_script.py

sans avoir à se demander si on doit utiliser python 2 ou 3.

Ensuite,
a) règle toujours l'encodage et
b) règle toujours utf-8

Ce bon docteur Sam l'a crié bien fort dans sa règle n°2.

IMPORTS CIRCULAIRES

Tu fais une référence circulaire dans tes deux modules : l'un appelle l'autre (import) et l'autre appelle l'un. Pas sûr que ce soit très bien à terme. Je rassemblerais pour le moment les deux codes en un histoire en premier lieu de le faire fonctionner. Pour ce faire, ajoute ceci à la fin du "pas-main" (je ne me souviens plus du nom, je ne suis pas sur le même PC) :

if __name__ == "__main__":
    main()

Crée au dessus une fonction main qui contiendra TOUT ton code extrait du fichier "Main" (moins les imports) :

def main():
    # tout le code du fichier 'main'
    # à coller ici

Corrige toutes les occurences de "Main.xxx(...)" --> "xxx(...)" :

main.my_canvas.itemconfigure(main.image,tags=('background-image',))

    en (2 x `main` à retirer) :

my_canvas.itemconfigure(image,tags=('background-image',))

(au passage, c'est 'itemconfigure', pas 'itemconfig', du moins c'est ce que disent mes deux documentations à ce sujet).

NOMMAGE

Toutes tes variables doivent contenir au moins 3 caractères (lisibilité). Ainsi, PAS de :

h, l = xxxx()"

mais plutôt des :

hauteur, longueur = xxxx()

Faut pas être avare de caractères. Sinon tu vas te retrouver avec des h, l , im, az, rr, gr, et, à un moment donné, tu sauras plus où tu en es.
Et je parle pas de ceux qui te relisent ^^ qui vont définitivement se pendre.

Il est préférable d'utiliser my_canvas plutôt que mycanvas et "bouton_theme" plutôt que "ButtonTheme" pour une variable.

Pas de mot clé pour les variables, change tous les :

listbox = tk.Listbox(...)

    en :

ma_listbox = tk.Listbox(...)  # ou tout autre nom de ton choix

    ou :

choix_theme_listbox = tk.Listbox(...)

EN RESUME

Pour résumer, lis ces bonnes pratiques et surtout la PEP8 (c'est long mais tellement profitable) puis mets ton code en conformité. Avec toutes ces erreurs de conformité dans ton code, va savoir si y'a pas un vieux loup qui empêche ton programme de faire ce que tu lui demandes. Une fois monsieurPropré --oui, le terme existe depuis aujourd'hui --, ce sera sans doute plus clair.

Oh, et tu peux utiliser les différents analyseurs de code PEP8 (pylint, pep8, ou même celui-ci en ligne, etc.). J'utilise la fonction prévue à cet effet dans PyDev sous Eclipse, ça affiche des petits triangles jaunes dans la marge des lignes "non conformes".

Bon, je sors avant de me faire gronder par la direction tellement je suis hors sujet.

@Agagax
tu m'en accordes du temps hein x), je t'en vrament suis reconnaissant !
Bon avec toutes ces infos j'ai du pain sur la planche mwa :x, j'éditerais tout mon code tranquillement ensuite c'est reparti !
ps : Erf je pensais que j'allais passer inaperçu en utilisant bon vieux yapf pour formater mon code automatiquement en pep8 par flemme :D, mais j'me suis fait grillé =/

Bon j'ai remis en forme mon code ^^
Par contre, je ne vois pas trop l'intérêt de mettre tout mon code dans un fichier ?
Pour l'import circulaire, je n'ai pas trouvé mieux pour l'instant pour répondre à la problématique :
Un fichier A utilise les fonctions du fichier B, et le fichier B a besoin des parametre du fichier A pour agir dessus (faire tout passer en local genere des incohérences complexe, notamment à la gestion des evenements dynamiques avec les fonctions lambda...)
, et ça a l'air de fonctionner pour l'instant

Par contre, je ne vois pas trop l'intérêt de mettre tout mon code dans
un fichier ?

En fait, c'est que je ne voyais pas l'intérêt de séparer en deux. C'est l'idée que B ait besoin des paramètres de A qui me gratouillait. Ca revenait presque à mettre des tas de variables en global.

En ce qui me concerne, j'ai tout collé en 1 fichier et tout corrigé en utilisant les passages de paramètres + retour stocké dans les variables.

Par exemple, la ligne d'appel de la fonction d'appel du 'bind' du canvas est, dans ma version revisitée :

my_canvas.bind('<Button-1>',
               lambda: clic_callback(my_canvas,
                                     nbre_clics,
                                     tag_dest,
                                     tag_srce,
                                     dest,
                                     srce))

et la fonction clic_callback(...) est :

def clic_callback(event, canv, nbre_clics, tag_dest, tag_srce, dest, srce):
    # et le code ...

A ce niveau, il aurait été préférable d'utiliser les classes, et là, tu fais ton fichier de classes puis un fichier principal (voire un troisième fichier de fonctions génériques si nécessaire). Le principal importerait alors le "class" et le "class" n'a pas besoin d'importer le principal.

Bon, donc j'ai reformaté ton code, ça me paraît plus clair, mais devines quoi : je n'arrive toujours pas à conserver le motif quand on efface le fond.

Je suis en train d'essayer ce qui serait finalement très simple oO à l'heure où j'écris ce commentaire. Si j'y arrive, je le signalerai.

@agagax
jete un coup d'oeil à mon code sur github (sur le lien du premier post) j'ai réussir à régler mon probleme, et cette fois avec un code plus propre pephuité etc.. etc.. :DD
En fait , j'ai des contraintes sur mon projet, à savoir : le programme doit être modulaire, et je dois éviter d'utiliser les classes (je ne gere pas eencore ça sous python , mais je compte m'y mettre sous peu, dans un future proche, je ferais peut etre un version de mon projet avec des classes histoire de m'entrainer un peu et rendre ce code plus pythonesque)
Sinon merci pour ta partcipation au poste x) ,
ps : je peux jeter un ccoup d'oeil à ton code remasteurisé? (version complete)

...