GettingStartedQML Spanish: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 4: Line 4:


Bienvenido al mundo de QML, el lenguaje declarativo de Interfaces de Usuario (UI). En esta guía de introducción, crearemos una simple aplicación de editor de texto utilizando QML. Después de leer esta guía, usted debería estar listo para desarrollar sus propias aplicaciones utilizando QML y Qt C+''.
Bienvenido al mundo de QML, el lenguaje declarativo de Interfaces de Usuario (UI). En esta guía de introducción, crearemos una simple aplicación de editor de texto utilizando QML. Después de leer esta guía, usted debería estar listo para desarrollar sus propias aplicaciones utilizando QML y Qt C+''.
<br />h2. QML para crear interfaces de usuario
 
<br />La aplicación que estamos construyendo es un editor de texto simple que va a cargar, guardar y realizar alguna manipulación de texto. Esta guía constará de dos partes. La primera parte consistirá en diseñar la interfaz de la aplicación y sus comportamientos (behaviors) utilizando lenguaje declarativo en QML. En la segunda parte, agregaremos funcionalidad para cargar y guardar un archivo mediante Qt C. Usaremos Meta-Object System de Qt para exponer funciones C''+ como propiedades para que puedan ser usadas por elementos QML. Al utilizar QML y Qt C++ podemos desacoplar eficientemente la lógica de la interfaz de usuario, de la lógica de la aplicación.
h2. QML para crear interfaces de usuario
 
La aplicación que estamos construyendo es un editor de texto simple que va a cargar, guardar y realizar alguna manipulación de texto. Esta guía constará de dos partes. La primera parte consistirá en diseñar la interfaz de la aplicación y sus comportamientos (behaviors) utilizando lenguaje declarativo en QML. En la segunda parte, agregaremos funcionalidad para cargar y guardar un archivo mediante Qt C. Usaremos Meta-Object System de Qt para exponer funciones C''+ como propiedades para que puedan ser usadas por elementos QML. Al utilizar QML y Qt C++ podemos desacoplar eficientemente la lógica de la interfaz de usuario, de la lógica de la aplicación.


[[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]]
[[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]]
Line 11: Line 13:
Para ejecutar el código de ejemplo de QML, simplemente provea el archivo QML como argumento a la herramienta qmlviewer incluida. La porción de C++ de este tutorial asume que el lector posee conocimiento básico sobre el procedimiento de compilación de Qt.
Para ejecutar el código de ejemplo de QML, simplemente provea el archivo QML como argumento a la herramienta qmlviewer incluida. La porción de C++ de este tutorial asume que el lector posee conocimiento básico sobre el procedimiento de compilación de Qt.


Capítulos del tutorial:<br /># Definiendo un Botón y un Menú<br /># Implementando una Barra de Menús<br /># Construyendo un Editor de Texto<br /># Decorando el Editor de Texto<br /># Extendiendo QML utilizando Qt C++
Capítulos del tutorial:
# Definiendo un Botón y un Menú
# Implementando una Barra de Menús
# Construyendo un Editor de Texto
# Decorando el Editor de Texto
# Extendiendo QML utilizando Qt C++


== Definiendo un Botón y un Menú ==
== Definiendo un Botón y un Menú ==
Line 21: Line 28:
En QML, el elemento visual básico es el elemento Rectangle (Rectángulo). El elemento Rectangle tiene propiedades para controlar su apariencia y ubicación.
En QML, el elemento visual básico es el elemento Rectangle (Rectángulo). El elemento Rectangle tiene propiedades para controlar su apariencia y ubicación.


Primero, la importación de Qt 4.7 permite que la herramienta qmlviewer importe los elementos QML que usaremos más tarde. Esta línea debe existir para cada archivo QML.<br />Note que la versión de los módulos de Qt se incluye en sentencia de importación
Primero, la importación de Qt 4.7 permite que la herramienta qmlviewer importe los elementos QML que usaremos más tarde. Esta línea debe existir para cada archivo QML.
Note que la versión de los módulos de Qt se incluye en sentencia de importación


Este simple rectángulo tiene un identificador único, simplebutton (botón simple), el cual está ligado a la propiedad id. Las propiedades del elemento Rectangle se enlazan con valores listando la propiedad seguida de dos puntos ':', y después el valor. En el código de ejemplo, el color gris está ligado con la propiedad color del Rectángulo. De manera similar, enlazamos el ancho y el alto del Rectángulo
Este simple rectángulo tiene un identificador único, simplebutton (botón simple), el cual está ligado a la propiedad id. Las propiedades del elemento Rectangle se enlazan con valores listando la propiedad seguida de dos puntos ':', y después el valor. En el código de ejemplo, el color gris está ligado con la propiedad color del Rectángulo. De manera similar, enlazamos el ancho y el alto del Rectángulo
Line 33: Line 41:
Para implementar la funcionalidad de click del botón, podemos utilizar el manejo de eventos de QML. El manejo de eventos de QML es muy similar a el mecanismo de Signals y Slots (Señales y Ranuras) de Qt. Las Signals se emiten y el Slot conectado es invocado.
Para implementar la funcionalidad de click del botón, podemos utilizar el manejo de eventos de QML. El manejo de eventos de QML es muy similar a el mecanismo de Signals y Slots (Señales y Ranuras) de Qt. Las Signals se emiten y el Slot conectado es invocado.


<code>Rectangle{<br /> id:simplebutton<br />
<code>Rectangle{
id:simplebutton


MouseArea{<br /> id: buttonMouseArea
MouseArea{
id: buttonMouseArea


anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors<br /> //onClicked handles valid mouse button clicks<br /> onClicked: console.log(buttonLabel.text + &quot; clicked&amp;quot; )<br /> }<br />}</code>
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>


Incluimos un elemento MouseArea (Área de Mouse) en nuestro elemento simplebutton. El elemento MouseArea describe el área interactiva en dónde los movimientos del mouse son detectados. Para nuestro botón, anclamos el MouseArea completa 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 llamadas anchors. QML utiliza layouts basados en anchors en dónde los items se pueden anclar a otros items, creando layouts robustos.
Incluimos un elemento MouseArea (Área de Mouse) en nuestro elemento simplebutton. El elemento MouseArea describe el área interactiva en dónde los movimientos del mouse son detectados. Para nuestro botón, anclamos el MouseArea completa 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 llamadas anchors. QML utiliza layouts basados en anchors en dónde los items se pueden anclar a otros items, creando layouts robustos.
Line 45: Line 60:
El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla e imprimir texto en pantalla cada vez que se hace click con el mouse sobre el botón.
El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla e imprimir texto en pantalla cada vez que se hace click con el mouse sobre el botón.


<code>Rectangle {<br /> id:Button<br />
<code>Rectangle {
id:Button


property color buttonColor: &quot;lightblue&amp;quot;<br /> property color onHoverColor: &quot;gold&amp;quot;<br /> property color borderColor: &quot;white&amp;quot;
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"


signal buttonClick()<br /> onButtonClick: {<br /> console.log(buttonLabel.text + &quot; clicked&amp;quot; )<br /> }
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}


MouseArea{<br /> onClicked: buttonClick()<br /> hoverEnabled: true<br /> onEntered: parent.border.color = onHoverColor<br /> onExited: parent.border.color = borderColor<br /> }
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<br /> color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br />}</code>
//determines the color of the button by using the conditional operator
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}</code>


Un botón completamente funcional se encuentra en Button.qml. En el fragmento de código de este artículo se omite cierto código, el cual se denota por puntos suspensivos, debido a que se presentó en secciones anteriores o a que es irrelevante para la discusión del código de la sección actual.
Un botón completamente funcional se encuentra en Button.qml. En el fragmento de código de este artículo se omite cierto código, el cual se denota por puntos suspensivos, debido a que se presentó en secciones anteriores o a que es irrelevante para la discusión del código de la sección actual.


Las propiedades personalizadas se declaran utilizando la sintaxis tipo de propiedad, nombre. En el código, la propiedad buttonColor (Color de Botón), de tipo color, se declara y enlaza al valor &quot;lightblue&amp;quot; (azul claro). La propiedad buttonColor se utiliza más tarde en una operación condicional para determinar el color de relleno del botón. Note que la asignación de un valor a una propiedad es posible utilizando el signo igual '=', en adición al enlace de de valores utilizando el carácter dos puntos ':'. Las propiedades personalizadas permiten que los elementos internos sean accesibles desde fuera del ámbito del Rectángulo. Existen tipos básicos QML como int, string, real y también un tipo llamado variant.
Las propiedades personalizadas se declaran utilizando la sintaxis tipo de propiedad, nombre. En el código, la propiedad buttonColor (Color de Botón), de tipo color, se declara y enlaza al valor "lightblue" (azul claro). La propiedad buttonColor se utiliza más tarde en una operación condicional para determinar el color de relleno del botón. Note que la asignación de un valor a una propiedad es posible utilizando el signo igual '=', en adición al enlace de de valores utilizando el carácter dos puntos ':'. Las propiedades personalizadas permiten que los elementos internos sean accesibles desde fuera del ámbito del Rectángulo. Existen tipos básicos QML como int, string, real y también un tipo llamado variant.


Mediante el enlace de los manejadores de señal onEntered y onExited, el borde de los botones cambiará a amarillo cuando el mouse se coloque encima del botón y el cambio de color se revertirá cuando el mouse salga del área de mouse.
Mediante el enlace de los manejadores de señal onEntered y onExited, el borde de los botones cambiará a amarillo cuando el mouse se coloque encima del botón y el cambio de color se revertirá cuando el mouse salga del área de mouse.
Line 75: Line 104:
Los Menús muestran el contenido de una lista, cada elemento posee la habilidad de realizar alguna acción. En QML, podemos crear un menú de varias maneras. Primero, crearemos un menú que contenga botones, los cuales eventualmente realizarán diferentes acciones. El código del menú está en FileMenu.qml
Los Menús muestran el contenido de una lista, cada elemento posee la habilidad de realizar alguna acción. En QML, podemos crear un menú de varias maneras. Primero, crearemos un menú que contenga botones, los cuales eventualmente realizarán diferentes acciones. El código del menú está en FileMenu.qml


<code>import Qt 4.7 import the main Qt QML module<br />import &quot;folderName&amp;quot; import the contents of the folder<br />import &quot;script.js&amp;quot; as Script import a Javascript file and name it as Script<br /></code>
<code>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
</code>


La sintaxis mostrada arriba muestra como utilizar la palabra clave import. Ésto se requiere para utilizar archivos JavaScript o QML que no están en el mismo directorio. Debido a que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para utilizarlo. Podemos directamente crear un elemento Button declarando Button{}, similar a la declaración de Rectangle{}.
La sintaxis mostrada arriba muestra como utilizar la palabra clave import. Ésto se requiere para utilizar archivos JavaScript o QML que no están en el mismo directorio. Debido a que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para utilizarlo. Podemos directamente crear un elemento Button declarando Button{}, similar a la declaración de Rectangle{}.
Line 81: Line 113:
En FileMenu.qml:
En FileMenu.qml:


<code>Row{<br /> anchors.centerIn: parent<br /> spacing: parent.width/6
<code>Row{
anchors.centerIn: parent
spacing: parent.width/6


Button{<br /> id: loadButton<br /> buttonColor: &quot;lightgrey&amp;quot;<br /> label: &quot;Load&amp;quot;<br /> }<br /> Button{<br /> buttonColor: &quot;grey&amp;quot;<br /> id: saveButton<br /> label: &quot;Save&amp;quot;<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Exit&amp;quot;<br /> buttonColor: &quot;darkgrey&amp;quot;
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"


onButtonClick: Qt.quit()<br /> }<br />}</code>
onButtonClick: Qt.quit()
}
}</code>


En FileMenu.qml, declaramos tres elementos Button. Están declarados dentro de un elemento Row (Fila), el cual posicionará a sus hijos a lo largo de una fila vertical. La declaración del Botón reside en Button.qml, el cual es el mismo Button.qml que utilizamos en la sección previa. Se pueden declarar nuevos enlaces de propiedades dentro de los nuevos botones creados, sobrescribiendo efectivamente las propiedades establecidas en Button.qml. El botón llamado exitButton (Botón Salir) cerrará la ventana y terminará la aplicación cuando se presione. Note que el manejador de signal onButtonClick en Button.qml será invocado en adición al manejador onButtonClick en exitButton.
En FileMenu.qml, declaramos tres elementos Button. Están declarados dentro de un elemento Row (Fila), el cual posicionará a sus hijos a lo largo de una fila vertical. La declaración del Botón reside en Button.qml, el cual es el mismo Button.qml que utilizamos en la sección previa. Se pueden declarar nuevos enlaces de propiedades dentro de los nuevos botones creados, sobrescribiendo efectivamente las propiedades establecidas en Button.qml. El botón llamado exitButton (Botón Salir) cerrará la ventana y terminará la aplicación cuando se presione. Note que el manejador de signal onButtonClick en Button.qml será invocado en adición al manejador onButtonClick en exitButton.
Line 101: Line 150:
== Implementando una Barra de Menús ==
== Implementando una Barra de Menús ==


Nuestra aplicación de editor de textos necesitará una manera de mostrar menús utilizando una barra de menús. La barra de menús realizará los cambios entre los diferentes menús y el usuario puede elegir cual menú desplegar.<br />El cambió de menú implica que los menús necesitan más estructura que simplemente mostrarlos en una fila. QML utiliza modelos y vistas para estructurar datos y mostrar los datos estructurados.
Nuestra aplicación de editor de textos necesitará una manera de mostrar menús utilizando una barra de menús. La barra de menús realizará los cambios entre los diferentes menús y el usuario puede elegir cual menú desplegar.
El cambió de menú implica que los menús necesitan más estructura que simplemente mostrarlos en una fila. QML utiliza modelos y vistas para estructurar datos y mostrar los datos estructurados.


=== Utilizando Modelos de Datos y Vistas ===
=== Utilizando Modelos de Datos y Vistas ===
Line 109: Line 159:
Declaramos dos elementos visuales en el menuListModel, el FileMenu y el EditMenu. Personalizamos los dos menús y los mostramos utilizando una ListView. El archivo MenuBar.qml contiene las declaraciones QML y en EditMenu.qml un simple menu edit está definido.
Declaramos dos elementos visuales en el menuListModel, el FileMenu y el EditMenu. Personalizamos los dos menús y los mostramos utilizando una ListView. El archivo MenuBar.qml contiene las declaraciones QML y en EditMenu.qml un simple menu edit está definido.


<code>VisualItemModel{<br /> id: menuListModel<br /> FileMenu{<br /> width: menuListView.width<br /> height: menuBar.height<br /> color: fileColor<br /> }<br /> EditMenu{<br /> color: editColor<br /> width: menuListView.width<br /> height: menuBar.height<br /> }<br />}</code>
<code>VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}</code>


El elemento ListView mostrará un modelo de acuerdo a un delegate. El delegate declarará los elemento del modelo de manera que se muestren en un elemento Row o en un grid. Nuestro menuListModel ya tiene elementos visibles, por lo tanto, no necesitamos declarar un delegate.
El elemento ListView mostrará un modelo de acuerdo a un delegate. El delegate declarará los elemento del modelo de manera que se muestren en un elemento Row o en un grid. Nuestro menuListModel ya tiene elementos visibles, por lo tanto, no necesitamos declarar un delegate.


<code>ListView{<br /> id: menuListView
<code>ListView{
id: menuListView


//Anchors are set to react to window anchors<br /> anchors.fill:parent<br /> anchors.bottom: parent.bottom<br /> width:parent.width<br /> height: parent.height
//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<br /> model: menuListModel
//the model contains the data
model: menuListModel


//control the movement of the menu switching<br /> snapMode: ListView.SnapOneItem<br /> orientation: ListView.Horizontal<br /> boundsBehavior: Flickable.StopAtBounds<br /> flickDeceleration: 5000<br /> highlightFollowsCurrentItem: true<br /> highlightMoveDuration:240<br /> highlightRangeMode: ListView.StrictlyEnforceRange<br />}</code>
//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
}</code>


Adicionalmente, ListView hereda de Flickable, haciendo que la lista responda al arrastre del mouse y otros gestos. La última porción del código de arriba establece las propiedades de Flickable para crear el movimiento de deslizamiento (flicking) que deseamos en nuestra vista. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de deslizamiento. Un valor más alto de la propiedad highlightMoveDuration da como resultado un cambio de menú más lento.
Adicionalmente, ListView hereda de Flickable, haciendo que la lista responda al arrastre del mouse y otros gestos. La última porción del código de arriba establece las propiedades de Flickable para crear el movimiento de deslizamiento (flicking) que deseamos en nuestra vista. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de deslizamiento. Un valor más alto de la propiedad highlightMoveDuration da como resultado un cambio de menú más lento.
Line 127: Line 203:
El rectángulo labelList tiene valor z de 1, denotando que será mostrado frente a la barra de menús. Los elementos con un valor de z más alto se muestran por sobre elementos con valores menores de z. El valor por defecto para z es 0.
El rectángulo labelList tiene valor z de 1, denotando que será mostrado frente a la barra de menús. Los elementos con un valor de z más alto se muestran por sobre elementos con valores menores de z. El valor por defecto para z es 0.


<code>Rectangle{<br /> id: labelList<br /> <br /> z: 1<br /> Row{<br /> anchors.centerIn: parent<br /> spacing:40<br /> Button{<br /> label: &quot;File&amp;quot;<br /> id: fileButton<br /> <br /> onButtonClick: menuListView.currentIndex = 0<br /> }<br /> Button{<br /> id: editButton<br /> label: &quot;Edit&amp;quot;<br /> <br /> onButtonClick: menuListView.currentIndex = 1<br /> }<br /> }<br />}</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>


Podemos acceder a los menús de la barra de herramientas que acabamos de crear deslizando hacia los menús o presionando los nombres de los menús de la parte superior. El cambio entre las pantallas del menú se siente intuitivo y responsivo.
Podemos acceder a los menús de la barra de herramientas que acabamos de crear deslizando hacia los menús o presionando los nombres de los menús de la parte superior. El cambio entre las pantallas del menú se siente intuitivo y responsivo.
Line 139: Line 235:
Nuestro editor de texto no es un editor de texto si no contiene un área para editar texto. El elemento TextEdit de QML permite la declaración de un área multilínea de texto editable. TextEdit es diferente de un elemento Text, el cual no permite al usuario editar el texto directamente.
Nuestro editor de texto no es un editor de texto si no contiene un área para editar texto. El elemento TextEdit de QML permite la declaración de un área multilínea de texto editable. TextEdit es diferente de un elemento Text, el cual no permite al usuario editar el texto directamente.


<code>TextEdit{<br /> id: textEditor<br /> anchors.fill:parent<br /> width:parent.width; height:parent.height<br /> color:&quot;midnightblue&amp;quot;<br /> focus: true
<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)<br />}</code>
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}</code>


El editor tiene establecidas las propiedades de color y ajuste de texto. El área de edición de texto está dentro de un área deslizable que desplazará el texto si el cursor está fuera del área visible. La función ensureVisible() verificará si el rectángulo del cursor está fuera de los límites de visibilidad y moverá el área de texto de acuerdo a esto. QML utiliza sintáxist de Javascript para sus scripts, y como se menciono previamente, los archivos Javascript se pueden importar y utilizar en un archivo QML.
El editor tiene establecidas las propiedades de color y ajuste de texto. El área de edición de texto está dentro de un área deslizable que desplazará el texto si el cursor está fuera del área visible. La función ensureVisible() verificará si el rectángulo del cursor está fuera de los límites de visibilidad y moverá el área de texto de acuerdo a esto. QML utiliza sintáxist de Javascript para sus scripts, y como se menciono previamente, los archivos Javascript se pueden importar y utilizar en un archivo QML.


<code>function ensureVisible®{<br /> if (contentX &gt;= r.x)<br /> contentX = r.x;<br /> else if (contentX+width &lt;= r.x+r.width)<br /> contentX = r.x+r.width-width;<br /> if (contentY &gt;= r.y)<br /> contentY = r.y;<br /> else if (contentY+height &lt;= r.y+r.height)<br /> contentY = r.y+r.height-height;<br />}</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 el Editor de Texto ===
=== Combinando Componentes para el Editor de Texto ===
Line 155: Line 266:
<code>Rectangle{
<code>Rectangle{


id: screen<br /> width: 1000; height: 1000
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<br /> property int partition: height/3
//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{<br /> id:menuBar<br /> height: partition<br /> width:parent.width<br /> z: 1<br /> }
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}


TextArea{<br /> id:textArea<br /> anchors.bottom:parent.bottom<br /> y: partition<br /> color: &quot;white&amp;quot;<br /> height: partition*2<br /> width:parent.width<br /> }<br />}</code>
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}</code>


Mediante la importación de componentes reutilizables, el código de nuestro Editor de Texto se ve mucho más simple. Ahora podemos personalizar la aplicación principal sin preocuparnos acerca de propiedades que ya tiene comportamientos definidos. Utilizando este enfoque, se puede crear fácilmente tanto el diseño de la aplicación como componentes de UI.
Mediante la importación de componentes reutilizables, el código de nuestro Editor de Texto se ve mucho más simple. Ahora podemos personalizar la aplicación principal sin preocuparnos acerca de propiedades que ya tiene comportamientos definidos. Utilizando este enfoque, se puede crear fácilmente tanto el diseño de la aplicación como componentes de UI.
Line 173: Line 299:
Nuestro Editor de Texto parece simple y por lo tanto es necesario que lo decoremos. Utilizando QML, podemos declarar las transiciones y la animación de nuestro Editor de Texto. Nuestra barra de menú está ocupando un tercio de la pantalla y seria bueno que solo apareciera cuando lo deseemos.
Nuestro Editor de Texto parece simple y por lo tanto es necesario que lo decoremos. Utilizando QML, podemos declarar las transiciones y la animación de nuestro Editor de Texto. Nuestra barra de menú está ocupando un tercio de la pantalla y seria bueno que solo apareciera cuando lo deseemos.


Podemos añadir una Interfaz Drawer, que minimice o expanda la barra de menú cuando se hace click. En nuestra implementación, tenemos un rectángulo delgado que responde a los click del mouse. El drawer, así como la aplicación, tienen dos estados: el estado &quot;drawer is open&amp;quot; y el estado &quot;drawer is closed&amp;quot;. El elemento drawer es una franja del rectángulo con una pequeña altura. Hay un elemento anidado Imagen que declara que un icono arrow será centrado dentro del drawer. El drawer asigna un estado a toda la aplicación, con el identificador de pantalla(screen), cada vez que un usuario haga clic en el área del mouse.
Podemos añadir una Interfaz Drawer, que minimice o expanda la barra de menú cuando se hace click. En nuestra implementación, tenemos un rectángulo delgado que responde a los click del mouse. El drawer, así como la aplicación, tienen dos estados: el estado "drawer is open" y el estado "drawer is closed". El elemento drawer es una franja del rectángulo con una pequeña altura. Hay un elemento anidado Imagen que declara que un icono arrow será centrado dentro del drawer. El drawer asigna un estado a toda la aplicación, con el identificador de pantalla(screen), cada vez que un usuario haga clic en el área del mouse.


<code> Rectangle{<br /> id:drawer<br /> height:15
<code> Rectangle{
id:drawer
height:15


Image{<br /> id: arrowIcon<br /> source: &quot;images/arrow.png&amp;quot;<br /> anchors.horizontalCenter: parent.horizontalCenter<br /> }
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}


MouseArea{<br /> id: drawerMouseArea<br /> anchors.fill:parent<br /> onClicked:{<br /> if (screen.state  &amp;quot;DRAWER_CLOSED&amp;quot;)&amp;#123;
MouseArea{
                     screen.state = &amp;quot;DRAWER_OPEN&amp;quot;
id: drawerMouseArea
                 &amp;#125;
anchors.fill:parent
                 else if (screen.state  &quot;DRAWER_OPEN&amp;quot;){<br /> screen.state = &quot;DRAWER_CLOSED&amp;quot;<br /> }<br /> }<br /> <br /> }<br /> }</code>
onClicked:{
if (screen.state  "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state  "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
}
}</code>


Un estado es simplemente una colección de configuraciones las cuales son declaradas en un elemento '''State'''. Una lista de los estados pueden ser listadas y ligadas a la propiedad states. En nuestras aplicación, los dos estados son lamados DRAWER_OPEN Y DRAWER_CLOSED. Las configuraciones de los elementos son declaradas en la PorpertyChanges. En el estados DRAWER_OPEN, hay cuatro elementos que recibirán los cambios de propiedad (PropertyChanges). El primer objeto, menuBar cambiará su '''y''' propiedad a 0. Del mismo modo, el objeto textArea descenderá auna nueva posición cuando el estado DRAWER_OPEN este activado. La textArea, el drawer, y el icono de drawer se someterán a los cambios de propiedad para cumplir con el estado actual.
Un estado es simplemente una colección de configuraciones las cuales son declaradas en un elemento '''State'''. Una lista de los estados pueden ser listadas y ligadas a la propiedad states. En nuestras aplicación, los dos estados son lamados DRAWER_OPEN Y DRAWER_CLOSED. Las configuraciones de los elementos son declaradas en la PorpertyChanges. En el estados DRAWER_OPEN, hay cuatro elementos que recibirán los cambios de propiedad (PropertyChanges). El primer objeto, menuBar cambiará su '''y''' propiedad a 0. Del mismo modo, el objeto textArea descenderá auna nueva posición cuando el estado DRAWER_OPEN este activado. La textArea, el drawer, y el icono de drawer se someterán a los cambios de propiedad para cumplir con el estado actual.


<code>states:[<br /> State {<br /> name: &quot;DRAWER_OPEN&amp;quot;<br /> PropertyChanges { target: menuBar; y: 0}<br /> PropertyChanges { target: textArea; y: partition + drawer.height}<br /> PropertyChanges { target: drawer; y: partition}<br /> PropertyChanges { target: arrowIcon; rotation: 180}<br /> },<br /> State {<br /> name: &quot;DRAWER_CLOSED&amp;quot;<br /> PropertyChanges { target: menuBar; y:<s>height; }<br /> PropertyChanges { target: textArea; y: drawer.height; height: screen.height</s> drawer.height }<br /> PropertyChanges { target: drawer; y: 0 }<br /> PropertyChanges { target: arrowIcon; rotation: 0 }<br /> }<br /> ]<br /></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>


Los cambios de estado son bruscos por ello es recomendable aplicar transiciones para suavizarlos. Las transiciones entre los estados se definen mediante el elemento '''Transition''', que puede enlazarse a los elementos dentro de la propiedad '''transitions'''. Nuestro editor de texto tiene una transición de estado cuando el estado cambia a DRAWER_OPEN o DRAWER_CLOSED. Es importante destacar que es necesario un estado de transición '''from''' y un '''to''', pero para nuestra transición, podemos utilizar el símbolo * para indicar que la transición se aplica a todos los cambios de estado.
Los cambios de estado son bruscos por ello es recomendable aplicar transiciones para suavizarlos. Las transiciones entre los estados se definen mediante el elemento '''Transition''', que puede enlazarse a los elementos dentro de la propiedad '''transitions'''. Nuestro editor de texto tiene una transición de estado cuando el estado cambia a DRAWER_OPEN o DRAWER_CLOSED. Es importante destacar que es necesario un estado de transición '''from''' y un '''to''', pero para nuestra transición, podemos utilizar el símbolo * para indicar que la transición se aplica a todos los cambios de estado.


Durante las transiciones, podemos asignar animaciones a los cambios de propiedad. Nuestra menuBar cambia de la posición de y:0 a y:-partition y se puede animar esta transición mediante el elemento '''NumberAnimation'''. Declaramos que las propiedades de los objetos van a animarse por un período de tiempo determinado y con una curva de aceleración determinada. Una curva de aceleración controla el comportamiento de las tasas de animación y la interpolación en las transiciones de estado. La curva de aceleración que elegimos es '''Easing.OutQuint''', que retarda el movimiento hacia el final de la animación. Lea atentamente el artículo &quot;QML's Animation&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativeanimation.html.
Durante las transiciones, podemos asignar animaciones a los cambios de propiedad. Nuestra menuBar cambia de la posición de y:0 a y:-partition y se puede animar esta transición mediante el elemento '''NumberAnimation'''. Declaramos que las propiedades de los objetos van a animarse por un período de tiempo determinado y con una curva de aceleración determinada. Una curva de aceleración controla el comportamiento de las tasas de animación y la interpolación en las transiciones de estado. La curva de aceleración que elegimos es '''Easing.OutQuint''', que retarda el movimiento hacia el final de la animación. Lea atentamente el artículo "QML's Animation":http://doc.qt.nokia.com/4.7/qdeclarativeanimation.html.


<code>transitions: [<br /> Transition {<br /> to: &quot;*&quot;<br /> NumberAnimation { target: textArea; properties: &quot;y, height&amp;quot;; duration: 100; easing.type:Easing.OutExpo }<br /> NumberAnimation { target: menuBar; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> NumberAnimation { target: drawer; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> }<br /> ]<br /></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>


Otra manera de animar a los cambios de propiedad es declarando un elemento de comportamiento (&quot;Behavior&amp;quot;:http://doc.qt.nokia.com/4.7/qml-behavior.html). Una transición sólo funciona durante los cambios de estado y el comportamiento puede establecer una animación para un cambio de propiedad en general. En el editor de texto, la flecha tiene un comportamiento NumberAnimation que anima su propiedad rotation cada vez que cambia la propiedad.
Otra manera de animar a los cambios de propiedad es declarando un elemento de comportamiento ("Behavior":http://doc.qt.nokia.com/4.7/qml-behavior.html). Una transición sólo funciona durante los cambios de estado y el comportamiento puede establecer una animación para un cambio de propiedad en general. En el editor de texto, la flecha tiene un comportamiento NumberAnimation que anima su propiedad rotation cada vez que cambia la propiedad.


<code>In TextEditor.qml:
<code>In TextEditor.qml:


Behavior{<br /> NumberAnimation{property: &quot;rotation&amp;quot;;easing.type: Easing.OutExpo }<br /> }<br /></code>
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
</code>


Volviendo a nuestros componentes con el conocimiento de los estados y las animaciones, podemos mejorar la apariencia de los componentes. En Button.qml, podemos añadir los cambios a la propiedad color y scale cuando se pulsa el botón. Los tipos color son animados usando ColorAnimation y los números son animados con NumberAnimation. La sintaxis de propertyName on que aparecen a continuación es de gran ayuda al elegir una sola propiedad.
Volviendo a nuestros componentes con el conocimiento de los estados y las animaciones, podemos mejorar la apariencia de los componentes. En Button.qml, podemos añadir los cambios a la propiedad color y scale cuando se pulsa el botón. Los tipos color son animados usando ColorAnimation y los números son animados con NumberAnimation. La sintaxis de propertyName on que aparecen a continuación es de gran ayuda al elegir una sola propiedad.


<code>In Button.qml:<br />
<code>In Button.qml:


color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor<br /> Behavior on color { ColorAnimation{ duration: 55} }
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
Behavior on color { ColorAnimation{ duration: 55} }


scale: buttonMouseArea.pressed ? 1.1 : 1.00<br /> Behavior on scale { NumberAnimation{ duration: 55} }<br /></code>
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
</code>


Además, podemos mejorar la apariencia de nuestros componentes QML mediante la adición de efectos de color tales como gradientes y efectos de opacidad. La declaración de un elemento '''Gradient''' anulará la propiedad color del elemento. Usted puede declarar un color en el gradient mediante el elemento '''GradientStop'''. El gradiente se coloca utilizando una escala, entre 0.0 y 1.0.
Además, podemos mejorar la apariencia de nuestros componentes QML mediante la adición de efectos de color tales como gradientes y efectos de opacidad. La declaración de un elemento '''Gradient''' anulará la propiedad color del elemento. Usted puede declarar un color en el gradient mediante el elemento '''GradientStop'''. El gradiente se coloca utilizando una escala, entre 0.0 y 1.0.


<code>In MenuBar.qml<br /> gradient: Gradient {<br /> GradientStop { position: 0.0; color: &quot;#8C8F8C&amp;quot; }<br /> GradientStop { position: 0.17; color: &quot;#6A6D6A&amp;quot; }<br /> GradientStop { position: 0.98;color: &quot;#3F3F3F&amp;quot; }<br /> GradientStop { position: 1.0; color: &quot;#0e1B20&amp;quot; }<br /> }<br /></code>
<code>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" }
}
</code>


Este gradiente es utilizado por la barra de menús para mostrar una simulación del gradiente de profundidad. El primer color empieza en 0.0 y el último color es de 1.0.
Este gradiente es utilizado por la barra de menús para mostrar una simulación del gradiente de profundidad. El primer color empieza en 0.0 y el último color es de 1.0.
Line 217: Line 397:


Hemos terminado la construcción de la interfaz de usuario de un editor de texto muy simple. En el futuro, se completara la interfaz de usuario, y podemos aplicar la lógica de la aplicación haciendo uso regular de Qt y C+''. QML funciona muy bien como una herramienta de creación de prototipos, que separa la lógica de la aplicación de distancia de el diseño de interfaz de usuario.  
Hemos terminado la construcción de la interfaz de usuario de un editor de texto muy simple. En el futuro, se completara la interfaz de usuario, y podemos aplicar la lógica de la aplicación haciendo uso regular de Qt y C+''. QML funciona muy bien como una herramienta de creación de prototipos, que separa la lógica de la aplicación de distancia de el diseño de interfaz de usuario.  
<br />[[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]]
 
<br />h3. Extendiendo QML utilizando C''+
[[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]]
 
h3. Extendiendo QML utilizando C''+


Ahora que tenemos el diseño de nuestro editor de textos, podemos implementar su funcionalidad utilizando C+''. Utilizar QML con C''+ nos permite crear la lógica de nuestra aplicación utilizando Qt. Podemos crear un contexto QML en una aplicación C++ utilizando las clases de Qt Declarative y mostrar los elementos utilizando un Graphics Scene. De manera alternativa, podemos exportar nuestro código C++ en un plugin que la herramienta qmlviewer puede leer. Para nuestra aplicación, implementaremos las funciones abrir y guardar en C++ y las exportaremos como un plugin. De esta manera, podremos cargar el archivo QML de manera directa en lugar de utilizar un ejecutable.
Ahora que tenemos el diseño de nuestro editor de textos, podemos implementar su funcionalidad utilizando C+''. Utilizar QML con C''+ nos permite crear la lógica de nuestra aplicación utilizando Qt. Podemos crear un contexto QML en una aplicación C++ utilizando las clases de Qt Declarative y mostrar los elementos utilizando un Graphics Scene. De manera alternativa, podemos exportar nuestro código C++ en un plugin que la herramienta qmlviewer puede leer. Para nuestra aplicación, implementaremos las funciones abrir y guardar en C++ y las exportaremos como un plugin. De esta manera, podremos cargar el archivo QML de manera directa en lugar de utilizar un ejecutable.
Line 240: Line 422:
<code> In cppPlugins.pro:
<code> In cppPlugins.pro:


TEMPLATE = lib<br /> CONFIG ''= qt plugin<br /> QT''= declarative
TEMPLATE = lib
CONFIG ''= qt plugin
QT''= declarative
 
DESTDIR ''= ../plugins
OBJECTS_DIR = tmp
MOC_DIR = tmp
 
TARGET = FileDialog


DESTDIR ''= ../plugins<br /> OBJECTS_DIR = tmp<br /> MOC_DIR = tmp
HEADERS''= directory.h  file.h  dialogPlugin.h
<br /> TARGET = FileDialog
<br /> HEADERS''= directory.h  file.h  dialogPlugin.h


SOURCES += directory.cpp  file.cpp  dialogPlugin.cpp<br /></code>
SOURCES += directory.cpp  file.cpp  dialogPlugin.cpp
</code>


En particular, compilamos Qt con el módulo declarativo y lo configuramos como un plugin, para esto necesitamos utilizar una plantilla de biblioteca (lib). Pondremos el plugin en el directorio de plugins del padre.
En particular, compilamos Qt con el módulo declarativo y lo configuramos como un plugin, para esto necesitamos utilizar una plantilla de biblioteca (lib). Pondremos el plugin en el directorio de plugins del padre.
Line 254: Line 443:
<code> In dialogPlugin.h:
<code> In dialogPlugin.h:


#include &lt;QtDeclarative/QDeclarativeExtensionPlugin&amp;gt;
#include <QtDeclarative/QDeclarativeExtensionPlugin>


class DialogPlugin : public QDeclarativeExtensionPlugin<br /> {<br /> Q_OBJECT
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT


public:<br /> void registerTypes(const char *uri);
public:
void registerTypes(const char *uri);


};</code>
};</code>


Nuestra clase plugin, DialogPlugin es una subclase de &quot;QDeclarativeExtensionPlugin&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html. Necesitamos implementar la función heredada, registerTypes(). El archivo dialogPlugin.cpp es similar a esto:
Nuestra clase plugin, DialogPlugin es una subclase de "QDeclarativeExtensionPlugin":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html. Necesitamos implementar la función heredada, registerTypes(). El archivo dialogPlugin.cpp es similar a esto:


<code> DialogPlugin.cpp:
<code> DialogPlugin.cpp:


#include &quot;dialogPlugin.h&amp;quot;<br /> #include &quot;directory.h&amp;quot;<br /> #include &quot;file.h&amp;quot;<br /> #include &lt;QtDeclarative/qdeclarative.h&amp;gt;
#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){
<br /> qmlRegisterType&amp;lt;Directory&amp;gt;(uri, 1, 0, &quot;Directory&amp;quot;);<br /> qmlRegisterType&amp;lt;File&amp;gt;(uri, 1, 0,&quot;File&amp;quot;);<br /> }
<br /> Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);</code>
<br />La función &quot;registerTypes()&quot;:http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#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 mayor, un número de versión menor y un nombre para nuestras clases.
<br />Necesitamos exportar el complemento utilizando la macro &quot;Q_EXPORT_PLUGIN2&amp;quot;:http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2#q-export-plugin2 macro. Note que en nuestro archivo dialogPlugin.h, tenemos la macro &quot;Q_OBJECT&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT en la parte superior de nuestra clase. Y también, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario.
<br />h3. Creando Propiedades QML en una clase C++
<br />Podemos crear elementos y propiedades QML usando el &quot;Sistema de Meta-Objetos de Qt&amp;quot;:http://doc.qt.nokia.com/4.7/metaobjects.html. Podemos implementar propiedades utilizando signals y slots, haciendo que Qt esté conciente de estas propiedades. Entonces estas propiedades podrán ser utilizadas en QML.
<br />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 utilizar &quot;QDir&amp;quot;:http://doc.qt.nokia.com/4.7/qdir.html, &quot;QFile&amp;quot;:http://doc.qt.nokia.com/4.7/qfile.html, y &quot;QTextStream&amp;quot;:http://doc.qt.nokia.com/4.7/qtextstream.html para implementar la lectura de directorios y flujos de entrada/salida.
<br /><code> class Directory : public QObject{
<br /> Q_OBJECT
<br /> Q_PROPERTY(int filesCount READ filesCount CONSTANT)<br /> Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)<br /> Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)<br /> Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )
<br /> …</code>
<br />La clase Directory utiliza el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para realizar el manejo de archivos. La clase Directory se exporta como un plugin y es utilizable en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro &quot;Q_PROPERTY&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY es una propiedad QML.
<br />La macro &quot;Q_PROPERTY&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY declara una propiedad y sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename, de tipo QString, puede ser leída utilizando la función filename() y puede ser escrita utilizando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite cuando la propiedad cambia. Las funciones de lectura y escritura están declaradas como públicas en el archivo de encabezado.
<br />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.
<br /><code> Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )<code>
<br />La propiedad files 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 los archivos con extensión .txt son válidos. Además, las &quot;QLists&amp;quot;:http://doc.qt.nokia.com/4.7/qlist.html se pueden utilizar en archivos QML al declararlas como QDeclarativeListProperty en C+''. El objeto plantilla necesita heredar de QObject, por lo tanto, 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.
<br /></code> class File : public QObject{
<br /> Q_OBJECT<br /> Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
<br /> …<br /> };<code>
<br />Las propiedades pueden entonces utilizarse en QML como parte de las propiedades del elemento Directory. Note que no necesitamos crear una propiedad identificador id en nuestro código C.
<br /></code> Directory{<br /> id: directory
<br /> filesCount<br /> filename<br /> fileContent<br /> files
<br /> files[0].name<br /> }<code>
<br />Como QML usa sintaxis y estructura de Javascript, podemos iterar a través de la lista de archivos y obtener sus propiedades. Para obtener la propiedad nombre del primer archivo, podemos llamar a files[0].name.
<br />Las funciones normales de C''+ también son accesibles desde QML. Las funciones cargar y guardar un archvio están implementadas en C++ y se declaran utilizando la macro &quot;Q_INVOKABLE&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE. De manera alternativa, podemos declarar las funciones como una ranura y las funciones serán accesibles desde QML.
<br /></code> In Directory.h:
<br /> &quot;Q_INVOKABLE&amp;quot;:http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE void saveFile&amp;amp;#40;&amp;#41;;<br /> Q_INVOKABLE void loadFile&amp;amp;#40;&amp;#41;;<br /><code>
<br />La clase Directory también tiene que notificar a otros objetos cuando el contenido del directorio cambie. Esta función se realiza utilizando una signal. Como se mencionó previamente, las signals de QML tienen un manejador correspondiente con sus nombres precedidos por &quot;on&amp;quot;. La signal tiene el nombre de directoryChanged y se emite cuando ocurre una actualización en un directorio. La actualización simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los elementos de QML pueden entonces ser notificados asignando una acción al manejador de signal onDirectoryChanged.
<br />Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista utilizan callbacks para acceder y modificar los contenidos de la lista. La propiedad lista es de tipo QDeclarativeListProperty&amp;lt;File&amp;gt;. Siempre que se accede a la lista, la función de acceso necesita devolver una QDeclarativeListProperty&amp;lt;File&amp;gt;. El tipo plantilla, File, necesita heredar de QObject. Además, para crear una &quot;QDeclarativeListProperty&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html, los métodos de acceso y modificación de la lista necesitan enviarse al constructor como apuntadores a función. La lista, una QList en nuestro caso, también necesita ser una lista de apuntadores a File.
<br />El constructor de &quot;QDeclarativeListProperty&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html y la implementación de Directory:
<br /></code> QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )<br /> QDeclarativeListProperty&amp;lt;File&amp;gt;( this, &amp;m_fileList, &amp;appendFiles, &amp;filesSize, &amp;fileAt, &amp;clearFilesPtr );<code>


El constructor envía apuntadores a funciones que agregan elementos al final de la lista, cuentan sus elementos, obtienen un elemento utilizando un índice y vacían la lista. Solo la función de agregar es obligatoria. Note que los apuntadores a función deben coincidir con la definición de &quot;AppendFunction&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AppendFunction-typedef, &quot;CountFunction&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#CountFunction-typedef, &quot;AtFunction&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#AtFunction-typedef, o &quot;ClearFunction&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#ClearFunction-typedef.
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
qmlRegisterType<File>(uri, 1, 0,"File");
}
 
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);</code>
 
La función "registerTypes()":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#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 mayor, un número de versión menor y un nombre para nuestras clases.
 
Necesitamos exportar el complemento utilizando la macro "Q_EXPORT_PLUGIN2":http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2#q-export-plugin2 macro. Note que en nuestro archivo dialogPlugin.h, tenemos la macro "Q_OBJECT":http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT en la parte superior de nuestra clase. Y también, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario.
 
h3. Creando Propiedades QML en una clase C++
 
Podemos crear elementos y propiedades QML usando el "Sistema de Meta-Objetos de Qt":http://doc.qt.nokia.com/4.7/metaobjects.html. Podemos implementar propiedades utilizando signals y slots, haciendo que Qt esté conciente de estas propiedades. Entonces estas propiedades podrán ser utilizadas 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 utilizar "QDir":http://doc.qt.nokia.com/4.7/qdir.html, "QFile":http://doc.qt.nokia.com/4.7/qfile.html, y "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para implementar la lectura de directorios y flujos de entrada/salida.
 
<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>
 
La clase Directory utiliza el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para realizar el manejo de archivos. La clase Directory se exporta como un plugin y es utilizable en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY es una propiedad QML.
 
La macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY declara una propiedad y sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename, de tipo QString, puede ser leída utilizando la función filename() y puede ser escrita utilizando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite cuando la propiedad cambia. Las funciones de lectura y escritura están declaradas 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.
 
<code> Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>
 
La propiedad files 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 los archivos con extensión .txt son válidos. Además, las "QLists":http://doc.qt.nokia.com/4.7/qlist.html se pueden utilizar en archivos QML al declararlas como QDeclarativeListProperty en C+''. El objeto plantilla necesita heredar de QObject, por lo tanto, 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.
 
</code> class File : public QObject{
 
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
 
};<code>
 
Las propiedades pueden entonces utilizarse en QML como parte de las propiedades del elemento Directory. Note que no necesitamos crear una propiedad identificador id en nuestro código C.
 
</code> Directory{
id: directory
 
filesCount
filename
fileContent
files
 
files[0].name
}<code>
 
Como QML usa sintaxis y estructura de Javascript, podemos iterar a través de la lista de archivos y obtener sus propiedades. Para obtener la propiedad nombre del primer archivo, podemos llamar a files[0].name.
 
Las funciones normales de C''+ también son accesibles desde QML. Las funciones cargar y guardar un archvio están implementadas en C++ y se declaran utilizando la macro "Q_INVOKABLE":http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE. De manera alternativa, podemos declarar las funciones como una ranura y las funciones serán accesibles desde QML.
 
</code> In Directory.h:
 
"Q_INVOKABLE":http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
<code>
 
La clase Directory también tiene que notificar a otros objetos cuando el contenido del directorio cambie. Esta función se realiza utilizando una signal. Como se mencionó previamente, las signals de QML tienen un manejador correspondiente con sus nombres precedidos por "on". La signal tiene el nombre de directoryChanged y se emite cuando ocurre una actualización en un directorio. La actualización simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los elementos de QML pueden entonces ser notificados asignando una acción al manejador de signal onDirectoryChanged.
 
Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista utilizan 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 una QDeclarativeListProperty<File>. El tipo plantilla, File, necesita heredar de QObject. Además, para crear una "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html, los métodos de acceso y modificación de la lista necesitan enviarse al constructor como apuntadores a función. La lista, una QList en nuestro caso, también necesita ser una lista de apuntadores a File.
 
El constructor de "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html y la implementación de Directory:
 
</code> QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
QDeclarativeListProperty<File>( this, &amp;m_fileList, &amp;appendFiles, &amp;filesSize, &amp;fileAt, &amp;clearFilesPtr );<code>
 
El constructor envía apuntadores a funciones que agregan elementos al final de la lista, cuentan sus elementos, obtienen un elemento utilizando un índice y vacían la lista. Solo la función de agregar es obligatoria. Note que los apuntadores a función deben coincidir con la definición 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, o "ClearFunction":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html#ClearFunction-typedef.
 
</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 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. La clase Directory utiliza QTextStream para leer el archivo y para sacar el contenido del archivo a otro archivo.
 
Con nuestro elemento Directory, podemos obtener 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 cuando ocurra algún cambio en el contenido del directorio.
 
Para compilar el plugin, ejecutamos qmake sobre el archivo de proyecto cppPlugins.pro, luego ejecutamos make para compilar y transferir el complemento al directorio plugins.
 
h3. Importando un Plugin 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 plugins y otros recursos.
 
</code> In qmldir:
 
Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml
 
plugin FileDialog plugins
<code>
 
El plugin que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El plugin compilado está en el directorio plugins.
 
h3. Integrando un Diálogo Archivo en el Menú Archivo
 
Nuestro FileMenu necesita mostrar el elemento FileDialog, el cual contiene una lista de archivos de texto en un directorio, esto permite 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 capturar el nombre de un archivo utilizando el teclado.
 
El elemento Directory se utiliza en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio actualizó su contenido. Esta notificación se realiza en el manejador de signal, onDirectoryChanged.
 
</code> In FileMenu.qml:
 
Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
<code>
 
Manteniendo 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.
 
</code> In FileDialog.qml:
 
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
<code>
 
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 elementos de datos en una rejilla de acuerdo a un delegate. El delegate maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una rejilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la aparición del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica cuando se emite la signal notifyRefresh, recargando los archivos en el directorio.
 
</code> 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()
}
}<code>
 
Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El elemento saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y luego copiará 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.
 
Además, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto.
 
[[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]]
 
h3. Finalización del Editor de Texto
 
[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png|http://doc.qt.nokia.com/4.7/images/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 también puede cargar contenido desde un archivo y realizar manipulación de texto.


</code> void appendFiles(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, File * file)<br /> File* fileAt(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, int index)<br /> int filesSize(QDeclarativeListProperty&amp;lt;File&amp;gt; * property)<br /> void clearFilesPtr(QDeclarativeListProperty&amp;lt;File&amp;gt; '''property)<br /><code>
'''''Visita la segunda parte de este tutorial introductorio''* [[GettingStartedQML_Spanish_p2|Introducción a la Programación con QML Parte 2]]
<br />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. La clase Directory utiliza QTextStream para leer el archivo y para sacar el contenido del archivo a otro archivo.
<br />Con nuestro elemento Directory, podemos obtener 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 cuando ocurra algún cambio en el contenido del directorio.
<br />Para compilar el plugin, ejecutamos qmake sobre el archivo de proyecto cppPlugins.pro, luego ejecutamos make para compilar y transferir el complemento al directorio plugins.
<br />h3. Importando un Plugin en QML
<br />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 plugins y otros recursos.
<br /></code> In qmldir:
<br /> Button ./Button.qml<br /> FileDialog ./FileDialog.qml<br /> TextArea ./TextArea.qml<br /> TextEditor ./TextEditor.qml<br /> EditMenu ./EditMenu.qml
<br /> plugin FileDialog plugins<br /><code>
<br />El plugin que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El plugin compilado está en el directorio plugins.
<br />h3. Integrando un Diálogo Archivo en el Menú Archivo
<br />Nuestro FileMenu necesita mostrar el elemento FileDialog, el cual contiene una lista de archivos de texto en un directorio, esto permite 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 capturar el nombre de un archivo utilizando el teclado.
<br />El elemento Directory se utiliza en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio actualizó su contenido. Esta notificación se realiza en el manejador de signal, onDirectoryChanged.
<br /></code> In FileMenu.qml:
<br /> Directory{<br /> id:directory<br /> filename: textInput.text<br /> onDirectoryChanged: fileDialog.notifyRefresh()<br /> }<br /><code>
<br />Manteniendo 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.
<br /></code> In FileDialog.qml:
<br /> signal notifyRefresh()<br /> onNotifyRefresh: dirView.model = directory.files<br /><code>
<br />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 elementos de datos en una rejilla de acuerdo a un delegate. El delegate maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una rejilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la aparición del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica cuando se emite la signal notifyRefresh, recargando los archivos en el directorio.
<br /></code> In FileMenu.qml:
<br /> Button{<br /> id: newButton<br /> label: &quot;New&amp;quot;<br /> onButtonClick:{<br /> textArea.textContent = &quot;&quot;<br /> }<br /> }<br /> Button{<br /> id: loadButton<br /> label: &quot;Load&amp;quot;<br /> onButtonClick:{<br /> directory.filename = textInput.text<br /> directory.loadFile&amp;amp;#40;&amp;#41;<br /> textArea.textContent = directory.fileContent<br /> }<br /> }<br /> Button{<br /> id: saveButton<br /> label: &quot;Save&amp;quot;<br /> onButtonClick:{<br /> directory.fileContent = textArea.textContent<br /> directory.filename = textInput.text<br /> directory.saveFile&amp;amp;#40;&amp;#41;<br /> }<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Exit&amp;quot;<br /> onButtonClick:{<br /> Qt.quit()<br /> }<br /> }<code>
<br />Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El elemento saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y luego copiará su nombre de archivo de la entrada de texto editable. Finalmente, el botón llama a la función saveFile&amp;amp;#40;&amp;#41;, 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.
<br />Además, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto.
<br />[[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]]
<br />h3. Finalización del Editor de Texto
<br />[[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png]]
<br />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 también puede cargar contenido desde un archivo y realizar manipulación de texto.
<br />'''''Visita la segunda parte de este tutorial introductorio''* [[GettingStartedQML_Spanish_p2|Introducción a la Programación con QML Parte 2]]

Revision as of 08:53, 25 February 2015


Introducción a la Programación con QML

Bienvenido al mundo de QML, el lenguaje declarativo de Interfaces de Usuario (UI). En esta guía de introducción, crearemos una simple aplicación de editor de texto utilizando QML. Después de leer esta guía, usted debería estar listo para desarrollar sus propias aplicaciones utilizando QML y Qt C+.

h2. QML para crear interfaces de usuario

La aplicación que estamos construyendo es un editor de texto simple que va a cargar, guardar y realizar alguna manipulación de texto. Esta guía constará de dos partes. La primera parte consistirá en diseñar la interfaz de la aplicación y sus comportamientos (behaviors) utilizando lenguaje declarativo en QML. En la segunda parte, agregaremos funcionalidad para cargar y guardar un archivo mediante Qt C. Usaremos Meta-Object System de Qt para exponer funciones C+ como propiedades para que puedan ser usadas por elementos QML. Al utilizar QML y Qt C++ podemos desacoplar eficientemente la lógica de la interfaz de usuario, de la lógica de la aplicación.

http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png

Para ejecutar el código de ejemplo de QML, simplemente provea el archivo QML como argumento a la herramienta qmlviewer incluida. La porción de C++ de este tutorial asume que el lector posee conocimiento básico sobre el procedimiento de compilación de Qt.

Capítulos del tutorial:

  1. Definiendo un Botón y un Menú
  2. Implementando una Barra de Menús
  3. Construyendo un Editor de Texto
  4. Decorando el Editor de Texto
  5. Extendiendo QML utilizando Qt C++

Definiendo un Botón y un Menú

Componente Básico - un Botón

Comenzamos nuestro editor de textos construyendo un botón. Funcionalmente, un botón tiene un área sensitiva al mouse y una etiqueta. Los botones realizan acciones cuando un usuario los presiona.

En QML, el elemento visual básico es el elemento Rectangle (Rectángulo). El elemento Rectangle tiene propiedades para controlar su apariencia y ubicación.

Primero, la importación de Qt 4.7 permite que la herramienta qmlviewer importe los elementos QML que usaremos más tarde. Esta línea debe existir para cada archivo QML. Note que la versión de los módulos de Qt se incluye en sentencia de importación

Este simple rectángulo tiene un identificador único, simplebutton (botón simple), el cual está ligado a la propiedad id. Las propiedades del elemento Rectangle se enlazan con valores listando la propiedad seguida de dos puntos ':', y después el valor. En el código de ejemplo, el color gris está ligado con la propiedad color del Rectángulo. De manera similar, enlazamos el ancho y el alto del Rectángulo

El elemento Text (Texto) es un campo de texto no editable. Nombramos a este elemento Text como buttonLabel. Para establecer el contenido del campo de texto, enlazamos un valor a su propiedad text. La etiqueta está contenida dentro del Rectángulo y con el fin de centrarla, asignamos las anchors (anclas) del elemento Text a su padre, el cual es llamado simplebutton. Las anchors se pueden enlazar con las anchors de otros elementos, permitiendo que las asignaciones de layouts sean más simples.

Vamos a guardar este código como SimpleButton.qml. Ejecutar el qmlviewer con este archivo como argumento mostrará el rectángulo gris con una etiqueta de texto.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png

Para implementar la funcionalidad de click del botón, podemos utilizar el manejo de eventos de QML. El manejo de eventos de QML es muy similar a el mecanismo de Signals y Slots (Señales y Ranuras) de Qt. Las Signals se emiten y el Slot conectado es invocado.

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 de Mouse) en nuestro elemento simplebutton. El elemento MouseArea describe el área interactiva en dónde los movimientos del mouse son detectados. Para nuestro botón, anclamos el MouseArea completa 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 llamadas anchors. QML utiliza layouts basados en anchors en dónde los items se pueden anclar a otros items, creando layouts robustos.

El MouseArea tiene muchos manejadores (handlers) de signals que son llamados durante los movimientos del mouse realizados dentro de los límites especificados por el MouseArea. Uno de ellos es onClicked, el cual es llamado cuando el botón de aceptar del mouse se presiona, siendo el botón izquierdo el botón aceptar por defecto. Podemos enlazar acciones al manejador onClicked. En nuestro ejemplo, console.log() imprime texto en pantalla cada vez que se hace click sobre el área de mouse. La función console.log() es una herramienta útil para propósitos de depuración y para mostrar texto en pantalla.

El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla e imprimir texto en pantalla cada vez que se hace click con el mouse sobre el botó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 se encuentra en Button.qml. En el fragmento de código de este artículo se omite cierto código, el cual se denota por puntos suspensivos, debido a que se presentó en secciones anteriores o a que es irrelevante para la discusión del código de la sección actual.

Las propiedades personalizadas se declaran utilizando la sintaxis tipo de propiedad, nombre. En el código, la propiedad buttonColor (Color de Botón), de tipo color, se declara y enlaza al valor "lightblue" (azul claro). La propiedad buttonColor se utiliza más tarde en una operación condicional para determinar el color de relleno del botón. Note que la asignación de un valor a una propiedad es posible utilizando el signo igual '=', en adición al enlace de de valores utilizando el carácter dos puntos ':'. Las propiedades personalizadas permiten que los elementos internos sean accesibles desde fuera del ámbito del Rectángulo. Existen tipos básicos QML como int, string, real y también un tipo llamado variant.

Mediante el enlace de los manejadores de señal onEntered y onExited, el borde de los botones cambiará a amarillo cuando el mouse se coloque encima del botón y el cambio de color se revertirá cuando el mouse salga del área de mouse.

La signal buttonClick está declarada en Button.qml mediante la colocación de la palabra clave signal frente al nombre de la signal Los manejadores de las signals se crean automáticamente con su nombre comenzando con on. Como resultado, onButtonClick es el manejador de buttonClick. Entonces a onButtonClick se le asigna una acción para realizar. En nuestro ejemplo de botón, el manejador de onClicked simplemente invocará onButtonClick, el cual mostrará algún texto. onButtonClick permite que los objetos externos accedan al área de mouse del botón de manera sencilla. (?)Por ejemplo, los elementos podrían tener más una declaración de MouseArea y una signal buttonClick puede mejorar la distinción entre los distintos manejadores de signals del MouseArea(?).

Ahora tenemos el conocimiento básico para implementar elementos en QML que puedan manejar movimientos básicos del mouse. Creamos una etiqueta de Texto dentro de un Rectángulo, personalizamos sus propiedades, e implementamos comportamientos que responden a los movimientos del mouse. Está idea de crear elementos dentro de elementos se repite a través de toda la aplicación del editor de textos.

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

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.png

Creando una Página de Menú

Hasta este momento, hemos cubierto como crear elementos y asignar comportamientos dentro de un sólo archivo QML. En esta sección, cubriremos como importar elementos QML y como reutilizar algunos de los componentes creados para construir otros componentes.

Los Menús muestran el contenido de una lista, cada elemento posee la habilidad de realizar alguna acción. En QML, podemos crear un menú de varias maneras. Primero, crearemos un menú que contenga 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 mostrada arriba muestra como utilizar la palabra clave import. Ésto se requiere para utilizar archivos JavaScript o QML que no están en el mismo directorio. Debido a que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para utilizarlo. Podemos directamente crear un elemento Button declarando Button{}, similar a la declaración de Rectangle{}.

En 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. Están declarados dentro de un elemento Row (Fila), el cual posicionará a sus hijos a lo largo de una fila vertical. La declaración del Botón reside en Button.qml, el cual es el mismo Button.qml que utilizamos en la sección previa. Se pueden declarar nuevos enlaces de propiedades dentro de los nuevos botones creados, sobrescribiendo efectivamente las propiedades establecidas en Button.qml. El botón llamado exitButton (Botón Salir) cerrará la ventana y terminará la aplicación cuando se presione. Note que el manejador de signal onButtonClick en Button.qml será invocado en adición al manejador onButtonClick en exitButton.

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png

La declaración de Row está declarada dentro de un Rectangle, creando un rectángulo contenedor para las filas de botones. Este rectángulo adicional crea una manera indirecta de organizar las filas de botones dentro de un menú

La declaración del menú edit (editar) es muy similar en esta etapa. El menú tiene botones con las etiquetas: Copy (Copiar), Paste (Pegar), y Select All (Seleccionar Todo).

http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png

Armados con nuestro conocimiento de importar y personalizar componentes previamente creados, ahora podemos combinar estas páginas de menú para crear una barra de menús, que consiste en botones para seleccionar el menú, y mire como podemos estructurar datos utilizando QML.

Implementando una Barra de Menús

Nuestra aplicación de editor de textos necesitará una manera de mostrar menús utilizando una barra de menús. La barra de menús realizará los cambios entre los diferentes menús y el usuario puede elegir cual menú desplegar. El cambió de menú implica que los menús necesitan más estructura que simplemente mostrarlos en una fila. QML utiliza modelos y vistas para estructurar datos y mostrar los datos estructurados.

Utilizando Modelos de Datos y Vistas

QML tiene diferentes vistas de datos para mostrar modelos. Nuestra barra de menús mostrará los menús en una lista, con el encabezado mostrando una fila con los nombres de los menús. La lista de menús se declara dentro de un VisualItemModel. El elemento VisualItemModel contiene elementos que ya tienen vistas, como elementos Rectangle y elementos de Interfaz de Usuario importados. Otrostipos de modelo como el elemento ListModel necesitan un delegate para mostrar su información.

Declaramos dos elementos visuales en el menuListModel, el FileMenu y el EditMenu. Personalizamos los dos menús y los mostramos utilizando una ListView. El archivo MenuBar.qml contiene las declaraciones QML y en EditMenu.qml un simple menu edit está definido.

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 delegate. El delegate declarará los elemento del modelo de manera que se muestren en un elemento Row o en un grid. Nuestro menuListModel ya tiene elementos visibles, por lo tanto, no necesitamos declarar un delegate.

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 al arrastre del mouse y otros gestos. La última porción del código de arriba establece las propiedades de Flickable para crear el movimiento de deslizamiento (flicking) que deseamos en nuestra vista. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de deslizamiento. Un valor más alto de la propiedad highlightMoveDuration da como resultado un cambio de menú más lento.

La ListView mantiene los elementos del modelo a través de un índice y cada elemento visual en el modelo es accesible a través de un índice con el valor del orden en que se declararon los elementos. Cambiar la propiedad currentIndex (índice actual) cambia el elemento resaltado en la ListView. El encabezado de nuestra barra de menú ejemplifica este efecto. Hay dos botones en una fila, ambos cambian el menú actual cuándo se presionan. El fileButton cambia el menú actual al menú file cuando se presiona, el índice será 0 debido a que FileMenu se declara primero en el menuListModel. De manera similar, el editButton cambiará el menú actual a EditMenu cuando se presiona.

El rectángulo labelList tiene valor z de 1, denotando que será mostrado frente a la barra de menús. Los elementos con un valor de z más alto se muestran por sobre elementos con valores menores de z. El valor por defecto para z 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
 }
 }
}

Podemos acceder a los menús de la barra de herramientas que acabamos de crear deslizando hacia los menús o presionando los nombres de los menús de la parte superior. El cambio entre las pantallas del menú se siente intuitivo y responsivo.

http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png

Construyendo un Editor de Texto

Declarando un Área de Texto (TextArea)

Nuestro editor de texto no es un editor de texto si no contiene un área para editar texto. El elemento TextEdit de QML permite la declaración de un área multilínea de texto editable. TextEdit es diferente de un elemento Text, el cual no permite al usuario editar el texto directamente.

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 establecidas las propiedades de color y ajuste de texto. El área de edición de texto está dentro de un área deslizable que desplazará el texto si el cursor está fuera del área visible. La función ensureVisible() verificará si el rectángulo del cursor está fuera de los límites de visibilidad y moverá el área de texto de acuerdo a esto. QML utiliza sintáxist de Javascript para sus scripts, y como se menciono previamente, los archivos Javascript se pueden importar y utilizar en 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

Ahora estamos listos para crear el diseño de nuestro editor de texto utilizando QML. El editor de texto tiene dos componentes, la barra de menús que creamos y el área de texto. QML nos permite reutilizar componentes, haciendo nuestro código más simple, importanto componentes y personalizándolos cuándo sea necesario. Nuestro editor de texto divide la ventana en dos; un tercio de la pantalla está dedicado a la barra de menús y dos tercios de la pantalla muestran el área de texto. La barra de menús se muestra en frente de cualquier otro elementos.

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

Mediante la importación de componentes reutilizables, el código de nuestro Editor de Texto se ve mucho más simple. Ahora podemos personalizar la aplicación principal sin preocuparnos acerca de propiedades que ya tiene comportamientos definidos. Utilizando este enfoque, se puede crear fácilmente tanto el diseño de la aplicación como componentes de UI.

http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png

Decorando el Editor de texto

Implementando una Interfaz Drawer

Nuestro Editor de Texto parece simple y por lo tanto es necesario que lo decoremos. Utilizando QML, podemos declarar las transiciones y la animación de nuestro Editor de Texto. Nuestra barra de menú está ocupando un tercio de la pantalla y seria bueno que solo apareciera cuando lo deseemos.

Podemos añadir una Interfaz Drawer, que minimice o expanda la barra de menú cuando se hace click. En nuestra implementación, tenemos un rectángulo delgado que responde a los click del mouse. El drawer, así como la aplicación, tienen dos estados: el estado "drawer is open" y el estado "drawer is closed". El elemento drawer es una franja del rectángulo con una pequeña altura. Hay un elemento anidado Imagen que declara que un icono arrow será centrado dentro del drawer. El drawer asigna un estado a toda la aplicación, con el identificador de pantalla(screen), cada vez que un usuario haga clic en el área del mouse.

 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 las cuales son declaradas en un elemento State. Una lista de los estados pueden ser listadas y ligadas a la propiedad states. En nuestras aplicación, los dos estados son lamados DRAWER_OPEN Y DRAWER_CLOSED. Las configuraciones de los elementos son declaradas en la PorpertyChanges. En el estados DRAWER_OPEN, hay cuatro elementos que recibirán los cambios de propiedad (PropertyChanges). El primer objeto, menuBar cambiará su y propiedad a 0. Del mismo modo, el objeto textArea descenderá auna nueva posición cuando el estado DRAWER_OPEN este activado. La textArea, el drawer, y el icono de drawer se someterán a los cambios de propiedad para cumplir con el 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 bruscos por ello es recomendable aplicar transiciones para suavizarlos. Las transiciones entre los estados se definen mediante el elemento Transition, que puede enlazarse a los elementos dentro de la propiedad transitions. Nuestro editor de texto tiene una transición de estado cuando el estado cambia a DRAWER_OPEN o DRAWER_CLOSED. Es importante destacar que es necesario un estado de transición from y un to, pero para nuestra transición, podemos utilizar el símbolo * para indicar que la transición se aplica a todos los cambios de estado.

Durante las transiciones, podemos asignar animaciones a los cambios de propiedad. Nuestra menuBar cambia de la posición de y:0 a y:-partition y se puede animar esta transición mediante el elemento NumberAnimation. Declaramos que las propiedades de los objetos van a animarse por un período de tiempo determinado y con una curva de aceleración determinada. Una curva de aceleración controla el comportamiento de las tasas de animación y la interpolación en las transiciones de estado. La curva de aceleración que elegimos es Easing.OutQuint, que retarda el movimiento hacia el final de la animación. Lea atentamente el artículo "QML's Animation":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 }
 }
 ]

Otra manera de animar a los cambios de propiedad es declarando un elemento de comportamiento ("Behavior":http://doc.qt.nokia.com/4.7/qml-behavior.html). Una transición sólo funciona durante los cambios de estado y el comportamiento puede establecer una animación para un cambio de propiedad en general. En el editor de texto, la flecha tiene un comportamiento NumberAnimation que anima su propiedad rotation cada vez que cambia la propiedad.

In TextEditor.qml:

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

Volviendo a nuestros componentes con el conocimiento de los estados y las animaciones, podemos mejorar la apariencia de los componentes. En Button.qml, podemos añadir los cambios a la propiedad color y scale cuando se pulsa el botón. Los tipos color son animados usando ColorAnimation y los números son animados con NumberAnimation. La sintaxis de propertyName on que aparecen a continuación es de gran ayuda al elegir una sola 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} }

Además, podemos mejorar la apariencia de nuestros componentes QML mediante la adición de efectos de color tales como gradientes y efectos de opacidad. La declaración de un elemento Gradient anulará la propiedad color del elemento. Usted puede declarar un color en el gradient mediante el elemento GradientStop. El gradiente se coloca utilizando 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 utilizado por la barra de menús para mostrar una simulación del gradiente de profundidad. El primer color empieza en 0.0 y el último color es de 1.0.

A dónde ir desde aquí

Hemos terminado la construcción de la interfaz de usuario de un editor de texto muy simple. En el futuro, se completara la interfaz de usuario, y podemos aplicar la lógica de la aplicación haciendo uso regular de Qt y C+. QML funciona muy bien como una herramienta de creación de prototipos, que separa la lógica de la aplicación de distancia de el diseño de interfaz de usuario.

http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png

h3. Extendiendo QML utilizando C+

Ahora que tenemos el diseño de nuestro editor de textos, podemos implementar su funcionalidad utilizando C+. Utilizar QML con C+ nos permite crear la lógica de nuestra aplicación utilizando Qt. Podemos crear un contexto QML en una aplicación C++ utilizando las clases de Qt Declarative y mostrar los elementos utilizando un Graphics Scene. De manera alternativa, podemos exportar nuestro código C++ en un plugin que la herramienta qmlviewer puede leer. Para nuestra aplicación, implementaremos las funciones abrir y guardar en C++ y las exportaremos como un plugin. De esta manera, podremos cargar el archivo QML de manera directa en lugar de utilizar un ejecutable.

Exponiendo clases C++ a QML

Implementaremos la apertura y guardado de archivos utilizando Qt y C+. Al registrar las clases y funciones de C+ podemos utilizarlas en QML. La clase necesita ser compilada como un plugin de Qt y el archivo QML necesita saber en dónde se localiza el plugin.

Para nuestra aplicación, necesitamos crear los siguientes elementos:

  1. Una clase Directory que se encargará de las operaciones relacionadas con los directorios
  2. Una clase File el cual es un QObject, que simula la lista de archivos en un directorio
  3. Una clase plugin que registrará la clase en el contexto QML
  4. Un archivo de proyecto de Qt para compilar el plugin
  5. Un archivo qmldir que le indique a la herramienta qmlviewer dónde encontrar el plugin

Construyendo un plugin de Qt

Para construir un plugin, necesitamos establecer lo siguiente en un archivo de proyecto de Qt. Primero, es necesario agregar los archivos de encabezado, de código fuente y los módulos de Qt en nuestro archivo de proyecto. Todos los archivos de proyecto y de código C++ están ene 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 plugin, para esto necesitamos utilizar una plantilla de biblioteca (lib). Pondremos el plugin en el directorio de plugins del padre.

Registrando una Clase en QML

 In dialogPlugin.h:

#include <QtDeclarative/QDeclarativeExtensionPlugin>

class DialogPlugin : public QDeclarativeExtensionPlugin
 {
 Q_OBJECT

public:
 void registerTypes(const char *uri);

};

Nuestra clase plugin, DialogPlugin es una subclase de "QDeclarativeExtensionPlugin":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html. Necesitamos implementar la función heredada, registerTypes(). El archivo dialogPlugin.cpp es similar a esto:

 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);

La función "registerTypes()":http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#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 mayor, un número de versión menor y un nombre para nuestras clases.

Necesitamos exportar el complemento utilizando la macro "Q_EXPORT_PLUGIN2":http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2#q-export-plugin2 macro. Note que en nuestro archivo dialogPlugin.h, tenemos la macro "Q_OBJECT":http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT en la parte superior de nuestra clase. Y también, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario.

h3. Creando Propiedades QML en una clase C++

Podemos crear elementos y propiedades QML usando el "Sistema de Meta-Objetos de Qt":http://doc.qt.nokia.com/4.7/metaobjects.html. Podemos implementar propiedades utilizando signals y slots, haciendo que Qt esté conciente de estas propiedades. Entonces estas propiedades podrán ser utilizadas 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 utilizar "QDir":http://doc.qt.nokia.com/4.7/qdir.html, "QFile":http://doc.qt.nokia.com/4.7/qfile.html, y "QTextStream":http://doc.qt.nokia.com/4.7/qtextstream.html para implementar la lectura de directorios y flujos 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 Directory utiliza el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para realizar el manejo de archivos. La clase Directory se exporta como un plugin y es utilizable en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY es una propiedad QML.

La macro "Q_PROPERTY":http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY declara una propiedad y sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename, de tipo QString, puede ser leída utilizando la función filename() y puede ser escrita utilizando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite cuando la propiedad cambia. Las funciones de lectura y escritura están declaradas 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 )<code>

La propiedad files 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 los archivos con extensión .txt son válidos. Además, las "QLists":http://doc.qt.nokia.com/4.7/qlist.html se pueden utilizar en archivos QML al declararlas como QDeclarativeListProperty en C+''. El objeto plantilla necesita heredar de QObject, por lo tanto, 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 entonces utilizarse en QML como parte de las propiedades del elemento Directory. Note que no necesitamos 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 obtener sus propiedades. Para obtener la propiedad nombre del primer archivo, podemos llamar a files[0].name.

Las funciones normales de C''+ también son accesibles desde QML. Las funciones cargar y guardar un archvio están implementadas en C++ y se declaran utilizando la macro "Q_INVOKABLE":http://doc.qt.nokia.com/4.7/qobject.html#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":http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
La clase Directory también tiene que notificar a otros objetos cuando el contenido del directorio cambie. Esta función se realiza utilizando una signal. Como se mencionó previamente, las signals de QML tienen un manejador correspondiente con sus nombres precedidos por "on". La signal tiene el nombre de directoryChanged y se emite cuando ocurre una actualización en un directorio. La actualización simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los elementos de QML pueden entonces ser notificados asignando una acción al manejador de signal onDirectoryChanged.

Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista utilizan 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 una QDeclarativeListProperty<File>. El tipo plantilla, File, necesita heredar de QObject. Además, para crear una "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html, los métodos de acceso y modificación de la lista necesitan enviarse al constructor como apuntadores a función. La lista, una QList en nuestro caso, también necesita ser una lista de apuntadores a File.

El constructor de "QDeclarativeListProperty":http://doc.qt.nokia.com/4.7/qdeclarativelistproperty.html 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 envía apuntadores a funciones que agregan elementos al final de la lista, cuentan sus elementos, obtienen un elemento utilizando un índice y vacían la lista. Solo la función de agregar es obligatoria. Note que los apuntadores a función deben coincidir con la definición 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, o "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 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. La clase Directory utiliza QTextStream para leer el archivo y para sacar el contenido del archivo a otro archivo.

Con nuestro elemento Directory, podemos obtener 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 cuando ocurra algún cambio en el contenido del directorio.

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

h3. Importando un Plugin 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 plugins y otros recursos.

In qmldir:

Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml
plugin FileDialog plugins
El plugin que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El plugin compilado está en el directorio plugins.

h3. Integrando un Diálogo Archivo en el Menú Archivo

Nuestro FileMenu necesita mostrar el elemento FileDialog, el cual contiene una lista de archivos de texto en un directorio, esto permite 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 capturar el nombre de un archivo utilizando el teclado. 

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

In FileMenu.qml:

Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
Manteniendo 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 elementos de datos en una rejilla de acuerdo a un delegate. El delegate maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una rejilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la aparición del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica cuando se emite la signal 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 elemento saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y luego copiará 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.

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

http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png

h3. Finalización del Editor de Texto

http://doc.qt.nokia.com/4.7/images/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 también puede cargar contenido desde un archivo y realizar manipulación de texto.

Visita la segunda parte de este tutorial introductorio* Introducción a la Programación con QML Parte 2