Flet: Botões

Widgets, propriedades e eventos

Estritamente dizendo deveríamos iniciar nosso estudo do Flet considerando os objetos mais básicos, no sentido de serem containeres de outros objetos. No entanto já vimos vários casos de pequenos aplicativos que usam botões. É difícil sequer exibir exemplos de código de GUI sem botões. Por isso vamos descrever aqui o uso dos botões, deixando para depois uma descrição dos controles de layout.

Alguns controles tem a função principal de obter informações do usuário, como botões, menus dropdown ou caixas de texto, para inserção de dados. Outros servem para a exibição de informações calculadas pelo código, mostrando gráficos, figuras ou textos. As caixas de textos podem ser usadas em ambos os casos.

Buttons (botões)

Os seguintes tipos de botões estão disponíveis (e ilustrados na figura 1), contendo as propriedades, métodos e respondendo a eventos:

Botões ElevatedButton FilledButton FilledTonalButton FloatingActionButton IconButton OutlinedButtonPopupMenuButton TextButton
Propriedades autofocus, bgcolor,data, color, content, elevation, icon, icon_color, style, text, tooltip, url, url_target
Eventos on_blur, on_click, on_focus, on_hover, on_long_press
Método focus()
Figura 1

Vamos usar alguns exemplos para ilustrar as propriedades e métodos desses objetos.

import flet as ft

def main(page: ft.Page):
    def mudar(e):
        bt2.disabled = not bt2.disabled
        bt2.text = "Desabilitado" if bt2.disabled else "Habilitado" 
        page.update()

    bt1 = ft.FilledButton(text="FilledButton", on_click=mudar, width=200)
    bt2 = ft.ElevatedButton("Habilitado", disabled=False, width=200)
    page.add(bt1, bt2)

ft.app(target=main)

Nesse caso um FilledButton aciona a função mudar que alterna a propriedade disabled do botão elevado. Observe que um botão com disabled=True não reage ao clique, nem à nenhum outro evento. O operador ternário foi usado: valor_se_true if condicao else valor_se_false.

Dois novos eventos são mostrados a seguir: on_hover, que é disparado quando o cursor se move sobre o botão (sem necessidade de clicar) e on_long_press, disparado com um clique longo. Um ícone é inserido nos botões ElevatedButton, cujas cores são alteradas pelos eventos descritos.

import flet as ft

def main(page: ft.Page):

    def azular(e):
        bt2.icon_color="blue"
        page.update()

    def vermelho(e):
        bt2.icon_color="red"
        page.update()

    bt1 = ft.ElevatedButton("Tornar azul", icon="chair_outlined", on_hover= azular, width=250)
    bt2 = ft.ElevatedButton("Tornar Vermelho", icon="park_rounded", on_long_press=vermelho, icon_color= "green", width=250)
    page.add(bt1, bt2)

ft.app(target=main)

Os botões assumem os estados mostrados na figura 2.

Figura 2: Execução do código acima.

 

Alguns Ícones pré-definidos do Flet

Uma grande quantidade de ícones está disponível e pode ser pesquisada em Flet.dev: Icons Browser.

Botões possuem a propriedade data que pode armazenar um objeto a ser passado para as funções acionadas por eventos. As propriedades dos widgets funcionam como variáveis globais. No exemplo abaixo temos uma caixa de texto, que exibe a propriedade data. O botão elevado também tem uma propridade data que não é exibida mas serve para armazenar quantas vezes o botão foi clicado. Ele serve de container para um Row contendo 3 ícones (ft.Icon(ft.icons.NOME_DO_ICONE)).

import flet as ft

def main(page: ft.Page):
    def bt_clicou(e):
        bt.data += 1
        txt.value = f"O botão foi clicado {bt.data} {'vezes' if bt.data >1 else 'vêz'}"
        page.update()

    txt = ft.Text("O botão não foi clicado", size=25, color="navyblue", italic=True)
    bt = ft.ElevatedButton("Clica!",
            content=ft.Row(
                [
                    ft.Icon(ft.icons.CHAIR, color="red"),
                    ft.Icon(ft.icons.COTTAGE, color="green"),
                    ft.Icon(ft.icons.AGRICULTURE, color="blue"),
                ],  alignment=ft.MainAxisAlignment.SPACE_AROUND,),
                    on_click=bt_clicou, data=0, width=150,
         )
    page.add(txt, bt)

ft.app(target=main)

Se o nome do ícone não for fornecido como primeiro parâmetro o nome do parâmetro deve ser nomeado: ft.Icon(color="red", name=ft.icons.CHAIR). E execução do código resulta nas janelas exibidas na figura 3.

Figura 3

As caixas de texto podem receber formatações diversas como size (tamanho da fonte), color (cor da fonte) bgcolor (cor do fundo), italic (itálico), weight (peso da fonte). A propriedade selectable informa se o texto exibido pode ser selecionado, e estilos diversos podem ser atribuídos em style. A página recebe a propriedade page.scroll = "adaptive" para que possa apresentar uma barra de scroll.

import flet as ft

def main(page: ft.Page):
    page.scroll = "adaptive"

    t1 = ft.Text("Tamanho 12 (Size 12)", size=12)
    t2 = ft.Text("Tamanho 20, Italic", size=32, color="red", italic=True)
    t3 = ft.Text("Tamanho 30, peso 100", size=30, color=ft.colors.WHITE,
                  bgcolor=ft.colors.BLUE_600, weight=ft.FontWeight.W_100)
    t4 = ft.Text(
            "Size 40, Normal",
            size=40,
            color=ft.colors.WHITE,
            bgcolor=ft.colors.ORANGE_800,
            weight=ft.FontWeight.NORMAL
        )
    t5 = ft.Text(
            "Size 30, Bold, Italic",
            size=30,
            color=ft.colors.WHITE,
            bgcolor=ft.colors.GREEN_700,
            weight=ft.FontWeight.BOLD,
            italic=True
        )
    t6 = ft.Text("Size 20, w900, selecionável", size=20, weight=ft.FontWeight.W_900, selectable=True)

    page.add(t1, t2, t3, t4, t5, t6) 

ft.app(target=main)

O resultado é mostrado na figura 4. A janela foi dimensionada para ser menor que o texto existente, mostrando a barra de scroll.

Figura 4

O número máximo de linhas exibidas, max_lines, ou largura e altura do texto, width e height são úteis quando se apresenta texto logo em uma janela.

import flet as ft

def main(page: ft.Page):
    page.scroll = "adaptive"
    
    texto1 = (
        "René Descartes (La Haye en Touraine, 31 de março de 1596, Estocolmo, 11 de"
        "fevereiro de 1650) foi um filósofo, físico e matemático francês. Durante a"
        "Idade Moderna, também era conhecido por seu nome latino Renatus Cartesius."
    )
    texto2 = (
        "Descartes, por vezes chamado de o fundador da filosofia moderna e o pai da"
        "matemática moderna, é considerado um dos pensadores mais importantes e"
        "influentes da História do Pensamento Ocidental. Inspirou contemporâneos e"
        "várias gerações de filósofos posteriores; boa parte da filosofia escrita "
        "a partir de então foi uma reação às suas obras ou a autores supostamente"
        "influenciados por ele. Muitos especialistas afirmam que, a partir de"
        "Descartes, inaugurou-se o racionalismo da Idade Moderna."
    )

    t7 = ft.Text("Limita texto longo a 1 linha, com elipses", style=ft.TextThemeStyle.HEADLINE_SMALL)
    t8 = ft.Text(texto1, max_lines=1, overflow="ellipsis")
    t9 = ft.Text("Limita texto longo a 2 linhas", style=ft.TextThemeStyle.HEADLINE_SMALL)
    t10 = ft.Text(texto2, max_lines=2)
    t11 = ft.Text("Limita largura e altura do texto longo", style=ft.TextThemeStyle.HEADLINE_SMALL)
    t12 = ft.Text(texto2, width=700, height=100)

    page.add(t7, t8, t9, t10, t11, t12)

ft.app(target=main)
Figura 5

Resultando na janela mostrada na figura 5. O parâmetro overflow="ellipsis" mostra uma elipses onde on texto foi quebrado. As definições de texto1 e texto2 correspondem a uma das formas de declarar strings longas no python.

Existem estilos pré-definidos. código abaixo usamos o texto do widget igual ao nome do estilo, para facilitar a visualização: style=ft.TextThemeStyle.NOME_DO_ESTILO. Apenas as definições estão mostradas e o resultado está na figura 6.

    page.add(
        ft.Text("DISPLAY_LARGE",   style=ft.TextThemeStyle.DISPLAY_LARGE),
        ft.Text("DISPLAY_MEDIUM",  style=ft.TextThemeStyle.DISPLAY_MEDIUM),
        ft.Text("DISPLAY_SMALL",   style=ft.TextThemeStyle.DISPLAY_SMALL),
        ft.Text("HEADLINE_LARGE",  style=ft.TextThemeStyle.HEADLINE_LARGE),
        ft.Text("HEADLINE_MEDIUM", style=ft.TextThemeStyle.HEADLINE_MEDIUM),
        ft.Text("HEADLINE_MEDIUM", style=ft.TextThemeStyle.HEADLINE_MEDIUM),
        ft.Text("TITLE_LARGE",     style=ft.TextThemeStyle.TITLE_LARGE),
        ft.Text("TITLE_MEDIUM",    style=ft.TextThemeStyle.TITLE_MEDIUM),
        ft.Text("TITLE_SMALL",     style=ft.TextThemeStyle.TITLE_SMALL),
        ft.Text("LABEL_LARGE",     style=ft.TextThemeStyle.LABEL_LARGE),
        ft.Text("LABEL_MEDIUM",    style=ft.TextThemeStyle.LABEL_MEDIUM),
        ft.Text("LABEL_SMALL",     style=ft.TextThemeStyle.LABEL_SMALL),
        ft.Text("BODY_LARGE",      style=ft.TextThemeStyle.BODY_LARGE),
        ft.Text("BODY_MEDIUM",     style=ft.TextThemeStyle.BODY_MEDIUM),
        ft.Text("BODY_SMALL",      style=ft.TextThemeStyle.BODY_SMALL)
    )
Figura 6

Propriedades dos controles pode ser alterados dinamicamente por meio de inputs recebidos do usuário (ou outra origem qualquer). No próximo exemplo o tamanho da fonte é controlado por um flet.Slider.

import flet as ft
def main(page: ft.Page):
    def font_size(e):
        t.size = int(sl.value)
        t.value = f"Texto escrito com fonte Bookerly, size={t.size}"
        t.update()

    t = ft.Text("Texto escrito com fonte Bookerly, size=10", size=10, font_family="Bookerly")
    sl = ft.Slider(min=0, max=100, divisions=10, value=10, label="{value}", width=500, on_change=font_size)

    page.add(t, sl)

ft.app(target=main)
Figura 7

O app gerado está na figura 7. Observe que a propriedade do Slider.label="value" exibe acima do cursor (como um tooltip) o valor do controle. O tamanho da fonte é ajustado de acordo com esse valor.

Se a fonte está instalada localmente basta usar font_family="Nome_da_Fonte". Para fontes remotas podemos definir uma ou várias fontes a serem usadas. page.fonts recebe um dicionário com nomes e locais das fontes.

    page.fonts = {
        "Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
        "Open Sans": "fonts/OpenSans-Regular.ttf",
    }
    page.theme = Theme(font_family="Kanit")
    page.add(
        ft.Text("Esse texto é renderizado com fonte Kanit"),
        ft.Text("Esse texto é renderizado com fonte 'Open Sans'", font_family="Open Sans"),

Ícones

O objeto fleet.Icon pode ser inserido em vários conteineres. Ele possui as propriedades (entre outras) color, name, size e tooltip. O tamanho default é size=24 enquanto tooltip é o texto exibido quando o cursor está sobre o ícone.
O código ilustra esse uso:

import flet as ft

def main(page: ft.Page):
    ic1 = ft.Icon(name=ft.icons.ADD_HOME_ROUNDED, color=ft.colors.AMBER_900)
    ic2 = ft.Icon(name=ft.icons.ZOOM_IN, color=ft.colors.BLUE_ACCENT_700, size=30)
    ic3 = ft.Icon(name=ft.icons.AIR_SHARP, color="blue", size=50)
    ic4 = ft.Icon(name="child_care", color="#ffc1c1", size=70, tooltip="ajustes")
    page.add(ft.Row([ic1, ic2, ic3, ic4, ft.Image(src=f"img/Search.png")]))

ft.app(target=main)
Figura 8: Resultado do código de exibição de ícones.

O nome do ícone pode ser dado como name=ft.icons.ZOOM_IN ou uma string name="child_care", onde o nome pode ser pesquisado no Icons Browser. Note que ft.icons contém ENUMS predefinidos e name=ft.icons.AIR_SHARP é o mesmo que name="air_sharp".

Ícones personalizados podem ser inseridos como imagens, como em flet.Image(src=f"img/Search.png"), onde o caminho pode ser absoluto ou relativo, em referência ao diretório onde está o aplicativo. Isso significa que a imagem da lupa exibida na figura está armazenada em pasta_do_aplicativo/img/Search.png.

Bibiografia


Flet: Objeto Page

Python com Flet: Widgets

Widgets, propriedades e eventos

Vimos no primeiro artigo dessa série que um aplicativo Python com Flet consiste em código Python para a realização da lógica do aplicativo, usando o Flet como camada de exibição. Mais tarde nos preocuparemos em fazer uma separação explícita das camadas. Por enquanto basta notar que o Flet cria uma árvore de widgets cujas propriedades são controladas pelo código. Widgets também podem estar associados à ações ligadas a funções. Portanto, para construir aplicativos com Flet, precisamos conhecer esses widgets, suas propriedades e eventos que respondem.

Alguns controles tem a função principal de obter informações do usuário, como botões, menus dropdown ou caixas de texto, para inserção de dados. Outros servem para a exibição de informações calculadas pelo código, mostrando gráficos, figuras ou textos. As caixas de textos podem ser usadas em ambos os casos.

Figura 10: Estrutura de árvore de um aplicativo Flet

A interface do Flet é montada como uma composição de controles, arranjados em uma hierarquia sob forma de árvore que se inicia com uma Page. Dentro dela são dispostos os demais controles, sendo que alguns deles são também conteineres, abrigando outros controles. Todos os controles, portanto, possuem um pai, exceto a Page. Controles como Column, Row e Dropdown podem conter controles filhos, como mostrado na figura 10.

Categorias de Controles

🔘 Controles Categoria Itens
🔘 Layout diagramação 16 itens
🔘 Navigation navegação 3 itens
🔘 Information Displays exibição de informação 8 itens
🔘 Buttons botões 8 itens
🔘 Input and Selections entrada e seleções 6 itens
🔘 Dialogs, Alerts, Panels dialogo e paineis 4 itens
🔘 Charts gráficos 5 itens
🔘 Animations animações 1 item
🔘 Utility utilidades 13 itens

Propriedades comuns a vários controles

Algumas propriedades são comuns a todos (ou quase todos) os controles. Vamos primeiro listá-las e depois apresentar alguns exemplos de uso. As propriedades marcadas com o sinal ≜ só são válidas quando estão dentro de uma controle Stack, que será descrito após a tabela.

bottom Distância entre a borda inferior do filho e a borda inferior do Stack.
data Um campo auxiliar de dados arbitrários que podem ser armazenados junto a um controle.
disabled Desabilitação do controle. Por padrão disabled = False. Um controle desabilitado fica sombreado e não reage a nenhum evento. Todos os filhos de um controle desabilitado ficam desabilitados.
expand Um controle dentro de uma coluna ou linha pode ser expandido para preencher o espaço disponível. expand=valor, onde valor pode ser booleano ou inteiro, um fator de expansão, usado para dividir o espaço entre vários controles.
hight Altura do controle, em pixeis.
left Distância da borda esquerda do filho à borda esquerda do Stack.
right Distância da borda direita do filho à borda direita do Stack.
top Distância da borda superior do filho ao topo do Stack.
visible Visibilidade do controle e seus filhos. vivible = True por padrão. Controles invisíveis não recebem foco, não podem ser selecionados nem respondem a eventos.
widht Largura de controle em pixeis.

Um Stack é um controle usado para posicionar controles em cima de outros (empilhados). Veremos mais sobre ele na seção sobre layouts.

Transformações (Transformations)

Transformações são operações realizadas sobre os controles

offset É uma translação aplicada sobre um controle, antes que ele seja renderizado. A translação é dada em uma escala relativa ao tamanho do controle. Um deslocamento de 0,25 realizará uma translação horizontal de 1/4 da largura do controle. Ex,: ft.Container(..., offset=ft.transform.Offset(-1, -1).
opacity Torna o controle parcialmente transparente. O default é opacity=1.0, nenhuma transparência. Se opacity=0.0controle é 100% transparente.
rotate Aplica uma rotação no controle em torno de seu centro. O parâmetro rotate pode receber um número, que é interpretado com o ângulo, em radianos, de rotação anti-horária. A rotação também pode ser especificada por meio de transform.Rotate, que permite estabelecer ângulo, alinhamento e posição de centro de rotação. Ex,: ft.Image(..., rotate=Rotate(angle=0.25 * pi, alignment=ft.alignment.center_left) representa uma rotação de 45° (π/4).
scale Controla a escala ao longo do plano 2D. O fator de escala padrão é 1,0. Por ex.: ft.Image(..., scale=Scale(scale_x=2, scale_y=0.5) multiplica as dimensões em x por 2 e em y por .5. Alternativamente Scale(scale=3) multiplica por 3 nas duas direções.

Exemplo de uso das Propriedades e Transformações

import flet as ft

def main(page: ft.Page):
    def mover_x(e):
        ct.left += 20
        page.update()

    def mover_y(e):
        ct.top += 20
        page.update()

    def mover(e):
        bt3.value += .2
        ct.offset=ft.transform.Offset(bt3.value, bt3.value)
        page.update()

    def sumir(e):
        ct.visible = not ct.visible
        page.update()

    def rodar(e):
        bt5.value+=.5
        ct.rotate=ft.Rotate(angle=bt5.value, alignment=ft.alignment.center)
        page.update()

    def opaco(e):
        ct.opacity -= .1
        page.update()

    def zerar(e):
        bt3.value=0
        bt5.value=0
        ct.left=0
        ct.top=0
        ct.offset=ft.transform.Offset(0, 0)
        ct.opacity = 1
        ct.visible = True
        ct.rotate=ft.Rotate(angle=0, alignment=ft.alignment.center)
        page.update()

    bt1 = ft.ElevatedButton(" ", icon="arrow_circle_right", on_click= mover_x, width=50)
    bt2 = ft.ElevatedButton(" ", icon="arrow_circle_down", on_click= mover_y, width=50)
    bt3 = ft.ElevatedButton(" ", icon="SUBDIRECTORY_ARROW_RIGHT", on_click= mover, width=50)
    bt3.value=0
    bt4 = ft.ElevatedButton("on/off", on_click= sumir, width=150)
    bt5 = ft.ElevatedButton("Rodar", on_click= rodar, width=150)
    bt5.value=0
    bt6 = ft.ElevatedButton("Opaco", on_click= opaco, width=150)
    bt7 = ft.ElevatedButton("Zerar", on_click= zerar, width=150)

    ct = ft.Container(bgcolor="red", width=50, height=50, left=0, top=0,
                      offset=ft.transform.Offset(0, 0))

    page.add(ft.Row([bt1, bt2, bt3, bt4, bt5, bt6, bt7]), ft.Stack([ct], width=1000, height=1000))

ft.app(target=main)

Os botões executam as funções:

  • bt1 ⇾ move para a direita, horizontalmente (ct.left += 20),
  • bt2 ⇾ move para baixo, na vertical (ct.top += 20),
  • bt3 ⇾ aumenta offset, nas duas direções (ct.offset=ft.transform.Offset(bt3.value, bt3.value)),
  • bt4 ⇾ torna a imagem invisível/visível (ct.visible = not ct.visible),
  • bt5 ⇾ gira imagem, anti-horário:
    ct.rotate=ft.Rotate(angle=bt5.value, alignment=ft.alignment.center),
  • bt6 ⇾ torna a cor mais translúcida (ct.opacity -= .1),
  • bt7 ⇾ retorna a imagem para o estado inicial,
Figura 12: Propriedades e Transformações

onde ct = ft.Container, é o container de cor vermelha, mostrado no figura 12.

Atalhos de Teclado

Qualquer pessoa que faz uso intensivo do computador sabe da importância dos Atalhos de Teclado (Keyboard shortcuts). A possibilidade de não mover a mão do teclado para acionar o mouse pode significar melhor usabilidade e aumento de produtividade. Para isso o Flet oferece para o programador a possibilidade de inserir atalhos em seus aplicativos para que seu usuário possa dinamizar seu uso.

Para capturar eventos produzidos pelo teclado o objeto page implementa o método on_keyboard_event, que gerencia o pressionamento de teclas de caracter, em combinação com teclas de controle. Esse evento passa o parâmetro eque é uma instância da classe KeyboardEvent, e tem as seguintes propriedades:

e.key Representação textual da tecla pressionada. Veja nota.
e.shift Booleano: True se a tecla “Shift” foi pressionada.
e.ctrl Booleano: True se a tecla “Control” foi pressionada.
e.alt Booleano: True se a tecla “Alt” foi pressionada.
e.meta Booleano: True se a tecla “Meta” foi pressionada††.

Nota: Além dos caracteres A … Z (todos apresentados em maiúsculas) também são representadas as teclas Enter, Backspace, F1 … F12, Escape, Insert … Page Down, Pause, etc. Alguns teclados permitem a reconfiguração de teclas, por exemplo fazendo F1 = Help, etc.
Nota††: A tecla Meta é representada em geral no Windows como tecla Windows e no Mac como tecla Command.

O seguinte código ilustra esse uso. A linha page.on_keyboard_event = on_teclado faz com que eventos de teclado acionem a função on_teclado. O objeto e leva as propriedades booleanas e.ctrl, e.alt, e.shift, e.meta e o texto e.key.

import flet as ft                                                          
                                                                            
def main(page: ft.Page):

    class BtControle(ft.TextField):
        def __init__(self, text):
            super().__init__()
            self.value = text
            self.width=100
            self.text_size=25
            self.bgcolor="blue"
            self.color="white"
            self.visible = False
            
    def on_teclado(e: ft.KeyboardEvent):
        c_ctrl.visible = e.ctrl
        c_alt.visible = e.alt
        c_shift.visible = e.shift
        c_meta.visible = e.meta
        c_key.visible = True
        c_key.value = e.key
        page.update()

    page.on_keyboard_event = on_teclado

    t1= ft.Text("Pressione qualquer tecla, combinada com \nCTRL, ALT, SHIFT ou META", size=25)
    c_ctrl = BtControle("Ctrl")
    c_alt = BtControle("Alt")
    c_shift = BtControle("Shift")
    c_meta = BtControle("Meta")
    c_key = BtControle("")

    page.add(t1)
    page.add(ft.Row(controls=[c_ctrl, c_alt, c_shift, c_meta, c_key]))

ft.app(target=main) 
Figura 13: Uso de “keyboards shortcuts”

O resultado desse código, quando executado e após o pressionamento simultaneo das teclas CTRL-ALT-SHIFT-J, está mostrado na figura 13.

O exemplo acima ilustra ainda uma característica da POO (programação orientada a objetos). Como temos que criar 5 caixas de texto iguais, exceto pelo seu valor, criamos uma classe BtControle (herdando de ft.TextField) e criamos cada botão como instância dessa classe. No código manipulamos a visibilidade desses botões.

Bibiografia


Python com Flet: Botões