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.

AttributeError: 'NoneType' object has no attribute 'destroy'

+3 votes

J'essaye de rendre modulaire un programme (permettant du dessin graphique via tkinter).
(Contrainte : Ne pas utiliser de classes)
voici mon main :

import tkinter as tk
import libui_fork_test as ui

# initialisations
imdict={}
murdict={}

cadredict={}
txtvar = ""
h, l = 2,2
current_coord = 0

root, my_canvas = None, None
ButtonDraw, ButtonDelete, ButtonOut = None, None, None
hauteur_label, largeur_label = None, None
hauteur_entry, largeur_entry = None, None

def get_label_dim():
    return largeur_label, hauteur_label

def get_label_fen():
    return root, my_canvas

def get_Buttons():
    return ButtonDraw, ButtonDelete, ButtonOu

def get_entry():
    return hauteur_entry, largeur_entry 

def my_main():

    global root, my_canvas, ButtonDraw, ButtonDelete, ButtonOut, hauteur_label, hauteur_entry, largeur_label, largeur_entry

    # fenêtre principale

    root = ui.create_root()

    # zone graphique

    my_canvas = ui.create_canvas(root)
    my_canvas.pack(side = tk.LEFT) 

    # configuration des bouttons

    ButtonDraw, ButtonDelete, ButtonOut = ui.create_all_buttons(root,my_canvas,h,l)

    # configuration des entrées hauteur/largeur du quadrillage

    hauteur_label = tk.Label(root, text = 'hauteur')
    hauteur_label.pack()
    hauteur_entry = tk.Entry(root)
    hauteur_entry.pack() 

    largeur_label = tk.Label(root, text = 'largeur')
    largeur_label.pack()
    largeur_entry = tk.Entry(root)
    largeur_entry.pack() 

    # gestion evt clic gauche

    my_canvas.bind('<Button-1>',ui.callback)

    root.mainloop()

if __name__=='__main__':
    my_main()

et ici ma lib qui utiliser les variables initalisées dans le main pour pouvoir agir dessus :

import tkinter as tk
import mainproj_fork_test as main

def create_root():
    """ creer la fenêtre principale"""
    root = tk.Tk()
    root.title('entrelaceur')
    return root

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

def create_button_draw():
    BoutonDraw = tk.Button(main.root, text = 'draw', command=create_noeuds)
    return BoutonDraw

def create_button_delete(root, my_canvas):
    BoutonDelete = tk.Button(main.root, text = 'delete', command = lambda:my_canvas.delete(tk.ALL))
    return BoutonDelete

def create_button_quitter(root):
    BoutonOut = tk.Button(main.root,text="Sortir",command=main.root.destroy)
    return BoutonOut

def create_all_buttons(root, my_canvas, h, l):
    """ Créer et pack les boutons : Draw permettant de dessiner les noeuds, Delete permettant de supprimer le quadrillage, Sortir permettant de détruire la fenetre principale"""
    BoutonDraw = create_button_draw()
    BoutonDraw.pack()

    BoutonDelete = create_button_delete(root, my_canvas)
    BoutonDelete.pack()

    BoutonOut = create_button_quitter(root)
    BoutonOut.pack()

    return (BoutonDraw,BoutonDelete, BoutonOut)

def create_noeuds():
    create_noeuds1()
    create_noeuds2()

def create_noeuds1():
    """ Crée un quadrillage de noeuds en fonction de sa dimension ligne/colonne """
    h = main.hauteur_entry.get()
    l = main.largeur_entry.get()
    i, j = 0, 0 
    x0,y0,x1,y1 = 30, 30 , 50 ,50
    # création du quadrillage de dimension ligne, colonne 
    cpt = 0 
    while i < int(l)-1 :
        while j < int(h)-1 :
            tag_current = ("mur",cpt)
            main.my_canvas.create_oval(x0,y0,x1,y1, fill = 'black', tag=tag_current)
            main.cadredict[tag_current]=[x0,y0,x1,y1]
            x0 ,x1 = x0 + 60 , x1 + 60
            j = j + 1
            cpt = cpt + 1
        j = 0
        i = i + 1
        y0, y1 = y0 + 60, y1 + 60
        x0, x1 = 30, 50
    print(main.cadredict)

def create_noeuds2():
    """ Crée le quadrillage sur lequel on basera le mur """ 
    h = main.hauteur_entry.get()
    l = main.largeur_entry.get()
    i, j = 0, 0
    x0,y0,x1,y1 = 0, 0, 20, 20
    cpt = 0 
    while i < int(l):
        while j < int(h):
            tag_current = ("cadre",cpt)
            main.my_canvas.create_oval(x0,y0,x1,y1, fill = 'red', tag=tag_current)
            main.murdict[tag_current]=[x0,y0,x1,y1]
            x0, x1 = x0 + 60, x1 + 60
            j = j+1
            cpt = cpt + 1
        j = 0
        i = i+1
        y0, y1 = y0 + 60, y1 + 60
        x0, x1 = 0, 20
    print(main.murdict)

def callback(event):
    """ Associe au clic de souris l'affichage des tags de l'objet graphique cliqué """
    # On récupere les tags de l'obj graphique dans tag
    items = main.my_canvas.find_withtag("current")
    tag = main.my_canvas.gettags(items)
    main.txtvar = tag
    print("tag obj graphique : ",main.txtvar)
    # On récupere les coordonnées du clic souris
    souris = main.my_canvas.find_closest(event.x, event.y)
    # On récupere les coordonnées de l'objet graphique le plus proche du clic souris
    print("coord clic : ", event.x, event.y, " ; coord obj graph visé : ",main.my_canvas.coords(souris[0]))

def create_matrice(ligne, colonne):
    n=[0]*ligne
    m=[0]*colonne
    l=[n,m]
    print(l)
"""
def clic_listbox(event):
    i=listbox.curselection()  ## Récupération de l'index de l'élément sélectionné 
    return listbox.get(i)  ## On retourne l'élément (un string) sélectionné  
"""

Problème : je ne sais pas avec quelle valeur initialisée mes variables qui vont ensuite devenir des instances d'objets tkinter.
Par exemple, ici j'ai mis

root = None 

Problème : quand je veux y avoir accès depuis la lib pour agir sur ma fenêtre principale :

python3 mainproj_fork_test.py 
Traceback (most recent call last):
  File "mainproj_fork_test.py", line 66, in <module>
    my_main()
  File "mainproj_fork_test.py", line 45, in my_main
    ButtonDraw, ButtonDelete, ButtonOut = ui.create_all_buttons(root,my_canvas,h,l)
  File "/home/boblinux/Documents/projet/proj_IHM/test/test_test/libui_fork_test.py", line 38, in create_all_buttons
    BoutonOut = create_button_quitter(root)
  File "/home/boblinux/Documents/projet/proj_IHM/test/test_test/libui_fork_test.py", line 27, in create_button_quitter
    BoutonOut = tk.Button(main.root,text="Sortir",command=main.root.destroy)
AttributeError: 'NoneType' object has no attribute 'destroy'

Voilà j'espère avoir été assez clair, mon but est de pouvoir partager des variables entre plusieurs fichiers (main + lib) pour rendre le programme modulaire, mais je vois pas trop comment me débrouiller pour ça en python =D.
kiss

demandé 30-Mar-2015 par boblinux (3,092 points)
edité 31-Mar-2015 par boblinux

Sur une note complètement séparée du problème : si tu dois utiliser des variables globales dans ton programme, c'est probablement qu'il faut l'améliorer. En l'occurence, encapsuler ton UI dans une classe te permettrait d'avoir un truc plus propre.

Le but du jeu est justement de ne pas utiliser les classes (désolé j'ai oublié de le préciser dans la question)

2 Réponses

+5 votes
 
Meilleure réponse

La solution est de ne jamais utiliser main.root pour accéder a ta variable root, mais de toujours la passer ne parametre.

Par exemple, tu fais :

 def create_canvas(root):
    """ creer la zone de dessin qui va contenir l'entrelacs ainsi que le quadrillage"""
    # ...
     # ici c'est ok, tu passes root en parametre
    mycanvas = tk.Canvas(root)
    # ...
    return mycanvas

Mais plus loin, tu accèdes a root via le module :

def create_button_quitter(root):
    # tu as root en parametre, mais tu passes main.root a tk.Button : 
    BoutonOut = tk.Button(main.root,text="Sortir",command=main.root.destroy)
    return BoutonOut

Une suggestion pour t'aider a debug ton script et mieux l'architecturer : virer les globals et ne manipuler que des variables locales passés en paramètres ;)

répondu 30-Mar-2015 par jc (2,674 points)
sélectionné 19-Mai-2015 par boblinux

En fait il y a une subtilité, certes c'était un boulette d'avoir mis en param root et avoir utiliser main.root mais là n'est pas la question x) '(enfin je pense...)
Le truc c'est que quand je crée mes boutons de cette façon : (avec param)

def create_button_draw(root):
    BoutonDraw = tk.Button(main.root, text = 'draw', command=lambda:create_noeuds(root))
    return BoutonDraw

Déjà je suis obligé d'utiliser lambda pour pouvoir utiliser ma fonction en parametre command, le fait de faire comme cela créera un bouton statique, je m'explique :
Mon appli créer un boutton dessiner qui permet de dessiner un quadrillage d'une certaine dimension h*l, donc cette dimension est dynamique, c'est à dire qu'elle est entrée en paramètre par l'utilisateur. Et curieusement, si je passe create_noeuds avec le parametre root, les nouvelles valeurs entrées par l'utilisateur pour définir les dimensions de l'utilisateur ne sont pas mises à jour ><'. Et donc quoi qu'entre l'utilisateur, les dimensions du quadrillage ne changent pas et reste sur les valeurs de base lors de la création du bouton...

0 votes

Bon, en fait, une fois avoir mis tous mes variables en locales, c'est à dire les avoir passé aux parametre de ma fonction, voici le nouveau code :
voici la lib :

cat libui_fork_test.py 
import tkinter as tk
import main_fork_test as main

def create_root():
    """ creer la fenêtre principale"""
    root = tk.Tk()
    root.title('entrelaceur')
    return root

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

def create_button_draw(h,l,canvas):
    BoutonDraw = tk.Button(main.root, text = 'draw', command=lambda:create_noeuds(h,l,canvas))
    return BoutonDraw

def create_button_delete(root, my_canvas):
    BoutonDelete = tk.Button(main.root, text = 'delete', command = lambda:my_canvas.delete(tk.ALL))
    return BoutonDelete

def create_button_quitter(root):
    BoutonOut = tk.Button(main.root,text="Sortir",command=root.destroy)
    return BoutonOut

def create_all_buttons(root, canvas, h, l):
    """ Créer et pack les boutons : Draw permettant de dessiner les noeuds, Delete permettant de supprimer le quadrillage, Sortir permettant de détruire la fenetre principale"""
    BoutonDraw = create_button_draw(h,l,canvas)
    BoutonDraw.pack()

    BoutonDelete = create_button_delete(root, canvas)
    BoutonDelete.pack()

    BoutonOut = create_button_quitter(root)
    BoutonOut.pack()

    return (BoutonDraw,BoutonDelete, BoutonOut)

def create_noeuds(h,l,canvas):
    create_noeuds1(h,l,canvas)
    create_noeuds2(h,l,canvas)

def create_noeuds1(h,l,canvas):
    """ Crée un quadrillage de noeuds en fonction de sa dimension ligne/colonne """
    #h = main.hauteur_entry.get()
    #l = main.largeur_entry.get()
    i, j = 0, 0 
    x0,y0,x1,y1 = 30, 30 , 50 ,50
    # création du quadrillage de dimension ligne, colonne 
    cpt = 0 
    while i < int(l)-1 :
        while j < int(h)-1 :
            tag_current = ("mur",cpt)
            canvas.create_oval(x0,y0,x1,y1, fill = 'black', tag=tag_current)
            main.cadredict[tag_current]=[x0,y0,x1,y1]
            x0 ,x1 = x0 + 60 , x1 + 60
            j = j + 1
            cpt = cpt + 1
        j = 0
        i = i + 1
        y0, y1 = y0 + 60, y1 + 60
        x0, x1 = 30, 50
    print(main.cadredict)

def create_noeuds2(h,l,canvas):
    """ Crée le quadrillage sur lequel on basera le mur """ 
    #h = main.hauteur_entry.get()
    #l = main.largeur_entry.get()
    i, j = 0, 0
    x0,y0,x1,y1 = 0, 0, 20, 20
    cpt = 0 
    while i < int(l):
        while j < int(h):
            tag_current = ("cadre",cpt)
            canvas.create_oval(x0,y0,x1,y1, fill = 'red', tag=tag_current)
            main.murdict[tag_current]=[x0,y0,x1,y1]
            x0, x1 = x0 + 60, x1 + 60
            j = j+1
            cpt = cpt + 1
        j = 0
        i = i+1
        y0, y1 = y0 + 60, y1 + 60
        x0, x1 = 0, 20
    print(main.murdict)

def callback(event):
    """ Associe au clic de souris l'affichage des tags de l'objet graphique cliqué """
    # On récupere les tags de l'obj graphique dans tag
    items = main.my_canvas.find_withtag("current")
    tag = main.my_canvas.gettags(items)
    main.txtvar = tag
    print("tag obj graphique : ",main.txtvar)
    # On récupere les coordonnées du clic souris
    souris = main.my_canvas.find_closest(event.x, event.y)
    # On récupere les coordonnées de l'objet graphique le plus proche du clic souris
    print("coord clic : ", event.x, event.y, " ; coord obj graph visé : ",main.my_canvas.coords(souris[0]))

def create_matrice(ligne, colonne):
    n=[0]*ligne
    m=[0]*colonne
    l=[n,m]
    print(l)
"""
def clic_listbox(event):
    i=listbox.curselection()  ## Récupération de l'index de l'élément sélectionné 
    return listbox.get(i)  ## On retourne l'élément (un string) sélectionné  
"""

voici le main :

cat main_fork_test.py 
import tkinter as tk
import libui_fork_test as ui

# initialisations
imdict={}
murdict={}

cadredict={}
txtvar = ""
h, l = 2,2
current_coord = 0

root, my_canvas = None, None
ButtonDraw, ButtonDelete, ButtonOut = None, None, None
hauteur_label, largeur_label = None, None
hauteur_entry, largeur_entry = None, None

def get_label_dim():
    return largeur_label, hauteur_label

def get_label_fen():
    return root, my_canvas

def get_Buttons():
    return ButtonDraw, ButtonDelete, ButtonOu

def get_entry():
    return hauteur_entry, largeur_entry 

def my_main():

    global root, my_canvas, ButtonDraw, ButtonDelete, ButtonOut, hauteur_label, hauteur_entry, largeur_label, largeur_entry

    # fenêtre principale

    root = ui.create_root()

    # zone graphique

    my_canvas = ui.create_canvas(root)
    my_canvas.pack(side = tk.LEFT) 

    # configuration des entrées hauteur/largeur du quadrillage

    hauteur_label = tk.Label(root, text = 'hauteur')
    hauteur_label.pack()
    hauteur_entry = tk.Entry(root)
    hauteur_entry.pack() 

    largeur_label = tk.Label(root, text = 'largeur')
    largeur_label.pack()
    largeur_entry = tk.Entry(root)
    largeur_entry.pack() 

    # configuration des bouttons
    ButtonDraw, ButtonDelete, ButtonOut = ui.create_all_buttons(root,my_canvas,h,l)
    # gestion evt clic gauche

    my_canvas.bind('<Button-1>',ui.callback)

    root.mainloop()

if __name__=='__main__':
    my_main()

Du coup, comme je disais en commentaires, l'utilisateur ne peut plus changer dynamiquement les dimensions du quadrillage... C'est à dire qu'il est toujours lancé avec les valeurs par défaut 2*2.
Vous pouvez l'essayer pour vérifier par vous-mêmes (ps : mettez un fichier image nommé theme4.gif dans votre dossier courant pour que l'exécution fonctionne normalment x))
. Du coup j'en suis toujour au point de départ,
A savoir : soit je donne des param à mes fct° et donc j'ai un quadrillage fixe et non dynamique à cause du lambda:create_noeuds(h,l,canvas) que je dois utiliser pour associer une commande au bouton Dessiner.
Soit j'essayer d'utiliser des variables globales d'un autre fichier, mais dans ce cas si je les initialise avec null, je ne pourrais pas les utiliser.. Je ne vois pas comment faire là j'suis bloqué =D.
La solut° serait de n'utiliser aucun parametre et d'utiliser uniquement les variables du main, mais dans ce cas avec quelle valeur les initialisées? vu que ce sont des objets tkinter :OO (c'est un vrai casse-tête :PP )

répondu 1-Avr-2015 par boblinux (3,092 points)
edité 1-Avr-2015 par boblinux

Pour le probleme de quadrillage fixe a cause de la lambda, tu auras peut être un élément de réponse sur ce post : http://stackoverflow.com/questions/20717203/python-tkinter-setting-up-button-callback-functions-with-a-loop

Question annexe : pourquoi as-tu une contrainte sur le fait de ne pas utiliser de classes ?

en un mot : le prof ><'
Sinon j'ai essayé la solution du lien, mais à vrai dire je n'ai pas très bien pigé sa contenance, mais au copier-coller ça n'a toujours pas l'air de fonctionner =/

...