Tutorial: Aplicação em Python + SQLite (Parte 01)
Overview
Bem-vindos a um passeio guiado pelo mundo da programação com Python! Neste tutorial, vamos mergulhar na criação de uma aplicação CRUD de clientes usando Python e SQLite. Com passos claros e um exemplo prático, transitaremos do frontend básico à compilação final para Windows (.exe) e Mac (.app). Prepare seu ambiente Python, e vamos juntos dar vida a essa aplicação!
Neste tutorial, vou demonstrar como construir uma aplicação simples em Python e persistir os dados utilizando SQLite. A aplicação que será criada é um CRUD de clientes bem simples, mas que servirá para a demonstração.
Ao final do tutorial, demonstrarei como compilar a aplicação criada para Windows (.exe) e para Mac (.app).
A aplicação irá se parecer com a imagem abaixo:
Cadastro de Clientes em Python
Não vou entrar muito na teoria sobre qual o melhor lugar para se começar: Frontend ou Backend. Acredito que, para este tutorial, o melhor será começar pelo Frontend.
Pré-requisito:
- Ter Python instalado na maquina;
- Possuir conhecimentos básicos em Python;
Índice:
- Parte 01: Frontend <[Você está aqui];
- Parte 02: Backend;
- Parte 03: Ligando o Backend com o Frontend;
- Parte 04: Compilando para Windows/Mac.
Parte 01_ Frontend;
Primeiro, crie um arquivo chamado GUI.py e realize a importação da biblioteca tkinter (utilizada para criar interface gráfica).
from tkinter import *
Agora defina uma classe chamada Gui:
class Gui():
"""Classe que define a interface gráfica da aplicação
"""
Como você sabe, o Python utiliza endentação para controlar o conteúdo de classes e funções. Com isso em mente todo o código a seguir fará parte dele. Vou manter a endentação, mas não sei se eles serão exibidos corretamente.
A primeira coisa que vamos definir é o objeto da janela e um título para ela:
#Criando a janela...
window = Tk()
window.wm_title("Cadastro de Clientes")
No Python, os objetos de input (entry) precisam de uma variável para fazer um bind com o valor, ou seja, o campo de input vai sempre jogar o valor que for inserido nele em uma variável.
Como você pode perceber pela imagem de exemplo, vamos ter quatro campos para o cadastro de clientes: Nome, Sobrenome, Email e CPF. Então vamos definir as variáveis que vamos precisar para esta aplicação:
#Criando variáveis que armazenarão o texto inserido pelo usuário...
txtNome = StringVar()
txtSobrenome = StringVar()
txtEmail = StringVar()
txtCPF = StringVar()
Agora vamos criar as Labels que ficarão ao lado dos campos:
#Criando os objetos que estarão na janela...
lblnome = Label(window, text="Nome")
lblsobrenome = Label(window, text="Sobrenome")
lblemail = Label(window, text="Email")
lblcpf = Label(window, text="CPF")
No fonte acima, instanciamos 4 labels. O primeiro argumento (window) define a qual janela este label pertence. Todos os objetos criados precisam desta variável para definir e o segundo (text=“Nome”) define o texto que será exibido nele.
Agora que temos os Labels, vamos definir os campos de input (entry):
entNome = Entry(window, textvariable=txtNome)
entSobrenome = Entry(window, textvariable=txtSobrenome)
entEmail = Entry(window, textvariable=txtEmail)
entCPF = Entry(window, textvariable=txtCPF)
No fonte acima, o primeiro argumento funciona da mesma forma que nos labels. Isso vai ser comum para todos os componentes (widget), então não vou mais comentar sobre ele. O segundo argumento (textvariable=txtNome) indica qual a variável que fará o bind com o valor do campo, ou seja, qual é a variável que receberá sempre o valor inserido no campo.
O próximo passo é criar o ListBox que listará todos os clientes cadastrados.
listClientes = Listbox(window)
E também um ScrollBar para funcionar em conjunto com o ListBox.
scrollClientes = Scrollbar(window)
Neste primeiro momento, estamos apenas criando o ScrollBar, mas precisaremos realizar outras ações neste objeto.
Agora vamos criar os botões que estarão disponíveis na interface:
btnViewAll = Button(window, text="Ver todos")
btnBuscar = Button(window, text="Buscar")
btnInserir = Button(window, text="Inserir")
btnUpdate = Button(window, text="Atualizar Selecionados")
btnDel = Button(window, text="Deletar Selecionados")
btnClose = Button(window, text="Fechar")
Assim como nos Labels, o argumento text (text=“Ver todos”) serve para definir qual o valor exibido no botão.
Todos os objetos que precisaremos estão criados, precisamos posicioná-los na janela. Para isso, vamos utilizar o posicionamento por grid. Esta operação é parecida com a forma que posicionamos elementos em uma grid no XAML, com a diferença que não precisamos definir quantidade de linhas ou colunas.
#Associando os objetos a grid da janela...
lblnome.grid(row=0,column=0)
lblsobrenome.grid(row=1,column=0)
lblemail.grid(row=2,column=0)
lblcpf.grid(row=3, column=0)
entNome.grid(row=0, column=1, padx=50, pady=50)
entSobrenome.grid(row=1, column=1)
entEmail.grid(row=2, column=1)
entCPF.grid(row=3, column=1)
listClientes.grid(row=0, column=2, rowspan=10)
scrollClientes.grid(row=0, column=6, rowspan=10)
btnViewAll.grid(row=4, column=0, columnspan=2)
btnBuscar.grid(row=5, column=0, columnspan=2)
btnInserir.grid(row=6, column=0, columnspan=2)
btnUpdate.grid(row=7, column=0, columnspan=2)
btnDel.grid(row=8, column=0, columnspan=2)
btnClose.grid(row=9, column=0, columnspan=2)
No código acima, definidos em qual linha (row) cada objeto está posicionado e em qual coluna (column). Quando necessário, utilizamos o columnspan para fazer com que um objeto ocupe mais de uma coluna ou o rowspan para fazer com que o objeto ocupe mais de uma linha.
O próximo passo é ligar o ListBox ao ScrollBar:
#Associando a Scrollbar com a Listbox...
listClientes.configure(yscrollcommand=scrollClientes.set)
No fonte acima, o argumento yscrollcommand é responsável por definir qual é o objeto do tipo scrollbar que será o responsável por acompanhar o rolamento vertical. Note que é necessário utilizar a propriedade .set do objeto scrollbar para que ele seja definido na ListBox.
Ainda precisamos de mais uma configuração mas, desta vez, na scrollbar.
scrollClientes.configure(command=listClientes.yview)
O argumento command (command=listClientes.yview) define qual o função será chamada quando a scrollbar for ativada. Neste caso, estamos falando que a posição vertical será alterada sempre que o ScrollBar for manipulado.
Os campos agora estão inseridos na janela e posicionados, mas temos um problema….
Cadastro de Clientes sem Swag
Esta janela está absurdamente feia. Precisamos acrescentar um pouco de swag nela…
A primeira coisa que precisamos fazer é criar variáveis para definir o padding dos elementos, a largura dos elementos de input (entry).
x_pad = 5
y_pad = 3
width_entry = 30
Precisamos aplicar o padding para quase todos os elementos, definir a largura dos botões e mais alguns pontos estéticos. Isso poderia ser feito manualmente, repetindo o código para todos os elementos. Todavia, não vamos fazer assim.
Faremos a mudança de estilo da seguinte forma: Uma iteração por todos os elementos da janela, realizando as alterações conforme passamos pelos elementos.
#Adicionando um pouco de SWAG a interface...
for child in window.winfo_children():
widget_class = child.__class__.__name__
if widget_class == "Button":
child.grid_configure(sticky='WE', padx=x_pad, pady=y_pad)
elif widget_class == "Listbox":
child.grid_configure(padx=0, pady=0, sticky='NS')
elif widget_class == "Scrollbar":
child.grid_configure(padx=0, pady=0, sticky='NS')
else:
child.grid_configure(padx=x_pad, pady=y_pad, sticky='N')
No código acima, fazemos as seguintes ações:
- Recuperamos todos os elementos contidos na janela, através do método .winfo_children();
- Depois fazemos uma iteração (for) pelos elementos;
- Na variável widget_class é armazenado o nome da classe (child.__class__.__name__) do elemento da iteração;
- Dependendo do nome da classe (armazenado na variável widget_class) serão realizadas alterações em algumas propriedades:
- padx e pady: Padding para o eixo X e Y do elemento. É referente ao espaço entre a borda deste elemento e a borda dos outros elementos da janela;
- sticky: Indica em qual ponto da janela (norte – N, sul – S, leste – E ou oeste W) o objeto estará ancorado. Se você combinar o ponto leste e oeste (EW), o elemento ocupará todo o espaço horizontal da coluna em que está localizado. O mesmo ocorre se colocarmos NS (norte-sul), o elemento ocupará todo o espaço vertical.
Para o ListBox e o ScrollBar, vamos definir padding zero para que eles fiquem colados, parecendo que são apenas um elemento.
Para finalizar esta seção, vamos criar uma função para ativar a janela. No tkinter, devemos chamar a função para que a janela comece a funcionar. Ele irá receber os eventos da janela e redirecioná-los para os widgets (elementos/objetos) dela.
Uma vez que a função mainloop() é chamada, não é mais possível realizar alterações visuais (incluindo criar/destruir objetos).
Pensando no futuro, caso “amanhã” seja necessário realizar mais alguma alteração antes de chamar a mainloop(), vamos encapsular esta chamada em uma função…
def run(self):
Gui.window.mainloop()
Esta função, quando chamada, não requer argumentos e ela faz referencia para a janela que estamos utilizando. O Python não usa a keyword this, ou seja, para referenciarmos um elemento contido na classe, utilizamos o proprio nome da classe. Por isso a referência foi feita desta forma: Gui.window.mainloop().
Que tal testar o que acabamos de fazer?
Crie outro arquivo, chamado application.py, nele vamos importar o arquivo Gui.py… A partir de agora, o código exemplificado faz parte de outro arquivo (application.py).
from GUI import *
No comando acima, estamos importando tudo do arquivo GUI.py, mas este processo é case sensitive, ou seja, se você salvou seu arquivo como gui.py ou Gui.py, deve adaptar o comando acima. (exemplo: Gui.py -> from Gui import * // gui.py -> from gui import *)
Agora vamos declarar e instanciar uma variável da classe Gui…
app = None
app = Gui()
Para finalizar, vamos “rodar” a janela…
app.run()
Ao executar este script, você conseguirá visualizar a janela, com todas as configurações que fizemos. O próximo passo é codificar os comportamentos dela, mas isso é para a próxima seção.
*Update 21/11/2018: Criei um repositório no Github com este fonte completo. *