Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.
Getting Started Programming with QML/pt-PT: Difference between revisions
No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
[toc align_right= | [toc align_right="yes" depth="4"] | ||
= Iniciando o desenvolvimento com QML = | = Iniciando o desenvolvimento com QML = | ||
Bem-vindo ao mundo do QML, a linguagem declarativa para interfaces com usuário. Neste guia introdutório nós criaremos um editor de texto simples usando QML. Uma vez lido este guia, você estará pronto para criar suas próprias aplicações usando QML e Qt C+''. | Bem-vindo ao mundo do QML, a linguagem declarativa para interfaces com usuário. Neste guia introdutório nós criaremos um editor de texto simples usando QML. Uma vez lido este guia, você estará pronto para criar suas próprias aplicações usando QML e Qt C+''. | ||
=== Capítulos do Tutorial: | h2. QML para criar interfaces com usuário | ||
A aplicação que vamos criar é um editor de text simples que irá carregar, salvar e fará algumas manipulações no texto. Este guia consite de duas partes. A primeira parte envolve ''design'' do ''layout'' da aplicação e seu comportamento, usando a linguagem declarativa QML. Na segunda parte, processos de carregar e salvar arquivos serão implementados com Qt C. Utilizando o "Sistema de Meta-Objetos do Qt":http://doc.qt.nokia.com/4.7/metaobjects.html, é possível expor funções e propriedades do escritas em C''+ que elementos QML conseguirão usar. Utilizando QML e Qt C+'', é possível manter separadas, de uma forma eficiente, a interface com usuário e a lógica da aplicação. | |||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png]] | |||
Para executar o exemplo de código QML, basta usar a ferramenta qmlviewer com o arquivo QML como argumento. A porção C''+ deste tutorial assume que o leitor tem conhecimentos básicos do procedimento de compilação de código Qt. | |||
=== Capítulos do Tutorial: | |||
# Definindo um Botão e um Menu | |||
# Implementando uma Barra de Menu | |||
# Construindo um Editor de Texto | |||
# Decorando o Editor de Texto | |||
# Estendendo QML usando Qt C++ === | |||
== Definindo um Botão e um Menu == | == Definindo um Botão e um Menu == | ||
Line 15: | Line 24: | ||
=== Componente Básico - um Botão === | === Componente Básico - um Botão === | ||
Comecemos nosso editor de texto criando um botão. Em termos de funcionalidade, um botão é uma área sensível ao ''mouse'' /rato com um texto. Botões reagem quando um usuário os pressionam. | Comecemos nosso editor de texto criando um botão. Em termos de funcionalidade, um botão é uma área sensível ao ''mouse'' /rato com um texto. Botões reagem quando um usuário os pressionam. | ||
Em QML, o item visual básico é o elemento "Rectangle":http://doc.qt.nokia.com/4.7/qml-rectangle.html (Retângulo). O elemento retângulo tem propriedades para controlar sua a aparência e localização. | |||
Primeiramente, o ''import Qt 4.7'' permite que a ferramenta '''qmlviewer''' importe os elementos QML que usaremos em seguida. Tal linha deve existir em todo arquivo QML. Observe que a versão dos módulos Qt é incluida na sentenção de importação. | Primeiramente, o ''import Qt 4.7'' permite que a ferramenta '''qmlviewer''' importe os elementos QML que usaremos em seguida. Tal linha deve existir em todo arquivo QML. Observe que a versão dos módulos Qt é incluida na sentenção de importação. | ||
Esse retângulo simples tem um identificador único, simplebutton, quue é atribuído a propriedade id. As propriedades do elemento Retângulo são ligadas aos valores através da listagem da propriedade, seguida de dois pontos, então o valor. Nesse exemplo de código, a cor cinza é associada à propriedade cor (''color'') do rectângulo. De modo similar, nós associamos a largura (''width'') e altura (''height'') do retângulo. | |||
O elemento "Text":http://doc.qt.nokia.com/4.7/qml-text.html (Texto) é um campo de texto não editável. Nós batizamos esse elemento texto de buttonLabel. Para configurar o conteúdo desse campo de texto, associamos o valor desejado à propriedade texto (''text''). Essa "etiqueta" fica no interior do retângulo e para centralizá-la, associamos as âncotas do elemento Text às de seu pai (''parent''), chamado simplebutton. Âncoras podem ser associadas às âncoras de outro item, simplificando assim o ''layout''. | |||
Salvemos esse código como SimpleButton.qml. Executando o qmlviewer com tal arquivo como argumento exibirá um retângulo cinza com um texto em seu interior. | |||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png]] | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png]] | ||
Para implementar a funcionalidade de clique no botão, podemos usar o manipulador de eventos do QML. O manipulador de eventos do QML é bastante parecido com o | Para implementar a funcionalidade de clique no botão, podemos usar o manipulador de eventos do QML. O manipulador de eventos do QML é bastante parecido com o "mecanismo de sinais e ''slots'' do Qt":http://doc.qt.nokia.com/4.7/signalsandslots.html. Sinais são emitidos e os ''slots'' conectados são chamados. | ||
<code> | <code> | ||
Rectangle{ | |||
id:simplebutton | |||
… | |||
MouseArea{ | MouseArea{ | ||
id: buttonMouseArea | |||
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors | anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors | ||
//onClicked handles valid mouse button clicks | |||
onClicked: console.log(buttonLabel.text + " clicked" ) | |||
} | |||
} | |||
</code> | |||
Adicionamos um elemento | Adicionamos um elemento "MouseArea":http://doc.qt.nokia.com/4.7/qml-mousearea.html ao nosso simplebutton. Elementos MouseArea descrevem a área interativa onde movimentos de mouse/rato são detectados. Para nosso botão, nós ancoramos a "MouseArea":http://doc.qt.nokia.com/4.7/qml-mousearea.html ao seu pai, que é o simplebutton. A sintaxe anchors.fill é uma forma de acessar uma propriedade específica, chamada ''fill'', dentro de um grupo de propriedades chamadas âncoras. QML usa "''layout'' baseado em âncoras":http://doc.qt.nokia.com/4.7/qml-anchor-layout.html onde os itens podem ser acorados uns aos outros, criando assim ''layouts'' robustos. | ||
A MouseArea tem vários manipuladores de sinais que são chamados durante os movimentos do mouse/rato dentro dos limites da MouseArea. Um deles é o onClicked ("ao clicar") e é chamado toda vez que um botão do mouse/rato é clicado, sendo que o botão esquerdo é o padrão. É possível associar ações ao onClicked. No nosso exemplo, console.log() exibe texto toda vez que a MouseArea recebe cliques. A função console.log() é útil para depuração e para saídas de texto. | |||
O código em SimpleButton.qml é suficiente para exibir um botão e escrever texto na saída padrão toda vez que receber um clique. | |||
<code> | <code> | ||
Rectangle { | |||
id:Button | |||
… | |||
property color buttonColor: | property color buttonColor: "lightblue" | ||
property color onHoverColor: "gold" | |||
property color borderColor: "white" | |||
signal buttonClick() | signal buttonClick() | ||
onButtonClick: { | |||
console.log(buttonLabel.text + " clicked" ) | |||
} | |||
MouseArea{ | MouseArea{ | ||
onClicked: buttonClick() | |||
hoverEnabled: true | |||
onEntered: parent.border.color = onHoverColor | |||
onExited: parent.border.color = borderColor | |||
} | |||
//determina a cor do botão usandoo operador condicional | //determina a cor do botão usandoo operador condicional | ||
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor | |||
} | |||
</code> | |||
Um botão funcional está em Button.qml. Os trechos de código deste arquivo tem partes omitidas, denotadas por reticências porque elas ou foram apresentados em seções anteriores ou são irrelevantes para o código em discussão atualmente. | Um botão funcional está em Button.qml. Os trechos de código deste arquivo tem partes omitidas, denotadas por reticências porque elas ou foram apresentados em seções anteriores ou são irrelevantes para o código em discussão atualmente. | ||
Propriedades personalizadas são declaradas usando a sintaxe de tipo property. No código, a propriedade buttonColor, do tipo color, é declarada e recebe o valor "lightblue". buttonColor é mais tarde usada em uma operação condicional para determinar a cor de preenchimento do botão. Note que atribuição de valores às propriedades é feita usando o sinal de igual =, enquanto associação de valores é feita usando dois pontos :. Propriedades personalizadas permitem que itens internos sejam acessíveis fora do escopo do nosso elemento Rectangle. Existem "tipos básicos QML":http://doc.qt.nokia.com/4.7/qdeclarativebasictypes.html tais como int, string, real, assim como um tipo chamado variant. | |||
Associando os manipuladores de sinais onEntered e onExited a cores, as bordas do botão ficarão amarelas quando o ponteiro do mouse/rato estiver sobre a área do botão e voltarão à cor original quando o ponteiro sai da área do botão. | |||
Um sinal buttonClick() é declarado em Button.qml colocando a palavra-chave signal em frente ao nome do sinal. Todos os sinais tem seus manipuladores automaticamente criados, com nomes começando em on. Como resultado, onButtonClick é o manipulador do sinal buttonClick. Com isso, atribui-se uma ação executada por onButtonClick. No nosso botão de exemplo, o manipulador de mouse/rato onClicked simplesmente chamará onButtonClick, que por sua vez exibirá um texto. onButtonClick possibilita objetos externos acessarem a MouseArea do Button de uma forma simples. Por exemplo, itens podem possuir mais que uma declaração de MouseAreae um sinal buttonClick pode identificar qual manipulador é o mais adequado para determindada situação. | |||
Nós agora temos o conhecimento básico para implementar itens em QML que lidam com movimentos básicos de mouse/rato. Nós criamos um elemento Text dentro de um elemento Rectangle, personalizamos suas propriedades, e implementamos comportamentos que respondem aos movimentos de mouse. A idéia de criar elementos dentro de elementos acontece repidamente ao longo do editor de texto que criaremos. | |||
O botão que temos é inútil a menos que o utilizemos como um componente para executar uma ação. Na próxima seção, criaremos um menu contendo vários desses botões. | |||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.pngp|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.pngp]] | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.pngp|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.pngp]] | ||
Line 47: | Line 92: | ||
=== Criando uma página de Menu === | === Criando uma página de Menu === | ||
Até agora, vimos como criar elementos e associar comportamentos em apenas um arquivo QML. Nesta seção, veremos como importar elementos QML e como reutilizar alguns dos componentes criados para criação de outros componentes. | Até agora, vimos como criar elementos e associar comportamentos em apenas um arquivo QML. Nesta seção, veremos como importar elementos QML e como reutilizar alguns dos componentes criados para criação de outros componentes. | ||
Menus exibem o conteúdo de uma lista, cada item sendo capaz de realizar uma ação. Com QML, podemos criar um menu de diferentes maneiras. Primeiramente, criaremos um menu contendo botoões que eventualmente realizarão alguma ação. O código do está no arquivo FileMenu.qml. | |||
<code> | <code> | ||
import Qt 4.7 importa o módulo principal Qt QML | |||
import "folderName" importa conteúdos do diretório folderNamefolder | |||
import "script.js" as Script importa um arquivo Javascript como Script | |||
</code> | |||
A sintaxe mostrada acima apresenta como usar a palavra-chave import. Isso é necessário para usar arquivos | A sintaxe mostrada acima apresenta como usar a palavra-chave import. Isso é necessário para usar arquivos "JavaScript":https://developer.mozilla.org/en/JavaScript, ou arquivos QML que não estejam no mesmo diretório. Como Button.qml está no mesmo diretório que FileMenu.qml, não precisamos importá-lo para que Buton seja usado. Para criar um elemento Button, basta declarar Button{}, similar a uma declaração Rectangle{}. | ||
<code> | <code> | ||
Em FileMenu.qml: | |||
Row{ | Row{ | ||
anchors.centerIn: parent | |||
spacing: parent.width/6 | |||
Button{ | Button{ | ||
id: loadButton | |||
buttonColor: "lightgrey" | |||
label: "Load" | |||
} | |||
Button{ | |||
buttonColor: "grey" | |||
id: saveButton | |||
label: "Save" | |||
} | |||
Button{ | |||
id: exitButton | |||
label: "Exit" | |||
buttonColor: "darkgrey" | |||
onButtonClick: Qt.quit() | onButtonClick: Qt.quit() | ||
} | |||
} | |||
</code> | |||
Em FileMenu.qml, nós declaramos três elementos Button. Eles são declarados dentro de um elemento | Em FileMenu.qml, nós declaramos três elementos Button. Eles são declarados dentro de um elemento "Row":http://doc.qt.nokia.com/4.7/qml-row.html, um posicionador que colocará seus filhos em uma fileira horizontal. A declaração deButton mora em Button.qml, que é o mesmo Button.qml que nós usamos na seção anterior. Novas associações de propriedades podem ser declaradas dentro dos novos botões criados, efetivamente sobrescrevendo as propriedades de Button.qml. O botão chamado exitButton fechará a janela quando for clicado. Observe que o manipulador de sinal onButtonClick de Button.qml será chamado além do manipulador onButtonClick em exitButton. | ||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png]] | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png]] | ||
Row é declarado em um Rectangle, criando um envoltório para a linha de botões. Esse botão adicional cria uma forma indireta de organizar a linha de botões dentro de um menu. | Row é declarado em um Rectangle, criando um envoltório para a linha de botões. Esse botão adicional cria uma forma indireta de organizar a linha de botões dentro de um menu. | ||
A declaração do menu de edição é semelhante. O menu tem botões com as seguintes legendas: Copy, Paste, e Select All. | |||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png]] | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png]] | ||
Line 77: | Line 147: | ||
=== Usando modelos de dados e visualizadores === | === Usando modelos de dados e visualizadores === | ||
QML tem diferentes | QML tem diferentes "visualizadores de dados":http://doc.qt.nokia.com/4.7/qdeclarativemodels.html para mostrar os "modelos de dados":http://doc.qt.nokia.com/4.7/qdeclarativemodels.html. Nossa barra de menu exibirá os menus em uma lista, com um cabeçalho mostrando uma linha de nomes de menus. A lista de menus é declarada dentro de "VisualItemModel":http://doc.qt.nokia.com/4.7/qml-visualitemmodel.html. O elemento VisualItemModel contém itens que já tem representação visual, tais como Rectangle e outros elementos de interface importados. Outros tipos de modelo, como o elemento "ListModel":http://doc.qt.nokia.com/4.7/qml-listmodel.html, precisam de um ''delegate'' para exibirem seus dados. | ||
Nós declaramos dois itens visuais no menuListModel: FileMenu e EditMenu. Nós personalizamos os dois menus e os exibimos usando um "ListView":http://doc.qt.nokia.com/4.7/qml-listview.html. O arquivo MenuBar.qml contém as declarações QML e um menu simples de edição é definido em EditMenu.qml. | |||
<code> | <code> | ||
VisualItemModel{ | |||
id: menuListModel | |||
FileMenu{ | |||
width: menuListView.width | |||
height: menuBar.height | |||
color: fileColor | |||
} | |||
EditMenu{ | |||
color: editColor | |||
width: menuListView.width | |||
height: menuBar.height | |||
} | |||
} | |||
</code> | |||
O elemento | O elemento "ListView":http://doc.qt.nokia.com/4.7/qml-listview.html mostrará dados de um modelo de acordo com um ''delegate''. O ''delegate'' declara os itens do modelo de forma a mostrá-los em um elemeto Row ou os mostra em um grid. Nosso menuListModel possui itens visíveis, por isso, não precisa um ''delegate''. | ||
<code> | <code> | ||
ListView{ | |||
id: menuListView | |||
//Âncoras são configuradas para reagir às âncoras da janela | //Âncoras são configuradas para reagir às âncoras da janela | ||
anchors.fill:parent | |||
anchors.bottom: parent.bottom | |||
width:parent.width | |||
height: parent.height | |||
//modelo que contém os dados | //modelo que contém os dados | ||
model: menuListModel | |||
//controla o movimento da troca de menus | //controla o movimento da troca de menus | ||
snapMode: ListView.SnapOneItem | |||
orientation: ListView.Horizontal | |||
boundsBehavior: Flickable.StopAtBounds | |||
flickDeceleration: 5000 | |||
highlightFollowsCurrentItem: true | |||
highlightMoveDuration:240 | |||
highlightRangeMode: ListView.StrictlyEnforceRange | |||
} | |||
</code> | |||
Adicionalmente, ListView herda do elemento | Adicionalmente, ListView herda do elemento "Flickable":http://doc.qt.nokia.com/4.7/qml-flickable.html, o que faz com que a lista responda a arrastos e outros gestos de mouse. A última porção de código configura as propriedades de Flickablepara criar o movimento desejado. Em particular, a propriedade highlightMoveDuration muda a duração da transição de ''flick''. Quanto maior o valor de highlightMoveDuration, menor a velocidade das transições do menu. | ||
ListView controla os itens do modelo através de um índice e cada item visual do modelo é acessível através desse index, seguindo a ordem de declaração. Alterando a propriedade currentIndex efetivamente altera o item destacado no ListView. O cabeçalho do nosso menu exemplifica esse efeito. Exitem dois botões em linha, ambos mudando o menu atual quando clicados. O item fileButton muda o menu atual para o menu de arquivos, com índice sendo 0 pois FileMenu é declaredo primeiro em menuListModel. De modo análogo, editButton mudará o menu atual para EditMenu. | |||
O retângulo labelList tem valor z igual a 1, denotando tque é mostrado à frente da barra de menus. Itens com maior z são exibidos à front dos itens com menores valores de z. O valor padrão de z é 0. | |||
<code> | <code> | ||
Rectangle{ | |||
id: labelList | |||
… | |||
z: 1 | |||
Row{ | |||
anchors.centerIn: parent | |||
spacing:40 | |||
Button{ | |||
label: "File" | |||
id: fileButton | |||
… | |||
onButtonClick: menuListView.currentIndex = 0 | |||
} | |||
Button{ | |||
id: editButton | |||
label: "Edit" | |||
… | |||
onButtonClick: menuListView.currentIndex = 1 | |||
} | |||
} | |||
} | |||
</code> | |||
A barra de menu que acaba de ser criada pode ser movida para acessar os menus ou através de cliques nos títulos dos menus no topo. A troca de menus é intuitiva e responsiva. | A barra de menu que acaba de ser criada pode ser movida para acessar os menus ou através de cliques nos títulos dos menus no topo. A troca de menus é intuitiva e responsiva. | ||
Line 103: | Line 228: | ||
=== Declarando um TextArea === | === Declarando um TextArea === | ||
Nosso editor de texto não é um editor de texto se não possuir uma área para edição de texto. O elemento | Nosso editor de texto não é um editor de texto se não possuir uma área para edição de texto. O elemento "TextEdit":http://doc.qt.nokia.com/4.7/qml-textedit.html provê a declaração de uma área de edição de texto de múltiplas linhas. "TextEdit":http://doc.qt.nokia.com/4.7/qml-textedit.html é diferente do elemento "Text":http://doc.qt.nokia.com/4.7/qml-text.html, que não permite a edição direta de seu conteúdo. | ||
<code> | <code> | ||
TextEdit{ | |||
id: textEditor | |||
anchors.fill:parent | |||
width:parent.width; height:parent.height | |||
color:"midnightblue" | |||
focus: true | |||
wrapMode: TextEdit.Wrap | wrapMode: TextEdit.Wrap | ||
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle) | onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle) | ||
} | |||
</code> | |||
O editor tem as propriedades de cor de fonte configuradas, bem como a quebra de linha. A área do TextEdit está dentro de uma área ''flickable'' que rola o texto se o cursor estiver fora da área visível. A função ensureVisible() verificará se o retângulo do cursor está fora dos limites da área visível e faz a movimentação quando necessária. QML usa sintaxe Javascript para seus scripts e, conforme dito anteriormente, arquivos Javascript podem ser importados e usados dentro de um arquivo QML. | O editor tem as propriedades de cor de fonte configuradas, bem como a quebra de linha. A área do TextEdit está dentro de uma área ''flickable'' que rola o texto se o cursor estiver fora da área visível. A função ensureVisible() verificará se o retângulo do cursor está fora dos limites da área visível e faz a movimentação quando necessária. QML usa sintaxe Javascript para seus scripts e, conforme dito anteriormente, arquivos Javascript podem ser importados e usados dentro de um arquivo QML. | ||
<code> | <code> | ||
function ensureVisible®{ | |||
if (contentX >= r.x) | |||
contentX = r.x; | |||
else if (contentX+width <= r.x+r.width) | |||
contentX = r.x+r.width-width; | |||
if (contentY >= r.y) | |||
contentY = r.y; | |||
else if (contentY+height <= r.y+r.height) | |||
contentY = r.y+r.height-height; | |||
} | |||
</code> | |||
=== Combinando componentes para o Editor de Texto === | === Combinando componentes para o Editor de Texto === | ||
Line 119: | Line 263: | ||
Agora estamos prontos para criar o ''layout'' do nosso editor de texto usando QML. O editor de texto tem dois componentes, a barra de menu que criamos e a área de texto. QML nos permite reutilizar elementos, consequentemente tornando nosso código mais simples, através da importação de componentes e personalizando quando necessário. Nosso editor de texto divide a janela em dois; um terço da tela fica dedicada à barra de menu e o restante para a área de texto. A barra de menu é exibida à frente de qualquer outro elemento. | Agora estamos prontos para criar o ''layout'' do nosso editor de texto usando QML. O editor de texto tem dois componentes, a barra de menu que criamos e a área de texto. QML nos permite reutilizar elementos, consequentemente tornando nosso código mais simples, através da importação de componentes e personalizando quando necessário. Nosso editor de texto divide a janela em dois; um terço da tela fica dedicada à barra de menu e o restante para a área de texto. A barra de menu é exibida à frente de qualquer outro elemento. | ||
<code> | <code> | ||
Rectangle{ | |||
id: screen | id: screen | ||
width: 1000; height: 1000 | |||
//a tela é dividida entre MenuBar e TextArea. 1/3 fica com MenuBar | //a tela é dividida entre MenuBar e TextArea. 1/3 fica com MenuBar | ||
property int partition: height/3 | |||
MenuBar{ | MenuBar{ | ||
id:menuBar | |||
height: partition | |||
width:parent.width | |||
z: 1 | |||
} | |||
TextArea{ | TextArea{ | ||
id:textArea | |||
anchors.bottom:parent.bottom | |||
y: partition | |||
color: "white" | |||
height: partition*2 | |||
width:parent.width | |||
} | |||
} | |||
</code> | |||
Importando componentes reutilizáveis, nosso arquivo TextEditor parece bem mais simples. Nós podemos então modificar a aplicação sem termos que nos preocupar com propriedades e comportamentos que já foram definidos. Usando essa abordagem, o ''layout'' e a interface com usuário podem ser criados de uma forma simples. | Importando componentes reutilizáveis, nosso arquivo TextEditor parece bem mais simples. Nós podemos então modificar a aplicação sem termos que nos preocupar com propriedades e comportamentos que já foram definidos. Usando essa abordagem, o ''layout'' e a interface com usuário podem ser criados de uma forma simples. | ||
Line 139: | Line 300: | ||
Nosso editor de texto parece simples, nós precisamos incrementá-lo. Usando QML, nós podemos declarar transições e animar nosso editor. Nossa barra de menu ocupa um terço da tela e seria bom que ela aparecesse apenas quando nós queremos usá-la. | Nosso editor de texto parece simples, nós precisamos incrementá-lo. Usando QML, nós podemos declarar transições e animar nosso editor. Nossa barra de menu ocupa um terço da tela e seria bom que ela aparecesse apenas quando nós queremos usá-la. | ||
Podemos adicionar uma interface de desenho, que irá contrair ou expandir a barra de menu quando clicada. Em nossa implementação, nós temos um retângulo fino que responde aos cliques de ''mouse''. A área de desenho, assim como a aplicação, possui dois estados: | Podemos adicionar uma interface de desenho, que irá contrair ou expandir a barra de menu quando clicada. Em nossa implementação, nós temos um retângulo fino que responde aos cliques de ''mouse''. A área de desenho, assim como a aplicação, possui dois estados: "área de desenho aberta" e "área de desenho fechada". O item área de desenho é uma retângulo estreito com pequena altura. Dentro dele um elemento "Image":http://doc.qt.nokia.com/4.7/qml-image.html declara que um ícone seta fica centralizado dentro da área de desenho. Esta por sua vez associa um estado à aplicação como um todo através do identificador ''screen'', toda vez que o usuário clicar sobre a MouseArea. | ||
<code> | <code> | ||
Rectangle{ | |||
id:drawer | |||
height:15 | |||
Image{ | Image{ | ||
id: arrowIcon | |||
source: "images/arrow.png" | |||
anchors.horizontalCenter: parent.horizontalCenter | |||
} | |||
MouseArea{ | MouseArea{ | ||
screen.state = | id: drawerMouseArea | ||
anchors.fill:parent | |||
else if (screen.state | onClicked:{ | ||
if (screen.state "DRAWER_CLOSED"){ | |||
screen.state = "DRAWER_OPEN" | |||
} | |||
else if (screen.state "DRAWER_OPEN"){ | |||
screen.state = "DRAWER_CLOSED" | |||
} | |||
} | |||
… | |||
} | |||
} | |||
</code> | |||
Um estado é simplesmente uma coleção de configurações e é declarado em um elemento | Um estado é simplesmente uma coleção de configurações e é declarado em um elemento "State":http://doc.qt.nokia.com/4.7/qml-state.html. Uma lista de estados pode ser listada e associada a propriedade states. Em nossa aplicação, os dois estados são chamados DRAWER_CLOSED e DRAWER_OPEN (área de desenho fechada e aberta, respectivamente). As configurações de itens são declaradas em elementos "PropertyChanges":http://doc.qt.nokia.com/4.7/qml-propertychanges.html. no estado DRAWER_OPEN, 4 itens receberão modificações de propriedades. O primeiro alvo, menuBar, mudará sua propriedade y para 0. Similarmente, textArea descerá para uma nova posição quando o estado for DRAWER_OPEN. Os elementos textArea, drawer e o ícone sofrerão alterações para se ajustarem ao estado corrente. | ||
<code> | <code> | ||
states:[ | |||
State { | |||
name: "DRAWER_OPEN" | |||
PropertyChanges { target: menuBar; y: 0} | |||
PropertyChanges { target: textArea; y: partition + drawer.height} | |||
PropertyChanges { target: drawer; y: partition} | |||
PropertyChanges { target: arrowIcon; rotation: 180} | |||
}, | |||
State { | |||
name: "DRAWER_CLOSED" | |||
PropertyChanges { target: menuBar; y:-height; } | |||
PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height } | |||
PropertyChanges { target: drawer; y: 0 } | |||
PropertyChanges { target: arrowIcon; rotation: 0 } | |||
} | |||
] | |||
</code> | |||
Mudanças de estado são abruptas e precisam de transições suaves. Transições entre estados são definidos usando o elemento de transição | Mudanças de estado são abruptas e precisam de transições suaves. Transições entre estados são definidos usando o elemento de transição "Transition":http://doc.qt.nokia.com/4.7/qml-transition.html, que pode ser associado a propriedade transitions. Nosso editor de texto tem uma transição de estado sempre que ha mudança para DRAWER_OPEN ou DRAWER_CLOSED. Importante observar que a transição precisa de um estado ''from'' (origem) e de um estado ''to'' (destino), mas para a nossa transição, podemos usar o símbolo coringa * para indicar que a transição se aplica a todas as mudanças de estado. | ||
Durante as transições, podemos atribuir animações às mudanças da propriedade. Nosso menuBar muda de posição de y:0 para y:-partition e podemos animar essaa transição usando o elemento | Durante as transições, podemos atribuir animações às mudanças da propriedade. Nosso menuBar muda de posição de y:0 para y:-partition e podemos animar essaa transição usando o elemento "NumberAnimation":http://doc.qt.nokia.com/4.7/qml-numberanimation.html. Nós declaramos que as propriedades dos alvos serão animadas durante um certo período de tempo usando uma determinada curva de atenuação (''easing curve''). Uma curva de atenuação controla as taxas de animação e interpolação de comportamento durante as transições de estado. A curva de atenuação que nós escolhemos é "Easing.OutQuint":http://doc.qt.nokia.com/4.7/qml-propertyanimation.html#easing.type-prop, que retarda o movimento perto do fim da animação. Por favor leia o "artigo sobre Animações no QML":http://doc.qt.nokia.com/4.7/qdeclarativeanimation.html. | ||
<code> | <code> | ||
transitions: [ | |||
Transition { | |||
to: "*" | |||
NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo } | |||
NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo } | |||
NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo } | |||
} | |||
] | |||
</code> | |||
Outra maneira de animar as alterações de propriedade é declarar um elemento de comportamento: Behavior. Uma transição só funciona durante as mudanças de estado e | Outra maneira de animar as alterações de propriedade é declarar um elemento de comportamento: Behavior. Uma transição só funciona durante as mudanças de estado e "Behavior":http://doc.qt.nokia.com/4.7/qml-behavior.html pode definir uma animação para a mudança de uma propriedade geral. No editor de texto, a seta tem uma NumberAnimation animando sua propriedade rotação sempre que essa propriedade sofre alterações. | ||
<code> | <code> | ||
Em TextEditor.qml: | |||
Behavior{ | Behavior{ | ||
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo } | |||
} | |||
</code> | |||
Voltando aos nossos componentes, com o conhecimento dos estados e animações, podemos melhorar a aparência dos componentes. Em Button.qml, podemos adicionar as alterações de propriedades de cor e escala quando o botão é clicado. Tipos de cores são animados usando | Voltando aos nossos componentes, com o conhecimento dos estados e animações, podemos melhorar a aparência dos componentes. Em Button.qml, podemos adicionar as alterações de propriedades de cor e escala quando o botão é clicado. Tipos de cores são animados usando "ColorAnimation":http://doc.qt.nokia.com/4.7/qml-coloranimation.html e os números são animados usando "NumberAnimation":http://doc.qt.nokia.com/4.7/qml-numberanimation.html. O propertyName na sintaxe mostrada abaixo é útil quando nosso alvo é uma única propriedade. | ||
<code> | <code> | ||
Em Button.qml: | |||
… | |||
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor | color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor | ||
Behavior on color { ColorAnimation{ duration: 55} } | |||
scale: buttonMouseArea.pressed ? 1.1 : 1.00 | scale: buttonMouseArea.pressed ? 1.1 : 1.00 | ||
Behavior on scale { NumberAnimation{ duration: 55} } | |||
</code> | |||
Além disso, podemos melhorar a aparência dos nossos componentes QML adicionando efeitos de cores, tais como gradientes e efeitos de opacidade. Declarando um elemento | Além disso, podemos melhorar a aparência dos nossos componentes QML adicionando efeitos de cores, tais como gradientes e efeitos de opacidade. Declarando um elemento "Gradient":http://doc.qt.nokia.com/4.7/qml-gradient.html vai substituir a propriedade de cor do elemento. Você pode declarar uma cor no gradiente usando o elemento "GradientStop":http://doc.qt.nokia.com/4.7/qml-gradientstop.html. O gradiente é posicionado usando uma escala entre 0.0 e 1.0. | ||
<code> | <code> | ||
Em MenuBar.qml | |||
gradient: Gradient { | |||
GradientStop { position: 0.0; color: "#8C8F8C" } | |||
GradientStop { position: 0.17; color: "#6A6D6A" } | |||
GradientStop { position: 0.98;color: "#3F3F3F" } | |||
GradientStop { position: 1.0; color: "#0e1B20" } | |||
} | |||
</code> | |||
Este gradiente é usado pela barra de menu para exibir um gradiente simulando profundidade. A primeira cor começa em 0.0 e que a última cor é de 1.0. | Este gradiente é usado pela barra de menu para exibir um gradiente simulando profundidade. A primeira cor começa em 0.0 e que a última cor é de 1.0. | ||
Line 183: | Line 405: | ||
Estamos terminando a construção da interface com usuário de um editor de texto muito simples. Indo adiante, a interface do usuário está completa, e nós podemos implementar a lógica do aplicativo usando Qt e C+''. QML funciona bem como uma ferramenta de prototipagem, separando a lógica da aplicação do ''design'' de interface com usuário. | Estamos terminando a construção da interface com usuário de um editor de texto muito simples. Indo adiante, a interface do usuário está completa, e nós podemos implementar a lógica do aplicativo usando Qt e C+''. QML funciona bem como uma ferramenta de prototipagem, separando a lógica da aplicação do ''design'' de interface com usuário. | ||
Agora que temos o ''layout'' do nosso editor de texto, podemos agora implementar as funcionalidades do editor de texto em C+''. Usando QML com C''+ nos permite criar a lógica da aplicação usando Qt. Podemos criar um contexto QML em uma aplicação C++ usando as classes | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png]] | ||
h2. Estendendo QML usando Qt C''+ | |||
Agora que temos o ''layout'' do nosso editor de texto, podemos agora implementar as funcionalidades do editor de texto em C+''. Usando QML com C''+ nos permite criar a lógica da aplicação usando Qt. Podemos criar um contexto QML em uma aplicação C++ usando as classes "Qt Declarative":http://doc.qt.nokia.com/4.7/qtbinding.html e exibir os elementos QML usando uma QGraphicsScene. Alternativamente, nós podemos exportar o nosso código C++ em um plugin que a ferramenta "qmlviewer":http://doc.qt.nokia.com/4.7/qmlviewer.html consegue ler. Para a nossa aplicação, vamos implementar em C++ as funções de carregar e salvar e exportá-las como um ''plugin''. Desta forma, só precisamos carregar o arquivo QML diretamente em vez de rodar um binário executável. | |||
=== Expondo classes C++ ao QML === | === Expondo classes C++ ao QML === | ||
Line 192: | Line 416: | ||
Nós implementaremos carregar e salvar arquivos usando Qt e C+''. Classes C''+ e funções podem ser utilizadas em QML, bastando registrá-las. A classe também deve ser compilado como um plugin_ Qt e o arquivo QML precisa saber onde o plugin está localizado. | Nós implementaremos carregar e salvar arquivos usando Qt e C+''. Classes C''+ e funções podem ser utilizadas em QML, bastando registrá-las. A classe também deve ser compilado como um plugin_ Qt e o arquivo QML precisa saber onde o plugin está localizado. | ||
Para a nossa aplicação, precisamos criar os seguintes itens: | Para a nossa aplicação, precisamos criar os seguintes itens: | ||
# Classe diretório que cuidará de operações relacionados com diretórios | |||
# Classe arquivo que é um "QObject":http://doc.qt.nokia.com/4.7/qobject.html, simulando a lista de arquivos de um diretório | |||
# Classe ''plugin'' que registrará a classe no contexto QML | |||
# Arquivo de projeto Qt que compilará o plugin | |||
# Um arquivo qmldir dizendo ao qmlviewer onde encontrar o plugin | |||
=== Construindo um plugin Qt === | === Construindo um plugin Qt === | ||
Line 198: | Line 427: | ||
Para criar um plugin, precisamos definir o seguinte em um arquivo de projeto Qt. Primeiramente, os arquivos de código-fonte necessários, cabeçalhos e módulos Qt precisam ser adicionados em nosso arquivo de projeto. Todos os arquivos de código C++ e arquivos de projeto estão no diretório filedialog. | Para criar um plugin, precisamos definir o seguinte em um arquivo de projeto Qt. Primeiramente, os arquivos de código-fonte necessários, cabeçalhos e módulos Qt precisam ser adicionados em nosso arquivo de projeto. Todos os arquivos de código C++ e arquivos de projeto estão no diretório filedialog. | ||
<code> | <code> | ||
In cppPlugins.pro: | |||
TEMPLATE = lib | |||
CONFIG ''= qt plugin | |||
QT''= declarative | |||
DESTDIR ''= ../plugins | |||
OBJECTS_DIR = tmp | |||
MOC_DIR = tmp | |||
TARGET = FileDialog | |||
HEADERS''= directory.h file.h dialogPlugin.h | |||
SOURCES += directory.cpp file.cpp dialogPlugin.cpp | SOURCES += directory.cpp file.cpp dialogPlugin.cpp | ||
</code> | |||
Em particular, nós compilamos Qt com o módulo '''declarative''' e o configuramos como um plugin, precisando de um modelo (template) de lib. Vamos colocar o plugin compilado dentro do plugins do diretório pai do atual. | Em particular, nós compilamos Qt com o módulo '''declarative''' e o configuramos como um plugin, precisando de um modelo (template) de lib. Vamos colocar o plugin compilado dentro do plugins do diretório pai do atual. | ||
Line 212: | Line 449: | ||
=== Registrando uma classe no QML === | === Registrando uma classe no QML === | ||
<code> | <code> | ||
In dialogPlugin.h: | |||
#include | #include <QtDeclarative/QDeclarativeExtensionPlugin> | ||
class DialogPlugin : public QDeclarativeExtensionPlugin | class DialogPlugin : public QDeclarativeExtensionPlugin | ||
{ | |||
Q_OBJECT | |||
public: | public: | ||
void registerTypes(const char *uri); | |||
}; | }; | ||
</code> | |||
Nossa classe plugin, DialogPlugin, é uma subclasse de | Nossa classe plugin, DialogPlugin, é uma subclasse de "QDeclarativeExtensionPlugin":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html. Precisamos implementar a função herdada, "registerTypes()":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#registerTypes. O arquivo dialogPlugin.cpp parece com o seguinte: | ||
<code> | <code> | ||
DialogPlugin.cpp: | |||
#include | #include "dialogPlugin.h" | ||
#include "directory.h" | |||
#include "file.h" | |||
#include <QtDeclarative/qdeclarative.h> | |||
void DialogPlugin::registerTypes(const char '''uri){ | void DialogPlugin::registerTypes(const char '''uri){ | ||
< | qmlRegisterType<Directory>(uri, 1, 0, "Directory"); | ||
< | qmlRegisterType<File>(uri, 1, 0,"File"); | ||
} | |||
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin); | |||
</code> | |||
A função registerTypes() registra nossas classes File e Directory no QML. Esta função precisa do nome da classe para o seu ''template'', um número de versão principal, um número de versão menor, e um nome para nossas classes. | |||
Precisamos exportar o plugin usando a macro "Q_EXPORT_PLUGIN2":http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2. Note que em nosso arquivo dialogPlugin.h, temos a macro "Q_OBJECT":http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT no topo da nossa classe. Assim sendo, precisamos executar qmake no arquivo de projeto para gerar o código de meta-objetos necessários. | |||
h3. Criando propriedades QML em uma classe C++ | |||
Podemos criar elementos QML e propriedades usando C++ e o "sistema de meta-objetos do Qt":http://doc.qt.nokia.com/4.7/metaobjects.html. Podemos implementar propriedades usando sinais e ''slots'', fazendo Qt conhecer essas propriedades. Estas propriedades podem ser utilizadas em QML. | |||
Para o editor de texto, precisamos salvar e carregar arquivos. Normalmente, esses recursos estão contidos em um diálogo de arquivo. Felizmente, nós podemos usar "QDir":http://doc.qt.nokia.com/4.7/qdir.html, "QFile":http://doc.qt.nokia.com/4.7/qfile.html e "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para implementar a leitura de diretório e dos fluxos de entrada e saída. | |||
<code> | |||
class Directory : public QObject{ | |||
Q_OBJECT | |||
Q_PROPERTY(int filesCount READ filesCount CONSTANT) | |||
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) | |||
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged) | |||
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT ) | |||
… | |||
</code> | |||
A classe Directory usa o sistema de meta-objetos do Qt para registrar propriedades que necessita para realizar tratamento de arquivos. A classe Directory é exportada como um plugin e é utilizável em QML como um elemento Directory. Cada propriedades listadas usando a macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY é uma propriedade QML. | |||
"Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY declara uma propriedade, bem como as suas funções de leitura e escrita no sistema de meta-objetos. Por exemplo, a propriedade filename, do tipo "QString":http://doc.qt.nokia.com/4.7/qstring.html, pode ser lido usando filename() e escrita utilizando a função setFileName(). Além disso, há um sinal associado à propriedade filename chamado filenameChanged(), que é emitido sempre que a propriedade sofre alterações. As funções de leitura e escrita são declaradas como públicas no cabeçalho do arquivo. | |||
Da mesma forma, temos as outras propriedades declaradas de acordo com seus usos. A propriedade filesCount indica o número de arquivos em um diretório. A propriedade filenameé definida para o nome do arquivo selecionado atualmente e o conteúdo carregado/salvo é armazenado na propriedade fileContent. | |||
<code> | |||
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT ) | |||
</code> | |||
A propriedade da lista de arquivos (''files'') é uma lista de todos os arquivos filtrados em um diretório. A classe Directory é implementada para filtrar os arquivos de texto inválidos; apenas arquivos com extensão .txt são válidos. Além disso, "QList":http://doc.qt.nokia.com/4.7/qlist.htmls podem ser usadas em arquivos QML declarando-as como "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html em C+''. O objeto da lista precisa herdar de um "QObject":http://doc.qt.nokia.com/4.7/qobject.html, portanto, a classe File também deve herdar "QObject":http://doc.qt.nokia.com/4.7/qobject.html. Na classe Directory, a lista de objetos File é armazenada em uma "QList":http://doc.qt.nokia.com/4.7/qlist.html chamada m_fileList. | |||
<code> | |||
class File : public QObject{ | |||
Q_OBJECT | |||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) | |||
… | |||
}; | |||
</code> | |||
As propriedades podem ser usadas em QML como parte de uma propriedade do elemento Directory. Note que não temos que criar uma propriedade identificadora id em nosso código C. | |||
<code> | |||
Directory{ | |||
id: directory | |||
filesCount | |||
filename | |||
fileContent | |||
files | |||
files[0].name | |||
} | |||
</code> | |||
Como QML usa a sintaxe e estrutura de Javascript, podemos percorrer a lista de arquivos e recuperar suas propriedades. Para recuperar a propriedade do arquivo do primeiro arquivo, podemos chamar files[0].name. | |||
Funções regulares de C''+ também são acessíveis no QML. As funções para carregar e salvar arquivos são implementadas em C++ e declaradas usando a macro "Q_INVOKABLE":http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE. Alternativamente, podemos declarar as funções como ''slots'' que serão acessíveis a partir do QML. | |||
<code> | |||
Em Directory.h: | |||
Q_INVOKABLE void saveFile(); | |||
Q_INVOKABLE void loadFile(); | |||
</code> | |||
A classe Directory também tem de notificar outros objetos quando o conteúdo do diretório for alterado. conseguimos isso utilizado um sinal. Como mencionado anteriormente, os sinais QML tem um manipulador correspondente com os seus nomes prefixados com ''on''. O sinal é chamado directoryChanged e é emitido sempre que houver uma atualização de diretório. A atualização simplesmente recarrega o conteúdo do diretório e atualiza a lista de arquivos válidos no diretório. Itens QML podem ser notificados anexando uma ação para o manipulador de sinal onDirectoryChanged. | |||
A lista de propriedades precisam ser exploradas mais a fundo. Isso acontece pois as propriedades lista usam callbacks para acessar e modificar o conteúdo da lista. A propriedade lista é do tipo QDeclarativeListProperty<File>. Sempre que a lista é acessada, a função de acesso precisa retornar uma QDeclarativeListProperty<File>. O tipo do modelo (''template''), arquivo, precisa ser derivado QObject. Além disso, para criar uma "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html, o método de acesso e modificadores da lista precisam ser passados para o consructor como ponteiros de função. A lista, um QList no nosso caso, também precisa ser uma lista de File. | |||
O construtor de "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html e a implementação de Directory: | |||
<code> | |||
QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 ) | |||
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr ); | |||
</code> | |||
O construtor passa ponteiros para funções que irã acrescentar à lista, contar a lista, recuperar o item usando um índice e esvaziar a lista. Apenas a função de acréscimo é obrigatória. Observe que os ponteiros de função devem coincidir com as definições de | O construtor passa ponteiros para funções que irã acrescentar à lista, contar a lista, recuperar o item usando um índice e esvaziar a lista. Apenas a função de acréscimo é obrigatória. Observe que os ponteiros de função devem coincidir com as definições de "AppendFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AppendFunction-typedef, "CountFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#CountFunction-typedef, "AtFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AtFunction-typedef ou "ClearFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#ClearFunction-typedef. | ||
<code> | <code> | ||
void appendFiles(QDeclarativeListProperty<File> * property, File * file) | |||
File* fileAt(QDeclarativeListProperty<File> * property, int index) | |||
int filesSize(QDeclarativeListProperty<File> * property) | |||
void clearFilesPtr(QDeclarativeListProperty<File> *property) | |||
</code> | |||
Para simplificar o nosso diálogo de arquivo, a classe Directory filtra arquivos de texto inválidos, que são arquivos que não tem um extensão .txt. Se um arquivo não tem a extensão .txt, então não vai aparecer nosso diálogo de arquivo. Além disso, a implementação garante que os arquivos salvos tem uma extensão txt. Directory usa | Para simplificar o nosso diálogo de arquivo, a classe Directory filtra arquivos de texto inválidos, que são arquivos que não tem um extensão .txt. Se um arquivo não tem a extensão .txt, então não vai aparecer nosso diálogo de arquivo. Além disso, a implementação garante que os arquivos salvos tem uma extensão txt. Directory usa "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para ler o arquivo e também para gravar a saída do conteúdo em um arquivo. | ||
Com o nosso elemento Directory, podemos recuperar os arquivos como uma lista, saber quantos arquivos de texto estão no diretório do aplicativo, obter o nome do arquivo e o conteúdo como uma ''string'', e ser notificado sempre que houver alterações no conteúdo do diretório. | |||
Para criar o plugin, execute qmake no arquivo de projeto cppPlugins.pro e em seguida, execute make para compilar e transferir o plugin para o diretório de plugins. | |||
=== Importando um plugin no QML === | === Importando um plugin no QML === | ||
Line 266: | Line 577: | ||
A ferramenta qmlviewer importa arquivos que estão no mesmo diretório da aplicação. Podemos também criar um arquivo qmldir contendo os locais dos arquivos QML que desejamos importar. O arquivo qmldir também pode guardar os locais de plugins e outros recursos. | A ferramenta qmlviewer importa arquivos que estão no mesmo diretório da aplicação. Podemos também criar um arquivo qmldir contendo os locais dos arquivos QML que desejamos importar. O arquivo qmldir também pode guardar os locais de plugins e outros recursos. | ||
<code> | <code> | ||
Em qmldir: | |||
Button ./Button.qml | Button ./Button.qml | ||
FileDialog ./FileDialog.qml | |||
TextArea ./TextArea.qml | |||
TextEditor ./TextEditor.qml | |||
EditMenu ./EditMenu.qml | |||
plugin FileDialog plugins | plugin FileDialog plugins | ||
</code> | |||
O plugin que acabamos de criar é chamado FileDialog, conforme indicado pelo campo TARGET no arquivo de projeto. O plugin é compilado no diretório de plugins. | O plugin que acabamos de criar é chamado FileDialog, conforme indicado pelo campo TARGET no arquivo de projeto. O plugin é compilado no diretório de plugins. | ||
Line 276: | Line 593: | ||
=== Integrando um Diálogo de Arquivos ao Menu Arquivo === | === Integrando um Diálogo de Arquivos ao Menu Arquivo === | ||
Nosso FileMenu precisa exibir o elemento FileDialog, contendo uma lista de arquivos de texto em um diretório, permitindo assim que o usuário selecione o arquivo, clicando na lista. Precisamos também atribuir salvar, carregar e novos botões para suas respectivas ações. O FileMenu contém uma entrada de texto editável para permitir que o usuário digite um nome de arquivo usando o teclado. | Nosso FileMenu precisa exibir o elemento FileDialog, contendo uma lista de arquivos de texto em um diretório, permitindo assim que o usuário selecione o arquivo, clicando na lista. Precisamos também atribuir salvar, carregar e novos botões para suas respectivas ações. O FileMenu contém uma entrada de texto editável para permitir que o usuário digite um nome de arquivo usando o teclado. | ||
O elemento Directory é usado no arquivo FileMenu.qml e notifica o elemento FileDialog que o diretório atualizou seu conteúdo. Esta notificação é realizada em um manipulador de sinal, onDirectoryChanged. | |||
<code> | <code> | ||
Em FileMenu.qml: | |||
Directory{ | Directory{ | ||
id:directory | |||
filename: textInput.text | |||
onDirectoryChanged: fileDialog.notifyRefresh() | |||
} | |||
</code> | |||
Mantendo a simplicidade da nossa aplicação, o diálogo de arquivos será sempre visível e não mostrará arquivos inválidos, que não tenha uma extensão .txt, | Mantendo a simplicidade da nossa aplicação, o diálogo de arquivos será sempre visível e não mostrará arquivos inválidos, que não tenha uma extensão .txt, | ||
<code> | <code> | ||
Em FileDialog.qml: | |||
signal notifyRefresh() | signal notifyRefresh() | ||
onNotifyRefresh: dirView.model = directory.files | |||
</code> | |||
O elemento FileDialog exibirá o conteúdo de um diretório lendo sua propriedade lista chamada de files. Os arquivos são usados como modelo de um elemento | O elemento FileDialog exibirá o conteúdo de um diretório lendo sua propriedade lista chamada de files. Os arquivos são usados como modelo de um elemento "GridView":http://doc.qt.nokia.com/4.7/qml-gridview.html, que exibe itens de dados em uma grade de acordo com um ''delegate''. O ''delegate'' lida com a aparência do modelo e nosso diálogo de arquivo simplesmente criará uma grade com texto centralizado no meio. Clicando no nome do arquivo resulta no aparecimento de um retângulo para destacar o nome do arquivo. O FileDialog é notificado sempre que o sinal notifyRefresh é emitido, recarregando os arquivos no diretório. | ||
<code> | <code> | ||
In FileMenu.qml: | |||
Button{ | Button{ | ||
id: newButton | |||
label: "New" | |||
onButtonClick:{ | |||
textArea.textContent = "" | |||
} | |||
} | |||
Button{ | |||
id: loadButton | |||
label: "Load" | |||
onButtonClick:{ | |||
directory.filename = textInput.text | |||
directory.loadFile(); | |||
textArea.textContent = directory.fileContent | |||
} | |||
} | |||
Button{ | |||
id: saveButton | |||
label: "Save" | |||
onButtonClick:{ | |||
directory.fileContent = textArea.textContent | |||
directory.filename = textInput.text | |||
directory.saveFile(); | |||
} | |||
} | |||
Button{ | |||
id: exitButton | |||
label: "Exit" | |||
onButtonClick:{ | |||
Qt.quit() | |||
} | |||
} | |||
</code> | |||
Nosso FileMenu agora pode se conectar às suas respectivas ações. O saveButton transferirá o texto do TextEdit para a propriedade fileContent do diretório e então copiar o seu nome de arquivo da entrada de texto editável. Finalmente, o botão chama a função saveFile | Nosso FileMenu agora pode se conectar às suas respectivas ações. O saveButton transferirá o texto do TextEdit para a propriedade fileContent do diretório e então copiar o seu nome de arquivo da entrada de texto editável. Finalmente, o botão chama a função saveFile();, salvando o arquivo. O sloadButton tem execução semelhante. Também, a ação New esvazia o conteúdo do TextEdit. | ||
Além disso, os botões EditMenu estão ligados às funções d TextEdit para copiar, colar e selecionar todo o texto no editor de texto. | |||
p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png]] | p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png]] |
Revision as of 09:57, 25 February 2015
[toc align_right="yes" depth="4"]
Iniciando o desenvolvimento com QML
Bem-vindo ao mundo do QML, a linguagem declarativa para interfaces com usuário. Neste guia introdutório nós criaremos um editor de texto simples usando QML. Uma vez lido este guia, você estará pronto para criar suas próprias aplicações usando QML e Qt C+.
h2. QML para criar interfaces com usuário
A aplicação que vamos criar é um editor de text simples que irá carregar, salvar e fará algumas manipulações no texto. Este guia consite de duas partes. A primeira parte envolve design do layout da aplicação e seu comportamento, usando a linguagem declarativa QML. Na segunda parte, processos de carregar e salvar arquivos serão implementados com Qt C. Utilizando o "Sistema de Meta-Objetos do Qt":http://doc.qt.nokia.com/4.7/metaobjects.html, é possível expor funções e propriedades do escritas em C+ que elementos QML conseguirão usar. Utilizando QML e Qt C+, é possível manter separadas, de uma forma eficiente, a interface com usuário e a lógica da aplicação.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png
Para executar o exemplo de código QML, basta usar a ferramenta qmlviewer com o arquivo QML como argumento. A porção C+ deste tutorial assume que o leitor tem conhecimentos básicos do procedimento de compilação de código Qt.
=== Capítulos do Tutorial:
- Definindo um Botão e um Menu
- Implementando uma Barra de Menu
- Construindo um Editor de Texto
- Decorando o Editor de Texto
- Estendendo QML usando Qt C++ ===
Definindo um Botão e um Menu
Componente Básico - um Botão
Comecemos nosso editor de texto criando um botão. Em termos de funcionalidade, um botão é uma área sensível ao mouse /rato com um texto. Botões reagem quando um usuário os pressionam. Em QML, o item visual básico é o elemento "Rectangle":http://doc.qt.nokia.com/4.7/qml-rectangle.html (Retângulo). O elemento retângulo tem propriedades para controlar sua a aparência e localização.
Primeiramente, o import Qt 4.7 permite que a ferramenta qmlviewer importe os elementos QML que usaremos em seguida. Tal linha deve existir em todo arquivo QML. Observe que a versão dos módulos Qt é incluida na sentenção de importação. Esse retângulo simples tem um identificador único, simplebutton, quue é atribuído a propriedade id. As propriedades do elemento Retângulo são ligadas aos valores através da listagem da propriedade, seguida de dois pontos, então o valor. Nesse exemplo de código, a cor cinza é associada à propriedade cor (color) do rectângulo. De modo similar, nós associamos a largura (width) e altura (height) do retângulo. O elemento "Text":http://doc.qt.nokia.com/4.7/qml-text.html (Texto) é um campo de texto não editável. Nós batizamos esse elemento texto de buttonLabel. Para configurar o conteúdo desse campo de texto, associamos o valor desejado à propriedade texto (text). Essa "etiqueta" fica no interior do retângulo e para centralizá-la, associamos as âncotas do elemento Text às de seu pai (parent), chamado simplebutton. Âncoras podem ser associadas às âncoras de outro item, simplificando assim o layout. Salvemos esse código como SimpleButton.qml. Executando o qmlviewer com tal arquivo como argumento exibirá um retângulo cinza com um texto em seu interior.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png
Para implementar a funcionalidade de clique no botão, podemos usar o manipulador de eventos do QML. O manipulador de eventos do QML é bastante parecido com o "mecanismo de sinais e slots do Qt":http://doc.qt.nokia.com/4.7/signalsandslots.html. Sinais são emitidos e os slots conectados são chamados.
Rectangle{
id:simplebutton
…
MouseArea{
id: buttonMouseArea
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
//onClicked handles valid mouse button clicks
onClicked: console.log(buttonLabel.text + " clicked" )
}
}
Adicionamos um elemento "MouseArea":http://doc.qt.nokia.com/4.7/qml-mousearea.html ao nosso simplebutton. Elementos MouseArea descrevem a área interativa onde movimentos de mouse/rato são detectados. Para nosso botão, nós ancoramos a "MouseArea":http://doc.qt.nokia.com/4.7/qml-mousearea.html ao seu pai, que é o simplebutton. A sintaxe anchors.fill é uma forma de acessar uma propriedade específica, chamada fill, dentro de um grupo de propriedades chamadas âncoras. QML usa "layout baseado em âncoras":http://doc.qt.nokia.com/4.7/qml-anchor-layout.html onde os itens podem ser acorados uns aos outros, criando assim layouts robustos. A MouseArea tem vários manipuladores de sinais que são chamados durante os movimentos do mouse/rato dentro dos limites da MouseArea. Um deles é o onClicked ("ao clicar") e é chamado toda vez que um botão do mouse/rato é clicado, sendo que o botão esquerdo é o padrão. É possível associar ações ao onClicked. No nosso exemplo, console.log() exibe texto toda vez que a MouseArea recebe cliques. A função console.log() é útil para depuração e para saídas de texto. O código em SimpleButton.qml é suficiente para exibir um botão e escrever texto na saída padrão toda vez que receber um clique.
Rectangle {
id:Button
…
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}
MouseArea{
onClicked: buttonClick()
hoverEnabled: true
onEntered: parent.border.color = onHoverColor
onExited: parent.border.color = borderColor
}
//determina a cor do botão usandoo operador condicional
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}
Um botão funcional está em Button.qml. Os trechos de código deste arquivo tem partes omitidas, denotadas por reticências porque elas ou foram apresentados em seções anteriores ou são irrelevantes para o código em discussão atualmente. Propriedades personalizadas são declaradas usando a sintaxe de tipo property. No código, a propriedade buttonColor, do tipo color, é declarada e recebe o valor "lightblue". buttonColor é mais tarde usada em uma operação condicional para determinar a cor de preenchimento do botão. Note que atribuição de valores às propriedades é feita usando o sinal de igual =, enquanto associação de valores é feita usando dois pontos :. Propriedades personalizadas permitem que itens internos sejam acessíveis fora do escopo do nosso elemento Rectangle. Existem "tipos básicos QML":http://doc.qt.nokia.com/4.7/qdeclarativebasictypes.html tais como int, string, real, assim como um tipo chamado variant. Associando os manipuladores de sinais onEntered e onExited a cores, as bordas do botão ficarão amarelas quando o ponteiro do mouse/rato estiver sobre a área do botão e voltarão à cor original quando o ponteiro sai da área do botão. Um sinal buttonClick() é declarado em Button.qml colocando a palavra-chave signal em frente ao nome do sinal. Todos os sinais tem seus manipuladores automaticamente criados, com nomes começando em on. Como resultado, onButtonClick é o manipulador do sinal buttonClick. Com isso, atribui-se uma ação executada por onButtonClick. No nosso botão de exemplo, o manipulador de mouse/rato onClicked simplesmente chamará onButtonClick, que por sua vez exibirá um texto. onButtonClick possibilita objetos externos acessarem a MouseArea do Button de uma forma simples. Por exemplo, itens podem possuir mais que uma declaração de MouseAreae um sinal buttonClick pode identificar qual manipulador é o mais adequado para determindada situação. Nós agora temos o conhecimento básico para implementar itens em QML que lidam com movimentos básicos de mouse/rato. Nós criamos um elemento Text dentro de um elemento Rectangle, personalizamos suas propriedades, e implementamos comportamentos que respondem aos movimentos de mouse. A idéia de criar elementos dentro de elementos acontece repidamente ao longo do editor de texto que criaremos. O botão que temos é inútil a menos que o utilizemos como um componente para executar uma ação. Na próxima seção, criaremos um menu contendo vários desses botões.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.pngp
Criando uma página de Menu
Até agora, vimos como criar elementos e associar comportamentos em apenas um arquivo QML. Nesta seção, veremos como importar elementos QML e como reutilizar alguns dos componentes criados para criação de outros componentes. Menus exibem o conteúdo de uma lista, cada item sendo capaz de realizar uma ação. Com QML, podemos criar um menu de diferentes maneiras. Primeiramente, criaremos um menu contendo botoões que eventualmente realizarão alguma ação. O código do está no arquivo FileMenu.qml.
import Qt 4.7 importa o módulo principal Qt QML
import "folderName" importa conteúdos do diretório folderNamefolder
import "script.js" as Script importa um arquivo Javascript como Script
A sintaxe mostrada acima apresenta como usar a palavra-chave import. Isso é necessário para usar arquivos "JavaScript":https://developer.mozilla.org/en/JavaScript, ou arquivos QML que não estejam no mesmo diretório. Como Button.qml está no mesmo diretório que FileMenu.qml, não precisamos importá-lo para que Buton seja usado. Para criar um elemento Button, basta declarar Button{}, similar a uma declaração Rectangle{}.
Em FileMenu.qml:
Row{
anchors.centerIn: parent
spacing: parent.width/6
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"
onButtonClick: Qt.quit()
}
}
Em FileMenu.qml, nós declaramos três elementos Button. Eles são declarados dentro de um elemento "Row":http://doc.qt.nokia.com/4.7/qml-row.html, um posicionador que colocará seus filhos em uma fileira horizontal. A declaração deButton mora em Button.qml, que é o mesmo Button.qml que nós usamos na seção anterior. Novas associações de propriedades podem ser declaradas dentro dos novos botões criados, efetivamente sobrescrevendo as propriedades de Button.qml. O botão chamado exitButton fechará a janela quando for clicado. Observe que o manipulador de sinal onButtonClick de Button.qml será chamado além do manipulador onButtonClick em exitButton.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png
Row é declarado em um Rectangle, criando um envoltório para a linha de botões. Esse botão adicional cria uma forma indireta de organizar a linha de botões dentro de um menu. A declaração do menu de edição é semelhante. O menu tem botões com as seguintes legendas: Copy, Paste, e Select All.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png
Munidos de nosso conhecimento de importar e personalizar componentes previamente criados, nós agora combinaremos essas páginas de menu para criar uma barra de menus, consistindo de botões buttons para seleção, e veremos como organizar dados usando QML.
Implementando uma Barra de Menu
Nossa aplicação de editor de texto precisará mostrar menus usando uma barra de menu. Essa barra alternará os diferentes menus e o usuário poderá selecionar qual menu será exibido. Alternar menu implica que os menus precisam de mais estrutura do que a simples exibição em linha. QML usa modelos e visualizadores para estruturar exibir dados.
Usando modelos de dados e visualizadores
QML tem diferentes "visualizadores de dados":http://doc.qt.nokia.com/4.7/qdeclarativemodels.html para mostrar os "modelos de dados":http://doc.qt.nokia.com/4.7/qdeclarativemodels.html. Nossa barra de menu exibirá os menus em uma lista, com um cabeçalho mostrando uma linha de nomes de menus. A lista de menus é declarada dentro de "VisualItemModel":http://doc.qt.nokia.com/4.7/qml-visualitemmodel.html. O elemento VisualItemModel contém itens que já tem representação visual, tais como Rectangle e outros elementos de interface importados. Outros tipos de modelo, como o elemento "ListModel":http://doc.qt.nokia.com/4.7/qml-listmodel.html, precisam de um delegate para exibirem seus dados. Nós declaramos dois itens visuais no menuListModel: FileMenu e EditMenu. Nós personalizamos os dois menus e os exibimos usando um "ListView":http://doc.qt.nokia.com/4.7/qml-listview.html. O arquivo MenuBar.qml contém as declarações QML e um menu simples de edição é definido em EditMenu.qml.
VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}
O elemento "ListView":http://doc.qt.nokia.com/4.7/qml-listview.html mostrará dados de um modelo de acordo com um delegate. O delegate declara os itens do modelo de forma a mostrá-los em um elemeto Row ou os mostra em um grid. Nosso menuListModel possui itens visíveis, por isso, não precisa um delegate.
ListView{
id: menuListView
//Âncoras são configuradas para reagir às âncoras da janela
anchors.fill:parent
anchors.bottom: parent.bottom
width:parent.width
height: parent.height
//modelo que contém os dados
model: menuListModel
//controla o movimento da troca de menus
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 5000
highlightFollowsCurrentItem: true
highlightMoveDuration:240
highlightRangeMode: ListView.StrictlyEnforceRange
}
Adicionalmente, ListView herda do elemento "Flickable":http://doc.qt.nokia.com/4.7/qml-flickable.html, o que faz com que a lista responda a arrastos e outros gestos de mouse. A última porção de código configura as propriedades de Flickablepara criar o movimento desejado. Em particular, a propriedade highlightMoveDuration muda a duração da transição de flick. Quanto maior o valor de highlightMoveDuration, menor a velocidade das transições do menu. ListView controla os itens do modelo através de um índice e cada item visual do modelo é acessível através desse index, seguindo a ordem de declaração. Alterando a propriedade currentIndex efetivamente altera o item destacado no ListView. O cabeçalho do nosso menu exemplifica esse efeito. Exitem dois botões em linha, ambos mudando o menu atual quando clicados. O item fileButton muda o menu atual para o menu de arquivos, com índice sendo 0 pois FileMenu é declaredo primeiro em menuListModel. De modo análogo, editButton mudará o menu atual para EditMenu. O retângulo labelList tem valor z igual a 1, denotando tque é mostrado à frente da barra de menus. Itens com maior z são exibidos à front dos itens com menores valores de z. O valor padrão de z é 0.
Rectangle{
id: labelList
…
z: 1
Row{
anchors.centerIn: parent
spacing:40
Button{
label: "File"
id: fileButton
…
onButtonClick: menuListView.currentIndex = 0
}
Button{
id: editButton
label: "Edit"
…
onButtonClick: menuListView.currentIndex = 1
}
}
}
A barra de menu que acaba de ser criada pode ser movida para acessar os menus ou através de cliques nos títulos dos menus no topo. A troca de menus é intuitiva e responsiva.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png
construindo um Editor de Texto
Declarando um TextArea
Nosso editor de texto não é um editor de texto se não possuir uma área para edição de texto. O elemento "TextEdit":http://doc.qt.nokia.com/4.7/qml-textedit.html provê a declaração de uma área de edição de texto de múltiplas linhas. "TextEdit":http://doc.qt.nokia.com/4.7/qml-textedit.html é diferente do elemento "Text":http://doc.qt.nokia.com/4.7/qml-text.html, que não permite a edição direta de seu conteúdo.
TextEdit{
id: textEditor
anchors.fill:parent
width:parent.width; height:parent.height
color:"midnightblue"
focus: true
wrapMode: TextEdit.Wrap
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}
O editor tem as propriedades de cor de fonte configuradas, bem como a quebra de linha. A área do TextEdit está dentro de uma área flickable que rola o texto se o cursor estiver fora da área visível. A função ensureVisible() verificará se o retângulo do cursor está fora dos limites da área visível e faz a movimentação quando necessária. QML usa sintaxe Javascript para seus scripts e, conforme dito anteriormente, arquivos Javascript podem ser importados e usados dentro de um arquivo QML.
function ensureVisible®{
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
Combinando componentes para o Editor de Texto
Agora estamos prontos para criar o layout do nosso editor de texto usando QML. O editor de texto tem dois componentes, a barra de menu que criamos e a área de texto. QML nos permite reutilizar elementos, consequentemente tornando nosso código mais simples, através da importação de componentes e personalizando quando necessário. Nosso editor de texto divide a janela em dois; um terço da tela fica dedicada à barra de menu e o restante para a área de texto. A barra de menu é exibida à frente de qualquer outro elemento.
Rectangle{
id: screen
width: 1000; height: 1000
//a tela é dividida entre MenuBar e TextArea. 1/3 fica com MenuBar
property int partition: height/3
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}
Importando componentes reutilizáveis, nosso arquivo TextEditor parece bem mais simples. Nós podemos então modificar a aplicação sem termos que nos preocupar com propriedades e comportamentos que já foram definidos. Usando essa abordagem, o layout e a interface com usuário podem ser criados de uma forma simples.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png
Decorando o Editor de Texto
Implementando uma interface de desenho
Nosso editor de texto parece simples, nós precisamos incrementá-lo. Usando QML, nós podemos declarar transições e animar nosso editor. Nossa barra de menu ocupa um terço da tela e seria bom que ela aparecesse apenas quando nós queremos usá-la.
Podemos adicionar uma interface de desenho, que irá contrair ou expandir a barra de menu quando clicada. Em nossa implementação, nós temos um retângulo fino que responde aos cliques de mouse. A área de desenho, assim como a aplicação, possui dois estados: "área de desenho aberta" e "área de desenho fechada". O item área de desenho é uma retângulo estreito com pequena altura. Dentro dele um elemento "Image":http://doc.qt.nokia.com/4.7/qml-image.html declara que um ícone seta fica centralizado dentro da área de desenho. Esta por sua vez associa um estado à aplicação como um todo através do identificador screen, toda vez que o usuário clicar sobre a MouseArea.
Rectangle{
id:drawer
height:15
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea{
id: drawerMouseArea
anchors.fill:parent
onClicked:{
if (screen.state "DRAWER_CLOSED"){
screen.state = "DRAWER_OPEN"
}
else if (screen.state "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
…
}
}
Um estado é simplesmente uma coleção de configurações e é declarado em um elemento "State":http://doc.qt.nokia.com/4.7/qml-state.html. Uma lista de estados pode ser listada e associada a propriedade states. Em nossa aplicação, os dois estados são chamados DRAWER_CLOSED e DRAWER_OPEN (área de desenho fechada e aberta, respectivamente). As configurações de itens são declaradas em elementos "PropertyChanges":http://doc.qt.nokia.com/4.7/qml-propertychanges.html. no estado DRAWER_OPEN, 4 itens receberão modificações de propriedades. O primeiro alvo, menuBar, mudará sua propriedade y para 0. Similarmente, textArea descerá para uma nova posição quando o estado for DRAWER_OPEN. Os elementos textArea, drawer e o ícone sofrerão alterações para se ajustarem ao estado corrente.
states:[
State {
name: "DRAWER_OPEN"
PropertyChanges { target: menuBar; y: 0}
PropertyChanges { target: textArea; y: partition + drawer.height}
PropertyChanges { target: drawer; y: partition}
PropertyChanges { target: arrowIcon; rotation: 180}
},
State {
name: "DRAWER_CLOSED"
PropertyChanges { target: menuBar; y:-height; }
PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height }
PropertyChanges { target: drawer; y: 0 }
PropertyChanges { target: arrowIcon; rotation: 0 }
}
]
Mudanças de estado são abruptas e precisam de transições suaves. Transições entre estados são definidos usando o elemento de transição "Transition":http://doc.qt.nokia.com/4.7/qml-transition.html, que pode ser associado a propriedade transitions. Nosso editor de texto tem uma transição de estado sempre que ha mudança para DRAWER_OPEN ou DRAWER_CLOSED. Importante observar que a transição precisa de um estado from (origem) e de um estado to (destino), mas para a nossa transição, podemos usar o símbolo coringa * para indicar que a transição se aplica a todas as mudanças de estado.
Durante as transições, podemos atribuir animações às mudanças da propriedade. Nosso menuBar muda de posição de y:0 para y:-partition e podemos animar essaa transição usando o elemento "NumberAnimation":http://doc.qt.nokia.com/4.7/qml-numberanimation.html. Nós declaramos que as propriedades dos alvos serão animadas durante um certo período de tempo usando uma determinada curva de atenuação (easing curve). Uma curva de atenuação controla as taxas de animação e interpolação de comportamento durante as transições de estado. A curva de atenuação que nós escolhemos é "Easing.OutQuint":http://doc.qt.nokia.com/4.7/qml-propertyanimation.html#easing.type-prop, que retarda o movimento perto do fim da animação. Por favor leia o "artigo sobre Animações no QML":http://doc.qt.nokia.com/4.7/qdeclarativeanimation.html.
transitions: [
Transition {
to: "*"
NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
}
]
Outra maneira de animar as alterações de propriedade é declarar um elemento de comportamento: Behavior. Uma transição só funciona durante as mudanças de estado e "Behavior":http://doc.qt.nokia.com/4.7/qml-behavior.html pode definir uma animação para a mudança de uma propriedade geral. No editor de texto, a seta tem uma NumberAnimation animando sua propriedade rotação sempre que essa propriedade sofre alterações.
Em TextEditor.qml:
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
Voltando aos nossos componentes, com o conhecimento dos estados e animações, podemos melhorar a aparência dos componentes. Em Button.qml, podemos adicionar as alterações de propriedades de cor e escala quando o botão é clicado. Tipos de cores são animados usando "ColorAnimation":http://doc.qt.nokia.com/4.7/qml-coloranimation.html e os números são animados usando "NumberAnimation":http://doc.qt.nokia.com/4.7/qml-numberanimation.html. O propertyName na sintaxe mostrada abaixo é útil quando nosso alvo é uma única propriedade.
Em Button.qml:
…
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
Behavior on color { ColorAnimation{ duration: 55} }
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
Além disso, podemos melhorar a aparência dos nossos componentes QML adicionando efeitos de cores, tais como gradientes e efeitos de opacidade. Declarando um elemento "Gradient":http://doc.qt.nokia.com/4.7/qml-gradient.html vai substituir a propriedade de cor do elemento. Você pode declarar uma cor no gradiente usando o elemento "GradientStop":http://doc.qt.nokia.com/4.7/qml-gradientstop.html. O gradiente é posicionado usando uma escala entre 0.0 e 1.0.
Em MenuBar.qml
gradient: Gradient {
GradientStop { position: 0.0; color: "#8C8F8C" }
GradientStop { position: 0.17; color: "#6A6D6A" }
GradientStop { position: 0.98;color: "#3F3F3F" }
GradientStop { position: 1.0; color: "#0e1B20" }
}
Este gradiente é usado pela barra de menu para exibir um gradiente simulando profundidade. A primeira cor começa em 0.0 e que a última cor é de 1.0.
Para onde seguir
Estamos terminando a construção da interface com usuário de um editor de texto muito simples. Indo adiante, a interface do usuário está completa, e nós podemos implementar a lógica do aplicativo usando Qt e C+. QML funciona bem como uma ferramenta de prototipagem, separando a lógica da aplicação do design de interface com usuário.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png
h2. Estendendo QML usando Qt C+
Agora que temos o layout do nosso editor de texto, podemos agora implementar as funcionalidades do editor de texto em C+. Usando QML com C+ nos permite criar a lógica da aplicação usando Qt. Podemos criar um contexto QML em uma aplicação C++ usando as classes "Qt Declarative":http://doc.qt.nokia.com/4.7/qtbinding.html e exibir os elementos QML usando uma QGraphicsScene. Alternativamente, nós podemos exportar o nosso código C++ em um plugin que a ferramenta "qmlviewer":http://doc.qt.nokia.com/4.7/qmlviewer.html consegue ler. Para a nossa aplicação, vamos implementar em C++ as funções de carregar e salvar e exportá-las como um plugin. Desta forma, só precisamos carregar o arquivo QML diretamente em vez de rodar um binário executável.
Expondo classes C++ ao QML
Nós implementaremos carregar e salvar arquivos usando Qt e C+. Classes C+ e funções podem ser utilizadas em QML, bastando registrá-las. A classe também deve ser compilado como um plugin_ Qt e o arquivo QML precisa saber onde o plugin está localizado.
Para a nossa aplicação, precisamos criar os seguintes itens:
- Classe diretório que cuidará de operações relacionados com diretórios
- Classe arquivo que é um "QObject":http://doc.qt.nokia.com/4.7/qobject.html, simulando a lista de arquivos de um diretório
- Classe plugin que registrará a classe no contexto QML
- Arquivo de projeto Qt que compilará o plugin
- Um arquivo qmldir dizendo ao qmlviewer onde encontrar o plugin
Construindo um plugin Qt
Para criar um plugin, precisamos definir o seguinte em um arquivo de projeto Qt. Primeiramente, os arquivos de código-fonte necessários, cabeçalhos e módulos Qt precisam ser adicionados em nosso arquivo de projeto. Todos os arquivos de código C++ e arquivos de projeto estão no diretório filedialog.
In cppPlugins.pro:
TEMPLATE = lib
CONFIG ''= qt plugin
QT''= declarative
DESTDIR ''= ../plugins
OBJECTS_DIR = tmp
MOC_DIR = tmp
TARGET = FileDialog
HEADERS''= directory.h file.h dialogPlugin.h
SOURCES += directory.cpp file.cpp dialogPlugin.cpp
Em particular, nós compilamos Qt com o módulo declarative e o configuramos como um plugin, precisando de um modelo (template) de lib. Vamos colocar o plugin compilado dentro do plugins do diretório pai do atual.
Registrando uma classe no QML
In dialogPlugin.h:
#include <QtDeclarative/QDeclarativeExtensionPlugin>
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT
public:
void registerTypes(const char *uri);
};
Nossa classe plugin, DialogPlugin, é uma subclasse de "QDeclarativeExtensionPlugin":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html. Precisamos implementar a função herdada, "registerTypes()":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#registerTypes. O arquivo dialogPlugin.cpp parece com o seguinte:
DialogPlugin.cpp:
#include "dialogPlugin.h"
#include "directory.h"
#include "file.h"
#include <QtDeclarative/qdeclarative.h>
void DialogPlugin::registerTypes(const char '''uri){
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
qmlRegisterType<File>(uri, 1, 0,"File");
}
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);
A função registerTypes() registra nossas classes File e Directory no QML. Esta função precisa do nome da classe para o seu template, um número de versão principal, um número de versão menor, e um nome para nossas classes. Precisamos exportar o plugin usando a macro "Q_EXPORT_PLUGIN2":http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2. Note que em nosso arquivo dialogPlugin.h, temos a macro "Q_OBJECT":http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT no topo da nossa classe. Assim sendo, precisamos executar qmake no arquivo de projeto para gerar o código de meta-objetos necessários.
h3. Criando propriedades QML em uma classe C++
Podemos criar elementos QML e propriedades usando C++ e o "sistema de meta-objetos do Qt":http://doc.qt.nokia.com/4.7/metaobjects.html. Podemos implementar propriedades usando sinais e slots, fazendo Qt conhecer essas propriedades. Estas propriedades podem ser utilizadas em QML. Para o editor de texto, precisamos salvar e carregar arquivos. Normalmente, esses recursos estão contidos em um diálogo de arquivo. Felizmente, nós podemos usar "QDir":http://doc.qt.nokia.com/4.7/qdir.html, "QFile":http://doc.qt.nokia.com/4.7/qfile.html e "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para implementar a leitura de diretório e dos fluxos de entrada e saída.
class Directory : public QObject{
Q_OBJECT
Q_PROPERTY(int filesCount READ filesCount CONSTANT)
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
…
A classe Directory usa o sistema de meta-objetos do Qt para registrar propriedades que necessita para realizar tratamento de arquivos. A classe Directory é exportada como um plugin e é utilizável em QML como um elemento Directory. Cada propriedades listadas usando a macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY é uma propriedade QML. "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY declara uma propriedade, bem como as suas funções de leitura e escrita no sistema de meta-objetos. Por exemplo, a propriedade filename, do tipo "QString":http://doc.qt.nokia.com/4.7/qstring.html, pode ser lido usando filename() e escrita utilizando a função setFileName(). Além disso, há um sinal associado à propriedade filename chamado filenameChanged(), que é emitido sempre que a propriedade sofre alterações. As funções de leitura e escrita são declaradas como públicas no cabeçalho do arquivo. Da mesma forma, temos as outras propriedades declaradas de acordo com seus usos. A propriedade filesCount indica o número de arquivos em um diretório. A propriedade filenameé definida para o nome do arquivo selecionado atualmente e o conteúdo carregado/salvo é armazenado na propriedade fileContent.
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
A propriedade da lista de arquivos (files) é uma lista de todos os arquivos filtrados em um diretório. A classe Directory é implementada para filtrar os arquivos de texto inválidos; apenas arquivos com extensão .txt são válidos. Além disso, "QList":http://doc.qt.nokia.com/4.7/qlist.htmls podem ser usadas em arquivos QML declarando-as como "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html em C+. O objeto da lista precisa herdar de um "QObject":http://doc.qt.nokia.com/4.7/qobject.html, portanto, a classe File também deve herdar "QObject":http://doc.qt.nokia.com/4.7/qobject.html. Na classe Directory, a lista de objetos File é armazenada em uma "QList":http://doc.qt.nokia.com/4.7/qlist.html chamada m_fileList.
class File : public QObject{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
…
};
As propriedades podem ser usadas em QML como parte de uma propriedade do elemento Directory. Note que não temos que criar uma propriedade identificadora id em nosso código C.
Directory{
id: directory
filesCount
filename
fileContent
files
files[0].name
}
Como QML usa a sintaxe e estrutura de Javascript, podemos percorrer a lista de arquivos e recuperar suas propriedades. Para recuperar a propriedade do arquivo do primeiro arquivo, podemos chamar files[0].name. Funções regulares de C+ também são acessíveis no QML. As funções para carregar e salvar arquivos são implementadas em C++ e declaradas usando a macro "Q_INVOKABLE":http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE. Alternativamente, podemos declarar as funções como slots que serão acessíveis a partir do QML.
Em Directory.h:
Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
A classe Directory também tem de notificar outros objetos quando o conteúdo do diretório for alterado. conseguimos isso utilizado um sinal. Como mencionado anteriormente, os sinais QML tem um manipulador correspondente com os seus nomes prefixados com on. O sinal é chamado directoryChanged e é emitido sempre que houver uma atualização de diretório. A atualização simplesmente recarrega o conteúdo do diretório e atualiza a lista de arquivos válidos no diretório. Itens QML podem ser notificados anexando uma ação para o manipulador de sinal onDirectoryChanged. A lista de propriedades precisam ser exploradas mais a fundo. Isso acontece pois as propriedades lista usam callbacks para acessar e modificar o conteúdo da lista. A propriedade lista é do tipo QDeclarativeListProperty<File>. Sempre que a lista é acessada, a função de acesso precisa retornar uma QDeclarativeListProperty<File>. O tipo do modelo (template), arquivo, precisa ser derivado QObject. Além disso, para criar uma "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html, o método de acesso e modificadores da lista precisam ser passados para o consructor como ponteiros de função. A lista, um QList no nosso caso, também precisa ser uma lista de File.
O construtor de "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html e a implementação de Directory:
QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );
O construtor passa ponteiros para funções que irã acrescentar à lista, contar a lista, recuperar o item usando um índice e esvaziar a lista. Apenas a função de acréscimo é obrigatória. Observe que os ponteiros de função devem coincidir com as definições de "AppendFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AppendFunction-typedef, "CountFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#CountFunction-typedef, "AtFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AtFunction-typedef ou "ClearFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#ClearFunction-typedef.
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *property)
Para simplificar o nosso diálogo de arquivo, a classe Directory filtra arquivos de texto inválidos, que são arquivos que não tem um extensão .txt. Se um arquivo não tem a extensão .txt, então não vai aparecer nosso diálogo de arquivo. Além disso, a implementação garante que os arquivos salvos tem uma extensão txt. Directory usa "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para ler o arquivo e também para gravar a saída do conteúdo em um arquivo. Com o nosso elemento Directory, podemos recuperar os arquivos como uma lista, saber quantos arquivos de texto estão no diretório do aplicativo, obter o nome do arquivo e o conteúdo como uma string, e ser notificado sempre que houver alterações no conteúdo do diretório. Para criar o plugin, execute qmake no arquivo de projeto cppPlugins.pro e em seguida, execute make para compilar e transferir o plugin para o diretório de plugins.
Importando um plugin no QML
A ferramenta qmlviewer importa arquivos que estão no mesmo diretório da aplicação. Podemos também criar um arquivo qmldir contendo os locais dos arquivos QML que desejamos importar. O arquivo qmldir também pode guardar os locais de plugins e outros recursos.
Em qmldir:
Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml
plugin FileDialog plugins
O plugin que acabamos de criar é chamado FileDialog, conforme indicado pelo campo TARGET no arquivo de projeto. O plugin é compilado no diretório de plugins.
Integrando um Diálogo de Arquivos ao Menu Arquivo
Nosso FileMenu precisa exibir o elemento FileDialog, contendo uma lista de arquivos de texto em um diretório, permitindo assim que o usuário selecione o arquivo, clicando na lista. Precisamos também atribuir salvar, carregar e novos botões para suas respectivas ações. O FileMenu contém uma entrada de texto editável para permitir que o usuário digite um nome de arquivo usando o teclado. O elemento Directory é usado no arquivo FileMenu.qml e notifica o elemento FileDialog que o diretório atualizou seu conteúdo. Esta notificação é realizada em um manipulador de sinal, onDirectoryChanged.
Em FileMenu.qml:
Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
Mantendo a simplicidade da nossa aplicação, o diálogo de arquivos será sempre visível e não mostrará arquivos inválidos, que não tenha uma extensão .txt,
Em FileDialog.qml:
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
O elemento FileDialog exibirá o conteúdo de um diretório lendo sua propriedade lista chamada de files. Os arquivos são usados como modelo de um elemento "GridView":http://doc.qt.nokia.com/4.7/qml-gridview.html, que exibe itens de dados em uma grade de acordo com um delegate. O delegate lida com a aparência do modelo e nosso diálogo de arquivo simplesmente criará uma grade com texto centralizado no meio. Clicando no nome do arquivo resulta no aparecimento de um retângulo para destacar o nome do arquivo. O FileDialog é notificado sempre que o sinal notifyRefresh é emitido, recarregando os arquivos no diretório.
In FileMenu.qml:
Button{
id: newButton
label: "New"
onButtonClick:{
textArea.textContent = ""
}
}
Button{
id: loadButton
label: "Load"
onButtonClick:{
directory.filename = textInput.text
directory.loadFile();
textArea.textContent = directory.fileContent
}
}
Button{
id: saveButton
label: "Save"
onButtonClick:{
directory.fileContent = textArea.textContent
directory.filename = textInput.text
directory.saveFile();
}
}
Button{
id: exitButton
label: "Exit"
onButtonClick:{
Qt.quit()
}
}
Nosso FileMenu agora pode se conectar às suas respectivas ações. O saveButton transferirá o texto do TextEdit para a propriedade fileContent do diretório e então copiar o seu nome de arquivo da entrada de texto editável. Finalmente, o botão chama a função saveFile();, salvando o arquivo. O sloadButton tem execução semelhante. Também, a ação New esvazia o conteúdo do TextEdit. Além disso, os botões EditMenu estão ligados às funções d TextEdit para copiar, colar e selecionar todo o texto no editor de texto.
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png
Concluindo o Editor de Texto
p=. http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png