Meilleur moyen de structurer une application tkinter?

136

Ce qui suit est la structure générale de mon programme tkinter python typique.

def funA():
    def funA1():
        def funA12():
            # stuff

    def funA2():
        # stuff

def funB():
    def funB1():
        # stuff

    def funB2():
        # stuff

def funC():
    def funC1():
        # stuff

    def funC2():
        # stuff


root = tk.Tk()

button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()

funA funBet funCfera apparaître une autre Toplevelfenêtre avec des widgets lorsque l'utilisateur cliquera sur le bouton 1, 2, 3.

Je me demande si c'est la bonne façon d'écrire un programme python tkinter? Bien sûr, cela fonctionnera même si j'écris de cette façon, mais est-ce le meilleur moyen? Cela semble stupide mais quand je vois les codes écrits par d'autres personnes, leur code n'est pas foiré avec un tas de fonctions et la plupart ont des classes.

Y a-t-il une structure spécifique que nous devrions suivre comme bonne pratique? Comment dois-je planifier avant de commencer à écrire un programme python?

Je sais qu'il n'existe pas de meilleure pratique en matière de programmation et je ne la demande pas non plus. Je veux juste quelques conseils et explications pour me garder dans la bonne direction alors que j'apprends Python par moi-même.

Chris Aung
la source
2
Voici un excellent tutoriel sur la conception de l'interface graphique tkinter, avec quelques exemples - python-textbok.readthedocs.org/en/latest / ... Voici un autre exemple avec un modèle de conception MVC - sukhbinder.wordpress.com/2014/12/ 25 /…
Bondolin
12
Cette question peut être large, mais elle est utile et est une réponse relativement populaire (par rapport à presque toutes les autres réponses [tkinter]). Je propose de rouvrir, car je vois qu'il est plus utile de l'ouvrir que de le fermer.
Bryan Oakley

Réponses:

271

Je préconise une approche orientée objet. Voici le modèle avec lequel je commence:

# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        <create the rest of your GUI here>

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Les choses importantes à noter sont:

  • Je n'utilise pas d'importation générique. J'importe le package en tant que "tk", ce qui nécessite que je préfixe toutes les commandes avec tk.. Cela évite la pollution globale des espaces de noms, et rend le code complètement évident lorsque vous utilisez des classes Tkinter, des classes ttk ou certaines des vôtres.

  • L'application principale est une classe . Cela vous donne un espace de noms privé pour tous vos rappels et fonctions privées, et facilite généralement l'organisation de votre code. Dans un style procédural, vous devez coder de haut en bas, définir les fonctions avant de les utiliser, etc. Avec cette méthode, vous ne le faites pas puisque vous ne créez réellement la fenêtre principale qu'à la toute dernière étape. Je préfère hériter de tk.Framejuste parce que je commence généralement par créer un cadre, mais ce n'est en aucun cas nécessaire.

Si votre application a des fenêtres de niveau supérieur supplémentaires, je vous recommande de faire de chacune de celles-ci une classe distincte, héritant de tk.Toplevel. Cela vous donne tous les mêmes avantages mentionnés ci-dessus - les fenêtres sont atomiques, elles ont leur propre espace de noms et le code est bien organisé. De plus, il est facile de mettre chacun dans son propre module une fois que le code commence à devenir volumineux.

Enfin, vous voudrez peut-être envisager d'utiliser des classes pour chaque partie majeure de votre interface. Par exemple, si vous créez une application avec une barre d'outils, un volet de navigation, une barre d'état et une zone principale, vous pouvez créer chacune de ces classes. Cela rend votre code principal assez petit et facile à comprendre:

class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.statusbar = Statusbar(self, ...)
        self.toolbar = Toolbar(self, ...)
        self.navbar = Navbar(self, ...)
        self.main = Main(self, ...)

        self.statusbar.pack(side="bottom", fill="x")
        self.toolbar.pack(side="top", fill="x")
        self.navbar.pack(side="left", fill="y")
        self.main.pack(side="right", fill="both", expand=True)

Puisque toutes ces instances partagent un parent commun, le parent devient effectivement la partie «contrôleur» d'une architecture modèle-vue-contrôleur. Ainsi, par exemple, la fenêtre principale pourrait placer quelque chose sur la barre d'état en appelant self.parent.statusbar.set("Hello, world"). Cela vous permet de définir une interface simple entre les composants, contribuant à maintenir le couplage à un minimum.

Bryan Oakley
la source
22
@Bryan Oakley connaissez-vous de bons exemples de codes sur Internet que je peux étudier leur structure?
Chris Aung
2
Je seconde l'approche orientée objet. Cependant, s'abstenir d'utiliser l'héritage sur votre classe qui appelle l'interface graphique est une bonne idée, d'après mon expérience. Il vous offre plus de flexibilité si les objets Tk et Frame sont des attributs d'une classe qui n'hérite de rien. De cette façon, vous pouvez accéder aux objets Tk et Frame plus facilement (et de manière moins ambiguë), et en détruire un ne détruira pas tout dans votre classe si vous ne le souhaitez pas. J'ai oublié la raison exacte pour laquelle c'est essentiel dans certains programmes, mais cela vous permet de faire plus de choses.
Brōtsyorfuzthrāx
1
le simple fait d'avoir une classe ne vous donnera-t-il pas un espace de noms privé? pourquoi sous-classer le cadre améliorer cela?
gcb
3
@gcb: oui, n'importe quelle classe vous donnera un espace de noms privé. Pourquoi sous-classer un cadre? Je vais généralement créer un cadre de toute façon, donc c'est une classe de moins à gérer (sous-classe de Frame, vs une classe héritant d'un objet, avec un cadre comme attribut). J'ai légèrement reformulé la réponse pour que cela soit plus clair. Merci pour les commentaires.
Bryan Oakley
2
@madtyn: il n'est pas nécessaire d'enregistrer une référence parent, à moins que vous ne l' utilisiez plus tard. Je ne l'ai pas sauvegardé car aucun des codes de mon exemple n'exigeait qu'il soit sauvegardé.
Bryan Oakley du
39

Le fait de placer chacune de vos fenêtres de niveau supérieur dans sa propre classe distincte vous permet de réutiliser le code et d'une meilleure organisation du code. Tous les boutons et méthodes pertinentes présents dans la fenêtre doivent être définis dans cette classe. Voici un exemple (tiré d' ici ):

import tkinter as tk

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow)

class Demo2:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
        self.quitButton.pack()
        self.frame.pack()
    def close_windows(self):
        self.master.destroy()

def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Regarde aussi:

J'espère que cela pourra aider.

alecxe
la source
6

Ce n'est pas une mauvaise structure; cela fonctionnera très bien. Cependant, vous devez avoir des fonctions dans une fonction pour exécuter des commandes lorsque quelqu'un clique sur un bouton ou quelque chose

Donc, ce que vous pouvez faire est d'écrire des classes pour ceux-ci, puis d'avoir des méthodes dans la classe qui gèrent les commandes pour les clics de bouton et autres.

Voici un exemple:

import tkinter as tk

class Window1:
    def __init__(self, master):
        pass
        # Create labels, entries,buttons
    def button_click(self):
        pass
        # If button is clicked, run this method and open window 2


class Window2:
    def __init__(self, master):
        #create buttons,entries,etc

    def button_method(self):
        #run this when button click to close window
        self.master.destroy()

def main(): #run mianloop 
    root = tk.Tk()
    app = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Habituellement, les programmes tk avec plusieurs fenêtres sont de plusieurs grandes classes et dans le __init__ toutes les entrées, les étiquettes, etc. sont créées, puis chaque méthode consiste à gérer les événements de clic de bouton

Il n'y a pas vraiment de bonne façon de le faire, tout ce qui fonctionne pour vous et fait le travail du moment qu'il est lisible et vous pouvez facilement l'expliquer car si vous ne pouvez pas facilement expliquer votre programme, il y a probablement une meilleure façon de le faire .

Jetez un œil à Penser à Tkinter .

En série
la source
3
"Penser en Tkinter" prône les importations mondiales, ce qui, à mon avis, est un très mauvais conseil.
Bryan Oakley
1
C'est vrai, je ne suggère pas que vous utilisiez des globals juste une partie de la structure methos de la classe principale, vous avez raison :)
Série
2

La POO doit être l'approche et framedoit être une variable de classe au lieu d' une variable d' instance .

from Tkinter import *
class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print "Tkinter is easy to use!"

root = Tk()
app = App(root)
root.mainloop()

entrez la description de l'image ici

Référence: http://www.python-course.eu/tkinter_buttons.php

Trevor
la source
2
Vous ne pouvez utiliser que TKintersur Python 2. Je recommanderais d'utiliser tkinterpour Python 3. Je placerais également les trois dernières lignes de code sous une main()fonction et l'appellerais à la fin du programme. J'éviterais certainement de l' utiliser from module_name import *car il pollue l'espace de noms global et peut réduire la lisibilité.
Zac
1
Comment pourriez-vous faire la différence entre button1 = tk.Button(root, command=funA)et button1 = ttk.Button(root, command=funA)si le tkintermodule d'extension était également importé? Avec la *syntaxe, les deux lignes de code semblent être button1 = Button(root, command=funA). Je ne recommanderais pas d'utiliser cette syntaxe.
Zac
0

L'organisation de votre application à l'aide de la classe facilite le débogage des problèmes et l'amélioration de l'application pour vous et les autres personnes qui travaillent avec vous.

Vous pouvez facilement organiser votre application comme ceci:

class hello(Tk):
    def __init__(self):
        super(hello, self).__init__()
        self.btn = Button(text = "Click me", command=close)
        self.btn.pack()
    def close():
        self.destroy()

app = hello()
app.mainloop()
muyustan
la source
-2

La meilleure façon d'apprendre à structurer votre programme est probablement de lire le code d'autres personnes, surtout s'il s'agit d'un grand programme auquel de nombreuses personnes ont contribué. Après avoir examiné le code de nombreux projets, vous devriez avoir une idée de ce que devrait être le style de consensus.

Python, en tant que langage, est spécial en ce sens qu'il existe des directives strictes sur la façon dont vous devez formater votre code. Le premier est le soi-disant "Zen of Python":

  • Beau est mieux que laid.
  • L'explicite vaut mieux que l'implicite.
  • Le simple vaut mieux que le complexe.
  • Complexe vaut mieux que compliqué.
  • Plat vaut mieux que niché.
  • Clairsemé vaut mieux que dense.
  • La lisibilité compte.
  • Les cas spéciaux ne sont pas assez spéciaux pour enfreindre les règles.
  • Bien que l'aspect pratique l'emporte sur la pureté.
  • Les erreurs ne devraient jamais passer silencieusement.
  • À moins d'être explicitement réduit au silence.
  • Face à l'ambiguïté, refusez la tentation de deviner.
  • Il devrait y avoir une - et de préférence une seule - façon évidente de le faire.
  • Bien que cette manière ne soit pas évidente au début, sauf si vous êtes néerlandais.
  • C'est mieux que jamais.
  • Bien que jamais ne soit souvent mieux que bien maintenant.
  • Si l'implémentation est difficile à expliquer, c'est une mauvaise idée.
  • Si la mise en œuvre est facile à expliquer, cela peut être une bonne idée.
  • Les espaces de noms sont une excellente idée - faisons-en plus!

Sur un plan plus pratique, il y a PEP8 , le guide de style pour Python.

Dans cet esprit, je dirais que votre style de code ne convient pas vraiment, en particulier les fonctions imbriquées. Trouvez un moyen de les aplatir, soit en utilisant des classes, soit en les déplaçant dans des modules séparés. Cela rendra la structure de votre programme beaucoup plus facile à comprendre.

Rose Inbar
la source
12
-1 pour utiliser le Zen de Python. Bien que ce soient tous de bons conseils, cela ne répond pas directement à la question qui a été posée. Retirez le dernier paragraphe et cette réponse pourrait s'appliquer à presque toutes les questions python sur ce site. C'est un bon conseil positif, mais cela ne répond pas à la question.
Bryan Oakley
1
@BryanOakley Je ne suis pas d'accord avec vous sur ce point. Oui, le Zen de Python est large et peut être utilisé pour répondre à de nombreuses questions. Il a mentionné dans le dernier paragraphe d'opter pour des classes ou de placer les fonctions dans des modules séparés. Il a également mentionné PEP8, un guide de style pour Python, avec des références à celui-ci. Bien qu'il ne s'agisse pas d'une réponse directe, je pense que cette réponse est crédible dans le fait qu'elle mentionne de nombreuses voies différentes qui peuvent être empruntées. C'est juste mon avis
Zac
1
Je suis venu ici pour chercher des réponses à cette question spécifique. Même pour une question ouverte, je ne peux rien faire avec cette réponse. -1'd de moi aussi.
jonathan
Pas question, la question est sur le point de structurer une application tkinter , rien sur les directives de style / codage / zen. Facile comme citer @Arbiter "Bien que ce ne soit pas une réponse directe", ce n'est PAS une réponse. C'est comme "peut-être oui et peut-être non", avec zen en tête.
m3nda
-7

Personnellement, je n'utilise pas l'approche orientée objet, principalement parce qu'elle a) ne fait que gêner; b) vous ne réutiliserez jamais cela en tant que module.

mais quelque chose qui n'est pas discuté ici, c'est que vous devez utiliser le threading ou le multiprocessing. Toujours. sinon votre application sera horrible.

faites simplement un test simple: lancez une fenêtre, puis récupérez une URL ou autre chose. les modifications sont que votre interface utilisateur ne sera pas mise à jour pendant la demande réseau. Cela signifie que la fenêtre de votre application sera cassée. dépend du système d'exploitation sur lequel vous vous trouvez, mais la plupart du temps, il ne sera pas redessiné, tout ce que vous faites glisser sur la fenêtre sera collé dessus, jusqu'à ce que le processus revienne à la boucle principale TK.

gcb
la source
4
Ce que vous dites n'est tout simplement pas vrai. J'ai écrit des tas d'applications basées sur tk, à la fois personnelles et commerciales, et je n'ai presque jamais eu à utiliser de threads. Les threads ont leur place, mais il n'est tout simplement pas vrai que vous devez les utiliser lors de l'écriture de programmes tkinter. Si vous avez de longues fonctions d'exécution, vous aurez peut-être besoin de threads ou de multiprocesseurs, mais il existe de très nombreux types de programmes que vous pouvez écrire qui n'ont pas besoin de threads.
Bryan Oakley
Je pense que si vous reformuliez votre réponse pour être un peu plus clair à ce sujet, ce serait une meilleure réponse. Il serait également très utile d'avoir un exemple canonique d'utilisation de threads avec tkinter.
Bryan Oakley
ne se souciait pas d'être la meilleure réponse ici car c'est un peu hors sujet. mais gardez à l'esprit que commencer par threading / multip est très simple. si vous devez ajouter plus tard, c'est une bataille perdue. et de nos jours, il n'y a absolument aucune application qui ne parlera jamais au réseau. et même si vous ignorez et pensez «je n'ai que peu d'E / S disque», demain votre client décide que le fichier vivra sur NFS et vous attendez les E / S réseau et votre application semble morte.
gcb
2
@ erm3nda: "chaque application connectée au réseau ou effectuant des écritures IO sera beaucoup plus rapide en utilisant des threads ou des sous-processus" - ce n'est tout simplement pas vrai. Le thread ne rendra pas nécessairement votre programme plus rapide et, dans certains cas, le ralentira. Dans la programmation GUI, la principale raison d'utiliser des threads est de pouvoir exécuter du code qui autrement bloquerait l'interface graphique.
Bryan Oakley le
2
@ erm3nda: non, je ne dis fils ne sont pas nécessaires du tout . Ils sont vraiment nécessaires (enfin, threads ou multiprocessing) pour beaucoup de choses. C'est juste qu'il existe une très grande classe d'applications GUI où tkinter convient mais où les threads ne sont tout simplement pas nécessaires. Et oui, les «installateurs, blocs-notes et autres outils simples» entrent dans cette catégorie. Le monde est composé de plus de ces "outils faciles" que de choses comme Word, Excel, Photoshop, etc. De plus, rappelez-vous que le contexte ici est tkinter . Tkinter n'est généralement pas utilisé pour de très grandes applications complexes.
Bryan Oakley le