Flet: Controles personalizados

Personalizando Controles

O Flet inclui muitos controles prontos para uso, como já listamos nessas páginas. Mesmo assim é sempre possível que o desenvolvedor pense em um controle diferente, personalizado para fim específico. No Flet podemos partir dos controles embutidos e modificá-los, usando os conceitos de programação orientada a objetos do Python. Os controles definidos dessa forma podem ser armazenados e reutilizados posteriormente em outros projetos.

Controles estilizados

A personalização mais simples consiste em usar um controle existente e o estilizar, gerando nova aparência e acrescentando funcionalidades (métodos). Para fazer isso você cria uma nova classe em Python que herda do controle Flet que se deseja personalizar. No exemplo seguinte modificamos um botão ElevatedButton:

class NovoBotao(ft.ElevatedButton)
    def __init__(self, texto):
        super().__init__()
        self.bgcolor = ft.colors.ORANGE_300
        self.color = ft.colors.GREEN_800
        self.text = texto

O controle assim personalizado possui um construtor que define propriedades e eventos, além de passar dados, no caso através da variável texto. A chamada a super().__init__() no construtor dá o acesso às propriedades e métodos originais do controle Flet usado na herança. Uma vez criada essa classe esse controle pode ser usado no aplicativo:

import flet as ft

def main(page: ft.Page):
    page.add(NovoBotao(text="Sim"), NovoBotao(text="Não"))

ft.app(target=main)

Um exemplo de uso pode ser visto no código abaixo:


import flet as ft

def clicou_sim(e):
    e.control.text = "Clicou sim!"
    e.control.update()

def clicou_nao(e):
    e.control.text = "Clicou não!"
    e.control.update()

class NovoBotao(ft.ElevatedButton):
    def __init__(self, clicou, texto="Não"):
        super().__init__()
        self.bgcolor = ft.colors.BLUE_800
        self.color = ft.colors.YELLOW_100
        self.icon="chair_outlined"
        self.icon_color="GREEN_100"
        self.text = texto
        self.on_click = clicou

def main(page: ft.Page):
    page.bgcolor=ft.colors.BLUE_50

    page.add(
        NovoBotao(texto="Sim", clicou = clicou_sim),
        NovoBotao(clicou = clicou_nao)
    )

ft.app(target=main)
Figura 1

No exemplo acima vemos que a classe estende um ElevatedButton e seu construtor permite a alteração do texto e do nome da função executada sob o evento on_click. Essas funções devem estar definidas antes do ponto em que serão chamadas, e antes da definição da classe. Por default o texto no botão será “Não”. A execução do código gera a janela inicial com os botões ilustrados na figura 1 (a), que se alteram para 1 (b) quando o botão “Não” é clicado.

Controles Compostos

Alguns exemplos de widgets do Flet que são também containers (podem conter outros controles internamente, são: View, Page, Container, Column, Row e Stack.

Os controles personalizados compostos herdam de controles que podem ser também containers, como Page, Row ou Column, que por sua vez abrigam outros controles filhos. O controle criado dessa forma tem acesso às propriedades e métodos do container e também dos controles filhos.

O exemplo abaixo mostra um esqueleto de um aplicativo TODO simples, que define um controle personalizado que herda de ft.Row e acrescenta outros controles.

                                                                                   
import flet as ft

class Row_Tarefa(ft.Row):
    def __init__(self, texto):
        super().__init__()
        self.ver_texto = ft.Text(texto)
        self.editar_texto = ft.TextField(texto, visible=False)
        self.editar_botao = ft.IconButton(
            icon=ft.icons.EDIT,
            on_click=self.modificar
        )
        self.gravar_botao = ft.IconButton(
            icon=ft.icons.SAVE,
            on_click=self.modificar,
            visible=False
        )
        self.controls = [
            ft.Checkbox(),
            self.ver_texto,
            self.editar_texto,
            self.editar_botao,
            self.gravar_botao,
        ]

    def modificar(self, e):
        op = e.control.icon=="save"
        self.editar_botao.visible = op
        self.gravar_botao.visible = not op
        self.ver_texto.visible = op
        self.editar_texto.visible = not op
        if op:
            self.ver_texto.value = self.editar_texto.value
        self.update()

def main(page: ft.Page):

    page.add(
        Row_Tarefa(texto="Fazer Compras"),
        Row_Tarefa(texto="Terminar Código"),
        Row_Tarefa(texto="Enviar Projeto"),
    )

ft.app(target=main)

Figura 2

A execução desse código gera a janela mostrada na figura 2: (a) é a janela inicial; (b) o primeiro botão de editar foi clicado e o texto alterado; (c) a alteração foi transferida para o texto, com um clique no botão de gravar.

Observe que o controle herda de Row e acrescenta a ele os controles ft.Text, ft.TextField e dois controles ft.IconButton, que não existem no controle pai. Esses controle são inseridos nas propriedades personalizadas ver_texto, editar_texto, editar_botao e gravar_botao. Definidas essas propriedades elas são inseridas, juntas com um Checkbox, na lista de controles (controls) (que e nativa de Row). Três linhas com o objeto Row_Tarefa são inseridos na página.

Tanto o botão gravar_botao quanto editar_botao respondem ao evento on_click ativando a função modificar que recebe o parâmetro e que contém a propriedade e.control.icon=="save"/"edit". A função decide com ela quais controles devem ser exibidos ou ocultados, e altera a propriedade ver_texto.value quando o botão gravar foi clicado.

Nenhum evento foi definido associado aos botões Checkbox que, por isso, não executam nenhuma ação.

Métodos do ciclo de vida

Quando você cria controles personalizados, alguns métodos se tornam disponíveis para o gerenciamento do ciclo de vida do controle:

Método Efeito
build() chamado quando o controle está sendo criado e atribuído à página. Ele pode ser sobrescrito (override) para implementar uma lógica que não pode ser executada no construtor do controle porque necessita do acesso à self.page. Por exemplo: esse seria o local certo para escolher um ícone que depende qual é a plataforma em self.page.platform.
did_mount() chamado após a adição do controle à página e recebe um uid transitório. Por exemplo: o widget Wheather chama uma API (API Open Weather) a cada minuto para atualizar seus dados climáticos.
will_unmount() chamado antes que o controle seja removido da página. Pode ser usado para operação de limpeza.
before_update() chamado sempre que o controle está sendo atualizado. O método update() não deve ser chamado dentro de before_update(), ou um loop infinito seria gerado.

Controles isolados

Controles personalizados recebem automaticamente a propriedade is_isolated, que recebe um booleano com False. Se for ajustado is_isolated = True esse controle fica isolado do layout geral, o que significa que quando o método update() for chamado no controle pai, o próprio pai e seus filhos serão atualizados, exceto o controle isolado. Para atualizar controles isolados, seu próprio método self.update() deve ser chamado. É boa prática que, se self.update() for chamado em um controle personalizado, que ele seja isolado.

Por exemplo, nos códigos acima, o botão NovoBotao não precisa ser isolado, mas Row_Tarefa(ft.Row) deve ser:

class Row_Tarefa(ft.Row):
    def __init__(self, texto):
        super().__init__()
        self.isolated = True
        self.ver_texto = ft.Text(texto)
        self.editar_texto = ft.TextField(texto, visible=False)

Leave a Reply

Your email address will not be published. Required fields are marked *