Getting Started Programming with QML/es-AR

From Qt Wiki
Jump to navigation Jump to search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

Comenzando a Programar con QML

Bienvenido al mundo de QML, el lenguaje declarativo para Interfaces de Usuario (IU). En esta guía Comenzar a Programar, crearemos una simple aplicación para editar textos usando QML. Luego de leer esta guía, deberías estar listo para desarrollar tus propias aplicaciones usando QML y Qt C+.

QML para Armar Interfaces de Usuario

La aplicación que estamos armando es un simple editor de texto que cargará, guardará y realizará alguna manipulación de texto. Esta guía consiste de dos partes. La primera parte involucrará desarrollar el diseño de la aplicación y los comportamientos usando lenguaje declarativo en QML. Para la segunda parte, cargar y guardar el archivo serán implementados usando Qt C. Al usar el Sistema de Meta-Objetos de Qt, podemos exponer funciones de C+ como propiedades que los elementos de QML pueden usar. Usando QML y Qt C++ podemos desacoplar de manera eficiente la lógica de la interface de la lógica de la aplicación.

qml-texteditor5_editmenu.png

Para ejecutar el código QML de ejemplo, simplemente provee a la herramienta incluida qmlviewer el nombre del archivo QML como argumento. La parte de C++ de este tutorial asume que el lector posee conocimiento básico de los procedimientos de compilación de Qt.

Capítulos del Tutorial:

  1. Definiendo un Botón y un Menú
  2. Implementando una Barra de Menú
  3. Armando un Editor de Texto
  4. Decorando un Editor de Texto
  5. Extendiendo QML al usar Qt C++

Definiendo un Botón y un Menú

Componente Básico - un Botón

Comenzamos nuestro editor de texto armando un botón. Funcionalmente un butón tiene un área sensible al ratón (mousse) y una etiqueta. Los botones realizan acciones cuando un usuario presiona el botón.

En QML, el ítem visual básico es el elemento Rectangle. El elemento Rectangle tiene propiedades para controlar la apariencia del elemento y su ubicación.

Primero, la instrucción import Qt 4.7 permite que la herramienta qmlviewer importe los elementos que usaremos luego. Esta línea debe existir en cada archivo QML. Tener presente que la versión de los módulos Qt se incluye en la instrucción import.

Este simple rectángulo tiene un identificador unívoco, simplebutton, el cual está ligado a la propiedad id. Las propiedades del elemento Rectangle están ligadas a valores al listar la propiedad, seguidos por dos puntos, luego el valor. En el código de ejemplo, el color gris está ligado a la propiedad color del elemento Rectangle. De manera similar, ligamos el ancho y alto del elemento Rectangle.

El elemento Text es un campo de texto de sólo lectura (no editable). Nombramos a este elemento Text buttonLabel. Para establecer el contenido de la cadena de caracteres de este campo Texto, ligamos un valor a la propiedad text. La etiqueta está contenida dentro del elemento Rectangle y para centrarla, asignamos las anclas del elemento Text a su padre, el cual se llama simplebutton. Las anclas pueden ligarse a las anclas de otros ítems, de esa forma facilitando las asignaciones de diseño.

Guardamos este código como SimpleButton.qml. Ejecutando qmlviewer con el nombre del archivo como argumento mostrará el rectángulo gris con la etiqueta de texto.

qml-texteditor1_simplebutton.png

Para implementar la funcionalidad del click del botón, podemos usar el manejo de eventos de QML. El manejo de eventos de QML es muy similar al mecanismo de señales y ranuras de Qt. Se emiten señales y las ranuras conectadas son invocadas.

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" )
 }
}

Incluimos un elemento MouseArea (área del ratón) n nuestro simplebutton. Los elementos MouseArea describen el área interactiva donde se detectan los movimientos del ratón. Para nuestro botón, anclamos el elemento MouseArea completo a su padre, el cual es simplebutton. La sintaxis anchors.fill es una manera de acceder a una propiedad específica llamada fill dentro de un grupo de propiedades llamado anchors. QML usa diseños basados en anclas donde los ítems pueden anclarse unos a otros, creando diseños robustos.

El elemento MouseArea tiene muchos manejadores de señales que se llaman durante los movimientos del ratón dentro de los límites especificados para el elemento MouseArea. Uno de ellos es onClicked y se llama cada vez que el botón apropiado del ratón se pulsa, siendo el izquierdo el valor por omisión. Podemos ligar acciones al manipulador onClicked. En nuestro ejemplo, console.log() muestra texto siempre que se hace click en el área del ratón. La función console.log() es una herramienta útil para propósitos de depuración (debugging) y para mostrar texto.

El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla y emitir texto cuando se selecciona mediante un click con el ratón.

 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
 }

//determines the color of the button by using the conditional operator
 color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
 }

Un botón completamente funcional está en Button.qml. Los fragmentos de código en este artículo tienen partes del código omitidas, denotadas por puntos suspensivos porque ellas fueron ya sea presentadas en secciones previas o no son relevantes para la discusión actual del código.

Las propiedades Custom se declaran usando la sintaxis property type. En el código, la propiedad buttonColor, de tipo color, se declara y liga al valor "lightblue" (celeste). buttonColor se usa más adelante en una operación condicional para determinar el color de relleno del botón. Notar que la asignación de valor a la propiedad es posible usando el signo = (igual), además de ligar el valor usando el caracter ':' (dos puntos). Las propiedades Custom permiten acceder a ítems internos fuera del alcance del elemento Rectangle. Hay tipos básicos de QML tales como int, string, real, como también un tipo llamado variant.

Al vincular los manejadores de señales onEntered y onExited a colores, el borde del botón se volverá amarillo cuando el mouse pase sobre el botón y volverá al otro color cuando el ratón salga del área del ratón.

Una señal buttonClick() se declara en Button.qml al colocar la palabra clave signal delante del nombre de la señal. Todas las señales tienen sus manejadores creados automáticamente, con sus nombres comenzando con el prefijo on. Como resultado, onButtonClick es el manejador de buttonClick. Luego onButtonClick se asigna a una acción para que sea llevada a cabo. En nuestro botón de ejemplo, el manejador onClicked del ratón simplemente invocará onButtonClick, el cual muestra un texto. onButtonClick permite que objetos externos accedan al área del ratón del elemento Button de manera sencilla. Por ejemplo, los ítems pueden tener más de una declaración MouseArea y una señal buttonClick puede hacer una mejor distinción entre los distintos manejadores de señal para MouseArea.

Ahora tenemos el conocimiento básico para implementar ítems en QML que pueden manejar movimientos básicos del ratón. Creamos una etiqueta Text dentro de un elemento Rectangle, ajustamos sus propiedades, e implementamos comportamientos que responden a movimientos del ratón. Esta idea de crear elementos dentro de elementos se repite a través de la aplicación del editor de texto.

Este botón no es útil a menos que se use como un componente para realizar una acción. En la próxima sección, pronto crearemos un menú conteniendo varios de estos botones.

qml-texteditor1_button.png

Creando una Página de Menú

Hasta este momento, hemos cubierto como crear elementos y asignar comportamientos dentro de un único archivo QML. En esta sección, cubriremos ccomo importar elementos QML y como reusar algunos de los componentes creados para armar otros componentes.

Los menúes muestran el contenido de una lista, con cada ítem teniendo la capacidad de realizar una acción. En QML, podemos crear un menú de varias formas. Primero, crearemos un menú que contiene botones los cuales eventualmente realizarán diferentes acciones. El código del menú está en FileMenu.qml.

import Qt 4.7 import the main Qt QML module
import "folderName" import the contents of the folder
import "script.js" as Script import a Javascript file and name it as Script

La sintaxis arriba muestra como usar la palabra reservada import. Esto se requiere para usar archivos JavaScript, o archivos QML que no están en el mismo directorio. Ya que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para usarlo. Directamente podemos crear un elemento Button declarando Button{}, de manera similar a la declaración Rectangle{}.

 In 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()
 }
 }

En FileMenu.qml, declaramos tres elementos Button. Se declaran dentro de un elemeto Row, un posicionador que ubicará sus hijos en una fila vertical. La declaración Button permanece en Button.qml, que es el mismo que el archivo Button.qml que usamos previamente. Pueden declararse nuevas vinculaciones a propiedades dentro de los nuevos botones creados, sobreescribiendo efectivamente las propiedades establecidas en Button.qml. El botón exitButton terminará y cerrará la ventana cuando sea seleccionado. Notar que el manejador de señal onButtonClick en Button.qml será llamado además del manejador onButtonClick en exitButton.

qml-texteditor1_filemenu.png

La declaración Row se realiza en un elemento Rectangle, creando un rectágulo contenedor para la fila de botones. Este rectángulo adicional crea una manera indirecta de organizar la fila de botones dentro de un menú.

La declaración del menu editar es muy similar a esta etapa. El menú tiene botones con las etiquetas: Copiar, Pegar y Seleccionar Todo.

qml-texteditor1_editmenu.png

Armados con nuestro conocimiento de importar y customizing componentes creados previamente, ahora podemos combinar estas páginas de menu para crear una barra de menú, que consiste de botones para seleccionar el menú, y mirar como podemos estructurar los datos usando QML.

Implementando una Barra de Menú

Nuestra aplicación de editor de texto necesitará una manera de mostrar los menúes usando una barra de menú. La barra de menú cambiará los diferentes menues y el usuario puede elegir qué menú mostrar. El cambio de menúes implica que los menúes necesitan más estructura que meramente mostrarlos en una fila. QML usa modelos y vistas para estructurar los datos y mostrar los datos estructurados.

Usando Modelos de Datos y Vistas

QML tiene diferentes vistas de datos que muestran modelos de datos. Nuestra barra de menú mostrará los menúes en una lista, con un encabezado que muestra una fila de los nombres de los menúes. La lista de menúes se declaran dentro de un elemento VisualItemModel. El elemento VisualItemModel contiene ítems que ya tienen vistas tales como elementos Rectangle y elementos importados de la IU. Otros tipos de modelos tales como el elemento ListModel necesitan un delegado para mostrar sus datos.

Declaramos dos ítems visuales en el elemento menuListModel, el ítem FileMenu y el ítem EditMenu. Personalizamos los dos menúes y los mostramos usando un elemento ListView. El archivo MenuBar.qml file contiene las declaraciones QML y un menú simple para editar se define en el archivo EditMenu.qml.

VisualItemModel{
 id: menuListModel
 FileMenu{
 width: menuListView.width
 height: menuBar.height
 color: fileColor
 }
 EditMenu{
 color: editColor
 width: menuListView.width
 height: menuBar.height
 }
 }

El elemento ListView mostrará un modelo de acuerdo a un delegado. El delegado puede declarar los ítems del modelo a mostrar en un elemento Row o mostrar los ítems en una grilla. Nuestro menuListModel ya tiene ítems visible, y de ese modo, no necesitamos declarar un delegado.

ListView{
 id: menuListView

//Anchors are set to react to window anchors
 anchors.fill:parent
 anchors.bottom: parent.bottom
 width:parent.width
 height: parent.height

//the model contains the data
 model: menuListModel

//control the movement of the menu switching
 snapMode: ListView.SnapOneItem
 orientation: ListView.Horizontal
 boundsBehavior: Flickable.StopAtBounds
 flickDeceleration: 5000
 highlightFollowsCurrentItem: true
 highlightMoveDuration:240
 highlightRangeMode: ListView.StrictlyEnforceRange
 }

Adicionalmente, ListView hereda de Flickable, haciendo que la lista responda a arrastres del ratón y otros gestos. La última parte del código arriba ajustas propiedades del elemento Flickable para crear en nuestra vista el movimiento de parpadeo deseado. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de parpadeo. Un valor highlightMoveDuration mayor resulta en un cambio de menú más lento.

El elemento ListView mantiene los ítems del modelo a través de un índice y cada ítem visual en el modelo es accesible a través del índice, en el orden de declaración. Cambiar el valor de currentIndex efectivamente cambia el ítem resaltado en el elemento ListView. El encabezado de nuestra barra de menú ejemplifica este efecto. Hay dos botones en una fila, ambos cambiando el menú actual cuando son seleccionados. El elemento fileButton cambia el menú actual al menú archivo cuando se selecciona, y el índice es 0 porque FileMenu se declaró primero en menuListModel. De manera similar, editButton cambiará el menú actual a EditMenu cuando se seleccione.

El rectángulo labelList tiene un valor z de 1, denotando que se muestra al frente de la barra de menú. Ítems con valores z mayores se muestran al frente de ítems con valores z menores. El valor de z por omisión es 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
 }
 }
 }

La barra de menú que hemos creado puede tener parpadeo para acceder a los menúes o al hacer click sobre los nombres del menú en la parte superior. El cambio de pantallas de menúes se siente intuitivo y con respuesta.

qml-texteditor2_menubar.png

Armando un Editor de Texto

Declarando un elemento TextArea

Nuestro editor de texto no es un editor de texto si no contiene un área de texto que se pueda editar. El elemento TextEdit de QML permite la declaración de un área de texto editable multi-línea. TextEdit es diferente de un elemento Text, el cual no permite que el usuario edite directamente el texto.

 TextEdit{
 id: textEditor
 anchors.fill:parent
 width:parent.width; height:parent.height
 color:"midnightblue"
 focus: true

wrapMode: TextEdit.Wrap

onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
 }

El editor tiene su propiedad color de fuente establecida y también para wrap el texto. El área TextEdit está dentro de un área parpadeante que se desplazará si el cursor de texto está fuera del área visible. La función ensureVisible() comprobará si el rectángulo del cursor está fuera de los límites visibles y moverá el área de texto de manera apropiada. QML usa sintaxis de Javascript para sus secuencias de comandos (scripts), y como se mencionó anteriormente, pueden importarse archivos Javascript y ser usados dentro de un archivo 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 el Editor de Texto

Estamos listos para crear el diseño de nuestro editor de texto usando QML. El editor de texto tiene dos componentes, la barra de menú que creamos y el área de texto. QML nos permite reusar componentes, y así, hacer nuestro código más simple, al importar componentes y personalizarlos cuando sea necesario. Nuestro editor de texto divide la ventana en dos; un tercio de la pantalla se dedica a la barra de menú y dos tercios de la pantalla muestran el área de texto. La barra de menú se muestra en frente de cualquier otro elemento.

 Rectangle{

id: screen
 width: 1000; height: 1000

//the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the 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
 }
 }

Al importar componentes reutilizables, nuestro código TextEditor luce mucho más simple. Podemos entonces personalizar la aplicación principal, sin preocuparnos acerca de las propiedades que ya han definido comportamientos. Usando esta aproximación, los diseños de aplicaciones y componentes de IU se pueden crear fácilmente.

qml-texteditor3_texteditor.png

Decorando el Editor de Texto

Implementando una Interface Cajón

Nuestro editor de texto luce simple y necesitamos decorarlo. Usando QML, podemos declarar transiciones y animar nuestro editor de texto. Nuestra barra de menú está ocupando un tercio de la pantalla y sería bueno hacerla aparecer cuando querramos.

Podemos agregar una interface cajón, que contraerá o expandirá la barra de menú cuando se haga click sobre ella. En nuestra implementación, tenemos un rectángulo delgado que responde a los clicks del ratón. El cajón, como también la aplicación, tienen dos estados: el estado "cajón está abierto" y el estado "cajón está cerrado". El ítem cajón es una tira de un rectángulo con una altura pequeña. Hay un elemento Image anidado que declara que un ícono de una flecha será centrado dentro del cajón. El cajón asigna un estado a toda la aplicación, con el identificador screen, siempre que un usuario hace click en el área del ratón.

 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"
 }
 }
 
 }
 }

Un estado es simplemente una colección de configuraciones y se declara en un elemento State. Una lista de estdos pueden listarse y asociarse a la propiedad estados. En nuestra aplicación, los dos estados se llaman DRAWER_CLOSED y DRAWER_OPEN. Los ítems de as configuraciones se declaran en elementos PropertyChanges. En el estado DRAWER_OPEN, hay cuatro ítems que recibirán cambios de propiedades. El primer destino, menuBar, cambiará su propiedad y a 0. De manera similar, textArea bajará a una nueva posición cuando el estado es DRAWER_OPEN. textArea, el cajón, y el ícono del cajón sufrirán cambios de propiedades para ajustarse al estado actual.

 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 }
 }
 ]

Los cambios de estado son abruptos y necesitamos transiciones más suaves. Las transiciones entre estados se definen usando el elemento Transition, el cual puede luego asociarse a la propiedad transitions del ítem. Nuestro editor de texto tiene una transición de estado siempre que los estados cambian tanto a DRAWER_OPEN o a DRAWER_CLOSED. De manera importante, la transición necesita un estado desde y un estado hacia pero para nuestras transiciones, podemos usar el símbolo comodín * para denotar que la transición se aplica a todos los cambios de estado.

Durante las transiciones, podemos asignar animaciones a la propiedad changes. Nuestra menuBar cambia la posición desde y:0 a y:-partition y podemos animar esta transición usando el elemento NumberAnimation. Declaramos que las propiedades de los destinos se animarán por un cierto período de tiempo y usamos una cierta curva de aceleración. Una curva de aceleración controla los ritmos de animación y el comportamiento de la interpolación durante las transiciones de estados. La curva de aceleración que elegimos es Easing.OutQuint, la cual enlentece el movimiento cerca del final de la animación. Por favor lee el artículo sobre Animación en QML.

 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 }
 }
 ]

Otra manera de animar cambios de propiedades es declarando un elemento Behavior. Una transición solo trabaja durante cambios de estado y Behavior puede establecer una animación para un cambio de una propiedad general. En el editor de texto, la flecha tiene un elemento NumberAnimation que anima su propiedad rotation siempre que la propiedad cambia.

 In TextEditor.qml:

Behavior{
 NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
 }

Volviendo a nuestros componentes con conocimiento de estados y animaciones, podemos mejorar el aspecto de los componentes. En Button.qml, podemos agregar cambios a las propiedades color y scale cuando se hace click en el botón. Los tipos de colores se animan usando ColorAnimation y los números se animar usando NumberAnimation. La sintaxis on propertyName mostrada debajo es útil cuando se enfoca una única propiedad.

 In 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} }

Adicionalmente, podemos mejorar el aspecto de nuestros componentes QML al agregar efectos de color tales como gradientes y efectos de opaquez. Declarar un elemento Gradient anula la propiedad color del elemento. Puedes declarar un color en el gradiente usando el elemento GradientStop. El gradiente se posiciona usando una escala, entre 0.0 y 1.0.

 In 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 es usado por la barra de menú para mostrar un gradiente simulando profundidad. El primer color comienza en 0.0 y el último color está en 1.0.

A dónde ir desde Aquí

Terminamos de armar la interface de usuario de un editor de textos muy simple. De aquí en adelante, la interfase de usuario está completa y podemos implementar la lógica de la aplicación usando Qt y C++ normal. QML trabaja muy bien como herramienta de prototipado, separando la lógica de la aplicación del diseño de la IU.

qml-texteditor4_texteditor.png

Extendiendo QML al usar Qt C++

Ahora que tenemos el diseño de nuestro editor de texto, podemos implementar las funcionalidades del editor de texto en C+. Usar QML con C+ nos permite crear la lógica de nuestra aplicación usando Qt. Podemos crear un contexto QML en una aplicación C++ usando las clases Declarative de Qt y mostramos los elementos QML usando Graphics Scene. De manera alternativa, podemos exportar nuestro código C++ en un complemento (plugin) que la herramienta qmlviewer pueda leer. Para nuestra aplicación, implementaremos las funciones cargar y guardar en C++ y las exportaremos como un complemento. De esta forma, solo necesitaremos cargar el archivo QML directamente en lugar de correr un ejecutable.

Exponiendo Clases C++ a QML

Estaremos implementando la carga y guardado del archivo usando Qt y C+. Las clases y funciones C+ pueden usarse en QML registrándolas. La clase también necesita compilarse como un complemento Qt y el archivo QML necesita saber donde se encuentra el complemento.

Para nuestra aplicación, necesitamos crear los siguientes ítems:

  1. Una clase Directory que manejará las operaciones relacionadas con directorios
  2. Una clase File que es un QObject, simulando la lista de archivos en un directorio
  3. Una clase plugin que registrará la clase en el contexto QML
  4. Un proyecto Qt que compilará el complemento
  5. Un archivo qmldir para indicarle a la herramienta qmlviewer donde está el complemento

Construyendo un complemento Qt

Para crear un complemento, necesitamos indicar los siguiente en un archivo de proyecto Qt. Primero, necesitamos agregar los fuentes, encabezados y módulos Qt a nuestro proyecto. Todo el código C++ y los archivos de proyecto están en el directorio 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

En particular, compilamos Qt con el módulo declarativo y lo configuramos como un complemento, necesitando una plantilla lib. Pondremos el complemento compilado en el directorio padre de complementos.

Registrando una Clase en QML

 In dialogPlugin.h:

#include <QDeclarativeExtensionPlugin>

class DialogPlugin : public QDeclarativeExtensionPlugin
 {
 Q_OBJECT

public:
 void registerTypes(const char *uri);

};

Nuestra clase complemento, DialogPlugin es una subclase de QDeclarativeExtensionPlugin. Necesitamos implementar la función heredada, registerTypes(). El archivo dialogPlugin.cpp se parece a esto:

 DialogPlugin.cpp:

#include "dialogPlugin.h"
 #include "directory.h"
 #include "file.h"
 #include <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);

La función registerTypes() registra nuestras clase File y Directory en QML. Esta función necesita el nombre de la clase para su plantilla, un número de versión principal, un número de versión menor y un nombre para nuestras clases.

Necesitamos exportar el complemento usando la macro Q_EXPORT_PLUGIN2 macro. Notar que en nuestro archivo dialogPlugin.h, tenemos la macro Q_OBJECT al comienzo de nuestra clase. Además, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario.

Creando Propiedades QML en una clase C++

Podemos crear elementos y propiedades QML usando el Sistema de Meta-Objetos de Qt. Podemos implementar propiedades usando señales y ranuras, haciendo que Qt esté al tanto de estas propiedades. Estas propiedades pueden luego ser usadas en QML.

Para el editor de texto, necesitamos ser capaces de cargar y guardar archivos. For the text editor, we need to be able to load and save files. Típicamente, estas funciones están contenidas en un diálogo de archivo. Afortunadamente, podemos usar QDir, QFile, y QTextStream para implementar lectura de directorios y streams de entrada/salida.

 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 )

 

La clase directorio usa el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para llevar a cabo el manejo de archivos. La clase Directory se exporta como un complemento y está para ser usada en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro Q_PROPERTY es una propiedad QML.

La macro Q_PROPERTY declara una propiedad como también sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename property, de tipo QString, se puede leer usando la función filename() y se escribe usando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite siempre que la propiedad cambia. Las funciones de lectura y escritura se declaran como públicas en el archivo de encabezado.

De manera similar, tenemos las otras propiedades declaradas de acuerdo a sus usos. La propiedad filesCount indica el número de archivos en un directorio. La propiedad filename property se establece con el nombre del archivo seleccionado actualmente y el contenido del archivo guardado/cargado se almacena en la propiedad fileContent.

 Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

La propiedad lista de archivos en una lista de todos los archivos filtrados en un directorio. La clase Directory se implementa para filtrar archivos de texto no válidos; solo son válidos archivos con una extensión .txt. Más aún, QLists pueden usarse en archivos QML al declararlas como QDeclarativeListProperty en C+. El objeto a manera de plantilla necesita heredar de QObject, de este modo, la clase File también debe heredar de QObject. En la clase Directory, la lista de objetos File se almacena en una QList llamada m_fileList.

 class File : public QObject{

 Q_OBJECT
 Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

 
 };

Las propiedades pueden luego usarse en QML como parte de las propiedades del elemento Directory. Notar que no tenemos que crear una propiedad identificador id en nuestro código C.

 Directory{
 id: directory

 filesCount
 filename
 fileContent
 files

 files[0].name
 }

Como QML usa sintaxis y estructura de Javascript, podemos iterar a través de la lista de archivos y recuperar sus propiedades. Para recuperar la propiedad nombre del primer archivo, podemos invocar files[0].name.

Las funciones normales de C+ son accesibles también desde QML. Las funciones para cargar y guardar un archvio se implementan en C++ y se declaran usando la macro Q_INVOKABLE. De manera alternativa, podemos declarar las funciones como una ranura y las funciones serán accesibles desde QML.

 In Directory.h:

 Q_INVOKABLE void saveFile();
 Q_INVOKABLE void loadFile();

La clase Directory tiene que notificar a otros objetos siempre que el contenido del directorio cambia. Esta función se realiza usando una señal. Como se mencionó previamente, las señales de QML tienen un manejador correspondiente con sus nombres precedidos por el prefijo on. La señal se llama directoryChanged y se emite siempre que hay un refresco en un directorio. El refresco simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los ítems de QML pueden luego ser notificados asignando una acción al manejador de señal onDirectoryChanged.

Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista usan callbacks para acceder y modificar los contenidos de la lista. La propiedad lista es de tipo QDeclarativeListProperty<File>. Siempre que se accede a la lista, la función de acceso necesita devolver QDeclarativeListProperty<File>. El tipo plantilla, File, necesita ser un derivado de QObject. Más aún, para crear QDeclarativeListProperty, la función para acceder a la lista y las modificadores necesitan pasarse al constructor como punteros a funciones. La lista, una QList en nuestro caso, también necesita ser una lista de punteros File.

El constructor de QDeclarativeListProperty y la implementación 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 );

El constructor pasa punteros a funciones que agregarán a la lista, cuentan la lista, recuperan un ítem usando un índice y vacían la lista. Solo la función de agregar es obligatoria. Notar que los punteros a funciones deben coincidir con la definición de AppendFunction, CountFunction, AtFunction, o ClearFunction.

 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 nuestro diálogo de archivo, la clase Directory filtra los archivos de texto no válidos, los cuales son archivos que no tienen una extensión .txt. Si un nombre de archivo no tiene la extensión .txt, entonces el archivo no aparecerá en nuestro diálogo de archivo. También, la implementación se asegura que los archivos guardados tengan una extensión .txt en el nombre del archivo. Directory usa QTextStream para leer el archivo y para sacar el contenido del archivo a un archivo.

Con nuestro elemento Directory, podemos recuperar los archivos como una lista, sabiendo cuantos archivos de texto hay en el directorio de la aplicación, obtener el nombre del archivo y su contenido como una cadena de caracteres, y ser notificados siempre que hay cambios en el contenido del directorio.

Para compilar el complemento, ejecutamos qmake sobre el archivo de proyecto cppPlugins.pro, luego ejecutamos make para compilar y transferir el complemento al directorio plugins.

Importando un Complemento en QML

La herramienta qmlviewer importa archivos que están en el mismo directorio de la aplicación. También podemos crear un archivo qmldir que contengan las ubicaciones de los archivos QML que queremos importar. El archivo qmldir puede almacenar también las ubicaciones de complementos y otros recursos.

 In qmldir:

Button ./Button.qml
 FileDialog ./FileDialog.qml
 TextArea ./TextArea.qml
 TextEditor ./TextEditor.qml
 EditMenu ./EditMenu.qml

plugin FileDialog plugins

El complemento que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El componente compilado está en el directorio plugins.

Integrando un Diálogo Archivo en el Menú Archivo

Nuestro FileMenu necesita mostrar el elemento FileDialog, que contiene una lista de archivos de texto en un directorio de esa forma permitiendo al usuario seleccionar el archivo al hacer click en la lista. También necesitamos asignar los botones guardar, abrir y nuevo a sus respectivas acciones. FileMenu contiene un campo de texto de entrada editable que permite al usuario tipear el nombre de un archivo usando el teclado.

El elemento Directory se usa en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio refrescó su contenido. Esta notificación se realiza en el manejador de señal, onDirectoryChanged.

 In FileMenu.qml:

Directory{
 id:directory
 filename: textInput.text
 onDirectoryChanged: fileDialog.notifyRefresh()
 }

Para mantener la simplicidad de nuestra aplicación, el diálogo de archivo siempre estará visible y no mostrará nombres de archivos no válidos, los cuales no tienen una extensión .txt en sus nombres.

 In FileDialog.qml:

signal notifyRefresh()
 onNotifyRefresh: dirView.model = directory.files

El elemento FileDialog mostrará el contenido de un directorio al leer su propiedad tipo lista llamados archivos. Los archivos se usan como el modelo de un elemento GridView, el cual muestra ítems de datos en una grilla de acuerdo a un delegado. El delegado maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una grilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la apariencia del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica siempre que se emite la señal notifyRefresh, recargando los archivos en el directorio.

 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()
 }
 }

Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El objeto saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y copia su nombre de archivo de la entrada de texto editable. Finalmente, el botón llama a la función saveFile(), guardando el archivo. La función sloadButton tiene una ejecución similar. También, la acción New vacía el contenido del elemento TextEdit.

Más aún, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto.

qml-texteditor5_filemenu.png

Finalización del Editor de Texto

qml-texteditor5_newfile.png

La aplicación puede funcionar como un simple editor de texto, capaz de aceptar texto y guardar el texto en un archivo. El editor de texto puede también cargar desde un archivo y realizar manipulación de texto.