Exemplos 2: Referências a controles
Variáveis são referências
Widgets são classes comuns do Python, com suas propriedades e métodos. Para atuar sobre os objetos criados fazemos uma referência a eles, por meio dos nomes de variáveis. Vale lembrar que no Python toda variável é uma refeerência a algum objeto. Por exemplo, no código abaixo, um objeto flet.Text foi atribuído à variável t.
1 import flet as ft 2 3 def main(page: ft.Page): 4 5 def mudar_texto(e): 6 t.value="Um texto em azul!" 7 t.color="blue" 8 page.update() 9 10 t = ft.Text(value="Um texto em vermelho!", color="red", size=20) 11 bt=ft.ElevatedButton("Mudar texto", on_click=mudar_texto) 12 page.add(t,bt) 13 14 ft.app(target=main)
A linha 10 contém o código de inicialização do objeto Text onde atribuimos valor a duas propriedades, respectivamente value (o texto que será exibido) e color (a cor da fonte usada). Com um clique no botão bt as propriedades t.value e t.color são alteradas, usando a variável como referência ao objeto. A figura 1 mostra o estado inical do aplicativo, com o texto em vermelho, e após o clique no botão Mudar texto.
Obs.: para exibir t e bt na mesma linha podemos inserir uma linha na página contendo os dois widgets: page.add(ft.Row([t,bt])). Nesse caso o controle Row (em flet.Row(<lista-de-controles>))) serve de container para os controles na lista <lista-de-controles>.
Vimos que page recebe uma lista (de controles) no parâmetro controls. Essa lista pode ser tratada como qualquer outra lista do Python. Por exemplo, ela possui o método pop(n).
1 for i in range(6): 2 page.controls.append(ft.Text(f"Exibindo a linha {i}")) 3 page.update() 4 5 page.controls.pop() 6 page.update()
Esse código insere e exibe 6 linhas de texto na página. Em seguida ela exclui a última linha com page.controls.pop(). Assim com o fazemos com outras listas, page.controls.pop(n) excluiria a n-ésima linha.
A figura 2 mostra as 5 linhas restantes após a ação de page.controls.pop()
.
Referências para controles com a Classe Ref
Outro exemplo explora a mesma possibilidade manipulação das variáveis como referências aos objetos de controle. Os objetos TextField são criados com seus textos vazios, preenchidos pelo usuário, usados para inserir a linha 9de cumprimento com o clique do botão. O comando page.update() atualiza na tela apenas as campos que foram alterados. A propriedade autofocus=True na linha 4 garante que o cursor voltará para o objeto nome após a execução da função fala_oi.
1 import flet as ft 2 3 def main(page): 4 nome = ft.TextField(label="Nome", autofocus=True) 5 sobrenome = ft.TextField(label="Sobrenome") 6 coluna_ola = ft.Column() 7 8 def fala_oi(e): 9 coluna_ola.controls.append(ft.Text(f"Olá, {nome.value} {sobrenome.value}!")) 10 nome.value = "" 11 sobrenome.value = "" 12 page.update() 13 nome.focus() 14 15 page.add( 16 nome, 17 sobrenome, 18 ft.ElevatedButton("Diga oi!", on_click=fala_oi), 19 coluna_ola, 20 ) 21 22 ft.app(target=main)
Em códigos mais longos e complexos um número maior de controles é adicionado, juntamente com suas propriedades e funções que manipulam seus eventos. Fica cada vez mais difícil, para o programador, se lembrar de todas as definições de nomes, por mais organizado que ele seja. A inserção de controles na página, efetuada nas linhas 15 a 20 não deixa muito clara a estrutura de controles inseridos.
Para isso o Flet incluiu uma classe utilitária chamada Ref, usada para fazer uma referência a um controle que pode ser usada em gerenciadores de eventos (event handlers) e, posteriormente, ser conectada a um controle real quando se constroi a árvore do aplicativo.
Para demontrar o uso dessa classe vamos alterar o código do exemplo acima usando Refs. Listamos abaixo como definir uma nova referência, como usar essa referência para inserir valores e, finalmente, como atribuir um controle real a essa referência, aproveitando todas as suas propriedades já definidas.
1 # criar uma referência a um TextField (por exemplo) 2 nome = ft.Ref[ft.TextField]() 3 4 # acessar o controle referenciado 5 nome.current.value = "José Ninguém" 6 7 # atribuindo a referência um controle real, que pode ser incluído na página 8 page.add( 9 ft.TextField(ref=nome, label="Nome", autofocus=True) 10 )
Vemos que objetos da classe Ref possuem a propriedade current que, por sua vez, contém as mesmas propriedades e métodos do pbjeto referenciado. Para ligar essa referência a um controle utilizamos a propriedade Control.ref do controle. Com essas definições a aplicativo pode ser escrito como:
1 import flet as ft 2 3 def main(page): 4 nome = ft.Ref[ft.TextField]() 5 sobrenome = ft.Ref[ft.TextField]() 6 coluna_ola = ft.Ref[ft.Column]() 7 8 def fala_oi(e): 9 coluna_ola.current.controls.append( 10 ft.Text(f"Olá, {nome.current.value} {sobrenome.current.value}!") 11 ) 12 nome.current.value = "" 13 sobrenome.current.value = "" 14 page.update() 15 nome.current.focus() 16 17 page.add( 18 ft.TextField(ref=nome, label="Nome", autofocus=True), 19 ft.TextField(ref=sobrenome, label="Sobrenome"), 20 ft.ElevatedButton("Diga oi!", on_click=fala_oi), 21 ft.Column(ref=coluna_ola), 22 ) 23 24 ft.app(target=main)
Observe que, fazendo isso, a estrutura da árvore de controles se torna mais claramente visível quando os controles são adicionados à página, na linha 17 e seguintes.
A figura 3 mostra os campos preenchidos e o resultado do clique no botão Diga oi.
Interação com o usuário
Claro que a maioria dos aplicativos demandam algum tipo de interação com o usuário, seja para escolher um ítem de busca, inserir novos dados ou escolher entre opções. Os exemplos acima usam de flet.TextField para a inserção de textos e ft.ElevatedButton para receber cliques e acionar uma função. Claro que existem muitas outras formas de interação que podem ser simples como um clique em botão ou sofisticadas quanto capturar a posição na tela de um clique, dentro de algum objeto desenhado na página.
Caixas de checagens e “dropdowns”
Vimos vários exemplos de uso das caixas de texto puro flet.Text(“Texto a exibir!”), usadas para exibir texto, e caixas interativas flet.TextField onde o usuário pode digitar dados e inserí-los no aplicativo.
As caixas de checagem, flet.Checkbox são úteis quando a escolha de dados é binária, geralmente sim/não.
1 import flet as ft 2 3 def main(page): 4 def checkbox_mudou(e): 5 texto_exibido.value = f"A caixa de checagem está {'marcada' if check.value else 'desmarcada'}." 6 page.update() 7 8 texto_exibido = ft.Text() 9 check = ft.Checkbox(label="Clique na caixa de checagem...", value=False, on_change=checkbox_mudou) 10 page.add(check, texto_exibido) 11 12 ft.app(target=main)
Esse código produz o resultado mostrado na figura, dependendo da caixa estar marcada ou não. O evento capturado nesse caso foi o flet.Checkbox.on_change que envia o evento para a função selecionada. Na função é lida a propriedade booleana check.value. A figura 4 mostra os dois estados possíveis do aplicativo.
Foi usado o operador trinário valor_se_verdadeiro if condição else valor_se_falso
.
Dropdowns, flet.Dropdown são úteis quando várias escolhas de dados estão disponíveis. O conjunto permitido de escolhas pode ser inserido durante o desenvolvimento ou alterado pelo usuário.
1 import flet as ft 2 3 def main(page: ft.Page): 4 def dropdown_mudou(e): 5 escolhido.value = f"Você afirmou que prefere {dpd_linguagem.value}" 6 page.update() 7 8 escolhido = ft.Text() 9 itens_escolher = [ 10 ft.dropdown.Option("Python"), 11 ft.dropdown.Option("Java"), 12 ft.dropdown.Option("Javascript"), 13 ft.dropdown.Option("C, C+"), 14 ft.dropdown.Option("Rust"), 15 ft.dropdown.Option("Ruby"), 16 ] 17 dpd_linguagem = ft.Dropdown( 18 label="Linguagem de programação", 19 width=100, 20 options=itens_escolher, 21 on_change=dropdown_mudou, 22 ) 23 page.add(dpd_linguagem, escolhido) 24 25 ft.app(target=main)
Observe que flet.Dropdown.options é uma lista de objetos dropdown.Option(“nome”). O resultado está mostrado na figura 5.
Além do evento change, as caixas dropdown também podem responder a eventos on_blur, disparado quando o controle perde o foco, e on_focus, quando o controle recebe o foco.
Os ítens na caixa de seleção podem ser inseridos ou apagados dinamicamente, em tempo de execução, como mostramos no código seguinte.
1 import flet as ft 2 3 def main(page: ft.Page): 4 def inserir(e): 5 if txt_insere.value: 6 dp_box.options.append(ft.dropdown.Option(txt_insere.value)) 7 dp_box.value, txt_insere.value = txt_insere.value, "" 8 page.update() 9 10 def apagar(e): 11 for item in dp_box.options: 12 if item.key == dp_box.value: 13 dp_box.options.remove(item) 14 page.update() 15 16 dp_box = ft.Dropdown(width=600) 17 txt_insere = ft.TextField(hint_text="Nome a inserir") 18 bt_insere = ft.ElevatedButton("Inserir", on_click=inserir) 19 bt_apaga = ft.OutlinedButton("Apagar item exibido", on_click=apagar) 20 page.add(dp_box, ft.Row(controls=[txt_insere, bt_insere, bt_apaga])) 21 22 ft.app(target=main)
Na linha 6 um item é inserido na caixa dropdown. dp_box.options
é uma lista que pode ser acrescida de elementos com append. Essa lista é composta de objetos flet.dropdown.Option(texto)
. A operação de apagar procura por um item nessa lista e o remove, caso encontrado. A propriedade item.key extrai o texto de cada componente da lista. O texte feito na linha 12 é necessário porque tentar remover um item inexistente de uma lista resulta em erro. O aplicativo executado exibe algo como o representado na figura 6.
Capturando cliques e “hovers”
Um exemplo de captura da posição do cursor do mouse no momento do clique pode ser visto na página Layout do Flet: View e Container na seção Container: Eventos onde um clique dentro de um container dispara um evento para exibir as posições do ponto clicado, relativo ao container e à página.
No exemplo abaixo as ações são iniciadas pelo evento flet.Container.on_hover, disparadas quando o cursor do mouse entra ou sai da área delimitada pelo controle. O evento e passado para a função limpar carrega a propriedade e.data == "true"
se o cursor entrou na área, e e.data == "false"
se saiu dela. Na saída uma cor é escolhida eleatoriamente entre as cores do dicionário cores.
1 import flet as ft 2 import random 3 4 def main(page: ft.Page): 5 cores = {0:"#000000", 1:"#1D74FF", 2:"#C51709", 3:"#FFE000", 4:"#00CB4F", 6 5:"#A866DC", 6:"#FF6600", 7:"#aaffff", 8:"#145375", 9:"#a4b3c5"} 7 8 def limpar(e): 9 page.controls.pop() 10 page.add(matriz()) 11 page.update() 12 13 def on_hover(e): 14 if e.data == "true": 15 e.control.bgcolor = "#efefef" 16 else: 17 e.control.bgcolor = cores[random.randrange(10)] 18 e.control.update() 19 20 def matriz(): 21 matr = ft.Column() 22 for i in range(10): 23 lin = [] 24 for j in range(10): 25 lin.append(ft.Container(width=50, height=50, 25 border=ft.border.all(1, "#293B4A"), 27 border_radius=ft.border_radius.all(10), 28 bgcolor="#F1EBC5", on_hover=on_hover) 29 ) 30 linha = ft.Row(lin) 31 matr.controls.append(linha) 32 return matr 33 34 tit=ft.Text("Passe o cursor sobre os quadrados", size=18, weight=ft.FontWeight.BOLD) 35 bt_limpar=ft.ElevatedButton("Limpar tudo", icon="arrow_circle_left", 36 bgcolor="white", width=150, height=30, on_click=limpar) 37 page.add(ft.Row([tit,bt_limpar]), matriz()) 38 39 ft.app(target=main)
O resultado do código está representado na figura 7, após o cursor ter sido passado sobre o objeto matriz(), que é uma coluna.
A construção da matriz de controles container é feita através da função matriz()
que retorna 10 linhas, cada uma com 10 containeres, todos respondendo à container.on_hover
com a mesma função, que altera a cor de fundo desse objeto.
O laço iniciado na linha 24 constroi um array com 10 objetos Containers (os quadrados). Esse array é inserido em uma linha flet.Row na linha 30. Essas 10 linhas são inseridas na coluna matr na linha 31.
O botão Limpar tudo remove o último controle inserido na página com page.controls.pop()
, sendo que esse controle é a matriz de quadrados coloridos. Em seguida ele repõe novos quadrados com as cores originais.