Escolar Documentos
Profissional Documentos
Cultura Documentos
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].
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
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
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.
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.
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.
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.
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.
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.1. Módulos do Qt
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.
Módulos Adicionais Qt
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.
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 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:-)
© 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]
Note
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.
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
Rectangle {
width: 360
height: 360
Text {
anchors.centerIn: parent
text: "Hello World"
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
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.
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
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
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
Manipulação de String
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.
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.
Quando você executar o código, verá uma janela com o tamanho de 240 x 120 pixels. Isso
é tudo.
#include <QtGui>
21
QScopedPointer<QWidget> widget(new CustomWidget());
widget->resize(240, 120);
widget->show();
return app.exec();
}
Widgets Personalizados
#include <QtWidgets>
#endif // 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"));
}
Widgets no Desktop
23
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
m_widget = new QListWidget(this);
layout->addWidget(m_widget);
QStringList cities;
cities << "Paris" << "London" << "Munich";
foreach(const QString& city, cities) {
m_widget->addItem(city);
}
void CustomWidget::updateItem()
{
QListWidgetItem* item = m_widget->currentItem();
if(item) {
item->setText(m_edit->text());
}
}
Desenhando Shapes
24
QGraphicsScene *m_scene;
};
CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
m_view = new QGraphicsView(this);
m_scene = new QGraphicsScene(this);
m_view->setScene(m_scene);
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);
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();
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
Rectangle {
width: 240; height: 1230
Rectangle {
width: 40; height: 40
anchors.centerIn: parent
color: '#FFBB33'
}
}
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 }
}
}
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]
Note
Do lado esquerdo, você verá o seletor de modos. Os seletores de modo contêm etapas
típicas do seu fluxo de trabalho.
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
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.
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.
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
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):
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
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.
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
// color property
color: "#4A4A4A"
source: 'assets/triangle_red.png'
}
// reference element by id
y: triangle.y + triangle.height + 20
color: 'white'
horizontalAlignment: Text.AlignHCenter
text: 'Triangle'
}
36
}
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
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.
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. 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.
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.
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 }.
Atenção
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
x: 24; y: 24
// (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
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!).
Atualmente, vamos nos concentrar nos elementos visuais fundamentais, como Item,
Rectangle, Text, Image e MouseArea.
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
Note
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
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.
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.
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
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
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.
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).
Rectangle {
id: root
// export button properties
property alias text: label.text
signal clicked
Text {
id: label
anchors.centerIn: parent
47
text: "Start"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.clicked()
}
}
}
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!"
}
}
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
Rectangle {
anchors.fill parent
color: "lightsteelblue"
border.color: "slategrey"
}
...
}
Com essa técnica, é fácil criar uma série inteira de componentes reutilizáveis.
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.
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
Item {
// set width based on given background
width: bg.width
height: bg.height
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
Note
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.
Note
52
// RedSquare.qml
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
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
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
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
BrightSquare {
id: root
width: 160
height: 160
Flow {
anchors.fill: parent
anchors.margins: 20
spacing: 20
RedSquare { }
BlueSquare { }
GreenSquare { }
}
}
55
// repeater.qml
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
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.
57
1. Um elemento preenche um elemento parent
GreenSquare {
BlueSquare {
width: 12
anchors.fill: parent
anchors.margins: 8
text: '(1)'
}
}
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)'
}
}
Note
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
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
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
}
}
Nós movemos este pedaço de código para o nosso próprio componente chamado
TLineEditV1 para reutilização.
// TLineEditV1.qml
Rectangle {
width: 96; height: input.height + 8
color: "lightsteelblue"
border.color: "gray"
TextInput {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}
}
Note
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
FocusScope {
width: 96; height: input.height + 8
Rectangle {
anchors.fill: parent
color: "lightsteelblue"
border.color: "gray"
TextInput {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}
62
}
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
FocusScope {
width: 96; height: 96
Rectangle {
anchors.fill: parent
color: "lightsteelblue"
border.color: "gray"
TextEdit {
id: input
anchors.fill: parent
anchors.margins: 4
focus: true
}
}
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"
}
}
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
}
}
© 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
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
Desbloqueie o poder!
66
// animation.qml
Image {
id: root
source: "assets/background.png"
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
}
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:
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:
Os principais tipos de animação serão discutidos durante este capítulo usando pequenos
exemplos.
68
ligação de propriedade)
Mais tarde, veremos também como as animações podem ser usadas dentro de transições
de estado.
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()
}
}
Note
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
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
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
}
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.
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
// EasingCurves.qml
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.
Note
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.
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
}
}
}
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.
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.
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.
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 { ... }
}
]
}
Note
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 }
}
]
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.
© 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
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.
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
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
}
}
}
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.
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.
É 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.
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.
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 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.
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
}
}
}
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
100
6.3.4. GridView
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
}
}
}
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.
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.
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.
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
MouseArea {
anchors.fill: parent
onClicked: {
theModel.append({"number": ++parent.count});
}
}
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:
}
}
}
}
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.
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
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"
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.
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
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.
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.
delegate: flipCardDelegate
model: 100
path: Path {
startX: root.width/2
startY: 0
pathItemCount: 16
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
}
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
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.
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"
ListView {
id: listView
anchors.fill: parent
model: imageModel
delegate: imageDelegate
}
}
À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.
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.
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
118
ListElement { name: "Bjarni Tryggvason"; nation: "Canada"; }
ListElement { name: "Dafydd Williams"; nation: "Canada"; }
}
}
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.
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.
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
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 ++.
121
gradientes, texto e diferentes conjuntos de comandos de criação de caminhos.
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()
}
}
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.
// 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
Canvas {
id: root
width: 120; height: 120
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = 'green'
ctx.strokeStyle = "blue"
ctx.lineWidth = 4
Note
7.2. Gradientes
O Canvas pode preencher formas com cores, mas também com gradientes ou imagens.
onPaint: {
var ctx = getContext("2d")
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
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.
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)
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")
}
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
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()
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
source-over
source-in
source-out
source-atop
onPaint: {
var ctx = getContext("2d")
ctx.globalCompositeOperation = "xor"
ctx.fillStyle = "#33a9ff"
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')
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()
}
}
Note
130
Recuperar dados de imagem parece não funcionar atualmente no Qt 5 Alpha SDK.
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.
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()
}
}
}
É 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
Qt Quick requer que você declare variável, então precisamos adicionar algumas
declarações var
for (var i=0;i<3;i++) {
...
}
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>
134
var hue = 0;
context.save();
context.beginPath();
context.lineWidth = 5 + Math.random() * 10;
// 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();
}
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);
}
</script>
</body>
</html>
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
...
}
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();
...
}
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.
137
O resultado final será semelhante a este.
Veja também
© 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
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.
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.
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
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
}
}
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).
141
lifeSpan: 2000
lifeSpanVariation: 500
size: 64
sizeVariation: 32
Tracer { color: 'green' }
}
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
143
Vamos tentar mover as partículas da esquerda para a direita da nossa cena usando as
direções de velocidade.
velocity: AngleDirection { }
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
}
}
O resultado é um arco que vai do centro esquerdo para o canto inferior direito.
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 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
}
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:
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)]
}
}
}
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).
Envelhecer
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
É 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
152
Wander
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
Rectangle {
id: root
width: 480; height: 240
color: "#1F1F1F"
property bool tracer: false
}
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
ParticleGroup {
name: 'explosion'
system: particleSystem
}
A propriedade jump declara que a mudança nos grupos deve ser imediata e não após uma
certa duração.
157
Note
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
}
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
}
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
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.
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.
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.
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.
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.
166
shader.Primeiro nos concentramos no shader de fragmento e depois voltaremos ao
shadervertex.
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
167
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * qt_Opacity;
}"
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.
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
}"
}
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).
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;
}"
}
}
}
Sem o fator tempo, teríamos apenas uma distorção, mas não uma distorção da viagem,
como as ondas.
Além disso, se não tivermos movido pixels nesse fragment shader, o efeito pareceria, a
princípio, um trabalho para um vertex shader.
172
Configurando a cena
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
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 }
}
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);
O efeito resultante não é realmente o efeito genial, mas já é um grande passo em direção a
ele.
Todo
Dobra Primitiva
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.
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;
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.
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;
}"
}
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
GenieEffect {
source: Image { source: 'assets/lighthouse.jpg' }
MouseArea {
anchors.fill: parent
onClicked: parent.minimized = !parent.minimized
}
}
}
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.
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).
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)
}
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;
183
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
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;
}"
}
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.
A biblioteca de graphics effects vem com o chamado testbed manual, que é uma ótima
ferramenta para descobrir interativamente os diferentes efeitos.
A effects library contém cerca de 20 efeitos. Uma lista do efeito e uma breve descrição
podem ser encontradas abaixo.
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
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
}
}
}
}
© 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
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
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.
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 ...
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.
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; }
}
}
]
Note
Item {
width: 1024
height: 600
VideoOutput {
anchors.fill: parent
source: camera
}
Camera {
id: camera
}
}
192
um visor, tirar fotos e acompanhar as fotos tiradas.
Camera {
id: camera
}
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
onImageSaved: {
imagePaths.append({"path": path})
listView.positionViewAtEnd();
}
}
194
corretamente. Isso, no entanto, não é nada que façamos no exemplo.
function startPlayback()
{
root.state = "playing";
setImageIndex(0);
playTimer.start();
}
function setImageIndex(i)
{
_imageIndex = i;
Timer {
id: playTimer
interval: 200
repeat: false
onTriggered: {
if (_imageIndex + 1 < imagePaths.count)
{
setImageIndex(_imageIndex + 1);
playTimer.start();
}
else
{
setImageIndex(-1);
root.state = "";
}
}
}
195
referência agora.
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" }
}
}
Item {
id: root
function setIndex(i) {
console.log("setting index to: " + i);
index = i;
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
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.
Rectangle {
width: 320
height: 320
color: '#ff0000'
}
198
# python -m SimpleHTTPServer 8080
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
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
Rectangle {
width: 320
height: 320
color: '#ff0000'
Button {
anchors.centerIn: parent
text: 'Click Me'
onClicked: Qt.quit()
}
}
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 { ... }
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
...
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.
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
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));
}
Note
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
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()
}
}
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.
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()
}
}
XmlListModel {
source: "http://localhost:8080/colors.xml"
query: "/colors"
XmlRole { name: 'color'; query: 'name/string()' }
XmlRole { name: 'value'; query: 'value/string()' }
}
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
207
app = Flask(__name__)
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.
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
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
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":"
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 )
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.
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.
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:
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.
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]);
}
});
}
}
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)
});
}
}
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.
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.
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.
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.
WebSocket {
id: socket
}
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"
}
}
}
}
11.8.1. WS Server
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
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');
});
11.8.2. Cliente WS
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 {}
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
}
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')
}
}
}
}
Executando o servidor
$ cd ws_server
$ node server.js
Executando o cliente
$ cd ws_client
$ qmlscene ws_client.qml
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
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.
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
}
}
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”.
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
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.
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.
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.
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
Note
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.
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
}
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.
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.
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.
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
}
Note
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.
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());
}
}
Item {
id: root
width: 1024
height: 600
Component.onCompleted: ImageCreator.createImageObject();
}
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.
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
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();
}
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.
Note
É possível destruir o objeto de dentro, tornando possível criar janelas pop-up auto-
destrutivas, por exemplo.
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
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);
}
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.
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";
res += "</scene>";
return res;
}
function deserialize() {
dsIndex = 0;
CreateObject.create(xmlModel.get(dsIndex).source, root, dsItemAdded);
}
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.
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.
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
Note
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.
// 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
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
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).
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]).
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:
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.
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]);
}
}
}
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());
}
}
}
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()
}
}
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
...
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;
}
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
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.
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.
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.
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
# 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"
255
MainWindow win;
win.resize(320, 240);
win.setVisible(true);
return app.exec();
}
// create the ui
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>
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;
}
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.
public:
// standard Qt constructor with parent for memory management
Person(QObject *parent = 0);
private:
// data members
QString m_name;
Gender m_gender;
};
258
o gênero desconhecido.
Person::Person(QObject *parent)
: QObject(parent)
, m_gender(Person::Unknown)
{
}
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.
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
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
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
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.
References
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.
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)
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
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
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.
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.
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};
265
for(int v : list) {
sum+= v;
}
QVERIFY(sum == 6);
}
while (i.hasNext()) {
sum += i.next();
}
QVERIFY(sum == 6);
}
int value = 3;
{ // using std::find with const iterator
QList<int>::const_iterator result = std::find(list.constBegin(), list
QVERIFY(*result == value);
}
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);
// 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);
15.4.4. Arquivo IO
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);
}
}
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].
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.
#include <QtCore>
#include <QtGui>
270
Q_OBJECT
public:
explicit DataEntryModel(QObject *parent = 0);
~DataEntryModel();
#endif // 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()
{
}
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"
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
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 { }
}
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.
#include <QtCore>
#include <QtGui>
// 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
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";
RoleEntryModel::~RoleEntryModel()
{
}
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.
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);
Anexar é muito simples. Nós reutilizamos a função insert com o tamanho do modelo.
void DynamicEntryModel::append(const QString &colorValue)
{
insert(count(), colorValue);
}
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
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
Note
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.
Note
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.
Window {
visible: true
width: 512
height: 300
Text {
anchors.centerIn: parent
text: "Hello World!"
}
}
QQmlApplicationEngine engine;
qmlRegisterType<CurrentTime>("org.example", 1, 0, "CurrentTime");
engine.load(source);
CurrentTime {
// access properties, functions, signals
}
QQmlApplicationEngine engine;
282
engine.rootContext().setContextProperty("current", current.value())
engine.load(source);
Note
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)
}
}
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.
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.
#include <QQmlExtensionPlugin>
public:
void registerTypes(const char *uri);
};
#endif // FILEIO_PLUGIN_H
#include <qqml.h>
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
286
Deixaremos de fora as propriedades, pois são setters e getters simples.
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!
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.
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.
ApplicationWindow {
id: root
title: qsTr("City UI")
width: 640
height: 480
visible: true
}
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: { }
}
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"
},
...
]
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
}
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.
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
}
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.
© 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.
© 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