Manual

do

Maker

.

com

Tutorial QML, QT e Quick - parte 2

Tutorial QML, QT e Quick - parte 2

No artigo anterior fiz uma introdução baseado em estudos de um dos exemplos. Esse artigo é uma continuidade do tutorial de QML, QT e Quick, baseando-se no mesmo exemplo.

A estrutura do QML

No arquivo main.qml temos o objeto raiz, que é a ApplicationWindow. A partir dela temos a possibilidade de controlar o comportamento de outros arquivos QML. Isso é fundamental, principalmente se quisermos utilizar funções, uma vez que os arquivos ui.qml não suportam funções diretamente.

No artigo anterior mostrei uma estrutura básica, que é a do próprio exemplo, acrescida de alguns recursos extras. Mas vou tentar simplificar mais ainda antes de começar a mostrar os recursos que compuseram a janela da imagem de destaque. Vejamos:

Page {
    width: 240
    height: 240
}

Aqui temos a definição de uma página. Não tem nada nela, apenas uma página sem conteúdo algum, com dimensões definidas. Se quisermos um preenchimento, precisamos de um dos elementos disponíveis na documentação, também citada no artigo anterior. Por exemplo, um retângulo:

Page {
    width: 240
    height: 240

    Rectangle {
        anchor.fill: parent
        color: "#012345"
    }
}

Agora temos uma página com um retângulo de cor 012345.

Esse exemplo é para demonstrar que os elementos podem ser aninhados (depende puramente do contexto), sempre tem um objeto raiz e o restante é declarado dentro dele. É importante ter isso em mente porque mais adiante vou demonstrar a composição dos elementos da janela.

Outra coisa a considerar é que estou fazendo uma janela imutável, o que garante a distribuição visual sem problemas, uma vez que rodará sempre no mesmo tipo de dispositivo. Mas se fosse uma aplicação para mobile, seria fundamental trabalhar a diagramação também. Veremos isso em outro artigo.

Timer com QML

É bem simples fazer um timer com QML, mas aqui eu tinha um objetivo específico; salvar um screenshot para repassar ao programa que exibe a imagem no display OLED 240x240.

No código mostrado no artigo anterior tem um timer com as seguintes características:

Timer {
                interval: 2000
                running: true
                repeat:  false
                onTriggered: external.grabToImage(function (result){
                    if (!result.saveToFile("teste.png")){
                        console.error("Unknown error saving to");
                    }
                    result.close();
                });
                //onTriggered: console.error('timeout ok');
            }
        }

Funciona, mas só serve para prova de conceito por algumas razões específicas; gravar o tempo inteiro uma imagem no SD gera gargalo e desgaste da mídia. Além disso, o timer inicia uma nova função a cada timeout, o que significa leak de memória. Esse timer foi criado com 2 segundos de intervalo, sem repetição e é executado ao iniciar o programa. No gatilho, um screenshot é tirado da página 1.

Primeiro, vamos corrigir a parte do timer:

Timer {
                interval: 1000
                running: true
                repeat:  true

                //parece q cria uma funcao a cada chamada. ver como cria a funcao fora do loop e chamar aqui
                onTriggered: external.grabToImage(function (result){
                    if (!result.saveToFile("/dev/shm/pagina1.png")){
                        console.error("Unknown error saving to");
                    }
                    result.destroy();
                });
                //onTriggered: console.error('timeout ok');
            }

Veja o comentário antes de onTriggered. Fiz por experimentação, conforme experimentei as anomalias. Isso é muito importante também; escrever código é relativamente simples, usar o famoso "Ctrl+Chups" é mais fácil ainda, mas na hora de colocar a aplicação pra rodar, qual será sua atitude? Vai tentar resolver compreendendo o que está fazendo ou vai colar o código em um grupo de ajuda?

Nada de errado em pedir ajudar em grupos, mas quando vejo que o esforço aplicado tende a zero, eu não ajudo. E não quer dizer que por ter resolvido a questão de memory leak (que eu mesmo criei) tenha escrito um código excelente (ainda pode não ser a maneira correta). Como disse, existe uma montanha de conceitos a ser absorvida, mas com os primeiros passos vem os primeiros tombos.

Tem vergonha de escrever seu próprio código e pedir ajuda? Não tenha. Já escrevi aberrações em C, mas se não as tivesse feito, não teria melhorado em C ("melhorar" e "ser bom" são coisas distintas). Se alguém for imbecil o suficiente para tentar menosprezá-lo ao pedir ajuda ou tentar mostrar orgulhosamente um resultado, ignore; trata-se de alguém carente precisando alimentar o próprio ego.

Esse código para o timer está bom o suficiente para não gerar os danos supracitados. Agora a cada 1 segundo o timer se repete. A imagem é salva em /dev/shm, que é um sistema de arquivos na memória. Assim, estamos fazendo (de maneira grotesca) um IPC (intercomunicação de processos) para repassar a imagem do screenshot ao programa que exibe a imagem no display. Por fim, result.close() não dá erro mas também não tem efeito, por isso foi substituído pela função result.destroy(). Assim, ao concluir sua tarefa, ele é eliminado da memória e tudo volta ao estado inicial.

Acessar propriedades de um arquivo QML a partir de outro arquivo QML

E afinal, de onde vem esse tal de "result"?

Primeiro, precisamos entender um conceito.

Os arquivos de ui do próprio exemplo do QtCreator se chamam Page1Form.ui.qmlPage2Form.ui.qml. Além desses dois arquivos, temos o nó raiz que e o main.qml. Para acessarmos os recursos dos arquivos ui precisamos "importá-los" para o nosso nó raiz. E para fazer isso, simplesmente declaramos o nome do arquivo (sem as extensões) no corpo da ApplicationWindow. Repare no exemplo original que eles estão lá, mas sem funcionalidades. Daí, em minha opinião pessoal, acho que o exemplo deveria ter dado só um passo a mais para ficar claro o conceito, que só me foi possível compreender em outro momento. Enfim, o que era:

Page1Form {
}

Ficou:

Page1Form {
            id: external

            Timer {
                interval: 1000
                running: true
                repeat:  true

                
                onTriggered: external.grabToImage(function (result){
                    if (!result.saveToFile("/dev/shm/merda.png")){
                        console.error("Unknown error saving to");
                    }
                    result.destroy();
                });
                //onTriggered: console.error('timeout ok');
            }
        }

Mas os conceitos não param por aí. Perceba que cada componente pode ter um identificador. No caso, o id: external foi utilizado para identificar a Page1Form. E como eu consigo acessar os componentes do form em questão?

Bem, no arquivo Page1Form temos o elemento Page. Seu identificador é id: zxc (esses nomes ficam a seu critério, eu os escolhi e concordo plenamente que não são os melhores, são apenas para estudo).  Eu quero acessar todos os elementos aninhados nessa Page, que possui um id específico. Para isso, cria-se um recurso que torna o acesso à página global. No arquivo Page1Form.ui.qml:

Page {
    id: zxc
    //property QtObject model
    property alias external: zxc
...

Utilizando property alias external: zxc criamos um alias com nome external que dá acesso às propriedades do elemento cujo id é zxc.

Desenhar a interface no QtDesign

No artigo anterior mostrei também o Qt Design para QML. A IDE fica completamente diferente da interface para programação com QWidgets, e isso é realmente incrível!

qtdesign-qml.webp

Ao clicar em Design no menu vertical à esquerda, esse será o formato aproximado da interface de desenvolvimento. Os componentes da segunda coluna à esquerda dependem do que você incluir no projeto, incluindo a versão. Por exemplo, no Raspberry Pi estou utilizando a versão nativa do Qt, baixada através dos repositórios. Nessa versão do Qt a limitação de versão do QtQuick e do QtQuick.Controls é a 2.0, por isso, mesmo tendo a versão 2.122.5 (respectivamente) no computador de desenvolvimento, eu troquei a versão para torná-lo 100% compatível com o Raspberry, não tendo surpresas na hora de enviar o código para compilação ao RPi. As consequências vão de herança de bugs à ausência de novos recursos, mas é uma fatalidade com a qual se deve conviver, se estiver com preguiça de compilar seu próprio Qt (e como acabei de formatar meu notebook, sim, estou com preguiça e para esse tutorial QML é bastante válido utilizar assim).

Quando estiver no modo Design, será bastante simples compor os itens da interface, utilizando os componentes visuais da segunda coluna à esquerda (Library). Eu confesso que sequer tentei fazer reposicionamento dos componentes pela janela Navigator. Quando necessário, entrei no modo Text Editor, então fui editando manualmente. Perceba na imagem acima que tem um quadrado azul. Ali tem um preview em tempo real das modificações que forem feitas no Text Editor, o que realmente auxilia a evitar erros na hora de escrever código nos arquivos QML.

Colocar uma imagem de background no QML

Pelo que vi, essa não é a maneira ideal. Mas como eu achei mais simples, preferi adicionar um componente Image dentro do componente Page do form. Como o fundo está branco por padrão, coloquei transparência (que vai de 0.0 à 1.0), assim não precisei fazer ajustes na imagem. O código para a imagem ficou assim:

Image {
        anchors.fill: parent
        x: 0
        y: 0
        id: rpi
        source: "raspberry.jpeg"
        opacity: 0.2
    }

E de onde vem a imagem?

Em Edit, clicando com o botão direito sobre o menu QRC, pode-se adicionar arquivos existentes ao projeto. O arquivo ficará no sistema de arquivos, mas a referência está no QRC. Depois disso, basta indicar o nome do arquivo a ser utilizado.

Como o exemplo para o display tira um shot apenas da página 1, os demais componentes (como o swipe) não aparecem. Tentei fazer o shot do nó raiz, mas não dá certo, talvez haja outra maneira de pegar todos os elementos da ApplicationWindow, mas nesse momento eu queria apenas chegar à prova de conceito disposta aqui.

Mudar propriedades do Dial

Eu queria mudar o formato e a cor do componente Dial. Pesquisando, encontrei na documentação que dois recursos podem ser modificados, dando um visual totalmente novo ao componente; são o background e o handle.

Dial {
        id: dialTemp
        x: 19
        y: 48
        width: 53
        height: 53
        value: 0.374

        background: Rectangle {
            x: dialTemp.width / 2 - width / 2
            y: dialTemp.height / 2 - height / 2
            width: Math.max(64, Math.min(dialTemp.width, dialTemp.height))
            height: width
            color: "transparent"
            radius: width / 2
            border.color: dialTemp.pressed ? "#17a81a" : "#21be2b"
            opacity: dialTemp.enabled ? 1 : 0.3
        }

        handle: Rectangle {
            id: handleItemTemp
            x: dialTemp.background.x + dialTemp.background.width / 2 - width / 2
            y: dialTemp.background.y + dialTemp.background.height / 2 - height / 2
            width: 6
            height: 3
            color: dialTemp.pressed ? "#17a81a" : "#21be2b"
            radius: 8
            antialiasing: true
            //opacity: control.enabled ? 1 : 0.3
            transform: [
                Translate {
                    y: -Math.min(
                           dialTemp.background.width,
                           dialTemp.background.height) * 0.4 + handleItemTemp.height / 2
                },
                Rotation {
                    angle: dialTemp.angle
                    origin.x: handleItemTemp.width / 2
                    origin.y: handleItemTemp.height / 2
                }
            ]
        }

Mudar propriedades do ProgressBar

Do mesmo modo, podemos dar "um tapa" no visual do progressbar. Para isso, podemos modificar apenas o elemento background.

ProgressBar {
        id: progressBar
        x: 14
        y: 29
        value: 0.6

        background: Rectangle {
            radius: 2
            color: "lightgray"
            border.color: "gray"
            border.width: 1
            //implicitWidth: 200
            //implicitHeight: 24
        }
    }

Não queria nada especial, precisava apenas dar um destaque ao elemento por causa da imagem utilizada como background para esse programa do tutorial QML.

Utilizar MQTT com Qt

Eu enxergo duas maneiras de desenvolver um projeto; definindo a experiência do usuário e então implementando os recursos necessários ou então pensando na maneira mais ágil, de menor custo de desenvolvimento e menor impacto.

Normalmente opto por pensar nos requisitos funcionais do sistema, então estudo uma maneira de implementar esses requisitos de forma descomplicada. A interface da imagem de destaque é um boneco desenvolvido para esse tutorial QML, mas todos os recursos necessários para fazê-los funcionar foram estudados previamente; o MQTT com QT, o acesso ao hardware periférico (o sensor) e a manipulação dos valores nos componentes.

No próximo artigo relacionado veremos como por pra funcionar, aí faço um vídeo e disponibilizo o código.

Onde comprar o display OLED 240x240 ST7789?

Esse display (com um ótimo preço, por sinal) pode ser adquirido no Baú da Eletrônica através desse link.

Onde comprar Raspberry Pi 4?

A MASUGUX já está com mais RPi 4, aproveite que o preço baixou um bocado!

Onde comprar o Raspberry Pi 3 B+?

Ainda dando um bom caldo, o RPi 3 B+ está disponível na Saravati, como você pode conferir nesse link.

Onde comprar Raspberry Pi 3 B?

A 3B tem pouca diferença da 3B+ e seu preço também é diferente. Dependendo do quanto pretende (ou pode) investir, ela não deixa de ser uma opção completamente viável.  

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.