Você está na página 1de 302

Qt5

Cadaques Book » next

Um livro sobre o Qt5


Last Build: March 21, 2016 at 20:39 CET

Bem-vindo ao livro online do Qt5 Cadaques! Porquê o Qt5? Porque o Qt5 é incrível!
Porquê cadaques Porque um dos autores teve um grande feriado nesta linha de costa
rochosa no nordeste de sparentn.

A coleção inteira de capítulos cobrindo programação Qt5, escrita por Juergen Bocklage-
Ryannel e Johan Thelin, está disponível aqui. Todo o conteúdo do livro é licenciado sob a
Creative Commons Attribution Non Commercial Share Alike 4.0
[http://creativecommons.org/licenses/by-nc/4.0] licença e exemplos são licenciados sob a BSD
[http://en.wikipedia.org/wiki/BSD_licenses].

Estamos trabalhando muito neste livro e isso significa várias coisas:

1. Não está finalizado. Nós estaremos lançando novos capítulos de tempos em tempos
e atualizando capítulos existentes em qualquer lugar.
2. Nós amamos seu apoio. Se você encontrar algum erro ou tiver sugestões, por favor,
use nosso sistema de feedback.Ele criará uma nova entrada de ingresso em nosso
sistema de tickets e nos ajudará a acompanhar.
3. Seja paciente. Estamos trabalhando em nosso tempo livre no livro e dependemos do
apoio de nossas empresas e familiares.

Apreciar!

Content

1. 1. Conheça o Qt 5
1.1. Prefácio
1.2. Introdução ao Qt5
1.3. Blocos de Construção Qt
1.4. O Qt Project
2. Iniciando
2.1. Instalando o SDK do Qt 5
2.2. Hello World
2.3. Tipos de Aplicação
2.4. Resumo
3. IDE do Qt Creator
3.1. A interface do usuário
3.2. Registrando seu Qt Kit
3.3. Gerenciando Projects
3.4. Usando o Editor

1
3.5. Localizador
3.6. Depuração
3.7. Atalhos
4. Iniciando no Quick
4.1. Sintaxe QML
4.2. Elementos básicos
4.3. Componentes
4.4. Transformações Simples
4.5. Posicionamento dos Elementos
4.6. Itens de Layout
4.7. Elementos de Entrada
4.8. Técnicas Avançadas
5. Elementos Fluidos
5.1. Animations
5.2. Estados e Transições
5.3. Técnicas Avançadas
6. Delegando Model-View
6.1. Conceito
6.2. Modelos Básicos
6.3. Visualização Dinâmica
6.4. Delegate
6.5. Técnicas Avançadas
6.6. Resumo
7. Elemento Canvas
7.1. API conveniente
7.2. Gradientes
7.3. Sombras
7.4. Imagens
7.5. Transformação
7.6. Modos de Composição
7.7. Buffers de Pixel
7.8. Pintura com Canvas
7.9. Portando Canvas a partir HTML5
8. Simulação de Partículas
8.1. Conceito
8.2. Simulação Simples
8.3. Parâmetros de Partículas
8.4. Partículas Direcionadas
8.5. Pintores de Partículas
8.6. Afetando partículas
8.7. Grupos de Partículas
8.8. Resumo
9. Efeito Shader
9.1. OpenGL Shaders
9.2. Elementos Shader
9.3. Fragmento Shaders

2
9.4. Efeito Wave (onda)
9.5. Vertex Shader
9.6. Efeito cortina
9.7. Biblioteca Qt GraphicsEffect
10. Multimídia
10.1. Jogando com a Mídia
10.2. Efeitos Sonoros
10.3. Streams de Video
10.4. Capturando Imagens
10.5. Técnicas Avançadas
10.6. Resumo
11. Networking
11.1. Servindo a UI via HTTP
11.2. Templating
11.3. Solicitações HTTP
11.4. Ficheiros locais
11.5. API REST
11.6. Autenticação usando o OAuth
11.7. Engine IO
11.8. Web Sockets
11.9. Resumo
12. Armazenamento
12.1. Configurações
12.2. Armazenamento Local - SQL
12.2. Armazenamento Local - SQL
13. QML Dinâmico
13.1. Carregando componentes dinamicamente
13.2. Criando e destruindo objetos
13.3. Rastreando Objetos Dinâmicos
13.4. Resumo
14. JavaScript
14.1. Navegador/HTML vs QtQuick/QML
14.2. A Linguagem
14.3. Objetos JS
14.4. Criando um Console JS
15. Qt e C++
15.1. Um aplicativo Boilerplate
15.2. O QObject
15.3. Build Systems
15.4. Classes comuns do Qt
15.5. Modelos em C++
16. Estendendo QML com C++
16.1. Entendendo o QML Run-time
16.2. Conteúdo do Plugin
16.3. Criando o plugin
16.4. Implementação FileIO

3
16.5. Usando FileIO
16.6. Resumo
16. Assets
16.1. Offline Books
16.2. código-fonte Examples

Fila do capítulo

A fila de capítulos são os capítulos em que estamos trabalhando. Eles podem estar quase
prontos ou em um estado infante. Experimente.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

4
Qt5 Cadaques Book » previous | next

1. Conheça o Qt 5
Section author: jryannel [https://github.com/jryannel]

Note

O código fonte deste capítulo pode ser encontrado no assets folder.

Este livro fornecerá a você um passeio pelos diferentes aspectos do desenvolvimento de


aplicativos usando a versão 5.x do Qt. Ele se concentra na nova tecnologia Qt Quick, mas
também fornece informações necessárias para escrever back-ends e extensão C ++ para o
Qt Quick.

Este capítulo fornece uma visão geral de alto nível do Qt 5. Ele mostra os diferentes
modelos de aplicativos disponíveis para desenvolvedores e um aplicativo de demonstração
do Qt 5 para obter uma prévia do que está por vir. Além disso, o capítulo tem como
objetivo fornecer uma visão ampla do conteúdo do Qt 5 e como entrar em contato com os
desenvolvedores do Qt 5.

1.1. Prefácio
História

O Qt 4 evoluiu desde 2005 e forneceu um terreno sólido para milhares de aplicativos e até
mesmo sistemas desktop e móveis completos. Os padrões de uso de usuários de
computadores mudaram nos últimos anos. De PCs estacionários a notebooks portáteis e
hoje em dia computadores móveis. O ambiente de trabalho clássico é cada vez mais
substituído por telas sempre conectadas baseadas em toque.Com isso, os paradigmas de
UX para desktop também mudam. Onde, como no passado, a interface do usuário do
Windows dominou o mundo, gastamos mais tempo hoje em dia em outras telas com outra
linguagem de interface do usuário.

O Qt 4 foi projetado para satisfazer o mundo da área de trabalho para ter um conjunto
coerente de widgets de interface do usuário disponível em todas as principais plataformas.
O desafio para os usuários do Qt mudou hoje e é mais para fornecer uma interface de
usuário baseada em toque para uma interface de usuário orientada ao cliente e para
permitir a interface de usuário moderna em todos os principais sistemas móveis e de
desktop. O Qt 4.7 começou a introduzir a tecnologia Qt Quick, que permite aos usuários
criar um conjunto de componentes de interface de usuário a partir de elementos simples
para obter uma nova UI completa, orientada pelas demandas do cliente.

5
1.1.1. O foco no Qt 5

O Qt 5 é uma atualização completa do muito bem sucedido lançamento do Qt 4. Com o Qt


4.8, o lançamento do Qt 4 tem quase 7 anos de idade. É hora de tornar um incrível kit de
ferramentas ainda mais incrível. Qt 5 é focado no seguinte:

Gráficos marcantes: A tecnologia Qt Quick 2 é baseada no OpenGL (ES) a partir do


uso de uma implementação de um grafo de cena. A pilha gráfica refeita permite um
novo nível de efeitos gráficos combinados com uma facilidade de uso jamais vista.
Produtividade: QML e JavaScript são o foco para a criação de interfaces. A
retaguarda ou back-end será escrita em C++. A divisão entre JavaScript e C++
permite uma interação mais fácil entre desenvolvedores front-end que se concentram
em criar lindas interfaces de usuário e desenvolvedores back-end que utilizam C++
com foco em estabilidade, desempenho e extensão de funcionalidades.
Portabilidade entre plataformas: Com a consolidada Plataforma de Abstração Qt
ou Qt Platform Abstraction, agora é possível portar Qt para uma grande variedade de
plataformas de maneira mais fácil e rápida. O Qt 5 tem uma estrutura baseada na
separação da plataforma em Qt Essentials (módulos essenciais) e Add-ons (módulos
extras), o que permite aos desenvolvedores de sistemas operacionais se focar nos
módulos essenciais e leva a um menor tempo de execução como um todo.
Desenvolvimento aberto: O Qt agora é realmente um projeto aberto hospedado em
qt.io [http://qt.io]. O desenvolvimento é aberto e comandado pela comunidade.

1.2. Introdução ao Qt5


1.2.1. Qt Quick

Qt Quick é o principal componente utilizado para construção de interfaces gráficas com


Qt5. O Qt Quick consiste em um conjunto de tecnologias reunidas:

QML - Linguagem de marcação para criação de interfaces


JavaScript - A linguagem de scripts dinâmica
Qt C++ - O conjunto de bibliotecas multiplataforma melhorada

6
Assim como HTML, QML é uma linguagem de marcação. Ela é composta de tags,
chamadas de elementos em QtQuick, que são definidas entre chaves Item {}. Ela foi
desenvolvida do zero com foco na criação de interfaces de usuários de maneira rápida e
fácil. As interfaces criadas podem ser melhoradas a partir do uso de código em JavaScript.
O Qt Quick é facilmente extensível a partir do uso de código nativo em C++. Em resumo,
a interface gráfica criada com código declarativo é chamada de front-end e a parte
desenvolvida com código nativo de back-end. Estes conceitos permitem a separação dos
mecanismos de computação intensiva e operações nativas das suas aplicações da parte de
desenvolvimento de interfaces.

Em um projeto típico, o front-end é desenvolvido em QML/JavaScript e o back-end, que


geralmente se comunica com o sistema operacional e realiza o trabalho pesado é
desenvolvido em Qt C++. Isso permite uma separação natural entre desenvolvedores
voltados para o projeto de interfaces gráficas dos desenvolvedores funcionais.
Normalmente o código do back-end é testado utilizando o próprio módulo de testes
unitários presente no framework Qt e disponibilizado para que os desenvolvedores front-
end possam utilizá-lo.

1.2.2. Construindo uma Interface de Usuário

Vamos criar uma interface de usuário simples utilizando o QtQuick, demonstrando alguns
aspectos da linguagem QML. Vamos construir uma paisagem com um cata-vento que se
movimenta.

7
Vamos começar criando um documento vazio chamado main.qml. Todos os arquivos
QML devem terminar com a extensão .qml. Como uma linguagem de marcação (assim
como HTML), um documento QML deve conter um e somente um elemento raiz, que no
nosso caso é o elemento Image que tem suas dimensões (largura e altura) definida pela
imagem exibida por ele:
import QtQuick 2.5

Image {
id: root
source: "images/background.png"
}

Como QML não apresenta uma limitação quanto ao tipo de elemento que pode ser usado
como raiz, nós usamos um elemento Image com a propriedade source configurada com o
caminho para a imagem de fundo que iremos exibir.

Note

8
Todo elemento possui propriedades, por exemplo, uma imagem tem uma largura
definida pela propriedade width, uma altura definida pela propriedade height e também
outras propriedades como o source que define o caminho da imagem que será exibida
pelo elemento. Caso contrário, precisaríamos definir a propriedade width e height para
alguns valores de pixel úteis.

Os elementos mais padrão estão localizados no módulo QtQuick , que incluímos na


primeira linha com a declaração de importação.

A propriedade especial id opcional e contém um identificador para fazer referência a


esse elemento posteriormente em outros locais no documento. Importante: Uma
propriedade id não pode ser alterada depois de ter sido definida e não pode ser definida
durante o tempo de execução. Usar root como id para o elemento-raiz é apenas um
hábito do autor e faz referência ao elemento mais previsível em documentos QML
maiores.

O pólo dos elementos de primeiro plano e a roda de pinos da nossa interface de usuário
são colocados como imagens separadas.

O pólo precisa ser colocado no centro horizontal do fundo em direção ao fundo. E o cata-
vento pode ser colocado no centro do fundo.

Normalmente, sua interface do usuário será composta de muitos tipos de elementos


diferentes e não apenas de elementos de imagem, como neste exemplo.
Image {
id: root
...
Image {
id: pole
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom

9
source: "images/pole.png"
}

Image {
id: wheel
anchors.centerIn: parent
source: "images/pinwheel.png"
}
...
}

Para colocar a roda de pinos no local central, usamos uma propriedade complexa chamada
anchor. A ancoragem permite que você especifique relações geométricas entre objetos
parent e irmãos. ex: Coloque-me no centro de outro elemento ( anchors.centerIn:
parent ). tem left, right, top, bottom, centerIn, fill, verticalCenter e horizontalCenter
relações em ambas as extremidades. Claro, eles precisam combinar. Não faz sentido
ancorar o meu lado esquerdo ao lado de cima de um elemento.

Então nós colocamos o cata-vento a ser centralizado no parent no nosso background.

Note

Às vezes você precisará fazer pequenos ajustes na centralização exata. Isso seria
possível com anchors.horizontalCenterOffset ou com
anchors.verticalCenterOffset. Propriedades de ajustes semelhantes também estão
disponíveis para todas as outras âncoras. Por favor, consulte a documentação para obter
uma lista completa das propriedades das âncoras.

Note

Colocando uma imagem como um elemento filho de nosso elemento raiz (O elemento
Image) mostra um conceito importante de uma linguagem declarativa. Você descreve a
interface do usuário na ordem de camadas e agrupamento, onde a camada superior
(nosso rectangle) é desenhado primeiro e as camadas filho são desenhadas sobre ele no
sistema de coordenadas local do elemento que o contém.

Para tornar o mostruário um pouco mais interessante, gostaríamos de tornar a cena


interativa. A idéia é girar a roda quando o usuário pressiona o mouse em algum lugar da
cena.

Nós usamos o elemento MouseArea e o tornamos tão grande quanto nosso elemento-raiz.
Image {
id: root
...
MouseArea {
anchors.fill: parent
onClicked: wheel.rotation += 90

10
}
...
}

O mouse area emite sinais quando um usuário clica dentro da área coberta. Você pode
ligar a este sinal substituindo a função onClicked. Neste caso, a referência a imagem da
roda e altere sua rotação em +90 graus.

Note

Isso funciona para todos os sinais, a nomenclatura está em on + SignalName nos casos
de título. Além disso, todas as propriedades emitem um sinal quando seu valor é
alterado. A nomenclatura é:

on + PropertyName + Changed

Se uma propriedade width estiver mudando, você poderá observá-la com it with
onWidthChanged: print(width) por exemplo.

Agora a roda girará, mas ainda não está fluida. A propriedade de rotação é alterada
imediatamente. O que nós gostaríamos que a propriedade mudasse em 90 graus ao longo
do tempo. Agora animações entram em jogo. Uma animação define como uma alteração
de propriedade é distribuída ao longo de uma duração. Para habilitar isso, usamos um tipo
de animação chamado comportamento da propriedade. O Behaviour especifica uma
animação para uma propriedade definida para cada alteração aplicada a essa propriedade.
Em suma, toda vez que a propriedade muda, a animação é executada. Esta é apenas uma
das várias maneiras de declarar uma animação em QML.
Image {
id: root
Image {
id: wheel
Behavior on rotation {
NumberAnimation {
duration: 250
}
}
}
}

Agora, sempre que a rotação da propriedade da roda for alterada, ela será animada usando
um NumberAnimation com uma duração de 250 ms. So cada volta de 90 graus levará 250
ms.

11
Note

Você não vai realmente ver a roda embaçada.Isto é apenas para indicar a rotação. Mas
uma roda borrada está na pasta de ativos. Talvez você queira tentar usar isso.

Agora a roda já parece muito melhor. Espero que isso tenha lhe dado uma pequena idéia
de como funciona a programação do Qt Quick.

1.3. Blocos de Construção Qt


Qt 5 consiste em uma grande quantidade de módulos. Um módulo em geral é uma
biblioteca para o desenvolvedor usar. Alguns módulos são obrigatórios para uma
plataforma habilitada pelo Qt. Eles formam um conjunto chamado Qt Essentials Modules.
Muitos módulos são opcionais e formam o Qt Add-On Modules. Espera-se que a maioria
dos desenvolvedores não tenha a necessidade de usá-los, mas é bom conhecê-los, pois eles
fornecem soluções inestimáveis para desafios comuns.

1.3.1. Módulos do Qt

Os módulos presentes na categoria Qt Essentials são essenciais para que o framework


funcione em uma plataforma. Eles fornecem a base para desenvolver aplicações modernas
utilizando o Qt 5 e a tecnologia Qt Quick 2.

Módulos Essenciais - Qt Essentials

O conjunto mínimo de módulos Qt 5 para iniciar a programação QML.

Módulo Descrição
Qt Core Classes do núcleo não relacionadas com tarefas gráficas que são

12
Qt Core utilizadas pelos outros módulos.

Classes base para construção de componentes de interfaces gráficas.


Qt GUI
Inclui OpenGL.
Qt Multimedia Classes para manipulação de áudio, vídeo, rádio e câmera.
Classes que tornam a utilização de comunicação em rede mais fácil e
Qt Network
portável.
Qt QML Classes base para as linguagens QML e JavaScript.
Framework declarativo que permite a construção de aplicações
Qt Quick
altamente dinâmicas com interfaces de usuário personalizadas.
Qt SQL Classes utilizadas para integração de banco de dados utilizando SQL.
Classes para construção de testes unitários para aplicações e
Qt Test
bibliotecas que utilizam o framework Qt.
Classes para implementação do WebKit2 e sua API QML. Veja
Qt WebKit
também o módulo Qt WebKit Widgets.
Qt WebKit Classes para implementação do WebKit1 e suas classes baseadas no
Widgets QWidget para o Qt 4.
Qt Widgets Classes que extendem o módulo Qt GUI com widgets C++.

Módulos Adicionais Qt

Além dos módulos essenciais, o Qt oferece módulos adicionais para os desenvolvedores,


que não fazem parte da distribuição básica. Segue uma breve lista de módulos adicionais
disponíveis.

Qt 3D - Um conjunto de APIs que permitem programar gráficos 3D de forma fácil e


declarativa.
Qt Bluetooth - APIs C++ e QML para utilizar a tecnologia Bluetooth.
Qt Contacts - APIs C++ e QML para acessar lista de endereços e contatos.
Qt Location - Permite a utilização de funcionalidades de localização,
posicionamento, mapas, navegação através de interfaces QML e C++. Backend
NMEA para posicionamento
Qt Organizer - APIs C++ e QML para acessar organizadores de eventos (todos,

13
Qt Publish and Subscribe
Qt Sensors - Acesso a sensores através de interfaces QML e C++.
Qt Service Framework - Permite as aplicações ler, navegar e escutar notificações de
mundança entre processos.
Qt System Info - Permite acessar informações relacionadas com o sistema e seus
recursos.
Qt Versit - Suporte para os formatos vCard e iCalendar.
Qt Wayland - Somente para Linux. Inclui a API Qt Compositor (servidor), e plugin
para a plataforma Wayland (clientes).
Qt Feedback - Respostas táteis e sonoras para ações de usuarios.
Qt JSON DB - Armazenamento no-SQL para objetos.

Note

Como estes módulos não fazem parte da distribuição principal, seus status de
desenvolvimento são diferentes, variando de acordo com a quantidade de
desenvolvedores que contribuem para sua manutenção e teste.

1.3.2. Plataformas suportadas

O Qt suporta uma grande variedade de plataformas. As principais plataformas desktop e


embarcadas são suportadas. Através do Qt Application Abstraction, atualmente portar
Qt para outras plataformas é uma tarefa mais fácil.

Testar o Qt 5 em uma plataforma é uma tarefa que leva bastante tempo. Um subconjunto
de plataformas foi selecionado pelo projeto Qt para compor o conjunto de plataformas
referência. Estas plataformas são altamente testadas, o que garante um bom nível de
qualidade. Mas ponha na sua mente: nenhum código é livre de erros.

1.4. O Qt Project
Extraído de Qt Project wiki [http://wiki.qt.io/]:

“O Qt Project é uma comunidade meritocrática baseada no consenso composta por


pessoas interessadas no framework Qt. Qualquer um que compartilha deste interesse pode
se juntar a comunidade, participar nos seus processos de decisões, e contribuir para o
desenvolvimento do Qt.”

O Qt-Project é a organização que mais contribui para o desenvolvimento da parte de


código livre do framework Qt. Ela forma a base para outros usuários contribuírem. O
maior contribuidor é a DIGIA, que detém os direitos comerciais sobre o Qt.

O Qt pode ser utilizado sob a licença de código livre ou comercial. A licença comercial é
utilizada por empresas que não podem ou não vão cumprir com os deveres da licença de

14
utilizada por empresas que não podem ou não vão cumprir com os deveres da licença de
código livre. Sem a licença comercial estas empresas não poderão utilizar o Qt
devidamente, o que não permitiria a DIGIA contribuir tanto para o código do Qt-Project.

Existem muitas empresas ao redor do mundo que mantém suas atividades baseadas em
consultorias e desenvolvimento de produtos utilizando o Qt e suas plataformas. Existem
vários projetos de código livre e desenvolvedores que utilizam Qt como sua principal
plataforma de desenvolvimento. É muito bom fazer parte desta comunidade vibrante e
trabalhar com estas ferramentas e bibliotecas incríveis. Isso pode fazer de você uma
pessoa melhor? Talvez:-)

Contribua aqui: http://wiki.qt.io/

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

15
Qt5 Cadaques Book » previous | next

2. Iniciando
Autor da seção: jryannel [https://github.com/jryannel]

Este capítulo irá apresentá-lo ao desenvolvimento com o Qt 5. Vamos mostrar-lhe como


instalar o Qt SDK e como você pode criar, bem como executar um simples aplicativo hello
world usando o Qt Creator IDE.

Note

O código fonte deste capítulo pode ser encontrado no assets folder.

2.1. Instalando o SDK do Qt 5


O Qt SDK inclui as ferramentas necessárias para criar aplicativos de área de trabalho ou
embarcados. A versão mais recente pode ser obtida pagina Qt-Company [http://qt.io].
Existem instaladores offline e online. O autor prefere pessoalmente o pacote de instalação
online, pois permite que você instale e atualize vários releases do Qt. Essa seria a maneira
recomendada de começar. O próprio SDK tem uma ferramenta de manutenção que permite
atualizar o SDK para a versão mais recente.

O Qt SDK é fácil de instalar e vem com seu próprio IDE para desenvolvimento rápido
chamado Qt Creator. O IDE é um ambiente altamente produtivo para codificação Qt e
recomendado para todos os leitores. Muitos desenvolvedores usam o Qt a partir da linha
de comando e você está livre para usar um editor de código de sua escolha.

Ao instalar o SDK, você deve selecionar a opção padrão e garantir que o Qt 5.x esteja
ativado. Então você está pronto para iniciar.

2.2. Hello World


Para testar sua instalação, criaremos um pequeno aplicativo hello world. Por favor abra Qt
Creator e crie um Qt Quick UI Project em ( File ‣ New File or Project ‣ Qt Quick Project
‣ Qt Quick UI ) and name the project HelloWorld.

Note

O Qt Creator IDE permite que você crie vários tipos de aplicativos. Se não for indicado
de outra forma, nós sempre usamos um projeto Qt Quick UI.

16
Sugestão

Um aplicativo Qt Quick típico é feito em tempo de execução chamado QmlEngine, que


carrega o código QML inicial. O desenvolvedor pode registrar tipos C ++ com o tempo
de execução para interagir com o código nativo. Esses tipos de C ++ também podem ser
empacotados em um plug-in e, em seguida, carregados dinamicamente usando uma
instrução de importação. As ferramentas qmlscene e qml são tempos de execução pré-
estabelecidos, que podem ser usados diretamente. Para começar, não vamos cobrir o lado
nativo do desenvolvimento e focar apenas nos aspectos QML do Qt 5.

O Qt Creator irá criar vários arquivos para você. O arquivo HelloWorld.qmlproject é o


arquivo de projeto no qual a configuração do projeto relevante é armazenada. Este arquivo
é gerenciado pelo Qt Creator, portanto, não edite.

Outro arquivo HelloWorld.qml é o nosso código de aplicativo. Abra e tente adivinhar o


que o aplicativo faz e continue a ler.
// HelloWorld.qml

import QtQuick 2.5

Rectangle {
width: 360
height: 360
Text {
anchors.centerIn: parent
text: "Hello World"
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}

O HelloWord.qml é escrito na linguagem QML. Discutiremos a linguagem QML com


mais profundidade no próximo capítulo. O QML descreve a interface do usuário como
uma árvore de elementos hierárquicos. Nesse caso, um retângulo de 360 x 360 pixels com
uma leitura de texto centralizada “Hello World”. Para capturar cliques de usuários, mouse
area abrange todo o retângulo e, quando o usuário clica nele, o aplicativo é encerrado.

Para executar o aplicativo por conta própria, pressione o Run tool no lado esquerdo ou
selecione Build ‣ Run no menu.

Qt Creator irá iniciar o qmlscene e passa o documento QML como o primeiro argumento.
O qmlscene analisará o documento e iniciará a interface do usuário. Agora você deve ver
algo assim:

17
Qt 5 parece estar funcionando e estamos prontos para continuar.

Dica

Se você for um integrador de sistemas, convém instalar o Qt SDK para obter a versão
mais recente do Qt estável, bem como uma versão do Qt compilada a partir do código-
fonte para o destino de dispositivo específico.

Construa a partir do Scratch

Se você quiser criar o Qt 5 a partir da linha de comando, primeiro precisará pegar uma
cópia do repositório de código e compilá-la.
git clone git://gitorious.org/qt/qt5.git
cd qt5
./init-repository
./configure -prefix $PWD/qtbase -opensource
make -j4

Após uma compilação bem-sucedida e 2 xícaras de café, o Qt 5 estará disponível na


pasta qtbase. Qualquer bebida será suficiente, no entanto, sugerimos café para melhores
resultados.

Se você quiser testar sua compilação, simplesmente comece qtbase/bin/qmlscene e


selecione um exemplo do Qt Quick para executá-lo...ou siga apenas nós para o próximo
capítulo.

Para testar sua instalação, criaremos um pequeno aplicativo hello world. Por favor, crie
um simples arquivo example.qml usando o seu editor de texto favorito e cole o seguinte
conteúdo dentro:
// HelloWorld.qml

import QtQuick 2.5

Rectangle {
width: 360

18
height: 360
Text {
anchors.centerIn: parent
text: "Greetings from Qt 5"
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}

Você pode executar agora o exemplo usando o tempo de execução padrão que vem com
o Qt 5:
$ qtbase/bin/qmlscene

2.3. Tipos de Aplicação


Esta seção é um resumo dos diferentes tipos de aplicativos possíveis que alguém poderia
escrever com o Qt 5. Não se limita à seleção apresentada, mas deve dar ao leitor uma idéia
melhor sobre o que pode ser feito com o Qt 5 em geral.

2.3.1. Aplicação de console

Um aplicativo de console não fornece nenhuma interface gráfica com o usuário e


normalmente será chamado como parte de um serviço do sistema ou da linha de comando.
O Qt 5 vem com uma série de componentes prontos que ajudam você a criar aplicativos de
plataforma cruzada de console de maneira muito eficiente. Por exemplo, as APIs de
arquivos de rede. Também manipulação de strings e, desde o Qt 5.1, analisador de linha de
comando eficiente.Como o Qt é uma API de alto nível no topo do C ++, você obtém
velocidade de programação combinada com a velocidade de execução. Não pense no Qt
como apenas um kit de ferramentas de interface do usuário - ele tem muito mais a
oferecer.

Manipulação de String

No primeiro exemplo, demonstramos como alguém poderia simplesmente adicionar 2


strings constantes. Este não é um aplicativo muito útil, mas dá uma idéia de como um
aplicativo C ++ nativo, sem um loop de eventos, poderia parecer.
// module or class includes
#include <QtCore>

// text stream is text-codec aware


QTextStream cout(stdout, QIODevice::WriteOnly);

int main(int argc, char** argv)

19
{
// avoid compiler warnings
Q_UNUSED(argc)
Q_UNUSED(argv)
QString s1("Paris");
QString s2("London");
// string concatenation
QString s = s1 + " " + s2 + "!";
cout << s << endl;
}

Classe Container

Este exemplo adiciona uma lista e lista a iteração ao aplicativo. O Qt vem com uma
grande coleção de classes contêineres que são fáceis de usar e usam os mesmos
paradigmas de API que o resto das classes do Qt.
QString s1("Hello");
QString s2("Qt");
QList<QString> list;
// stream into containers
list << s1 << s2;
// Java and STL like iterators
QListIterator<QString> iter(list);
while(iter.hasNext()) {
cout << iter.next();
if(iter.hasNext()) {
cout << " ";
}
}
cout << "!" << endl;

Aqui nós mostramos algumas funções de lista avançadas, que permitem unir uma lista de
strings em uma string.Isso é muito útil quando você precisa prosseguir com a entrada de
texto baseada em linha. O inverso (string para string-list) também é possível usando a
função QString::split().
QString s1("Hello");
QString s2("Qt");
// convenient container classes
QStringList list;
list << s1 << s2;
// join strings
QString s = list.join(" ") + "!";
cout << s << endl;

Arquivo IO

No próximo trecho, lemos um arquivo CSV do diretório local e fazemos um loop pelas
linhas para extrair as células de cada linha. Fazendo isso, obtemos os dados da tabela do
arquivo CSV em ca. 20 linhas de código. A leitura de arquivos nos fornece apenas um
fluxo de bytes. Para convertê-lo em um texto Unicode válido, precisamos usar o fluxo de
texto e passar o arquivo como um fluxo de nível inferior. Para gravar arquivos CSV, basta

20
abrir o arquivo no modo de gravação e canalizar as linhas para o fluxo de texto.
QList<QStringList> data;
// file operations
QFile file("sample.csv");
if(file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
// loop forever macro
forever {
QString line = stream.readLine();
// test for null string 'String()'
if(line.isNull()) {
break;
}
// test for empty string 'QString("")'
if(line.isEmpty()) {
continue;
}
QStringList row;
// for each loop to iterate over containers
foreach(const QString& cell, line.split(",")) {
row.append(cell.trimmed());
}
data.append(row);
}
}
// No cleanup necessary.

Isso conclui nossa seção sobre o aplicativo baseado em console com o Qt.

2.3.2. Aplicativo Widget

Aplicativos baseados em console são muito úteis, mas às vezes você precisa ter uma UI.
Além disso, os aplicativos baseados na UI provavelmente precisarão de um back-end para
ler / gravar arquivos, comunicar-se pela rede ou manter os dados em um contêiner.

Neste primeiro snippet para aplicativos baseados em widgets, fazemos o mínimo


necessário para criar uma janela e mostrá-la. Um widget sem pai no mundo do Qt é uma
janela. Usamos o ponteiro com escopo definido para garantir que o widget seja excluído
quando o ponteiro com escopo sair do escopo. O objeto de aplicação encapsula o tempo de
execução do Qt e, com a chamada exec() iniciamos o loop de eventos. A partir daí, o
aplicativo reage apenas a eventos acionados por mouse ou teclado ou outros provedores de
eventos, como rede ou arquivo de IO. O aplicativo só sairá quando o loop de eventos for
encerrado. Isso é feito chamando quit() no aplicativo ou fechando a janela.

Quando você executar o código, verá uma janela com o tamanho de 240 x 120 pixels. Isso
é tudo.
#include <QtGui>

int main(int argc, char** argv)


{
QApplication app(argc, argv);

21
QScopedPointer<QWidget> widget(new CustomWidget());
widget->resize(240, 120);
widget->show();
return app.exec();
}

Widgets Personalizados

Quando você trabalha em interfaces de usuário, você precisará criar widgets


customizados. Normalmente, um widget é uma área de janela preenchida com chamadas
de pintura. Além disso, o widget tem conhecimento interno de como lidar com entrada de
teclado ou mouse e como reagir a disparos externos. Para fazer isso no Qt, precisamos
derivar do QWidget e sobrescrever várias funções para pintura e manipulação de eventos.
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H

#include <QtWidgets>

class CustomWidget : public QWidget


{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
QPoint m_lastPos;
};

#endif // CUSTOMWIDGET_H

Na implementação, desenhamos uma pequena borda em nosso widget e um pequeno


retângulo na última posição do mouse. Isso é muito comum em um widget personalizado
de baixo nível. Eventos de mouse ou teclado alteram o estado interno do widget e acionam
uma atualização de pintura. Não queremos detalhar muito esse código, mas é bom saber
que você tem essa capacidade. O Qt vem com um grande conjunto de widgets de desktop
prontos, de modo que a probabilidade é alta de que você não precise fazer isso.
#include "customwidget.h"

CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
}

void CustomWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QRect r1 = rect().adjusted(10,10,-10,-10);
painter.setPen(QColor("#33B5E5"));
painter.drawRect(r1);

QRect r2(QPoint(0,0),QSize(40,40));

22
if(m_lastPos.isNull()) {
r2.moveCenter(r1.center());
} else {
r2.moveCenter(m_lastPos);
}
painter.fillRect(r2, QColor("#FFBB33"));
}

void CustomWidget::mousePressEvent(QMouseEvent *event)


{
m_lastPos = event->pos();
update();
}

void CustomWidget::mouseMoveEvent(QMouseEvent *event)


{
m_lastPos = event->pos();
update();
}

Widgets no Desktop

Os desenvolvedores do Qt já fizeram tudo isso por você e fornecem um conjunto de


widgets de desktop, que parecerão nativos em diferentes sistemas operacionais. Seu
trabalho é, então, organizar esses diferentes widgets em um contêiner de widgets em
painéis maiores. Um widget no Qt também pode ser um contêiner para outros widgets.
Isso é realizado pelo relacionamento pai-filho. Isso significa que precisamos tornar nossos
widgets prontos como buttons, check boxes, radio button mas também lists e grids um
filho de outro widget. Uma maneira de conseguir isso é exibida abaixo.

Here is the header file for a so called widget container.


class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = 0);
private slots:
void itemClicked(QListWidgetItem* item);
void updateItem();
private:
QListWidget *m_widget;
QLineEdit *m_edit;
QPushButton *m_button;
};

Na implementação, usamos layouts para organizar melhor nossos widgets. Os


gerenciadores de layout reprojetam os widgets de acordo com algumas políticas de
tamanho quando o widget de contêiner é redimensionado. Neste exemplo, temos uma lista,
uma edição de linha e um botão organizado verticalmente para permitir a edição de uma
lista de cidades. Usamos o signal e os slots do Qt para conectar objetos remetentes e
destinatários.
CustomWidget::CustomWidget(QWidget *parent) :

23
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
m_widget = new QListWidget(this);
layout->addWidget(m_widget);

m_edit = new QLineEdit(this);


layout->addWidget(m_edit);

m_button = new QPushButton("Quit", this);


layout->addWidget(m_button);
setLayout(layout);

QStringList cities;
cities << "Paris" << "London" << "Munich";
foreach(const QString& city, cities) {
m_widget->addItem(city);
}

connect(m_widget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(itemClick


connect(m_edit, SIGNAL(editingFinished()), this, SLOT(updateItem()));
connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
}

void CustomWidget::itemClicked(QListWidgetItem *item)


{
Q_ASSERT(item);
m_edit->setText(item->text());
}

void CustomWidget::updateItem()
{
QListWidgetItem* item = m_widget->currentItem();
if(item) {
item->setText(m_edit->text());
}
}

Desenhando Shapes

Alguns problemas são melhores visualizados. Se o problema em questão parece


fracamente com objetos geométricos, a visualização de gráficos qt é um bom candidato.
Uma visualização de gráficos organiza formas geométricas simples em uma cena. O
usuário pode interagir com essas formas ou elas são posicionadas usando um algoritmo.
Para preencher uma exibição gráfica, você precisa de uma exibição gráfica e uma cena
gráfica. A cena é anexada à exibição e é preenchida com itens gráficos. Aqui está um
pequeno exemplo. Primeiro o arquivo de cabeçalho com a declaração da visão e cena.
class CustomWidgetV2 : public QWidget
{
Q_OBJECT
public:
explicit CustomWidgetV2(QWidget *parent = 0);
private:
QGraphicsView *m_view;

24
QGraphicsScene *m_scene;

};

Na implementação, a cena é anexada à visão primeiro. A visualização é um widget e é


organizada em nosso widget de contêiner. No final, adicionamos um pequeno retângulo à
cena, que é então renderizado na exibição.
#include "customwidgetv2.h"

CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
m_view = new QGraphicsView(this);
m_scene = new QGraphicsScene(this);
m_view->setScene(m_scene);

QVBoxLayout *layout = new QVBoxLayout(this);


layout->setMargin(0);
layout->addWidget(m_view);
setLayout(layout);

QGraphicsItem* rect1 = m_scene->addRect(0,0, 40, 40, Qt::NoPen, QColor("#FFBB


rect1->setFlags(QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsMovable
}

2.3.3. Adaptando Data

Até agora nós cobrimos principalmente tipos de dados básicos e como usar widgets e
visualizações gráficas. Muitas vezes, em seu aplicativo, você precisará de uma quantidade
maior de dados estruturados, que também precisam ser persistentemente armazenados. Os
dados também precisam ser exibidos. Para isso, o Qt usa modelos. Um modelo simples é o
modelo de lista de strings, que é preenchido com strings e depois anexado a uma view de
lista.
m_view = new QListView(this);
m_model = new QStringListModel(this);
view->setModel(m_model);

QList<QString> cities;
cities << "Munich" << "Paris" << "London";
model->setStringList(cities);

Outra maneira popular de armazenar ou recuperar dados é o SQL. O Qt vem com o


SQLite embarcado e também possui suporte para outros mecanismos de banco de dados
(MySQL, PostgresSQL, ...). Primeiro você precisa criar seu banco de dados usando um
esquema, como este:
CREATE TABLE city (name TEXT, country TEXT);
INSERT INTO city value ("Munich", "Germany");
INSERT INTO city value ("Paris", "France");
INSERT INTO city value ("London", "United Kingdom");

25
Para usar o sql, precisamos adicionar o módulo sql ao nosso arquivo .pro
QT += sql

E então podemos abrir nosso banco de dados usando C++. Primeiro, precisamos recuperar
um novo objeto de banco de dados para o mecanismo de banco de dados especificado.
Com este objeto de banco de dados, nós abrimos o banco de dados. Para o SQLite, basta
especificar o caminho para o arquivo do banco de dados. Qt fornece algum modelo de
banco de dados de alto nível, um deles é o modelo de tabela, que usa um identificador de
tabela e uma opção onde cláusula para selecionar os dados. O modelo resultante pode ser
anexado a uma visualização de lista como o outro modelo anterior.
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName('cities.db');
if(!db.open()) {
qFatal("unable to open database");
}

m_model = QSqlTableModel(this);
m_model->setTable("city");
m_model->setHeaderData(0, Qt::Horizontal, "City");
m_model->setHeaderData(1, Qt::Horizontal, "Country");

view->setModel(m_model);
m_model->select();

Para um nível mais alto de operações de modelo, o Qt fornece um modelo de proxy de


arquivo de classificação, que permite que você, no formulário básico, classifique e filtre
outro modelo.
QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
proxy->setSourceModel(m_model);
view->setModel(proxy);
view->setSortingEnabled(true);

A filtragem é feita com base na coluna para ser filtros e uma string como argumento de
filtro.
proxy->setFilterKeyColumn(0);
proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
proxy->setFilterFixedString(QString)

O modelo proxy de filtro é muito mais poderoso do que o demonstrado aqui. Por
enquanto, basta lembrar que existe.

Note

Esta foi uma visão geral do diferente tipo de aplicativo clássico que você poderia
desenvolver com o Qt 5. O desktop está em movimento e, em breve, os dispositivos
móveis serão o nosso desktop de amanhã. Os dispositivos móveis têm um design de
interface de usuário diferente. Eles são muito mais simplistas do que os aplicativos de
desktop. Eles fazem uma coisa e fazem simplesmente e focados. As animações são uma

26
parte importante da experiência. Uma interface de usuário precisa se sentir viva e
fluente. As tecnologias tradicionais da Qt não são adequadas para este mercado.

A seguir: Qt Quick

2.3.4. Aplicativo Qt Quick

Existe um conflito inerente no desenvolvimento de software moderno. A interface do


usuário está se movendo muito mais rápido que nossos serviços de back-end. Em uma
tecnologia tradicional, você desenvolve o chamado front-end no mesmo ritmo do back-
end. Isso resulta em conflitos quando os clientes desejam alterar a interface do usuário
durante um projeto ou desenvolvem a ideia de uma interface do usuário durante o projeto.
Projetos ágeis, requerem métodos ágeis.

Qt Quick fornece um ambiente declarativo onde sua interface de usuário (o front-end) é


declarada como HTML e seu back-end está em código C ++ nativo. Isso permite que você
obtenha o melhor dos dois mundos.

Abaixo é um Qt Quick UI simples


import QtQuick 2.5

Rectangle {
width: 240; height: 1230
Rectangle {
width: 40; height: 40
anchors.centerIn: parent
color: '#FFBB33'
}
}

A linguagem de declaração é chamada de QML e precisa de um tempo para ser executada.


O Qt fornece um tempo de execução padrão chamado qmlscen, mas também não é tão
difícil escrever um tempo de execução personalizado. Para isso, precisamos de uma visão
rápida e definir o documento QML principal como fonte. A única coisa que resta é mostrar
a interface do usuário.
QQuickView* view = new QQuickView();
QUrl source = QUrl::fromLocalFile("main.qml");
view->setSource(source);
view.show();

Voltando aos nossos exemplos anteriores. Em um exemplo, usamos um modelo de cidade


C ++. Seria ótimo se pudéssemos usar esse modelo dentro de nosso código QML
declarativo.

Para permitir isso, primeiro codificamos nosso front-end para ver como gostaríamos de
usar um modelo de cidade. Nesse caso, o front-end espera um objeto chamado cityModel
que podemos usar dentro de uma visualização de lista.

27
import QtQuick 2.5

Rectangle {
width: 240; height: 120
ListView {
width: 180; height: 120
anchors.centerIn: parent
model: cityModel
delegate: Text { text: model.city }
}
}

Para ativar o cityModel podemos principalmente reutilizar nosso modelo anterior e


adicionar uma propriedade de contexto ao nosso contexto raiz (o contexto raiz é o outro
elemento-raiz no documento principal)
m_model = QSqlTableModel(this);
... // some magic code
QHash<int, QByteArray> roles;
roles[Qt::UserRole+1] = "city";
roles[Qt::UserRole+2] = "country";
m_model->setRoleNames(roles);
view->rootContext()->setContextProperty("cityModel", m_model);

Sugestão

Isso não está completamente correto, pois o modelo de tabela SQL contém os dados nas
colunas e um modelo QML espera os dados como funções. Portanto, é necessário haver
um mapeamento entre colunas e funções. Por favor, veja a página wiki QML and
QSqlTableModel [http://wiki.qt.io/QML_and_QSqlTableModel].

2.4. Resumo
Vimos como instalar o Qt SDK e como criar nosso primeiro aplicativo. Depois,
percorremos os diferentes tipos de aplicativos para obter uma visão geral do Qt,
mostrando alguns recursos que o Qt oferece para o desenvolvimento de aplicativos.
Espero que você tenha uma boa impressão de que o Qt é um kit de ferramentas de
interface de usuário muito rico e oferece tudo que um desenvolvedor de aplicativos pode
esperar e muito mais. Ainda assim, o Qt não bloqueia você em bibliotecas específicas,
pois você sempre pode usar outras bibliotecas ou estender o Qt por conta própria. Também
é rico quando se trata de suportar diferentes modelos de aplicativos: console, interface de
usuário clássica de desktop e interface de usuário de touch.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

28
Qt5 Cadaques Book » previous | next

3. IDE do Qt Creator
Section author: jryannel [https://github.com/jryannel]

O Qt Creator é o ambiente de desenvolvimento integrado padrão para o Qt. É escrito pelos


desenvolvedores do Qt para desenvolvedores do Qt. O IDE está disponível em todas as
principais plataformas de desktop, por exemplo, Windows / Mac / Linux. Já vimos clientes
usando o Qt Creator em um dispositivo incorporado. O Qt Creator possui uma interface de
usuário eficiente e enxuta e realmente brilha para tornar o desenvolvedor produtivo. O Qt
Creator pode ser usado para executar sua interface de usuário do Qt Quick, mas também
para compilar código c ++ e isso para seu sistema host ou para outro dispositivo usando
um cross-compiler.

Note

O código fonte deste capítulo pode ser encontrado no assets folder.

3.1. A interface do usuário


Ao iniciar o Qt Creator, você é recebido pelo Welcome screen. Lá você encontrará as dicas
mais importantes sobre como continuar dentro do Qt Creator e seus projetos usados
recentemente. Você também verá a lista de sessões, que pode estar vazia para você. Uma
sessão é uma coleção de projetos armazenados para sua referência. Isso é muito útil
quando você tem vários clientes com projetos maiores.

Do lado esquerdo, você verá o seletor de modos. Os seletores de modo contêm etapas
típicas do seu fluxo de trabalho.

Welcome mode: Para sua orientação

29
Edit mode: Concentre-se no código
Design mode: Concentre-se no UI design
Debug mode: Recuperar informações sobre um aplicativo em execução
Projects mode: Modifique seus projetos e construa a configuração
Analyze mode: Para detectar vazamentos de memória e perfis
Help mode: Fácil acesso à documentação do Qt

Abaixo dos seletores de modo, você encontrará o seletor de configuração do projeto eo


run/debug -> depuração)

Na maior parte do tempo, você estará no modo de edição com o editor de código no painel
central. De tempos em tempos, você visitará o modo Projetos quando precisar configurar
seu projeto. E então você pressione Run. O Qt Creator é inteligente o suficiente para
garantir que seu projeto seja totalmente construído antes de executá-lo.

Na parte inferior estão os painéis de saída para problemas, mensagens de aplicativos,


mensagens de compilação e outras mensagens.

3.2. Registrando seu Qt Kit


O Qt Kit é provavelmente o aspecto mais difícil quando se trata de trabalhar com o Qt
Creator inicialmente. Um Qt Kit é um conjunto de uma versão, compilador e dispositivo
do Qt e algumas outras configurações. Ele é usado para identificar de forma exclusiva a
combinação de ferramentas para a criação do seu projeto. Um kit típico para a área de
trabalho conteria um compilador GCC e uma versão Qt (por exemplo, Qt 5.1.1) e um
dispositivo (“Desktop”). Depois de criar um projeto, você precisa atribuir um kit a um
projeto antes que o criador do qt possa construir o projeto. Antes de você poder criar um

30
primeiro kit, você precisa ter um compilador instalado e ter uma versão do Qt registrada.
Uma versão do Qt é registrada especificando o caminho para o executável do qmake. O Qt
Creator consulta o qmake para obter informações necessárias para identificar a versão do
Qt.

Adicionar um kit e registrar uma versão Qt é feito em Settings ‣ entre em Build & Run. Lá
você também pode ver quais compiladores estão registrados.

Note

Por favor, primeiro verifique se o seu Qt Creator já tem a versão correta do Qt registrada
e então garanta que um Kit para sua combinação de compilador e Qt e dispositivo seja
especificado. Você não pode construir um projeto sem um kit.

3.3. Gerenciando Projects


O Qt Creator gerencia seu código-fonte em projetos. Você pode criar um novo projeto em
File ‣ New File or Project. Quando você cria um projeto, você tem muitas opções de
modelos de aplicativos. O Qt Creator é capaz de criar aplicativos móveis e para
desktop.Aplicativo que usa Widgets Qt Quick ou Qt Quick e controles ou até mesmo
projetos vazios. Suporte para projetos HTML5 e python. Para um iniciante é difícil
escolher, então escolhemos três tipos de projetos para você.

Applications / Qt Quick 2.0 UI: Isso criará um projeto somente QML / JS para
você, sem nenhum código C ++. Faça isso se desejar esboçar uma nova interface de
usuário ou planejar criar um aplicativo com uma UI moderna, no qual as partes
nativas são entregues por plug-ins.
Libraries / Qt Quick 2.0 Extension Plug-in: Use este assistente para criar um stub
para um plug-in para Qt Quick UI. Um plug-in é usado para estender o Qt Quick com
elementos nativos.
Other Project / Empty Qt Project: Um projeto vazio. Tome isso se você quiser
codificar seu aplicativo com c ++ com scratch. Esteja ciente de que você precisa
saber o que está fazendo aqui.

Note

Durante as primeiras partes do livro, usaremos principalmente o tipo de projeto Qt Quick


2.0 UI. Mais tarde, para descrever alguns aspectos do c ++, usaremos o tipo Empty-Qt-
Project ou algo semelhante. Para estender o Qt Quick com nossos próprios plug-ins
nativos, usaremos o tipo de assistente Qt Quick 2.0 Extension Plug-in.

3.4. Usando o Editor

31
Quando você abre um projeto ou acaba de criar um novo projeto, o Qt Creator mudará
para o modo de edição. Você deve ver à esquerda seus arquivos de projeto e na área
central o editor de código. Selecionar arquivos à esquerda os abrirá no editor. O editor
fornece realce de sintaxe, conclusão de código e correções rápidas. Também suporta vários
comandos para refatoração de código. Ao trabalhar com o editor, você terá a sensação de
que tudo reage imediatamente. Isso é graças aos desenvolvedores do Qt Creator.

3.5. Localizador
O localizador é um componente central dentro do Qt Creator. Ele permite que os
desenvolvedores naveguem rapidamente para locais específicos dentro do código-fonte ou
dentro da ajuda. Para abrir o localizador, pressione Ctrl+K.

Um pop-up está vindo do canto inferior esquerdo e mostra uma lista de opções. Se você
acabou de pesquisar um arquivo dentro do seu projeto, simplesmente pressione a primeira
letra do nome do arquivo. O localizador também aceita, portanto o *main.qml também
funcionará. Caso contrário, você também pode prefixar sua pesquisa para pesquisar por
um tipo de conteúdo específico.

32
Por favor, experimente. Por exemplo, para abrir a ajuda para o elemento QML Rectangle
abra o localizador e digite ?rectangle. Enquanto você digita, o localizador atualizará as
sugestões até encontrar a referência que você está procurando.

3.6. Depuração
O Qt Creator vem com suporte a depuração C ++ e QML.

Note

Hmm, acabei de perceber que não usei muita depuração. Espero que seja um bom sinal.
Precisa pedir a alguém para me ajudar aqui. Enquanto isso, dê uma olhada no Qt Creator
documentation [http://http://doc.qt.io/qtcreator/index.html].

3.7. Atalhos
Atalhos são a diferença entre um sistema agradável de usar e um sistema profissional.
Como profissional, você passa centenas de horas na frente de sua inscrição. Cada atalho
que faz seu fluxo de trabalho contar mais rápido. Felizmente, os desenvolvedores do Qt
Creator pensam o mesmo e adicionaram literalmente centenas de atalhos ao aplicativo.

Para começar, temos alguns atalhos básicos de coleção (na notação na janela):

Ctrl+B - Construir projeto


Ctrl+R - Executar projeto
Ctrl+Tab - Alternar entre documentos abertos
Ctrl+K - Abrir Localizador
Esc - Voltar (aperte várias vezes e você está de volta no editor)
F2 - Siga o símbolo sob o cursor
F4 - Alternar entre cabeçalho e codigo fonte (somente útil para código c ++)

Lista de Qt Creator shortcuts [http://doc.qt.io/qtcreator/creator-keyboard-shortcuts.html] da


documentação.

Note

33
Você pode editar os atalhos de dentro do Creator usando a caixa de diálogo de
configurações.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

34
Qt5 Cadaques Book » previous | next

4. Iniciando no Quick
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Este capítulo fornece uma visão geral do QML, a linguagem de interface de usuário
declarativa usada no Qt 5. Discutiremos a sintaxe do QML, que é uma árvore de
elementos, seguida por uma visão geral dos elementos básicos mais importantes. Mais
adiante, examinaremos brevemente como criar nossos próprios elementos, chamados
componentes e como transformar elementos usando manipulações de propriedade. No
final, veremos como organizar os elementos juntos em um layout e, finalmente, observar
os elementos em que o usuário pode fornecer informações.

4.1. Sintaxe QML


O QML é uma linguagem declarativa usada para descrever a interface do usuário do seu
aplicativo. Ele divide a interface do usuário em elementos menores, que podem ser
combinados em componentes. O QML descreve a aparência e o comportamento desses
elementos da interface do usuário. Essa descrição da interface do usuário pode ser
enriquecida com código JavaScript para fornecer uma lógica simples, mas também mais
complexa. Nesta perspectiva, segue o padrão HTML-JavaScript, mas o QML é projetado
desde o início para descrever as interfaces do usuário e não os documentos de texto.

Na sua maneira mais simples, o QML é uma hierarquia de elementos. Elementos filho
herdam o sistema de coordenadas da raiz. Uma coordenada x, y é sempre relativa a raiz.

35
Vamos começar com um exemplo simples de um arquivo QML para explicar a sintaxe
diferente.

// RectangleExample.qml

import QtQuick 2.5

// The root element is the Rectangle


Rectangle {
// name this element root
id: root

// properties: <name>: <value>


width: 120; height: 240

// color property
color: "#4A4A4A"

// Declare a nested element (child of root)


Image {
id: triangle

// reference the parent


x: (parent.width - width)/2; y: 40

source: 'assets/triangle_red.png'
}

// Another child of root


Text {
// un-named element

// reference element by id
y: triangle.y + triangle.height + 20

// reference root element


width: root.width

color: 'white'
horizontalAlignment: Text.AlignHCenter
text: 'Triangle'
}

36
}

A declaração import importa um módulo em uma versão específica. Em geral, você


sempre quer importar o QtQuick 2.0 como seu conjunto inicial de elementos
Comentários podem ser feitos usando // para comentários de linha única ou / * * / para
comentários de várias linhas. Assim como em C / C ++ e JavaScript
Todo arquivo QML precisa ter exatamente um elemento raiz, como HTML
Um elemento é declarado por seu tipo seguido por { }
Elementos podem ter propriedades, eles estão na forma name : value
Elementos arbitrários dentro de um documento QML podem ser acessados usando id
(um identificador sem aspas)
Os elementos podem ser aninhados, o que significa que um elemento raiz pode ter
elementos filhos. O elemento pai pode ser acessado usando a palavra-chave parent

Tip

Muitas vezes você deseja acessar um elemento específico por id ou um elemento raiz
usando a palavra-chave parent. Por isso, é uma boa prática nomear seu elemento raiz
“root” usando id: root. Então você não precisa pensar em como o elemento raiz é
nomeado no seu documento QML.

Sugestão

Você pode executar o exemplo usando o tempo de execução do Qt Quick a partir da


linha de comando do seu sistema operacional da seguinte forma:
$ $QTDIR/bin/qmlscene RectangleExample.qml

Onde você precisa substituir o $QTDIR para o caminho para sua instalação do Qt. O
qmlscene executa e inicializa o Qt Quick runtime e interpreta o arquivo QML fornecido.

No Qt Creator você pode abrir o arquivo de projeto correspondente e executar o


documento RectangleExample.qml.

4.1.1. Propriedades

Os elementos são declarados usando seu nome de elemento, mas são definidos usando
suas propriedades ou criando propriedades customizadas. Uma propriedade é um par de
valores-chave simples, por ex. width : 100, text: 'Greetings', color: '#FF0000'.
Uma propriedade tem um tipo bem definido e pode ter um valor inicial.

Text {

// (1) identifier

37
id: thisLabel

// (2) set x- and y-position


x: 24; y: 16

// (3) bind height to 2 * width


height: 2 * width

// (4) custom property


property int times: 24

// (5) property alias


property alias anotherTimes: thisLabel.times

// (6) set text appended by value


text: "Greetings " + times

// (7) font is a grouped property


font.family: "Ubuntu"
font.pixelSize: 24

// (8) KeyNavigation is an attached property


KeyNavigation.tab: otherLabel

// (9) signal handler for property changes


onHeightChanged: console.log('height:', height)

// focus is need to receive key events


focus: true

// change color based on focus value


color: focus?"red":"black"
}

Vamos analisar os diferentes recursos das propriedades:

1. id é um valor de propriedade muito especial, é usado para referenciar elementos


dentro de um arquivo QML (chamado “documento” em QML). O id não é um tipo
de string, mas sim um identificador e parte da sintaxe do QML. Um id precisa ser
único dentro de um documento e não pode ser redefinido para um valor diferente,
nem pode ser consultado. (Ele se comporta mais como um ponteiro no mundo C ++.)

2. Uma propriedade pode ser configurada para um valor, dependendo do seu tipo. Se
nenhum valor for dado para uma propriedade, um valor inicial será escolhido. Você
precisa consultar a documentação do elemento específico para obter mais
informações sobre o valor inicial de uma propriedade.

3. Uma propriedade pode depender de uma ou muitas outras propriedades. Isso é


chamado de binding. Uma propriedade associada é atualizada quando suas
propriedades dependentes são alteradas. Funciona como um contrato, neste caso, o
height deve ser sempre duas vezes a width.

4. A adição de propriedades próprias a um elemento é feita usando o qualificador

38
property seguido pelo tipo, o nome e o valor inicial opcional (property <type>
<name> : <value>). Se nenhum valor inicial é dado, um valor inicial do sistema é
escolhido.

Note

Você também pode declarar uma propriedade para ser a propriedade padrão se
nenhum nome de propriedade for fornecido ao prefixar a declaração de propriedade
com a palavra-chave default. Isso é usado, por exemplo, quando você adiciona
elementos filho, os elementos filho são adicionados automaticamente à propriedade
padrão children da lista de tipos, se eles forem elementos visíveis.

5. Outra maneira importante de declarar propriedades é usar a palavra-chave alias


(property alias <name> : <reference>). A palavra-chave alias nos permite
encaminhar uma propriedade de um objeto ou um objeto de dentro do tipo para um
escopo externo. Usaremos essa técnica posteriormente ao definir componentes para
exportar as propriedades internas ou os IDs do elemento para o nível raiz. Um alias
de propriedade não precisa de um tipo, ele usa o tipo da propriedade ou objeto
referenciado.

6. A propriedade text depende dos tempos de propriedade customizada times do tipo


int. O valor baseado em int é convertido automaticamente em um tipo string. A
expressão em si é outro exemplo de vinculação e resultados no texto que está sendo
atualizado sempre que a propriedade times for alterada.

7. Algumas propriedades são propriedades agrupadas. Esse recurso é usado quando uma
propriedade é mais estruturada e as propriedades relacionadas devem ser agrupadas.
Outra maneira de escrever propriedades agrupadas é font { family: "Ubuntu";
pixelSize: 24 }.

8. Algumas propriedades estão anexadas ao próprio elemento. Isso é feito para


elementos relevantes globais que aparecem apenas uma vez no aplicativo (por
exemplo, entrada de teclado). A escrita é <Element>.<property>: <value>.

9. Para cada propriedade você pode fornecer um manipulador de sinal. Esse


manipulador é chamado depois que a propriedade é alterada. Por exemplo, aqui
queremos ser notificados sempre que a altura for alterada e usar o console integrado
para registrar uma mensagem no sistema.

Atenção

Um ID de elemento só deve ser usado para referenciar elementos dentro de seu


documento (por exemplo, o arquivo atual). O QML fornece um mecanismo chamado
escopo dinâmico, em que documentos carregados posteriormente substituem os IDs dos
elementos dos documentos carregados anteriormente. Isso possibilita fazer referência a
IDs de elementos de documentos carregados anteriormente, se eles ainda não foram

39
sobrescritos. É como criar variáveis globais. Infelizmente isso freqüentemente leva a um
código realmente ruim na prática, onde o programa depende da ordem de execução.
Infelizmente isso não pode ser desativado. Só use isso com cuidado ou, melhor ainda,
não use esse mecanismo. É melhor exportar o elemento que você deseja fornecer para o
mundo externo usando as propriedades no elemento-raiz do documento.

4.1.2. Scripting

QML e JavaScript (também conhecidos como ECMAScript) são melhores amigos. No


capítulo JavaScript vamos entrar em mais detalhes sobre esta simbiose. Atualmente,
queremos apenas conscientizá-lo sobre esse relacionamento.
Text {
id: label

x: 24; y: 24

// custom counter property for space presses


property int spacePresses: 0

text: "Space pressed: " + spacePresses + " times"

// (1) handler for text changes


onTextChanged: console.log("text changed to:", text)

// need focus to receive key events


focus: true

// (2) handler with some JS


Keys.onSpacePressed: {
increment()
}

// clear the text on escape


Keys.onEscapePressed: {
label.text = ''
}

// (3) a JS function
function increment() {
spacePresses = spacePresses + 1
}
}

1. O texto alterado manipulador onTextChanged imprime o texto atual toda vez que o
texto é alterado devido a uma tecla de barra de espaço pressionada
2. Quando o elemento de texto recebe a tecla de barra espaço (porque o usuário
pressionou a barra de espaço no teclado), chamamos um increment() função
JavaScript.
3. Definição de uma função JavaScript na forma de função function <name>
(<parameters>) { ... }, que incrementa nosso spacePressed. Toda vez que
spacePressed é incrementado, as propriedades ligadas também serão atualizadas.

40
Note

A diferença entre o QML : (ligação) e o JavaScript = (assignment) é esse a ligação é um


contrato e se mantém ao longo da vida útil da ligação, considerando que o JavaScript
assignment (=) é uma atribuição de valor único. A vida de uma ligação termina, quando
uma ligação é definida para a propriedade ou mesmo quando um valor JavaScript é
atribuído é à propriedade. Por exemplo, um manipulador de chaves definindo a
propriedade text para uma string vazia destruiria nossa exibição de incremento:
Keys.onEscapePressed: {
label.text = ''
}

Depois de pressionar a tecla de escape, pressionar a barra de espaço não atualizará mais
a tela, pois a ligação anterior da propriedade de text (text: “Space pressed: ” +
spacePresses + ” times”) foi destruída.

Quando você tem estratégias conflitantes para alterar uma propriedade, como neste caso
(texto atualizado por uma alteração em um incremento de propriedade por meio de uma
ligação e texto limpo por uma atribuição de JavaScript), não é possível usar associações.
Você precisa usar a atribuição nos dois caminhos de mudança de propriedade, pois a
ligação será destruída pela atribuição (contrato quebrado!).

4.2. Elementos básicos


Os elementos podem ser agrupados em elementos visuais e não visuais. Um elemento
visual (like the Rectangle) tem uma geometria e normalmente apresenta uma área na
tela.Um elemento não visual (como um Timer)fornece uma funcionalidade geral,
normalmente usada para manipular os elementos visuais.

Atualmente, vamos nos concentrar nos elementos visuais fundamentais, como Item,
Rectangle, Text, Image e MouseArea.

4.2.1. Elemento Item

Item é o elemento base de todos os elementos visuais, como todos os outros elementos
visuais herdam do Item. Ele não pinta nada por si só, mas define todas as propriedades
comuns em todos os elementos visuais:

Grupo Propriedades
x e y para definir a posição superior esquerda, width e height para a
Geometria expansão do elemento e também a ordem de empilhamento z para
levantar ou reduzir elementos de seus ordenamentos naturais
Manipulação de anchors (esquerda, direita, superior, inferior, centralizado vertical e
layout horizontal) para posicionar elementos em relação a outros elementos

41
layout com suas margins

propriedades anexadas Key e KeyNavigation para controlar a


Manipulação de
manipulação de chaves e a propriedade focus de entrada para ativar o
chaves
tratamento de chaves em primeiro lugar
scale e rotate transformação transform a lista de propriedades de
Transformação transformação genérica para transformação x,y,z e seu ponto
transformOrigin
opacity para controlar a transparência, visible para mostrar / ocultar
elementos, clip recortar para restringir as operações de pintura ao
Visual
limite do elemento e smooth para melhorar a qualidade da
renderização
states listam a propriedade com a lista de estados suportada e a
Definição de
state como também a lista de transitions para animar as mudanças
estado
de estado.

Para entender melhor as diferentes propriedades, tentaremos introduzi-las ao longo deste


capítulo no contexto do elemento apresentado. Lembre-se de que essas propriedades
fundamentais estão disponíveis em todos os elementos visuais e funcionam da mesma
forma nesses elementos.

Note

O elemento Item geralmente é usado como um contêiner para outros elementos,


semelhante ao elemento div em HTML.

4.2.2. Elemento Rectangle

O Rectangle estende o Item e adiciona uma cor de preenchimento a ele. Além disso, ele
suporta bordas definidas por border.color e border.width. Para criar retângulos
arredondados, você pode usar a propriedade radius.

Note

Os valores de cores válidos são os mesmos nomes de cores SVG (Veja


http://www.w3.org/TR/css3-color/#svg-color). Você pode fornecer cores em QML de
maneiras diferentes, mas a maneira mais comum é uma sequência RGB (‘#FF4444’) ou
como um nome de cor (por exemplo, "branco").

42
Além de uma cor de preenchimento e uma borda, o retângulo também suporta gradientes
personalizados.

Um gradiente é definido por uma série de paradas de gradiente. Cada parada tem uma
posição e uma cor. A posição marca a posição no eixo (0 = top, 1 = bottom). A cor do
GradientStop marca a cor nessa posição.

Note

Um retângulo sem width/height não será visível. Isso acontece frequentemente quando
você tem vários retângulos de largura (altura) dependendo um do outro e algo dará
errado em sua composição lógica. Então cuidado!

Note

Não é possível criar um gradiente inclinado. Para isso, é melhor usar imagens
predefinidas. Uma possibilidade seria apenas girar o retângulo com o gradiente, mas
esteja ciente de que a geometria de um retângulo girado não mudará e, portanto, levará a
confusão, já que a geometria do elemento não é a mesma que a área visível. Da
perspectiva dos autores, é realmente melhor usar imagens de gradiente projetadas nesse
caso.

4.2.3. Elemento Text

Para exibir texto, você pode usar o elemento Text. Sua propriedade mais notável é a
propriedade text do tipo string. O elemento calcula sua largura e altura iniciais com base
no texto e na fonte usados. A fonte pode ser influenciada usando o grupo de propriedades
da fonte (ex: font.family, font.pixelSize, ...). Para alterar a cor do texto, basta usar a
propriedade color.
Text {
text: "The quick brown fox"
color: "#303030"
font.family: "Ubuntu"
font.pixelSize: 28
}

43
O texto pode ser alinhado a cada lado e ao centro usando as propriedades
horizontalAlignment e verticalAlignment. Para aprimorar ainda mais a renderização
de texto, você pode usar a propriedade style e styleColor, que permite renderizar o texto
no modo de contorno, relevo e profundo. Para textos mais longos, você geralmente quer
definir uma posição de break como ... Um texto muito, isso pode ser conseguido usando a
propriedade elide. A propriedade elide permite que você defina a posição elide para a
esquerda, direita ou meio do texto.Caso você não queira que o "..." do modo elide apareça,
mas ainda deseja ver o texto completo, também é possível envolver o texto usando a
propriedade wrapMode (funciona somente quando a largura é explicitamente definida):
Text {
width: 40; height: 120
text: 'A very long text'
// '...' shall appear in the middle
elide: Text.ElideMiddle
// red sunken text styling
style: Text.Sunken
styleColor: '#FF4444'
// align text to the top
verticalAlignment: Text.AlignTop
// only sensible when no elide mode
// wrapMode: Text.WordWrap
}

O Elemento Text exibe apenas o texto fornecido. Não renderiza qualquer decoração de
background. Além do texto renderizado, o elemento Text é transparente. Faz parte do seu
design geral fornecer um fundo sensato ao elemento de texto.

Note

Be aware a Text initial width (height) is depending on the text string and on the font set.
A Text element with no width set and no text will not be visible, as the initial width will
be 0.

Note

Muitas vezes, quando você deseja criar elementos Text de layout, você precisa
diferenciar entre alinhar o texto dentro da caixa de limite do elemento Text ou alinhar a
própria caixa de limite do elemento. No primeiro, você deseja usar as propriedades
horizontalAlignment verticalAlignment e, no último caso, você deseja manipular a

44
geometria do elemento ou usar âncoras.

4.2.4. Elemento Image

O elemento Image é capaz de exibir imagens em vários formatos (ex: PNG, JPG, GIF,
BMP, WEBP). Para obter uma lista completa dos formatos de imagem suportados,
consulte a documentação do Qt. Além da propriedade sourcepara fornecer o URL da
imagem, ele contém um fillMode que controla o comportamento de redimensionamento.
Image {
x: 12; y: 12
// width: 72
// height: 72
source: "assets/triangle_red.png"
}
Image {
x: 12+64+12; y: 12
// width: 72
height: 72/2
source: "assets/triangle_red.png"
fillMode: Image.PreserveAspectCrop
clip: true
}

Note

Um URL pode ser um caminho local com barras ( ”./images/home.png” ) ou um link da


web (ex: “http://example.org/home.png”).

Note

O elemento Image que usam PreserveAspectCrop também devem ativar o recorte para
evitar que os dados da imagem sejam renderizados fora dos limites da Image. Por
padrão, o recorte está desativado (clip : false). Você precisa ativar o recorte (clip :
true) tpara restringir a pintura ao retângulo delimitador de elementos. Isso pode ser
usado em qualquer elemento visual.

Dica

Usando o C ++, você pode criar seu próprio provedor de imagem usando o
QQmlImageProvider [http://doc.qt.io/qt-5//qqmlimageprovider.html]. Isso permite que você crie
imagens rapidamente e carregue imagens encadeadas.

45
4.2.5. Elemento MouseArea

Para interagir com esses elementos, você frequentemente usará MouseArea. É um item
invisível retangular onde você pode capturar eventos do mouse. Mouse area é
frequentemente usada junto com um item visível para executar comandos quando o
usuário interage com a parte visual.
Rectangle {
id: rect1
x: 12; y: 12
width: 76; height: 96
color: "lightsteelblue"
MouseArea {
id: area
width: parent.width
height: parent.height
onClicked: rect2.visible = !rect2.visible
}
}

Rectangle {
id: rect2
x: 112; y: 12
width: 76; height: 96
border.color: "lightsteelblue"
border.width: 4
radius: 8
}

Note

Este é um aspecto importante do Qt Quick, o manuseio de entrada é separado da


apresentação visual. Com isso, você pode mostrar ao usuário um elemento de interface,
mas a área de interação pode ser maior.

4.3. Componentes
Um componente é um elemento reutilizável e o QML fornece diferentes maneiras de criar
componentes.Um componente é um elemento reutilizável e o QML fornece diferentes
maneiras de criar componentes. (ex: Button.qml). Você pode usar o componente como
qualquer outro elemento do módulo QtQuick, no nosso caso você usaria isso no seu

46
código como Button { ... }.

Por exemplo, vamos criar um retângulo contendo um componente de texto e mouse area.
Isso se assemelha a um simples botão e não precisa ser mais complicado para nossos
propósitos.

A UI será semelhante a isso. À esquerda a UI no estado inicial, à direita depois que o


botão foi clicado.

Nossa tarefa agora é extrair o botão UI em um componente reutilizável. Para isso,


pensamos em uma possível API para o nosso botão. Você pode fazer isso imaginando
como alguém deveria usar o botão. Veja o que eu criei:
// minimal API for a button
Button {
text: "Click Me"
onClicked: { // do something }
}

Gostaria de definir o texto usando uma propriedade text e implementar meu próprio
manipulador de cliques. Também espero que o botão tenha um tamanho inicial razoável,
que eu possa sobrescrever (ex: with width: 240 for example).

Para conseguir isso, criamos um arquivo Button.qml e copiamos a UI do nosso botão.


Button.qml e copiamos a interface do nosso botão. Além disso, precisamos exportar as
propriedades que um usuário pode querer alterar no nível da raiz.
// Button.qml

import QtQuick 2.5

Rectangle {
id: root
// export button properties
property alias text: label.text
signal clicked

width: 116; height: 26


color: "lightsteelblue"
border.color: "slategrey"

Text {
id: label
anchors.centerIn: parent

47
text: "Start"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.clicked()
}
}
}

Nós exportamos o texto e clicamos o sinal no nível da raiz. Normalmente, nomeamos a


raiz do elemento raiz para facilitar a referência. Usamos o recurso de alias do QML, que
é uma maneira de exportar propriedades dentro de elementos QML aninhados para o nível
raiz e disponibilizá-lo para o mundo externo. É importante saber que apenas as
propriedades do nível raiz podem ser acessadas de fora desse arquivo por outros
componentes.

Para usar nosso novo elemento Button podemos simplesmente declará-lo em nosso
arquivo. Então, o exemplo anterior se tornará um pouco simplificado.
Button { // our Button component
id: button
x: 12; y: 12
text: "Start"
onClicked: {
status.text = "Button clicked!"
}
}

Text { // text changes when button was clicked


id: status
x: 12; y: 76
width: 116; height: 26
text: "waiting ..."
horizontalAlignment: Text.AlignHCenter
}

Agora você pode usar quantos botões quiser em sua UI usando apenas o Button { ... }.
Um botão real pode ser mais complexo, por exemplo, fornecendo feedback quando
clicado ou mostrando uma decoração mais agradável.

Note

Pessoalmente, você pode até dar um passo adiante e usar um item como um elemento
raiz. Isso impede que os usuários alterem a cor do nosso botão projetado e nos fornece
mais controle sobre a API exportada.Na prática, isso significa que precisaríamos
substituir o Rectangle da raiz por um Item e torná-lo um elemento aninhado no item
raiz.

Item {
id: root

48
width: 116; height: 26

property alias text: label.text


signal clicked

Rectangle {
anchors.fill parent
color: "lightsteelblue"
border.color: "slategrey"
}
...
}

Com essa técnica, é fácil criar uma série inteira de componentes reutilizáveis.

4.4. Transformações Simples


Uma transformação manipula a geometria de um objeto. Itens QML podem, em geral, ser
traduzidos, rotacionados e escalados. Existe uma forma simples dessas operações e uma
maneira mais avançada.

Vamos começar com as transformações simples. Aqui é a nossa cena como nosso ponto de
partida.

Uma tradução simples é feita através da alteração da posição x,y. Uma rotação é feita
usando a propriedade rotation. O valor é fornecido em graus (0 .. 360). Um
dimensionamento é feito usando a propriedade scale e um valor <1 significa que o
elemento é reduzido e >1 significa que o elemento é dimensionado para cima. A rotação e
o dimensionamento não alteram sua geometria. Os itens x,y e width/height não
mudaram. Apenas as instruções de pintura são transformadas.

Antes de mostrarmos o exemplo, gostaria de apresentar um pequeno ajudante: O elemento


ClickableImage. O ClickableImage é apenas uma imagem com mouse area. Isso traz
uma regra útil - se você copiou um trecho de código três vezes, extraia-o em um
componente.
// ClickableImage.qml

// Simple image which can be clicked

import QtQuick 2.5

Image {
id: root
signal clicked

MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}

49
Usamos nossa imagem clicável para apresentar três objetos (box, circle, triangle). Cada
objeto realiza uma transformação simples quando clicado. Clicar no plano de fundo
redefinirá a cena.
// transformation.qml

import QtQuick 2.5

Item {
// set width based on given background
width: bg.width
height: bg.height

Image { // nice background image


id: bg
source: "assets/background.png"
}

MouseArea {
id: backgroundClicker
// needs to be before the images as order matters
// otherwise this mousearea would be before the other elements
// and consume the mouse events
anchors.fill: parent
onClicked: {
// reset our little scene
circle.x = 84
box.rotation = 0
triangle.rotation = 0
triangle.scale = 1.0
}
}

ClickableImage {
id: circle
x: 84; y: 68
source: "assets/circle_blue.png"
antialiasing: true
onClicked: {
// increase the x-position on click
x += 20
}
}

ClickableImage {

50
id: box
x: 164; y: 68
source: "assets/box_green.png"
antialiasing: true
onClicked: {
// increase the rotation on click
rotation += 15
}
}

ClickableImage {
id: triangle
x: 248; y: 68
source: "assets/triangle_red.png"
antialiasing: true
onClicked: {
// several transformations
rotation += 15
scale += 0.05
}
}

function _test_transformed() {
circle.x += 20
box.rotation = 15
triangle.scale = 1.2
triangle.rotation = -15
}

function _test_overlap() {
circle.x += 40
box.rotation = 15
triangle.scale = 2.0
triangle.rotation = 45
}

O círculo incrementa a posição x em cada clique e a caixa gira em cada clique. O triângulo
girará e redimensionará a imagem em cada clique para demonstrar uma transformação
combinada. Para a operação de dimensionamento e rotação, definimos o antialiasing:
true para ativar o anti-aliasing, que é desativado (mesmo que a propriedade de recorte
clip) fpor motivos de desempenho. No seu próprio trabalho, quando você vê algumas
bordas rasterizadas em seus gráficos, provavelmente deve alternar suavemente.

51
Note

Para obter melhor qualidade visual ao dimensionar imagens, recomenda-se reduzir as


imagens em vez de subir. Dimensionar uma imagem com um fator de escala maior
resultará em artefatos de dimensionamento (imagem borrada). Ao dimensionar uma
imagem, você deve considerar o uso do antialiasing : true para permitir o uso de
um filtro de qualidade mais alta.

O background MouseArea abrange todo o plano de fundo e redefine os valores do objeto.

Note

Elementos que aparecem anteriormente no código têm uma ordem de empilhamento


menor (chamada ordem z). Se você clicar o suficiente no circle verá que ele se move
abaixo da box. A ordem z também pode ser manipulada pela z-property de um item.

Isso ocorre porque a box aparece mais tarde no código. O mesmo se aplica também às
mouse areas. A mouse area mais tarde no código irá se sobrepor (e assim pegar os
eventos de mouse) de uma área de mouse anteriormente no código.

Por favor, lembre-se: A ordem dos elementos no documento é importante.

4.5. Posicionamento dos Elementos


Há vários elementos QML usados para posicionar itens. Estes são chamados de
posicionadores e os seguintes são fornecidos no módulo QtQuick Row, Column, Grid e
Flow. Eles podem ser vistos mostrando o mesmo conteúdo na ilustração abaixo.

Note

Antes de entrarmos em detalhes, deixe-me apresentar alguns elementos auxiliares. Os


quadrados vermelhos, azuis, verdes, mais claros e mais escuros. Cada um desses
componentes contém um retângulo colorido 48x48 pixels. Como referência ao código-
fonte para o RedSquare:

52
// RedSquare.qml

import QtQuick 2.5

Rectangle {
width: 48
height: 48
color: "#ea7025"
border.color: Qt.lighter(color)
}

Observe o uso do Qt.lighter(color) para produzir uma cor de borda mais clara com
base na cor de preenchimento. Nós usaremos estes helpers nos próximos exemplos para
tornar o código-fonte mais compacto e esperançosamente legível. Por favor, lembre-se,
cada retângulo é inicial 48x48 pixels.

O elemento Column organiza itens filhos em uma coluna, empilhando-os uns sobre os
outros. A propriedade de spacing pode ser usada para distanciar cada um dos elementos
filho um do outro.

// column.qml

import QtQuick 2.5

DarkSquare {
id: root
width: 120
height: 240

Column {
id: row
anchors.centerIn: parent
spacing: 8
RedSquare { }
GreenSquare { width: 96 }
BlueSquare { }
}
}

// M1<<

53
O elemento Row coloca seus itens filhos um ao lado do outro, da esquerda para a direita,
ou da direita para a esquerda, dependendo da propriedade layoutDirection. Novamente,
o spacing é usado para separar itens filhos.

// row.qml

import QtQuick 2.5

BrightSquare {
id: root
width: 400; height: 120

Row {
id: row
anchors.centerIn: parent
spacing: 20
BlueSquare { }
GreenSquare { }
RedSquare { }
}
}

O elemento Grid organiza seus filhos em uma grade, definindo as propriedades rows e
columns, o número ou linhas ou colunas podem ser restritas. Por não definir nenhum
deles, o outro é calculado a partir do número de itens filhos. Por exemplo, definir linhas
como 3 e adicionar 6 itens filhos resultará em 2 colunas. A propriedade flow e
layoutDirection são usados para controlar a ordem em que os itens são adicionados à
grade, enquanto spacing controla a quantidade de espaço que separa os itens filhos.

// grid.qml

import QtQuick 2.5

BrightSquare {
id: root
width: 160
height: 160

54
Grid {
id: grid
rows: 2
columns: 2
anchors.centerIn: parent
spacing: 8
RedSquare { }
RedSquare { }
RedSquare { }
RedSquare { }
}

O posicionador final é o Flow. Adiciona seus itens filhos em um fluxo (flow). A direção do
fluxo é controlada usando flow e layoutDirection. Pode correr de lado ou de cima para
baixo. Também pode ser executado da esquerda para a direita ou na direção oposta.À
medida que os itens são adicionados no fluxo, eles são agrupados para formar novas linhas
(rows) ou colunas (columns), conforme necessário. Para que um fluxo funcione, ele deve
ter largura ou altura. Isso pode ser definido diretamente ou por meio de layouts de âncora.

// flow.qml

import QtQuick 2.5

BrightSquare {
id: root
width: 160
height: 160

Flow {
anchors.fill: parent
anchors.margins: 20
spacing: 20
RedSquare { }
BlueSquare { }
GreenSquare { }
}
}

Um elemento freqüentemente usado com posicionadores é o Repeater. Ele funciona como


um loop e itera sobre um modelo. No caso mais simples, um modelo é apenas um valor
que fornece a quantidade de loops.

55
// repeater.qml

import QtQuick 2.5

DarkSquare {
id: root
width: 252
height: 252
property variant colorArray: ["#00bde3", "#67c111", "#ea7025"]

Grid{
anchors.fill: parent
anchors.margins: 8
spacing: 4
Repeater {
model: 16
Rectangle {
width: 56; height: 56
property int colorIndex: Math.floor(Math.random()*3)
color: root.colorArray[colorIndex]
border.color: Qt.lighter(color)
Text {
anchors.centerIn: parent
color: "#f0f0f0"
text: "Cell " + index
}
}
}
}
}

Neste exemplo de repetidor, usamos alguma nova magia. Nós definimos nossa própria
propriedade de cor, que usamos como uma matriz de cores. O repetidor cria uma série de
retângulos (16, conforme definido pelo modelo). Para cada laço, ele cria o retângulo,
conforme definido pelo filho do repetidor. No retângulo nós escolhemos a cor usando
funções matemáticas JS Math.floor(Math.random()*3). Isso nos dá um número aleatório
no intervalo de 0..2, que usamos para selecionar a cor do nosso array de cores. Como
observado anteriormente, o JavaScript é uma parte essencial do Qt Quick, assim, as
bibliotecas padrão estão disponíveis para nós.

56
Um repeater injeta a propriedade indexno repetidor. Ele contém o índice de loop atual.
(0,1,..15). Podemos usar isso para tomar nossas próprias decisões com base no índice ou,
no nosso caso, visualizar o índice atual com o elemento Text.

Note

Um tratamento mais avançado de modelos maiores e visualizações cinéticas com


delegates dinâmicos é abordado em um capítulo próprio de visão de modelo. Os
repetidores são melhor usados quando há uma pequena quantidade de dados estáticos a
serem apresentados.

4.6. Itens de Layout


Todo

precisamos remover todos os usos de âncoras anteriormente?

O QML fornece uma maneira flexível de layout de itens usando âncoras. O conceito de
ancoragem faz parte das propriedades fundamentais do Item e está disponível para todos
os elementos QML visuais.Uma âncora age como um contrato e é mais forte que as
mudanças de geometria concorrentes. Âncoras são expressões de relatividade, você
sempre precisa de um elemento relacionado para ancorar.

Um elemento tem 6 linhas principais de ancoragem (top, bottom, left, right,


horizontalCenter, verticalCenter). Adicional, há a âncora da linha de base para o texto nos
elementos Texto. Cada linha de âncora vem com um deslocamento. No caso de top,
bottom, left e right eles são chamados de margens. Para horizontalCenter, verticalCenter e
baseline, eles são chamados de offsets.

57
1. Um elemento preenche um elemento parent
GreenSquare {
BlueSquare {
width: 12
anchors.fill: parent
anchors.margins: 8
text: '(1)'
}
}

2. Um elemento é deixado alinhado ao parent


GreenSquare {
BlueSquare {
width: 48
y: 8
anchors.left: parent.left
anchors.leftMargin: 8
text: '(2)'
}
}

3. Um elemento do lado esquerdo está alinhado ao lado direito dos parent


GreenSquare {
BlueSquare {
width: 48
anchors.left: parent.right
text: '(3)'
}
}

4. Elementos alinhados ao centro. Blue1 é horizontalmente centrado no parent. Blue2


também é horizontalmente centrado, mas em Blue1 e a parte superior está alinhada
com a linha inferior Blue1.
GreenSquare {
BlueSquare {
id: blue1
width: 48; height: 24
y: 8

58
anchors.horizontalCenter: parent.horizontalCenter
}
BlueSquare {
id: blue2
width: 72; height: 24
anchors.top: blue1.bottom
anchors.topMargin: 4
anchors.horizontalCenter: blue1.horizontalCenter
text: '(4)'
}
}

5. Um elemento é centrado em um elemento parent


GreenSquare {
BlueSquare {
width: 48
anchors.centerIn: parent
text: '(5)'
}
}

6. Um elemento é centralizado com um offset à esquerda em um elemento parent


usando linhas centrais horizontais e verticais
GreenSquare {
BlueSquare {
width: 48
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: -12
anchors.verticalCenter: parent.verticalCenter
text: '(6)'
}
}

Note

Nossos quadrados foram aprimorados para permitir o arrastamento. Experimente o


exemplo e arraste alguns quadrados. Você verá que (1) não pode ser arrastado, pois está
ancorado em todos os lados, com certeza você pode arrastar o parent de (1), pois ele não
está ancorado. (2) pode ser arrastado verticalmente, pois apenas o lado esquerdo está
ancorado. Similar se aplica a (3). (4) só pode ser arrastado verticalmente, pois ambos os
quadrados são centralizados horizontalmente. (5) é centralizado no parent e, como tal,
não pode ser arrastado, semelhante se aplica a (7). Arrastar um elemento significa mudar
sua posição x,y. As anchoring is stronger than geometry changes such as x,y, Arrastar é
restrito pelas linhas ancoradas. Nós veremos esse efeito mais tarde quando discutirmos
animações.

4.7. Elementos de Entrada


Nós já usamos MouseArea como um elemento de entrada do mouse. Em seguida, vamos

59
nos concentrar na entrada do teclado. Começamos com os elementos de edição de texto:
TextInput e TextEdit.

4.7.1. TextInput

O TextInput permite que o usuário insira uma linha de texto. O elemento suporta
restrições de entrada, como validator, inputMask, e echoMode.
// textinput.qml

import QtQuick 2.5

Rectangle {
width: 200
height: 80
color: "linen"

TextInput {
id: input1
x: 8; y: 8
width: 96; height: 20
focus: true
text: "Text Input 1"
}

TextInput {
id: input2
x: 8; y: 36
width: 96; height: 20
text: "Text Input 2"
}
}

O usuário pode clicar dentro de um TextInput para alterar o foco. Para suportar a
mudança do foco pelo teclado, podemos usar a propriedade anexada KeyNavigation.
// textinput2.qml

import QtQuick 2.5

Rectangle {
width: 200
height: 80
color: "linen"

TextInput {
id: input1
x: 8; y: 8
width: 96; height: 20
focus: true

60
text: "Text Input 1"
KeyNavigation.tab: input2
}

TextInput {
id: input2
x: 8; y: 36
width: 96; height: 20
text: "Text Input 2"
KeyNavigation.tab: input1
}
}

A propriedade anexada KeyNavigation oferece suporte a uma predefinição de teclas de


navegação na qual um ID de elemento é ligado para alternar o foco na pressionamento de
tecla especificada.

Um elemento de entrada de texto vem sem apresentação visual, além de um cursor


intermitente e o texto digitado. Para que o usuário possa reconhecer o elemento como um
elemento de entrada, ele precisa de alguma decoração visual, por exemplo, um retângulo
simples. Ao colocar o TextInput dentro de um elemento, você precisa exportar as
principais propriedades que deseja que outras pessoas possam acessar.

Nós movemos este pedaço de código para o nosso próprio componente chamado
TLineEditV1 para reutilização.

// TLineEditV1.qml

import QtQuick 2.5

Rectangle {
width: 96; height: input.height + 8
color: "lightsteelblue"
border.color: "gray"

property alias text: input.text


property alias input: input

TextInput {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}
}

Note

Se você quiser exportar o TextInput completamente, poderá exportar o elemento usando


a entrada do property alias input: input. O primeiro input é o nome da
propriedade, onde a 2nd entrada é o id do elemento.

61
Nós reescrevemos nosso exemplo KeyNavigation com o novo componente TLineEditV1.
Rectangle {
...
TLineEditV1 {
id: input1
...
}
TLineEditV1 {
id: input2
...
}
}

E tente a tecla tab para navegação. Você perceberá que o foco não muda para input2. O
simples uso do focus:true não é suficiente. O problema surge, que o foco foi transferido
para o elemento input2 o item de nível superior dentro do TlineEditV1 (nosso Rectangle)
recebeu foco e não redirecionou o foco para o TextInput. Para evitar isso, o QML oferece
o FocusScope.

4.7.2. FocusScope

Um focus scope declara que o último elemento filho com focus:true recebe o foco se o
focus scope receber o foco. Por isso, encaminha o foco para o último elemento de
solicitação de foco. Vamos criar uma segunda versão do nosso componente TLineEdit
chamado TLineEditV2 usando o focus scope como elemento raiz.
// TLineEditV2.qml

import QtQuick 2.5

FocusScope {
width: 96; height: input.height + 8
Rectangle {
anchors.fill: parent
color: "lightsteelblue"
border.color: "gray"

property alias text: input.text


property alias input: input

TextInput {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}

62
}

Nosso exemplo será agora assim:


Rectangle {
...
TLineEditV2 {
id: input1
...
}
TLineEditV2 {
id: input2
...
}
}

Pressionando a tecla tab agora comuta o foco entre os 2 componentes e o elemento filho
correto dentro do componente está focado.

4.7.3. TextEdit

O TextEdit é muito semelhante ao TextInput e suporta um campo de edição de texto


com várias linhas. Ele não tem as propriedades de restrição de texto, pois isso depende da
consulta do tamanho pintado do texto (paintedHeight, paintedWidth). Também criamos
seu próprio componente chamado TTextEdit para fornecer uma edição background e use
o focus scope para melhor encaminhamento de foco.
// TTextEdit.qml

import QtQuick 2.5

FocusScope {
width: 96; height: 96
Rectangle {
anchors.fill: parent
color: "lightsteelblue"
border.color: "gray"

property alias text: input.text


property alias input: input

TextEdit {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}
}

Você pode usá-lo como o componente TLineEdit


// textedit.qml

63
import QtQuick 2.5

Rectangle {
width: 136
height: 120
color: "linen"

TTextEdit {
id: input
x: 8; y: 8
width: 120; height: 104
focus: true
text: "Text Edit"
}
}

4.7.4. Elemento Keys

A propriedade anexada Keys permite executar código com base em determinados


pressionamentos de tecla. Por exemplo, para mover um quadrado ao redor e dimensionar,
podemos enganchar nas teclas para cima, baixo, esquerda e direita para traduzir o
elemento e a tecla mais, menos para dimensionar o elemento.
// keys.qml

import QtQuick 2.5

DarkSquare {
width: 400; height: 200

GreenSquare {
id: square
x: 8; y: 8
}
focus: true
Keys.onLeftPressed: square.x -= 8
Keys.onRightPressed: square.x += 8
Keys.onUpPressed: square.y -= 8
Keys.onDownPressed: square.y += 8
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Plus:
square.scale += 0.2
break;
case Qt.Key_Minus:
square.scale -= 0.2
break;
}

64
}
}

4.8. Técnicas Avançadas


Todo

Para ser escrito

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

65
Qt5 Cadaques Book » previous | next

5. Elementos Fluidos
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:59 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Até agora, nós vimos principalmente elementos gráficos simples e como organizá-los e
manipulá-los. Este capítulo trata de como controlar essas alterações de forma que o valor
de uma propriedade não apenas mude instantaneamente, mas também como o valor muda
com o tempo: uma animação. Essa tecnologia é uma das principais bases para as modernas
interfaces do usuário e pode ser estendida com um sistema para descrever sua interface de
usuário usando estados e transições. Cada estado define um conjunto de alterações de
propriedade e pode ser combinado com animações sobre alterações de estado, chamadas
transições.

5.1. Animações
Animações são aplicadas a alterações de propriedade. Uma animação define a curva de
interpolação quando o valor da propriedade é alterado para criar transições suaves de um
valor para outro. Uma animação é definida por uma série de propriedades de destino a
serem animadas, uma curva de atenuação para a curva de interpolação e, na maioria dos
casos, uma duração, que define o tempo para a alteração da propriedade. Todas as
animações no Qt Quick são controladas pelo mesmo temporizador e, portanto, são
sincronizadas. Isso melhora o desempenho e a qualidade visual das animações.

Note

As animações controlam como a propriedade é alterada, ou seja, a interpolação de


valores. Este é um conceito fundamental. O QML é baseado em elementos, propriedades
e scripts. Cada elemento fornece dezenas de propriedades, cada propriedade está
esperando para ser animada por você. Durante o livro, você verá que este é um campo de
jogo espetacular. Você vai se surpreender vendo algumas animações e apenas admirando
a beleza delas e com certeza também aguçar seu gênio criativo. Por favor, lembre-se
então: As animações controlam as alterações de propriedade e cada elemento tem
dezenas de propriedades à sua disposição.

Desbloqueie o poder!

66
// animation.qml

import QtQuick 2.5

Image {
id: root
source: "assets/background.png"

property int padding: 40


property int duration: 400
property bool running: false

Image {
id: box
x: root.padding;
y: (root.height-height)/2
source: "assets/box_green.png"

NumberAnimation on x {
to: root.width - box.width - root.padding
duration: root.duration
running: root.running
}
RotationAnimation on rotation {
to: 360
duration: root.duration
running: root.running
}
}

MouseArea {
anchors.fill: parent
onClicked: root.running = true
}

O exemplo acima mostra uma animação simples aplicada na propriedade x e rotation de


rotação. Cada animação tem uma duração de 4000 milissegundos (ms) e faz um loop para
sempre.A animação em x move gradualmente a coordenada x do objeto para 240px. A
animação na rotação vai do ângulo atual para 360 graus. Ambas as animações são
executadas em paralelo e são iniciadas assim que a UI é carregada.

Agora você pode brincar com a animação alterando a propriedade to e duration adicionar
outra animação, por exemplo, na opacity ou até mesmo na scale. Combinando estes,
pode parecer que o objeto está desaparecendo no espaço profundo. Experimente!

67
5.1.1. Elementos de animação

Existem vários tipos de elementos de animação, cada um otimizado para um caso de uso
específico. Aqui está uma lista das animações mais proeminentes:

PropertyAnimation - Anima alterações nos valores da propriedade


NumberAnimation - Anima alterações nos valores do tipo q-real
ColorAnimation - Anima alterações nos valores das cores
RotationAnimation - Anima alterações nos valores de rotação

Além desses elementos de animação básicos e amplamente utilizados, o Qt Quick também


oferece animações mais especializadas para casos de uso específicos:

PauseAnimation - Fornece uma pausa para uma animação


SequentialAnimation - Permite que as animações sejam executadas
sequencialmente
ParallelAnimation - Permite que as animações sejam executadas em paralelo
AnchorAnimation - Anima alterações nos valores âncora
ParentAnimation - Anima as alterações nos valores parent
SmoothedAnimation - Permite que uma propriedade rastreie um valor sem problemas
SpringAnimation - Permite que uma propriedade rastreie um valor em um
movimento semelhante a uma mola
PathAnimation - Anima um item ao longo de um caminho
Vector3dAnimation - Anima alterações nos valores QVector3d

Nós aprenderemos mais tarde como criar uma seqüência de animações. Ao trabalhar em
animações mais complexas, surge a necessidade de alterar uma propriedade ou executar
um script durante uma animação em andamento. Para isso, o Qt Quick oferece os
elementos de ação, que podem ser usados em todos os lugares onde os outros elementos
de animação podem ser usados:

PropertyAction - Especifica alterações de propriedade imediatas durante a animação


ScriptAction - Define scripts para serem executados durante uma animação

Os principais tipos de animação serão discutidos durante este capítulo usando pequenos
exemplos.

5.1.2. Aplicando Animações

A animação pode ser aplicada de várias maneiras:

Animation on property - é executado automaticamente após o elemento estar


totalmente carregado
Behavior on property - é executado automaticamente quando o valor da propriedade
é alterado
Standalone Animation - é executado quando a animação é iniciada explicitamente
usando start()ou running execução está definida como verdadeira (ex: por uma

68
ligação de propriedade)

Mais tarde, veremos também como as animações podem ser usadas dentro de transições
de estado.

Extenendo ClickableImage Versão 2

Para demonstrar o uso de animações, reutilizamos nosso componente ClickableImage


de um capítulo anterior e o estendemos com um elemento de texto.
// ClickableImageV2.qml
// Simple image which can be clicked

import QtQuick 2.5

Item {
id: root
width: container.childrenRect.width
height: container.childrenRect.height
property alias text: label.text
property alias source: image.source
signal clicked

Column {
id: container
Image {
id: image
}
Text {
id: label
width: image.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#ececec"
}
}

MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}

Para organizar o elemento abaixo da imagem, usamos um posicionador de Column e


calculamos a largura e a altura com base na propriedade childrenRect da coluna. Nós
expusemos duas propriedades: text e a source da imagem como também o sinal
clicked. Também queríamos que o texto fosse tão amplo quanto a imagem e deveria
ser quebrado. Nós alcançamos o último usando a propriedade wrapMode dos elementos
Text.

Note

Devido à inversão da dependência de geometria (a geometria parent depende da

69
geometria do filho), não podemos definir uma largura / altura no ClickableImageV2,
pois isso quebrará a nossa ligação largura / altura. Esta é uma limitação do nosso
design interno e, como designer de componentes, você deve estar ciente disso.
Normalmente, você deve preferir que a geometria do filho dependa da geometria do
parent.

Os objetos ascendentes.

Os três objetos estão todos na mesma posição y (y = 200). Eles precisam viajar todos para
y = 40. Cada um deles usando um método diferente com diferentes efeitos colaterais e
recursos.
ClickableImageV2 {
id: greenBox
x: 40; y: root.height-height
source: "assets/box_green.png"
text: "animation on property"
NumberAnimation on y {
to: 40; duration: 4000
}
}

1st objeto

O 1st objeto viaja usando a estratégia Animation on <property>. A animação começa


imediatamente. Quando um objeto é clicado, sua posição y é redefinida para a posição
inicial, isso se aplica a todos os objetos. No 1st objeto, a reinicialização não terá nenhum
efeito enquanto a animação estiver sendo executada. É até perturbador, pois a posição y é
definida por uma fração de segundo para um novo valor antes que a animação comece.
Tais mudanças de propriedades concorrentes devem ser evitadas.
ClickableImageV2 {

70
id: blueBox
x: (root.width-width)/2; y: root.height-height
source: "assets/box_blue.png"
text: "behavior on property"
Behavior on y {
NumberAnimation { duration: 4000 }
}

onClicked: y = 40
// random y on each click
// onClicked: y = 40+Math.random()*(205-40)
}

2nd objeto

O 2nd objeto viaja usando um behavior on animação. Esse comportamento informa a


propriedade, toda vez que o valor da propriedade é alterado, ele é alterado por meio dessa
animação. O comportamento pode ser desativado por enabled : false no elemento
Behavior. O objeto começará a viajar quando você clicar nele (a posição y será definida
como 40). Outro clique não tem influência, pois a posição já está definida. Você poderia
tentar usar um valor aleatório ex: 40+(Math.random()*(205-40)) para a posição y. Você
verá que o objeto sempre será animado para a nova posição e adaptará sua velocidade para
corresponder aos 4 segundos até o destino definido pela duração das animações.
ClickableImageV2 {
id: redBox
x: root.width-width-40; y: root.height-height
source: "assets/box_red.png"
onClicked: anim.start()
// onClicked: anim.restart()

text: "standalone animation"

NumberAnimation {
id: anim
target: redBox
properties: "y"
to: 40
duration: 4000
}
}

3rd objeto

O 3rd objeto usa uma standalone animation. A animação é definida como seu próprio
elemento e pode estar em todo lugar no documento.O clique iniciará a animação usando a
função de animações start(). Cada animação tem uma função start(), stop(), resume(),
restart(). A animação em si contém muito mais informações do que os outros tipos de
animação anteriores. Precisamos definir o destino e as propriedades para declarar o
elemento de destino a ser animado e quais propriedades queremos animar. Precisamos
definir um valor to e, neste caso, definimos também um valor from para permitir um
reinício da animação.

71
Um clique no fundo irá redefinir todos os objetos para sua posição inicial. O 1st O objeto
não pode ser reiniciado, exceto pelo reinício do programa que aciona o carregamento do
elemento.

Note

Outra maneira de iniciar / parar uma animação é vincular uma propriedade à propriedade
de running de uma animação. Isso é útil quando a entrada do usuário está no controle de
propriedades:
NumberAnimation {
...
// animation runs when mouse is pressed
running: area.pressed
}
MouseArea {
id: area
}

5.1.3. Curvas Easing

A alteração de valor de uma propriedade pode ser controlada por uma animação. A
atenuação de atributos permite influenciar a curva de interpolação de uma alteração de
propriedade. Todas as animações que definimos agora usam uma interpolação linear
porque o tipo de atenuação inicial de uma animação é Easing.Linear. É melhor
visualizado com um pequeno gráfico, em que o eixo y é a propriedade a ser animada e o
eixo x é o tempo (duration). Uma interpolação linear traçaria uma linha reta a partir do
valor from no início da animação para o valor to no final da animação. Então, o tipo de
atenuação define a curva de mudança. Os tipos de atenuação são cuidadosamente
escolhidos para suportar um ajuste natural para um objeto em movimento, por exemplo,
quando uma página desliza para fora. Inicialmente, a página deve deslizar lentamente e,
em seguida, ganhar a velocidade para finalmente deslizar para fora em alta velocidade,

72
semelhante a virar a página de um livro.

Note

Animações não devem ser usadas em excesso. Como outros aspectos UI design também
animações devem ser projetados com cuidado e apoiar o UI fluir e não dominá-lo. O
olho é muito sensível a objetos em movimento e as animações podem facilmente distrair
o usuário.

No próximo exemplo, tentaremos algumas curvas de atenuação. Cada curva de atenuação


é exibida por uma imagem clicável e, quando clicada, define um novo tipo de atenuação
na animação square e, em seguida, acionar um restart() para executar a animação com
a nova curva.

O código deste exemplo foi um pouco mais complicado. Primeiro criamos uma grade de
EasingTypes e uma Box que é controlada pelos tipos de easing. Um tipo easing apenas
exibe a curva que a caixa deve usar para sua animação. Quando o usuário clica em uma
curva de atenuação, a caixa se move em uma direção de acordo com a curva de atenuação.
A animação em si é uma animação autônoma com o destino definido na caixa e
configurada para animação de propriedade x com duração de 2 segundos.

Note

O interior do EasingType processa a curva em tempo real e o leitor interessado pode


procurá-la no exemplo EasingCurves.

// EasingCurves.qml

import QtQuick 2.5


import QtQuick.Layouts 1.2

Rectangle {

73
id: root
width: childrenRect.width
height: childrenRect.height

color: '#4a4a4a'
gradient: Gradient {
GradientStop { position: 0.0; color: root.color }
GradientStop { position: 1.0; color: Qt.lighter(root.color, 1.2) }
}

ColumnLayout {

Grid {
spacing: 8
columns: 5
EasingType {
easingType: Easing.Linear
title: 'Linear'
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InExpo
title: "InExpo"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.OutExpo
title: "OutExpo"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutExpo
title: "InOutExpo"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutCubic
title: "InOutCubic"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.SineCurve
title: "SineCurve"
onClicked: {

74
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutCirc
title: "InOutCirc"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutElastic
title: "InOutElastic"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutBack
title: "InOutBack"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
EasingType {
easingType: Easing.InOutBounce
title: "InOutBounce"
onClicked: {
animation.easing.type = easingType
box.toggle = !box.toggle
}
}
}
Item {
height: 80
Layout.fillWidth: true
Box {
id: box
property bool toggle
x: toggle?20:root.width-width-20
anchors.verticalCenter: parent.verticalCenter
gradient: Gradient {
GradientStop { position: 0.0; color: "#2ed5fa" }
GradientStop { position: 1.0; color: "#2467ec" }
}
Behavior on x {
NumberAnimation {
id: animation
duration: 500
}
}
}
}
}
}

75
A você joga com isto, por favor observe a mudança de velocidade durante uma animação.
Algumas animações parecem mais naturais para o objeto e algumas se sentem irritantes.

Além da duration e easing.type você é capaz de afinar as animações. Por exemplo, o


general PropertyAnimation onde a maioria das animações herdadas de suporta
adicionalmente propriedades easing.amplitude, easing.overshoot e easing.period
que permite ajustar o comportamento de determinadas curvas de atenuação. Nem todas as
curvas de atenuação suportam esses parâmetros. Por favor consulte o easing table
[http://doc.qt.io/qt-5//qml-qtquick-propertyanimation.html#easing-prop] da documentação do
PropertyAnimation para verificar se um parâmetro de atenuação tem influência sobre
uma curva de atenuação.

Note

Escolher a animação certa para o elemento no contexto da interface do usuário é crucial


para o resultado. Lembre-se de que a animação deve suportar o fluxo da UI; para não
irritar o usuário.

5.1.4. Animações Agrupadas

Muitas vezes as animações serão mais complexas do que apenas animar uma propriedade.
Você pode querer executar várias animações ao mesmo tempo ou uma após a outra ou até
mesmo executar um script entre duas animações. Para isso, a animação agrupada oferece a
você uma possibilidade. Como o nome sugere, é possível agrupar animações. O
agrupamento pode ser feito de duas maneiras: paralela ou sequencial. Você pode usar o
elemento SequentialAnimation ou the ParallelAnimation,que funcionam como
contêineres de animação para outros elementos de animação. Estas animações agrupadas
são animações e podem ser usadas exatamente como tal.

Todas as animações secundárias diretas de uma animação paralela serão executadas em


paralelo, quando iniciadas. Isso permite que você anime diferentes propriedades ao mesmo
tempo.
// parallelanimation.qml
import QtQuick 2.5

76
BrightSquare {
id: root
width: 600
height: 400
property int duration: 3000
property Item ufo: ufo

Image {
anchors.fill: parent
source: "assets/ufo_background.png"
}

ClickableImageV3 {
id: ufo
x: 20; y: root.height-height
text: 'ufo'
source: "assets/ufo.png"
onClicked: anim.restart()
}

ParallelAnimation {
id: anim
NumberAnimation {
target: ufo
properties: "y"
to: 20
duration: root.duration
}
NumberAnimation {
target: ufo
properties: "x"
to: 160
duration: root.duration
}
}
}

Uma animação sequencial primeiro executará a primeira animação filha e continuará a


partir daí.

77
A animação agrupada também pode ser aninhada, por exemplo, uma animação sequencial
pode ter duas animações paralelas como animações secundárias e assim por diante.
Podemos visualizar isso com um exemplo de bola de futebol. A ideia é lançar uma bola da
esquerda para a direita e animar seu comportamento.

Para entender a animação, precisamos dissecá-la nas transformações integrais do objeto.


Precisamos nos lembrar de animações para fazer alterações na propriedade animada. Aqui
estão as diferentes transformações:

Um x-movimentação da esquerda para a direita (X1)


Uma movimentação de y de baixo para cima (Y1) seguido por uma movimentação de
cima para baixo (Y2) com alguns saltando
Uma rotação ao longo de 360 durante toda a duração da animação (ROT1)

Toda a duração da animação deve demorar três segundos.

78
Começamos com um item vazio como elemento raiz da largura de 480 e altura de 300.
import QtQuick 2.5

Item {
id: root
width: 480
height: 300
property int duration: 3000

...
}

Definimos nossa duração total de animação como referência para sincronizar melhor as
partes da animação.

O próximo passo seria adicionar ao background, que no nosso caso são 2 retângulos com
gradientes verdes e azuis.
Rectangle {
id: sky
width: parent.width
height: 200
gradient: Gradient {
GradientStop { position: 0.0; color: "#0080FF" }
GradientStop { position: 1.0; color: "#66CCFF" }
}
}
Rectangle {
id: ground
anchors.top: sky.bottom
anchors.bottom: root.bottom
width: parent.width
gradient: Gradient {
GradientStop { position: 0.0; color: "#00FF00" }
GradientStop { position: 1.0; color: "#00803F" }
}
}

79
O retângulo azul superior ocupa 200 pixels da altura e o inferior é ancorado ao topo no céu
e ao fundo no elemento-raiz.

Vamos trazer a bola de futebol para o verde. A bola é uma imagem, armazenada em
“assets/soccer_ball.png”. Para o começo, gostaríamos de posicioná-lo no canto inferior
esquerdo, perto da borda.
Image {
id: ball
x: 0; y: root.height-height
source: "assets/soccer_ball.png"

MouseArea {
anchors.fill: parent
onClicked: {
ball.x = 0;
ball.y = root.height-ball.height;
ball.rotation = 0;
anim.restart()
}
}
}

A imagem tem um mouse area anexada a ela. Se a bola for clicada, a posição da bola será
reiniciada e a animação reiniciada.

Vamos começar com uma animação sequencial para as duas primeiras movimentações.
SequentialAnimation {
id: anim
NumberAnimation {
target: ball
properties: "y"
to: 20
duration: root.duration * 0.4
}
NumberAnimation {

80
target: ball
properties: "y"
to: 240
duration: root.duration * 0.6
}
}

Isso especifica que 40% da duração total da animação é a animação para cima e 60% a
animação para baixo. Uma animação após a outra como uma sequência. As
transformações são animadas em um caminho linear, mas não há curva atualmente. As
curvas serão adicionadas posteriormente usando as curvas de atenuação, no momento em
que nos concentramos em obter as transformações animadas.

Em seguida, precisamos adicionar a movimentação x. A movimentação-x deve correr em


paralelo com a movimentação-y, então precisamos encapsular a sequência de
movimentação-y em uma animação paralela junto com a movimentação-x.
ParallelAnimation {
id: anim
SequentialAnimation {
// ... our Y1, Y2 animation
}
NumberAnimation { // X1 animation
target: ball
properties: "x"
to: 400
duration: root.duration
}
}

No final, gostaríamos que a bola estivesse girando. Para isso, precisamos adicionar outra
animação à animação paralela. Nós escolhemos o RotationAnimation , pois é
especializado em rotação.
ParallelAnimation {
id: anim
SequentialAnimation {

81
// ... our Y1, Y2 animation
}
NumberAnimation { // X1 animation
// X1 animation
}
RotationAnimation {
target: ball
properties: "rotation"
to: 720
duration: root.duration
}
}

Essa é toda a seqüência de animação. A única coisa que resta é fornecer as curvas de
flexão corretas para os movimentos da bola.Para a animação Y1 eu uso um curva
Easing.OutCirc como isso deve se parecer mais com um movimento circular. Y2 é
aprimorado usando um Easing.OutBounce como a bola deve saltar e o salto deve
acontecer no final (tente um Easing.InBounce e você vê o salto vai começar
imediatamente). O X1 e ROT1 animação são deixados como está com uma curva linear.

Aqui está o código final da animação para sua referência:


ParallelAnimation {
id: anim
SequentialAnimation {
NumberAnimation {
target: ball
properties: "y"
to: 20
duration: root.duration * 0.4
easing.type: Easing.OutCirc
}
NumberAnimation {
target: ball
properties: "y"
to: root.height-ball.height
duration: root.duration * 0.6
easing.type: Easing.OutBounce
}
}
NumberAnimation {
target: ball
properties: "x"
to: root.width-ball.width
duration: root.duration
}
RotationAnimation {
target: ball
properties: "rotation"
to: 720
duration: root.duration
}
}

5.2. Estados e Transições

82
Muitas vezes, partes de uma interface de usuário podem ser descritas em estados. Um
estado define um conjunto de alterações de propriedade e pode ser acionado por uma
determinada condição. Adicional esses comutadores de estado podem ter uma transição
anexada que define como essas alterações devem ser animadas ou quaisquer ações
adicionais devem ser aplicadas. As ações também podem ser aplicadas quando um estado
é inserido.

5.2.1. Estados

Você define estados em QML com o elemento State, que precisa estar vinculado à matriz
states de qualquer elemento do item. Um estado é identificado por meio de um nome de
estado e consiste, em sua forma mais simples, de uma série de alterações de propriedades
nos elementos. O estado padrão é definido pelas propriedades iniciais do elemento e é
nomeado "" (a string vazia).
Item {
id: root
states: [
State {
name: "go"
PropertyChanges { ... }
},
State {
name: "stop"
PropertyChanges { ... }
}
]
}

Um estado é alterado, atribuindo um novo nome de estado à propriedade state do


elemento com os estados definidos.

Note

Outra maneira de alternar estados é usando a propriedade when do elemento State. A


propriedade when pode ser configurada para uma expressão que é avaliada como
verdadeira quando o estado deve ser aplicado.

Item {
id: root
states: [
...
]

Button {
id: goButton
...
onClicked: root.state = "go"
}
}

83
Por exemplo, um semáforo pode ter duas luzes de sinalização. A parte superior sinaliza a
parada com uma cor vermelha e a inferior sinaliza com uma cor verde. Neste exemplo, as
duas luzes não devem brilhar ao mesmo tempo. Vamos dar uma olhada no diagrama de
gráficos de estado.

Quando o sistema é ligado, ele entra automaticamente no modo de parada como estado
padrão. O estado de parada muda a light1 para vermelho e a light2 para preto
(desativado). Um evento externo agora pode acionar uma mudança de estado para o estado
"go". No estado de partida, alteramos as propriedades de cor de light1 para black (off) e
acendemos light2 para verde para indicar que os transitores podessem passar agora.

Para realizar este cenário, começamos a esboçar nossa interface de usuário para as 2 luzes.
Para simplificar, usamos dois retângulos com o raio definido para a metade da largura (e a
largura é igual à altura, o que significa que é um quadrado).
Rectangle {
id: light1
x: 25; y: 15
width: 100; height: width
radius: width/2
color: root.black
border.color: Qt.lighter(color, 1.1)
}

Rectangle {
id: light2
x: 25; y: 135
width: 100; height: width
radius: width/2
color: root.black
border.color: Qt.lighter(color, 1.1)
}

Conforme definido no gráfico de estados, queremos ter dois estados, um o estado "go" e o
outro, o estado "stop" state, , em que cada um deles altera os semáforos respectivos para
vermelho ou verde. Nós definimos a propriedade state para stop para garantir que o
estado inicial de nosso semáforo seja o estado de stop.

84
Note

Poderíamos ter conseguido o mesmo efeito com apenas um estado "go" e nenhum estado
de "stop" explícito, definindo a cor da light1 tpara vermelho e a cor da light2 para
preto. O estado inicial "" definido pelos valores iniciais da propriedade agiria como o
estado "stop".

state: "stop"

states: [
State {
name: "stop"
PropertyChanges { target: light1; color: root.red }
PropertyChanges { target: light2; color: root.black }
},
State {
name: "go"
PropertyChanges { target: light1; color: root.black }
PropertyChanges { target: light2; color: root.green }
}
]

Usando PropertyChanges { target: light2; color: "black" } não é realmente


necessário neste exemplo, pois a cor inicial da light2 já é preta. Em um estado, é
necessário apenas descrever como as propriedades devem mudar de seu estado padrão (e
não do estado anterior).

Uma mudança de estado é acionada usando mouse areaque cobre todo o semáforo e
alterna entre o estado de ir e parar quando clicado.
MouseArea {
anchors.fill: parent
onClicked: parent.state = (parent.state == "stop"? "go" : "stop")
}

85
Agora podemos alterar com sucesso o estado da lâmpada de tráfego. Para tornar a UI mais
atraente e com aparência natural, devemos adicionar algumas transições com efeitos de
animação. Uma transição pode ser desencadeada por uma mudança de estado.

Note

É possível criar uma lógica semelhante usando scripts em vez de estados QML. Os
desenvolvedores podem facilmente cair na armadilha de escrever mais um programa
JavaScript do que um programa QML.

5.2.2. Transições

Uma série de transições pode ser adicionada a cada item. Uma transição é executada por
uma mudança de estado. Você pode definir em qual estado alterar uma determinada
transição pode ser aplicada usando as propriedades from: e to:.Essas duas propriedades
funcionam como um filtro, quando o filtro é verdadeiro, a transição será aplicada. Você
também pode usar o elenco selvagem “*” que significa ““Qualquer estado”. Por exemplo
from:"*"; to:"*" significa de qualquer estado para qualquer outro estado e é o valor
padrão para from e to, o que significa que a transição é aplicada a todos os comutadores
de estado.

Para este exemplo, gostaríamos de animar as mudanças de cor ao mudar de estado de “go”
para “stop”. Para a outra mudança de estado invertida (“stop” para “go”) queremos manter
uma mudança de cor imediata e não aplicar uma transição. Restringimos a transição com
as propriedades de from e to para filtrar apenas a alteração de estado de “go” para “stop”.
Dentro da transição, adicionamos duas animações de cores para cada luz, que animará as
alterações de propriedade definidas na descrição do estado.

86
transitions: [
Transition {
from: "stop"; to: "go"
// from: "*"; to: "*"
ColorAnimation { target: light1; properties: "color"; duration:
ColorAnimation { target: light2; properties: "color"; duration:
}
]

Você pode alterar o estado clicando no botão da UI. O estado é aplicado imediatamente e
também altera o estado enquanto uma transição está em execução. Portanto, tente clicar na
UI enquanto o estado está em transição de “stop” para “go”. Você verá a mudança
acontecerá imediatamente.

Você poderia brincar com essa UI por exemplo, redimensionando a luz inativa para
destacar a luz ativa. Para isso, você precisaria adicionar outra alteração de propriedade
para escalar para os estados e também manipular a animação para a propriedade de escala
na transição. Outra opção seria adicionar um estado “attention” que indica onde as luzes
estão piscando em amarelo. Para isso, você precisaria adicionar uma animação sequencial
à transição por um segundo indo para amarelo (“to” propriedade da animação e um
segundo indo para “black”). Talvez você também queira alterar a curva de atenuação para
torná-la visualmente mais atraente.

5.3. Técnicas Avançadas


Todo

Para ser escrito

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

87
Qt5 Cadaques Book » previous | next

6. Delegando Model-View
Section author: e8johan [https://bitbucket.org/e8johan]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado noassets folder.

No Qt Quick, os dados são separados da apresentação por meio de uma separação de


model-view.Para cada exibição, a visualização de cada elemento de dados é separada em
um delegate.O Qt Quick vem com um conjunto de modelos e visualizações predefinidos.
Para utilizar o sistema, é preciso entender essas classes e saber como criar delegates
apropriados para obter a aparência e a sensação corretas.

6.1. Conceito
Um dos aspectos mais importantes no desenvolvimento de interfaces de usuário é manter a
representação dos dados separada da visualização.Por exemplo, uma lista telefônica pode
ser organizada como uma lista vertical de entradas de texto ou uma grade de imagens dos
contatosEm ambos os casos, os dados são idênticos: a lista telefônica, mas a visualização é
diferente.Essa divisão é comumente referida como o padrão de exibição de modelo. Nesse
padrão, os dados são chamados de modelo, enquanto a visualização é manipulada pela
visão.

No QML, o modelo e a visualização são unidos pelo delegate. A responsabilidade é


dividida da seguinte forma. O modelo fornece os dados.Para cada item de dados, pode
haver vários valores. No exemplo acima, cada entrada da agenda telefônica tem um nome,
uma foto e um número.Os dados são organizados em uma visualização, em que cada item
é visualizado usando um delegate. A tarefa da exibição é organizar os representantes,
enquanto cada representante mostra os valores de cada item de modelo ao usuário.

88
6.2. Modelos Básicos
A maneira mais básica de separar os dados da apresentação é usar o elemento
Repeater.Ele é usado para instanciar uma matriz de itens e é fácil de combinar com um
posicionador para preencher uma parte da interface do usuário.Um repetidor usa um
modelo, que pode ser qualquer coisa, desde o número de itens a serem instanciados, até
um modelo de coleta de dados totalmente soprado da Internet.

Em sua forma mais simples, o repetidor pode ser usado para instanciar um número
especificado de itensCada item terá acesso a uma propriedadeindexde variável, que pode
ser usado para diferenciar os itens.No exemplo abaixo, um repetidor é usado para criar 10
instâncias de um item.O número de itens é controlado usando a propriedademodelPara
cada item, oRectanglecontendo um elementoTextencontrado dentro do itemRepeateré
instanciado.Como você pode ver, a propriedadetexté definida para o valor doindex,
portanto os itens são numerados de zero a nove.
import QtQuick 2.5
import "../common"

Column {
spacing: 2

Repeater {
model: 10
BlueBox {
width: 120
height: 32
text: index
}
}
}

89
Por mais legais que sejam as listas de itens numerados, às vezes é interessante exibir um
conjunto de dados mais complexo. Ao substituir o valor do model inteiro por um array
JavaScript, podemos conseguir isso. O conteúdo da matriz pode ser de qualquer tipo, seja
strings, inteiros ou objetos. No exemplo abaixo, uma lista de seqüências de string é usada.
Ainda podemos acessar e usar a variável index mas também temos acesso ao modelData
contendo os dados de cada elemento na matriz.
import QtQuick 2.5
import "../common"

Column {
spacing: 2

Repeater {
model: ["Enterprise", "Columbia", "Challenger", "Discovery", "Endeavour"

BlueBox {
width: 100
height: 32
radius: 3

text: modelData + ' (' + index + ')'


}
}
}

90
Sendo capaz de expor os dados de uma matriz, você logo se encontra em uma posição em
que precisa de vários dados por item na matriz. É aqui que os modelos entram na imagem.
Um dos modelos mais triviais e um dos mais usados é o ListModel. Um modelo de lista é
simplesmente uma coleção de itens ListElement. Dentro de cada elemento da lista, um
número de propriedades pode ser vinculado a valores. Por exemplo, no exemplo abaixo,
um nome e uma cor são fornecidos para cada elemento.

As propriedades vinculadas dentro de cada elemento são anexadas a cada item instanciado
pelo repetidor. Isso significa que as variávei name e surfaceColor estão disponíveis
dentro do escopo de cada item Rectangle e Text criado pelo repetidor. Isso não só facilita
o acesso aos dados, como também facilita a leitura do código-fonte. O surfaceColor é a
cor do círculo à esquerda do nome, não algo obscuro como dados da coluna i da linha j.
import QtQuick 2.5
import "../common"

Column {
spacing: 2

Repeater {
model: ListModel {
ListElement { name: "Mercury"; surfaceColor: "gray" }
ListElement { name: "Venus"; surfaceColor: "yellow" }
ListElement { name: "Earth"; surfaceColor: "blue" }
ListElement { name: "Mars"; surfaceColor: "orange" }
ListElement { name: "Jupiter"; surfaceColor: "orange" }
ListElement { name: "Saturn"; surfaceColor: "yellow" }
ListElement { name: "Uranus"; surfaceColor: "lightBlue" }
ListElement { name: "Neptune"; surfaceColor: "lightBlue" }
}

BlueBox {
width: 120
height: 32

radius: 3
text: name

Box {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4

91
width: 16
height: 16

radius: 8

color: surfaceColor
}
}
}
}

O conteúdo do repetidor que está sendo instanciado para cada item é, na verdade, o que
está vinculado à propriedade padrão, delegate. Isso significa que o código do exemplo
Example 01 Isso significa que o código do exemplo. Observe que a única diferença é que
o nome da propriedade do delegate é explicitado explicitamente no último.
import QtQuick 2.5
import "../common"

Column {
spacing: 2

Repeater {
model: 10

delegate: BlueBox {
width: 100
height: 32
text: index
}
}
}

6.3. Visualização Dinâmica


Repetidores funcionam bem para conjuntos limitados e estáticos de dados, mas no mundo
real, os modelos são geralmente mais complexos - e maiores. Aqui, uma solução mais

92
inteligente é necessária. Para isso, o Qt Quick fornece os elementos ListView e GridView.
Ambos são baseados em uma área Flickable , para que o usuário possa se movimentar
em um conjunto maior de dados. Ao mesmo tempo, eles limitam o número de delegates
instanciados simultaneamente. Para um modelo grande, isso significa menos elementos na
cena de uma só vez.

Os dois elementos são semelhantes em seu uso. Assim, começaremos com o ListView e,
em seguida, descreveremos o GridView com o primeiro como ponto de partida da
comparação.

O ListView é semelhante ao elemento Repeater. Ele usa um model, instancia um


delegate e entre os delegates, pode haver spacing. A lista abaixo mostra como uma
configuração simples pode parecer.
import QtQuick 2.5
import "../common"

Background {
width: 80
height: 300

93
ListView {
anchors.fill: parent
anchors.margins: 20

clip: true

model: 100

delegate: numberDelegate
spacing: 5
}

Component {
id: numberDelegate

GreenBox {
width: 40
height: 40
text: index
}
}
}

Se o modelo contiver mais dados do que pode caber na tela, o ListView mostrará apenas
parte da lista. No entanto, como consequência do comportamento padrão do Qt Quick, a
exibição de lista não limita a área da tela na qual os representantes são mostrados. Isso
significa que os representantes podem estar visíveis fora da visualização de lista e que a
criação dinâmica e a destruição de representantes fora da visualização de lista ficam
visíveis para o usuário. Para evitar isso, o recorte deve ser ativado no elemento ListView
definindo a propriedade do clip como true. A ilustração abaixo mostra o resultado disso,
comparado a quando a propriedade do clip é deixada como false.

94
Para o usuário, o ListView é uma área rolável (scrolling). Suporta rolagem cinética, o que
significa que pode ser movimentada para percorrer rapidamente o conteúdo. Por padrão,
ele também pode ser esticado além do final do conteúdo e, em seguida, retorna para
sinalizar ao usuário que o fim foi atingido.

O comportamento no final da exibição é controlado usando a propriedade


boundsBehavior. Este é um valor enumerado e pode ser configurado a partir do
comportamento padrão, Flickable.DragAndOvershootBounds, em que a exibição pode
ser arrastada e deslocada para fora de seus limites, para Flickable.StopAtBounds, onde a
exibição nunca se moverá para fora de seus limites. O meio termo
Flickable.DragOverBounds ermite que o usuário arraste a exibição para fora de seus
limites, mas os movimentos serão interrompidos no limite.

É possível limitar as posições em que uma visualização pode parar. Isso é controlado
usando a propriedade snapMode. O comportamento padrão ListView.NoSnap, permite que
a visualização pare em qualquer posição. Ao definir a propriedade snapMode para o
ListView.SnapToItem, a visualização sempre alinhará a parte superior de um item com
sua parte superior. Finalmente, o ListView.SnapOneItem, a exibição não irá parar mais de
um item do primeiro item visível quando o botão do mouse ou o toque for liberado. O
último modo é muito útil ao folhear as páginas.

6.3.1. Orientação

A exibição de lista fornece uma lista de rolagem vertical por padrão, mas a rolagem
horizontal pode ser tão útil quanto. A direção da exibição de lista é controlada pela
propriedade orientation. Pode ser definido como o valor padrão, ListView.Vertical,
ou ListView.Horizontal. Uma lista horizontal é mostrada abaixo.
import QtQuick 2.5
import "../common"

Background {
width: 480
height: 80

95
ListView {
anchors.fill: parent
anchors.margins: 20
spacing: 4
clip: true
model: 100
orientation: ListView.Horizontal
delegate: numberDelegate
}

Component {
id: numberDelegate

GreenBox {
width: 40
height: 40
text: index
}
}
}

Como você pode dizer, a direção da horizontal flui da esquerda para a direita por padrão.
Isso pode ser controlado pela propriedade layoutDirection que pode ser definida como
Qt.LeftToRight ou Qt.RightToLeft, dependendo da direção do fluxo.

6.3.2. Navegação Keyboard e Highlighting

Ao usar o ListView em uma configuração baseada em touch (toque), a exibição em si é


suficiente. Em um cenário com um teclado, ou mesmo apenas teclas de seta para
selecionar um item, é necessário um mecanismo para indicar o item atual. No QML, isso é
chamado highlighting.

As visualizações suportam um delegate de highlight que é mostrado na exibição junto com


os representantes. Pode ser considerado um delegate adicional, apenas que é apenas
instanciado uma vez e é movido para a mesma posição que o item atual.

No exemplo abaixo isso é demonstrado. Existem duas propriedades envolvidas para que
isso funcione. Primeiro, a propriedade focus é definida como true. Isso dá ao ListView o
foco do teclado. Em segundo lugar, a propriedade de highlight é definida para apontar o
delegate de destaque a ser usado. O delegate highlight recebe x, y e height da altura do
item atual. Se width não for especificada, a largura do item atual também será usada.

No exemplo, a propriedade anexada ListView.view.width é usada para largura. As


propriedades anexadas disponíveis aos representantes são discutidas mais adiante na seção
delegate deste capítulo, mas é bom saber que as mesmas propriedades estão disponíveis
para destacar os representantes também.

96
import QtQuick 2.5
import "../common"

Background {
width: 240
height: 300

ListView {
id: view
anchors.fill: parent
anchors.margins: 20

clip: true

model: 100

delegate: numberDelegate
spacing: 5

highlight: highlightComponent
focus: true
}

Component {
id: highlightComponent

GreenBox {
width: ListView.view.width
}
}

Component {
id: numberDelegate

Item {
width: ListView.view.width
height: 40

Text {
anchors.centerIn: parent

font.pixelSize: 10

text: index
}
}
}
}
// M1>>

97
Ao usar um highlight em conjunto com um ListView, várias propriedades podem ser
usadas para controlar seu comportamento. O highlightRangeMode controla como o
highlight é afetado pelo que é mostrado na exibição(view). A configuração padrão,
ListView.NoHighlightRange significa que o highlight e o intervalo visível de itens na
exibição não estão relacionados.

O valor ListView.StrictlyEnforceRange garante que o highlight esteja sempre visível.


Se uma ação tentar mover o highlight para fora da parte visível da exibição, o item atual
será alterado de acordo, para que o highlight permaneça visível.

O meio termo é o valor ListView.ApplyRange. Ele tenta manter o destaque visível, mas
não altera o item atual para impor isso. Em vez disso, o destaque pode sair de vista, se
necessário.

Na configuração padrão, a visão é responsável por mover o destaque para a posição. A


velocidade do movimento e o redimensionamento podem ser controlados, seja como
velocidade ou como duração. As propriedades envolvidas são highlightMoveSpeed,
highlightMoveDuration, highlightResizeSpeed e highlightResizeDuration. Por
padrão, a velocidade é definida como 400 pixels por segundo e a duração é definida como
-1, indicando que a velocidade e a distância controlam a duração. Se uma velocidade e
uma duração forem definidas, a que resulta na animação mais rápida é escolhida.

Para controlar o movimento de highlight mais detalhadamente, a propriedade


highlightFollowCurrentItem pode ser definida como false. Isso significa que a
exibição não é mais responsável pelo movimento do delegate do highlight. Em vez disso,
o movimento pode ser controlado através do Behavior ou de uma animação.

No exemplo abaixo, a propriedade y do delegate highlight é vinculada à propriedade


anexada ListView.view.currentItem.y. Isso garante que o highlight siga o item atual.
No entanto, como não permitimos que a view mova o highlight,podemos controlar como o
elemento é movido. Isso é feito através do Behavior on y. No exemplo abaixo, o
movimento é dividido em três etapas: desaparecendo, em movimento, antes de
desaparecer. Observe como os elementos SequentialAnimation e PropertyAnimation

98
podem ser usados em combinação com o NumberAnimation para criar um movimento mais
complexo.
Component {
id: highlightComponent

Item {
width: ListView.view.width
height: ListView.view.currentItem.height

y: ListView.view.currentItem.y

Behavior on y {
SequentialAnimation {
PropertyAnimation { target: highlightRectangle; property:
NumberAnimation { duration: 1 }
PropertyAnimation { target: highlightRectangle; property:
}
}

GreenBox {
id: highlightRectangle
anchors.fill: parent
}
}
}

6.3.3. Header e Footer

No final do conteúdo ListView um header e um elementofooter podem ser inseridos.


Estes podem ser considerados locais de delegates especiais no início ou no final da lista.
Para uma lista horizontal, elas não aparecerão no head ou no foot, mas sim no início ou no
final, dependendo do layoutDirection usado.

O exemplo abaixo ilustra como um header e um footer podem ser usados para melhorar a
percepção do início e do fim de uma lista. Existem outros usos para esses elementos de
lista especiais. Por exemplo, eles podem ser usados para manter botões para carregar mais
conteúdo.
import QtQuick 2.5
import "../common"

Background {
width: 240
height: 300

ListView {
anchors.fill: parent
anchors.margins: 20

clip: true

model: 4

99
delegate: numberDelegate
spacing: 2

header: headerComponent
footer: footerComponent
}

Component {
id: headerComponent

YellowBox {
width: ListView.view.width
height: 20
text: 'Header'

}
}

Component {
id: footerComponent

YellowBox {
width: ListView.view.width
height: 20
text: 'Footer'
}
}

Component {
id: numberDelegate

GreenBox {
width: ListView.view.width
height: 40
text: 'Item #' + index
}
}
}

Note

delegates de header e footer não respeitam a propriedade spacing de um ListView, em


vez disso, eles são colocados diretamente adjacentes ao próximo item delegate na lista.
Isso significa que qualquer espaçamento deve fazer parte dos itens de header e footer.

100
6.3.4. GridView

Usando GridView é muito semelhante ao uso de um ListView. A única diferença real é


que a exibição de grade coloca os representantes em uma grade bidimensional em vez de
em uma lista linear.

Em comparação com uma exibição de lista, a exibição de grade não depende do


espaçamento e do tamanho de seus representantes.Em vez disso, ele usa as propriedades
cellWidth e cellHeight para controlar as dimensões dos representantes de conteúdo.
Cada item delegate é então colocado no canto superior esquerdo de cada célula.
import QtQuick 2.5
import "../common"

Background {
width: 220
height: 300

GridView {
id: view

101
anchors.fill: parent
anchors.margins: 20

clip: true

model: 100

cellWidth: 45
cellHeight: 45

delegate: numberDelegate
}

Component {
id: numberDelegate

GreenBox {
width: 40
height: 40
text: index
}
}
}

Um GridView contém headers e footers, pode usar um delegate de destaque e suporta


modos de snap, bem como vários comportamentos de limites. Também pode ser orientado
em diferentes direções e orientações.

A orientação é controlada usando a propriedade flow. Pode ser definido como


GridView.LeftToRight ou GridView.TopToBottom. O primeiro valor preenche uma
grade da esquerda para a direita, adicionando linhas de cima para baixo. OView é rolável
na direção vertical..O último valor adiciona itens de cima para baixo, preenchendo a vista
da esquerda para a direita. A direção de rolagem é horizontal neste caso.

Além da propriedade flow a propriedade layoutDirection pode adaptar a direção da


grade para os idiomas da esquerda para a direita ou da direita para a esquerda, dependendo
do valor usado.

6.4. Delegate
Quando se trata de usar modelos e exibições em uma interface de usuário personalizada, o
delegate desempenha um grande papel na criação de uma aparência. Como cada item em
um modelo é visualizado por meio de um delegate, o que é realmente visível para o
usuário são os representantes.

Cada delegate obtém acesso a várias propriedades anexadas, algumas do modelo de dados,
outras da exibição. A partir do modelo, as propriedades transmitem os dados de cada item
para o delegate. Do ponto de vista, as propriedades transmitem informações de estado
relacionadas ao delegate dentro da exibição.

As propriedades mais comumente usadas anexadas da exibição são

102
ListView.isCurrentItem e ListView.view. O primeiro é um booleano indicando se o
item é o item atual, enquanto o último é uma referência somente leitura para a visão atual.
Através do acesso à visão, é possível criar delegates gerais e reutilizáveis que se adaptam
ao tamanho e à natureza da visão em que estão contidos. No exemplo abaixo, a width de
cada delegate é vinculada à width da exibição, enquanto a color do background de cada
delegate depende da propriedade ListView.isCurrentItem anexada.
import QtQuick 2.5

Rectangle {
width: 120
height: 300

gradient: Gradient {
GradientStop { position: 0.0; color: "#f6f6f6" }
GradientStop { position: 1.0; color: "#d7d7d7" }
}

ListView {
anchors.fill: parent
anchors.margins: 20

clip: true

model: 100

delegate: numberDelegate
spacing: 5

focus: true
}

Component {
id: numberDelegate

Rectangle {
width: ListView.view.width
height: 40

color: ListView.isCurrentItem?"#157efb":"#53d769"
border.color: Qt.lighter(color, 1.1)

Text {
anchors.centerIn: parent

font.pixelSize: 10

text: index
}
}
}
}

103
Se cada item no modelo estiver associado a uma ação, por exemplo, clicar em um item
atua sobre ele, essa funcionalidade é uma parte de cada delegate. Isso divide o
gerenciamento de eventos entre a exibição, que manipula a navegação entre os itens na
exibição, e o delegate que lida com ações em um item específico.

A maneira mais básica de fazer isso é criar uma MouseAreadentro de cada delegate e agir
no sinal onClicked. Isso é demonstrado no exemplo da próxima seção deste capítulo.

6.4.1. Adicionando Animações e Removendo Itens

Em alguns casos, o conteúdo mostrado em uma exibição muda com o tempo. Itens são
adicionados e removidos conforme o modelo de dados de subjacência é alterado. Nesses
casos, geralmente é uma boa ideia empregar perguntas visuais para dar ao usuário um
senso de direção e ajudar o usuário a entender quais dados são adicionados ou removidos.

Convenientemente, as views QML anexam dois sinais, onAdd e onRemove, a cada delegate
do item. Ao conectar animações a elas, é fácil criar o movimento necessário para ajudar o
usuário a identificar o que está acontecendo.

O exemplo abaixo demonstra isso através do uso de um ListModel preenchido


dinamicamente. Na parte inferior da tela, um botão para adicionar novos itens é
exibido.Quando é clicado, um novo item é adicionado ao modelo usando o método
append. Isso aciona a criação de um novo delegate na exibição e a emissão do sinal
GridView.onAdd. A SequentialAnimation anexada ao sinal faz com que o item seja
ampliado, animando a propriedade scale do delegate.

Quando um delegate na exibição é clicado, o item é removido do modelo por meio de uma
chamada para o método remove. Isso faz com que o sinal GridView.onRemove seja
emitido, acionando outro SequentialAnimation. Desta vez, no entanto, a destruição do
delegate deve ser adiada até que a animação seja concluída. Para fazer isso, o elemento
PropertyAction é usado para definir a propriedade GridView.delayRemove como true
antes da animação e false depois. Isso garante que a animação seja concluída antes que o

104
item delegate seja removido.
import QtQuick 2.5

Rectangle {
width: 480
height: 300

gradient: Gradient {
GradientStop { position: 0.0; color: "#dbddde" }
GradientStop { position: 1.0; color: "#5fc9f8" }
}

ListModel {
id: theModel

ListElement { number: 0 }
ListElement { number: 1 }
ListElement { number: 2 }
ListElement { number: 3 }
ListElement { number: 4 }
ListElement { number: 5 }
ListElement { number: 6 }
ListElement { number: 7 }
ListElement { number: 8 }
ListElement { number: 9 }
}

Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 20

height: 40

color: "#53d769"
border.color: Qt.lighter(color, 1.1)

Text {
anchors.centerIn: parent

text: "Add item!"


}

MouseArea {
anchors.fill: parent

onClicked: {
theModel.append({"number": ++parent.count});
}
}

property int count: 9


}

GridView {
anchors.fill: parent
anchors.margins: 20

105
anchors.bottomMargin: 80

clip: true

model: theModel

cellWidth: 45
cellHeight: 45

delegate: numberDelegate
}

Component {
id: numberDelegate

Rectangle {
id: wrapper

width: 40
height: 40

gradient: Gradient {
GradientStop { position: 0.0; color: "#f8306a" }
GradientStop { position: 1.0; color: "#fb5b40" }
}

Text {
anchors.centerIn: parent

font.pixelSize: 10

text: number
}

MouseArea {
anchors.fill: parent

onClicked: {
theModel.remove(index);
}
}

GridView.onRemove: SequentialAnimation {
PropertyAction { target: wrapper; property: "GridView.delayRemove
NumberAnimation { target: wrapper; property: "scale"; to: 0;
PropertyAction { target: wrapper; property: "GridView.delayRemove
}

GridView.onAdd: SequentialAnimation {
NumberAnimation { target: wrapper; property: "scale"; from:
}
}
}
}

6.4.2. delegates que mudam de forma

106
Um mecanismo comumente usado em listas é que o item atual é expandido quando
ativado. Isso pode ser usado para permitir que o item seja expandido dinamicamente para
preencher a tela e inserir uma nova parte da interface do usuário, ou pode ser usado para
fornecer um pouco mais de informações para o item atual em uma determinada lista.

No exemplo abaixo, cada item é expandido para a extensão total do ListView que o
contém quando clicado. O espaço extra é então usado para adicionar mais informações. O
mecanismo usado para controlar isso é um estado, expanded que cada delegate de item
pode inserir, onde o item é expandido. Nesse estado, várias propriedades são alteradas.

Primeiro de tudo a height do wrapper é definida para a altura do ListView. A imagem em


miniatura é então ampliada e movida para baixo, de modo a mover-se da sua pequena
posição para a posição maior. Além disso, os dois itens ocultos, os factsView e
closeButton são mostrados alterando a opacity dos elementos. Finalmente, o ListView é
configurado.

Configurar o ListView envolve a configuração do contentsY, que é o topo da parte


visível da exibição, para o valor y do delegate. A outra mudança é definir a interactive
da exibição como false. Isso impede que a view se mova. O usuário não pode mais rolar
pela lista ou alterar o item atual.

Quando o item primeiro é clicado, ele entra no estado expanded fazendo com que o
delegate do item preencha o ListView e o conteúdo a ser reorganizado. Quando o botão
Fechar é clicado, o estado é limpo, fazendo com que o delegate retorne ao seu estado
anterior e reative o ListView.
import QtQuick 2.5

Item {
width: 300
height: 480

Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#4a4a4a" }
GradientStop { position: 1.0; color: "#2b2b2b" }
}
}

ListView {
id: listView

anchors.fill: parent

delegate: detailsDelegate
model: planets
}

ListModel {
id: planets

ListElement { name: "Mercury"; imageSource: "images/mercury.jpeg"; facts:

107
ListElement { name: "Venus"; imageSource: "images/venus.jpeg"; facts:
ListElement { name: "Earth"; imageSource: "images/earth.jpeg"; facts:
ListElement { name: "Mars"; imageSource: "images/mars.jpeg"; facts:
}

Component {
id: detailsDelegate

Item {
id: wrapper

width: listView.width
height: 30

Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top

height: 30

color: "#333"
border.color: Qt.lighter(color, 1.2)
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4

font.pixelSize: parent.height-4
color: '#fff'

text: name
}
}

Rectangle {
id: image

width: 26
height: 26

anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 2
anchors.topMargin: 2

color: "black"

Image {
anchors.fill: parent

fillMode: Image.PreserveAspectFit

source: imageSource
}
}

108
MouseArea {
anchors.fill: parent
onClicked: parent.state = "expanded"
}

Item {
id: factsView

anchors.top: image.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom

opacity: 0

Rectangle {
anchors.fill: parent

gradient: Gradient {
GradientStop { position: 0.0; color: "#fed958" }
GradientStop { position: 1.0; color: "#fecc2f" }
}
border.color: '#000000'
border.width: 2

Text {
anchors.fill: parent
anchors.margins: 5

clip: true
wrapMode: Text.WordWrap
color: '#1f1f21'

font.pixelSize: 12

text: facts
}
}
}

Rectangle {
id: closeButton

anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 2
anchors.topMargin: 2

width: 26
height: 26

color: "#157efb"
border.color: Qt.lighter(color, 1.1)

opacity: 0

MouseArea {
anchors.fill: parent
onClicked: wrapper.state = ""

109
}
}

states: [
State {
name: "expanded"

PropertyChanges { target: wrapper; height: listView.height


PropertyChanges { target: image; width: listView.width;
PropertyChanges { target: factsView; opacity: 1 }
PropertyChanges { target: closeButton; opacity: 1 }
PropertyChanges { target: wrapper.ListView.view; contentY:
}
]

transitions: [
Transition {
NumberAnimation {
duration: 200;
properties: "height,width,anchors.rightMargin,anchors.top
}
}
]
}
}
}

110
As técnicas demonstradas aqui para expandir o delegate para preencher a visão inteira
podem ser empregadas para tornar um item delegate mudar a forma de forma muito
menor. Por exemplo, ao navegar por uma lista de músicas, o item atual poderia ser um
pouco maior, acomodando mais informações sobre esse item específico.

6.5. Técnicas Avançadas


6.5.1. O PathView

O elemento PathView é a o mais poderoso, mas também mais complexa, fornecida no Qt


Quick. Torna possível criar uma visualização onde os itens são dispostos ao longo de um
caminho arbitrário. Ao longo do mesmo caminho, atributos como escala, opacidade e
muito mais podem ser controlados em detalhes.

Ao usar o PathView, você precisa definir um delegate e um caminho. Além disso, o


próprio PathView pode ser personalizado por meio de um intervalo de propriedades. O
mais comum sendo pathItemCount, controlando o número de itens visíveis de uma vez, e
as propriedades de controle de intervalo de destaque preferredHighlightBegin,
preferredHighlightEnd e highlightRangeMode, controlando onde ao longo do caminho
o item atual deve ser mostrado.

Antes de examinar as propriedades de controle de intervalo de highlight em profundidade,


devemos examinar a propriedade path. A propriedade path espera que um elemento Path
defina o caminho que os representantes seguem conforme o PathView está sendo rolado.

111
O caminho é definido usando as propriedades startX e startY em combinações com
elementos de caminho, como PathLine, PathQuad e PathCubic. Esses elementos são
unidos para formar um caminho bidimensional.

Todo

nós cobrimos a linha, quad e cúbico através de uma ilustração, ou precisamos de um


parágrafo sobre eles?

Quando o caminho tiver sido definido, é possível ajustá-lo ainda mais usando os
elementos PathPercent e PathAttribute. Eles são colocados entre os elementos do
caminho e fornecem um controle mais refinado sobre o caminho e os representantes nele.
O PathPercent controla o tamanho de uma parte do caminho que foi coberto entre cada
elemento. Isso, por sua vez, controla a distribuição de delegates ao longo do caminho, pois
eles são distribuídos proporcionalmente ao percentual avançado.

É onde as propriedades preferredHighlightBegin e preferredHighlightEnd do


PathView entram na imagem. Ambos esperam valores reais no intervalo entre zero e um.
O final também é esperado para ser mais ou igual ao início. Definindo essas duas
propriedades para, por exemplo, 0,5, o item atual será exibido no local a cinquenta por
cento ao longo do caminho.

No Caminho, os elementos PathAttribute são colocados entre elementos, exatamente


como elementos PathPercent. Eles permitem que você especifique valores de propriedade
que são interpolados ao longo do caminho. Essas propriedades são anexadas aos
representantes e podem ser usadas para controlar qualquer propriedade concebível.

O exemplo abaixo demonstra como o elemento PathView é usado para criar uma exibição
de cartões que o usuário pode percorrer. Ele emprega vários truques para fazer isso. O
caminho consiste em três elementos do PathLine. Usando elementos PathPercent o
elemento central é corretamente centrado e fornece espaço suficiente para não ser confuso

112
por outros elementos. Usando elementos PathAttribute a rotação, tamanho e valor z são
controlados.

Em adição ao path, a propriedade pathItemCount do PathView foi definida. Isso controla


o quão densamente povoado o caminho será. O preferredHighlightBegin e
preferredHighlightEnd o PathView.onPath é usado para controlar a visibilidade dos
representantes.
PathView {
anchors.fill: parent

delegate: flipCardDelegate
model: 100

path: Path {
startX: root.width/2
startY: 0

PathAttribute { name: "itemZ"; value: 0 }


PathAttribute { name: "itemAngle"; value: -90.0; }
PathAttribute { name: "itemScale"; value: 0.5; }
PathLine { x: root.width/2; y: root.height*0.4; }
PathPercent { value: 0.48; }
PathLine { x: root.width/2; y: root.height*0.5; }
PathAttribute { name: "itemAngle"; value: 0.0; }
PathAttribute { name: "itemScale"; value: 1.0; }
PathAttribute { name: "itemZ"; value: 100 }
PathLine { x: root.width/2; y: root.height*0.6; }
PathPercent { value: 0.52; }
PathLine { x: root.width/2; y: root.height; }
PathAttribute { name: "itemAngle"; value: 90.0; }
PathAttribute { name: "itemScale"; value: 0.5; }
PathAttribute { name: "itemZ"; value: 0 }
}

pathItemCount: 16

preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
}

O delegate, mostrado abaixo, utiliza as propriedades anexadas itemZ, itemAngle e


itemScale dos elementos PathAttribute. Vale a pena notar que as propriedades anexadas
do delegate estão disponíveis somente a partir do wrapper. Assim, a propriedade rotX é
definida para poder acessar o valor a partir do elemento Rotation.

Outro detalhe específico do PathView que merece destaque é o uso da propriedade


PathView.onPath anexada. É uma prática comum vincular a visibilidade a isso, pois isso
permite que o PathView mantenha elementos invisíveis para fins de armazenamento em
cache. Isso geralmente não pode ser tratado por meio de recorte, já que os representantes
de itens de um PathView são colocados com mais liberdade do que os representantes de
itens das visualizações ListView ou GridView.
Component {

113
id: flipCardDelegate

BlueBox {
id: wrapper

width: 64
height: 64
antialiasing: true

gradient: Gradient {
GradientStop { position: 0.0; color: "#2ed5fa" }
GradientStop { position: 1.0; color: "#2467ec" }
}

visible: PathView.onPath

scale: PathView.itemScale
z: PathView.itemZ

property variant rotX: PathView.itemAngle


transform: Rotation {
axis { x: 1; y: 0; z: 0 }
angle: wrapper.rotX;
origin { x: 32; y: 32; }
}
text: index
}
}

Ao transformar imagens ou outros elementos complexos no PathView, um truque de


otimização de desempenho comum é vincular a propriedade smooth do elemento Image à
propriedade anexada PathView.view.moving. Isto significa que as imagens são menos
bonitas enquanto se movem, mas suavemente transformadas quando paradas. Não adianta
gastar poder de processamento em escalas suaves quando a exibição está em movimento,
pois o usuário não conseguirá ver isso de qualquer maneira.

6.5.2. Um modelo de XML

Como o XML é um formato de dados onipresente, o QML fornece o elemento


XmlListModel que expõe dados XML como um modelo. O elemento pode buscar dados
XML local ou remotamente e, em seguida, processa os dados usando expressões XPath.

O exemplo abaixo demonstra a busca de imagens de um fluxo RSS. A propriedade de


source refere-se a um local de remoção por HTTP e os dados são baixados
automaticamente.

114
Quando os dados foram baixados, eles são processados em itens e funções do modelo. A
propriedade query é um XPath que representa a consulta base para criar itens de modelo.
Neste exemplo, o caminho é /rss/channel/item, Assim, para cada tag de item, dentro de
uma tag de canal, dentro de uma tag RSS, um item de modelo é criado.

Para cada item de modelo, várias funções são extraídas. Estes são representados pelos
elementos XmlRole. Cada função recebe um nome, que o delegate pode acessar por meio
de uma propriedade anexada. O valor real de cada propriedade é determinado pela
consulta XPath para cada função. Por exemplo, a propriedade title corresponde à
consulta title/string() retornando o conteúdo entre as tags <title> e </title>.

A propriedade imageSource é mais interessante, pois não apenas extrai uma string do
XML, mas também a processa. No fluxo fornecido, cada item contém uma imagem,
representada por uma tag <img src=. Utilizando as funções substring-after
esubstring-before XPath, a localização da imagem é extraída e retornada. Assim, a
propriedade imageSource pode ser usada diretamente como source de um elemento
Image.

import QtQuick 2.5


import QtQuick.XmlListModel 2.0
import "../common"

Background {
width: 300
height: 480

Component {

115
id: imageDelegate

Box {
width: listView.width
height: 220
color: '#333'

Column {
Text {
text: title
color: '#e0e0e0'
}
Image {
width: listView.width
height: 200
fillMode: Image.PreserveAspectCrop
source: imageSource
}
}
}
}

XmlListModel {
id: imageModel

source: "http://feeds.nationalgeographic.com/ng/photography/photo-of-the-
query: "/rss/channel/item"

XmlRole { name: "title"; query: "title/string()" }


XmlRole { name: "imageSource"; query: "substring-before(substring-after(d
}

ListView {
id: listView
anchors.fill: parent
model: imageModel
delegate: imageDelegate
}
}

6.5.3. Listas com Seções

Às vezes, os dados em uma lista podem ser divididos em seções. Pode ser tão simples
quanto dividir uma lista de contatos em seções sob cada letra do alfabeto ou faixas de
música em álbuns. Usando uma ListView é possível dividir uma lista simples em
categorias, fornecendo mais profundidade à experiência.

116
Para usar seções, o section.property e a section.criteria devem ser configurados.
Asection.property define qual propriedade usar para dividir o conteúdo em seções.
Aqui, é importante saber que o modelo deve ser classificado de forma que cada seção seja
composta de elementos contínuos, caso contrário, o mesmo nome de propriedade pode
aparecer em vários locais.

A section.criteria pode ser definido como ViewSection.FullString ou


ViewSection.FirstCharacter. O primeiro é o valor padrão e pode ser usado para
modelos que tenham seções claras, por exemplo, faixas de álbuns de música. O último
assume o primeiro caractere de uma propriedade e significa que qualquer propriedade
pode ser usada para isso. O exemplo mais comum é o sobrenome dos contatos em uma
lista telefônica.

Quando as seções foram definidas, elas podem ser acessadas de cada item usando as
propriedades anexadas ListView.section, ListView.previousSection e
ListView.nextSection. Usando essas propriedades, é possível detectar o primeiro e o
último item de uma seção e agir de acordo.

Também é possível atribuir um componente de delegate de seção à propriedade


section.delegate de uma ListView. Isso cria um delegate de cabeçalho de seção que é
inserido antes de qualquer item de uma seção. O componente delegate pode acessar o
nome da seção atual usando a propriedade anexada section.

O exemplo abaixo demonstra o conceito da seção, mostrando uma lista de homens do


espaço seccionados após sua nacionalidade. A nation é usado como o section.property.
O componente section.delegate, mostra um título para cada nação, exibindo o nome da
nação. Em cada seção, os nomes dos homens espaciais são mostrados usando o
componente spaceManDelegate.
import QtQuick 2.5
import "../common"

Background {
width: 300

117
height: 290

ListView {
anchors.fill: parent
anchors.margins: 20

clip: true

model: spaceMen

delegate: spaceManDelegate

section.property: "nation"
section.delegate: sectionDelegate
}

Component {
id: spaceManDelegate

Item {
width: ListView.view.width
height: 20
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
font.pixelSize: 12
text: name
color: '#1f1f1f'
}
}
}

Component {
id: sectionDelegate

BlueBox {
width: ListView.view.width
height: 20
text: section
fontColor: '#e0e0e0'
}
}

ListModel {
id: spaceMen

ListElement { name: "Abdul Ahad Mohmand"; nation: "Afganistan"; }


ListElement { name: "Marcos Pontes"; nation: "Brazil"; }
ListElement { name: "Alexandar Panayotov Alexandrov"; nation: "Bulgaria"
ListElement { name: "Georgi Ivanov"; nation: "Bulgaria"; }
ListElement { name: "Roberta Bondar"; nation: "Canada"; }
ListElement { name: "Marc Garneau"; nation: "Canada"; }
ListElement { name: "Chris Hadfield"; nation: "Canada"; }
ListElement { name: "Guy Laliberte"; nation: "Canada"; }
ListElement { name: "Steven MacLean"; nation: "Canada"; }
ListElement { name: "Julie Payette"; nation: "Canada"; }
ListElement { name: "Robert Thirsk"; nation: "Canada"; }

118
ListElement { name: "Bjarni Tryggvason"; nation: "Canada"; }
ListElement { name: "Dafydd Williams"; nation: "Canada"; }
}
}

6.5.4. Ajuste de Desempenho

O desempenho percebido de uma view de um modelo depende muito do tempo necessário


para preparar novos delegates. Por exemplo, ao rolar para baixo através de um ListView,
os delegates são adicionados fora da vista na parte inferior e são removidos assim que
deixam a visão por cima da view. Isso se torna aparente se a propriedade do clip estiver
definida false. Se os delegates demorarem muito tempo para inicializar, ficará evidente
para o usuário assim que a visualização for rolada muito rapidamente.

Para contornar esse problema, você pode ajustar as margens, em pixels, nas laterais de
uma exibição de rolagem. Isso é feito usando a propriedade cacheBuffer. No caso
descrito acima, rolagem vertical, ele controlará quantos pixels acima e abaixo do ListView
que conterá delegates preparados. Combinar isso com o carregamento assíncrono nos
elementos da Image podem, por exemplo, dar tempo para que as imagens sejam carregadas
antes de serem exibidas.

Ter mais delegates sacrifica a memória para uma experiência mais suave e um pouco mais
de tempo para inicializar cada delegate. Isso não resolve o problema de delegates
complexos. Cada vez que um delegate é instanciado, seu conteúdo é avaliado e compilado.
Isso leva tempo e, se levar muito tempo, levará a uma experiência ruim de rolagem. Ter
muitos elementos em um delegate também degradará o desempenho da rolagem.
Simplesmente custa ciclos para mover muitos elementos.

Para solucionar os dois problemas posteriores, recomenda-se usar os elementos Loader.


Estes podem ser usados para instanciar elementos adicionais quando eles são necessários.
Por exemplo, um delegate em expansão pode usar um Loader para adiar a instanciação de
sua visualização detalhada até que seja necessária. Pela mesma razão, é bom manter a
quantidade de JavaScript no mínimo em cada delegate. É melhor deixá-los chamar partes
complexas de JavaScript que residem fora de cada delegate. Isso reduz o tempo gasto na
compilação do JavaScript sempre que um delegate é criado.

6.6. Resumo
Neste capítulo, analisamos modelos, visualizações e delegates. Para cada entrada de dados
em um modelo, uma visualização instancia um delegate visualizando os dados. Isso separa
os dados da apresentação.

Um modelo pode ser um único inteiro, em que a variável de index é fornecida ao


delegate. Se uma matriz JavaScript for usada como modelo, a variável modelData
representará os dados do índice atual da matriz, enquanto o index manterá o índice. Para
casos mais complexos, em que vários valores precisam ser fornecidos por cada item de

119
dados, um ListModel preenchido com itens de ListElement é uma solução melhor.

Para modelos estáticos, um Repeater pode ser usado como a view. É fácil combiná-lo
com um posicionador como Row, Column, Grid ou Flow para criar partes da interface do
usuário. Para modelos de dados dinâmicos ou grandes, uma visualização como ListView
ou GridView é mais apropriada. Eles criam instâncias de delegação quando necessário,
reduzindo o número de elementos ao vivo na cena de uma só vez.

Os representantes usados nas visualizações podem ser itens estáticos com propriedades
vinculadas a dados do modelo ou podem ser dinâmicos, com estados dependendo se estão
em foco ou não. Usando os sinais onAdd e onRemove da view, eles podem até ser animados
à medida que aparecem e desaparecem.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

120
Qt5 Cadaques Book » previous | next

7. Elemento Canvas
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código-fonte para este capítulo pode ser encontrado no assets folder.

Logo no início, quando o QML foi introduzido no Qt4, houve algumas discussões sobre se
o Qt Quick precisa de uma elipse. O problema com a elipse é que outros podem
argumentar que outras formas também precisam ser suportadas. Portanto, não há elipse no
Qt Quick apenas formas retangulares. Se você precisasse de um no Qt4, você precisaria
usar uma imagem ou escrever seu próprio elemento de elipse C ++.

Para permitir desenhos com script, o Qt 5 introduz o elemento canvas. Os elementos


canvas fornecem uma tela de bitmap dependente de resolução, que pode ser usada para
gráficos, jogos ou para pintar outras imagens visuais em tempo real usando JavaScript. O
elemento da tela é baseado no elemento canvas HTML5.

A idéia fundamental do elemento canvas é renderizar caminhos usando um objeto 2D de


contexto. O objeto 2D de contexto contém as funções gráficas necessárias, enquanto a tela
funciona como a tela de desenho. O contexto 2D suporta traçados, preenchimentos,

121
gradientes, texto e diferentes conjuntos de comandos de criação de caminhos.

Vamos ver um exemplo de um desenho de caminho simples:


import QtQuick 2.5

Canvas {
id: root
// canvas size
width: 200; height: 200
// handler to override for drawing
onPaint: {
// get context to draw with
var ctx = getContext("2d")
// setup the stroke
ctx.lineWidth = 4
ctx.strokeStyle = "blue"
// setup the fill
ctx.fillStyle = "steelblue"
// begin a new path to draw
ctx.beginPath()
// top-left start point
ctx.moveTo(50,50)
// upper line
ctx.lineTo(150,50)
// right line
ctx.lineTo(150,150)
// bottom line
ctx.lineTo(50,150)
// left line through path closing
ctx.closePath()
// fill using fill style
ctx.fill()
// stroke using line width and stroke style
ctx.stroke()
}
}

Isso produz um retângulo preenchido com um ponto de referência de 50,50 e um tamanho


de 100 e um traço usado como uma decoração de borda.

A largura do traço é definida como 4 e usa uma cor azul definida por strokeStyle. A
forma final é configurada para ser preenchida pelo fillStyle até a cor "steelblue”.
Somente chamando stroke ou fill o caminho real será desenhado e eles podem ser

122
usados independentemente uns dos outros. Uma chamada para stroke ou fill desenhará
o caminho atual. Não é possível armazenar um caminho para reutilização posterior, apenas
um estado de desenho pode ser armazenado e restaurado.

Em QML, o elemento Canvas age como um contêiner para o desenho. O objeto de


contexto 2D fornece a operação de desenho real. O desenho real precisa ser feito dentro do
manipulador de eventos onPaint.
Canvas {
width: 200; height: 200
onPaint: {
var ctx = getContext("2d")
// setup your path
// fill or/and stroke
}
}

O próprio canvas fornece um típico sistema de coordenadas cartesianas bidimensionais,


em que o canto superior esquerdo é o ponto (0,0). A typical order of commands for this
path based API is the following:

1. configuração de stroke e/ou fill


2. Criar caminho (path)
3. Stroke e/ou fill
onPaint: {
var ctx = getContext("2d")

// setup the stroke


ctx.strokeStyle = "red"

// create a path
ctx.beginPath()
ctx.moveTo(50,50)
ctx.lineTo(150,50)

// stroke path
ctx.stroke()
}

Isto produz uma linha traçada horizontal do ponto P1 (50,50) ao ponto P2 (150,50).

Note

Normalmente, você sempre quer definir um ponto de início quando redefine seu
caminho, portanto, a primeira operação após beginPath geralmente é moveTo.

123
7.1. API conveniente
Para operações em retângulos, é fornecida uma API de conveniência que desenha
diretamente e precisa de uma chamada de traço ou de preenchimento.
// convenient.qml

import QtQuick 2.5

Canvas {
id: root
width: 120; height: 120
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = 'green'
ctx.strokeStyle = "blue"
ctx.lineWidth = 4

// draw a filles rectangle


ctx.fillRect(20, 20, 80, 80)
// cut our an inner rectangle
ctx.clearRect(30,30, 60, 60)
// stroke a border from top-left to
// inner center of the larger rectangle
ctx.strokeRect(20,20, 40, 40)
}
}

Note

A área do traço estende a metade da largura da linha em ambos os lados do caminho.


Uma largura de linha de 4 px atrairá 2 px para fora do caminho e 2 px para dentro.

7.2. Gradientes
O Canvas pode preencher formas com cores, mas também com gradientes ou imagens.
onPaint: {
var ctx = getContext("2d")

var gradient = ctx.createLinearGradient(100,0,100,200)


gradient.addColorStop(0, "blue")
gradient.addColorStop(0.5, "lightsteelblue")

124
ctx.fillStyle = gradient
ctx.fillRect(50,50,100,100)
}

O gradiente neste exemplo é definido ao longo do ponto inicial (100,0) até o ponto final
(100,200), o que dá uma linha vertical no meio do nosso canvas. As paradas de gradiente
podem ser definidas como uma cor de 0.0 (ponto inicial do gradiente) a 1.0 (ponto final do
gradiente). Aqui nós usamos uma cor “blue” a 0.0 (100,0) e uma cor “lightsteelblue” na
posição 0.5 (100.200). O gradiente é definido muito maior do que o retângulo que
queremos desenhar, portanto, o retângulo corta o gradiente até a geometria definida.

Note

O gradiente é definido nas coordenadas do canvas e não nas coordenadas relativas ao


caminho a ser pintado. Um canvas não possui o conceito de coordenadas relativas, como
já estamos acostumados a partir do QML.

7.3. Sombras
Um caminho pode ser aprimorado visualmente usando sombras com o objeto de contexto
2D. Uma sombra é uma área ao redor do caminho com um deslocamento, cor e desfoque
especificado. Para isso, você pode especificar um shadowColor, shadowOffsetX,
shadowOffsetY e um shadowBlur. Tudo isso precisa ser definido usando o contexto 2D. O
contexto 2D é sua única API para as operações de desenho.

Uma sombra também pode ser usada para criar um efeito de brilho ao redor de um
caminho.No próximo exemplo, criamos um texto “Canvas” com um brilho branco ao
redor. Tudo isso em um fundo escuro para melhor visibilidade.

Primeiro desenhamos o dark background:


// setup a dark background
ctx.strokeStyle = "#333"
ctx.fillRect(0,0,canvas.width,canvas.height);

então definimos nossa configuração de sombra, que será usada para o próximo caminho:

125
// setup a blue shadow
ctx.shadowColor = "#2ed5fa";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 10;

Finalmente, desenhamos nosso texto "Canvas" usando uma fonte grande de 80px em
negrito Ubuntu font family.
// render green text
ctx.font = 'bold 80px Ubuntu';
ctx.fillStyle = "#24d12e";
ctx.fillText("Canvas!",30,180);

7.4. Imagens
O canvas QML suporta o desenho de imagens de várias fontes. Para usar uma imagem
dentro da tela, a imagem precisa ser carregada primeiro. Vamos usar o manipulador
Component.onCompleted para carregar a imagem em nosso exemplo.

onPaint: {
var ctx = getContext("2d")

// draw an image
ctx.drawImage('assets/ball.png', 10, 10)

// store current context setup


ctx.save()
ctx.strokeStyle = '#ff2a68'
// create a triangle as clip region
ctx.beginPath()
ctx.moveTo(110,10)
ctx.lineTo(155,10)
ctx.lineTo(135,55)

126
ctx.closePath()
// translate coordinate system
ctx.clip() // create clip from the path
// draw image with clip applied
ctx.drawImage('assets/ball.png', 100, 10)
// draw stroke around path
ctx.stroke()
// restore previous context
ctx.restore()

Component.onCompleted: {
loadImage("assets/ball.png")
}

A esquerda mostra a imagem da nossa bola pintada na posição superior esquerda de


10x10. A imagem da direita mostra a bola com um caminho de clipe aplicado. Imagens e
qualquer outro caminho podem ser cortados usando outro caminho. O recorte é aplicado
definindo um caminho e chamando a função clip(). Todas as operações de desenho a
seguir agora serão cortadas por esse caminho. O recorte é desativado novamente,
restaurando o estado anterior ou definindo a região do clipe para todo o canvas.

7.5. Transformação
O canvas permite transformar o sistema de coordenadas de várias maneiras. Isso é muito
semelhante à transformação oferecida pelos itens da QML. Você tem a possibilidade de
scale, rotate, translate o sistema de coordenadas. Em diferença para QML, a origem
da transformação é sempre a origem do canvas. Por exemplo, para dimensionar um
caminho ao redor do centro, você precisaria traduzir a origem do canvas para o centro do
caminho. Também é possível aplicar uma transformação mais complexa usando o método
de transformação.
// transform.qml

import QtQuick 2.5

Canvas {
id: root
width: 240; height: 120
onPaint: {
var ctx = getContext("2d")
ctx.strokeStyle = "blue"
ctx.lineWidth = 4

127
ctx.beginPath()
ctx.rect(-20, -20, 40, 40)
ctx.translate(120,60)
ctx.stroke()

// draw path now rotated


ctx.strokeStyle = "green"
ctx.rotate(Math.PI/4)
ctx.stroke()
}
}

Além de traduzir o canvas também permite scale(x,y) em torno do eixo x e y, para girar
usando rotate(angle), onde o ângulo é dado em raio (360 degree = 2*Math.PI) e usar
uma transformação de matriz usando o setTransform(m11, m12, m21, m22, dx, dy).

Note

Para reconfigurar qualquer transformação, você pode chamar a função


resetTransform() para configurar a matriz de transformação de volta para a matriz de
identidade:
ctx.resetTransform()

7.6. Modos de Composição


Composição permite desenhar uma forma e misturá-la com os pixels existentes.O canvas
suporta vários modos de composição usando a operação
globalCompositeOperation(mode).

source-over
source-in
source-out
source-atop

onPaint: {
var ctx = getContext("2d")
ctx.globalCompositeOperation = "xor"
ctx.fillStyle = "#33a9ff"

for(var i=0; i<40; i++) {

128
ctx.beginPath()
ctx.arc(Math.random()*400, Math.random()*200, 20, 0, 2*Math.PI)
ctx.closePath()
ctx.fill()
}
}

Este pequeno exemplo percorre uma lista de modos compostos e gera um retângulo com
um círculo.
property var operation : [
'source-over', 'source-in', 'source-over',
'source-atop', 'destination-over', 'destination-in',
'destination-out', 'destination-atop', 'lighter',
'copy', 'xor', 'qt-clear', 'qt-destination',
'qt-multiply', 'qt-screen', 'qt-overlay', 'qt-darken',
'qt-lighten', 'qt-color-dodge', 'qt-color-burn',
'qt-hard-light', 'qt-soft-light', 'qt-difference',
'qt-exclusion'
]

onPaint: {
var ctx = getContext('2d')

for(var i=0; i<operation.length; i++) {


var dx = Math.floor(i%6)*100
var dy = Math.floor(i/6)*100
ctx.save()
ctx.fillStyle = '#33a9ff'
ctx.fillRect(10+dx,10+dy,60,60)
// TODO: does not work yet
ctx.globalCompositeOperation = root.operation[i]
ctx.fillStyle = '#ff33a9'
ctx.globalAlpha = 0.75
ctx.beginPath()
ctx.arc(60+dx, 60+dy, 30, 0, 2*Math.PI)
ctx.closePath()
ctx.fill()
ctx.restore()
}
}

7.7. Buffers de Pixel


Ao trabalhar com canvas, você pode recuperar dados de pixel da tela para ler ou manipular
os pixels da sua tela. Para ler o uso de dados de imagem createImageData(sw,sh)ou
getImageData(sx,sy,sw,sh). Ambas as funções retornam um objeto ImageData com
width, height e variável de data. A variável de dados contém uma matriz unidimensional
dos dados de pixel recuperados no formato RGBA em que cada valor varia no intervalo de
0 a 255. Para definir pixels na tela, você pode usar a função putImageData(imagedata,,
dx, dy).

Outra maneira de recuperar o conteúdo canvas é armazenar os dados em uma imagem.


Isso pode ser obtido com as funções Canvas, save(path) ou toDataURL(mimeType),onde

129
a função posterior retorna uma URL de imagem, que pode ser usada para ser carregada por
um elemento Image.
import QtQuick 2.5

Rectangle {
width: 240; height: 120
Canvas {
id: canvas
x: 10; y: 10
width: 100; height: 100
property real hue: 0.0
onPaint: {
var ctx = getContext("2d")
var x = 10 + Math.random(80)*80
var y = 10 + Math.random(80)*80
hue += Math.random()*0.1
if(hue > 1.0) { hue -= 1 }
ctx.globalAlpha = 0.7
ctx.fillStyle = Qt.hsla(hue, 0.5, 0.5, 1.0)
ctx.beginPath()
ctx.moveTo(x+5,y)
ctx.arc(x,y, x/10, 0, 360)
ctx.closePath()
ctx.fill()
}
MouseArea {
anchors.fill: parent
onClicked: {
var url = canvas.toDataURL('image/png')
print('image url=', url)
image.source = url
}
}
}

Image {
id: image
x: 130; y: 10
width: 100; height: 100
}

Timer {
interval: 1000
running: true
triggeredOnStart: true
repeat: true
onTriggered: canvas.requestPaint()
}
}

Em nosso pequeno exemplo, pintamos a cada segundo um pequeno círculo no canvas


esquerda. Quando se usa mouse area o conteúdo canvas é armazenado e um URL de
imagem é recuperado. No lado direito do nosso exemplo, a imagem é exibida.

Note

130
Recuperar dados de imagem parece não funcionar atualmente no Qt 5 Alpha SDK.

7.8. Pintura com Canvas


Neste exemplo, gostaríamos de criar um pequeno aplicativo de pintura usando o elemento
Canvas.

Para isso, organizamos quatro quadrados de cor no topo da nossa cena, usando um
posicionador de linha. Um quadrado de cor é um retângulo simples preenchido com um
mouse area para detectar cliques.
Row {
id: colorTools
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 8
}
property variant activeSquare: red
property color paintColor: "#33B5E5"
spacing: 4
Repeater {
model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444"]
ColorSquare {
id: red
color: modelData
active: parent.paintColor == color
onClicked: {
parent.paintColor = color
}
}
}
}

131
As cores são armazenadas em uma matriz e na cor da tinta. Quando um usuário clica em
um dos quadrados, a cor do quadrado é atribuída à propriedade paintColor da linha
chamada colorTools.

Para ativar o rastreamento dos eventos do mouse temos um MouseArea cobrindo o


elemento canvas e conectamos os manipuladores pressionados e de posição alterada.
Canvas {
id: canvas
anchors {
left: parent.left
right: parent.right
top: colorTools.bottom
bottom: parent.bottom
margins: 8
}
property real lastX
property real lastY
property color color: colorTools.paintColor

onPaint: {
var ctx = getContext('2d')
ctx.lineWidth = 1.5
ctx.strokeStyle = canvas.color
ctx.beginPath()
ctx.moveTo(lastX, lastY)
lastX = area.mouseX
lastY = area.mouseY
ctx.lineTo(lastX, lastY)
ctx.stroke()
}
MouseArea {
id: area
anchors.fill: parent
onPressed: {
canvas.lastX = mouseX
canvas.lastY = mouseY
}
onPositionChanged: {
canvas.requestPaint()
}
}
}

Um pressionamento do mouse armazena a posição inicial do mouse nas propriedades


lastX e lastY. Cada alteração na posição do mouse aciona uma solicitação de pintura na
tela, o que resultará em chamar o manipulador onPaint handler.

Para finalmente desenhar o traçado do usuário, no manipulador onPaint começamos um


novo caminho e passamos para a última posição. Em seguida, reunimos a nova posição da
área do mouse area e desenhamos uma linha com a cor selecionada para a nova posição. A
posição do mouse é armazenada como a nova última posição.

7.9. Portando Canvas a partir HTML5


132
https://developer.mozilla.org/en/Canvas_tutorial/Transformations
http://en.wikipedia.org/wiki/Spirograph

É bastante fácil de portar um gráfico canvas HTML5 para usar canvas no QML. Dos
milhares de exemplos, nós escolhemos um e tentamos ele mesmo.

Gráfico de Spiro

Nós usamos um spiro graph [http://en.wikipedia.org/wiki/Spirograph] exemplo do projeto Mozilla


como nossa fundação. O HTML5 original foi postado como parte do canvas tutorial
[https://developer.mozilla.org/en/Canvas_tutorial/Transformations].

Há, onde algumas linhas precisávamos mudar:

Qt Quick requer que você declare variável, então precisamos adicionar algumas
declarações var
for (var i=0;i<3;i++) {
...
}

Adaptado o método draw para receber o objeto Context2D


function draw(ctx) {
...
}

precisávamos adaptar a tradução para cada spiro devido a diferentes tamanhos


ctx.translate(20+j*50,20+i*50);

Finalmente, incluímos nosso manipulador onPaint. Dentro de nós, adquirimos um


contexto e chamamos nossa função de desenhar.
onPaint: {
var ctx = getContext("2d");
draw(ctx);
}

O resultado é um gráfico de spiro portado usando a canvas QML

133
Isso é tudo.

Glowing Lines

Aqui está um port mais complexo da organização do W3C. O original pretty glowing lines
[http://www.w3.org/TR/2dcontext/#examples] tem alguns aspectos bem legais, o que torna a
portabilidade mais desafiadora.

<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Pretty Glowing Lines</title>
</head>
<body>

<canvas width="800" height="450"></canvas>


<script>
var context = document.getElementsByTagName('canvas')[0].getContext('2d');

// initial start position


var lastX = context.canvas.width * Math.random();
var lastY = context.canvas.height * Math.random();

134
var hue = 0;

// closure function to draw


// a random bezier curve with random color with a glow effect
function line() {

context.save();

// scale with factor 0.9 around the center of canvas


context.translate(context.canvas.width/2, context.canvas.height/2);
context.scale(0.9, 0.9);
context.translate(-context.canvas.width/2, -context.canvas.height/2);

context.beginPath();
context.lineWidth = 5 + Math.random() * 10;

// our start position


context.moveTo(lastX, lastY);

// our new end position


lastX = context.canvas.width * Math.random();
lastY = context.canvas.height * Math.random();

// random bezier curve, which ends on lastX, lastY


context.bezierCurveTo(context.canvas.width * Math.random(),
context.canvas.height * Math.random(),
context.canvas.width * Math.random(),
context.canvas.height * Math.random(),
lastX, lastY);

// glow effect
hue = hue + 10 * Math.random();
context.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
context.shadowColor = 'white';
context.shadowBlur = 10;
// stroke the curve
context.stroke();
context.restore();
}

// call line function every 50msecs


setInterval(line, 50);

function blank() {
// makes the background 10% darker on each call
context.fillStyle = 'rgba(0,0,0,0.1)';
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
}

// call blank function every 50msecs


setInterval(blank, 40);

</script>
</body>
</html>

Em HTML5, o objeto Context2D pode pintar a qualquer momento em canvas. No QML,


ele só pode apontar dentro do manipulador onPaint. O timer em uso com setInterval

135
aciona em HTML5 o traço da linha ou em branco na tela. Devido ao tratamento diferente
em QML, não é possível apenas chamar essas funções, porque precisamos passar pelo
manipulador onPaint. Além disso, as apresentações coloridas precisam ser adaptadas.
Vamos passar pelas mudanças um por um.

Tudo começa com o elemento canvas. Para simplificar, apenas usamos o elemento Canvas
como o elemento raiz do nosso arquivo QML.
import QtQuick 2.5

Canvas {
id: canvas
width: 800; height: 450

...
}

Para desvendar a chamada direta das funções através do setInterval, substituímos as


chamadas setInterval por dois temporizadores que solicitarão uma repintura. Um Timer
é acionado após um curto intervalo e nos permite executar algum código. Como não
podemos dizer à função de pintura qual operação gostaríamos, definimos para cada
operação que um sinalizador de bool solicite uma operação e acione uma solicitação de
repintura.

Aqui está o código para a operação da linha. A operação blank é semelhante.


...
property bool requestLine: false

Timer {
id: lineTimer
interval: 40
repeat: true
triggeredOnStart: true
onTriggered: {
canvas.requestLine = true
canvas.requestPaint()
}
}

Component.onCompleted: {
lineTimer.start()
}
...

Agora temos uma indicação de que (linha ou em branco ou até mesmo ambos) a operação
que precisamos realizar durante a operação onPaint. Quando entramos no manipulador
onPaint para cada solicitação de pintura, precisamos extrair a inicialização da variável no
elemento canvas.
Canvas {
...
property real hue: 0
property real lastX: width * Math.random();

136
property real lastY: height * Math.random();
...
}

Agora nossa função de pintura deve ficar assim:


onPaint: {
var context = getContext('2d')
if(requestLine) {
line(context)
requestLine = false
}
if(requestBlank) {
blank(context)
requestBlank = false
}
}

A função de linha foi extraída para um canvas como argumento.


function line(context) {
context.save();
context.translate(canvas.width/2, canvas.height/2);
context.scale(0.9, 0.9);
context.translate(-canvas.width/2, -canvas.height/2);
context.beginPath();
context.lineWidth = 5 + Math.random() * 10;
context.moveTo(lastX, lastY);
lastX = canvas.width * Math.random();
lastY = canvas.height * Math.random();
context.bezierCurveTo(canvas.width * Math.random(),
canvas.height * Math.random(),
canvas.width * Math.random(),
canvas.height * Math.random(),
lastX, lastY);

hue += Math.random()*0.1
if(hue > 1.0) {
hue -= 1
}
context.strokeStyle = Qt.hsla(hue, 0.5, 0.5, 1.0);
// context.shadowColor = 'white';
// context.shadowBlur = 10;
context.stroke();
context.restore();
}

A maior mudança foi o uso das funções QML Qt.rgba() e Qt.hsla() que exigiam a
adaptação dos valores ao intervalo de 0.0 ... 1.0 em QML.

O mesmo se aplica à função blank.


function blank(context) {
context.fillStyle = Qt.rgba(0,0,0,0.1)
context.fillRect(0, 0, canvas.width, canvas.height);
}

137
O resultado final será semelhante a este.

Veja também

W3C HTML Canvas 2D Context Specification [http://www.w3.org/TR/2dcontext/]


Mozilla Canvas Documentation [https://developer.mozilla.org/en/HTML/Canvas]
HTML5 Canvas Tutorial [http://www.html5canvastutorials.com/]

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

138
Qt5 Cadaques Book » previous | next

8. Simulação de Partículas
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Partículas são técnicas de computação gráfica para visualizar certos efeitos gráficos.
Efeitos típicos podem ser: folhas caindo, fogo, explosões, meteoros, nuvens, etc.

Ele difere da renderização de outros gráficos, pois a renderização de partículas é baseada


em aspectos difusos. O resultado não é exatamente previsível na base de pixels.
Parâmetros para o sistema de partículas descrevem os limites para a simulação estocástica.
Os fenômenos renderizados com partículas são frequentemente difíceis de visualizar com
técnicas de renderização tradicionais. O bom é que você pode deixar os elementos QML
interagirem com os sistemas de partículas. Além disso, como os parâmetros são expressos
como propriedades, eles podem ser animados usando as técnicas tradicionais de animação.

8.1. Conceito
IO coração da simulação de partículas é o ParticleSystem, que controla a linha de tempo
compartilhada. Uma cena pode ter vários sistemas de partículas, cada um deles com uma
linha de tempo independente. Uma partícula é emitida usando um elemento Emitter e
visualizada com um ParticlePainter, que pode ser uma imagem, um item QML ou um
item de shader. Um emissor também fornece a direção da partícula usando um espaço
vetorial. Uma partícula emitida não pode mais ser manipulada pelo emissor. O módulo de
partículas fornece o Affector, que permite manipular parâmetros da partícula depois de
ter sido emitida.

Partículas em um sistema podem compartilhar transições temporizadas usando o elemento


ParticleGroup. Por padrão, todas as partículas estão no grupo vazio (‘’).

139
ParticleSystem - gerencia linha de tempo compartilhada entre os emissores
Emitter - emite partículas lógicas no sistema
ParticlePainter - partículas são visualizadas por um pintor de partículas
Direction - espaço vetorial para partículas emitidas
ParticleGroup - cada partícula é um membro de um grupo
Affector - manipula partículas depois de terem sido emitidas

8.2. Simulação Simples


Vamos dar uma olhada em uma simulação muito simples para começar. O Qt Quick torna
realmente muito simples começar com a renderização de partículas. Para isso precisamos:

UmParticleSystem que liga todos os elementos a uma simulação


Um Emitter que emite partículas no sistema
Um elemento derivado doParticlePainter , que visualiza as partículas
import QtQuick 2.5
import QtQuick.Particles 2.0

Rectangle {
id: root
width: 480; height: 160
color: "#1f1f1f"

ParticleSystem {
id: particleSystem
}

Emitter {
id: emitter
anchors.centerIn: parent
width: 160; height: 80
system: particleSystem
emitRate: 10
lifeSpan: 1000
lifeSpanVariation: 500
size: 16
endSize: 32
Tracer { color: 'green' }
}

ImageParticle {

140
source: "assets/particle.png"
system: particleSystem
}
}

O resultado do exemplo ficará assim:

Começamos com um retângulo escuro de 80x80 pixels como nosso elemento raiz e
background. Aí declaramos umParticleSystem. Este é sempre o primeiro passo quando o
sistema liga todos os outros elementos. Normalmente, o próximo elemento é o Emitter,
que define a área emissora com base na caixa delimitadora e nos parâmetros básicos para
as partículas a serem emitidas. O emissor está ligado ao sistema usando a propriedade
system.

O emissor neste exemplo emite 10 partículas por segundo (emitRate: 10) sobre a área do
emissor com uma vida útil de 1000 ms (lifeSpan : 1000) e uma variação de vida útil
entre partículas emitidas de 500 mseg (lifeSpanVariation: 500). Uma partícula deve
começar com um tamanho de 16px (size: 16) e no final da sua vida deve ser 32px
(endSize: 32).

O retângulo com borda verde é um elemento traçador para mostrar a geometria do


emissor. Isso visualiza que, embora as partículas sejam emitidas dentro da caixa
delimitadora dos emissores, a renderização não é limitada à caixa delimitadora dos
emissores. A posição de renderização depende do tempo de vida e direção da partícula.
Isso ficará mais claro quando analisarmos como alterar as partículas de direção.

O emissor emite partículas lógicas. Uma partícula lógica é visualizada usando


umParticlePainter. Neste exemplo, usamos um ImageParticle, que usa um URL de
imagem como propriedade de origem. A partícula de imagem também possui várias outras
propriedades, que controlam a aparência da partícula média.

emitRate: partículas emitidas por segundo (o padrão é 10 por segundo)


lifeSpan: milissegundos que a partícula deve durar (o padrão é 1000 ms)
size, endSize: tamanho das partículas no início e no final de sua vida (o padrão é 16
px)

Alterar essas propriedades pode influenciar o resultado de maneira drástica


Emitter {
id: emitter
anchors.centerIn: parent
width: 20; height: 20
system: particleSystem
emitRate: 40

141
lifeSpan: 2000
lifeSpanVariation: 500
size: 64
sizeVariation: 32
Tracer { color: 'green' }
}

Além de aumentar a taxa de emissão para 40 e a duração de vida para 2 segundos, o


tamanho agora começa em 64 pixels e diminui 32 pixels no final da vida útil de uma
partícula.

Aumentar ainda mais o endSize levaria a um backgroundmais ou menos branco. Por


favor, note também quando as partículas são emitidas apenas na área definida pelo
emissor, a renderização não é restrita a ele.

8.3. Parâmetros de Partículas


Já vimos como mudar o comportamento do emissor para mudar nossa simulação. O pintor
de partículas usado nos permite como a imagem da partícula é visualizada para cada
partícula.

Voltando ao nosso exemplo, atualizamos nosso ImageParticle. Primeiro nós mudamos


nossa imagem de partícula para uma pequena imagem de estrela faiscante:
ImageParticle {
...
source: 'assets/star.png'
}

A partícula deve ser colorida numa cor dourada que varia de partícula para partícula em
+/- 20%:
color: '#FFD700'
colorVariation: 0.2

Para tornar a cena mais viva, gostaríamos de girar as partículas. Cada partícula deve
começar em 15 graus no sentido horário e varia entre as partículas em +/- 5 graus. Além
disso, a partícula deve girar continuamente com a velocidade de 45 graus por segundo. A
velocidade também deve variar de partícula para partícula em +/- 15 graus por segundo:
rotation: 15
rotationVariation: 5
rotationVelocity: 45
rotationVelocityVariation: 15

142
Por último, mas não menos importante, alteramos o efeito de entrada para a partícula. Este
é o efeito usado quando uma partícula ganha vida. Neste caso, queremos usar o efeito de
escala:
entryEffect: ImageParticle.Scale

Então agora temos estrelas douradas girando em todo o lugar.

Aqui está o código que mudamos para a partícula da imagem em um bloco.


ImageParticle {
source: "assets/star.png"
system: particleSystem
color: '#FFD700'
colorVariation: 0.2
rotation: 0
rotationVariation: 45
rotationVelocity: 15
rotationVelocityVariation: 15
entryEffect: ImageParticle.Scale
}

8.4. Partículas Direcionadas


Vimos que partículas podem girar. Mas as partículas também podem ter uma trajetória. A
trajetória é especificada como a velocidade ou aceleração de partículas definidas por uma
direção estocástica, também denominada espaço vetorial.

Existem diferentes espaços vetoriais disponíveis para definir a velocidade ou aceleração


de uma partícula:

AngleDirection - a direction that varies in angle


PointDirection - a direction that varies in x and y components
TargetDirection - a direction towards the target point

143
Vamos tentar mover as partículas da esquerda para a direita da nossa cena usando as
direções de velocidade.

Nós primeiro tentamos o AngleDirection. Para isso, precisamos especificar o


AngleDirection como um elemento da propriedadevelocitydo nosso emissor:

velocity: AngleDirection { }

O ângulo em que as partículas são emitidas é especificado usando a propriedade angle. O


ângulo é fornecido como valor entre 0..360 graus e 0 pontos à direita. Para o nosso
exemplo, gostaríamos que as partículas se movessem para a direita, então 0 já é a direção
certa. As partículas devem se espalhar em +/- 5 graus:
velocity: AngleDirection {
angle: 0
angleVariation: 15
}

Agora nós definimos nossa direção, o próximo passo é especificar a velocidade da


partícula. Isso é definido por uma magnitude. A magnitude é definida em pixels por
segundo. Como temos ca. 640px para viajar 100 parece ser um bom número. Isso
significaria um tempo médio de vida de 6,4 segundos que uma partícula atravessaria o
espaço aberto. Para tornar a viagem das partículas mais interessante, variamos a
magnitude usando magnitudeVariation e configuramos isso para a metade da magnitude:
velocity: AngleDirection {
...
magnitude: 100
magnitudeVariation: 50
}

Aqui está o código-fonte completo, com um tempo de vida médio definido para 6,4
segundos. Nós ajustamos a largura e a altura do emissor para 1px. Isso significa que todas
as partículas são emitidas no mesmo local e, a partir daí, viajam com base em nossa
trajetória.
Emitter {

144
id: emitter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 1; height: 1
system: particleSystem
lifeSpan: 6400
lifeSpanVariation: 400
size: 32
velocity: AngleDirection {
angle: 0
angleVariation: 15
magnitude: 100
magnitudeVariation: 50
}
}

Então, o que está fazendo a aceleração? A aceleração adiciona um vetor de aceleração a


cada partícula, o que altera o vetor de velocidade ao longo do tempo. Por exemplo, vamos
fazer uma trajetória como um arco de estrelas. Para isso, mudamos nossa direção de
velocidade para -45 graus e removemos as variações, para melhor visualizar um arco
coerente:
velocity: AngleDirection {
angle: -45
magnitude: 100
}

A direção de aceleração deve ser de 90 graus (direção descendente) e escolhemos um


quarto da magnitude da velocidade para isso:
acceleration: AngleDirection {
angle: 90
magnitude: 25
}

O resultado é um arco que vai do centro esquerdo para o canto inferior direito.

Os valores são descobertos por tentativa e erro.

145
Aqui está o código completo do nosso emissor.
Emitter {
id: emitter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 1; height: 1
system: particleSystem
emitRate: 10
lifeSpan: 6400
lifeSpanVariation: 400
size: 32
velocity: AngleDirection {
angle: -45
angleVariation: 0
magnitude: 100
}
acceleration: AngleDirection {
angle: 90
magnitude: 25
}
}

No próximo exemplo, gostaríamos que as partículas viajassem novamente da esquerda


para a direita, mas desta vez usamos o espaço vetorial PointDirection.

UmPointDirection derivou o espaço vetorial de um componente x e y. Por exemplo, se


você deseja que as partículas percorram um vetor de 45 graus, é necessário especificar o
mesmo valor para x e y.

No nosso caso, queremos que as partículas viajem da esquerda para a direita, construindo
um cone de 15 graus. Para isso, especificamos um PointDirection como nosso espaço
vetorial de velocidade:
velocity: PointDirection { }

Para alcançar uma velocidade de deslocamento de 100 px por segundo, definimos nosso
componente x para 100. Para os 15 graus (que é 1/6 de 90 graus), especificamos uma
variação de 100/6:
velocity: PointDirection {
x: 100
y: 0
xVariation: 0
yVariation: 100/6
}

O resultado deve ser partículas viajando em um cone de 15 graus da direita para a


esquerda.

146
Agora chegando ao nosso último concorrente, oTargetDirection. A direção do alvo nos
permite especificar um ponto de destino como uma coordenada x e y em relação ao
emissor ou a um item. Quando um item é especificado, o centro do item se tornará o ponto
de destino. Você pode alcançar o cone de 15 graus especificando uma variação de 1/6 do
alvo x:
velocity: TargetDirection {
targetX: 100
targetY: 0
targetVariation: 100/6
magnitude: 100
}

Note

A direção do alvo é ótima para usar quando você tem uma coordenada x / y específica na
qual você deseja que o fluxo de partículas seja emitido.

Eu te poupo da imagem da mesma forma que a anterior, ao invés disso eu tenho uma
busca por você.

Na imagem a seguir, os círculos vermelho e verde especificam cada um item alvo para a
direção de destino da velocidade respectiva da propriedade de aceleração. Cada direção de
destino tem os mesmos parâmetros. Aqui a pergunta: Quem é responsável pela velocidade
e quem é pela aceleração?

147
8.5. Pintores de Partículas
Até agora só usamos o pintor de partículas baseado em imagens para visualizar partículas.
Qt vem também com outros pintores de partículas:

ItemParticle: pintor de partículas baseado em delegate


CustomParticle: pintor de partículas baseado em shader

O ItemParticle pode ser usado para emitir itens QML como partículas. Para isso, você
precisa especificar seu próprio delegate para a partícula.
ItemParticle {
id: particle
system: particleSystem
delegate: itemDelegate
}

Nosso delegate, neste caso, é uma imagem aleatória (usando Math.random()), visualizada
com uma borda branca e um tamanho aleatório.
Component {
id: itemDelegate
Item {
id: container
width: 32*Math.ceil(Math.random()*3); height: width
Image {
anchors.fill: parent
anchors.margins: 4
source: 'assets/'+images[Math.floor(Math.random()*9)]
}
}
}

Emitimos 4 imagens por segundo com um tempo de vida de 4 segundos cada. As


partículas desaparecem automaticamente para dentro e para fora.

148
Para casos mais dinâmicos, também é possível criar um item por conta própria e deixar a
partícula assumir o controle com take(item, priority). Por isso, a simulação de
partículas assume o controle de sua partícula e lida com o item como uma partícula
comum. Você pode recuperar o controle do item usando give(item). Você pode
influenciar ainda mais as partículas do item, interrompendo a progressão da sua vida
usando o freeze(item) e retomando sua vida usando ounfreeze(item).

8.6. Afetando partículas


As partículas são emitidas pelo emissor. Depois que uma partícula foi emitida, ela não
pode mais ser alterada pelo emissor. O afeto permite que você influencie as partículas após
elas terem sido emitidas.

Cada tipo de affector afeta as partículas de uma maneira diferente:

Age - Alterar onde a partícula está em seu ciclo de vida


Attractor - Atrair partículas para um ponto específico
Friction - Diminui o movimento proporcional à velocidade atual da partícula
Gravity - Conjunto é uma aceleração em um ângulo
Turbulence - Forças semelhantes a fluidos com base em uma imagem de ruído
Wander - Aleatoriamente variar a trajetória
GroupGoal - Mudar o estado de um grupo de uma partícula
SpriteGoal - Mudar o estado de uma partícula de sprite

Envelhecer

Permite que a partícula envelheça mais rapidamente. a propriedade lifeLeft especificou a


quantidade de vida que uma partícula deve ter deixado.
Age {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
advancePosition: true
lifeLeft: 1200
once: true
Tracer {}
}

149
No exemplo, encurtamos a vida das partículas superiores uma vez, quando elas atingem o
afector de idade para 1200 ms. Como definimos oadvancePosition como true, vemos a
partícula aparecendo novamente em uma posição quando a partícula tem 1200 msecs à
vida

Atrator

O attractor atrai partículas para um ponto específico. O ponto é especificado usando


pointX e pointY, que é relativo à geometria do attrator. A força especifica a força de
atração. No nosso exemplo, deixamos as partículas viajar da esquerda para a direita. O
atrator é colocado no topo e metade das partículas viajam através do attrator.O affector só
afeta as partículas enquanto elas estão em sua caixa delimitadora. Essa divisão nos permite
ver o fluxo normal e o fluxo afetado simultaneamente.
Attractor {
anchors.horizontalCenter: parent.horizontalCenter
width: 160; height: 120
system: particleSystem
pointX: 0
pointY: 0
strength: 1.0
Tracer {}
}

É fácil ver que a metade superior das partículas é afetada pela atração pelo topo. O ponto
de atração é colocado no canto superior esquerdo (0/0 ponto) do atrator com uma força de
1,0.

150
Friction (Atrito)

O friction atrasa as partículas por um fator até que um certo limite seja atingido.
Friction {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
factor : 0.8
threshold: 25
Tracer {}
}

Na área de friction(atrito) superior, as partículas são desaceleradas por um fator de 0,8 até
que a partícula alcance 25 pixels por segundo de velocidade. O ato de limite é como um
filtro. Partículas viajando acima da velocidade limite são retardadas pelo fator dado.

Gravidade

TO Gravit aplica uma aceleração. No exemplo, fluímos as partículas de baixo para cima
usando uma direção de ângulo. O lado direito não é afetado, onde à esquerda é aplicado
um efeito de gravidade. A gravidade é angulada a 90 graus (direção do fundo) com uma

151
magnitude de 50.
Gravity {
width: 240; height: 240
system: particleSystem
magnitude: 50
angle: 90
Tracer {}
}

As partículas do lado esquerdo tentam subir, mas a aceleração constante aplicada no fundo
arrasta-as para a direção da gravidade.

Turbulência

O turbulenceaplica um mapa de caos de vetores de força às partículas. O mapa do caos é


definido por uma imagem de ruído, que pode ser definida com a propriedadenoiseSource.
A força define quão forte o vetor será aplicado nos movimentos das partículas.
Turbulence {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
strength: 100
Tracer {}
}

Na área superior do exemplo, as partículas são influenciadas pela turbulência. Seu


movimento é mais errático. A quantidade de desvio errático do caminho original é
definida pela força.

152
Wander

The wander manipula a trajetória. Com a propriedadeaffectedParameter pode ser


especificado pelo parâmetro (velocity, position or acceleration) é afetado pelo wander. A
propriedadepace especifica o máximo de alterações de atributos por segundo. O yVariance
e yVariance especifica a influência no componente x e y da trajetória da partícula.
Wander {
anchors.horizontalCenter: parent.horizontalCenter
width: 240; height: 120
system: particleSystem
affectedParameter: Wander.Position
pace: 200
yVariance: 240
Tracer {}
}

No wander superior, as partículas são arrastadas por mudanças aleatórias na trajetória.


Neste caso, a posição é alterada 200 vezes por segundo na direção y.

8.7. Grupos de Partículas

153
No início deste capítulo, afirmamos que as partículas estão em grupos, o que é, por
padrão, um grupo vazio (‘’). Usando oGroupGoal é possível deixar a partícula mudar de
grupo. Para visualizar isso, gostaríamos de criar um pequeno fogo de artifício, onde os
foguetes começam no espaço e explodem no ar em um espetacular fogo de artifício.

O exemplo é dividido em duas partes. A 1ª parte chamada “Launch Time” está relacionada
à configuração da cena e introdução de grupos de partículas e a 2ª parte chamada “Let
there be fireworks” foca nas mudanças do grupo.

Vamos começar!

Launch Time

Para continuar, criamos uma cena sombria típica:


import QtQuick 2.5
import QtQuick.Particles 2.0

Rectangle {
id: root
width: 480; height: 240
color: "#1F1F1F"
property bool tracer: false
}

A propriedade do rastreador será usada para ativar e desativar a cena do rastreador. O


próximo passo é declarar nosso sistema de partículas:
ParticleSystem {
id: particleSystem
}

E nossas duas partículas de imagem (uma para o foguete e outra para a fumaça de escape):
ImageParticle {
id: smokePainter
system: particleSystem
groups: ['smoke']

154
source: "assets/particle.png"
alpha: 0.3
entryEffect: ImageParticle.None
}

ImageParticle {
id: rocketPainter
system: particleSystem
groups: ['rocket']
source: "assets/rocket.png"
entryEffect: ImageParticle.None
}

Você pode ver nas imagens, elas usam a propriedadegroups para declarar a qual grupo a
partícula pertence. Basta apenas declarar um nome e um grupo implícito será criado pelo
Qt Quick.

Agora é hora de lançar alguns foguetes no ar. Para isso, criamos um emissor na parte
inferior de nossa cena e definimos a velocidade para uma direção ascendente. Para simular
alguma gravidade, definimos uma aceleração para baixo:
Emitter {
id: rocketEmitter
anchors.bottom: parent.bottom
width: parent.width; height: 40
system: particleSystem
group: 'rocket'
emitRate: 2
maximumEmitted: 4
lifeSpan: 4800
lifeSpanVariation: 400
size: 32
velocity: AngleDirection { angle: 270; magnitude: 150; magnitudeVariation:
acceleration: AngleDirection { angle: 90; magnitude: 50 }
Tracer { color: 'red'; visible: root.tracer }
}

O emissor está no grupo "rocket", o mesmo que o nosso pintor de partículas de foguetes.
Através do nome do grupo, eles estão unidos. O emissor emite partículas no grupo ‘rocket’
e o pintor de partículas de foguete vai machucá-las.

Para o escape, usamos um emissor de trilha, que segue o nosso foguete. Declara um grupo
próprio chamado ‘smoke’ e segue as partículas do grupo ‘rocket’:
TrailEmitter {
id: smokeEmitter
system: particleSystem
emitHeight: 1
emitWidth: 4
group: 'smoke'
follow: 'rocket'
emitRatePerParticle: 96
velocity: AngleDirection { angle: 90; magnitude: 100; angleVariation: 5
lifeSpan: 200
size: 16

155
sizeVariation: 4
endSize: 0
}

A fumaça é direcionada para baixo para simular a força que a fumaça sai do foguete. O
emitHeight e emitWidthespecificam o que está ao redor da partícula seguido de onde as
partículas de fumaça devem ser emitidas. Se isso não for especificado, a partícula é
seguida, mas para este exemplo queremos aumentar o efeito que as partículas originam de
um ponto central próximo ao final do foguete.

Se você começar o exemplo agora, verá os foguetes voando e alguns até voando para fora
da cena. Como isso não é realmente desejado, precisamos atrasá-los antes que eles saiam
da tela. Um friction ode ser usado aqui para desacelerar as partículas até um limite
mínimo:
Friction {
groups: ['rocket']
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
threshold: 5
factor: 0.9
}

No friction você também precisa declarar quais grupos de partículas devem afetar. O
friction(atrito) retardará todos os foguetes, que são 80 pixels para baixo a partir do topo da
tela para baixo por um fator de 0,9 (tente 100 e você verá que eles quase param
imediatamente) até atingirem uma velocidade de 5 pixels por segundo. Como as partículas
ainda têm uma aceleração para baixo aplicada, os foguetes começarão a afundar em
direção ao solo depois que chegarem ao fim de sua vida útil.

Como subir no ar é um trabalho árduo e uma situação muito instável, queremos simular
algumas turbulências enquanto o navio está subindo:
Turbulence {
groups: ['rocket']
anchors.bottom: parent.bottom
width: parent.width; height: 160
system: particleSystem
strength: 25
Tracer { color: 'green'; visible: root.tracer }
}

Também a turbulência precisa declarar quais grupos afetará. A turbulência que ele mesmo
alcança dos 160 pixels de baixo para cima (até atingir a borda do atrito). Eles também
poderiam se sobrepor.

Quando você iniciar o exemplo, verá que os foguetes estão subindo e, em seguida, serão
desacelerados pelo atrito e retornarão ao solo pela aceleração ainda aplicada para baixo. A
próxima coisa seria começar o fogo de artifício.

156
Note

A imagem mostra a cena com os rastreadores habilitados para mostrar as diferentes


áreas. Partículas de foguete são emitidas na área vermelha e afetadas pela turbulência na
área azul. Finalmente eles são retardados pelo atrito na área verde e começam a cair
novamente, por causa da constante aceleração aplicada para baixo.

Que haja fogos de artifício

Para poder transformar o foguete em um lindo fogo de artifício, precisamos adicionar um


ParticleGrouppara encapsular as alterações:

ParticleGroup {
name: 'explosion'
system: particleSystem
}

Nós mudamos para o grupo de partículas usando um GroupGoal. O affector de meta de


grupo é colocado perto do centro vertical da tela e afetará o grupo ‘rocket’. Com a
propriedade groupGoal , definimos o grupo-alvo para a mudança para ‘explosion’, nosso
grupo de partículas definido anteriormente:
GroupGoal {
id: rocketChanger
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
groups: ['rocket']
goalState: 'explosion'
jump: true
Tracer { color: 'blue'; visible: root.tracer }
}

A propriedade jump declara que a mudança nos grupos deve ser imediata e não após uma
certa duração.

157
Note

No lançamento do Qt 5 alpha, a duration da mudança de grupo pode não funcionar.


Alguma ideia?

Como o grupo do foguete agora muda para o nosso grupo de partículas de ‘explosion’
quando a partícula de foguete entra na área de meta do grupo, precisamos adicionar o fogo
de artifício dentro do grupo de partículas:
// inside particle group
TrailEmitter {
id: explosionEmitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 750
emitRatePerParticle: 200
size: 32
velocity: AngleDirection { angle: -90; angleVariation: 180; magnitude: 50
}

A explosão emite partículas no grupo ‘sparkle’.Vamos definir em breve um pintor de


partículas para este grupo. O emissor da trilha usado segue a partícula do foguete e emite
por partículas do foguete 200. As partículas são direcionadas para cima e variam em 180
graus.

Como as partículas são emitidas no grupo ‘sparkle’, também precisamos definir um pintor
de partículas para as partículas:
ImageParticle {
id: sparklePainter
system: particleSystem
groups: ['sparkle']
color: 'red'
colorVariation: 0.6
source: "assets/star.png"
alpha: 0.3
}

Os brilhos dos nossos fogos de artifício devem ser pequenas estrelas vermelhas com uma
cor quase transparente para permitir alguns efeitos de brilho.

Para tornar o fogo de artifício mais espetacular, também adicionamos um segundo emissor
de trilhas ao nosso grupo de partículas, que emitirá partículas em um cone estreito para
baixo:
// inside particle group
TrailEmitter {
id: explosion2Emitter
anchors.fill: parent
group: 'sparkle'

158
follow: 'rocket'
lifeSpan: 250
emitRatePerParticle: 100
size: 32
velocity: AngleDirection { angle: 90; angleVariation: 15; magnitude: 400
}

Caso contrário, a configuração é semelhante ao outro emissor da trilha de explosão. É isso


aí.

Aqui está o resultado final.

Aqui está o código fonte completo do fogo de artifício do foguete.


import QtQuick 2.5
import QtQuick.Particles 2.0

Rectangle {
id: root
width: 480; height: 240
color: "#1F1F1F"
property bool tracer: false

ParticleSystem {
id: particleSystem
}

ImageParticle {
id: smokePainter
system: particleSystem
groups: ['smoke']
source: "assets/particle.png"
alpha: 0.3
}

ImageParticle {
id: rocketPainter
system: particleSystem
groups: ['rocket']
source: "assets/rocket.png"
entryEffect: ImageParticle.Fade

159
}

Emitter {
id: rocketEmitter
anchors.bottom: parent.bottom
width: parent.width; height: 40
system: particleSystem
group: 'rocket'
emitRate: 2
maximumEmitted: 8
lifeSpan: 4800
lifeSpanVariation: 400
size: 128
velocity: AngleDirection { angle: 270; magnitude: 150; magnitudeVariation
acceleration: AngleDirection { angle: 90; magnitude: 50 }
Tracer { color: 'red'; visible: root.tracer }
}

TrailEmitter {
id: smokeEmitter
system: particleSystem
group: 'smoke'
follow: 'rocket'
size: 16
sizeVariation: 8
emitRatePerParticle: 16
velocity: AngleDirection { angle: 90; magnitude: 100; angleVariation:
lifeSpan: 200
Tracer { color: 'blue'; visible: root.tracer }
}

Friction {
groups: ['rocket']
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
threshold: 5
factor: 0.9

Turbulence {
groups: ['rocket']
anchors.bottom: parent.bottom
width: parent.width; height: 160
system: particleSystem
strength:25
Tracer { color: 'green'; visible: root.tracer }
}

ImageParticle {
id: sparklePainter
system: particleSystem
groups: ['sparkle']
color: 'red'
colorVariation: 0.6
source: "assets/star.png"
alpha: 0.3

160
}

GroupGoal {
id: rocketChanger
anchors.top: parent.top
width: parent.width; height: 80
system: particleSystem
groups: ['rocket']
goalState: 'explosion'
jump: true
Tracer { color: 'blue'; visible: root.tracer }
}

ParticleGroup {
name: 'explosion'
system: particleSystem

TrailEmitter {
id: explosionEmitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 750
emitRatePerParticle: 200
size: 32
velocity: AngleDirection { angle: -90; angleVariation: 180; magnitude
}

TrailEmitter {
id: explosion2Emitter
anchors.fill: parent
group: 'sparkle'
follow: 'rocket'
lifeSpan: 250
emitRatePerParticle: 100
size: 32
velocity: AngleDirection { angle: 90; angleVariation: 15; magnitude:
}
}
}

8.8. Resumo
As partículas são uma maneira muito poderosa e divertida de expressar fenômenos
gráficos como uma fumaça. fogo de artifício, elementos visuais aleatórios. A API
estendida no Qt 5 é muito poderosa e acabamos de arranhar sua superfície. Existem vários
elementos com os quais ainda não jogamos, como sprites, tabelas de tamanho ou tabelas
de cores. Além disso, quando as partículas parecem muito divertidas, elas têm um grande
potencial quando usadas com sabedoria para criar algum tipo de apanhador de olhos em
qualquer interface de usuário. Usando muitos efeitos de partículas dentro de uma interface
de usuário vai certamente levar à impressão de um jogo. Criar jogos é também a força real
das partículas.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a

161
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

162
Qt5 Cadaques Book » previous | next

9. Efeito Shader
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado noassets folder.

Objetivo

http://doc.qt.io/qt-5/qml-qtquick-shadereffect.html
http://www.opengl.org/registry/doc/GLSLangSpec.4.20.6.clean.pdf
http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf
http://www.lighthouse3d.com/opengl/glsl/
http://wiki.delphigl.com/index.php/Tutorial_glsl
Qt5Doc qtquick-shaders [http://doc.qt.io/qt-5//qtquick-shaders.html]

Faça uma breve introdução aos efeitos do shader e, em seguida, apresente os efeitos do
shader e seu uso.

Shaders nos permite criar efeitos impressionantes de renderização no topo da API do


SceneGraph, aproveitando diretamente o poder do OpenGL em execução na GPU.Shaders
são implementados usando os elementos ShaderEffect e ShaderEffectSource.O algoritmo
do shader em si é implementado usando a OpenGL Shading Language.

Praticamente, significa que você mistura o código QML com o código do shader.Na
execução, o código do shader será enviado para a GPU e compilado e executado na
GPU.Os elementos shader do QML permitem que você interaja por meio de propriedades
com a implementação do shader OpenGL.

Vamos primeiro dar uma olhada nos shaders do OpenGL.

9.1. OpenGL Shaders


O OpenGL usa um pipeline de renderização dividido em estágios. Um pipeline OpenGL
simplificado conteria um vertex e um fragmento de shader.

163
O vertex shader recebe dados de vértices e deve atribuí-lo àgl_Positionda rotina.No
próximo estágio, os vertexes são cortados, transformados e rasterizados para saída de
pixels.A partir daí, os fragmentos (pixels) chegam ao sombreador do fragmento e podem
ainda ser manipulados e a cor resultante precisa ser atribuída aogl_FragColor. O vertex
shaderé chamado para cada ponto de canto de seu polígono(vertex = point in 3D)e é
responsável por qualquer manipulação 3D desses pontos.O fragmento(fragment = pixel)
shader é chamado para cada pixel e determina a cor desse pixel.

9.2. Elementos Shader


Para programar shaders, o Qt Quick fornece dois elementos. O ShaderEffectSource e o
ShaderEffect.O efeito do shader aplica-se a shaders personalizados e a fonte de efeito do
shader renderiza um item QML em uma textura e o renderiza.Como efeito de shader, você
pode aplicar-se shaders personalizados à sua forma retangular e usar fontes para a
operação do shader.Uma fonte pode ser uma imagem, que é usada como uma textura ou
como uma fonte de efeito de sombreamento.

O shader padrão usa a origem e a processa sem modificações.


import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Row {
anchors.centerIn: parent
spacing: 20
Image {
id: sourceImage
width: 80; height: width
source: 'assets/tulips.jpg'
}
ShaderEffect {
id: effect
width: 80; height: width
property variant source: sourceImage
}
ShaderEffect {
id: effect2
width: 80; height: width
// the source where the effect shall be applied to
property variant source: sourceImage
// default vertex shader code
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;

164
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
gl_Position = qt_Matrix * qt_Vertex;
}"
// default fragment shader code
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"
}
}
}

No exemplo acima, temos uma linha de 3 imagens.A primeira é a imagem real. O segundo
é renderizado usando o sombreador padrão e o terceiro é renderizado usando o código de
sombreador padrão para o fragmento e overtex extraídos do código-fonte do Qt 5.

Note

Se você não quiser ver a imagem de origem e apenas a imagem afetada, defina
aImagecomo invisível(visible : false).Os efeitos do shader ainda usarão os dados da
imagem, mas o elementoImagenão será renderizado.

Vamos dar uma olhada mais de perto no código do shader.


vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
gl_Position = qt_Matrix * qt_Vertex;
}"

165
Ambos os shaders são do lado do Qt, uma string ligada à propriedadevertexShader e
fragmentShader.Todo código de shader tem que ter uma funçãomain() { ... }, que é
executada pela GPU.Variáveis começando comqt_são fornecidas por padrão pelo Qt.

Here a short rundown on the variables:

uniform valor não muda durante o processamento


attribute ligação com dados externos
varying valor compartilhado entre shaders
highp valor de alta precisão
lowp valor de baixa precisão
mat4 Matrix float 4x4
vec2 2=dim float vector
sampler2D Textura 2D
float Scalar floating

Uma referência melhor é aOpenGL ES 2.0 API Quick Reference Card


[http://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf]

Agora podemos ser mais capazes de entender quais são as variáveis:

qt_Matrix:matriz de visão de projeção de modelo


qt_Vertex:posição Atual dovertex
qt_MultiTexCoord0: Coordenada de textura
qt_TexCoord0: Coordenada de textura compartilhada

Então temos disponível a matriz de projeção, o vertexatual e a coordenada de textura.A


coordenada de textura refere-se à textura dada como fonte.Na funçãomain()Na função
main () armazenamos a coordenada de textura para uso posterior no shader de
fragmento.Cada shader devertexprecisa atribuir agl_Position.Isso é feito usando aqui
multiplicando a matriz do projeto pelovertex, nosso ponto em 3D.

O shader de fragmento recebe nossa coordenada de textura do shader de vertexe também a


textura de nossa propriedade de origem QML.Deve-se notar como é fácil passar variáveis
entre o código do shader e o QML.Adicional nós temos a opacidade do efeito shader
disponível comoqt_Opacity.Cada fragment shader precisa atribuir a variávelgl_FragColor
variable, isso é feito no código de shader padrão escolhendo o pixel da textura de origem e
multiplicando-o com a opacidade.
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"

Durante os próximos exemplos, vamos brincar com algumas mecânicas simples de

166
shader.Primeiro nos concentramos no shader de fragmento e depois voltaremos ao
shadervertex.

9.3. Fragmento Shaders


O fragment shaderé chamado para cada pixel a ser renderizado. Vamos desenvolver uma
pequena lente vermelha, que irá aumentar o valor do canal de cor vermelha da imagem.

Configurando a cena

Primeiro nós configuramos nossa cena, com uma grade centrada no campo e nossa
imagem de origem exibida.
import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Grid {
anchors.centerIn: parent
spacing: 20
rows: 2; columns: 4
Image {
id: sourceImage
width: 80; height: width
source: 'assets/tulips.jpg'
}
}
}

Um shader vermelho

Em seguida, adicionaremos um shader, que exibe um retângulo vermelho, fornecendo para


cada fragmento um valor de cor vermelha.
fragmentShader: "

167
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * qt_Opacity;
}"

No fragment shadersimplesmente atribuímosvec4(1.0, 0.0, 0.0, 1.0)que representa uma cor


vermelha com opacidade total(alpha=1.0) para ogl_FragColorpara cada fragmento.

Um shader vermelho com textura

Agora queremos aplicar a cor vermelha a cada pixel de textura. Para isso, precisamos da
textura de volta novertex shader.Como não fazemos mais nada novertex shader o vertex
shader padrão é suficiente para nós.
ShaderEffect {
id: effect2
width: 80; height: width
property variant source: sourceImage
visible: root.step>1
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(1.0, 0.
}"
}

O shader completo contém agora de volta a nossa fonte de imagem como propriedade
variante e deixamos de fora o vertex shaderque, se não especificado, é overtex shader
padrão.

No fragment shader escolhemos o fragmento de texturatexture2D(source, qt_TexCoord0)e


aplique a cor vermelha a ele.

168
A propriedade do canal vermelho

Não é legal codificar o valor do canal vermelho, por isso gostaríamos de controlar o valor
do lado da QML.Para isso, adicionamos uma propriedaderedChannelao nosso efeito
shader e também declaramos umuniform lowp float redChanneluniforme dentro do
nossofragment shader.Isso é tudo para criar um valor a partir do código do shader
disponível para o lado da QML. Muito simples.
ShaderEffect {
id: effect3
width: 80; height: width
property variant source: sourceImage
property real redChannel: 0.3
visible: root.step>2
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform lowp float redChannel;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(redChan
}"
}

Para tornar a lente realmente uma lente, nós mudamos a corvec4para servec4(redChannel,
1.0, 1.0, 1.0)para que as outras cores sejam multiplicadas por 1,0 e somente a parte
vermelha é multiplicada pela nossa variávelredChannel.

169
O canal vermelho animado

Como a propriedaderedChannelé apenas uma propriedade normal, ela também pode ser
animada como todas as propriedades em QML.Assim, podemos usar as propriedades do
QML para animar valores na GPU para influenciar nossos shaders. Quão legal é isso!
ShaderEffect {
id: effect4
width: 80; height: width
property variant source: sourceImage
property real redChannel: 0.3
visible: root.step>3
NumberAnimation on redChannel {
from: 0.0; to: 1.0; loops: Animation.Infinite; duration: 4000
}

fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform lowp float redChannel;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(redChan
}"
}

Aqui o resultado final.

170
O efeito do shader na segunda linha é animado de 0.0 a 1.0 com uma duração de 4
segundos. Assim, a imagem passa de nenhuma informação vermelha (0,0 vermelho) para
uma imagem normal (1,0 vermelho).

9.4. Efeito Wave (onda)


Neste exemplo mais complexo, vamos criar um efeito de onda com o fragment shader. A
forma de onda é baseada na curva do sinus e influencia as coordenadas de textura usadas
para a cor.
import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Row {
anchors.centerIn: parent
spacing: 20
Image {
id: sourceImage
width: 160; height: width
source: "assets/coastline.jpg"
}
ShaderEffect {
width: 160; height: width
property variant source: sourceImage
property real frequency: 8
property real amplitude: 0.1
property real time: 0.0
NumberAnimation on time {
from: 0; to: Math.PI*2; duration: 1000; loops: Animation.Infinite
}

fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform highp float frequency;
uniform highp float amplitude;

171
uniform highp float time;
void main() {
highp vec2 pulse = sin(time - frequency * qt_TexCoord0);
highp vec2 coord = qt_TexCoord0 + amplitude * vec2(pulse.x, -
gl_FragColor = texture2D(source, coord) * qt_Opacity;
}"
}
}
}

O cálculo da onda é baseado em um pulso e na manipulação de coordenadas de textura. A


equação de pulso nos dá uma onda senoidal dependendo do tempo atual e da coordenada
de textura usada:
highp vec2 pulse = sin(time - frequency * qt_TexCoord0);

Sem o fator tempo, teríamos apenas uma distorção, mas não uma distorção da viagem,
como as ondas.

Para a cor, usamos a cor em uma coordenada de textura diferente:


highp vec2 coord = qt_TexCoord0 + amplitude * vec2(pulse.x, -pulse.x);

A coordenada da textura é influenciada pelo nosso valor x de pulso. O resultado disso é


uma onda em movimento.

Além disso, se não tivermos movido pixels nesse fragment shader, o efeito pareceria, a
princípio, um trabalho para um vertex shader.

9.5. Vertex Shader


O vertex shaderpode ser usado para manipular os vértices fornecidos pelo efeitoshader.Em
casos normais, o efeito do shader tem 4 vértices(top-left, top-right, bottom-left and
bottom-right).Cada vértice relatado é do tipovec4.Para visualizar overtex shadervamos
programar um efeito gênio.Este efeito é freqüentemente usado para deixar uma área de
janela retangular desaparecer em um ponto.

172
Configurando a cena

Primeiro vamos configurar nossa cena novamente.


import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Image {
id: sourceImage
width: 160; height: width
source: "assets/lighthouse.jpg"
visible: false
}
Rectangle {
width: 160; height: width
anchors.centerIn: parent
color: '#333333'
}
ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source: sourceImage
property bool minimized: false
MouseArea {
anchors.fill: parent
onClicked: genieEffect.minimized = !genieEffect.minimized
}
}
}

Isso fornece uma cena com um backgrounde um efeitoshaderusando uma imagem como a
textura de origem.A imagem original não é visível na imagem produzida pelo nosso efeito
genial.Adicionamos um retângulo escuro na mesma geometria que o efeito shader para
que possamos detectar melhor onde precisamos clicar para reverter o efeito.

173
O efeito é acionado clicando na imagem, isso é definido pela mouse areaque cobre o
efeito.No manipuladoronClickednós alternamos a propriedade booleana
personalizadaminimized. Usaremos essa propriedade mais tarde para alternar o efeito.

Minimize e normalize

Depois de configurarmos a cena, definimos uma propriedade do tipo real


chamadaminimize,a propriedade conterá o valor atual de nossaminimization. O valor irá
variar de 0,0 a 1,0 e é controlado por uma animação seqüencial.
property real minimize: 0.0

SequentialAnimation on minimize {
id: animMinimize
running: genieEffect.minimized
PauseAnimation { duration: 300 }
NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine
PauseAnimation { duration: 1000 }
}

SequentialAnimation on minimize {
id: animNormalize
running: !genieEffect.minimized
NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine
PauseAnimation { duration: 1300 }
}

A animação é acionada pela combinação da propriedademinimized.Agora que


configuramos todo o nosso entorno, finalmente podemos olhar para o nossovertex shader.
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
uniform highp float minimize;
uniform highp float width;
uniform highp float height;
void main() {

174
qt_TexCoord0 = qt_MultiTexCoord0;
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
pos.x = mix(qt_Vertex.x, width, minimize);
gl_Position = qt_Matrix * pos;
}"

O vertex shaderé chamado para cada vértice quatro vezes, no nosso caso.Os parâmetros
definidos qt padrão são fornecidos, comoqt_Matrix, qt_Vertex, qt_MultiTexCoord0,
qt_TexCoord0.Nós discutimos a variável já anteriormente.Adicionalmente nós ligamos as
variáveis minimize, width e height nosso efeito shader em nosso código vertex shader.Na
função principal, armazenamos a coordenada de textura atual em nossaqt_TexCoord0para
torná-lo disponível para ofragment shader.Agora copiamos a posição atual e modificamos
a posiçãox e y do vertex (vertice):
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
pos.x = mix(qt_Vertex.x, width, minimize);

A funçãomix(...)fornece uma interpolação linear entre os dois primeiros parâmetros no


ponto (0.0-1.0) fornecido pelo 3º parâmetro.Portanto, no nosso caso, interpolamos para y
entre a posição y atual e a altura com base no valor de minimização atual, semelhante para
x.Tenha em mente que o valor de minimização é animado pela nossa animação seqüencial
e viaja de 0,0 a 1,0 (ou vice-versa).

O efeito resultante não é realmente o efeito genial, mas já é um grande passo em direção a
ele.

Todo

melhor explicação, talvez desenhe os 4 vértices e sua interpolação

Dobra Primitiva

Então, minimizamos os componentes x e y de nossos vértices. Agora gostaríamos de

175
modificar levemente a manipulação x e torná-la dependendo do valor atual de y.As
mudanças necessárias são bem pequenas.A posição y é calculada como antes.A
interpolação da posição x depende agora da posição y dos vértices:
highp float t = pos.y / height;
pos.x = mix(qt_Vertex.x, width, t * minimize);

Isso resulta em uma posição x tendendo para a largura quando a posição y é maior. Em
outras palavras, os dois vértices superiores não são afetados, pois têm uma posição y de 0
e os dois vértices inferiores x posições que se dobram em direção à largura, de modo que
se inclinam na mesma posição x.

import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Image {
id: sourceImage
width: 160; height: width
source: "assets/lighthouse.jpg"
visible: false
}
Rectangle {
width: 160; height: width
anchors.centerIn: parent
color: '#333333'
}
ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source: sourceImage
property real minimize: 0.0
property bool minimized: false

SequentialAnimation on minimize {
id: animMinimize
running: genieEffect.minimized

176
PauseAnimation { duration: 300 }
NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine
PauseAnimation { duration: 1000 }
}

SequentialAnimation on minimize {
id: animNormalize
running: !genieEffect.minimized
NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine
PauseAnimation { duration: 1300 }
}

vertexShader: "
uniform highp mat4 qt_Matrix;
uniform highp float minimize;
uniform highp float height;
uniform highp float width;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
// M1>>
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
highp float t = pos.y / height;
pos.x = mix(qt_Vertex.x, width, t * minimize);
gl_Position = qt_Matrix * pos;

Melhor Bending (dobra)

Como o bendingnão é realmente satisfatória atualmente, vamos adicionar várias partes


para melhorar a situação.Primeiro, aprimoramos nossa animação para suportar uma
propriedade própria debending.Isso é necessário, pois obendingdeve acontecer
imediatamente e a minimização y deve ser adiada em breve.Ambas as animações têm na
soma a mesma duração(300+700+1000 and 700+1300).
property real bend: 0.0
property bool minimized: false

// change to parallel animation


ParallelAnimation {
id: animMinimize
running: genieEffect.minimized
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 1; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1000 }
}
// adding bend animation

177
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'bend'
to: 1; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
}

Adicional para tornar o bendinguma curva suave, o efeito y na posição x não é modificado
por uma função curva de 0..1 e apos.xdepende agora da nova animação da
propriedadebend:
highp float t = pos.y / height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, width, t * bend);

A curva começa suavemente no valor 0.0, cresce e pára suavemente em direção ao valor
1.0. Aqui está um gráfico da função no intervalo especificado. Para nós, apenas o intervalo
de 0..1 é de interesse.

A mudança mais visual é aumentando nossa quantidade de pontos de vértice. Os pontos de


vértices usados podem ser aumentados usando uma malha:
mesh: GridMesh { resolution: Qt.size(16, 16) }

O efeito shaderagora tem uma grade distribuída de igualdade de 16x16 vértices ao invés
dos vértices 2x2 usados anteriormente.Isso faz com que a interpolação entre os vértices
pareça muito mais suave.

178
Você pode ver também a influência da curva que está sendo usada, bem como os bending
smoothesno final.É aqui que obending tem o efeito mais forte.

Escolhendo Lados

Como um aprimoramento final, queremos poder mudar de lado.O lado (side) é em direção
ao qual o efeito gênio desaparece.Até agora desaparece sempre em direção à largura.Ao
adicionar uma propriedadeside, podemos modificar o ponto entre 0 e largura.
ShaderEffect {
...
property real side: 0.5

vertexShader: "
...
uniform highp float side;
...
pos.x = mix(qt_Vertex.x, side * width, t * bend);
"
}

Embalagem

A última coisa a fazer é empacotar nosso efeito muito bem.Para isso, extraímos nosso

179
código de efeito genial em um componente próprio chamadoGenieEffect.Tem o
efeitoshadercomo o elemento raiz.Removemos amouse areapois isso não deveria estar
dentro do componente, pois o acionamento do efeito pode ser alternado pela
propriedademinimized.
import QtQuick 2.5

ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source
mesh: GridMesh { resolution: Qt.size(10, 10) }
property real minimize: 0.0
property real bend: 0.0
property bool minimized: false
property real side: 1.0

ParallelAnimation {
id: animMinimize
running: genieEffect.minimized
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 1; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1000 }
}
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'bend'
to: 1; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
}

ParallelAnimation {
id: animNormalize
running: !genieEffect.minimized
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 0; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1300 }
}
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'bend'
to: 0; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1000 }

180
}
}

vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
uniform highp float height;
uniform highp float width;
uniform highp float minimize;
uniform highp float bend;
uniform highp float side;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
highp float t = pos.y / height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, side * width, t * bend);
gl_Position = qt_Matrix * pos;
}"
}

Você pode usar agora o efeito simplesmente assim:


import QtQuick 2.5

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

GenieEffect {
source: Image { source: 'assets/lighthouse.jpg' }
MouseArea {
anchors.fill: parent
onClicked: parent.minimized = !parent.minimized
}
}
}

Nós simplificamos o código removendo nosso retângulo de background e atribuímos a


imagem diretamente ao efeito, em vez de carregá-la dentro de um elemento de imagem
independente.

9.6. Efeito cortina


No último exemplo de efeitos shader, eu gostaria de lhe trazer o efeito de cortina. Este
efeito foi publicado em maio de 2011 como parte do Qt labs for shader effects
[http://labs.qt.nokia.com/2011/05/03/qml-shadereffectitem-on-qgraphicsview/].

181
Naquela época eu realmente amava esses efeitos e o efeito de cortina era o meu favorito
deles. Eu simplesmente amo como a cortina se abre e escondo o objeto do background.

Peguei o código e o adaptei para o Qt 5, que era simples. Também fez algumas
simplificações para poder usá-lo melhor para um mostruário. Então, se você estiver
interessado no exemplo completo, visite o blog do laboratório.

Apenas um pequeno bot para o background, a cortina é na verdade uma imagem


chamadafabric.jpg e é a fonte de um efeito shader. O efeito vertex shader para balançar a
cortina e usa o fragment shader para fornecer algumas shades. Aqui está um diagrama
simples para você entender melhor o código.

A shades waved (onduladas) da cortina são calculadas através de uma curva sin com 7
up/downs (7*PI=21.99...) na largura da cortina. A outra parte importante é o swing. A
parte topWidth da cortina é animada quando a cortina é aberta ou fechada. O bottomWidth
segue o topWidth com um SpringAnimation. Com isso, criamos o efeito da parte inferior
oscilante da cortina. O swing fornece a força deste balanço interpolado sobre o
componente y dos vértices (vertexes).

O efeito de cortina está localizado no componente CurtainEffect.qml onde a imagem da

182
malha age como a fonte de textura. Não há nada de novo no uso de shaders aqui, apenas
uma maneira diferente de manipular a gl_Position do vértice e o gl_FragColor no
fragment shader.
import QtQuick 2.5

ShaderEffect {
anchors.fill: parent

mesh: GridMesh {
resolution: Qt.size(50, 50)
}

property real topWidth: open?width:20


property real bottomWidth: topWidth
property real amplitude: 0.1
property bool open: false
property variant source: effectSource

Behavior on bottomWidth {
SpringAnimation {
easing.type: Easing.OutElastic;
velocity: 250; mass: 1.5;
spring: 0.5; damping: 0.05
}
}

Behavior on topWidth {
NumberAnimation { duration: 1000 }
}

ShaderEffectSource {
id: effectSource
sourceItem: effectImage;
hideSource: true
}

Image {
id: effectImage
anchors.fill: parent
source: "assets/fabric.png"
fillMode: Image.Tile
}

vertexShader: "
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
uniform highp mat4 qt_Matrix;
varying highp vec2 qt_TexCoord0;
varying lowp float shade;

uniform highp float topWidth;


uniform highp float bottomWidth;
uniform highp float width;
uniform highp float height;
uniform highp float amplitude;

183
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;

highp vec4 shift = vec4(0.0, 0.0, 0.0, 0.0);


highp float swing = (topWidth - bottomWidth) * (qt_Vertex.y / height)
shift.x = qt_Vertex.x * (width - topWidth + swing) / width;

shade = sin(21.9911486 * qt_Vertex.x / width);


shift.y = amplitude * (width - topWidth + swing) * shade;

gl_Position = qt_Matrix * (qt_Vertex - shift);

shade = 0.2 * (2.0 - shade ) * ((width - topWidth + swing) / width);


}"

fragmentShader: "
uniform sampler2D source;
varying highp vec2 qt_TexCoord0;
varying lowp float shade;
void main() {
highp vec4 color = texture2D(source, qt_TexCoord0);
color.rgb *= 1.0 - shade;
gl_FragColor = color;
}"
}

The effect is used in the curtaindemo.qml file.


import QtQuick 2.5

Item {
id: root
width: background.width; height: background.height

Image {
id: background
anchors.centerIn: parent
source: 'assets/background.png'
}

Text {
anchors.centerIn: parent
font.pixelSize: 48
color: '#efefef'
text: 'Qt5 Cadaques'
}

CurtainEffect {
id: curtain
anchors.fill: parent
}

MouseArea {
anchors.fill: parent
onClicked: curtain.open = !curtain.open
}
}

184
A cortina é aberta através de uma propriedade open property personalizada no efeito de
cortina. Nós usamos um MouseArea para acionar a abertura e fechamento da cortina.

9.7. Biblioteca Qt GraphicsEffect


A biblioteca de graphics é uma coleção de efeitos shader. Feito pelos desenvolvedores do
Qt. É um ótimo conjunto de ferramentas para ser usado em seu aplicativo, mas também
uma ótima fonte para aprender a construir shaders.

A biblioteca de graphics effects vem com o chamado testbed manual, que é uma ótima
ferramenta para descobrir interativamente os diferentes efeitos.

O testbed está localizado abaixo$QTDIR/qtgraphicaleffects/tests/manual/testbed.

A effects library contém cerca de 20 efeitos. Uma lista do efeito e uma breve descrição
podem ser encontradas abaixo.

Graphics Effects List

Lista de efeitos gráficos


Categoria Efeito Descrição
mescla dois itens de origem usando um
Blend Blend
modo de mesclagem
Color BrightnessContrast ajusta brilho e contraste
Colorize define cor no espaço de cores HSL
ColorOverlay aplica uma camada de cor
Desaturate reduz a saturação de cor
GammaAdjust ajusta a luminância
HueSaturation ajusta cores no espaço de cores HSL
LevelAdjust ajusta as cores no espaço de cores RGB
Gradient ConicalGradient desenha um gradiente cônico
LinearGradient desenha um gradiente linear
RadialGradient desenha um gradiente radial
move os pixels do item de origem de acordo

185
com a fonte de deslocamento especificada
Drop Shadow DropShadow desenha uma sombra
InnerShadow desenha uma sombra interna
Blur FastBlur aplica um efeito de desfoque rápido
aplica um efeito de desfoque de qualidade
GaussianBlur
superior
aplica um efeito de desfoque de intensidade
MaskedBlur
variável
desfoca repetidamente, fornecendo um forte
RecursiveBlur
efeito de desfoque
aplica um efeito de desfoque de movimento
Motion Blur DirectionalBlur
direcional
aplica um efeito de desfoque de movimento
RadialBlur
radial
aplica um efeito de desfocagem do
ZoomBlur
movimento do zoom
Glow Glow desenha um efeito de brilho externo
desenha um efeito de brilho externo
RectangularGlow
retangular
Mask OpacityMask mascara o item de origem com outro item
mascara o item de origem com outro item e
ThresholdMask
aplica um valor limite

Aqui está um exemplo usando o efeito FastBlur da categoria Blur:


import QtQuick 2.5
import QtGraphicalEffects 1.0

Rectangle {
width: 480; height: 240
color: '#1e1e1e'

Row {
anchors.centerIn: parent
spacing: 16

Image {
id: sourceImage
source: "assets/tulips.jpg"
width: 200; height: width
sourceSize: Qt.size(parent.width, parent.height)
smooth: true
}

FastBlur {
width: 200; height: width
source: sourceImage
radius: blurred?32:0
property bool blurred: false

186
Behavior on radius {
NumberAnimation { duration: 1000 }
}

MouseArea {
id: area
anchors.fill: parent
onClicked: parent.blurred = !parent.blurred
}
}
}
}

A imagem à esquerda é a imagem original. Clicar na imagem à direita alternará a


propriedade desfocada e animará o raio do desfoque de 0 a 32 durante 1 segundo. A
imagem à direita mostra a imagem desfocada.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

187
Qt5 Cadaques Book » previous | next

10. Multimídia
Section author: e8johan [https://bitbucket.org/e8johan]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Os elementos multimídia do QtMultimedia possibilitam a reprodução e gravação de mídia,


como som, vídeo ou imagens. A decodificação e a codificação são tratadas por meio de
back-ends específicos da plataforma. Por exemplo, o popular framework gstreamer é
usado no Linux, enquanto o DirectShow é usado no Windows e o QuickTime no OS X.

Os elementos multimídia não fazem parte da API principal do Qt Quick. Em vez disso,
eles são fornecidos por meio de uma API separada disponibilizada importando o
QtMultimedia 5.6 conforme mostrado abaixo:
import QtMultimedia 5.6

10.1. Jogando com a Mídia


O caso mais básico de integração multimídia em um aplicativo QML é a reprodução de
mídia. Isso é feito usando o elemento MediaPlayer opcionalmente em combinação com
um elementoVideoOutput se a origem for uma imagem ou um vídeo. O elemento
MediaPlayer possui uma propriedade source apontando para a mídia a ser
reproduzida.Quando uma fonte de mídia foi ligada, é simplesmente uma questão de
chamar a função de play para começar a tocar.

Se você deseja reproduzir mídia visual, ou seja, imagens ou vídeo, também deve
configurar um elemento VideoOutput.executando a reprodução está vinculado à saída de
vídeo através da propriedadesource.

No exemplo mostrado abaixo, o MediaPlayerrecebe um arquivo com conteúdo de vídeo


como source. UmVideoOutputé criado e vinculado ao media player. Assim que o
componente principal for totalmente inicializado, ou seja, no Component.onCompleted, a
função de reprodução do play é chamada.
import QtQuick 2.5
import QtMultimedia 5.6

Item {

188
width: 1024
height: 600

MediaPlayer {
id: player
source: "trailer_400p.ogg"
}

VideoOutput {
anchors.fill: parent
source: player
}

Component.onCompleted: {
player.play();
}
}
// M1>>

Operações básicas, como alterar o volume ao reproduzir mídia, são controladas pela
propriedade volume do elemento MediaPlayer. Existem outras propriedades úteis também.
Por exemplo, as propriedades duration e position podem ser usadas para construir uma
barra de progresso. Se a propriedade seekable fortrue, é possível até mesmo atualizar a
position quando a barra de progresso é tocada. O exemplo abaixo mostra como isso é
adicionado ao exemplo de reprodução básica acima.
Rectangle {
id: progressBar

anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 100

height: 30

color: "lightGray"

Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom

width: player.duration>0?parent.width*player.position/player.duration

color: "darkGray"
}

MouseArea {
anchors.fill: parent

onClicked: {
if (player.seekable) {
player.position = player.duration * mouse.x/width;
}
}
}

189
}

A propriedadeposition é atualizada apenas uma vez por segundo no caso padrão. Isso
significa que a barra de progresso será atualizada em grandes etapas, a menos que a
duração da mídia seja longa o suficiente, em comparação com o número de pixels que a
barra de progresso é larga. Isso pode, no entanto, ser alterado através do acesso à
propriedade mediaObject e sua propriedade notifyInterval. Ele pode ser configurado
para o número de milissegundos entre cada atualização de posição, aumentando a
suavidade da interface do usuário.
Connections {
target: player
onMediaObjectChanged: {
if (player.mediaObject) {
player.mediaObject.notifyInterval = 50;
}
}
}

Todo

O código acima não tem nenhum efeito no intervalo de atualização! Parece não haver
objeto de mídia ...

Ao usar o MediaPlayer para criar um media player, é bom monitorar a propriedade


status do player. É uma enumeração dos possíveis status, variando de
MediaPlayer.Buffered``para``MediaPlayer.InvalidMedia. Os valores possíveis estão
resumidos nos marcadores abaixo:

MediaPlayer.UnknownStatus. O status é desconhecido.


MediaPlayer.NoMedia. O player não possui uma fonte de mídia atribuída. A
reprodução está parada.
MediaPlayer.Loading. O Player está carregando a mídia.
MediaPlayer.Loaded. A mídia foi carregada. A reprodução está parada.
MediaPlayer.Stalled. O carregamento de mídia parou.
MediaPlayer.Buffering. A mídia está sendo armazenada em buffer.
MediaPlayer.Buffered. A mídia foi armazenada em buffer, isso significa que o
player pode começar a reproduzir a mídia.
MediaPlayer.EndOfMedia. O fim da mídia foi alcançado. A reprodução está parada.
MediaPlayer.InvalidMedia. A mídia não pode ser reproduzida. A reprodução está
parada.

Como mencionado nos marcadores acima, o estado de reprodução pode variar com o
tempo. Chamar oplay, pause oustop altera o estado, mas a mídia em questão também
pode ter efeito. Por exemplo, o fim pode ser alcançado ou pode ser inválido, fazendo com
que a reprodução pare. O estado de reprodução atual pode ser rastreado através da
propriedade playbackState. Os valores podem ser MediaPlayer.PlayingState,

190
MediaPlayer.PausedState ouMediaPlayer.StoppedState.

Usando a propriedade autoPlay, o MediaPlayer pode tentar ir para o estado de


reprodução assim que a propriedade source for alterada. Uma propriedade similar é o
autoLoad com que o player tente carregar a mídia assim que a propriedade source for
alterada. The latter property is enabled by default.

Também é possível deixar o MediaPlayer fazer um loop em um item de mídia.A


propriedade loops controla quantas vezes osource deve ser reproduzido. Definir a
propriedade como MediaPlayer.Infinite causa um loop infinito. Ótimo para animações
contínuas ou uma música de fundo em loop.

10.2. Efeitos Sonoros


Ao reproduzir efeitos sonoros, o tempo de resposta desde a solicitação de reprodução até a
reprodução real se torna importante. Nessa situação, o elemento SoundEffect é útil. Ao
configurar a propriedade source,uma simples chamada para a função de reprodução
inicia imediatamente a reprodução.

Isso pode ser utilizado para feedback de áudio ao tocar na tela, conforme mostrado abaixo.
SoundEffect {
id: beep
source: "beep.wav"
}

Rectangle {
id: button

anchors.centerIn: parent

width: 200
height: 100

color: "red"

MouseArea {
anchors.fill: parent
onClicked: beep.play()
}
}

O elemento também pode ser utilizado para acompanhar uma transição com áudio. Para
acionar a reprodução de uma transição, o elementoScriptAction é usado.
SoundEffect {
id: swosh
source: "swosh.wav"
}

transitions: [
Transition {

191
ParallelAnimation {
ScriptAction { script: swosh.play(); }
PropertyAnimation { properties: "rotation"; duration: 200; }
}
}
]

Além da função play, um número de propriedades semelhantes às oferecidas pelo


MediaPlayer estão disponíveis. Exemplos são volume e loops. O último pode ser definido
como SoundEffect.Infinite para reprodução infinita. Para interromper a reprodução,
chame a função stop.

Note

Quando o backend do PulseAudio for usado, a parada não será interrompida


instantaneamente, mas apenas evitará loops adicionais. Isso ocorre devido a limitações
na API subjacente.

10.3. Streams de Video


O elemento VideoOutput não está limitado ao uso em combinação com elementos
doMediaPlayer. Também pode ser usado diretamente com fontes de vídeo para mostrar
um fluxo de vídeo ao vivo. Usando um elemento Camera como source,o aplicativo está
completo. O fluxo de vídeo de umaCamera pode ser usado para fornecer uma transmissão
ao vivo para o usuário. Este fluxo funciona como a visualização de pesquisa ao capturar
fotos.
import QtQuick 2.5
import QtMultimedia 5.6

Item {
width: 1024
height: 600

VideoOutput {
anchors.fill: parent
source: camera
}

Camera {
id: camera
}
}

10.4. Capturando Imagens


Um dos principais recursos do elemento Camera é que ele pode ser usado para tirar fotos.
Vamos usar isso em um aplicativo simples de stop-motion. Nele, você aprenderá a mostrar

192
um visor, tirar fotos e acompanhar as fotos tiradas.

A interface do usuário é mostrada abaixo. Consiste em três partes principais. No fundo,


você encontrará o visor, à direita, uma coluna de botões e, na parte inferior, uma lista de
imagens tiradas. A ideia é tirar uma série de fotos e clicar no botão Play Sequence. Isso
reproduzirá as imagens, criando um filme de stop motion simples.

A parte do visor da câmera é simplesmente um elementoCamera usado como source em


umVideoOutput. Isso mostrará ao usuário um videostream ao vivo da câmera.
VideoOutput {
anchors.fill: parent
source: camera
}

Camera {
id: camera
}

A lista de fotos é um ListView orientado horizontalmente mostra imagens de um


ListModel chamadoimagePaths. No fundo, um Rectangle preto semitransparente é
usado.
ListModel {
id: imagePaths
}

ListView {
id: listView

anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 10

height: 100

193
orientation: ListView.Horizontal
spacing: 10

model: imagePaths

delegate: Image {
height: 100
source: path
fillMode: Image.PreserveAspectFit
}

Rectangle {
anchors.fill: parent
anchors.topMargin: -10

color: "black"
opacity: 0.5
}
}

Para o disparo de imagens, você precisa saber que o elemento Camera contém um conjunto
de subelementos para várias tarefas. Para capturar imagens estáticas, o elemento
Camera.imageCaptureé usado. Quando você chama o método capture, uma foto é tirada.
Isso faz com que o Camera.imageCapture emita primeiro o sinal imageCaptured seguido
pelo sinalimageSaved.
Button {
id: shotButton

text: "Take Photo"


onClicked: {
camera.imageCapture.capture();
}
}

Para interceptar os sinais de um subelemento, é necessário um elemento Connections.


Nesse caso, não precisamos mostrar uma imagem de visualização, mas basta adicionar a
imagem resultante ao ListViewna parte inferior da tela. Mostrado no exemplo abaixo, o
caminho para a imagem salva é fornecido como o argumento do path com o sinal.
Connections {
target: camera.imageCapture

onImageSaved: {
imagePaths.append({"path": path})
listView.positionViewAtEnd();
}
}

Para mostrar uma visualização, conecte-se ao imageCaptured e use o argumentopreview


como source de um elemento Image. Um argumento de requestId é enviado ao longo de
ambos imageCaptured e imageSaved. Este valor é retornado do método capture. Usando
isso, a captura de uma imagem pode ser rastreada através do ciclo completo. Dessa forma,
a visualização pode ser usada primeiro e, em seguida, ser substituída pela imagem salva

194
corretamente. Isso, no entanto, não é nada que façamos no exemplo.

A última parte do aplicativo é a reprodução real. Isso é controlado usando um elemento


Timer e um pouco de JavaScript. A variável_imageIndex é usada para acompanhar a
imagem mostrada atualmente. Quando a última imagem tiver sido exibida, a reprodução
será interrompida. No exemplo, oroot.state é usado para ocultar partes da interface do
usuário ao reproduzir a sequência.
property int _imageIndex: -1

function startPlayback()
{
root.state = "playing";
setImageIndex(0);
playTimer.start();
}

function setImageIndex(i)
{
_imageIndex = i;

if (_imageIndex >= 0 && _imageIndex < imagePaths.count)


image.source = imagePaths.get(_imageIndex).path;
else
image.source = "";
}

Timer {
id: playTimer

interval: 200
repeat: false

onTriggered: {
if (_imageIndex + 1 < imagePaths.count)
{
setImageIndex(_imageIndex + 1);
playTimer.start();
}
else
{
setImageIndex(-1);
root.state = "";
}
}
}

10.5. Técnicas Avançadas


Todo

A API de câmera do Qt 5 está com falta de documentação no momento. Eu adoraria


cobrir controles de câmera mais avançados, como exposição e foco, mas não há
intervalos ou valores, nem guias claros sobre como usar as APIs nos documentos de

195
referência agora.

10.5.1. Implementando uma lista de reprodução

A API multimídia do Qt 5 não oferece suporte para listas de reprodução. Sorte, é fácil
construir um. A ideia é poder configurá-lo com um modelo de itens e um elemento
MediaPlayer como mostrado abaixo. O elemento Playlist é responsável por definir
osourceenquanto o MediaPlayer, enquanto o playstate é controlado pelo player.
MediaPlayer {
id: player
playlist: Playlist {
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
}
}

A primeira metade do elemento Playlist mostrada abaixo, cuida da configuração do


elemento source dado um índice na funçãosetIndex. Também implementa as funções
next e previous para navegar na lista.

Item {
id: root

property int index: 0


property MediaPlayer mediaPlayer
property ListModel items: ListModel {}

function setIndex(i) {
console.log("setting index to: " + i);

index = i;

if (index < 0 || index >= items.count) {


index = -1;
mediaPlayer.source = "";
} else {
mediaPlayer.source = items.get(index).source;
}
}

function next() {
setIndex(index + 1);
}

function previous() {
setIndex(index + 1);
}

O truque para fazer a lista de reprodução continuar para o próximo elemento no final de
cada elemento é monitorar a propriedade de status do statusoMediaPlayer. Assim que o
estado MediaPlayer.EndOfMediafor atingido, o índice será aumentado e a reprodução

196
retomada ou, se o final da lista for atingido, a reprodução será interrompida.
Connections {
target: root.mediaPlayer

onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia) {
root.next();
if (root.index == -1) {
root.mediaPlayer.stop();
} else {
root.mediaPlayer.play();
}
}
}
}

10.6. Resumo
A API de mídia fornecida pelo Qt fornece mecanismos para reproduzir e capturar vídeo e
áudio. Através do elemento VideoOutput a fonte de vídeo pode ser exibida na interface do
usuário. Através do elemento MediaPlayera maioria das reproduções podem ser
manipuladas, mesmo que o SoundEffect possa ser usado para sons de baixa latência. Para
capturar ou exibir apenas um fluxo de vídeo ao vivo, o elemento Camera element é usado.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

197
Qt5 Cadaques Book » previous | next

11. Networking
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Qt 5 vem com um rico conjunto de classes de rede no lado C ++. Há, por exemplo, classes
de alto nível na camada de protocolo http em uma forma de solicitação-resposta, como
QNetworkRequest, QNetworkReply e QNetworkAccessManager. Mas também classes de
níveis mais baixos na camada do protocolo TCP/IP ou UDP, como QTcpSocket,
QTcpServer e QUdpSocket. Existem classes adicionais para gerenciar proxies, cache de
rede e também a configuração de rede do sistema.

Este capítulo não será sobre redes C ++, este capítulo é sobre Qt Quick e networking.
Então, como posso conectar minha interface de usuário QML/JS diretamente a um serviço
de rede ou como posso servir minha interface de usuário através de um serviço de rede?
Existem bons livros e referências para cobrir a programação em rede com o Qt/C ++.
Então, é apenas uma maneira de ler o capítulo sobre a integração de C ++ para criar uma
camada de integração para alimentar seus dados no mundo do Qt Quick.

11.1. Servindo a UI via HTTP


Para carregar uma interface de usuário simples via HTTP, precisamos ter um servidor web,
que serve os documentos UI. Começamos com nosso próprio servidor web simples usando
um one-liner python. Mas primeiro precisamos ter nossa interface de usuário de
demonstração. Para isso, criamos um pequeno arquivo main.qml na pasta do nosso projeto
e criamos um retângulo vermelho no interior.
// main.qml
import QtQuick 2.5

Rectangle {
width: 320
height: 320
color: '#ff0000'
}

Para servir este arquivo, lançamos um pequeno script python:


$ cd <PROJECT>

198
# python -m SimpleHTTPServer 8080

Agora nosso arquivo deve estar acessível via http://localhost:8080/main.qml. Você


pode testá-lo com:
$ curl http://localhost:8080/main.qml

Ou apenas aponte seu navegador para o local. Seu navegador não entende o QML e não
será capaz de processar o documento. Precisamos criar agora esse navegador para
documentos QML.Para renderizar o documento, precisamos apontar nosso qmlscene para
o local. Infelizmente, o qmlscenestá limitado apenas a arquivos locais. Poderíamos superar
essa limitação escrevendo nossa própria substituição doqmlscene ou simples carregá-la
dinamicamente usando QML. Nós escolhemos o carregamento dinâmico, pois funciona
bem. Para isso, usamos um elemento loader para recuperar o documento remoto.
// remote.qml
import QtQuick 2.5

Loader {
id: root
source: 'http://localhost:8080/main2.qml'
onLoaded: {
root.width = item.width
root.height = item.height
}
}

Now we can ask the qmlscene to load the local remote.qml loader document. There is one
glitch still. The loader will resize to the size of the loaded item. And our qmlscene needs
also to adapt to that size. This can be accomplished using the --resize-to-root option to
the qmlscene:
$ qmlscene --resize-to-root remote.qml

Redimensionar para raiz informa a qml scene para redimensionar sua janela para o
tamanho do elemento raiz. O controle remoto agora está carregando omain.qml do nosso
servidor local e se redimensiona para a interface de usuário carregada. Doce e simples.

Note

Se você não quer rodar um servidor local, você também pode usar o serviço gist do
GitHub. O Gist é uma área de transferência como o serviço online, como o PasteBin e
outros. Está disponível em https://gist.github.com . Eu criei para este exemplo uma
pequena essência sob o URhttps://gist.github.com/jryannel/7983492 . Isso revelará um
retângulo verde. Como o URL do gist fornecerá o site como código HTML, precisamos
anexar um /raw ao url para recuperar o arquivo raw (bruto) e não o código HTML.
// remote.qml
import QtQuick 2.5

Loader {

199
id: root
source: 'https://gist.github.com/jryannel/7983492/raw'
onLoaded: {
root.width = item.width
root.height = item.height
}
}

Para carregar outro arquivo pela rede, basta referenciar o nome do componente. Por
exemplo, um Button.qml pode ser acessado normalmente, contanto que esteja na mesma
pasta remota.

Todo

Isso é verdade? Quais são as regras?

11.1.1. Componentes em Rede

Vamos criar uma pequena experiência. Adicionamos ao nosso lado remoto um pequeno
botão como um componente reutilizável.
- src/main.qml
- src/Button.qml

Modificamos nosso main.qml para usar o botão e salvá-lo como main2.qml:


import QtQuick 2.5

Rectangle {
width: 320
height: 320
color: '#ff0000'

Button {
anchors.centerIn: parent
text: 'Click Me'
onClicked: Qt.quit()
}
}

E inicie o nosso servidor web novamente:


$ cd src
# python -m SimpleHTTPServer 8080

E o nosso carregador remoto carrega o QML principal novamente via http


$ qmlscene --resize-to-root remote.qml

O que vemos é um erro:

200
http://localhost:8080/main2.qml:11:5: Button is not a type

Portanto, o QML não pode resolver o componente do botão quando ele é carregado
remotamente. Se o código fosse localmenteqmlscene src/main.qml isso não seria
problema. Localmente, o Qt pode analisar o diretório e detectar quais componentes estão
disponíveis, mas remotamente não existe a função “list-dir” para http. Podemos forçar o
QML a carregar o elemento usando a instrução de importação dentro domain.qml:
import "http://localhost:8080" como remoto

...

Remote.Button { ... }

Isso funcionará quando o qmlscene for executado novamente:


$ qmlscene --resize-to-root remote.qml

Aqui o código completo:


// main2.qml
import QtQuick 2.5
import "http://localhost:8080" 1.0 as Remote

Rectangle {
width: 320
height: 320
color: '#ff0000'

Remote.Button {
anchors.centerIn: parent
text: 'Click Me'
onClicked: Qt.quit()
}
}

Uma opção melhor é usar o arquivo qmldir no lado do servidor para controlar a
exportação.
// qmldir
Button 1.0 Button.qml

E então atualizando o main.qml:


import "http://localhost:8080" 1.0 as Remote

...

Remote.Button { ... }

Note

Carregando

201
Ao usar componentes de um sistema de arquivos local, eles são criados imediatamente
sem latência. Quando os componentes são carregados pela rede, eles são criados de forma
assíncrona. Isso tem o efeito de que o tempo de criação é desconhecido e um elemento
pode ainda não estar totalmente carregado quando outros já estiverem concluídos. Leve
isso em consideração ao trabalhar com componentes carregados na rede.

11.2. Templating
Ao trabalhar com projetos HTML, eles geralmente usam o desenvolvimento orientado por
modelos. Um pequeno stub HTML é expandido no lado do servidor com código gerado
pelo servidor usando um mecanismo de modelo. Por exemplo, para uma lista de fotos, o
cabeçalho da lista seria codificado em HTML e a lista de imagens dinâmicas seria gerada
dinamicamente usando um mecanismo de modelo. Em geral, isso também pode ser feito
usando QML, mas há alguns problemas com isso.

Primeiro não é necessário. A razão pela qual os desenvolvedores de HTML estão fazendo
isso é superar as limitações do backend HTML. Ainda não existe nenhum modelo de
componente em HTML, por isso, os aspectos dinâmicos devem ser cobertos usando esse
mecanismo ou usando-se programaticamente o javascript no lado do cliente. Muitos
frameworks JSestão disponíveis (jQuery, dojo, backbone, angular, ...)ara resolver esse
problema e colocar mais lógica no navegador do lado do cliente para se conectar a um
serviço de rede. The client would then just use a web-service API (ex: servindo dados
JSON ou XML) para se comunicar com o servidor. Essa parece também a melhor
abordagem para o QML.

O segundo problema é o cache de componentes QML. Quando o QML acessa um


componente, ele armazena em cache a árvore de renderização e apenas carrega a versão
em cache para renderização. Uma versão modificada no disco ou remoto não seria
detectada sem reiniciar o cliente. Para superar esse problema, poderíamos usar um truque.
Poderíamos usar fragmentos de URL para carregar o URL
(exhttp://localhost:8080/main.qml#1234), onde ‘#1234’ é o fragmento. O servidor HTTP
sempre exibe o mesmo documento, mas o QML armazena este documento usando o URL
completo, incluindo o fragmento. Toda vez que acessarmos essa URL, o fragmento
precisaria ser alterado e o cache QML não receberá um resultado positivo. Um fragmento
pode ser, por exemplo, o tempo atual em mili segundos ou um número aleatório.
Loader {
source: 'http://localhost:8080/main.qml#' + new Date().getTime()
}

Em resumo, a criação de modelos é possível, mas não é realmente recomendada e não


atende à força do QML. Uma abordagem melhor é usar os serviços da Web que fornecem
dados JSON ou XML.

11.3. Solicitações HTTP

202
Uma requisição http está em Qt tipicamente feita usandoQNetworkRequest e
QNetworkReply a partir do site c ++ e então a resposta seria empurrada usando a
integração Qt / C ++ para o espaço QML. Por isso, tentamos colocar um pouco de sobra
aqui para usar as ferramentas atuais que o Qt Quick nos fornece para nos comunicarmos
com um endpoint de rede. Para isso, usamos um objeto auxiliar para fazer o pedido de
http, ciclo de resposta. Ele vem na forma do objeto XMLHttpRequestdo javascript

O XMLHttpRequestTheXMLHttpRequestpermite ao usuário registrar uma função de


identificador de resposta e uma URL. Uma solicitação pode ser enviada usando um dos
verbos http (get, post, put, delete, ...) para fazer o pedido. Quando a resposta chega, a
função do manipulador é chamada. A função de manuseio é chamada várias vezes. Toda
vez que o estado da solicitação foi alterado (por exemplo, os cabeçalhos(headers)
chegaram ou a solicitação está concluída).

Aqui um pequeno exemplo:


function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED');
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE');
}
}
xhr.open("GET", "http://example.com");
xhr.send();
}

Para uma resposta, você pode obter o formato XML ou apenas o texto bruto. É possível
iterar sobre o XML resultante, mas mais comumente usado é o texto bruto hoje em dia
para uma resposta formatada em JSON. O documento JSON será usado para converter
texto em um objeto JS usandJSON.parse(text).
...
} else if(xhr.readyState === XMLHttpRequest.DONE) {
var object = JSON.parse(xhr.responseText.toString());
print(JSON.stringify(object, null, 2));
}

No manipulador de resposta, acessamos o texto de resposta bruto e o convertemos em um


objeto javascript. Esse objeto JSON agora é um objeto JS válido (no javascript, um objeto
pode ser um objeto ou uma matriz(array).

Note

Parece que a conversão toString()primeiro torna o código mais estável. Sem a


conversão explícita, tive vários erros de analisador. Não tenho certeza qual a causa disso.

203
11.3.1. Chamadas do Flickr

Vamos dar uma olhada em um exemplo no mundo real. Um exemplo típico é usar o
serviço Flickr para recuperar um feed público das novas imagens enviadas. Para isso,
podemos usar o urlhttp://api.flickr.com/services/feeds/photos_public.gne.
Infelizmente, ele retorna por padrão um fluxo XML, que pode ser facilmente analisado
pelo XmlListModel em qml. Por exemplo, gostaríamos de nos concentrar nos dados JSON.
Para se tornar uma resposta JSON limpa, precisamos anexar alguns parâmetros à
solicitação: http://api.flickr.com/services/feeds/photos_public.gne?
format=json&nojsoncallback=1. Isso retornará uma resposta JSON sem o retorno de
chamada JSON.

Note

Um retorno de chamada JSON envolve a resposta JSON em uma chamada de função.


Este é um atalho usado na programação HTML, em que uma tag de script é usada para
fazer uma solicitação JSON. A resposta irá acionar uma função local definida pelo
retorno de chamada. Não há mecanismo que funcione com retornos de chamada JSON
no QML.

Vamos primeiro examinar a resposta usando curl:


curl "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsonca

A resposta será algo assim:


{
"title": "Recent Uploads tagged munich",
...
"items": [
{
"title": "Candle lit dinner in Munich",
"media": {"m":"http://farm8.staticflickr.com/7313/11444882743_2f5f87169f_
...
},{
"title": "Munich after sunset: a train full of \"must haves\" =",
"media": {"m":"http://farm8.staticflickr.com/7394/11443414206_a462c80e83_
...
}
]
...
}

O documento JSON retornado possui uma estrutura definida. Um objeto que possui um
título e uma propriedade de itens. Onde o título é uma string e itens é uma matriz de
objetos. Ao converter este texto em um documento JSON, você pode acessar as entradas
individuais, como é uma estrutura de objeto / matriz JS válida.

204
// JS code
obj = JSON.parse(response);
print(obj.title) // => "Recent Uploads tagged munich"
for(var i=0; i<obj.items.length; i++) {
// iterate of the items array entries
print(obj.items[i].title) // title of picture
print(obj.items[i].media.m) // url of thumbnail
}

Como uma matriz JS válida, podemos usar aobj.items também como um modelo para
uma exibição de lista. Vamos tentar fazer isso agora. Primeiro, precisamos recuperar a
resposta e convertê-la em um objeto JS válido. E então podemos apenas definir a
propriedade response.items como um modelo para uma exibição de lista.
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(...) {
...
} else if(xhr.readyState === XMLHttpRequest.DONE) {
var response = JSON.parse(xhr.responseText.toString());
// set JS object as model for listview
view.model = response.items;
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?forma
xhr.send();
}

Aqui está o código fonte completo, onde criamos a solicitação, quando o componente é
carregado. A resposta da solicitação é então usada como modelo para nossa visualização
de lista simples.
import QtQuick 2.5

Rectangle {
width: 320
height: 480
ListView {
id: view
anchors.fill: parent
delegate: Thumbnail {
width: view.width
text: modelData.title
iconSource: modelData.media.m
}
}

function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE')
var json = JSON.parse(xhr.responseText.toString())

205
view.model = json.items
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?f
xhr.send();
}

Component.onCompleted: {
request()
}
}

Quando o documento estiver totalmente carregado ( Component.onCompleted )


solicitamos o conteúdo do feed mais recente do Flickr. Na chegada, analisamos a resposta
JSON e definimos a matriz(array) de items como o modelo para nossa visualização. A
exibição de lista tem um delegate, que exibe o ícone de miniatura e o texto do título em
uma linha.

Uma outra opção seria ter um marcador de posiçãoListModel e anexar cada item ao
modelo de lista. Para suportar modelos maiores, é necessário oferecer suporte a paginação
(ex: página 1 de 10) e recuperação de conteúdo lento.

11.4. Ficheiros locais


Também é possível carregar arquivos locais (XML/JSON) usando o XMLHttpRequest.
Por exemplo, um arquivo local chamado “colors.json” pode ser carregado usando:
xhr.open("GET", "colors.json");

Usamos isso para ler uma tabela de cores e exibi-la como uma grade. Não é possível
modificar o arquivo do lado do Qt Quick. Para armazenar dados de volta à fonte,
precisaríamos de um pequeno servidor HTTP baseado em REST ou de uma extensão
nativa do Qt Quick para acesso a arquivos.
import QtQuick 2.5

Rectangle {
width: 360
height: 360
color: '#000'

GridView {
id: view
anchors.fill: parent
cellWidth: width/4
cellHeight: cellWidth
delegate: Rectangle {
width: view.cellWidth
height: view.cellHeight
color: modelData.value
}
}

206
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE');
var obj = JSON.parse(xhr.responseText.toString());
view.model = obj.colors
}
}
xhr.open("GET", "colors.json");
xhr.send();
}

Component.onCompleted: {
request()
}
}

Em vez de usar o XMLHttpRequest também é possível usar o XmlListModel para acessar


arquivos locais.
import QtQuick.XmlListModel 2.0

XmlListModel {
source: "http://localhost:8080/colors.xml"
query: "/colors"
XmlRole { name: 'color'; query: 'name/string()' }
XmlRole { name: 'value'; query: 'value/string()' }
}

Com o XmlListModel só é possível ler arquivos XML e não arquivos JSON.

11.5. API REST


Para usar um serviço da web, primeiro precisamos criar um. Usaremos o Flask
(http://flask.pocoo.org) um simples servidor de aplicativos HTTP baseado em python, para
criar um serviço web simples em cores. Você também pode usar todos os outros servidores
da Web que aceitem e retornem dados JSON. A ideia é ter uma lista de cores nomeadas,
que podem ser gerenciadas via web-service. Gerenciado neste caso significa CRUD
(create-read-update-delete).

Um serviço web simples no Flask pode ser escrito em um arquivo. Nós começamos com
um arquivo server.py vazio. Dentro desse arquivo, criamos um código de caldeira e
carregamos nossas cores iniciais a partir de um arquivo JSON externo. Veja também a
documentação do Flask quickstart [http://flask.pocoo.org/docs/quickstart/].
from flask import Flask, jsonify, request
import json

colors = json.load(file('colors.json', 'r'))

207
app = Flask(__name__)

# ... service calls go here

if __name__ == '__main__':
app.run(debug = True)

Quando você executa esse script, ele fornece um servidor da web em http://localhost:5000,
que ainda não é útil para nada.

Vamos agora começar a adicionar os nossos endpoints CRUD


(Create,Read,Update,Delete) ao nosso pequeno serviço web.

11.5.1. Pedido de Leitura

Para ler dados do nosso servidor web, forneceremos um método GET para todas as cores.
@app.route('/colors', methods = ['GET'])
def get_colors():
return jsonify( { "colors" : colors })

Isso retornará as cores abaixo doendpoint‘/colors’. Para testar isso, podemos usar o curl
para criar uma solicitação http.
curl -i -GET http://localhost:5000/colors

Which will return us the list of colors as JSON data.

11.5.2. Entrada de leitura

Para ler uma cor individual pelo nome, fornecemos o endpoint de detalhes, localizado em
‘/colors/<name>’. O nome é um parâmetro para o terminal, que identifica uma cor
individual.
@app.route('/colors/<name>', methods = ['GET'])
def get_color(name):
for color in colors:
if color["name"] == name:
return jsonify( color )

E podemos testá-lo usando o curl novamente. Por exemplo, para obter a entrada da cor
vermelha.
curl -i -GET http://localhost:5000/colors/red

Ele retornará uma entrada de cor como dados JSON.

11.5.3. Criar entrada

208
Até agora, acabamos de usar os métodos HTTP GET. Para criar uma entrada no lado do
servidor, usaremos um método POST e passaremos as novas informações de cores com os
dados do POST. A localização do ponto final é a mesma que para obter todas as cores.
Mas desta vez esperamos uma solicitação POST.
@app.route('/colors', methods= ['POST'])
def create_color():
color = {
'name': request.json['name'],
'value': request.json['value']
}
colors.append(color)
return jsonify( color ), 201

Curl é flexível o suficiente para nos permitir fornecer dados JSON como a nova entrada
dentro da solicitação POST.
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"gray1","value":"

11.5.4. Atualizar entrada

Para atualizar uma entrada individual, usamos o método PUT HTTP. O endpoint é o
mesmo que recuperar uma entrada de cor individual. Quando a cor foi atualizada com
sucesso, retornamos a cor atualizada como dados JSON.
@app.route('/colors/<name>', methods= ['PUT'])
def update_color(name):
for color in colors:
if color["name"] == name:
color['value'] = request.json.get('value', color['value'])
return jsonify( color )

Na solicitação de curvatura, fornecemos apenas os valores a serem atualizados como


dados JSON e o ponto de extremidade nomeado para identificar a cor a ser atualizada.
curl -i -H "Content-Type: application/json" -X PUT -d '{"value":"#666"}' http://l

11.5.5. Excluir entrada

A exclusão de uma entrada é feita usando o verbo HTTP DELETE. Ele também usa o
mesmo terminal para uma cor individual, mas desta vez o HTTP DELETE.
@app.route('/colors/<name>', methods=['DELETE'])
def delete_color(name):
success = False
for color in colors:
if color["name"] == name:
colors.remove(color)
success = True
return jsonify( { 'result' : success } )

209
Essa solicitação é semelhante à solicitação GET para uma cor individual.
curl -i -X DELETE http://localhost:5000/colors/red

Agora podemos ler todas as cores, ler uma cor específica, criar uma nova cor, atualizar
uma cor e excluir uma cor. Também sabemos os endpoints HTTP para nossa API.

Ação HTTP Endpoint


Read All GET http://localhost:5000/colors
Create Entry POST http://localhost:5000/colors
Read Entry GET http://localhost:5000/colors/<name>
Update Entry PUT http://localhost:5000/colors/<name>
Delete Entry DELETE http://localhost:5000/colors/<name>

Nosso pequeno servidor REST está completo agora e podemos nos concentrar no QML e
no lado do cliente. Para criar uma API fácil de usar, precisamos mapear cada ação para
uma solicitação HTTP individual e fornecer uma API simples para nossos usuários.

11.5.6. Cliente REST

Para demonstrar um cliente REST, escrevemos uma pequena grade de cores. A grade de
cores exibe as cores recuperadas do serviço da web por meio de solicitações HTTP. Nossa
interface de usuário fornece os seguintes comandos:

Get color list


Create color
Read last color
Update last color
Delete last color

Agrupamos nossa API em um arquivo JS próprio chamado colorservice.js e


importamos para nossa UI comoService. Dentro do módulo de serviço, criamos uma
função auxiliar para fazer as solicitações HTTP para nós:
// colorservice.js
function request(verb, endpoint, obj, cb) {
print('request: ' + verb + ' ' + BASE + (endpoint?'/' + endpoint:''))
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
print('xhr: on ready state change: ' + xhr.readyState)
if(xhr.readyState === XMLHttpRequest.DONE) {
if(cb) {
var res = JSON.parse(xhr.responseText.toString())
cb(res);
}
}
}
xhr.open(verb, BASE + (endpoint?'/' + endpoint:''));
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'application/json');

210
var data = obj?JSON.stringify(obj):''
xhr.send(data)
}

Leva quatro argumentos. O verb, que define o verbo HTTP a ser usado (GET, POST, PUT,
DELETE). O segundo parâmetro é o terminal a ser usado como postfix para o endereço
BASE (ex: ‘http://localhost:5000/colors‘). O terceiro parâmetro é o obj opcional, para ser
enviado como dados JSON para o serviço. O último parâmetro define um retorno de
chamada a ser chamado quando a resposta é retornada. O retorno de chamada recebe um
objeto de resposta com os dados da resposta. Antes de enviarmos a solicitação, indicamos
que enviamos e aceitamos dados JSON modificando o cabeçalho da solicitação.

Usando esta função auxiliar de solicitação, podemos implementar os comandos simples


que definimos anteriormente (create, read, update, delete):
// colorservice.js
function get_colors(cb) {
// GET http://localhost:5000/colors
request('GET', null, null, cb)
}

function create_color(entry, cb) {


// POST http://localhost:5000/colors
request('POST', null, entry, cb)
}

function get_color(name, cb) {


// GET http://localhost:5000/colors/<name>
request('GET', name, null, cb)
}

function update_color(name, entry, cb) {


// PUT http://localhost:5000/colors/<name>
request('PUT', name, entry, cb)
}

function delete_color(name, cb) {


// DELETE http://localhost:5000/colors/<name>
request('DELETE', name, null, cb)
}

Este código reside na implementação do serviço. Na UI usamos o serviço para


implementar nossos comandos. Nós temos um ListModel com o igridModel como
provedor de dados para o GridView. Os comandos são indicados usando um elemento
UIButton.

Lendo a lista de cores do servidor.


// rest.qml
import "colorservice.js" as Service
...
// read colors command
Button {
text: 'Read Colors';

211
onClicked: {
Service.get_colors( function(resp) {
print('handle get colors resp: ' + JSON.stringify(resp));
gridModel.clear();
var entries = resp.data;
for(var i=0; i<entries.length; i++) {
gridModel.append(entries[i]);
}
});
}
}

Crie uma nova entrada de cor no servidor.


// rest.qml
import "colorservice.js" as Service
...
// create new color command
Button {
text: 'Create New';
onClicked: {
var index = gridModel.count-1
var entry = {
name: 'color-' + index,
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.create_color(entry, function(resp) {
print('handle create color resp: ' + JSON.stringify(resp))
gridModel.append(resp)
});
}
}

Lendo uma cor com base em seu nome.


// rest.qml
import "colorservice.js" as Service
...
// read last color command
Button {
text: 'Read Last Color';
onClicked: {
var index = gridModel.count-1
var name = gridModel.get(index).name
Service.get_color(name, function(resp) {
print('handle get color resp:' + JSON.stringify(resp))
message.text = resp.value
});
}
}

Atualize uma entrada de cor no servidor com base no nome da cor.


// rest.qml
import "colorservice.js" as Service
...
// update color command
Button {

212
text: 'Update Last Color'
onClicked: {
var index = gridModel.count-1
var name = gridModel.get(index).name
var entry = {
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.update_color(name, entry, function(resp) {
print('handle update color resp: ' + JSON.stringify(resp))
var index = gridModel.count-1
gridModel.setProperty(index, 'value', resp.value)
});
}
}

Exclua uma cor pelo nome da cor.


// rest.qml
import "colorservice.js" as Service
...
// delete color command
Button {
text: 'Delete Last Color'
onClicked: {
var index = gridModel.count-1
var name = gridModel.get(index).name
Service.delete_color(name)
gridModel.remove(index, 1)
}
}

Isso conclui as operações CRUD (create, read, update, delete) usando uma API REST. Há
também outras possibilidades para gerar uma API de serviço da Web. Um poderia ser
baseado em módulo e cada módulo teria um endpoint. E a API pode ser definida usando
JSON RPC (http://www.jsonrpc.org/). Claro que também a API baseada em XML é
possível, mas a abordagem JSON tem grandes vantagens, pois a análise é construída no
QML/JS como parte do JavaScript.

11.6. Autenticação usando o OAuth


OAuth é um protocolo aberto para permitir a autorização segura em um método simples e
padrão a partir de aplicativos da Web, móveis(mobile) e de desktop. OAuth é usado para
autenticar um cliente contra serviços da Web comuns, como Google, Facebook e Twitter.

Note

Para um serviço da Web personalizado, você também pode usar a autenticação HTTP
padrão, por exemplo, usando o nome de usuário e a senha XMLHttpRequest no método
get (ex: xhr.open(verb, url, true, username, password))

OAuth atualmente não faz parte de uma API QML/JS. Então você precisaria escrever

213
algum código C ++ e exportar a autenticação para QML/JS. Outro problema seria o
armazenamento seguro do token de acesso.

Aqui estão alguns links que achamos úteis:

http://oauth.net/
http://hueniverse.com/oauth/
https://github.com/pipacs/o2
http://www.johanpaul.com/blog/2011/05/oauth2-explained-with-qt-quick/

11.7. Engine IO
Engin.IO é um serviço da web da DIGIA. Ele permite acessar a partir do aplicativo Qt /
QML para o armazenamento NoSQL do Engin.IO. É um armazenamento de objeto de
armazenamento baseado em nuvem com uma API Qt/QML de fácil acesso e um console
de administração. Se você quiser armazenar alguns dados na nuvem a partir de um
aplicativo QML, isso seria um caminho de entrada fácil com um excelente suporte a
QML/JS.

Por favor, consulte a documentação do EnginIO [http://engin.io] para mais ajuda.

11.8. Web Sockets


O módulo WebSockets fornece uma imposição do protocolo WebSockets para clientes e
servidores WebSockets. Ele espelha o módulo Qt CPP. Permite enviar mensagens de string
e binárias usando um canal de comunicação full duplex. Um websocket é normalmente
estabelecido fazendo uma conexão HTTP com o servidor e o servidor “atualiza” a
conexão para uma conexão WebSocket.

No Qt/QML, você também pode usar os objetos WebSocket e WebSocketServer para criar
uma conexão direta com o websocket. O protocolo websocket usa o esquema de URL
“ws” ou “wss” para uma conexão segura.

Você pode usar o módulo qml do soquete da web importando-o primeiro.


import Qt.WebSockets 1.0

WebSocket {
id: socket
}

Para testar seu socket da web, usaremos o echo server http://websocket.org.


import QtQuick 2.5
import Qt.WebSockets 1.0

Text {
width: 480

214
height: 48

horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

WebSocket {
id: socket
url: "ws://echo.websocket.org"
active: true
onTextMessageReceived: {
text = message
}
onStatusChanged: {
if (socket.status == WebSocket.Error) {
console.log("Error: " + socket.errorString)
} else if (socket.status == WebSocket.Open) {
socket.sendTextMessage("ping")
} else if (socket.status == WebSocket.Closed) {
text += "\nSocket closed"
}
}
}
}

Você deve ver a mensagem ping enviamos socket.sendTextMessage("ping") como


resposta no campo de texto.

11.8.1. WS Server

Você pode facilmente criar seu próprio servidor WS usando a parte C ++ do Qt


WebSocket ou usar uma implementação WS diferente, o que eu acho muito interessante. É
interessante porque permite conectar a incrível qualidade de renderização do QML com os
grandes servidores de aplicativos da web em expansão. Neste exemplo, usaremos um
servidor de soquete da web baseado no nó JS usando o módulo ws
[https://npmjs.org/package/ws]. Para isso, primeiro você precisa instalar onode js
[http://nodejs.org/]. Em seguida, crie uma pasta ws_server e instale o pacote ws usando o
package manager (npm).

O código deve criar um servidor de eco simples no NodeJS para retornar nossas
mensagens ao nosso cliente QML.

215
$ cd ws_server
$ npm install ws

A ferramenta npm baixa e instala o pacote ws e as dependências na sua pasta local.

Um server.js será a implementação do nosso servidor. O código do servidor criará um


servidor de soquete da web na porta 3000 e ouvirá uma conexão de entrada. Em uma
conexão de entrada, ele enviará uma saudação e aguardará as mensagens do cliente. Cada
mensagem enviada por um cliente em um soquete será enviada de volta ao cliente.
var WebSocketServer = require('ws').Server;

var server = new WebSocketServer({ port : 3000 });

server.on('connection', function(socket) {
console.log('client connected');
socket.on('message', function(msg) {
console.log('Message: %s', msg);
socket.send(msg);
});
socket.send('Welcome to Awesome Chat');
});

console.log('listening on port ' + server.options.port);

Você precisa se acostumar com a notação de JavaScript e os callbacks da função.

11.8.2. Cliente WS

No lado do cliente, precisamos de uma exibição de lista para exibir as mensagens e um


TextInput para o usuário inserir uma nova mensagem de bate-papo.

216
Usaremos uma etiqueta com cor branca no exemplo.
// Label.qml
import QtQuick 2.5

Text {
color: '#fff'
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}

Nossa visão de bate-papo é uma visão de lista, onde o texto é anexado a um modelo de
lista. Cada entrada é exibida usando uma linha de prefixo e rótulo de mensagem. Usamos
um fator cw de largura de célula para dividir o com em 24 colunas.
// ChatView.qml
import QtQuick 2.5

ListView {
id: root
width: 100
height: 62

model: ListModel {}

function append(prefix, message) {


model.append({prefix: prefix, message: message})
}

delegate: Row {
width: root.width
height: 18
property real cw: width/24
Label {
width: cw*1
height: parent.height
text: model.prefix
}
Label {
width: cw*23
height: parent.height
text: model.message
}
}
}

A entrada de bate-papo é apenas uma entrada de text input wrapped com uma borda
colorida.
// ChatInput.qml
import QtQuick 2.5

FocusScope {
id: root
width: 240
height: 32
Rectangle {

217
anchors.fill: parent
color: '#000'
border.color: '#fff'
border.width: 2
}

property alias text: input.text

signal accepted(string text)

TextInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4
anchors.rightMargin: 4
onAccepted: root.accepted(text)
color: '#fff'
focus: true
}
}

Quando o soquete da web recebe uma mensagem, ele anexa a mensagem à visualização do
bate-papo. O mesmo se aplica a uma mudança de status. Além disso, quando o usuário
digita uma mensagem de bate-papo, uma cópia é anexada à visualização de bate-papo no
lado do cliente e a mensagem é enviada ao servidor.
// ws_client.qml
import QtQuick 2.5
import Qt.WebSockets 1.0

Rectangle {
width: 360
height: 360
color: '#000'

ChatView {
id: box
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: input.top
}
ChatInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
focus: true
onAccepted: {
print('send message: ' + text)
socket.sendTextMessage(text)
box.append('>', text)
text = ''
}
}
WebSocket {

218
id: socket

url: "ws://localhost:3000"
active: true
onTextMessageReceived: {
box.append('<', message)
}
onStatusChanged: {
if (socket.status == WebSocket.Error) {
box.append('#', 'socket error ' + socket.errorString)
} else if (socket.status == WebSocket.Open) {
box.append('#', 'socket open')
} else if (socket.status == WebSocket.Closed) {
box.append('#', 'socket closed')
}
}
}
}

Você precisa primeiro executar o servidor e, em seguida, o cliente. Não há mecanismo de


conexão de repetição em nosso cliente simples.

Executando o servidor
$ cd ws_server
$ node server.js

Executando o cliente
$ cd ws_client
$ qmlscene ws_client.qml

Ao digitar o texto e pressionar enter, você deve ver algo assim.

219
11.9. Resumo
Isso conclui nosso capítulo sobre a rede QML. Por favor, tenha em mente que o Qt tem no
lado nativo uma API de rede muito mais rica como no lado da QML atualmente. Mas a
ideia do capítulo é empurrar os limites da rede QML e como se integrar aos serviços
baseados em nuvem.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

220
Qt5 Cadaques Book » previous | next

12. Armazenamento
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Este capítulo cobrirá o armazenamento de dados usando o Qt Quick no Qt 5. O Qt Quick


oferece apenas maneiras limitadas de armazenar dados locais diretamente. Nesse sentido,
age mais como um navegador. IEm muitos projetos, o armazenamento de dados é feito
pelo backend do C ++ e a funcionalidade necessária é exportada para o front-end do Qt
Quick. O Qt Quick não fornece acesso ao sistema de arquivos do host para ler e gravar
arquivos conforme você é usado do lado do Qt C ++. Portanto, a tarefa do engenheiro de
back-end é escrever um desses plug-ins ou usar um canal de rede para se comunicar com
um servidor local, que fornece esses recursos.

Cada aplicativo precisa armazenar informações menores e maiores de forma persistente.


Isso pode ser feito localmente no sistema de arquivos ou remoto em um servidor. Algumas
informações serão estruturadas e simples (ex: configurações), algumas serão grandes e
complicadas, por exemplo, arquivos de documentação e algumas serão grandes e
estruturadas e exigirão algum tipo de conexão de banco de dados. Aqui nós cobriremos
principalmente os recursos internos do Qt Quick para armazenar dados como também os
modos de rede.

12.1. Configurações
O Qt vem em seu lado nativo com a classe C ++ QSettings que permite armazenar as
configurações do aplicativo (também conhecidas como opções, preferências) de uma
maneira dependente do sistema. Ele usa a infra-estrutura disponível no seu sistema
operacional. Adicional, ele suporta um formato de arquivo INI comum para o
gerenciamento de arquivos de configurações entre plataformas.

No Qt 5.2 Settings entraram no mundo QML. A API ainda está no módulo de


laboratórios, o que significa que a API pode ser quebrada no futuro. Então esteja ciente.

Aqui está um pequeno exemplo, que aplica um valor de cor a um retângulo de base. Toda
vez que o usuário clica na janela, uma nova cor aleatória é gerada. Quando o aplicativo é
fechado e relançado novamente, você deve ver sua última cor. A cor padrão deve ser a cor

221
inicialmente definida no retângulo raiz.
import QtQuick 2.5
import Qt.labs.settings 1.0

Rectangle {
id: root
width: 320; height: 240
color: '#000000'
Settings {
id: settings
property alias color: root.color
}
MousArea {
anchors.fill: parent
onClicked: root.color = Qt.hsla(Math.random(), 0.5, 0.5, 1.0);
}
}

O valor das configurações é armazenado toda vez que o valor é alterado. Isso pode não ser
sempre o que você quer. Para armazenar as configurações somente quando necessário,
você pode usar propriedades padrão.
Rectangle {
id: root
color: settings.color
Settings {
id: settings
property color color: '#000000'
}
function storeSettings() { // executed maybe on destruction
settings.color = root.color
}
}

Também é possível armazenar configurações em diferentes categorias usando a


propriedade category.
Settings {
category: 'window'
property alias x: window.x
property alias y: window.x
property alias width: window.width
property alias height: window.height
}

As configurações são armazenadas de acordo com o nome do aplicativo, organização e


domínio. Esta informação é normalmente definida na função principal do seu código c++.
int main(int argc, char** argv) {
...
QCoreApplication::setApplicationName("Awesome Application");
QCoreApplication::setOrganizationName("Awesome Company");
QCoreApplication::setOrganizationDomain("org.awesome");
...
}

222
12.2. Armazenamento Local - SQL
O Qt Quick suporta uma API de armazenamento local conhecida pelos navegadores da
API de local storage API. A API está disponível em “import QtQuick.LocalStorage 2.0”.

Em geral, ele armazena o conteúdo em um banco de dados SQLITE no local específico do


sistema em um arquivo baseado em ID exclusivo com base no nome e na versão do banco
de dados fornecidos. Não é possível listar ou excluir bancos de dados existentes. Você
pode encontrar o local de armazenamentoQQmlEngine::offlineStoragePath().

Você usa a API criando primeiro um objeto de banco de dados e, em seguida, criando
transações no banco de dados. Cada transação pode conter uma ou mais consultas SQL. A
transação será revertida quando uma consulta SQL falhar dentro da transação.

Por exemplo, para ler de uma simples tabela de notas com uma coluna de texto, você
poderia usar o armazenamento local da seguinte forma:
import QtQuick 2.5
import QtQuick.LocalStorage 2.0

Item {
Component.onCompleted: {
var db = LocalStorage.openDatabaseSync("MyExample", "1.0", "Example datab
db.transaction( function(tx) {
var result = tx.executeSql('select * from notes');
for(var i = 0; i < result.rows.length; i++) {
print(result.rows[i].text);
}
}
});
}
}

Rectangle Maluco

Como exemplo, suponha que gostaríamos de armazenar a posição de um retângulo em


nossa cena.

223
Aqui nosso exemplo base.
import QtQuick 2.5

Item {
width: 400
height: 400

Rectangle {
id: crazy
objectName: 'crazy'
width: 100
height: 100
x: 50
y: 50
color: "#53d769"
border.color: Qt.lighter(color, 1.1)
Text {
anchors.centerIn: parent
text: Math.round(parent.x) + '/' + Math.round(parent.y)
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}

Você pode arrastar o retângulo livremente ao redor. Quando você fechar o aplicativo e
iniciá-lo novamente, o retângulo estará na mesma posição.

Agora gostaríamos de acrescentar que a posição x/y do retângulo é armazenada dentro do


banco de dados SQL. Para isso, precisamos adicionar um init, read e store a função de

224
banco de dados. Essas funções são chamadas quando no componente concluído e na
destruição do componente.
import QtQuick 2.5
import QtQuick.LocalStorage 2.0

Item {
// reference to the database object
property var db;

function initDatabase() {
// initialize the database object
}

function storeData() {
// stores data to DB
}

function readData() {
// reads and applies data from DB
}

Component.onCompleted: {
initDatabase();
readData();
}

Component.onDestruction: {
storeData();
}
}

Você também pode extrair o código do banco de dados em uma biblioteca JS própria, que
faz toda a lógica. Esta seria a maneira preferida se a lógica se tornasse mais complicada.

Na função de inicialização do banco de dados, criamos o objeto DB e garantimos que a


tabela SQL seja criada.
function initDatabase() {
print('initDatabase()')
db = LocalStorage.openDatabaseSync("CrazyBox", "1.0", "A box who remembers it
db.transaction( function(tx) {
print('... create table')
tx.executeSql('CREATE TABLE IF NOT EXISTS data(name TEXT, value TEXT)'
});
}

Em seguida, o aplicativo chama a função de leitura para ler dados existentes do banco de
dados. Aqui precisamos diferenciar se já existem dados na tabela. Para verificar,
analisamos quantas linhas a cláusula select retornou.
function readData() {
print('readData()')
if(!db) { return; }

225
db.transaction( function(tx) {
print('... read crazy object')
var result = tx.executeSql('select * from data where name="crazy"');
if(result.rows.length === 1) {
print('... update crazy geometry')
// get the value column
var value = result.rows[0].value;
// convert to JS object
var obj = JSON.parse(value)
// apply to object
crazy.x = obj.x;
crazy.y = obj.y;
}
});
}

Esperamos que os dados sejam armazenados em uma string JSON dentro da coluna de
valor. Isso não é típico do SQL, mas funciona bem com o código JS. Então, ao invés de
armazenar o x, y como propriedades na tabela, nós os armazenamos como um objeto JS
completo usando os métodos JSON stringify/parse. No final, obtemos um objeto JS válido
com as propriedades x e y, que podemos aplicar em nosso retângulo maluco.

Para armazenar os dados, precisamos diferenciar os casos de atualização e inserção.


Usamos update quando já existe um registro e inserimos se não existe nenhum registro sob
o nome “crazy”.
function storeData() {
print('storeData()')
if(!db) { return; }
db.transaction( function(tx) {
print('... check if a crazy object exists')
var result = tx.executeSql('SELECT * from data where name = "crazy"'
// prepare object to be stored as JSON
var obj = { x: crazy.x, y: crazy.y };
if(result.rows.length === 1) {// use update
print('... crazy exists, update it')
result = tx.executeSql('UPDATE data set value=? where name="crazy"'
} else { // use insert
print('... crazy does not exists, create it')
result = tx.executeSql('INSERT INTO data VALUES (?,?)', ['crazy'
}
});
}

Em vez de selecionar todo o conjunto de registros, também poderíamos usar a função de


contagem SQLITE assim: SELECT COUNT(*) from data where name = "crazy" que
retornaria usar uma linha com a quantidade de linhas afetadas pela consulta selecionada.
Caso contrário, isso é um código SQL comum. Como um recurso adicional, usamos o
valor de ligação SQL usando o?na consulta.

Agora você pode arrastar o retângulo e, ao sair do aplicativo, o banco de dados armazena a
posição x/y e a aplica na próxima execução do aplicativo.

226
12.3. Outras APIs de armazenamento
Para armazenar diretamente de dentro do QML, esses são os principais tipos de
armazenamento. A força real do Qt Quick vem do fato de estendê-lo com o C++ para fazer
interface com seus sistemas de armazenamento nativo ou usar a API de rede para fazer
interface com um sistema de armazenamento remoto, como a nuvem Qt.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

227
Qt5 Cadaques Book » previous | next

13. QML Dinâmico


Section author: e8johan [https://bitbucket.org/e8johan]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

Até agora, tratamos o QML como uma ferramenta para construir um conjunto estático de
cenas e navegar entre elas. Dependendo de vários estados e regras lógicas, uma interface
de usuário dinâmica e ao vivo é construída. Ao trabalhar com o QML e o JavaScript de
maneira mais dinâmica, a flexibilidade e as possibilidades se expandem ainda mais.
Componentes podem ser carregados e instanciados em tempo de execução, elementos
podem ser destruídos. Interfaces de usuário criadas dinamicamente podem ser salvas em
disco e depois restauradas.

13.1. Carregando componentes dinamicamente


A maneira mais fácil de carregar dinamicamente diferentes partes do QML é usar o
elemento Loader. Ele serve como um espaço reservado para o item que está sendo
carregado. O item a ser carregado é controlado por meio da propriedade source ou da
propriedade sourceComponent. O primeiro carrega o item de um determinado URL,
enquanto o segundo instancia um componente.

Como o carregador serve como um espaço reservado para o item que está sendo
carregado, seu tamanho depende do tamanho do item e vice-versa. Se o elementoLoader
tiver um tamanho, seja por definir awidth e height ou por meio de ancoragem, o item
carregado receberá o tamanho da loader. Se oLoadernão tiver tamanho, ele será
redimensionado de acordo com o tamanho do item que está sendo carregado.

O exemplo descrito abaixo demonstra como duas partes separadas da interface do usuário
podem ser carregadas no mesmo espaço usando um elementoLoader. A ideia é ter uma
discagem rápida que pode ser digital ou analógica, como mostra a ilustração abaixo. O
código em torno da discagem não é afetado por qual item é carregado no momento.

228
A primeira etapa no aplicativo é declarar um elemento Loader. Observe que a propriedade
sourceé deixada de fora. Isso ocorre porque osource depende de qual estado a interface
do usuário está.
Loader {
id: dialLoader

anchors.fill: parent
}

Na propriedade states do parent nodialLoader um conjunto de


elementosPropertyChanges direciona o carregamento de arquivos QML diferentes,

229
dependendo do state. A propriedade source é um caminho de arquivo relativo neste
exemplo, mas também pode ser um URL completo, buscando o item pela Web.
states: [
State {
name: "analog"
PropertyChanges { target: analogButton; color: "green"; }
PropertyChanges { target: dialLoader; source: "Analog.qml"; }
},
State {
name: "digital"
PropertyChanges { target: digitalButton; color: "green"; }
PropertyChanges { target: dialLoader; source: "Digital.qml"; }
}
]

Para tornar o item carregado ativo, sua propriedade speed deve estar vinculada à
propriedade speed da raiz. Isso não pode ser feito como uma ligação direta, pois o item
nem sempre é carregado e muda com o tempo. Em vez disso, um elemento Binding deve
ser usado. A propriedade target da ligação é alterada toda vez que o Loader aciona o
sinal onLoaded.
Loader {
id: dialLoader

anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: analogButton.top

onLoaded: {
binder.target = dialLoader.item;
}
}
Binding {
id: binder

property: "speed"
value: speed
}

O sinalonLoaded permite que o carregamento da QML atue quando o item foi carregado
(loader). De maneira semelhante, o QML sendo carregado pode depender do sinal
Component.onCompleted. Este é o sinal realmente disponível para todos os componentes,
independentemente de como eles são carregados. Por exemplo, o componente raiz de um
aplicativo inteiro pode nos dar o pontapé inicial quando toda a interface do usuário for
carregada.

13.1.1. Conectando Indiretamente

Ao criar elementos QML dinamicamente, você não pode se conectar a sinais usando a
abordagem onSignalName usada para configuração estática. Em vez disso, o elemento
Connections deve ser usado.Ele se conecta a qualquer número de sinais de um

230
elementotarget.

Tendo definido a propriedade de destino de um elementoConnections os sinais podem ser


conectados como de costume, ou seja, usando a abordagemonSignalName. No entanto,
alterando a propriedade target diferentes elementos podem ser monitorados em
momentos diferentes.

No exemplo acima, uma interface de usuário que consiste em duas áreas clicáveis é
apresentada ao usuário. Quando uma das áreas é clicada, ela é mostrada usando uma
animação. A área esquerda é mostrada no trecho de código abaixo. NaMouseArea, o
leftClickedAnimation é acionado, fazendo com que a área pisque.

Rectangle {
id: leftRectangle

width: 290
height: 200

color: "green"

MouseArea {
id: leftMouseArea
anchors.fill: parent
onClicked: leftClickedAnimation.start();
}

Text {
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
text: "Click me!"
}

231
}

Além das duas áreas clicáveis, um Connections é usado. Isso dispara uma terceira
animação quando o ativo, ou seja, otarget do elemento, é clicado.
Connections {
id: connections
onClicked: activeClickedAnimation.start();
}

Para determinar qual MouseArea deve ser direcionada, dois estados são definidos. Observe
que não podemos definir a propriedade targetusando um elementoPropertyChanges pois
ela já contém uma propriedadetarget. Em vez disso, um StateChangeScript é utilizado.
states: [
State {
name: "left"
StateChangeScript {
script: connections.target = leftMouseArea
}
},
State {
name: "right"
StateChangeScript {
script: connections.target = rightMouseArea
}
}
]

Ao experimentar o exemplo, vale a pena notar que, quando vários manipuladores de sinais
são usados, todos são chamados. A ordem de execução destes é, no entanto, indefinida.

Ao criar um elemento Connections sem definir a propriedadetargeta propriedade é


padronizada como parent. Isso significa que ele deve ser explicitamente definido
nullpara evitar a captura de sinais do parent até que o target seja definido. Esse
comportamento torna possível criar componentes de manipulador de sinal personalizados
com base em um elementoConnections. Desta forma, o código que reage aos sinais pode
ser encapsulado e reutilizado.

No exemplo abaixo, o componente o Flasher pode ser colocado dentro de


qualquerMouseArea. Quando clicado, ele aciona uma animação, fazendo com parent
pisque. Na mesma MouseArea a tarefa real que está sendo acionada também pode ser
executada. Isso separa o feedback padronizado do usuário, ou seja, o flash da ação real.
import QtQuick 2.5

Connections {
onClicked: {
// Automatically targets the parent
}
}

Para usar o Flasher, basta instanciar um Flasher dentro de cada MouseArea,e tudo

232
funciona.
import QtQuick 2.5

Item {
// A background flasher that flashes the background of any parent MouseAr
}

Ao usar um elemento Connections para monitorar os sinais de vários tipos de elementos


target, às vezes você se encontra em uma situação em que os sinais disponíveis variam
entre os destinos. Isso resulta no elemento Connections gerando erros de tempo de
execução quando os sinais são perdidos. Para evitar isso, a propriedade
ignoreUnknownSignalpode ser definida comotrue. Isso ignora todos esses erros.

Note

Geralmente é uma má idéia para suprimir mensagens de erro.

13.1.2. Vinculando Indiretamente

Assim como não é possível se conectar diretamente a sinais de elementos criados


dinamicamente, nem é possível vincular propriedades de um elemento criado
dinamicamente sem trabalhar com um elemento de ponte. Para ligar uma propriedade de
qualquer elemento, incluindo elementos criados dinamicamente, o elemento Binding é
usado.

O elemento Binding permite especificar um elementotarget, umaproperty para vincular


e um value para vinculá-lo. Por meio do uso de um elemento Binding` é possível, por
exemplo, vincular propriedades de um elemento dinamicamente. Isso foi demonstrado no
exemplo introdutório deste capítulo, como mostrado abaixo.
Loader {
id: dialLoader

anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: analogButton.top

onLoaded: {
binder.target = dialLoader.item;
}
}
Binding {
id: binder

property: "speed"
value: speed
}

233
Como o elemento de destino deBinding nem sempre é definido e talvez nem sempre tenha
uma determinada propriedade, a propriedade when do elemento Binding pode ser usada
para limitar o tempo em que a vinculação está ativa. Por exemplo, pode ser limitado a
modos específicos na interface do usuário.

13.2. Criando e destruindo objetos


O elemento Loader possibilita preencher parte de uma interface de usuário
dinamicamente. No entanto, a estrutura geral da interface ainda é estática. Através do
JavaScript é possível dar mais uma etapa e instanciar elementos QML completamente
dinamicamente.

Antes de nos aprofundarmos nos detalhes da criação de elementos dinamicamente,


precisamos entender o fluxo de trabalho. Ao carregar um pedaço de QML de um arquivo
ou mesmo através da Internet, um componente é criado. O componente encapsula o
código QML interpretado e pode ser usado para criar itens. Isso significa que carregar um
código QML e instanciar itens dele é um processo de dois estágios. Primeiro, o código
QML é analisado em um componente. Em seguida, o componente é usado para instanciar
objetos de item reais.

Além de criar elementos a partir do código QML armazenado em arquivos ou em


servidores, também é possível criar objetos QML diretamente de sequências de texto
contendo código QML. Os itens criados dinamicamente são tratados de maneira
semelhante quando instanciados.

13.2.1. Carregando e instanciando itens dinamicamente

Ao carregar um pedaço de QML, ele é primeiro interpretado em um componente. Isso


inclui o carregamento de dependências e a validação do código. A localização do QML
sendo carregado pode ser um arquivo local, um recurso Qt ou até mesmo um local de rede
de distância especificado por um URL. Isso significa que o tempo de carregamento pode
ser desde instantâneo, por exemplo, um recurso Qt localizado na RAM sem nenhuma
dependência não carregada, até muito longo, significando um pedaço de código localizado
em um servidor lento com várias dependências que precisam ser carregadas.

O status de um componente que está sendo criado pode ser rastreado por sua propriedade
status. Os valores disponíveis são Component.Null, Component.Loading,
Component.Ready e Component.Error. O Null para Loading para Ready é o fluxo usual.
Em qualquer estágio, o status pode mudar para Error. Nesse caso, o componente não
pode ser usado para criar novas instâncias de objeto. A função Component.errorString()
pode ser usada para recuperar uma descrição de erro legível pelo usuário.

Ao carregar componentes em conexões lentas, a propriedade progress pode ser útil. Ele
varia de 0.0, o que significa que nada foi carregado, para 1.0 indicando que tudo foi
carregado. Quando o status do componente muda paraReady, o componente pode ser
usado para instanciar objetos. O código abaixo demonstra como isso pode ser alcançado,

234
levando em consideração o evento em que o componente está pronto ou não sendo criado
diretamente, bem como o caso em que o componente está pronto um pouco mais tarde.
var component;

function createImageObject() {
component = Qt.createComponent("dynamic-image.qml");
if (component.status === Component.Ready || component.status === Component
finishCreation();
} else {
component.statusChanged.connect(finishCreation);
}
}

function finishCreation() {
if (component.status === Component.Ready) {
var image = component.createObject(root, {"x": 100, "y": 100});
if (image === null) {
console.log("Error creating image");
}
} else if (component.status === Component.Error) {
console.log("Error loading component:", component.errorString());
}
}

O código acima é mantido em um codigo-fonte JavaScript separado, referenciado no


arquivo QML principal.
import QtQuick 2.5
import "create-component.js" as ImageCreator

Item {
id: root

width: 1024
height: 600

Component.onCompleted: ImageCreator.createImageObject();
}

A funçãocreateObject de um componente é usada para criar instâncias de objetos, como


mostrado acima. Isso não se aplica apenas a componentes carregados dinamicamente, mas
também a elementos Component incluídos no código QML. O objeto resultante pode ser
usado na cena QML como qualquer outro objeto. A única diferença é que não tem um id.

A função createObject usa dois argumentos. O primeiro é um objeto parent do tipoItem.


A segunda é uma lista de propriedades e valores no formato {"name": value, "name":
value}. Isso é demonstrado no exemplo abaixo. Observe que o argumento de propriedades
é opcional.
var image = component.createObject(root, {"x": 100, "y": 100});

Note

235
Uma instância de componente criada dinamicamente não é diferente de um
elementoComponent. O elemento Component in-line também fornece funções para
instanciar objetos dinamicamente.

13.2.2. Dinamicamente instanciando itens do texto

Às vezes, é conveniente poder instanciar um objeto a partir de uma cadeia(string) de texto


de QML. Se nada mais, é mais rápido do que colocar o código em um arquivo de origem
separado. Para isso, a função Qt.createQmlObject é usada.

A função leva três argumentos: qml, parent e filepath. O argumento qml contém a string
do código QML para instanciar. O argumento parent fornece um objeto pai para o objeto
recém-criado. O argumento filepath é usado ao relatar quaisquer erros da criação do
objeto. O resultado retornado da função é um novo objeto ou null.

Atenção

A função createQmlObject sempre retorna imediatamente.Para que a função seja bem


sucedida, todas as dependências da chamada devem ser carregadas. Isso significa que, se
o código passado para a função fizer referência a um componente não carregado, a
chamada falhará e retornará null.Para lidar melhor com isso, a abordagem
createComponent / createObject deve ser usada.

Os objetos criados usando a função Qt.createQmlObject se parecem com qualquer outro


objeto criado dinamicamente. Isso significa que é idêntico a todos os outros objetos QML,
além de não ter um id. No exemplo abaixo, um novo elemento Rectangle é instanciado a
partir do código QML em linha quando o elemento root foi criado.
import QtQuick 2.5

Item {
id: root

width: 1024
height: 600

function createItem() {
Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 100; width
}

Component.onCompleted: root.createItem();
}

13.2.3. Gerenciando Elementos Criados Dinamicamente

Objetos criados dinamicamente podem ser tratados como qualquer outro objeto em uma

236
cena QML. No entanto, existem algumas armadilhas que precisam ser tratadas. O mais
importante é o conceito de contextos de criação.

O contexto de criação de um objeto criado dinamicamente é o contexto em que está sendo


criado. Este não é necessariamente o mesmo contexto em que o parent existe. Quando o
contexto de criação é destruído, o mesmo acontece com as ligações relativas ao objeto.
Isso significa que é importante implementar a criação de objetos dinâmicos em um local
no código que será instanciado durante todo o tempo de vida dos objetos.

Objetos criados dinamicamente também podem ser destruídos dinamicamente. Ao fazer


isso, existe uma regra básica: nunca tente destruir um objeto que você não criou. Isso
também inclui elementos que você criou, mas não usa um mecanismo dinâmico, como
Component.createObject oucreateQmlObject.

Um objeto é destruído chamando sua função destroy. A função usa um argumento


opcional que é um inteiro especificando quantos milissegundos os objetos devem existir
antes de serem destruídos. Isso é útil para, por exemplo, deixar o objeto concluir uma
transição final.
item = Qt.createQmlObject(...);
...
item.destroy();

Note

É possível destruir o objeto de dentro, tornando possível criar janelas pop-up auto-
destrutivas, por exemplo.

13.3. Rastreando Objetos Dinâmicos


Trabalhando com objetos dinâmicos, muitas vezes é necessário rastrear os objetos criados.
Outra característica comum é poder armazenar e restaurar o estado dos objetos dinâmicos.
Ambas as tarefas são facilmente manipuladas usando um ListModel que preenchemos
dinamicamente.

No exemplo mostrado abaixo, dois tipos de elementos, rockets e ufos, podem ser criados e
movidos pelo usuário. Para poder manipular toda a cena dos elementos criados
dinamicamente, usamos um modelo para rastrear os itens.

Todo

ilustração

O modelo, umListModel, é preenchido conforme os itens são criados. A referência do


objeto é rastreada ao lado do URL de origem usado ao instanciá-lo. Este último não é

237
estritamente necessário para rastrear os objetos, mas será útil depois.
import QtQuick 2.5
import "create-object.js" as CreateObject

Item {
id: root

ListModel {
id: objectsModel
}

function addUfo() {
CreateObject.create("ufo.qml", root, itemAdded);
}

function addRocket() {
CreateObject.create("rocket.qml", root, itemAdded);
}

function itemAdded(obj, source) {


objectsModel.append({"obj": obj, "source": source})
}

Como você pode ver no exemplo acima, o create-object.js é uma forma mais
generalizada do JavaScript introduzida anteriormente. O método create usa três
argumentos: um URL de origem, um elemento raiz e um retorno de chamada para chamar
quando terminar. O callback é chamado com dois argumentos: uma referência ao objeto
recém-criado e o URL de origem usado.

Isso significa que cada vez que funções addUfo ouaddRocket são chamadas, a função
itemAdded será chamada quando o novo objeto tiver sido criado. O último anexará a
referência do objeto e a URL de origem ao modelo objectsModel.

O objectsModel pode ser usado de várias maneiras. No exemplo em questão, a função


clearItems conta com isso. Esta função demonstra duas coisas. Primeiro, como iterar
sobre o modelo e executar uma tarefa, ou seja, chamando a função destroy para cada item
para removê-lo. Em segundo lugar, destaca o fato de que o modelo não é atualizado à
medida que os objetos são destruídos. Em vez de remover o item de modelo conectado ao
objeto em questão, a propriedadeobj desse item de modelo é definida como null. Para
remediar isso, o código explicitamente precisa limpar o item do modelo à medida que os
objetos são removidos.
function clearItems() {
while(objectsModel.count > 0) {
objectsModel.get(0).obj.destroy();
objectsModel.remove(0);
}
}

Tendo um modelo representando todos os itens criados dinamicamente, é fácil criar uma
função que serialize os itens. No código de exemplo, as informações serializadas
consistem no URL de origem de cada objeto ao longo de suas propriedades x e y. Estas

238
são as propriedades que podem ser alteradas pelo usuário. A informação é usada para
construir uma string de documento XML.
function serialize() {
var res = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<scene>\n";

for(var ii=0; ii < objectsModel.count; ++ii) {


var i = objectsModel.get(ii);
res += " <item>\n <source>" + i.source + "</source>\n <x>"
}

res += "</scene>";

return res;
}

A cadeia de documentos XML pode ser usada com umXmlListModel definindo a


propriedade xml do modelo. No código abaixo, o modelo é mostrado ao longo da função
deserialize. A função deserialize ativa a desserialização definindo o dsIndex para se
referir ao primeiro item do modelo e, em seguida, chamando a criação desse item. O
retorno de chamada, dsItemAdded define as propriedades x e y do objeto recém-criado.
Em seguida, atualiza o índice e cria o proximo objeto, se houver.
XmlListModel {
id: xmlModel
query: "/scene/item"
XmlRole { name: "source"; query: "source/string()" }
XmlRole { name: "x"; query: "x/string()" }
XmlRole { name: "y"; query: "y/string()" }
}

function deserialize() {
dsIndex = 0;
CreateObject.create(xmlModel.get(dsIndex).source, root, dsItemAdded);
}

function dsItemAdded(obj, source) {


itemAdded(obj, source);
obj.x = xmlModel.get(dsIndex).x;
obj.y = xmlModel.get(dsIndex).y;

dsIndex ++;

if (dsIndex < xmlModel.count)


CreateObject.create(xmlModel.get(dsIndex).source, root, dsItemAdded
}

property int dsIndex

O exemplo demonstra como um modelo pode ser usado para rastrear itens criados e como
é fácil serializar e desserializar essas informações. Isso pode ser usado para armazenar
uma cena dinamicamente preenchida, como um conjunto de widgets. No exemplo, um
modelo foi usado para rastrear cada item.

239
Uma solução alternativa seria usar a propriedade children da raiz de uma cena para
rastrear itens. Isso, no entanto, exige que os próprios itens conheçam o URL de origem
para recriá-los. Também requer que a cena consista apenas em itens criados
dinamicamente, para evitar a tentativa de serializar e depois desserializar os objetos
alocados estaticamente.

13.4. Resumo
Neste capítulo, analisamos a criação de elementos QML dinamicamente. Isso nos permite
criar cenas QML livremente, abrindo as portas para a configuração do usuário e as
arquiteturas baseadas em plug-in.

A maneira mais fácil de carregar dinamicamente um elemento QML é usar um elemento


Loader. Isso age como um espaço reservado para o conteúdo que está sendo carregado.

Para uma abordagem mais dinâmica, a função Qt.createQmlObject pode ser usada para
instanciar uma cadeia de QML. Essa abordagem, no entanto, tem limitações. A solução
completa é criar dinamicamente um Component usando a função Qt.createComponent.
Objetos são criados chamando a funçãocreateObject de um Component.

Como ligações e conexões de sinal dependem da existência de um idde objeto ou acesso à


instanciação de objeto, uma abordagem alternativa deve ser usada para objetos criados
dinamicamente. Para criar uma ligação, o elemento Binding é usado. O elemento
Connections possibilita a conexão a sinais de um objeto criado dinamicamente.

Um dos desafios de trabalhar com itens criados dinamicamente é rastreá-los. Isso pode ser
feito usando um ListModel. Por ter um modelo rastreando os itens criados
dinamicamente, é possível implementar funções para serialização e desserialização,
possibilitando armazenar e restaurar cenas criadas dinamicamente.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

240
Qt5 Cadaques Book » previous | next

14. JavaScript
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

JavaScript é a lingua-franca no desenvolvimento de clientes web. Ele também começa a


ganhar força no desenvolvimento de servidores web, principalmente pelo nó js. Como tal,
é uma adição adequada como uma linguagem imperativa para o lado da linguagem
declarativa QML. O próprio QML como uma linguagem declarativa é usado para
expressar a hierarquia da interface com o usuário, mas é limitado para expressar o código
operacional. Às vezes você precisa de uma maneira de expressar operações, aqui o
JavaScript entra em cena.

Note

Existe uma pergunta aberta na comunidade Qt sobre a mistura certa sobre


QML/JS/QtC++ em um aplicativo Qt moderno. A mistura recomendada comumente
aceita é limitar a parte JS de seu aplicativo ao mínimo e fazer sua lógica de negócios
dentro do QtC++ e a lógica da UI dentro do QML/JS.

Este livro empurra os limites, o que nem sempre é a combinação certa para o
desenvolvimento de um produto e nem para todos. É importante seguir as habilidades de
sua equipe e seu gosto pessoal. Em caso de dúvida, siga a recomendação.

Aqui está um pequeno exemplo de como JS se parece, misturado com QML:


Button {
width: 200
height: 300
property bool checked: false
text: "Click to toggle"

// JS function
function doToggle() {
checked = !checked
}

onTriggered: {
// this is also JavaScript

241
doToggle();
console.log('checked: ' + checked)
}
}

Portanto, o JavaScript pode vir de muitos lugares dentro do QML como uma função JS
autônoma, como um módulo JS e pode estar em todo lado direito de uma associação de
propriedade.
import "util.js" as Util // import a pure JS module

Button {
width: 200
height: width*2 // JS on the right side of property binding

// standalone function (not really useful)


function log(msg) {
console.log("Button> " + msg);
}

onTriggered: {
// this is JavaScript
log();
Qt.quit();
}
}

Dentro do QML você declara a interface do usuário, com JavaScript você a torna
funcional. Então, quanto JavaScript você deveria escrever? Depende do seu estilo e da sua
familiaridade com o desenvolvimento do JS. JS é uma linguagem fracamente tipada, o que
dificulta detectar defeitos de tipo. Também funções esperam todas as variações de
argumentos, o que pode ser um erro muito desagradável para detectar. A maneira de
identificar defeitos é um teste de unidade rigoroso ou um teste de aceitação. Então, se você
desenvolver uma lógica real (não algumas linhas de código de cola) no JS, você deve
começar a usar a abordagem de teste primeiro. Em geral, equipes mistas (Qt/C++ e
QML/JS) são muito bem-sucedidas quando minimizam a quantidade de JS no frontend
como a lógica do domínio e fazem o trabalho pesado no Qt C++ no backend. O backend
deve então ser rigorosamente testado na unidade, para que os desenvolvedores frontend
possam confiar no código e focar em todos esses pequenos requisitos de interface com o
usuário.

Note

Em geral: os desenvolvedores de back-end são orientados por funcionalidade e os


desenvolvedores de frontend são orientados pela história do usuário.

14.1. Navegador/HTML vs QtQuick/QML


O navegador é o runtime de execução para renderizar HTML e executar o Javascript

242
associado ao HTML. Hoje em dia, as aplicações web modernas contêm muito mais
JavaScript do que HTML. O Javascript dentro do navegador é um ambiente ECMAScript
padrão com algumas adições de navegador. Um ambiente JS típico dentro do navegador
conhece o objeto window para acessar a janela do navegador. Há também os seletores de
DOM básicos que são usados pelo jQuery para fornecer os seletores CSS. Adicional existe
uma função setTimeout para chamar uma função após um certo tempo. Além destes, o
ambiente é um ambiente JavaScript padrão, semelhante ao QML/JS.

O que também é diferente é onde o JS pode aparecer dentro de HTML e QML. Em HTML
você só pode adicionar JS em manipuladores de eventos (ex: página carregada, mouse
pressionado). Por exemplo, seu JS inicializa normalmente no carregamento da página, que
é comparável ao Component.onCompleted no QML. Por exemplo, você não pode usar JS
para ligações(bindings) de propriedade (pelo menos não diretamente, o AngularJS
aprimora a árvore DOM para permitir isso, mas isso está longe do HTML padrão).

Portanto, no QML, o JS é muito mais um cidadão de primeira classe e muito mais


integrado à árvore de renderização do QML. O que torna a sintaxe muito mais legível.
Além disso, as pessoas que desenvolveram aplicativos HTML/JS se sentirão em casa
dentro do QML/JS.

14.2. A Linguagem
Este capítulo não oferece uma introdução geral ao JavaScript. Existem outros livros
disponíveis para uma introdução geral ao JavaScript, por favor visite este linkMozilla
Developer Network [https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-
introduction_to_JavaScript].

Na superfície, o JavaScript é uma linguagem muito comum e não difere muito de outras
linguagens:
function countDown() {
for(var i=0; i<10; i++) {
console.log('index: ' + i)
}
}

function countDown2() {
var i=10;
while( i>0 ) {
i--;
}
}

Mas esteja avisado que JS tem escopo de função e não escopo de bloco como em C++
Functions and function scope
[https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Functions_and_function_scope]).

As declarações if ... else, break,continuam a funcionar como esperado. O switch case


também pode comparar outros tipos e não apenas valores inteiros:

243
function getAge(name) {
// switch over a string
switch(name) {
case "father":
return 58;
case "mother":
return 56;
}
return unknown;
}

JS conhece vários valores que podem ser falsos, por ex:false, 0, "", undefined, null).
Por exemplo, uma função retorna por padrão undefined. Para testar o uso falso use o
operador de identidade===.O operador de igualdade == fará conversão de tipo para testar a
igualdade.Se possível, use o operador de igualdade mais rápido e melhor ===, que testará
a identidade(veja emComparison operators [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators].

Sob o capô, o javascript tem suas próprias maneiras de fazer as coisas. Por exemplo,
matrizes(arrays):
function doIt() {
var a = [] // empty arrays
a.push(10) // addend number on arrays
a.push("Monkey") // append string on arrays
console.log(a.length) // prints 2
a[0] // returns 10
a[1] // returns Monkey
a[2] // returns undefined
a[99] = "String" // a valid assignment
console.log(a.length) // prints 100
a[98] // contains the value undefined
}

Também para pessoas vindas de C++ ou Java, que estão acostumadas com uma linguagem
OO, o JS simplesmente funciona diferente. JS não é puramente uma linguagem OO, é uma
linguagem chamada protótipo. Cada objeto tem um objeto de protótipo. Um objeto é
criado com base em seu objeto de protótipo. Por favor, leia mais sobre isso no livro
Javascript the Good Parts by Douglas Crockford [http://javascript.crockford.com] ou assista ao
vídeo abaixo.

244
JavaScript: The Good Parts

Para testar alguns snippets JS pequenos, você pode usar o JS Console on-lineJS Console
[http://jsconsole.com] ou apenas criar um pequeno código QML:

import QtQuick 2.5

Item {
function runJS() {
console.log("Your JS code goes here");
}
Component.onCompleted: {
runJS();
}
}

14.3. Objetos JS
Ao trabalhar com JS, existem alguns objetos e métodos que são usados com mais
frequência. Esta é uma pequena coleção deles.

Math.floor(v), Math.ceil(v), Math.round(v) - maior, menor, inteiro arredondado


de float
Math.random() - crie um número aleatório entre 0 e 1
Object.keys(o) - obter chaves do objeto (incluindo QObject)
JSON.parse(s), JSON.stringify(o) - conversão entre o objeto JS e a string JSON
Number.toFixed(p) - float de precisão fixa
Date - Manipulação de data

Você pode encontrá-los também em: JavaScript reference [https://developer.mozilla.org/en-


US/docs/Web/JavaScript/Reference]

Aqui alguns exemplos pequenos e limitados de como usar o JS com o QML. Eles devem
dar uma ideia de como você pode usar o JS dentro do QML

245
Imprimir todas as chaves do item QML
Item {
id: root
Component.onCompleted: {
var keys = Object.keys(root);
for(var i=0; i<keys.length; i++) {
var key = keys[i];
// prints all properties, signals, functions from object
console.log(key + ' : ' + root[key]);
}
}
}

Analisar um objeto para uma string JSON e voltar


Item {
property var obj: {
key: 'value'
}

Component.onCompleted: {
var data = JSON.stringify(obj);
console.log(data);
var obj = JSON.parse(data);
console.log(obj.key); // > 'value'
}
}

Data atual
Item {
Timer {
id: timeUpdater
interval: 100
running: true
repeat: true
onTriggered: {
var d = new Date();
console.log(d.getSeconds());
}
}
}

Chame uma função pelo nome


Item {
id: root

function doIt() {
console.log("doIt()")
}

Component.onCompleted: {
// Call using function execution

246
root["doIt"]();
var fn = root["doIt"];
// Call using JS call method (could pass in a custom this object and argument
fn.call()
}
}

14.4. Criando um Console JS


Como um pequeno exemplo, vamos criar um console JS. Precisamos de um campo de
entrada onde o usuário possa inserir suas expressões JS e, idealmente, deve haver uma
lista de resultados de saída. Como isso deve parecer mais com um aplicativo de desktop,
usamos o módulo QtQuick Controls.

Note

Um console JS dentro de seu próximo projeto pode ser realmente benéfico para testes.
Aprimorado com um efeito Quake-Terminal, também é bom para impressionar os
clientes. Para usá-lo com sabedoria, você precisa controlar o escopo no qual o console JS
avalia, por ex: a tela visível atual, o modelo de dados principal, um objeto principal
singleton ou todos juntos.

247
Usamos o Qt Creator para criar um projeto Qt Quick UI usando controles QtQuick. Nós
chamamos o projeto JSConsole. Depois que o assistente terminar, já temos uma estrutura
básica para o aplicativo com uma janela de aplicativo e um menu para sair do aplicativo.

Para a entrada usamos um TextFielde um Button para enviar a entrada para avaliação. O
resultado da avaliação da expressão é exibido usando um ListView com um ListModel
como modelo e dois rótulos para exibir a expressão e o resultado avaliado.
// part of JSConsole.qml
ApplicationWindow {
id: root

...

ColumnLayout {
anchors.fill: parent
anchors.margins: 9
RowLayout {
Layout.fillWidth: true
TextField {
id: input
Layout.fillWidth: true
focus: true

248
onAccepted: {
// call our evaluation function on root
root.jsCall(input.text)
}
}
Button {
text: qsTr("Send")
onClicked: {
// call our evaluation function on root
root.jsCall(input.text)
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: '#333'
border.color: Qt.darker(color)
opacity: 0.2
radius: 2
}

ScrollView {
id: scrollView
anchors.fill: parent
anchors.margins: 9
ListView {
id: resultView
model: ListModel {
id: outputModel
}
delegate: ColumnLayout {
width: ListView.view.width
Label {
Layout.fillWidth: true
color: 'green'
text: "> " + model.expression
}
Label {
Layout.fillWidth: true
color: 'blue'
text: "" + model.result
}
Rectangle {
height: 1
Layout.fillWidth: true
color: '#333'
opacity: 0.2
}
}
}
}
}
}
}

A função de avaliação jsCall faz a avaliação não por si só, isso foi movido para um

249
módulo JS (jsconsole.js) para uma separação mais clara.
// part of JSConsole.qml

import "jsconsole.js" as Util

...

ApplicationWindow {
id: root

...

function jsCall(exp) {
var data = Util.call(exp);
// insert the result at the beginning of the list
outputModel.insert(0, data)
}
}

Por segurança, não usamos a função eval do JS, pois isso permitiria ao usuário modificar
o escopo local. Usamos o construtor Function para criar uma função JS em tempo de
execução e passar em nosso escopo como essa variável. Como a função é criada toda vez
que ela não atua como um encerramento e armazena seu próprio escopo, precisamos usar
this.a = 10 para armazenar o valor dentro deste escopo da função.

// jsconsole.js
.pragma library

var scope = {
// our custom scope injected into our function evaluation
}

function call(msg) {
var exp = msg.toString();
console.log(exp)
var data = {
expression : msg
}
try {
var fun = new Function('return (' + exp + ');');
data.result = JSON.stringify(fun.call(scope), null, 2)
console.log('scope: ' + JSON.stringify(scope, null, 2) + 'result: '
} catch(e) {
console.log(e.toString())
data.error = e.toString();
}
return data;
}

O retorno de dados da função de chamada é um objeto JS com uma propriedade result,


expression e error: data: { expression: {}, result: {}, error: {}. Podemos usar
esse objeto JS diretamente dentro do ListModel e acessá-lo a partir do delegate,
ex:model.expression nos dá a expressão de entrada. Pela simplicidade do exemplo,
ignoramos o resultado do erro.

250
© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

251
Qt5 Cadaques Book » previous | next

15. Qt e C++
Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado noassets folder.

Qt é um kit de ferramentas C++ com uma extensão para QML e Javascript. Existem
muitos bindings de linguagem para o Qt, mas como o Qt é desenvolvido em C++, o
espírito do C++ pode ser encontrado em todas as classes. Nesta seção, veremos o Qt a
partir de uma perspectiva C++ para construir uma melhor compreensão de como estender
o QML com plug-ins nativos desenvolvidos usando o C++. Através do C++, é possível
estender e controlar o ambiente de execução fornecido ao QML.

Este capítulo irá, assim como o Qt, exigir que o leitor tenha algum conhecimento básico
de C++. O Qt não depende de recursos avançados do C++, e eu geralmente considero o
estilo Qt do C++ muito legível, então não se preocupe se você sentir que o seu
conhecimento em C++ é instável.

Aproximando-se do Qt a partir de uma direção em C++, você descobrirá que o Qt


enriquece o C++ com vários recursos de linguagem modernas, ativados através da
disponibilização de dados de introspecção. Isso é possível através do uso da classe
QObject do QObject. Dados de introspecção, ou metadados, mantêm informações das
classes em tempo de execução(run-time), algo que o C++ comum não faz. Isso possibilita
sondar dinamicamente os objetos para obter informações sobre detalhes como suas
propriedades e métodos disponíveis.

O Qt usa essa meta informação para habilitar um conceito de retorno de chamada muito
frouxamente vinculado usando sinais e slots. Cada sinal pode ser conectado a qualquer
número de slots ou até mesmo outros sinais .Quando um sinal é emitido de uma instância
de objeto, os slots conectados são chamados. O objeto emissor de sinal não precisa saber
nada sobre o objeto que possui o slot e vice-versa, esse mecanismo é usado para criar
componentes muito reutilizáveis com muito poucas dependências entre componentes.

Os recursos de introspecção também são usados para criar ligações dinâmicas de


linguagem, tornando possível expor uma instância de objeto C++ para QML e tornar as
funções C++ ativáveis do Javascript. Existem outras ligações para o Qt C++ e, além do
JavaScript padrão, um bindingpopular é a ligação do Python chamadoPyQt
[http://www.riverbankcomputing.co.uk/software/pyqt/intro].

252
Além deste conceito central, o Qt possibilita o desenvolvimento de aplicativos
multiplataforma usando C++. O Qt C++ fornece uma abstração de plataforma nos
diferentes sistemas operacionais, o que permite que o desenvolvedor se concentre na tarefa
em questão e não nos detalhes de como você abre um arquivo em diferentes sistemas
operacionais. Isso significa que você pode recompilar o mesmo código-fonte para
Windows, OS X e Linux, e o Qt cuida das diferentes maneiras de lidar com certas coisas
do sistema operacional. O resultado final são aplicativos criados nativamente com a
aparência e a sensação da plataforma de destino. Como o celular é a nova área de trabalho,
as versões mais recentes do Qt também podem segmentar várias plataformas móveis
usando o mesmo código-fonte, por ex: iOS, Android, Jolla, BlackBerry, Ubuntu Phone,
Tizen.

Quando se trata de reutilizar, não apenas o código-fonte pode ser reutilizado, mas as
habilidades do desenvolvedor também são reutilizáveis. Uma equipe que conheça a Qt
pode alcançar muito mais plataformas do que uma equipe que se concentra apenas em
uma tecnologia específica de plataforma única e, como a Qt é tão flexível, a equipe pode
criar diferentes componentes do sistema usando a mesma tecnologia.

Para todas as plataformas, o Qt oferece um conjunto de tipos básicos, por ex: strings
suporte unicode completo, lists, vectors, buffers. Ele também fornece uma abstração
comum para o loop principal da plataforma de destino, além de suporte de rede e
segmentação entre plataformas. A filosofia geral é que, para um desenvolvedor de
aplicativos, o Qt vem com todas as funcionalidades necessárias incluídas. Para tarefas
específicas de domínio, como a interface com suas bibliotecas nativas, o Qt vem com

253
várias classes auxiliares para tornar isso mais fácil.

15.1. Um aplicativo Boilerplate


A melhor maneira de entender o Qt é começar de um pequeno aplicativo de demonstração.
Este aplicativo cria um simples"Hello World!" string e grava em um arquivo usando
caracteres unicode.
#include <QCoreApplication>
#include <QString>
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QDebug>

int main(int argc, char *argv[])


{
QCoreApplication app(argc, argv);

// prepare the message


QString message("Hello World!");

// prepare a file in the users home directory named out.txt


QFile file(QDir::home().absoluteFilePath("out.txt"));
// try to open the file in write mode
if(!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can not open file with write access";
return -1;
}
// as we handle text we need to use proper text codecs
QTextStream stream(&file);
// write message to file via the text stream
stream << message;

// do not start the eventloop as this would wait for external IO


// app.exec();

// no need to close file, closes automatically when scope ends


return 0;
}

O exemplo simples demonstra o uso do acesso a arquivos e a maneira correta de escrever


texto em um arquivo usando codecs de texto através do fluxo de texto. Para dados
binários, existe um fluxo binário de plataforma cruzada chamadoQDataStream. As
diferentes classes que usamos são incluídas usando o nome da classe. Outra possibilidade
seria usar um nome de módulo e classe, ex:#include <QtCore/QFile>. Para os
preguiçosos, há também a possibilidade de incluir um módulo inteiro usando#include
<QtCore>. Ex: NoQtCore você tem as classes mais comuns usadas para um aplicativo, que
não são dependentes da UI. Dê uma olhada na lista de classes doQtCore class list
[http://doc.qt.io/qt-5/qtcore-module.html] ou na visão geral do QtCore overview [http://doc.qt.io/qt-
5/qtcore-index.html].

254
Você constrói o aplicativo usando o qmake e make. O QMake lê um arquivo de projeto e
gera um Makefile que pode ser chamado usando o make. O arquivo de projeto é
independente de plataforma e o qmake tem algumas regras para aplicar as configurações
específicas da plataforma ao arquivo de criação gerado. O projeto também pode conter
escopos de plataforma para regras específicas da plataforma, que são necessárias em
alguns casos específicos. Aqui está um exemplo de um arquivo de projeto simples.
# build an application
TEMPLATE = app

# use the core module and do not use the gui module
QT += core
QT -= gui

# name of the executable


TARGET = CoreApp

# allow console output


CONFIG += console

# for mac remove the application bundling


macx {
CONFIG -= app_bundle
}

# sources to be build
SOURCES += main.cpp

Nós não iremos aprofundar neste tópico. Basta lembrar que o Qt usa arquivos de projeto
para projetos e o qmake gera os arquivos de criação específicos da plataforma a partir
desses arquivos de projeto.

O exemplo de código simples acima apenas grava o texto e sai do aplicativo. Para uma
ferramenta de linha de comando, isso é bom o suficiente. Para uma interface de usuário,
você precisaria de um loop de eventos que aguardasse a entrada do usuário e, de alguma
forma, planejasse operações de re-draw. Então aqui segue o mesmo exemplo agora usa um
botão da área de trabalho para acionar a escrita.

Nossomain.cpp surpreendentemente ficou menor. Nós movemos o código para uma classe
própria para poder usar sinal/slots para a entrada do usuário, ex: o clique do botão. O
mecanismo de sinal/slot normalmente precisa de uma instância de objeto, como você verá
em breve.
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
#include "mainwindow.h"

int main(int argc, char** argv)


{
QApplication app(argc, argv);

255
MainWindow win;
win.resize(320, 240);
win.setVisible(true);

return app.exec();
}

Namain principal, simplesmente criamos o objeto da aplicação e iniciamos o loop de


eventos usando exec(). Por enquanto, o aplicativo fica no loop de eventos e aguarda a
entrada do usuário.
int main(int argc, char** argv)
{
QApplication app(argc, argv); // init application

// create the ui

return app.exec(); // execute event loop


}

O Qt oferece várias tecnologias de UI. Para este exemplo, usamos a biblioteca da interface
do usuário do Desktop Widgets usando o puro Qt C++. Criamos uma janela principal que
hospedará um botão para acionar a funcionalidade e também a janela principal hospedará
nossa funcionalidade principal, que conhecemos do exemplo anterior.

A janela principal em si é um widget. Ele se torna uma janela de nível superior, pois não
possui nenhum parent. Isso vem de como o Qt vê uma interface de usuário como uma
árvore de elementos de interface do usuário. Neste caso, a janela principal é o elemento
raiz, tornando-se assim uma janela, enquanto o botão push é um parent da janela principal
e se torna um widget dentro da janela.
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class MainWindow : public QMainWindow


{
public:

256
MainWindow(QWidget* parent=0);
~MainWindow();
public slots:
void storeContent();
private:
QPushButton *m_button;
};

#endif // MAINWINDOW_H

Além disso, definimos um slot público chamadostoreContent() que deve ser chamado
quando o botão é clicado. Um slot é um método C++ que é registrado no sistema de meta
objeto Qt e pode ser chamado dinamicamente.
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_button = new QPushButton("Store Content", this);

setCentralWidget(m_button);
connect(m_button, &QPushButton::clicked, this, &MainWindow::storeContent
}

MainWindow::~MainWindow()
{

void MainWindow::storeContent()
{
qDebug() << "... store content";
QString message("Hello World!");
QFile file(QDir::home().absoluteFilePath("out.txt"));
if(!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can not open file with write access";
return;
}
QTextStream stream(&file);
stream << message;
}

Na janela principal, primeiro criamos o botão e, em seguida, registramos o sinalclicked()


com o slot storeContent() usando o método connect. Toda vez que o sinal clicado é
emitido, o storeContent() é chamado. Tão simples como isso, os objetos se comunicam
via sinal e slots com acoplamento frouxo.

15.2. O QObject
Conforme descrito na introdução, o QObject é o que permite a introspecção de Qt. É a
classe base de quase todas as classes no Qt. Exceções são tipos de valor, como QColor,
QString e QList.

257
Um objeto Qt é um objeto C++ padrão, mas com mais habilidades. Estes podem ser
divididos em dois grupos: introspecção e gerenciamento de memória. A primeira significa
que um objeto Qt está ciente de seu nome de classe, seu relacionamento com outras
classes, assim como seus métodos e propriedades. O conceito de gerenciamento de
memória significa que cada objeto Qt pode ser o parent de objetos filhos. Oe parent
édonodos filhos, e quando o parent é destruído, é responsável por destruir seus filhos.

A melhor maneira de entender como as habilidades do QObject afetam uma classe é pegar
uma classe C++ padrão e o Qt ativá-la. A classe mostrada abaixo representa uma classe
comum.

A classe de pessoa é uma classe de dados com um nome e propriedades de gênero. A


classe de pessoa usa o sistema de objetos da Qt para adicionar informações meta à classe
c++. Ele permite que os usuários de um objeto pessoa se conectem aos slots e sejam
notificados quando as propriedades forem alteradas.
class Person : public QObject
{
Q_OBJECT // enabled meta object abilities

// property declarations required for QML


Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(Gender gender READ gender WRITE setGender NOTIFY genderChanged

// enables enum introspections


Q_ENUMS(Gender)

public:
// standard Qt constructor with parent for memory management
Person(QObject *parent = 0);

enum Gender { Unknown, Male, Female, Other };

QString name() const;


Gender gender() const;

public slots: // slots can be connected to signals


void setName(const QString &);
void setGender(Gender);

signals: // signals can be emitted


void nameChanged(const QString &name);
void genderChanged(Gender gender);

private:
// data members
QString m_name;
Gender m_gender;
};

O construtor passa o parent para a superclasse e inicializa os membros. As classes de valor


do Qt são automaticamente inicializadas. Neste caso,QString irá inicializar para uma
string null (QString::isNull()) e o membro gender será explicitamente inicializado para

258
o gênero desconhecido.
Person::Person(QObject *parent)
: QObject(parent)
, m_gender(Person::Unknown)
{
}

A função getter é nomeada após a propriedade e é normalmente uma função const


simples. O setter emite o sinal alterado quando a propriedade realmente mudou. Para isso,
inserimos um guarda para comparar o valor atual com o novo valor. E somente quando o
valor difere, atribuímos a variável membro e emitimos o sinal alterado.
QString Person::name() const
{
return m_name;
}

void Person::setName(const QString &name)


{
if (m_name != name) // guard
{
m_name = name;
emit nameChanged(m_name);
}
}

Tendo uma classe derivada doQObject, ganhamos mais habilidades de meta-objeto que
podemos explorar usando o método metaObject(). Por exemplo, recuperando o nome da
classe do objeto.
Person* person = new Person();
person->metaObject()->className(); // "Person"
Person::staticMetaObject.className(); // "Person"

Existem muitos outros recursos que podem ser acessados pela classe base doQObject e
pelo objeto meta. Por favor, confira a documentação do QMetaObject.

15.3. Build Systems


Construir software de forma confiável em diferentes plataformas pode ser uma tarefa
complexa.Você encontrará ambientes diferentes com diferentes compiladores, caminhos e
variações de biblioteca. O objetivo do Qt é proteger o desenvolvedor de aplicativos desses
problemas de plataforma cruzada. Para isso, o Qt introduziu o gerador de arquivos de
compilação qmake. qmake opera em um arquivo de projeto com o final .pro. Este arquivo
de projeto contém instruções sobre o aplicativo e as fontes a serem usadas. Executar o
qmake neste arquivo de projeto irá gerar um Makefile para você no unix e mac e até
mesmo no Windows se o toolchain do compilador mingw estiver sendo usado. Caso
contrário, pode criar um projeto de estúdio visual ou um projeto xcode.

Um fluxo de compilação típico no Qt sob o unix seria:

259
$ edit myproject.pro
$ qmake // generates Makefile
$ make

O Qt também permite que você use construções shadow. Uma construção shadow é uma
construção fora do local do código-fonte. Suponha que temos uma pasta myproject com
um arquivo myproject.pro. O fluxo seria assim:
$ mkdir build
$ cd build
$ qmake ../myproject/myproject.pro

Criamos uma pasta de compilação e, em seguida, chamamos o qmake de dentro da pasta


de compilação com a localização da pasta do nosso projeto. Isso configurará o arquivo de
criação de forma que todos os artefatos de construção sejam armazenados na pasta de
construção, e não na pasta do código-fonte. Isso nos permite criar builds para diferentes
versões do qt e construir configurações ao mesmo tempo e também não sobrecarregar a
nossa pasta de código soruce que é sempre uma coisa boa.

Quando você está usando o Qt Creator, ele faz essas coisas nos bastidores para você e
você não precisa se preocupar com essas etapas normalmente. Para projetos maiores e
para uma compreensão mais profunda do fluxo, é recomendável que você aprenda a
construir seu projeto qt a partir da linha de comando.

15.3.1. QMake

QMake é a ferramenta que lê seu arquivo de projeto e gera o arquivo de construção. Um


arquivo de projeto é uma gravação simplificada da configuração do seu projeto,
dependências externas e seus arquivos de origem. O arquivo de projeto mais simples é
provavelmente este:
// myproject.pro

SOURCES += main.cpp

Aqui nós construímos uma aplicação executável que terá o nome myproject com base no
nome do arquivo do projeto. A compilação só conterá o arquivo de origem main.cpp.E,
por padrão, usaremos o módulo QtCore e QtGui para este projeto. Se nosso projeto fosse
um aplicativo QML, precisaríamos adicionar o módulo QtQuick e QtQml à lista:
// myproject.pro

QT += qml quick

SOURCES += main.cpp

Agora o arquivo de construção sabe vincular-se aos módulos QtQml e QtQuick Qt.
QMake use o conceito de =, += e -= para atribuir, adicionar, remover elementos de uma
lista de opções, respectivamente. Para uma construção de console pura sem dependências
da UI você removeria o módulo QtGui:

260
// myproject.pro

QT -= gui

SOURCES += main.cpp

Quando você deseja criar uma biblioteca em vez de um aplicativo, é necessário alterar o
modelo de construção:
// myproject.pro
TEMPLATE = lib

QT -= gui

HEADERS += utils.h
SOURCES += utils.cpp

Agora o projeto será construído como uma biblioteca sem dependências da UI do usuário
e usado o utils.h e o arquivo de origem utils.cpp. O formato da biblioteca dependerá
do SO em que você está construindo o projeto.

Muitas vezes você terá configurações mais complicadas e precisará criar um conjunto de
projetos. Para isso, o qmake oferece o modelo subdirs. Suponha que teríamos um projeto
mylib e myapp. Então nossa configuração poderia ser assim:
my.pro
mylib/mylib.pro
mylib/utils.h
mylib/utils.cpp
myapp/myapp.pro
myapp/main.cpp

Nós já sabemos como o mylib.pro e o myapp.pro se pareceriam. O my.pro como o arquivo


de projeto abrangente seria assim:
// my.pro
TEMPLATE = subdirs

subdirs = mylib \
myapp

myapp.depends = mylib

Isso declara um projeto com dois subprojetos: mylib e myapp, ondemyapp depende
domylib. Quando você executa o qmake neste arquivo de projeto, ele gera um arquivo de
construção para cada projeto em uma pasta correspondente. Quando você executa o
arquivo make para my.pro, todos os subprojetos também são criados.

Às vezes, você precisa fazer uma coisa em uma plataforma e outra em outras plataformas
com base na sua configuração. Para este qmake introduz o conceito de escopos. Um
escopo é aplicado quando uma opção de configuração é configurada como true.

Por exemplo, para usar uma implementação de utilitários específicos do UNIX, você

261
poderia usar:
unix {
SOURCES += utils_unix.cpp
} else {
SOURCES += utils.cpp
}

O que ele diz é que, se a variável CONFIG contiver uma opção unix, aplique esse escopo,
caso contrário, use outro caminho. O típico é remover o pacote de aplicativos no mac:
macx {
CONFIG -= app_bundle
}

Isso criará seu aplicativo como um executável simples no mac e não como uma pasta .app
que é usada para a instalação do aplicativo.

Os projetos baseados em QMake são normalmente a escolha número um quando você


inicia a programação de aplicativos Qt. Existem também outras opções por aí. Todos têm
seus benefícios e desvantagens. Em breve discutiremos essas outras opções a seguir.

References

QMake Manual [http://doc.qt.io/qt-5//qmake-manual.html] - Tabela do manual de qmake


QMake Language [http://doc.qt.io/qt-5//qmake-language.html] - Atribuição de valor, escopos
QMake Variables [http://doc.qt.io/qt-5//qmake-variable-reference.html] - Variáveis como
TEMPLATE, CONFIG, QTsão explicados aqui

15.3.2. CMake

O CMake é uma ferramenta criada pela Kitware. Kitware é muito bem conhecido pelo seu
software de visualização 3D VTK e também pelo CMake, o gerador de makefile
multiplataforma.Ele usa uma série de arquivos CMakeLists.txt para gerar arquivos de
criação específicos da plataforma. CMake é usado pelo projeto KDE e, como tal, tem um
relacionamento especial com a comunidade Qt.

OCMakeLists.txt é o arquivo usado para armazenar a configuração do projeto. Para um


simples hello world usando o QtCore, o arquivo de projeto ficaria assim:
// ensure cmake version is at least 3.0
cmake_minimum_required(VERSION 3.0)
// adds the source and build location to the include path
set(CMAKE_INCLUDE_CURRENT_DIR ON)
// Qt's MOC tool shall be automatically invoked
set(CMAKE_AUTOMOC ON)
// using the Qt5Core module
find_package(Qt5Core)
// create excutable helloworld using main.cpp
add_executable(helloworld main.cpp)
// helloworld links against Qt5Core

262
target_link_libraries(helloworld Qt5::Core)

Isto irá construir um executável helloworld usando main.cpp e ligado à biblioteca externa
Qt5Core. O arquivo de construção pode ser modificado para ser mais genérico:
// sets the PROJECT_NAME variable
project(helloworld)
cmake_minimum_required(VERSION 3.0)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core)

// creates a SRC_LIST variable with main.cpp as single entry


set(SRC_LIST main.cpp)
// add an executable based on the project name and source list
add_executable(${PROJECT_NAME} ${SRC_LIST})
// links Qt5Core to the project executable
target_link_libraries(${PROJECT_NAME} Qt5::Core)

Você pode ver que CMake é bastante poderoso. Leva algum tempo para se acostumar com
a sintaxe. Em geral, diz-se que o CMake é mais adequado para projetos grandes e
complexos.

References

CMake Help [http://www.cmake.org/documentation/] - available online but also as QtHelp


format
Running CMake [http://www.cmake.org/runningcmake/]
KDE CMake Tutorial [https://techbase.kde.org/Development/Tutorials/CMake]
CMake Book [http://www.kitware.com/products/books/CMakeBook.html]
CMake and Qt [http://www.cmake.org/cmake/help/v3.0/manual/cmake-qt.7.html]

15.4. Classes comuns do Qt


A classe QObject forma as bases do Qt, mas existem muito mais classes no framework.
Antes de continuarmos a olhar para o QML e como estendê-lo, vamos dar uma olhada em
algumas classes básicas do Qt que são úteis para se conhecer.

Os exemplos de código mostrados nesta seção são escritos usando a biblioteca Qt Test. Ele
oferece uma ótima maneira de explorar a API do Qt e armazená-la para referência futura.
QVERIFY, QCOMPARE são funções fornecidas pela biblioteca de testes para confirmar uma
determinada condição. Usaremos{} scopes para evitar colisões de nomes. Então não fique
confuso.

15.4.1. QString

Em geral, a manipulação de texto no Qt é baseada em unicode. Para isso você usa a


QString. Ele vem com uma variedade de ótimas funções que você esperaria de um
framework moderno. Para dados de 8 bits, você usaria normalmente a classeQByteArray e,

263
para identificadores ASCII, o QLatin1String para preservar a memória. Para uma lista de
strings você pode usar um QList<QString> ou simplesmente a classe QStringList(que é
derivada deQList<QString>).

Aqui estão alguns exemplos de como usar a classe QString. QString pode ser criado na
pilha, mas armazena seus dados no heap. Além disso, ao atribuir uma string a outra, os
dados não serão copiados - apenas uma referência aos dados. Então isso é muito barato e
permite que o desenvolvedor se concentre no código e não no manuseio da memória.
QString usa contadores de referência para saber quando os dados podem ser excluídos
com segurança. Esse recurso é chamado Implicit Sharing [http://doc.qt.io/qt-5//implicit-
sharing.html] e é usado em muitas classes do Qt.

QString data("A,B,C,D"); // create a simple string


// split it into parts
QStringList list = data.split(",");
// create a new string out of the parts
QString out = list.join(",");
// verify both are the same
QVERIFY(data == out);
// change the first character to upper case
QVERIFY(QString("A") == out[0].toUpper());

Aqui vamos mostrar como converter um número em uma string e voltar. Existem também
funções de conversão para float ou double e outros tipos. Apenas procure a função na
documentação do Qt usada aqui e você encontrará as outras.
// create some variables
int v = 10;
int base = 10;
// convert an int to a string
QString a = QString::number(v, base);
// and back using and sets ok to true on success
bool ok(false);
int v2 = a.toInt(&ok, base);
// verify our results
QVERIFY(ok == true);
QVERIFY(v = v2);

Muitas vezes, no texto, você precisa ter um texto parametrizado. Uma opção poderia ser
usar QString("Hello" + name) mas um método mais flexível é a abordagem do
marcador arg. Ele preserva a ordem também durante a tradução quando o pedido pode ser
alterado.
// create a name
QString name("Joe");
// get the day of the week as string
QString weekday = QDate::currentDate().toString("dddd");
// format a text using paramters (%1, %2)
QString hello = QString("Hello %1. Today is %2.").arg(name).arg(weekday);
// This worked on Monday. Promise!
if(Qt::Monday == QDate::currentDate().dayOfWeek()) {
QCOMPARE(QString("Hello Joe. Today is Monday."), hello);
} else {
QVERIFY(QString("Hello Joe. Today is Monday.") != hello);

264
}

Às vezes você quer usar caracteres unicode diretamente no seu código. Para isso você
precisa lembrar como marcá-los para as classes QChar e QString.
// Create a unicode character using the unicode for smile :-)
QChar smile(0x263A);
// you should see a :-) on you console
qDebug() << smile;
// Use a unicode in a string
QChar smile2 = QString("\u263A").at(0);
QVERIFY(smile == smile2);
// Create 12 smiles in a vector
QVector<QChar> smilies(12);
smilies.fill(smile);
// Can you see the smiles
qDebug() << smilies;

Isto dá-lhe alguns exemplos de como tratar facilmente texto consciente unicode no Qt.
Para não unicode, a classe QByteArray também possui muitas funções auxiliares para
conversão. Por favor, leia a documentação do Qt para QString pois contém muitos bons
exemplos.

15.4.2. Recipientes Seqüenciais

Uma lista, fila, vetor ou lista vinculada é um contêiner seqüencial. O contêiner sequencial
mais utilizado é a classe QList. É uma classe baseada em modelo e precisa ser inicializada
com um tipo. Também é compartilhado implícito e armazena os dados internamente no
heap. Todas as classes de contêiner devem ser criadas na pilha. Normalmente você nunca
deseja usar o novonew QList<T>(), o que significa nunca usar new com um contêiner.

O QList é tão versátil quanto a classe QString e oferece uma ótima API para explorar
seus dados. Abaixo está um pequeno exemplo de como usar e iterar em uma lista usando
alguns novos recursos do C++ 11.
// Create a simple list of ints using the new C++11 initialization
// for this you need to add "CONFIG += c++11" to your pro file.
QList<int> list{1,2};

// append another int


list << 3;

// We are using scopes to avoid variable name clashes

{ // iterate through list using Qt for each


int sum(0);
foreach (int v, list) {
sum += v;
}
QVERIFY(sum == 6);
}
{ // iterate through list using C++ 11 range based loop
int sum = 0;

265
for(int v : list) {
sum+= v;
}
QVERIFY(sum == 6);
}

{ // iterate through list using JAVA style iterators


int sum = 0;
QListIterator<int> i(list);

while (i.hasNext()) {
sum += i.next();
}
QVERIFY(sum == 6);
}

{ // iterate through list using STL style iterator


int sum = 0;
QList<int>::iterator i;
for (i = list.begin(); i != list.end(); ++i) {
sum += *i;
}
QVERIFY(sum == 6);
}

// using std::sort with mutable iterator using C++11


// list will be sorted in descending order
std::sort(list.begin(), list.end(), [](int a, int b) { return a > b; });
QVERIFY(list == QList<int>({3,2,1}));

int value = 3;
{ // using std::find with const iterator
QList<int>::const_iterator result = std::find(list.constBegin(), list
QVERIFY(*result == value);
}

{ // using std::find using C++ lambda and C++ 11 auto variable


auto result = std::find_if(list.constBegin(), list.constBegin(), [value
QVERIFY(*result == value);
}

15.4.3. Contêineres Associativos

Um mapa, um dicionário ou um conjunto são exemplos de contêineres associativos. Eles


armazenam um valor usando uma chave. Eles são conhecidos por sua pesquisa rápida.
Demonstramos o uso do contêiner associativo mais utilizado no QHash demonstrando
também alguns novos recursos do C++ 11.
QHash<QString, int> hash({{"b",2},{"c",3},{"a",1}});
qDebug() << hash.keys(); // a,b,c - unordered
qDebug() << hash.values(); // 1,2,3 - unordered but same as order as keys

QVERIFY(hash["a"] == 1);
QVERIFY(hash.value("a") == 1);

266
QVERIFY(hash.contains("c") == true);

{ // JAVA iterator
int sum =0;
QHashIterator<QString, int> i(hash);
while (i.hasNext()) {
i.next();
sum+= i.value();
qDebug() << i.key() << " = " << i.value();
}
QVERIFY(sum == 6);
}

{ // STL iterator
int sum = 0;
QHash<QString, int>::const_iterator i = hash.constBegin();
while (i != hash.constEnd()) {
sum += i.value();
qDebug() << i.key() << " = " << i.value();
i++;
}
QVERIFY(sum == 6);
}

hash.insert("d", 4);
QVERIFY(hash.contains("d") == true);
hash.remove("d");
QVERIFY(hash.contains("d") == false);

{ // hash find not successfull


QHash<QString, int>::const_iterator i = hash.find("e");
QVERIFY(i == hash.end());
}

{ // hash find successfull


QHash<QString, int>::const_iterator i = hash.find("c");
while (i != hash.end()) {
qDebug() << i.value() << " = " << i.key();
i++;
}
}

// QMap
QMap<QString, int> map({{"b",2},{"c",2},{"a",1}});
qDebug() << map.keys(); // a,b,c - ordered ascending

QVERIFY(map["a"] == 1);
QVERIFY(map.value("a") == 1);
QVERIFY(map.contains("c") == true);

// JAVA and STL iterator work same as QHash

15.4.4. Arquivo IO

Geralmente, é necessário ler e gravar arquivos. QFile é na verdade um QObject mas na


maioria dos casos é criado na pilha. QFile contém sinais para informar o usuário quando

267
os dados podem ser lidos. Isso permite ler partes de dados de forma assíncrona até que
todo o arquivo seja lido. Por conveniência, também permite a leitura de dados no modo de
bloqueio. Isso deve ser usado apenas para quantidades menores de dados e não para
arquivos grandes. Felizmente, usamos apenas pequenas quantidades de dados nesses
exemplos.

Além de ler dados brutos de um arquivo em um QByteArray você também pode ler tipos
de dados usando a cadeia QDataStream e unicode usando o QTextStream. Nós vamos
mostrar-lhe como.
QStringList data({"a", "b", "c"});
{ // write binary files
QFile file("out.bin");
if(file.open(QIODevice::WriteOnly)) {
QDataStream stream(&file);
stream << data;
}
}
{ // read binary file
QFile file("out.bin");
if(file.open(QIODevice::ReadOnly)) {
QDataStream stream(&file);
QStringList data2;
stream >> data2;
QCOMPARE(data, data2);
}
}
{ // write text file
QFile file("out.txt");
if(file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
QString sdata = data.join(",");
stream << sdata;
}
}
{ // read text file
QFile file("out.txt");
if(file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
QStringList data2;
QString sdata;
stream >> sdata;
data2 = sdata.split(",");
QCOMPARE(data, data2);
}
}

15.4.5. Mais aulas

O Qt é um rico framework de aplicativos. Como tal, tem milhares de classes. Leva algum
tempo para se acostumar com todas essas classes e como usá-las. Felizmente o Qt tem
uma documentação muito boa com muitos exemplos úteis incluídos. Na maioria das
vezes, você pesquisa por uma turma e os casos de uso mais comuns já são fornecidos
como snippets. O que significa que você acabou de copiar e adaptar esses trechos.

268
Também os exemplos do Qt no código-fonte do Qt são de grande ajuda. Certifique-se de
tê-los disponíveis e pesquisáveis para tornar sua vida mais produtiva. Não perca tempo. A
comunidade do Qt é sempre útil. Quando você pergunta, é muito útil fazer perguntas
exatas e fornecer um exemplo simples que mostre suas necessidades. Isso irá melhorar
drasticamente o tempo de resposta dos outros. Então invista um pouquinho de tempo para
tornar a vida dos outros que querem ajudá-lo mais fácil :-).

Aqui algumas classes cuja documentação o autor acha que é uma leitura obrigatória:
QObject [http://doc.qt.io/qt-5//qobject.html], QString [http://doc.qt.io/qt-5//qstring.html], QByteArray
[http://doc.qt.io/qt-5//qbytearray.html], QFile [http://doc.qt.io/qt-5//qfile.html], QDir [http://doc.qt.io/qt-
5//qdir.html], QFileInfo [http://doc.qt.io/qt-5//qfileinfo.html], QIODevice [http://doc.qt.io/qt-
5//qiodevice.html], QTextStream [http://doc.qt.io/qt-5//qtextstream.html], QDataStream
[http://doc.qt.io/qt-5//qdatastream.html], QDebug [http://doc.qt.io/qt-5//qdebug.html], QLoggingCategory
[http://doc.qt.io/qt-5//qloggingcategory.html], QTcpServer [http://doc.qt.io/qt-5//qtcpserver.html],
QTcpSocket [http://doc.qt.io/qt-5//qtcpsocket.html], QNetworkRequest [http://doc.qt.io/qt-
5//qnetworkrequest.html], QNetworkReply [http://doc.qt.io/qt-5//qnetworkreply.html],
QAbstractItemModel [http://doc.qt.io/qt-5//qabstractitemmodel.html], QRegExp [http://doc.qt.io/qt-
5//qregexp.html], QList [http://doc.qt.io/qt-5//qlist.html], QHash [http://doc.qt.io/qt-5//qhash.html],
QThread [http://doc.qt.io/qt-5//qthread.html], QProcess [http://doc.qt.io/qt-5//qprocess.html],
QJsonDocument [http://doc.qt.io/qt-5//qjsondocument.html], QJSValue [http://doc.qt.io/qt-
5//qjsvalue.html].

Isso deve ser suficiente para o começo.

15.5. Modelos em C++


Os modelos no QML servem ao propósito de fornecer dados paraListViews, PathViews e
outras visualizações que usam um modelo e criam uma instância de um delegado para
cada entrada no modelo. A visualização é inteligente o suficiente para criar apenas essas
instâncias que estão visíveis ou no intervalo de cache. Isso possibilita ter modelos grandes
com dezenas de milhares de entradas, mas ainda assim ter uma interface muito boa. O
delegado age como um modelo a ser renderizado com os dados das entradas do modelo.
Portanto, em resumo: uma visualização renderiza entradas do modelo usando um delegado
como modelo. O modelo é um provedor de dados para visualizações.

Quando você não quiser usar o C++, também poderá definir modelos em QML puro. Você
tem várias maneiras de fornecer um modelo para a exibição. Para manipulação de dados
provenientes de C++ ou grande quantidade de dados, o modelo C++ é mais adequado do
que essas abordagens de QML puro. Mas muitas vezes você só precisa de algumas
entradas, então esses modelos QML são adequados.
ListView {
// using a integer as model
model: 5
delegate: Text { text: 'index: ' + index }
}

269
ListView {
// using a JS array as model
model: ['A', 'B', 'C', 'D', 'E']
delegate: Text { 'Char['+ index +']: ' + modelData }
}

ListView {
// using a dynamic QML ListModel as model
model: ListModel {
ListElement { char: 'A' }
ListElement { char: 'B' }
ListElement { char: 'C' }
ListElement { char: 'D' }
ListElement { char: 'E' }
}
delegate: Text { 'Char['+ index +']: ' + model.char }
}

As visualizações QML sabem como lidar com esses diferentes modelos. Para modelos
vindos do mundo C++, a visão espera que um protocolo específico seja seguido. Este
protocolo é definido em uma API (QAbstractItemModel) juntamente com a documentação
para o comportamento dinâmico. A API foi desenvolvida para o mundo de widgets de
desktop e é flexível o suficiente para atuar como base para árvores, ou tabelas de múltiplas
colunas, bem como listas. No QML, quase usamos apenas a versão da lista da API
(QAbstractListModel). A API contém algumas funções obrigatórias a serem
implementadas e algumas são opcionais. As partes opcionais lidam principalmente com o
caso de uso dinâmico de adição ou remoção de dados.

15.5.1. Um modelo simples

Um típico modelo QML C++ deriva de QAbstractListModel e implementa pelo menos a


função data e rowCount. Neste exemplo, usaremos uma série de nomes de cores SVG
fornecidos pela classeQColor e os exibiremos usando nosso modelo. Os dados são
armazenados em um contêiner de dadosQList<QString>.

Nosso DataEntryModel é derivado doQAbstractListModel e implementa as funções


obrigatórias. Podemos ignorar o parent emrowCount , pois isso é usado apenas em um
modelo de árvore. A classe QModelIndex fornece as informações de linha e coluna para a
célula, para as quais a visualização deseja recuperar dados. A visualização está obtendo
informações do modelo em uma linha/coluna e base de função. O QAbstractListModel é
definido no QtCore mas noQColor noQtGui. É por isso que temos a dependência QtGui
adicional. Para aplicações QML, não há problema em depender do QtGui , mas
normalmente não deve depender do QtWidgets.
#ifndef DATAENTRYMODEL_H
#define DATAENTRYMODEL_H

#include <QtCore>
#include <QtGui>

class DataEntryModel : public QAbstractListModel


{

270
Q_OBJECT
public:
explicit DataEntryModel(QObject *parent = 0);
~DataEntryModel();

public: // QAbstractItemModel interface


virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
private:
QList<QString> m_data;
};

#endif // DATAENTRYMODEL_H

No lado da implementação, a parte mais complexa é a função de dados. Primeiro,


precisamos fazer uma verificação de intervalo. E então nós verificamos o papel de
exibição. O Qt::DisplayRoleé a função de texto padrão que uma visualização solicitará.
Há um pequeno conjunto de funções padrão definidas no Qt que podem ser usadas, mas
normalmente um modelo definirá suas próprias funções para maior clareza. Todas as
chamadas que não contêm a função de exibição são ignoradas no momento e o valor
padrão QVariant() é retornado.
#include "dataentrymodel.h"

DataEntryModel::DataEntryModel(QObject *parent)
: QAbstractListModel(parent)
{
// initialize our data (QList<QString>) with a list of color names
m_data = QColor::colorNames();
}

DataEntryModel::~DataEntryModel()
{
}

int DataEntryModel::rowCount(const QModelIndex &parent) const


{
Q_UNUSED(parent);
// return our data count
return m_data.count();
}

QVariant DataEntryModel::data(const QModelIndex &index, int role) const


{
// the index returns the requested row and column information.
// we ignore the column and only use the row information
int row = index.row();

// boundary check for the row


if(row < 0 || row >= m_data.count()) {
return QVariant();
}

// A model can return data for different roles.


// The default role is the display role.
// it can be accesses in QML with "model.display"
switch(role) {

271
case Qt::DisplayRole:
// Return the color name for the particular row
// Qt automatically converts it to the QVariant type
return m_data.value(row);
}

// The view asked for other data, just return an empty QVariant
return QVariant();
}

A próxima etapa seria registrar o modelo com QML usando a chamada qmlRegisterType.
Isso é feito dentro do main.cpp antes do arquivo QML ser carregado.
#include <QtGui>
#include <QtQml>

#include "dataentrymodel.h"

int main(int argc, char *argv[])


{
QGuiApplication app(argc, argv);

// register the type DataEntryModel


// under the url "org.example" in version 1.0
// under the name "DataEntryModel"
qmlRegisterType<DataEntryModel>("org.example", 1, 0, "DataEntryModel");

QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

return app.exec();
}

Agora você pode acessar o DataEntryModel usando a declaraçãoimport org.example


1.0 e usá-la exatamente como outro item DataEntryModel {}do QML-.

Usamos isso neste exemplo para exibir uma lista simples de entradas de cores.
import org.example 1.0

ListView {
id: view
anchors.fill: parent
model: DataEntryModel {}
delegate: ListDelegate {
// use the defined model role "display"
text: model.display
}
highlight: ListHighlight { }
}

O ListDelegate é um tipo personalizado para exibir algum texto. O ListHighlight é


apenas um retângulo. O código foi extraído para manter o exemplo compacto.

A visualização agora pode exibir uma lista de strings usando o modelo C++ e a
propriedade display do modelo. Ainda é muito simples, mas já utilizável em QML.

272
Normalmente, os dados são fornecidos de fora do modelo e o modelo atuaria como uma
interface para a exibição.

15.5.2. Dados mais complexos

Na realidade, os dados do modelo são frequentemente muito mais complexos. Portanto, é


necessário definir funções personalizadas para que a visualização possa consultar outros
dados por meio de propriedades. Por exemplo, o modelo pode fornecer não apenas a cor
como string hexadecimal, mas também a matiz, saturação e brilho do modelo de cores
HSV “model.hue”, “model.saturation” e “model.brightness” em QML.
#ifndef ROLEENTRYMODEL_H
#define ROLEENTRYMODEL_H

#include <QtCore>
#include <QtGui>

class RoleEntryModel : public QAbstractListModel


{
Q_OBJECT
public:
// Define the role names to be used
enum RoleNames {
NameRole = Qt::UserRole,
HueRole = Qt::UserRole+2,
SaturationRole = Qt::UserRole+3,
BrightnessRole = Qt::UserRole+4
};

explicit RoleEntryModel(QObject *parent = 0);


~RoleEntryModel();

// QAbstractItemModel interface
public:
virtual int rowCount(const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override
protected:
// return the roles mapping to be used by QML
virtual QHash<int, QByteArray> roleNames() const override;
private:
QList<QColor> m_data;
QHash<int, QByteArray> m_roleNames;
};

#endif // ROLEENTRYMODEL_H

No cabeçalho, adicionamos o mapeamento de funções a ser usado para o QML. Quando o


QML tenta agora acessar uma propriedade do modelo (ex: “model.name”) o listview
pesquisará o mapeamento para “name”e perguntará ao modelo por dados usando o
NameRole. As funções definidas pelo usuário devem começar com Qt::UserRole e
precisam ser exclusivas para cada modelo.
#include "roleentrymodel.h"

273
RoleEntryModel::RoleEntryModel(QObject *parent)
: QAbstractListModel(parent)
{
// Set names to the role name hash container (QHash<int, QByteArray>)
// model.name, model.hue, model.saturation, model.brightness
m_roleNames[NameRole] = "name";
m_roleNames[HueRole] = "hue";
m_roleNames[SaturationRole] = "saturation";
m_roleNames[BrightnessRole] = "brightness";

// Append the color names as QColor to the data list (QList<QColor>)


for(const QString& name : QColor::colorNames()) {
m_data.append(QColor(name));
}

RoleEntryModel::~RoleEntryModel()
{
}

int RoleEntryModel::rowCount(const QModelIndex &parent) const


{
Q_UNUSED(parent);
return m_data.count();
}

QVariant RoleEntryModel::data(const QModelIndex &index, int role) const


{
int row = index.row();
if(row < 0 || row >= m_data.count()) {
return QVariant();
}
const QColor& color = m_data.at(row);
qDebug() << row << role << color;
switch(role) {
case NameRole:
// return the color name as hex string (model.name)
return color.name();
case HueRole:
// return the hue of the color (model.hue)
return color.hueF();
case SaturationRole:
// return the saturation of the color (model.saturation)
return color.saturationF();
case BrightnessRole:
// return the brightness of the color (model.brightness)
return color.lightnessF();
}
return QVariant();
}

QHash<int, QByteArray> RoleEntryModel::roleNames() const


{
return m_roleNames;
}

A implementação agora mudou apenas em dois lugares. Primeiro na inicialização. Nós

274
agora inicializamos a lista de dados com os tipos de dados QColor. Além disso, definimos
o mapa do nome da função para ser acessível para o QML. Este mapa é retornado mais
tarde no ::roleNames function.

A segunda mudança está na função ::data. Estendemos a troca para cobrir as outras
funções (ex: hue, saturation, brightness). Não há como retornar um nome SVG de uma
cor, pois uma cor pode receber qualquer cor e os nomes SVG são limitados. Então nós
pulamos isso. Armazenar os nomes requereria criar uma estrutura struct { QColor,
QString } para poder identificar a cor nomeada.

Depois de registrar o tipo, podemos usar o modelo e suas entradas em nossa interface de
usuário.
ListView {
id: view
anchors.fill: parent
model: RoleEntryModel {}
focus: true
delegate: ListDelegate {
text: 'hsv(' +
Number(model.hue).toFixed(2) + ',' +
Number(model.saturation).toFixed() + ',' +
Number(model.brightness).toFixed() + ')'
color: model.name
}
highlight: ListHighlight { }
}

Nós convertemos o tipo retornado em um tipo de número JS para poder formatar o número
usando a notação de ponto fixo. O código também funcionaria sem a chamada Number
(ex: plain model.saturation.toFixed(2)). Qual formato escolher, depende de quanto
você confia nos dados recebidos.

15.5.3. Dados Dinâmicos

Dados dinâmicos cobrem os aspectos de inserir, remover e limpar os dados do modelo. O


QAbstractListModelespera um determinado comportamento quando as entradas são
removidas ou inseridas. O comportamento é expresso em sinais que precisam ser
chamados antes e depois da manipulação. Por exemplo, para inserir uma linha em um
modelo, primeiro você precisa emitir o sinalbeginInsertRows, manipular os dados e,
finalmente, emitirendInsertRows.

Vamos adicionar as seguintes funções aos nossos cabeçalhos. Essas funções são
declaradas usando Q_INVOKABLE para poder chamá-las do QML. Outra maneira seria
declará-los um espaço público.
// inserts a color at the index (0 at begining, count-1 at end)
Q_INVOKABLE void insert(int index, const QString& colorValue);
// uses insert to insert a color at the end
Q_INVOKABLE void append(const QString& colorValue);
// removes a color from the index

275
Q_INVOKABLE void remove(int index);
// clear the whole model (ex: reset)
Q_INVOKABLE void clear();

Além disso, definimos uma propriedade count para obter o tamanho do modelo e um
método get para obter uma cor no índice fornecido. Isso é útil quando você deseja iterar
sobre o conteúdo do modelo do QML.
// gives the size of the model
Q_PROPERTY(int count READ count NOTIFY countChanged)
// gets a color at the index
Q_INVOKABLE QColor get(int index);

A implementação para inserção verifica primeiro os limites e se o valor fornecido é válido.


Só então começamos a inserir os dados.
void DynamicEntryModel::insert(int index, const QString &colorValue)
{
if(index < 0 || index > m_data.count()) {
return;
}
QColor color(colorValue);
if(!color.isValid()) {
return;
}
// view protocol (begin => manipulate => end]
emit beginInsertRows(QModelIndex(), index, index);
m_data.insert(index, color);
emit endInsertRows();
// update our count property
emit countChanged(m_data.count());
}

Anexar é muito simples. Nós reutilizamos a função insert com o tamanho do modelo.
void DynamicEntryModel::append(const QString &colorValue)
{
insert(count(), colorValue);
}

Remover é semelhante a inserir, mas chama de acordo com o protocolo de operação de


remoção.
void DynamicEntryModel::remove(int index)
{
if(index < 0 || index >= m_data.count()) {
return;
}
emit beginRemoveRows(QModelIndex(), index, index);
m_data.removeAt(index);
emit endRemoveRows();
// do not forget to update our count property
emit countChanged(m_data.count());
}

O funçãocountauxiliar é trivial. Apenas retorna a contagem de dados. A função get

276
também é bastante simples.
QColor DynamicEntryModel::get(int index)
{
if(index < 0 || index >= m_data.count()) {
return QColor();
}
return m_data.at(index);
}

Você precisa ter cuidado ao retornar apenas um valor que o QML entenda. Se não for um
dos tipos ou tipos básicos de QML que você deseja para o QML, será necessário registrar
primeiro o tipo com qmlRegisterType ouqmlRegisterUncreatableType. Você
usaqmlRegisterUncreatableType se o usuário não puder instanciar seu próprio objeto no
QML.

Agora você pode usar o modelo em QML e inserir, anexar, remover entradas do modelo.
Aqui está um pequeno exemplo que permite ao usuário inserir um nome de cor ou um
valor hexadecimal de cor e a cor é então anexada ao modelo e mostrada na exibição de
lista. O círculo vermelho no delegado permite que o usuário remova essa entrada do
modelo. Após a entrada ser removida, a visualização de lista é notificada pelo modelo e
atualiza seu conteúdo.

E aqui está o código QML. Você encontra o código-fonte completo também nos ativos
deste capítulo. O exemplo usa o módulo QtQuick.Controls e QtQuick.Layout para tornar o
código mais compacto. Esses módulos de controle fornecem um conjunto de elementos ui
relacionados à área de trabalho no QtQuick e o módulo de layouts fornece alguns
gerenciadores de layout muito úteis.
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.2

// our module
import org.example 1.0

277
Window {
visible: true
width: 480
height: 480

Background { // a dark background


id: background
}

// our dyanmic model


DynamicEntryModel {
id: dynamic
onCountChanged: {
// we print out count and the last entry when count is changing
print('new count: ' + count);
print('last entry: ' + get(count-1));
}
}

ColumnLayout {
anchors.fill: parent
anchors.margins: 8
ScrollView {
Layout.fillHeight: true
Layout.fillWidth: true
ListView {
id: view
// set our dynamic model to the views model property
model: dynamic
delegate: ListDelegate {
width: ListView.view.width
// construct a string based on the models proeprties
text: 'hsv(' +
Number(model.hue).toFixed(2) + ',' +
Number(model.saturation).toFixed() + ',' +
Number(model.brightness).toFixed() + ')'
// sets the font color of our custom delegates
color: model.name

onClicked: {
// make this delegate the current item
view.currentIndex = index
view.focus = true
}
onRemove: {
// remove the current entry from the model
dynamic.remove(index)
}
}
highlight: ListHighlight { }
// some fun with transitions :-)
add: Transition {
// applied when entry is added
NumberAnimation {
properties: "x"; from: -view.width;
duration: 250; easing.type: Easing.InCirc
}

278
NumberAnimation { properties: "y"; from: view.height;
duration: 250; easing.type: Easing.InCirc
}
}
remove: Transition {
// applied when entry is removed
NumberAnimation {
properties: "x"; to: view.width;
duration: 250; easing.type: Easing.InBounce
}
}
displaced: Transition {
// applied when entry is moved
// (e.g because another element was removed)
SequentialAnimation {
// wait until remove has finished
PauseAnimation { duration: 250 }
NumberAnimation { properties: "y"; duration: 75
}
}
}
}
}
TextEntry {
id: textEntry
onAppend: {
// called when the user presses return on the text field
// or clicks the add button
dynamic.append(color)
}

onUp: {
// called when the user presses up while the text field is focuse
view.decrementCurrentIndex()
}
onDown: {
// same for down
view.incrementCurrentIndex()
}

}
}
}

Model view programming é uma das tarefas mais difíceis no Qt. É uma das poucas classes
em que você precisa implementar uma interface como um desenvolvedor de aplicativos
normal. Todas as outras classes que você usa normalmente. O esboço de modelos deve
sempre começar no lado da QML. Você deve imaginar como seus usuários usariam seu
modelo dentro do QML. Para isso, geralmente é uma boa ideia criar um protótipo primeiro
usando o ListModel para ver como isso funciona melhor no QML. Isso também é verdade
quando se trata de definir APIs do QML. Disponibilizar dados do C++ para o QML não é
apenas um limite de tecnologia, mas também uma mudança de paradigma de
programação, do imperativo para a programação de estilo declarativo. Então esteja
preparado para alguns retrocessos e momentos aha :-).

279
15.5.4. Técnicas Avançadas
© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

280
Qt5 Cadaques Book » previous | next

16. Estendendo QML com C++


Section author: jryannel [https://github.com/jryannel]

Note

Last Build: March 21, 2016 at 20:39 CET

O código fonte deste capítulo pode ser encontrado no assets folder.

A execução de QML dentro do espaço confinado que o QML como linguagem oferece às
vezes pode ser limitante. Ao estender o run-time do QML com a funcionalidade nativa
escrita em C++, o aplicativo pode utilizar o desempenho e a liberdade totais da plataforma
de base.

16.1. Entendendo o QML Run-time


Ao executar o QML, ele está sendo executado em um ambiente de tempo de execução. O
tempo de execução é implementado em C++ no módulo QtQml. Ele consiste em um
mecanismo, responsável pela execução de QML, contextos, mantendo as propriedades
acessíveis para cada componente e componentes, os elementos QML instanciados.
#include <QtGui>
#include <QtQml>

int main(int argc, char **argv)


{
QGuiApplication app(argc, argv);
QUrl source(QStringLiteral("qrc:/main.qml"));
QQmlApplicationEngine engine;
engine.load(source);
return app.exec();
}

No exemplo, o QGuiApplication No exemplo, o QGuiApplication encapsula tudo o que


está relacionado à instância do aplicativo (ex: nome do aplicativo, argumentos da linha de
comando e gerenciamento do loop de eventos). O QQmlApplicationEngine gerencia a
ordem hierárquica de contextos e componentes. Requer um arquivo qml típico para ser
carregado como o ponto inicial do seu aplicativo. Neste caso, é um main.qml contendo
uma janela e um tipo de texto.

Note

Carregar um main.qmlcom um Item simples como o tipo de raiz através

281
doQmlApplicationEngine não mostrará nada no seu monitor, já que requer uma janela
para gerenciar uma superfície para renderização. O mecanismo é capaz de carregar o
código qml que não contém nenhuma interface de usuário (Ex: plain objects). Por isso,
não cria uma janela para você por padrão. O qmlsceneou o novo tempo de execução qml
internamente primeiro verificará se o arquivo qml principal contém uma janela como um
item raiz e se não criar um para você e definir o item raiz como um filho para a janela
recém-criada.

import QtQuick 2.5


import QtQuick.Window 2.2

Window {
visible: true
width: 512
height: 300

Text {
anchors.centerIn: parent
text: "Hello World!"
}
}

No arquivo qml nós declaramos nossas dependências aqui é QtQuick e QtQuick.Window.


Essa declaração irá disparar uma pesquisa para esses módulos nos caminhos de
importação e, com sucesso, carregará os plugins necessários pelo mecanismo. Os tipos
recém-carregados serão disponibilizados para o arquivo qml controlado por um qmldir.

Também é possível atalho a criação do plugin, adicionando nossos tipos diretamente ao


mecanismo. Aqui nós assumimos que temos uma classe baseada em CurrentTime
QObject.

QQmlApplicationEngine engine;

qmlRegisterType<CurrentTime>("org.example", 1, 0, "CurrentTime");

engine.load(source);

Agora também podemos usar o tipoCurrentTime em nosso arquivo qml.


import org.example 1.0

CurrentTime {
// access properties, functions, signals
}

Para os realmente preguiçosos, há também o caminho muito direto através das


propriedades do contexto.
QScopedPointer<CurrentTime> current(new CurrentTime());

QQmlApplicationEngine engine;

282
engine.rootContext().setContextProperty("current", current.value())

engine.load(source);

Note

Não misture setContextProperty() e setProperty(). O primeiro define uma


propriedade de contexto em um contexto qmlsetProperty() sdefine um valor de
propriedade dinâmica em umQObject e não irá ajudá-lo.

Agora você pode usar a propriedade atual em todo o seu aplicativo. Graças à herança de
contexto.
import QtQuick 2.5
import QtQuick.Window 2.0

Window {
visible: true
width: 512
height: 300

Component.onCompleted: {
console.log('current: ' + current)
}
}

Aqui estão as diferentes maneiras de estender a QML em geral:

Propriedades de contexto - setContextProperty()


Registre o tipo com o mecanismo - chamandoqmlRegisterType em seu main.cpp
Plugins de extensão QML - a serem discutidos a seguir

Context properties são fáceis de usar para pequenas aplicações. Eles não exigem muitos
esforços, você apenas expõe a API do seu sistema com um tipo de objeto global. É útil
garantir que não haja conflitos de nomes (ex: usando um caractere especial para esse ($)
por exemplo $.currentTime). $ é um caractere válido para variáveis JS.

Registering QML types permite ao usuário controlar o ciclo de vida de um objeto c++ a
partir do QML. Isso não é possível com as propriedades de contexto. Também não polui o
namespace global. Ainda assim, todos os tipos precisam ser registrados primeiro e, por
isso, todas as bibliotecas precisam ser vinculadas no início do aplicativo, o que na maioria
dos casos não é realmente um problema.

O sistema mais flexível é fornecido pelos QML extension plugins. Eles permitem
registrar tipos em um plug-in que é carregado quando o primeiro arquivo QML chama o
identificador de importação. Além disso, usando um singleton QML, não há mais
necessidade de poluir o namespace global. Plugins permitem que você reutilize módulos
em projetos, o que é bastante útil quando você faz mais de um projeto com o Qt.

283
Para o restante deste capítulo, vamos nos concentrar nos plugins de extensão qml. Como
eles fornecem a maior flexibilidade e reutilização.

16.2. Conteúdo do Plugin


Um plugin é uma biblioteca com uma interface definida, que é carregada sob demanda.
Isso difere de uma biblioteca, pois uma biblioteca é vinculada e carregada na inicialização
do aplicativo. No caso QML, a interface é chamada QQmlExtensionPlugin. Existem dois
métodos interessantes para nós initializeEngine() e registerTypes(). Quando o plug-
in é carregado primeiro, o initializeEngine() é chamado, o que nos permite acessar o
mecanismo para expor os objetos do plug-in ao contexto raiz. Na maioria, você só usará o
método registerTypes(). Isso permite que você registre seus tipos personalizados de
QML com o mecanismo no URL fornecido.

Vamos recuar um pouco e pensar em um tipo de IO de arquivo em potencial que nos


permitiria ler/escrever pequenos arquivos de texto em formato QML. Uma primeira
iteração poderia se parecer com isso em uma implementação de QML ridicularizada.
// FileIO.qml (good)
QtObject {
function write(path, text) {};
function read(path) { return "TEXT"}
}

Esta é uma implementação qml pura de uma possível API QML baseada em C++ para
explorar uma API. Nós vemos que devemos ter uma função de leitura e escrita. Onde a
função de gravação usa um caminho e um texto e a função de leitura pega um caminho e
retorna um texto. Como parece, caminho e texto são parâmetros comuns e talvez
possamos extraí-los como propriedades.
// FileIO.qml (better)
QtObject {
property url source
property string text
function write() { // open file and write text };
function read() { // read file and assign to text };
}

Sim, isso parece mais uma API do QML. Usamos propriedades para permitir que nosso
ambiente se vincule a nossas propriedades e reaja a mudanças.

Para criar esta API em C++, precisaríamos criar uma interface como esta.
class FileIO : public QObject {
...
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
...
public:
Q_INVOKABLE void read();
Q_INVOKABLE void write();

284
...
}

Esse tipo deFileIO precisa ser registrado com o mecanismo QML. Queremos usá-lo no
módulo “org.example.io”
import org.example.io 1.0

FileIO {
}

Um plugin pode expor vários tipos com o mesmo módulo. Mas não pode expor vários
módulos de um plugin. Portanto, há um relacionamento de um para um entre módulos e
plug-ins. Essa relação é expressa pelo identificador do módulo.

16.3. Criando o plugin


O Qt Creator contém um assistente para criar um QtQuick 2 QML Extension Plugin que
usamos para criar um plugin chamadofileio com umFileIO para começar no módulo
“org.example.io”.

A classe de plug-ins é derivada de QQmlExtensionPlugin e implementa a


funçãoregisterTypes(). A linhaQ_PLUGIN_METADATA é obrigatória para identificar o
plugin como um plugin de extensão qml. Além disso, não há nada de espetacular
acontecendo.
#ifndef FILEIO_PLUGIN_H
#define FILEIO_PLUGIN_H

#include <QQmlExtensionPlugin>

class FileioPlugin : public QQmlExtensionPlugin


{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")

public:
void registerTypes(const char *uri);
};

#endif // FILEIO_PLUGIN_H

Na implementação doregisterTypes simplesmente registramos nossa classe FileIO


usando a função qmlRegisterType.
#include "fileio_plugin.h"
#include "fileio.h"

#include <qqml.h>

void FileioPlugin::registerTypes(const char *uri)


{

285
// @uri org.example.io
qmlRegisterType<FileIO>(uri, 1, 0, "FileIO");
}

Curiosamente não podemos ver aqui o módulo URI (ex: org.example.io). Isso parece ser
definido do lado de fora.

Quando você olha para o seu diretório de projetos, você encontrará um arquivo qmldir.
Este arquivo especifica o conteúdo do seu plugin qml ou melhor o lado QML do seu
plugin. Deve ser assim para você.
module org.example.io
plugin fileio

O módulo é o URI sob o qual o seu plugin é acessível por outros e a linha do plugin deve
ser idêntica ao nome do seu plugin (no mac isso seria libfileio_debug.dylib no sistema de
arquivos e fileio noqmldir). Esses arquivos foram criados pelo Qt Creator com base nas
informações fornecidas. O módulo uri também está disponível no arquivo .pro. Há é usado
para construir o diretório de instalação.

Quando você chama make install na sua pasta build, a biblioteca será copiada para a
pasta Qtqml(para o Qt 5.4 no mac isso seria“~/Qt/5.4/clang_64/qml”. O caminho exato
depende do seu local de instalação do Qt e do compilador usado em seu sistema). Lá você
encontrará uma biblioteca dentro da pasta “org/example/io”. O conteúdo são esses dois
arquivos atualmente
libfileio_debug.dylib
qmldir

Ao importar um módulo chamado “org.example.io”, o mecanismo qml procurará em um


dos caminhos de importação e tentará localizar o caminho “org/example/io” com um
qmldir. O qmldir então dirá ao mecanismo qual biblioteca deve ser carregada como um
plugin de extensão qml usando qual módulo URI. Dois módulos com o mesmo URI se
sobrepõem.

16.4. Implementação FileIO


A implementação do FileIO é direta. Lembre-se de que a API que queremos criar deve se
parecer com isso.
class FileIO : public QObject {
...
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
...
public:
Q_INVOKABLE void read();
Q_INVOKABLE void write();
...
}

286
Deixaremos de fora as propriedades, pois são setters e getters simples.

O método de leitura abre um arquivo no modo de leitura e lê os dados usando um fluxo de


texto.
void FileIO::read()
{
if(m_source.isEmpty()) {
return;
}
QFile file(m_source.toLocalFile());
if(!file.exists()) {
qWarning() << "Does not exits: " << m_source.toLocalFile();
return;
}
if(file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
m_text = stream.readAll();
emit textChanged(m_text);
}
}

Quando o texto é alterado, é necessário informar outras pessoas sobre a alteração usando
emit textChanged(m_text). Caso contrário, a vinculação de propriedade não funcionará.

O método write faz o mesmo, mas abre o arquivo no modo de gravação e usa o fluxo para
gravar o conteúdo.
void FileIO::write()
{
if(m_source.isEmpty()) {
return;
}
QFile file(m_source.toLocalFile());
if(file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << m_text;
}
}

Não esqueça de chamar make install no final. Caso contrário, os arquivos do plug-in não
serão copiados para a pasta qml e o mecanismo qml não conseguirá localizar o módulo.

Como a leitura e a escrita estão bloqueando, você só deve usar este FileIO para pequenos
textos, caso contrário, você bloqueará o thread da UI do Qt. Esteja avisado!

16.5. Usando FileIO


Agora podemos usar nosso arquivo recém-criado para acessar alguns dados interessantes.
Para este exemplo, queremos ler alguns dados da cidade em um formato JSON e exibi-los
em uma tabela. Vamos usar dois projetos, um o plugin de extensão (chamado fileio) que
nos fornece uma maneira de ler e escrever texto de um arquivo eo outro, que exibe os

287
dados em uma tabela (CityUI) usando o arquivo io para leitura e escrita de arquivos. Os
dados usados neste exemplo estão no arquivocities.json.

JSON é apenas texto, que é formatado de tal forma que pode ser convertido em um
objeto/array JS válido e de volta ao texto. Usamos nossoFileIO para ler os dados
formatados em JSON e convertê-los em um objeto JS usando JSON.parse(). Os dados são
usados posteriormente como um modelo para a exibição de tabela. Este é basicamente o
conteúdo da nossa função de documento lido. Para salvar, convertemos os dados de volta
para um formato de texto e usamos a função de gravação para salvar.

Os dados JSON da cidade são um arquivo de texto formatado, com um conjunto de


entradas de dados da cidade, em que cada entrada contém dados interessantes sobre a
cidade.
[
{
"area": "1928",
"city": "Shanghai",
"country": "China",
"flag": "22px-Flag_of_the_People's_Republic_of_China.svg.png",
"population": "13831900"
},
...
]

16.5.1. A janela do Aplicativo

Usamos o assistente Qt Creator QtQuick Application para criar um aplicativo baseado

288
em controles Qt Quick. Não usaremos os novos formulários QML, pois isso é difícil de
explicar em um livro, embora a nova abordagem de formulários com um arquivo ui.qml
seja muito mais utilizável do que a anterior. Então você pode remover/excluir o arquivo de
formulários por enquanto.

A configuração básica é um ApplicationWindow que pode conter uma barra de


ferramentas, barra de menus e barra de status. Só usaremos a barra de menu para criar
algumas entradas de menu padrão para abrir e salvar o documento. A configuração básica
apenas exibirá uma janela vazia.
import QtQuick 2.5
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
id: root
title: qsTr("City UI")
width: 640
height: 480
visible: true
}

16.5.2. Usando Ações

Para melhor usar/reutilizar nossos comandos, usamos o tipo Action QML. Isso nos
permitirá mais tarde usar a mesma ação também para uma barra de ferramentas em
potencial. As ações abrir e salvar e sair são padrão. A ação abrir e salvar ainda não contém
nenhuma lógica, isso virá depois. A barra de menu é criada com um menu de arquivo e
essas três entradas de ação. Adicional nós preparamos já uma caixa de diálogo de arquivo,
que nos permitirá escolher o documento da cidade mais tarde. Um diálogo não é visível
quando declarado, você precisa usar o métodoopen() para mostrá-lo.
...
Action {
id: save
text: qsTr("&Save")
shortcut: StandardKey.Save
onTriggered: { }
}

Action {
id: open
text: qsTr("&Open")
shortcut: StandardKey.Open
onTriggered: {}
}

Action {
id: exit
text: qsTr("E&xit")
onTriggered: Qt.quit();
}

289
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem { action: open }
MenuItem { action: save }
MenuSeparator { }
MenuItem { action: exit }
}
}

...

FileDialog {
id: openDialog
onAccepted: { }
}

16.5.3. Formatando a tabela

O conteúdo dos dados da cidade deve ser exibido em uma tabela. Para isso usamos o
controle TableView e declaramos 4 colunas: cidade, país, área, população. Cada coluna é
um TableViewColumn. Mais tarde, adicionaremos colunas ao sinalizador e removeremos a
operação que exigirá um delegate de coluna personalizado.
TableView {
id: view
anchors.fill: parent
TableViewColumn {
role: 'city'
title: "City"
width: 120
}
TableViewColumn {
role: 'country'
title: "Country"
width: 120
}
TableViewColumn {
role: 'area'
title: "Area"
width: 80
}
TableViewColumn {
role: 'population'
title: "Population"
width: 80
}
}

Agora o aplicativo deve mostrar uma barra de menu com um menu de arquivos e uma
tabela vazia com 4 cabeçalhos de tabela. O próximo passo será preencher a tabela com
dados úteis usando nossa extensão FileIO.

290
O documento cities.json é uma matriz de entradas da cidade. Aqui está um exemplo.
[
{
"area": "1928",
"city": "Shanghai",
"country": "China",
"flag": "22px-Flag_of_the_People's_Republic_of_China.svg.png",
"population": "13831900"
},
...
]

Nosso trabalho é permitir que o usuário selecione o arquivo, leia-o, converta-o e


configure-o na visualização da tabela.

16.5.4. Lendo dados

Para isso, permitimos que a ação aberta abra o diálogo de arquivo. Quando o usuário
seleciona um arquivo, o método onAccepted é chamado na caixa de diálogo de arquivo.
Lá chamamos a funçãoreadDocument(). A função readDocument()configura a url da
caixa de diálogo de arquivo para o nosso objeto FileIO e chama o método read(). O texto
carregado do FileIO é então analisado usando o método JSON.parse() e o objeto
resultante é diretamente definido na exibição da tabela como um modelo. Quão
conveniente é isso.
Action {
id: open
...
onTriggered: {
openDialog.open()
}
}

...

FileDialog {
id: openDialog
onAccepted: {
root.readDocument()
}
}

function readDocument() {

291
io.source = openDialog.fileUrl
io.read()
view.model = JSON.parse(io.text)
}

FileIO {
id: io
}

16.5.5. Escrevendo dados

Para salvar o documento, conectamos a ação de salvamento à função saveDocument(). A


função salvar documento pega o modelo da visão, que é um objeto JS e o converte em
uma string usando a função JSON.stringify(). A string resultante é configurada para a
propriedade text do nosso objeto FileIO e chamamos write() para salvar os dados no
disco. Os parâmetros “null” e “4” na função stringify formatarão os dados JSON
resultantes usando recuo com 4 espaços. Isto é apenas para uma melhor leitura do
documento salvo.
Action {
id: save
...
onTriggered: {
saveDocument()
}
}

function saveDocument() {
var data = view.model
io.text = JSON.stringify(data, null, 4)
io.write()
}

FileIO {
id: io
}

Este é basicamente o aplicativo que lê, grava e exibe um documento JSON. Pense em todo
o tempo gasto escrevendo leitores e escritores XML. Com o JSON, tudo o que você
precisa é de uma maneira de ler e escrever um arquivo de texto ou enviar um buffer de
texto.

292
16.5.6. Toque final

O aplicativo ainda não está totalmente pronto. Ainda queremos mostrar os sinalizadores e
permitir que o usuário modifique o documento removendo cidades do modelo.

Os sinalizadores são armazenados para este exemplo em relação ao documentomain.qml


em uma flags folder. Para poder mostrar a eles, a coluna da tabela precisa definir um
delegate personalizado para renderizar a imagem do sinalizador.
TableViewColumn {
delegate: Item {
Image {
anchors.centerIn: parent
source: 'flags/' + styleData.value
}
}
role: 'flag'
title: "Flag"
width: 40
}

Isso é tudo. Ele expõe a propriedade de sinalizador do modelo JS comostyleData.value


para o delegate. O delegate então ajusta o caminho da imagem para pré-pendurar
'flags/' e exibe-o.

Para remover, usamos uma técnica semelhante para exibir um botão de remoção.
TableViewColumn {
delegate: Button {
iconSource: "remove.png"
onClicked: {

293
var data = view.model
data.splice(styleData.row, 1)
view.model = data
}
}
width: 40
}

Para a operação de remoção de dados, obtemos o modelo de visualização e, em seguida,


removemos uma entrada usando a função splice.Esse método está disponível para nós,
pois o modelo é do tipo matriz JS. O método de emenda altera o conteúdo de uma matriz,
removendo elementos existentes e/ou adicionando novos elementos.

Infelizmente, um array JS não é tão inteligente quanto um modelo Qt como


oQAbstractItemModel, que notifica a exibição sobre mudanças de linha ou alterações de
dados. A visualização não mostrará dados atualizados até o momento, pois ela nunca é
notificada sobre alterações. Somente quando definimos os dados de volta para a exibição,
a exibição reconhece novos dados e atualiza o conteúdo da exibição. Definir o modelo
novamente usando view.model = data é uma maneira de permitir que a visualização
saiba que houve uma alteração nos dados.

16.6. Resumo
O plugin criado é um plugin muito simples, mas pode ser reutilizado agora e estendido por

294
outros tipos para diferentes aplicações. Usando plugins cria uma solução muito flexível.
Por exemplo, agora você pode iniciar a UI usando apenas oqmlscene. Abra a pasta onde o
seu projeto CityUI é iniciar a interface do usuário com qmlscene main.qml. Eu realmente
encorajo você a escrever seus aplicativos de uma maneira que eles trabalhem com um
qmlscene. Isso tem um tremendo aumento no tempo de retorno para o desenvolvedor da
UI e também é um bom habbit manter uma separação clara.

O uso de plug-ins tem uma desvantagem de que a implementação fica mais difícil para
aplicativos simples. Agora você precisa implantar seu plug-in com seu aplicativo. Se este
for um problema para você, você ainda pode usar o mesmo objeto FileIO para registrá-lo
diretamente em seu main.cpp usando qmlRegisterType. O código QML permaneceria o
mesmo.

Muitas vezes, em projetos maiores, você não usa um aplicativo como tal. Você tem um
runtime de qml simples semelhante ao qmlscene e requer que toda a funcionalidade nativa
venha como plugins. E seus projetos são simples projetos puros de qml usando esses
plugins de extensão qml. Isso fornece uma grande flexibilidade e remove a etapa de
compilação das alterações da UI do usuário. Depois de editar um arquivo QML, basta
executar a UI. Isso permite que os criadores da interface do usuário permaneçam flexíveis
e ágeis para fazer todas essas pequenas alterações no envio de pixels.

Plugins fornecem uma separação agradável e limpa entre desenvolvimento de backend


C++ e desenvolvimento frontend QML. Ao desenvolver plug-ins QML, tenha sempre em
mente o lado QML e não hesite em começar com um mockup QML primeiro para validar
sua API antes de implementá-la em C++. Se uma API é escrita em C++, as pessoas
geralmente hesitam em mudá-la ou não falar de reescrevê-la. Zombar de uma API no
QML oferece muito mais flexibilidade e menos investimento inicial. Ao usar plugins, o
switch entre uma API falsa e a API real está apenas alterando o caminho de importação
para o tempo de execução(runtime) qml.

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

295
Qt5 Cadaques Book » previous

16. Assets
Os recursos contêm todos os arquivos para ler o livro off-line e também os exemplos de
capítulos como formato para download.

16.1. Offline Books


Book as eBook
Book as PDF
Book as QtHelp

16.2. Exemplos - código-fonte


Chapter 01 examples (ch01-assets.tgz)
Chapter 04 examples (ch04-assets.tgz)
Chapter 05 examples (ch05-assets.tgz)
Chapter 06 examples (ch06-assets.tgz)
Chapter 07 examples (ch07-assets.tgz)
Chapter 08 examples (ch08-assets.tgz)
Chapter 09 examples (ch09-assets.tgz)
Chapter 10 examples (ch10-assets.tgz)
Chapter 11 examples (ch11-assets.tgz)
Chapter 12 examples (ch12-assets.tgz)
Chapter 13 examples (ch13-assets.tgz)
Chapter 14 examples (ch14-assets.tgz)
Chapter 15 examples (ch15-assets.tgz)
Chapter 16 examples (ch16-assets.tgz)

© Copyright 2012-2014 Jürgen Bocklage-Ryannel and Johan Thelin. This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License. Last updated on Mar 21,
2016.

296
Índice
Navegação 1
Um livro sobre o Qt5 1
Navegação 5
1. Conheça o Qt 5 5
1.1. Prefácio 5
1.1.1. O foco no Qt 5 6
1.2. Introdução ao Qt5 6
1.2.1. Qt Quick 6
1.2.2. Construindo uma Interface de Usuário 7
1.3. Blocos de Construção Qt 12
1.3.1. Módulos do Qt 12
1.3.2. Plataformas suportadas 14
1.4. O Qt Project 14
Navegação 16
2. Iniciando 16
2.1. Instalando o SDK do Qt 5 16
2.2. Hello World 16
2.3. Tipos de Aplicação 19
2.3.1. Aplicação de console 19
2.3.2. Aplicativo Widget 21
2.3.3. Adaptando Data 25
2.3.4. Aplicativo Qt Quick 27
2.4. Resumo 28
Navegação 29
3. IDE do Qt Creator 29
3.1. A interface do usuário 29
3.2. Registrando seu Qt Kit 30
3.3. Gerenciando Projects 31
3.4. Usando o Editor 31
3.5. Localizador 32
3.6. Depuração 33
3.7. Atalhos 33
Navegação 35
4. Iniciando no Quick 35
4.1. Sintaxe QML 35
4.1.1. Propriedades 37

297
4.1.2. Scripting 40
4.2. Elementos básicos 41
4.2.1. Elemento Item 41
4.2.2. Elemento Rectangle 42
4.2.3. Elemento Text 43
4.2.4. Elemento Image 45
4.2.5. Elemento MouseArea 46
4.3. Componentes 46
4.4. Transformações Simples 49
4.5. Posicionamento dos Elementos 52
4.6. Itens de Layout 57
4.7. Elementos de Entrada 59
4.7.1. TextInput 60
4.7.2. FocusScope 62
4.7.3. TextEdit 63
4.7.4. Elemento Keys 64
4.8. Técnicas Avançadas 65
Navegação 66
5. Elementos Fluidos 66
5.1. Animações 66
5.1.1. Elementos de animação 68
5.1.2. Aplicando Animações 68
5.1.3. Curvas Easing 72
5.1.4. Animações Agrupadas 76
5.2. Estados e Transições 82
5.2.1. Estados 83
5.2.2. Transições 86
5.3. Técnicas Avançadas 87
Navegação 88
6. Delegando Model-View 88
6.1. Conceito 88
6.2. Modelos Básicos 89
6.3. Visualização Dinâmica 92
6.3.1. Orientação 95
6.3.2. Navegação Keyboard e Highlighting 96
6.3.3. Header e Footer 99
6.3.4. GridView 101
6.4. Delegate 102
6.4.1. Adicionando Animações e Removendo Itens 104

298
6.4.2. delegates que mudam de forma 106
6.5. Técnicas Avançadas 111
6.5.1. O PathView 111
6.5.2. Um modelo de XML 114
6.5.3. Listas com Seções 116
6.5.4. Ajuste de Desempenho 119
6.6. Resumo 119
Navegação 121
7. Elemento Canvas 121
7.1. API conveniente 124
7.2. Gradientes 124
7.3. Sombras 125
7.4. Imagens 126
7.5. Transformação 127
7.6. Modos de Composição 128
7.7. Buffers de Pixel 129
7.8. Pintura com Canvas 131
7.9. Portando Canvas a partir HTML5 132
Navegação 139
8. Simulação de Partículas 139
8.1. Conceito 139
8.2. Simulação Simples 140
8.3. Parâmetros de Partículas 142
8.4. Partículas Direcionadas 143
8.5. Pintores de Partículas 148
8.6. Afetando partículas 149
8.7. Grupos de Partículas 153
8.8. Resumo 161
Navegação 163
9. Efeito Shader 163
9.1. OpenGL Shaders 163
9.2. Elementos Shader 164
9.3. Fragmento Shaders 167
9.4. Efeito Wave (onda) 171
9.5. Vertex Shader 172
9.6. Efeito cortina 181
9.7. Biblioteca Qt GraphicsEffect 185
Navegação 188
10. Multimídia 188
299
10.1. Jogando com a Mídia 188
10.2. Efeitos Sonoros 191
10.3. Streams de Video 192
10.4. Capturando Imagens 192
10.5. Técnicas Avançadas 195
10.5.1. Implementando uma lista de reprodução 196
10.6. Resumo 197
Navegação 198
11. Networking 198
11.1. Servindo a UI via HTTP 198
11.1.1. Componentes em Rede 200
11.2. Templating 202
11.3. Solicitações HTTP 202
11.3.1. Chamadas do Flickr 204
11.4. Ficheiros locais 206
11.5. API REST 207
11.5.1. Pedido de Leitura 208
11.5.2. Entrada de leitura 208
11.5.3. Criar entrada 208
11.5.4. Atualizar entrada 209
11.5.5. Excluir entrada 209
11.5.6. Cliente REST 210
11.6. Autenticação usando o OAuth 213
11.7. Engine IO 214
11.8. Web Sockets 214
11.8.1. WS Server 215
11.8.2. Cliente WS 216
11.9. Resumo 220
Navegação 221
12. Armazenamento 221
12.1. Configurações 221
12.2. Armazenamento Local - SQL 223
12.3. Outras APIs de armazenamento 227
Navigation 228
13. QML Dinâmico 228
13.1. Carregando componentes dinamicamente 228
13.1.1. Conectando Indiretamente 230
13.1.2. Vinculando Indiretamente 233
13.2. Criando e destruindo objetos 234

300
13.2.1. Carregando e instanciando itens dinamicamente 234
13.2.2. Dinamicamente instanciando itens do texto 236
13.2.3. Gerenciando Elementos Criados Dinamicamente 236
13.3. Rastreando Objetos Dinâmicos 237
13.4. Resumo 240
Navegação 241
14. JavaScript 241
14.1. Navegador/HTML vs QtQuick/QML 242
14.2. A Linguagem 243
14.3. Objetos JS 245
14.4. Criando um Console JS 247
Navigation 252
15. Qt e C++ 252
15.1. Um aplicativo Boilerplate 254
15.2. O QObject 257
15.3. Build Systems 259
15.3.1. QMake 260
15.3.2. CMake 262
15.4. Classes comuns do Qt 263
15.4.1. QString 263
15.4.2. Recipientes Seqüenciais 265
15.4.3. Contêineres Associativos 266
15.4.4. Arquivo IO 267
15.4.5. Mais aulas 268
15.5. Modelos em C++ 269
15.5.1. Um modelo simples 270
15.5.2. Dados mais complexos 273
15.5.3. Dados Dinâmicos 275
15.5.4. Técnicas Avançadas 280
Navegação 281
16. Estendendo QML com C++ 281
16.1. Entendendo o QML Run-time 281
16.2. Conteúdo do Plugin 284
16.3. Criando o plugin 285
16.4. Implementação FileIO 286
16.5. Usando FileIO 287
16.5.1. A janela do Aplicativo 288
16.5.2. Usando Ações 289
16.5.3. Formatando a tabela 290

301
16.5.4. Lendo dados 291
16.5.5. Escrevendo dados 292
16.5.6. Toque final 293
16.6. Resumo 294
Navegação 296
16. Assets 296
16.1. Offline Books 296
16.2. Exemplos - código-fonte 296

302

Você também pode gostar