Melhor maneira de estruturar um aplicativo tkinter?

136

A seguir, é apresentada a estrutura geral do meu típico programa python tkinter.

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 funBe funCexibirá outras Topleveljanelas com widgets quando o usuário clicar no botão 1, 2, 3.

Gostaria de saber se esta é a maneira correta de escrever um programa python tkinter? Claro, funcionará mesmo se eu escrever dessa maneira, mas é a melhor? Parece estúpido, mas quando vejo os códigos que outras pessoas escrevem, o código deles não é bagunçado com muitas funções e, principalmente, elas têm classes.

Existe alguma estrutura específica que devemos seguir como boas práticas? Como devo planejar antes de começar a escrever um programa python?

Sei que não existem práticas recomendadas em programação e também não estou pedindo. Eu só quero alguns conselhos e explicações para me manter na direção certa enquanto estou aprendendo Python sozinho.

Chris Aung
fonte
2
Aqui está um excelente tutorial sobre o design da interface gráfica do usuário tkinter, com alguns exemplos - python-textbok.readthedocs.org/en/latest/… Aqui está outro exemplo com um padrão de design MVC - sukhbinder.wordpress.com/2014/12/ 25 /…
Bondolin 18/15
12
Essa pergunta pode ser ampla, mas é útil eh como uma resposta relativamente popular (em relação a quase todas as outras respostas [tkinter]). Estou nomeando para reabrir, pois vejo que abrir é mais útil do que fechar.
Bryan Oakley

Respostas:

271

Defendo uma abordagem orientada a objetos. Este é o modelo com o qual começo:

# 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()

As coisas importantes a serem observadas são:

  • Eu não uso uma importação de curinga. Eu importo o pacote como "tk", o que exige que eu prefixe todos os comandos com tk.. Isso evita a poluição global do espaço para nome, além de tornar o código completamente óbvio quando você está usando as classes Tkinter, ttk ou algumas de sua preferência.

  • A principal aplicação é uma classe . Isso fornece um espaço de nome privado para todos os seus retornos de chamada e funções privadas, e geralmente facilita a organização do seu código. Em um estilo processual, é necessário codificar de cima para baixo, definindo funções antes de usá-las, etc. Com esse método, você não cria, pois na verdade não cria a janela principal até a última etapa. Prefiro herdar tk.Frameapenas porque normalmente começo criando um quadro, mas não é de forma alguma necessário.

Se seu aplicativo tiver janelas adicionais de nível superior, recomendo que cada uma delas seja uma classe separada, herdada de tk.Toplevel. Isso oferece as mesmas vantagens mencionadas acima - as janelas são atômicas, possuem seu próprio espaço para nome e o código é bem organizado. Além disso, facilita a inserção de cada um em seu próprio módulo assim que o código começa a aumentar.

Por fim, você pode considerar o uso de classes para todas as partes principais da sua interface. Por exemplo, se você estiver criando um aplicativo com uma barra de ferramentas, um painel de navegação, uma barra de status e uma área principal, poderá criar cada uma dessas classes. Isso torna seu código principal bem pequeno e fácil de entender:

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)

Como todas essas instâncias compartilham um pai comum, o pai se torna efetivamente a parte "controladora" de uma arquitetura de controlador de exibição de modelo. Assim, por exemplo, a janela principal pode colocar algo na barra de status chamando self.parent.statusbar.set("Hello, world"). Isso permite definir uma interface simples entre os componentes, ajudando a manter o acoplamento no mínimo.

Bryan Oakley
fonte
22
@Bryan Oakley, você conhece algum bom exemplo de código na Internet que eu possa estudar sua estrutura?
precisa
2
Eu apóio a abordagem orientada a objetos. No entanto, abster-se de usar herança em sua classe que chama a GUI é uma boa ideia, na minha experiência. Ele oferece mais flexibilidade se os objetos Tk e Frame forem atributos de uma classe que não herda de nada. Dessa forma, você pode acessar os objetos Tk e Frame com mais facilidade (e menos ambiguamente), e destruir um não destruirá tudo na sua classe, se você não quiser. Esqueci a razão exata por que isso é vital em alguns programas, mas permite que você faça mais coisas.
Brōtsyorfuzthrāx
1
simplesmente não ter uma classe lhe dará um espaço para nome privado? por que subclassificar o quadro melhorar isso?
gcb 8/09/15
3
@gcb: sim, qualquer classe fornecerá um espaço para nome privado. Por que subclassificar um quadro? Normalmente, eu vou criar um quadro de qualquer maneira, portanto é uma classe a menos para gerenciar (subclasse de Quadro, vs uma classe herdada do objeto, com um quadro como atributo). Eu reformulei a resposta um pouco para deixar isso mais claro. Obrigado pelo feedback.
Bryan Oakley
2
@madtyn: não há necessidade de salvar uma referência parent, a menos que você a utilize mais tarde. Não o salvei porque nenhum dos códigos no meu exemplo exigia que ele fosse salvo.
Bryan Oakley
39

Colocar cada uma de suas janelas de nível superior em sua própria classe separada fornece reutilização de código e melhor organização de código. Quaisquer botões e métodos relevantes presentes na janela devem ser definidos dentro desta classe. Aqui está um exemplo (retirado daqui ):

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()

Veja também:

Espero que ajude.

alecxe
fonte
6

Esta não é uma estrutura ruim; vai funcionar muito bem. No entanto, você precisa ter funções em uma função para executar comandos quando alguém clica em um botão ou algo

Então, o que você pode fazer é escrever classes para elas e, em seguida, ter métodos na classe que manipulam comandos para os cliques no botão e outros.

Aqui está um exemplo:

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()

Normalmente, programas tk com várias janelas são várias classes grandes e, em __init__todas as entradas, rótulos etc., são criados e, em seguida, cada método é manipular eventos de clique no botão

Não existe realmente uma maneira certa de fazê-lo, o que funciona para você e faz o trabalho contanto que seja legível e você pode explicá-lo facilmente, porque se você não puder explicar facilmente seu programa, provavelmente haverá uma maneira melhor de fazê-lo. .

Dê uma olhada no Thinking in Tkinter .

Serial
fonte
3
"Thinking in Tkinter" defende as importações globais, o que eu acho que é um péssimo conselho.
Bryan Oakley
1
Thts verdade Eu não sugiro que você use globals apenas algumas das você está certa classe methos estrutura principal :)
Serial
2

OOP deve ser a abordagem e framedeve ser uma variável de classe em vez de variável de instância .

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()

insira a descrição da imagem aqui

Referência: http://www.python-course.eu/tkinter_buttons.php

Trevor
fonte
2
Você só pode usar TKinterno Python 2. Eu recomendaria usar o tkinterPython 3. Eu também colocaria as últimas três linhas de código em uma main()função e chamaria isso no final do programa. Eu definitivamente evitaria usá- from module_name import *lo, pois polui o espaço para nome global e pode reduzir a legibilidade.
Zac
1
Como você pode dizer a diferença entre button1 = tk.Button(root, command=funA)e button1 = ttk.Button(root, command=funA)se o tkintermódulo de extensão também estava sendo importado? Com a *sintaxe, ambas as linhas de código parecem ser button1 = Button(root, command=funA). Eu não recomendaria usar essa sintaxe.
Zac
0

A organização do seu aplicativo usando classe facilita para você e outras pessoas que trabalham com você a depurar problemas e melhorar o aplicativo com facilidade.

Você pode organizar facilmente seu aplicativo assim:

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
fonte
-2

Provavelmente, a melhor maneira de aprender a estruturar seu programa é lendo o código de outras pessoas, especialmente se for um programa grande para o qual muitas pessoas contribuíram. Depois de analisar o código de muitos projetos, você deve ter uma idéia de qual deve ser o estilo de consenso.

O Python, como linguagem, é especial, pois há algumas diretrizes fortes sobre como você deve formatar seu código. O primeiro é o chamado "Zen of Python":

  • Bonito é melhor que feio.
  • Explícito é melhor que implícito.
  • Simples é melhor que complexo.
  • Complexo é melhor que complicado.
  • Flat é melhor que aninhado.
  • Esparso é melhor que denso.
  • Legibilidade conta.
  • Casos especiais não são especiais o suficiente para violar as regras.
  • Embora praticidade supere a pureza.
  • Os erros nunca devem passar silenciosamente.
  • A menos que seja explicitamente silenciado.
  • Diante da ambiguidade, recuse a tentação de adivinhar.
  • Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo.
  • Embora esse caminho possa não ser óbvio a princípio, a menos que você seja holandês.
  • Agora é melhor do que nunca.
  • Embora nunca tenha sido muitas vezes é melhor do que direito agora.
  • Se a implementação é difícil de explicar, é uma má ideia.
  • Se a implementação é fácil de explicar, pode ser uma boa ideia.
  • Os namespaces são uma ótima idéia - vamos fazer mais!

Em um nível mais prático, há o PEP8 , o guia de estilo para o Python.

Com isso em mente, eu diria que seu estilo de código não se encaixa realmente, principalmente as funções aninhadas. Encontre uma maneira de simplificá-las, usando classes ou movendo-as para módulos separados. Isso tornará a estrutura do seu programa muito mais fácil de entender.

Inbar Rose
fonte
12
-1 para usar o Zen do Python. Embora seja um bom conselho, ele não aborda diretamente a pergunta que foi feita. Retire o último parágrafo e esta resposta pode se aplicar a quase todas as perguntas sobre python neste site. É um conselho bom e positivo, mas não responde à pergunta.
Bryan Oakley
1
@BryanOakley, eu discordo de você nisso. Sim, o Zen do Python é amplo e pode ser usado para resolver muitas questões. Ele mencionou no parágrafo final a opção por aulas ou a colocação de funções em módulos separados. Ele também mencionou o PEP8, um guia de estilo para Python, com referências a ele. Embora não seja uma resposta direta, acho que essa resposta é credível no fato de mencionar muitas rotas diferentes que podem ser tomadas. Essa é apenas a minha opinião
Zac
1
Eu vim aqui procurando respostas para esta pergunta específica. Mesmo para uma pergunta em aberto, não posso fazer nada com esta resposta. -1'd de mim também.
jonathan
De jeito nenhum, a questão está prestes a estruturar um aplicativo tkinter , nada sobre diretrizes de estilo / codificação / zen. Fácil como citar @Arbiter "Embora não seja uma resposta direta", portanto, NÃO é uma resposta. É como "talvez sim e talvez não", com o zen anexado.
M3nda
-7

Eu pessoalmente não uso a abordagem orientada a objeções, principalmente porque a) apenas atrapalha; b) você nunca reutilizará isso como um módulo.

mas algo que não é discutido aqui, é que você deve usar encadeamento ou multiprocessamento. Sempre. caso contrário, seu aplicativo será péssimo.

basta fazer um teste simples: inicie uma janela e, em seguida, busque algum URL ou qualquer outra coisa. alterações são sua interface do usuário não será atualizada enquanto a solicitação de rede estiver acontecendo. Ou seja, a janela do seu aplicativo será quebrada. dependem do sistema operacional em que você está, mas, na maioria das vezes, ele não será redesenhado; tudo o que você arrastar pela janela será colado nele, até que o processo retorne ao mainloop do TK.

gcb
fonte
4
O que você diz simplesmente não é verdade. Eu escrevi hudreds de aplicativos baseados em tk, tanto pessoais quanto comerciais, e quase nunca tive que usar threads. Os threads têm seu lugar, mas simplesmente não é verdade que você deve usá-los ao escrever programas tkinter. Se você possui funções de execução longa, pode precisar de threads ou multiprocessamento, mas existem muitos tipos de programas que você pode escrever que não precisam de threads.
Bryan Oakley
Eu acho que se você reformulasse sua resposta para ficar um pouco mais claro, seria uma resposta melhor. Também seria realmente bom ter um exemplo canônico de usar threads com o tkinter.
Bryan Oakley
não se preocupou em ser a melhor resposta aqui, porque é meio fora de tópico. mas lembre-se de que começar com threading / multip é muito fácil. se você precisar adicionar mais tarde, é uma batalha perdida. e hoje em dia, não existe absolutamente nenhum aplicativo que nunca fale com a rede. e mesmo que você ignore e pense 'eu só tenho pouca E / S de disco', amanhã seu cliente decide que o arquivo ficará no NFS e você estará aguardando a E / S de rede e seu aplicativo parecerá morto.
gcb 8/09/15
2
@ erm3nda: "todo aplicativo conectado à rede ou gravando IO será muito mais rápido usando threads ou subprocessos" - isso simplesmente não é verdade. A segmentação não necessariamente tornará seu programa mais rápido e, em alguns casos, será mais lento. Na programação da GUI, o principal motivo para usar os threads é poder executar algum código que, de outra forma, bloquearia a GUI.
Bryan Oakley
2
@ erm3nda: não, eu sou não dizendo tópicos não são necessários em tudo . Eles são definitivamente necessários (bem, threads ou multiprocessamento) para muitas coisas. Apenas existe uma classe muito grande de aplicativos de GUI em que o tkinter é adequado, mas onde os threads simplesmente não são necessários. E sim, "instaladores, blocos de notas e outras ferramentas fáceis" se enquadram nessa categoria. O mundo é composto por mais dessas "ferramentas fáceis" do que por coisas como word, excel, photoshop, etc. Além disso, lembre-se de que o contexto aqui é o seguinte . O Tkinter normalmente não é usado para aplicativos muito grandes e complexos.
Bryan Oakley