Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.
Getting Started Programming with QML/es-AR: Difference between revisions
No edit summary |
AutoSpider (talk | contribs) m (AutoSpider moved page GettingStartedProgrammingwithQML es-AR to Getting Started Programming with QML/es-AR: Localisation) |
||
(7 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
{{Cleanup | reason=Auto-imported from ExpressionEngine.}} | |||
[[Category:Learning]] | [[Category:Learning]] | ||
Line 4: | Line 6: | ||
Bienvenido al mundo de QML, el lenguaje declarativo para Interfaces de Usuario (IU). En esta guía Comenzar a Programar, crearemos una simple aplicación para editar textos usando QML. Luego de leer esta guía, deberías estar listo para desarrollar tus propias aplicaciones usando QML y Qt C+''. | Bienvenido al mundo de QML, el lenguaje declarativo para Interfaces de Usuario (IU). En esta guía Comenzar a Programar, crearemos una simple aplicación para editar textos usando QML. Luego de leer esta guía, deberías estar listo para desarrollar tus propias aplicaciones usando QML y Qt C+''. | ||
=== QML para Armar Interfaces de Usuario === | |||
La aplicación que estamos armando es un simple editor de texto que cargará, guardará y realizará alguna manipulación de texto. Esta guía consiste de dos partes. La primera parte involucrará desarrollar el diseño de la aplicación y los comportamientos usando lenguaje declarativo en QML. Para la segunda parte, cargar y guardar el archivo serán implementados usando Qt C. Al usar el [http://doc.qt.nokia.com/4.7/metaobjects.html Sistema de Meta-Objetos de Qt], podemos exponer funciones de C''+ como propiedades que los elementos de QML pueden usar. Usando QML y Qt C++ podemos desacoplar de manera eficiente la lógica de la interface de la lógica de la aplicación. | |||
http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png | |||
Capítulos del Tutorial: | Para ejecutar el código QML de ejemplo, simplemente provee a la herramienta incluida [http://doc.qt.nokia.com/4.7/qmlviewer.html qmlviewer] el nombre del archivo QML como argumento. La parte de C++ de este tutorial asume que el lector posee conocimiento básico de los procedimientos de compilación de Qt. | ||
Capítulos del Tutorial: | |||
# Definiendo un Botón y un Menú | |||
# Implementando una Barra de Menú | |||
# Armando un Editor de Texto | |||
# Decorando un Editor de Texto | |||
# Extendiendo QML al usar Qt C++ | |||
=== Definiendo un Botón y un Menú === | === Definiendo un Botón y un Menú === | ||
Line 19: | Line 27: | ||
Comenzamos nuestro editor de texto armando un botón. Funcionalmente un butón tiene un área sensible al ratón (''mousse'') y una etiqueta. Los botones realizan acciones cuando un usuario presiona el botón. | Comenzamos nuestro editor de texto armando un botón. Funcionalmente un butón tiene un área sensible al ratón (''mousse'') y una etiqueta. Los botones realizan acciones cuando un usuario presiona el botón. | ||
En QML, el ítem visual básico es el elemento | En QML, el ítem visual básico es el elemento [http://doc.qt.nokia.com/4.7/qml-rectangle.html Rectangle]. El elemento Rectangle tiene propiedades para controlar la apariencia del elemento y su ubicación. | ||
Primero, la instrucción import Qt 4.7 permite que la herramienta qmlviewer importe los elementos que usaremos luego. Esta línea debe existir en cada archivo QML. Tener presente que la versión de los módulos Qt se incluye en la instrucción import. | Primero, la instrucción import Qt 4.7 permite que la herramienta qmlviewer importe los elementos que usaremos luego. Esta línea debe existir en cada archivo QML. Tener presente que la versión de los módulos Qt se incluye en la instrucción import. | ||
Line 29: | Line 37: | ||
Guardamos este código como SimpleButton.qml. Ejecutando qmlviewer con el nombre del archivo como argumento mostrará el rectángulo gris con la etiqueta de texto. | Guardamos este código como SimpleButton.qml. Ejecutando qmlviewer con el nombre del archivo como argumento mostrará el rectángulo gris con la etiqueta de texto. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png | |||
Para implementar la funcionalidad del click del botón, podemos usar el manejo de eventos de QML. El manejo de eventos de QML es muy similar al mecanismo de | Para implementar la funcionalidad del click del botón, podemos usar el manejo de eventos de QML. El manejo de eventos de QML es muy similar al mecanismo de [http://doc.qt.nokia.com/4.7/signalsandslots.html señales y ranuras de Qt]. Se emiten señales y las ranuras conectadas son invocadas. | ||
<code>Rectangle{ | <code>Rectangle{ | ||
id:simplebutton | |||
… | |||
MouseArea{ | MouseArea{ | ||
id: buttonMouseArea | |||
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors | anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors | ||
//onClicked handles valid mouse button clicks | |||
onClicked: console.log(buttonLabel.text + " clicked" ) | |||
} | |||
}</code> | |||
Incluimos un elemento | Incluimos un elemento [http://doc.qt.nokia.com/4.7/qml-mousearea.html MouseArea] (área del ratón) n nuestro simplebutton. Los elementos [http://doc.qt.nokia.com/4.7/qml-mousearea.html MouseArea] describen el área interactiva donde se detectan los movimientos del ratón. Para nuestro botón, anclamos el elemento MouseArea completo a su padre, el cual es simplebutton. La sintaxis anchors.fill es una manera de acceder a una propiedad específica llamada fill dentro de un grupo de propiedades llamado anchors. QML usa [http://doc.qt.nokia.com/4.7/qml-anchor-layout.html diseños basados en anclas] donde los ítems pueden anclarse unos a otros, creando diseños robustos. | ||
El elemento MouseArea tiene muchos manejadores de señales que se llaman durante los movimientos del ratón dentro de los límites especificados para el elemento MouseArea. Uno de ellos es onClicked y se llama cada vez que el botón apropiado del ratón se pulsa, siendo el izquierdo el valor por omisión. Podemos ligar acciones al manipulador onClicked. En nuestro ejemplo, console.log() muestra texto siempre que se hace click en el área del ratón. La función console.log() es una herramienta útil para propósitos de depuración (debugging) y para mostrar texto. | El elemento MouseArea tiene muchos manejadores de señales que se llaman durante los movimientos del ratón dentro de los límites especificados para el elemento MouseArea. Uno de ellos es onClicked y se llama cada vez que el botón apropiado del ratón se pulsa, siendo el izquierdo el valor por omisión. Podemos ligar acciones al manipulador onClicked. En nuestro ejemplo, console.log() muestra texto siempre que se hace click en el área del ratón. La función console.log() es una herramienta útil para propósitos de depuración (debugging) y para mostrar texto. | ||
Line 45: | Line 60: | ||
El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla y emitir texto cuando se selecciona mediante un click con el ratón. | El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla y emitir texto cuando se selecciona mediante un click con el ratón. | ||
<code> Rectangle { | <code> Rectangle { | ||
id:Button | |||
… | |||
property color buttonColor: | property color buttonColor: "lightblue" | ||
property color onHoverColor: "gold" | |||
property color borderColor: "white" | |||
signal buttonClick() | signal buttonClick() | ||
onButtonClick: { | |||
console.log(buttonLabel.text + " clicked" ) | |||
} | |||
MouseArea{ | MouseArea{ | ||
onClicked: buttonClick() | |||
hoverEnabled: true | |||
onEntered: parent.border.color = onHoverColor | |||
onExited: parent.border.color = borderColor | |||
} | |||
//determines the color of the button by using the conditional operator | //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 está en Button.qml. Los fragmentos de código en este artículo tienen partes del código omitidas, denotadas por puntos suspensivos porque ellas fueron ya sea presentadas en secciones previas o no son relevantes para la discusión actual del código. | Un botón completamente funcional está en Button.qml. Los fragmentos de código en este artículo tienen partes del código omitidas, denotadas por puntos suspensivos porque ellas fueron ya sea presentadas en secciones previas o no son relevantes para la discusión actual del código. | ||
Las propiedades Custom se declaran usando la sintaxis property type. En el código, la propiedad buttonColor, de tipo color, se declara y liga al valor | Las propiedades Custom se declaran usando la sintaxis property type. En el código, la propiedad buttonColor, de tipo color, se declara y liga al valor "lightblue" (celeste). buttonColor se usa más adelante en una operación condicional para determinar el color de relleno del botón. Notar que la asignación de valor a la propiedad es posible usando el signo = (igual), además de ligar el valor usando el caracter ':' (dos puntos). Las propiedades Custom permiten acceder a ítems internos fuera del alcance del elemento Rectangle. Hay tipos básicos de QML tales como int, string, real, como también un tipo llamado variant. | ||
Al vincular los manejadores de señales onEntered y onExited a colores, el borde del botón se volverá amarillo cuando el mouse pase sobre el botón y volverá al otro color cuando el ratón salga del área del ratón. | Al vincular los manejadores de señales onEntered y onExited a colores, el borde del botón se volverá amarillo cuando el mouse pase sobre el botón y volverá al otro color cuando el ratón salga del área del ratón. | ||
Line 67: | Line 96: | ||
Este botón no es útil a menos que se use como un componente para realizar una acción. En la próxima sección, pronto crearemos un menú conteniendo varios de estos botones. | Este botón no es útil a menos que se use como un componente para realizar una acción. En la próxima sección, pronto crearemos un menú conteniendo varios de estos botones. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.png | |||
=== Creando una Página de Menú === | === Creando una Página de Menú === | ||
Line 73: | Line 102: | ||
Hasta este momento, hemos cubierto como crear elementos y asignar comportamientos dentro de un único archivo QML. En esta sección, cubriremos ccomo importar elementos QML y como reusar algunos de los componentes creados para armar otros componentes. | Hasta este momento, hemos cubierto como crear elementos y asignar comportamientos dentro de un único archivo QML. En esta sección, cubriremos ccomo importar elementos QML y como reusar algunos de los componentes creados para armar otros componentes. | ||
Los menúes muestran el contenido de una lista, con cada ítem teniendo la capacidad de realizar una acción. En QML, podemos crear un menú de varias formas. Primero, crearemos un menú que contiene botones los cuales eventualmente realizarán diferentes acciones. | Los menúes muestran el contenido de una lista, con cada ítem teniendo la capacidad de realizar una acción. En QML, podemos crear un menú de varias formas. Primero, crearemos un menú que contiene botones los cuales eventualmente realizarán diferentes acciones. | ||
El código del menú está en FileMenu.qml. | |||
<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 arriba muestra como usar la palabra reservada import. Esto se requiere para usar archivos JavaScript, o archivos QML que no están en el mismo directorio. Ya que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para usarlo. Directamente podemos crear un elemento Button declarando Button{}, de manera similar a la declaración Rectangle{}. | La sintaxis arriba muestra como usar la palabra reservada import. Esto se requiere para usar archivos JavaScript, o archivos QML que no están en el mismo directorio. Ya que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para usarlo. Directamente podemos crear un elemento Button declarando Button{}, de manera similar a la declaración Rectangle{}. | ||
Line 79: | Line 112: | ||
<code> In FileMenu.qml: | <code> In FileMenu.qml: | ||
Row{ | Row{ | ||
anchors.centerIn: parent | |||
spacing: parent.width/6 | |||
Button{ | Button{ | ||
id: loadButton | |||
buttonColor: "lightgrey" | |||
label: "Load" | |||
} | |||
Button{ | |||
buttonColor: "grey" | |||
id: saveButton | |||
label: "Save" | |||
} | |||
Button{ | |||
id: exitButton | |||
label: "Exit" | |||
buttonColor: "darkgrey" | |||
onButtonClick: Qt.quit() | onButtonClick: Qt.quit() | ||
} | |||
}</code> | |||
En FileMenu.qml, declaramos tres elementos Button. Se declaran dentro de un elemeto Row, un posicionador que ubicará sus hijos en una fila vertical. La declaración Button permanece en Button.qml, que es el mismo que el archivo Button.qml que usamos previamente. Pueden declararse nuevas vinculaciones a propiedades dentro de los nuevos botones creados, sobreescribiendo efectivamente las propiedades establecidas en Button.qml. El botón exitButton terminará y cerrará la ventana cuando sea seleccionado. Notar que el manejador de señal onButtonClick en Button.qml será llamado además del manejador onButtonClick en exitButton. | En FileMenu.qml, declaramos tres elementos Button. Se declaran dentro de un elemeto Row, un posicionador que ubicará sus hijos en una fila vertical. La declaración Button permanece en Button.qml, que es el mismo que el archivo Button.qml que usamos previamente. Pueden declararse nuevas vinculaciones a propiedades dentro de los nuevos botones creados, sobreescribiendo efectivamente las propiedades establecidas en Button.qml. El botón exitButton terminará y cerrará la ventana cuando sea seleccionado. Notar que el manejador de señal onButtonClick en Button.qml será llamado además del manejador onButtonClick en exitButton. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png | |||
La declaración Row se realiza en un elemento Rectangle, creando un rectágulo contenedor para la fila de botones. Este rectángulo adicional crea una manera indirecta de organizar la fila de botones dentro de un menú. | La declaración Row se realiza en un elemento Rectangle, creando un rectágulo contenedor para la fila de botones. Este rectángulo adicional crea una manera indirecta de organizar la fila de botones dentro de un menú. | ||
Line 93: | Line 143: | ||
La declaración del menu editar es muy similar a esta etapa. El menú tiene botones con las etiquetas: Copiar, Pegar y Seleccionar Todo. | La declaración del menu editar es muy similar a esta etapa. El menú tiene botones con las etiquetas: Copiar, Pegar y Seleccionar Todo. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png | |||
Armados con nuestro conocimiento de importar y customizing componentes creados previamente, ahora podemos combinar estas páginas de menu para crear una barra de menú, que consiste de botones para seleccionar el menú, y mirar como podemos estructurar los datos usando QML. | Armados con nuestro conocimiento de importar y customizing componentes creados previamente, ahora podemos combinar estas páginas de menu para crear una barra de menú, que consiste de botones para seleccionar el menú, y mirar como podemos estructurar los datos usando QML. | ||
Line 107: | Line 157: | ||
Declaramos dos ítems visuales en el elemento menuListModel, el ítem FileMenu y el ítem EditMenu. Personalizamos los dos menúes y los mostramos usando un elemento ListView. El archivo MenuBar.qml file contiene las declaraciones QML y un menú simple para editar se define en el archivo EditMenu.qml. | Declaramos dos ítems visuales en el elemento menuListModel, el ítem FileMenu y el ítem EditMenu. Personalizamos los dos menúes y los mostramos usando un elemento ListView. El archivo MenuBar.qml file contiene las declaraciones QML y un menú simple para editar se define en el archivo EditMenu.qml. | ||
<code>VisualItemModel{ | <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 delegado. El delegado puede declarar los ítems del modelo a mostrar en un elemento Row o mostrar los ítems en una grilla. Nuestro menuListModel ya tiene ítems visible, y de ese modo, no necesitamos declarar un delegado. | El elemento ListView mostrará un modelo de acuerdo a un delegado. El delegado puede declarar los ítems del modelo a mostrar en un elemento Row o mostrar los ítems en una grilla. Nuestro menuListModel ya tiene ítems visible, y de ese modo, no necesitamos declarar un delegado. | ||
<code>ListView{ | <code>ListView{ | ||
id: menuListView | |||
//Anchors are set to react to window anchors | //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 | //the model contains the data | ||
model: menuListModel | |||
//control the movement of the menu switching | //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 a arrastres del ratón y otros gestos. La última parte del código arriba ajustas propiedades del elemento Flickable para crear en nuestra vista el movimiento de parpadeo deseado. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de parpadeo. Un valor highlightMoveDuration mayor resulta en un cambio de menú más lento. | Adicionalmente, ListView hereda de Flickable, haciendo que la lista responda a arrastres del ratón y otros gestos. La última parte del código arriba ajustas propiedades del elemento Flickable para crear en nuestra vista el movimiento de parpadeo deseado. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de parpadeo. Un valor highlightMoveDuration mayor resulta en un cambio de menú más lento. | ||
Line 125: | Line 201: | ||
El rectángulo labelList tiene un valor z de 1, denotando que se muestra al frente de la barra de menú. Ítems con valores z mayores se muestran al frente de ítems con valores z menores. El valor de z por omisión es 0. | El rectángulo labelList tiene un valor z de 1, denotando que se muestra al frente de la barra de menú. Ítems con valores z mayores se muestran al frente de ítems con valores z menores. El valor de z por omisión es 0. | ||
<code>Rectangle{ | <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> | |||
La barra de menú que hemos creado puede tener parpadeo para acceder a los menúes o al hacer click sobre los nombres del menú en la parte superior. El cambio de pantallas de menúes se siente intuitivo y con respuesta. | La barra de menú que hemos creado puede tener parpadeo para acceder a los menúes o al hacer click sobre los nombres del menú en la parte superior. El cambio de pantallas de menúes se siente intuitivo y con respuesta. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png | |||
=== Armando un Editor de Texto === | === Armando un Editor de Texto === | ||
Line 137: | Line 233: | ||
Nuestro editor de texto no es un editor de texto si no contiene un área de texto que se pueda editar. El elemento TextEdit de QML permite la declaración de un área de texto editable multi-línea. TextEdit es diferente de un elemento Text, el cual no permite que el usuario edite directamente el texto. | Nuestro editor de texto no es un editor de texto si no contiene un área de texto que se pueda editar. El elemento TextEdit de QML permite la declaración de un área de texto editable multi-línea. TextEdit es diferente de un elemento Text, el cual no permite que el usuario edite directamente el texto. | ||
<code> TextEdit{ | <code> TextEdit{ | ||
id: textEditor | |||
anchors.fill:parent | |||
width:parent.width; height:parent.height | |||
color:"midnightblue" | |||
focus: true | |||
wrapMode: TextEdit.Wrap | wrapMode: TextEdit.Wrap | ||
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle) | onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle) | ||
}</code> | |||
El editor tiene su propiedad color de fuente establecida y también para wrap el texto. El área TextEdit está dentro de un área parpadeante que se desplazará si el cursor de texto está fuera del área visible. La función ensureVisible() comprobará si el rectángulo del cursor está fuera de los límites visibles y moverá el área de texto de manera apropiada. QML usa sintaxis de Javascript para sus secuencias de comandos (scripts), y como se mencionó anteriormente, pueden importarse archivos Javascript y ser usados dentro de un archivo QML. | El editor tiene su propiedad color de fuente establecida y también para wrap el texto. El área TextEdit está dentro de un área parpadeante que se desplazará si el cursor de texto está fuera del área visible. La función ensureVisible() comprobará si el rectángulo del cursor está fuera de los límites visibles y moverá el área de texto de manera apropiada. QML usa sintaxis de Javascript para sus secuencias de comandos (scripts), y como se mencionó anteriormente, pueden importarse archivos Javascript y ser usados dentro de un archivo QML. | ||
<code> function ensureVisible®{ | <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 153: | Line 264: | ||
<code> Rectangle{ | <code> Rectangle{ | ||
id: screen | 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 | //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{ | MenuBar{ | ||
id:menuBar | |||
height: partition | |||
width:parent.width | |||
z: 1 | |||
} | |||
TextArea{ | TextArea{ | ||
id:textArea | |||
anchors.bottom:parent.bottom | |||
y: partition | |||
color: "white" | |||
height: partition*2 | |||
width:parent.width | |||
} | |||
}</code> | |||
Al importar componentes reutilizables, nuestro código TextEditor luce mucho más simple. Podemos entonces personalizar la aplicación principal, sin preocuparnos acerca de las propiedades que ya han definido comportamientos. Usando esta aproximación, los diseños de aplicaciones y componentes de IU se pueden crear fácilmente. | Al importar componentes reutilizables, nuestro código TextEditor luce mucho más simple. Podemos entonces personalizar la aplicación principal, sin preocuparnos acerca de las propiedades que ya han definido comportamientos. Usando esta aproximación, los diseños de aplicaciones y componentes de IU se pueden crear fácilmente. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png | |||
=== Decorando el Editor de Texto === | === Decorando el Editor de Texto === | ||
Line 171: | Line 297: | ||
Nuestro editor de texto luce simple y necesitamos decorarlo. Usando QML, podemos declarar transiciones y animar nuestro editor de texto. Nuestra barra de menú está ocupando un tercio de la pantalla y sería bueno hacerla aparecer cuando querramos. | Nuestro editor de texto luce simple y necesitamos decorarlo. Usando QML, podemos declarar transiciones y animar nuestro editor de texto. Nuestra barra de menú está ocupando un tercio de la pantalla y sería bueno hacerla aparecer cuando querramos. | ||
Podemos agregar una interface cajón, que contraerá o expandirá la barra de menú cuando se haga click sobre ella. En nuestra implementación, tenemos un rectángulo delgado que responde a los clicks del ratón. El cajón, como también la aplicación, tienen dos estados: el estado | Podemos agregar una interface cajón, que contraerá o expandirá la barra de menú cuando se haga click sobre ella. En nuestra implementación, tenemos un rectángulo delgado que responde a los clicks del ratón. El cajón, como también la aplicación, tienen dos estados: el estado "cajón está abierto" y el estado "cajón está cerrado". El ítem cajón es una tira de un rectángulo con una altura pequeña. Hay un elemento Image anidado que declara que un ícono de una flecha será centrado dentro del cajón. El cajón asigna un estado a toda la aplicación, con el identificador screen, siempre que un usuario hace click en el área del ratón. | ||
<code> Rectangle{ | <code> Rectangle{ | ||
id:drawer | |||
height:15 | |||
Image{ | Image{ | ||
id: arrowIcon | |||
source: "images/arrow.png" | |||
anchors.horizontalCenter: parent.horizontalCenter | |||
} | |||
MouseArea{ | MouseArea{ | ||
screen.state = | id: drawerMouseArea | ||
anchors.fill:parent | |||
else if (screen.state | onClicked:{ | ||
if (screen.state "DRAWER_CLOSED"){ | |||
screen.state = "DRAWER_OPEN" | |||
} | |||
else if (screen.state "DRAWER_OPEN"){ | |||
screen.state = "DRAWER_CLOSED" | |||
} | |||
} | |||
… | |||
} | |||
}</code> | |||
Un estado es simplemente una colección de configuraciones y se declara en un elemento State. Una lista de estdos pueden listarse y asociarse a la propiedad estados. En nuestra aplicación, los dos estados se llaman DRAWER_CLOSED y DRAWER_OPEN. Los ítems de as configuraciones se declaran en elementos PropertyChanges. En el estado DRAWER_OPEN, hay cuatro ítems que recibirán cambios de propiedades. El primer destino, menuBar, cambiará su propiedad y a 0. De manera similar, textArea bajará a una nueva posición cuando el estado es DRAWER_OPEN. textArea, el cajón, y el ícono del cajón sufrirán cambios de propiedades para ajustarse al estado actual. | Un estado es simplemente una colección de configuraciones y se declara en un elemento State. Una lista de estdos pueden listarse y asociarse a la propiedad estados. En nuestra aplicación, los dos estados se llaman DRAWER_CLOSED y DRAWER_OPEN. Los ítems de as configuraciones se declaran en elementos PropertyChanges. En el estado DRAWER_OPEN, hay cuatro ítems que recibirán cambios de propiedades. El primer destino, menuBar, cambiará su propiedad y a 0. De manera similar, textArea bajará a una nueva posición cuando el estado es DRAWER_OPEN. textArea, el cajón, y el ícono del cajón sufrirán cambios de propiedades para ajustarse al estado actual. | ||
<code> states:[ | <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 abruptos y necesitamos transiciones más suaves. Las transiciones entre estados se definen usando el elemento Transition, el cual puede luego asociarse a la propiedad transitions del ítem. Nuestro editor de texto tiene una transición de estado siempre que los estados cambian tanto a DRAWER_OPEN o a DRAWER_CLOSED. De manera importante, la transición necesita un estado desde y un estado hacia pero para nuestras transiciones, podemos usar el símbolo comodín * para denotar que la transición se aplica a todos los cambios de estado. | Los cambios de estado son abruptos y necesitamos transiciones más suaves. Las transiciones entre estados se definen usando el elemento Transition, el cual puede luego asociarse a la propiedad transitions del ítem. Nuestro editor de texto tiene una transición de estado siempre que los estados cambian tanto a DRAWER_OPEN o a DRAWER_CLOSED. De manera importante, la transición necesita un estado desde y un estado hacia pero para nuestras transiciones, podemos usar el símbolo comodín * para denotar que la transición se aplica a todos los cambios de estado. | ||
Line 190: | Line 347: | ||
Durante las transiciones, podemos asignar animaciones a la propiedad changes. Nuestra menuBar cambia la posición desde y:0 a y:-partition y podemos animar esta transición usando el elemento NumberAnimation. Declaramos que las propiedades de los destinos se animarán por un cierto período de tiempo y usamos una cierta curva de aceleración. Una curva de aceleración controla los ritmos de animación y el comportamiento de la interpolación durante las transiciones de estados. La curva de aceleración que elegimos es Easing.OutQuint, la cual enlentece el movimiento cerca del final de la animación. Por favor lee el artículo sobre Animación en QML. | Durante las transiciones, podemos asignar animaciones a la propiedad changes. Nuestra menuBar cambia la posición desde y:0 a y:-partition y podemos animar esta transición usando el elemento NumberAnimation. Declaramos que las propiedades de los destinos se animarán por un cierto período de tiempo y usamos una cierta curva de aceleración. Una curva de aceleración controla los ritmos de animación y el comportamiento de la interpolación durante las transiciones de estados. La curva de aceleración que elegimos es Easing.OutQuint, la cual enlentece el movimiento cerca del final de la animación. Por favor lee el artículo sobre Animación en QML. | ||
<code> transitions: [ | <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 cambios de propiedades es declarando un elemento Behavior. Una transición solo trabaja durante cambios de estado y Behavior puede establecer una animación para un cambio de una propiedad general. En el editor de texto, la flecha tiene un elemento NumberAnimation que anima su propiedad rotation siempre que la propiedad cambia. | Otra manera de animar cambios de propiedades es declarando un elemento Behavior. Una transición solo trabaja durante cambios de estado y Behavior puede establecer una animación para un cambio de una propiedad general. En el editor de texto, la flecha tiene un elemento NumberAnimation que anima su propiedad rotation siempre que la propiedad cambia. | ||
Line 196: | Line 360: | ||
<code> In TextEditor.qml: | <code> In TextEditor.qml: | ||
Behavior{ | Behavior{ | ||
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo } | |||
}</code> | |||
Volviendo a nuestros componentes con conocimiento de estados y animaciones, podemos mejorar el aspecto de los componentes. En Button.qml, podemos agregar cambios a las propiedades color y scale cuando se hace click en el botón. Los tipos de colores se animan usando ColorAnimation y los números se animar usando NumberAnimation. La sintaxis on propertyName mostrada debajo es útil cuando se enfoca una única propiedad. | Volviendo a nuestros componentes con conocimiento de estados y animaciones, podemos mejorar el aspecto de los componentes. En Button.qml, podemos agregar cambios a las propiedades color y scale cuando se hace click en el botón. Los tipos de colores se animan usando ColorAnimation y los números se animar usando NumberAnimation. La sintaxis on propertyName mostrada debajo es útil cuando se enfoca una única propiedad. | ||
<code> In Button.qml: | <code> In Button.qml: | ||
… | |||
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor | color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor | ||
Behavior on color { ColorAnimation{ duration: 55} } | |||
scale: buttonMouseArea.pressed ? 1.1 : 1.00 | scale: buttonMouseArea.pressed ? 1.1 : 1.00 | ||
Behavior on scale { NumberAnimation{ duration: 55} } </code> | |||
Adicionalmente, podemos mejorar el aspecto de nuestros componentes QML al agregar efectos de color tales como gradientes y efectos de opaquez. Declarar un elemento Gradient anula la propiedad color del elemento. Puedes declarar un color en el gradiente usando el elemento GradientStop. El gradiente se posiciona usando una escala, entre 0.0 y 1.0. | Adicionalmente, podemos mejorar el aspecto de nuestros componentes QML al agregar efectos de color tales como gradientes y efectos de opaquez. Declarar un elemento Gradient anula la propiedad color del elemento. Puedes declarar un color en el gradiente usando el elemento GradientStop. El gradiente se posiciona usando una escala, entre 0.0 y 1.0. | ||
<code> In MenuBar.qml | <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 usado por la barra de menú para mostrar un gradiente simulando profundidad. El primer color comienza en 0.0 y el último color está en 1.0. | Este gradiente es usado por la barra de menú para mostrar un gradiente simulando profundidad. El primer color comienza en 0.0 y el último color está en 1.0. | ||
Line 216: | Line 391: | ||
Terminamos de armar la interface de usuario de un editor de textos muy simple. De aquí en adelante, la interfase de usuario está completa y podemos implementar la lógica de la aplicación usando Qt y C++ normal. QML trabaja muy bien como herramienta de prototipado, separando la lógica de la aplicación del diseño de la IU. | Terminamos de armar la interface de usuario de un editor de textos muy simple. De aquí en adelante, la interfase de usuario está completa y podemos implementar la lógica de la aplicación usando Qt y C++ normal. QML trabaja muy bien como herramienta de prototipado, separando la lógica de la aplicación del diseño de la IU. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png | |||
=== Extendiendo QML al usar Qt C++ === | === Extendiendo QML al usar Qt C++ === | ||
Line 240: | Line 415: | ||
<code> In cppPlugins.pro: | <code> In cppPlugins.pro: | ||
TEMPLATE = lib | TEMPLATE = lib | ||
CONFIG += qt plugin | |||
QT+= declarative | |||
DESTDIR += ../plugins | |||
OBJECTS_DIR = tmp | |||
MOC_DIR = tmp | |||
TARGET = FileDialog | |||
HEADERS+= directory.h file.h dialogPlugin.h | |||
SOURCES += directory.cpp file.cpp dialogPlugin.cpp | SOURCES += directory.cpp file.cpp dialogPlugin.cpp | ||
</code> | |||
En particular, compilamos Qt con el módulo declarativo y lo configuramos como un complemento, necesitando una plantilla lib. Pondremos el complemento compilado en el directorio padre de complementos. | En particular, compilamos Qt con el módulo declarativo y lo configuramos como un complemento, necesitando una plantilla lib. Pondremos el complemento compilado en el directorio padre de complementos. | ||
Line 254: | Line 436: | ||
<code> In dialogPlugin.h: | <code> In dialogPlugin.h: | ||
#include | #include <QDeclarativeExtensionPlugin> | ||
class DialogPlugin : public QDeclarativeExtensionPlugin | class DialogPlugin : public QDeclarativeExtensionPlugin | ||
{ | |||
Q_OBJECT | |||
public: | public: | ||
void registerTypes(const char *uri); | |||
};</code> | };</code> | ||
Line 266: | Line 451: | ||
<code> DialogPlugin.cpp: | <code> DialogPlugin.cpp: | ||
#include | #include "dialogPlugin.h" | ||
#include "directory.h" | |||
#include "file.h" | |||
#include <qdeclarative.h> | |||
void DialogPlugin::registerTypes(const char '''uri){ | void DialogPlugin::registerTypes(const char '''uri){ | ||
< | |||
qmlRegisterType<Directory>(uri, 1, 0, "Directory"); | |||
qmlRegisterType<File>(uri, 1, 0,"File"); | |||
} | |||
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);</code> | |||
La función [http://doc.qt.nokia.com/4.7/qdeclarativeextensionplugin.html#registerTypes registerTypes()] registra nuestras clase File y Directory en QML. Esta función necesita el nombre de la clase para su plantilla, un número de versión principal, un número de versión menor y un nombre para nuestras clases. | |||
Necesitamos exportar el complemento usando la macro [http://doc.qt.nokia.com/4.7/qtplugin.html#Q_EXPORT_PLUGIN2#q-export-plugin2 Q_EXPORT_PLUGIN2] macro. Notar que en nuestro archivo dialogPlugin.h, tenemos la macro [http://doc.qt.nokia.com/4.7/qobject.html#Q_OBJECT Q_OBJECT] al comienzo de nuestra clase. Además, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario. | |||
=== Creando Propiedades QML en una clase C++ === | |||
Podemos crear elementos y propiedades QML usando el [http://doc.qt.nokia.com/4.7/metaobjects.html Sistema de Meta-Objetos] de Qt. Podemos implementar propiedades usando señales y ranuras, haciendo que Qt esté al tanto de estas propiedades. Estas propiedades pueden luego ser usadas en QML. | |||
Para el editor de texto, necesitamos ser capaces de cargar y guardar archivos. For the text editor, we need to be able to load and save files. Típicamente, estas funciones están contenidas en un diálogo de archivo. Afortunadamente, podemos usar [http://doc.qt.nokia.com/4.7/qdir.html QDir], [http://doc.qt.nokia.com/4.7/qfile.html QFile], y [http://doc.qt.nokia.com/4.7/qtextstream.html QTextStream] para implementar lectura de directorios y streams 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 directorio usa el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para llevar a cabo el manejo de archivos. La clase Directory se exporta como un complemento y está para ser usada en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro [http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY Q_PROPERTY] es una propiedad QML. | |||
La macro [http://doc.qt.nokia.com/4.7/qobject.html#Q_PROPERTY Q_PROPERTY] declara una propiedad como también sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename property, de tipo QString, se puede leer usando la función filename() y se escribe usando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite siempre que la propiedad cambia. Las funciones de lectura y escritura se declaran como públicas en el archivo de encabezado. | |||
< | |||
De manera similar, tenemos las otras propiedades declaradas de acuerdo a sus usos. La propiedad filesCount indica el número de archivos en un directorio. La propiedad filename property se establece con el nombre del archivo seleccionado actualmente y el contenido del archivo guardado/cargado se almacena en la propiedad fileContent. | |||
<code> Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )</code> | |||
La propiedad lista de archivos en una lista de todos los archivos filtrados en un directorio. La clase Directory se implementa para filtrar archivos de texto no válidos; solo son válidos archivos con una extensión .txt. Más aún, QLists pueden usarse en archivos QML al declararlas como QDeclarativeListProperty en C+''. El objeto a manera de plantilla necesita heredar de QObject, de este modo, la clase File también debe heredar de QObject. En la clase Directory, la lista de objetos File se almacena en una QList llamada m_fileList. | |||
<code> class File : public QObject{ | |||
Q_OBJECT | |||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) | |||
… | |||
};</code> | |||
Las propiedades pueden luego usarse en QML como parte de las propiedades del elemento Directory. Notar que no tenemos que crear una propiedad identificador id en nuestro código C. | |||
<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 recuperar sus propiedades. Para recuperar la propiedad nombre del primer archivo, podemos invocar files[0].name. | |||
Las funciones normales de C''+ son accesibles también desde QML. Las funciones para cargar y guardar un archvio se implementan en C++ y se declaran usando la macro [http://doc.qt.nokia.com/4.7/qobject.html#Q_INVOKABLE 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 void saveFile(); | |||
Q_INVOKABLE void loadFile(); | |||
</code> | |||
La clase Directory tiene que notificar a otros objetos siempre que el contenido del directorio cambia. Esta función se realiza usando una señal. Como se mencionó previamente, las señales de QML tienen un manejador correspondiente con sus nombres precedidos por el prefijo on. La señal se llama directoryChanged y se emite siempre que hay un refresco en un directorio. El refresco simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los ítems de QML pueden luego ser notificados asignando una acción al manejador de señal onDirectoryChanged. | |||
Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista usan callbacks para acceder y modificar los contenidos de la lista. La propiedad lista es de tipo QDeclarativeListProperty<File>. Siempre que se accede a la lista, la función de acceso necesita devolver QDeclarativeListProperty<File>. El tipo plantilla, File, necesita ser un derivado de QObject. Más aún, para crear QDeclarativeListProperty, la función para acceder a la lista y las modificadores necesitan pasarse al constructor como punteros a funciones. La lista, una QList en nuestro caso, también necesita ser una lista de punteros File. | |||
El constructor de QDeclarativeListProperty y la implementación de Directory: | |||
<code> QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 ) | |||
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );</code> | |||
El constructor pasa punteros a funciones que agregarán a la lista, cuentan la lista, recuperan un ítem usando un índice y vacían la lista. Solo la función de agregar es obligatoria. Notar que los punteros a funciones deben coincidir con la definición de AppendFunction, CountFunction, AtFunction, o ClearFunction. | El constructor pasa punteros a funciones que agregarán a la lista, cuentan la lista, recuperan un ítem usando un índice y vacían la lista. Solo la función de agregar es obligatoria. Notar que los punteros a funciones deben coincidir con la definición de AppendFunction, CountFunction, AtFunction, o ClearFunction. | ||
< | <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. Directory usa QTextStream para leer el archivo y para sacar el contenido del archivo a un archivo. | Para simplificar nuestro diálogo de archivo, la clase Directory filtra los archivos de texto no válidos, los cuales son archivos que no tienen una extensión .txt. Si un nombre de archivo no tiene la extensión .txt, entonces el archivo no aparecerá en nuestro diálogo de archivo. También, la implementación se asegura que los archivos guardados tengan una extensión .txt en el nombre del archivo. Directory usa QTextStream para leer el archivo y para sacar el contenido del archivo a un archivo. | ||
Line 315: | Line 552: | ||
La herramienta qmlviewer importa archivos que están en el mismo directorio de la aplicación. También podemos crear un archivo qmldir que contengan las ubicaciones de los archivos QML que queremos importar. El archivo qmldir puede almacenar también las ubicaciones de complementos y otros recursos. | La herramienta qmlviewer importa archivos que están en el mismo directorio de la aplicación. También podemos crear un archivo qmldir que contengan las ubicaciones de los archivos QML que queremos importar. El archivo qmldir puede almacenar también las ubicaciones de complementos y otros recursos. | ||
< | <code> In qmldir: | ||
Button ./Button.qml | Button ./Button.qml | ||
FileDialog ./FileDialog.qml | |||
TextArea ./TextArea.qml | |||
TextEditor ./TextEditor.qml | |||
EditMenu ./EditMenu.qml | |||
plugin FileDialog plugins< | plugin FileDialog plugins | ||
</code> | |||
El complemento que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El componente compilado está en el directorio plugins. | El complemento que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El componente compilado está en el directorio plugins. | ||
Line 329: | Line 571: | ||
El elemento Directory se usa en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio refrescó su contenido. Esta notificación se realiza en el manejador de señal, onDirectoryChanged. | El elemento Directory se usa en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio refrescó su contenido. Esta notificación se realiza en el manejador de señal, onDirectoryChanged. | ||
< | <code> In FileMenu.qml: | ||
Directory{ | Directory{ | ||
id:directory | |||
filename: textInput.text | |||
onDirectoryChanged: fileDialog.notifyRefresh() | |||
} | |||
</code> | |||
Para mantener la simplicidad de nuestra aplicación, el diálogo de archivo siempre estará visible y no mostrará nombres de archivos no válidos, los cuales no tienen una extensión .txt en sus nombres. | Para mantener la simplicidad de nuestra aplicación, el diálogo de archivo siempre estará visible y no mostrará nombres de archivos no válidos, los cuales no tienen una extensión .txt en sus nombres. | ||
< | <code> In FileDialog.qml: | ||
signal notifyRefresh() | 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 ítems de datos en una grilla de acuerdo a un delegado. El delegado maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una grilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la apariencia del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica siempre que se emite la señal notifyRefresh, recargando los archivos en el directorio. | El elemento FileDialog mostrará el contenido de un directorio al leer su propiedad tipo lista llamados archivos. Los archivos se usan como el modelo de un elemento GridView, el cual muestra ítems de datos en una grilla de acuerdo a un delegado. El delegado maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una grilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la apariencia del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica siempre que se emite la señal notifyRefresh, recargando los archivos en el directorio. | ||
< | <code> In FileMenu.qml: | ||
Button{ | Button{ | ||
id: newButton | |||
label: "New" | |||
onButtonClick:{ | |||
textArea.textContent = "" | |||
} | |||
} | |||
Button{ | |||
id: loadButton | |||
label: "Load" | |||
onButtonClick:{ | |||
directory.filename = textInput.text | |||
directory.loadFile() | |||
textArea.textContent = directory.fileContent | |||
} | |||
} | |||
Button{ | |||
id: saveButton | |||
label: "Save" | |||
onButtonClick:{ | |||
directory.fileContent = textArea.textContent | |||
directory.filename = textInput.text | |||
directory.saveFile() | |||
} | |||
} | |||
Button{ | |||
id: exitButton | |||
label: "Exit" | |||
onButtonClick:{ | |||
Qt.quit() | |||
} | |||
}</code> | |||
Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El objeto saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y copia su nombre de archivo de la entrada de texto editable. Finalmente, el botón llama a la función saveFile | Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El objeto saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y copia su nombre de archivo de la entrada de texto editable. Finalmente, el botón llama a la función saveFile(), guardando el archivo. La función sloadButton tiene una ejecución similar. También, la acción New vacía el contenido del elemento TextEdit. | ||
Más aún, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto. | Más aún, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto. | ||
http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png | |||
=== Finalización del Editor de Texto === | === 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 puede también cargar desde un archivo y realizar manipulación de texto. | La aplicación puede funcionar como un simple editor de texto, capaz de aceptar texto y guardar el texto en un archivo. El editor de texto puede también cargar desde un archivo y realizar manipulación de texto. |
Latest revision as of 16:12, 4 May 2015
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine. Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean. |
Comenzando a Programar con QML
Bienvenido al mundo de QML, el lenguaje declarativo para Interfaces de Usuario (IU). En esta guía Comenzar a Programar, crearemos una simple aplicación para editar textos usando QML. Luego de leer esta guía, deberías estar listo para desarrollar tus propias aplicaciones usando QML y Qt C+.
QML para Armar Interfaces de Usuario
La aplicación que estamos armando es un simple editor de texto que cargará, guardará y realizará alguna manipulación de texto. Esta guía consiste de dos partes. La primera parte involucrará desarrollar el diseño de la aplicación y los comportamientos usando lenguaje declarativo en QML. Para la segunda parte, cargar y guardar el archivo serán implementados usando Qt C. Al usar el Sistema de Meta-Objetos de Qt, podemos exponer funciones de C+ como propiedades que los elementos de QML pueden usar. Usando QML y Qt C++ podemos desacoplar de manera eficiente la lógica de la interface de la lógica de la aplicación.
Para ejecutar el código QML de ejemplo, simplemente provee a la herramienta incluida qmlviewer el nombre del archivo QML como argumento. La parte de C++ de este tutorial asume que el lector posee conocimiento básico de los procedimientos de compilación de Qt.
Capítulos del Tutorial:
- Definiendo un Botón y un Menú
- Implementando una Barra de Menú
- Armando un Editor de Texto
- Decorando un Editor de Texto
- Extendiendo QML al usar Qt C++
Definiendo un Botón y un Menú
Componente Básico - un Botón
Comenzamos nuestro editor de texto armando un botón. Funcionalmente un butón tiene un área sensible al ratón (mousse) y una etiqueta. Los botones realizan acciones cuando un usuario presiona el botón.
En QML, el ítem visual básico es el elemento Rectangle. El elemento Rectangle tiene propiedades para controlar la apariencia del elemento y su ubicación.
Primero, la instrucción import Qt 4.7 permite que la herramienta qmlviewer importe los elementos que usaremos luego. Esta línea debe existir en cada archivo QML. Tener presente que la versión de los módulos Qt se incluye en la instrucción import.
Este simple rectángulo tiene un identificador unívoco, simplebutton, el cual está ligado a la propiedad id. Las propiedades del elemento Rectangle están ligadas a valores al listar la propiedad, seguidos por dos puntos, luego el valor. En el código de ejemplo, el color gris está ligado a la propiedad color del elemento Rectangle. De manera similar, ligamos el ancho y alto del elemento Rectangle.
El elemento Text es un campo de texto de sólo lectura (no editable). Nombramos a este elemento Text buttonLabel. Para establecer el contenido de la cadena de caracteres de este campo Texto, ligamos un valor a la propiedad text. La etiqueta está contenida dentro del elemento Rectangle y para centrarla, asignamos las anclas del elemento Text a su padre, el cual se llama simplebutton. Las anclas pueden ligarse a las anclas de otros ítems, de esa forma facilitando las asignaciones de diseño.
Guardamos este código como SimpleButton.qml. Ejecutando qmlviewer con el nombre del archivo como argumento mostrará el rectángulo gris con la etiqueta de texto.
Para implementar la funcionalidad del click del botón, podemos usar el manejo de eventos de QML. El manejo de eventos de QML es muy similar al mecanismo de señales y ranuras de Qt. Se emiten señales y las ranuras conectadas son invocadas.
Rectangle{
id:simplebutton
…
MouseArea{
id: buttonMouseArea
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
//onClicked handles valid mouse button clicks
onClicked: console.log(buttonLabel.text + " clicked" )
}
}
Incluimos un elemento MouseArea (área del ratón) n nuestro simplebutton. Los elementos MouseArea describen el área interactiva donde se detectan los movimientos del ratón. Para nuestro botón, anclamos el elemento MouseArea completo a su padre, el cual es simplebutton. La sintaxis anchors.fill es una manera de acceder a una propiedad específica llamada fill dentro de un grupo de propiedades llamado anchors. QML usa diseños basados en anclas donde los ítems pueden anclarse unos a otros, creando diseños robustos.
El elemento MouseArea tiene muchos manejadores de señales que se llaman durante los movimientos del ratón dentro de los límites especificados para el elemento MouseArea. Uno de ellos es onClicked y se llama cada vez que el botón apropiado del ratón se pulsa, siendo el izquierdo el valor por omisión. Podemos ligar acciones al manipulador onClicked. En nuestro ejemplo, console.log() muestra texto siempre que se hace click en el área del ratón. La función console.log() es una herramienta útil para propósitos de depuración (debugging) y para mostrar texto.
El código en SimpleButton.qml es suficiente para mostrar un botón en la pantalla y emitir texto cuando se selecciona mediante un click con el ratón.
Rectangle {
id:Button
…
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}
MouseArea{
onClicked: buttonClick()
hoverEnabled: true
onEntered: parent.border.color = onHoverColor
onExited: parent.border.color = borderColor
}
//determines the color of the button by using the conditional operator
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}
Un botón completamente funcional está en Button.qml. Los fragmentos de código en este artículo tienen partes del código omitidas, denotadas por puntos suspensivos porque ellas fueron ya sea presentadas en secciones previas o no son relevantes para la discusión actual del código.
Las propiedades Custom se declaran usando la sintaxis property type. En el código, la propiedad buttonColor, de tipo color, se declara y liga al valor "lightblue" (celeste). buttonColor se usa más adelante en una operación condicional para determinar el color de relleno del botón. Notar que la asignación de valor a la propiedad es posible usando el signo = (igual), además de ligar el valor usando el caracter ':' (dos puntos). Las propiedades Custom permiten acceder a ítems internos fuera del alcance del elemento Rectangle. Hay tipos básicos de QML tales como int, string, real, como también un tipo llamado variant.
Al vincular los manejadores de señales onEntered y onExited a colores, el borde del botón se volverá amarillo cuando el mouse pase sobre el botón y volverá al otro color cuando el ratón salga del área del ratón.
Una señal buttonClick() se declara en Button.qml al colocar la palabra clave signal delante del nombre de la señal. Todas las señales tienen sus manejadores creados automáticamente, con sus nombres comenzando con el prefijo on. Como resultado, onButtonClick es el manejador de buttonClick. Luego onButtonClick se asigna a una acción para que sea llevada a cabo. En nuestro botón de ejemplo, el manejador onClicked del ratón simplemente invocará onButtonClick, el cual muestra un texto. onButtonClick permite que objetos externos accedan al área del ratón del elemento Button de manera sencilla. Por ejemplo, los ítems pueden tener más de una declaración MouseArea y una señal buttonClick puede hacer una mejor distinción entre los distintos manejadores de señal para MouseArea.
Ahora tenemos el conocimiento básico para implementar ítems en QML que pueden manejar movimientos básicos del ratón. Creamos una etiqueta Text dentro de un elemento Rectangle, ajustamos sus propiedades, e implementamos comportamientos que responden a movimientos del ratón. Esta idea de crear elementos dentro de elementos se repite a través de la aplicación del editor de texto.
Este botón no es útil a menos que se use como un componente para realizar una acción. En la próxima sección, pronto crearemos un menú conteniendo varios de estos botones.
Creando una Página de Menú
Hasta este momento, hemos cubierto como crear elementos y asignar comportamientos dentro de un único archivo QML. En esta sección, cubriremos ccomo importar elementos QML y como reusar algunos de los componentes creados para armar otros componentes.
Los menúes muestran el contenido de una lista, con cada ítem teniendo la capacidad de realizar una acción. En QML, podemos crear un menú de varias formas. Primero, crearemos un menú que contiene botones los cuales eventualmente realizarán diferentes acciones. El código del menú está en FileMenu.qml.
import Qt 4.7 import the main Qt QML module
import "folderName" import the contents of the folder
import "script.js" as Script import a Javascript file and name it as Script
La sintaxis arriba muestra como usar la palabra reservada import. Esto se requiere para usar archivos JavaScript, o archivos QML que no están en el mismo directorio. Ya que Button.qml está en el mismo directorio que FileMenu.qml, no necesitamos importar el archivo Button.qml para usarlo. Directamente podemos crear un elemento Button declarando Button{}, de manera similar a la declaración Rectangle{}.
In FileMenu.qml:
Row{
anchors.centerIn: parent
spacing: parent.width/6
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"
onButtonClick: Qt.quit()
}
}
En FileMenu.qml, declaramos tres elementos Button. Se declaran dentro de un elemeto Row, un posicionador que ubicará sus hijos en una fila vertical. La declaración Button permanece en Button.qml, que es el mismo que el archivo Button.qml que usamos previamente. Pueden declararse nuevas vinculaciones a propiedades dentro de los nuevos botones creados, sobreescribiendo efectivamente las propiedades establecidas en Button.qml. El botón exitButton terminará y cerrará la ventana cuando sea seleccionado. Notar que el manejador de señal onButtonClick en Button.qml será llamado además del manejador onButtonClick en exitButton.
La declaración Row se realiza en un elemento Rectangle, creando un rectágulo contenedor para la fila de botones. Este rectángulo adicional crea una manera indirecta de organizar la fila de botones dentro de un menú.
La declaración del menu editar es muy similar a esta etapa. El menú tiene botones con las etiquetas: Copiar, Pegar y Seleccionar Todo.
Armados con nuestro conocimiento de importar y customizing componentes creados previamente, ahora podemos combinar estas páginas de menu para crear una barra de menú, que consiste de botones para seleccionar el menú, y mirar como podemos estructurar los datos usando QML.
Implementando una Barra de Menú
Nuestra aplicación de editor de texto necesitará una manera de mostrar los menúes usando una barra de menú. La barra de menú cambiará los diferentes menues y el usuario puede elegir qué menú mostrar. El cambio de menúes implica que los menúes necesitan más estructura que meramente mostrarlos en una fila. QML usa modelos y vistas para estructurar los datos y mostrar los datos estructurados.
Usando Modelos de Datos y Vistas
QML tiene diferentes vistas de datos que muestran modelos de datos. Nuestra barra de menú mostrará los menúes en una lista, con un encabezado que muestra una fila de los nombres de los menúes. La lista de menúes se declaran dentro de un elemento VisualItemModel. El elemento VisualItemModel contiene ítems que ya tienen vistas tales como elementos Rectangle y elementos importados de la IU. Otros tipos de modelos tales como el elemento ListModel necesitan un delegado para mostrar sus datos.
Declaramos dos ítems visuales en el elemento menuListModel, el ítem FileMenu y el ítem EditMenu. Personalizamos los dos menúes y los mostramos usando un elemento ListView. El archivo MenuBar.qml file contiene las declaraciones QML y un menú simple para editar se define en el archivo EditMenu.qml.
VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}
El elemento ListView mostrará un modelo de acuerdo a un delegado. El delegado puede declarar los ítems del modelo a mostrar en un elemento Row o mostrar los ítems en una grilla. Nuestro menuListModel ya tiene ítems visible, y de ese modo, no necesitamos declarar un delegado.
ListView{
id: menuListView
//Anchors are set to react to window anchors
anchors.fill:parent
anchors.bottom: parent.bottom
width:parent.width
height: parent.height
//the model contains the data
model: menuListModel
//control the movement of the menu switching
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 5000
highlightFollowsCurrentItem: true
highlightMoveDuration:240
highlightRangeMode: ListView.StrictlyEnforceRange
}
Adicionalmente, ListView hereda de Flickable, haciendo que la lista responda a arrastres del ratón y otros gestos. La última parte del código arriba ajustas propiedades del elemento Flickable para crear en nuestra vista el movimiento de parpadeo deseado. En particular, la propiedad highlightMoveDuration cambia la duración de la transición de parpadeo. Un valor highlightMoveDuration mayor resulta en un cambio de menú más lento.
El elemento ListView mantiene los ítems del modelo a través de un índice y cada ítem visual en el modelo es accesible a través del índice, en el orden de declaración. Cambiar el valor de currentIndex efectivamente cambia el ítem resaltado en el elemento ListView. El encabezado de nuestra barra de menú ejemplifica este efecto. Hay dos botones en una fila, ambos cambiando el menú actual cuando son seleccionados. El elemento fileButton cambia el menú actual al menú archivo cuando se selecciona, y el índice es 0 porque FileMenu se declaró primero en menuListModel. De manera similar, editButton cambiará el menú actual a EditMenu cuando se seleccione.
El rectángulo labelList tiene un valor z de 1, denotando que se muestra al frente de la barra de menú. Ítems con valores z mayores se muestran al frente de ítems con valores z menores. El valor de z por omisión es 0.
Rectangle{
id: labelList
…
z: 1
Row{
anchors.centerIn: parent
spacing:40
Button{
label: "File"
id: fileButton
…
onButtonClick: menuListView.currentIndex = 0
}
Button{
id: editButton
label: "Edit"
…
onButtonClick: menuListView.currentIndex = 1
}
}
}
La barra de menú que hemos creado puede tener parpadeo para acceder a los menúes o al hacer click sobre los nombres del menú en la parte superior. El cambio de pantallas de menúes se siente intuitivo y con respuesta.
Armando un Editor de Texto
Declarando un elemento TextArea
Nuestro editor de texto no es un editor de texto si no contiene un área de texto que se pueda editar. El elemento TextEdit de QML permite la declaración de un área de texto editable multi-línea. TextEdit es diferente de un elemento Text, el cual no permite que el usuario edite directamente el texto.
TextEdit{
id: textEditor
anchors.fill:parent
width:parent.width; height:parent.height
color:"midnightblue"
focus: true
wrapMode: TextEdit.Wrap
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}
El editor tiene su propiedad color de fuente establecida y también para wrap el texto. El área TextEdit está dentro de un área parpadeante que se desplazará si el cursor de texto está fuera del área visible. La función ensureVisible() comprobará si el rectángulo del cursor está fuera de los límites visibles y moverá el área de texto de manera apropiada. QML usa sintaxis de Javascript para sus secuencias de comandos (scripts), y como se mencionó anteriormente, pueden importarse archivos Javascript y ser usados dentro de un archivo QML.
function ensureVisible®{
if (contentX >= r.x)
contentX = r.x;—
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
Combinando Componentes para el Editor de Texto
Estamos listos para crear el diseño de nuestro editor de texto usando QML. El editor de texto tiene dos componentes, la barra de menú que creamos y el área de texto. QML nos permite reusar componentes, y así, hacer nuestro código más simple, al importar componentes y personalizarlos cuando sea necesario. Nuestro editor de texto divide la ventana en dos; un tercio de la pantalla se dedica a la barra de menú y dos tercios de la pantalla muestran el área de texto. La barra de menú se muestra en frente de cualquier otro elemento.
Rectangle{
id: screen
width: 1000; height: 1000
//the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the MenuBar
property int partition: height/3
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}
Al importar componentes reutilizables, nuestro código TextEditor luce mucho más simple. Podemos entonces personalizar la aplicación principal, sin preocuparnos acerca de las propiedades que ya han definido comportamientos. Usando esta aproximación, los diseños de aplicaciones y componentes de IU se pueden crear fácilmente.
Decorando el Editor de Texto
Implementando una Interface Cajón
Nuestro editor de texto luce simple y necesitamos decorarlo. Usando QML, podemos declarar transiciones y animar nuestro editor de texto. Nuestra barra de menú está ocupando un tercio de la pantalla y sería bueno hacerla aparecer cuando querramos.
Podemos agregar una interface cajón, que contraerá o expandirá la barra de menú cuando se haga click sobre ella. En nuestra implementación, tenemos un rectángulo delgado que responde a los clicks del ratón. El cajón, como también la aplicación, tienen dos estados: el estado "cajón está abierto" y el estado "cajón está cerrado". El ítem cajón es una tira de un rectángulo con una altura pequeña. Hay un elemento Image anidado que declara que un ícono de una flecha será centrado dentro del cajón. El cajón asigna un estado a toda la aplicación, con el identificador screen, siempre que un usuario hace click en el área del ratón.
Rectangle{
id:drawer
height:15
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea{
id: drawerMouseArea
anchors.fill:parent
onClicked:{
if (screen.state "DRAWER_CLOSED"){
screen.state = "DRAWER_OPEN"
}
else if (screen.state "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
…
}
}
Un estado es simplemente una colección de configuraciones y se declara en un elemento State. Una lista de estdos pueden listarse y asociarse a la propiedad estados. En nuestra aplicación, los dos estados se llaman DRAWER_CLOSED y DRAWER_OPEN. Los ítems de as configuraciones se declaran en elementos PropertyChanges. En el estado DRAWER_OPEN, hay cuatro ítems que recibirán cambios de propiedades. El primer destino, menuBar, cambiará su propiedad y a 0. De manera similar, textArea bajará a una nueva posición cuando el estado es DRAWER_OPEN. textArea, el cajón, y el ícono del cajón sufrirán cambios de propiedades para ajustarse al estado actual.
states:[
State {
name: "DRAWER_OPEN"
PropertyChanges { target: menuBar; y: 0}
PropertyChanges { target: textArea; y: partition + drawer.height}
PropertyChanges { target: drawer; y: partition}
PropertyChanges { target: arrowIcon; rotation: 180}
},
State {
name: "DRAWER_CLOSED"
PropertyChanges { target: menuBar; y:-height; }
PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height }
PropertyChanges { target: drawer; y: 0 }
PropertyChanges { target: arrowIcon; rotation: 0 }
}
]
Los cambios de estado son abruptos y necesitamos transiciones más suaves. Las transiciones entre estados se definen usando el elemento Transition, el cual puede luego asociarse a la propiedad transitions del ítem. Nuestro editor de texto tiene una transición de estado siempre que los estados cambian tanto a DRAWER_OPEN o a DRAWER_CLOSED. De manera importante, la transición necesita un estado desde y un estado hacia pero para nuestras transiciones, podemos usar el símbolo comodín * para denotar que la transición se aplica a todos los cambios de estado.
Durante las transiciones, podemos asignar animaciones a la propiedad changes. Nuestra menuBar cambia la posición desde y:0 a y:-partition y podemos animar esta transición usando el elemento NumberAnimation. Declaramos que las propiedades de los destinos se animarán por un cierto período de tiempo y usamos una cierta curva de aceleración. Una curva de aceleración controla los ritmos de animación y el comportamiento de la interpolación durante las transiciones de estados. La curva de aceleración que elegimos es Easing.OutQuint, la cual enlentece el movimiento cerca del final de la animación. Por favor lee el artículo sobre Animación en QML.
transitions: [
Transition {
to: "*"
NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
}
]
Otra manera de animar cambios de propiedades es declarando un elemento Behavior. Una transición solo trabaja durante cambios de estado y Behavior puede establecer una animación para un cambio de una propiedad general. En el editor de texto, la flecha tiene un elemento NumberAnimation que anima su propiedad rotation siempre que la propiedad cambia.
In TextEditor.qml:
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
Volviendo a nuestros componentes con conocimiento de estados y animaciones, podemos mejorar el aspecto de los componentes. En Button.qml, podemos agregar cambios a las propiedades color y scale cuando se hace click en el botón. Los tipos de colores se animan usando ColorAnimation y los números se animar usando NumberAnimation. La sintaxis on propertyName mostrada debajo es útil cuando se enfoca una única propiedad.
In Button.qml:
…
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
Behavior on color { ColorAnimation{ duration: 55} }
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
Adicionalmente, podemos mejorar el aspecto de nuestros componentes QML al agregar efectos de color tales como gradientes y efectos de opaquez. Declarar un elemento Gradient anula la propiedad color del elemento. Puedes declarar un color en el gradiente usando el elemento GradientStop. El gradiente se posiciona usando una escala, entre 0.0 y 1.0.
In MenuBar.qml
gradient: Gradient {
GradientStop { position: 0.0; color: "#8C8F8C" }
GradientStop { position: 0.17; color: "#6A6D6A" }
GradientStop { position: 0.98;color: "#3F3F3F" }
GradientStop { position: 1.0; color: "#0e1B20" }
}
Este gradiente es usado por la barra de menú para mostrar un gradiente simulando profundidad. El primer color comienza en 0.0 y el último color está en 1.0.
A dónde ir desde Aquí
Terminamos de armar la interface de usuario de un editor de textos muy simple. De aquí en adelante, la interfase de usuario está completa y podemos implementar la lógica de la aplicación usando Qt y C++ normal. QML trabaja muy bien como herramienta de prototipado, separando la lógica de la aplicación del diseño de la IU.
Extendiendo QML al usar Qt C++
Ahora que tenemos el diseño de nuestro editor de texto, podemos implementar las funcionalidades del editor de texto en C+. Usar QML con C+ nos permite crear la lógica de nuestra aplicación usando Qt. Podemos crear un contexto QML en una aplicación C++ usando las clases Declarative de Qt y mostramos los elementos QML usando Graphics Scene. De manera alternativa, podemos exportar nuestro código C++ en un complemento (plugin) que la herramienta qmlviewer pueda leer. Para nuestra aplicación, implementaremos las funciones cargar y guardar en C++ y las exportaremos como un complemento. De esta forma, solo necesitaremos cargar el archivo QML directamente en lugar de correr un ejecutable.
Exponiendo Clases C++ a QML
Estaremos implementando la carga y guardado del archivo usando Qt y C+. Las clases y funciones C+ pueden usarse en QML registrándolas. La clase también necesita compilarse como un complemento Qt y el archivo QML necesita saber donde se encuentra el complemento.
Para nuestra aplicación, necesitamos crear los siguientes ítems:
- Una clase Directory que manejará las operaciones relacionadas con directorios
- Una clase File que es un QObject, simulando la lista de archivos en un directorio
- Una clase plugin que registrará la clase en el contexto QML
- Un proyecto Qt que compilará el complemento
- Un archivo qmldir para indicarle a la herramienta qmlviewer donde está el complemento
Construyendo un complemento Qt
Para crear un complemento, necesitamos indicar los siguiente en un archivo de proyecto Qt. Primero, necesitamos agregar los fuentes, encabezados y módulos Qt a nuestro proyecto. Todo el código C++ y los archivos de proyecto están en el directorio filedialog.
In cppPlugins.pro:
TEMPLATE = lib
CONFIG += qt plugin
QT+= declarative
DESTDIR += ../plugins
OBJECTS_DIR = tmp
MOC_DIR = tmp
TARGET = FileDialog
HEADERS+= directory.h file.h dialogPlugin.h
SOURCES += directory.cpp file.cpp dialogPlugin.cpp
En particular, compilamos Qt con el módulo declarativo y lo configuramos como un complemento, necesitando una plantilla lib. Pondremos el complemento compilado en el directorio padre de complementos.
Registrando una Clase en QML
In dialogPlugin.h:
#include <QDeclarativeExtensionPlugin>
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT
public:
void registerTypes(const char *uri);
};
Nuestra clase complemento, DialogPlugin es una subclase de QDeclarativeExtensionPlugin. Necesitamos implementar la función heredada, registerTypes(). El archivo dialogPlugin.cpp se parece a esto:
DialogPlugin.cpp:
#include "dialogPlugin.h"
#include "directory.h"
#include "file.h"
#include <qdeclarative.h>
void DialogPlugin::registerTypes(const char '''uri){
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
qmlRegisterType<File>(uri, 1, 0,"File");
}
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);
La función registerTypes() registra nuestras clase File y Directory en QML. Esta función necesita el nombre de la clase para su plantilla, un número de versión principal, un número de versión menor y un nombre para nuestras clases.
Necesitamos exportar el complemento usando la macro Q_EXPORT_PLUGIN2 macro. Notar que en nuestro archivo dialogPlugin.h, tenemos la macro Q_OBJECT al comienzo de nuestra clase. Además, necesitamos ejecutar qmake sobre el archivo del proyecto para generar el código meta-objeto necesario.
Creando Propiedades QML en una clase C++
Podemos crear elementos y propiedades QML usando el Sistema de Meta-Objetos de Qt. Podemos implementar propiedades usando señales y ranuras, haciendo que Qt esté al tanto de estas propiedades. Estas propiedades pueden luego ser usadas en QML.
Para el editor de texto, necesitamos ser capaces de cargar y guardar archivos. For the text editor, we need to be able to load and save files. Típicamente, estas funciones están contenidas en un diálogo de archivo. Afortunadamente, podemos usar QDir, QFile, y QTextStream para implementar lectura de directorios y streams de entrada/salida.
class Directory : public QObject{
Q_OBJECT
Q_PROPERTY(int filesCount READ filesCount CONSTANT)
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
…
La clase directorio usa el Sistema de Meta-Objetos de Qt para registrar propiedades que necesita para llevar a cabo el manejo de archivos. La clase Directory se exporta como un complemento y está para ser usada en QML como el elemento Directory. Cada una de las propiedades listadas usando la macro Q_PROPERTY es una propiedad QML.
La macro Q_PROPERTY declara una propiedad como también sus funciones de lectura y escritura en el Sistema de Meta-Objetos de Qt. Por ejemplo, la propiedad filename property, de tipo QString, se puede leer usando la función filename() y se escribe usando la función setFilename(). Adicionalmente, hay una señal asociada a la propiedad filename llamada filenameChanged(), la cual se emite siempre que la propiedad cambia. Las funciones de lectura y escritura se declaran como públicas en el archivo de encabezado.
De manera similar, tenemos las otras propiedades declaradas de acuerdo a sus usos. La propiedad filesCount indica el número de archivos en un directorio. La propiedad filename property se establece con el nombre del archivo seleccionado actualmente y el contenido del archivo guardado/cargado se almacena en la propiedad fileContent.
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
La propiedad lista de archivos en una lista de todos los archivos filtrados en un directorio. La clase Directory se implementa para filtrar archivos de texto no válidos; solo son válidos archivos con una extensión .txt. Más aún, QLists pueden usarse en archivos QML al declararlas como QDeclarativeListProperty en C+. El objeto a manera de plantilla necesita heredar de QObject, de este modo, la clase File también debe heredar de QObject. En la clase Directory, la lista de objetos File se almacena en una QList llamada m_fileList.
class File : public QObject{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
…
};
Las propiedades pueden luego usarse en QML como parte de las propiedades del elemento Directory. Notar que no tenemos que crear una propiedad identificador id en nuestro código C.
Directory{
id: directory
filesCount
filename
fileContent
files
files[0].name
}
Como QML usa sintaxis y estructura de Javascript, podemos iterar a través de la lista de archivos y recuperar sus propiedades. Para recuperar la propiedad nombre del primer archivo, podemos invocar files[0].name.
Las funciones normales de C+ son accesibles también desde QML. Las funciones para cargar y guardar un archvio se implementan en C++ y se declaran usando la macro Q_INVOKABLE. De manera alternativa, podemos declarar las funciones como una ranura y las funciones serán accesibles desde QML.
In Directory.h:
Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
La clase Directory tiene que notificar a otros objetos siempre que el contenido del directorio cambia. Esta función se realiza usando una señal. Como se mencionó previamente, las señales de QML tienen un manejador correspondiente con sus nombres precedidos por el prefijo on. La señal se llama directoryChanged y se emite siempre que hay un refresco en un directorio. El refresco simplemente carga el contenido del directorio y actualiza la lista de archivos válidos en el directorio. Los ítems de QML pueden luego ser notificados asignando una acción al manejador de señal onDirectoryChanged.
Las propiedades tipo lista necesitan se exploradas un poco más. Esto es porque las propiedades tipo lista usan callbacks para acceder y modificar los contenidos de la lista. La propiedad lista es de tipo QDeclarativeListProperty<File>. Siempre que se accede a la lista, la función de acceso necesita devolver QDeclarativeListProperty<File>. El tipo plantilla, File, necesita ser un derivado de QObject. Más aún, para crear QDeclarativeListProperty, la función para acceder a la lista y las modificadores necesitan pasarse al constructor como punteros a funciones. La lista, una QList en nuestro caso, también necesita ser una lista de punteros File.
El constructor de QDeclarativeListProperty y la implementación de Directory:
QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );
El constructor pasa punteros a funciones que agregarán a la lista, cuentan la lista, recuperan un ítem usando un índice y vacían la lista. Solo la función de agregar es obligatoria. Notar que los punteros a funciones deben coincidir con la definición de AppendFunction, CountFunction, AtFunction, o ClearFunction.
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *property)
Para simplificar nuestro diálogo de archivo, la clase Directory filtra los archivos de texto no válidos, los cuales son archivos que no tienen una extensión .txt. Si un nombre de archivo no tiene la extensión .txt, entonces el archivo no aparecerá en nuestro diálogo de archivo. También, la implementación se asegura que los archivos guardados tengan una extensión .txt en el nombre del archivo. Directory usa QTextStream para leer el archivo y para sacar el contenido del archivo a un archivo.
Con nuestro elemento Directory, podemos recuperar los archivos como una lista, sabiendo cuantos archivos de texto hay en el directorio de la aplicación, obtener el nombre del archivo y su contenido como una cadena de caracteres, y ser notificados siempre que hay cambios en el contenido del directorio.
Para compilar el complemento, ejecutamos qmake sobre el archivo de proyecto cppPlugins.pro, luego ejecutamos make para compilar y transferir el complemento al directorio plugins.
Importando un Complemento en QML
La herramienta qmlviewer importa archivos que están en el mismo directorio de la aplicación. También podemos crear un archivo qmldir que contengan las ubicaciones de los archivos QML que queremos importar. El archivo qmldir puede almacenar también las ubicaciones de complementos y otros recursos.
In qmldir:
Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml
plugin FileDialog plugins
El complemento que recién creamos se llama FileDialog, como se indica en el campo TARGET en el archivo del proyecto. El componente compilado está en el directorio plugins.
Integrando un Diálogo Archivo en el Menú Archivo
Nuestro FileMenu necesita mostrar el elemento FileDialog, que contiene una lista de archivos de texto en un directorio de esa forma permitiendo al usuario seleccionar el archivo al hacer click en la lista. También necesitamos asignar los botones guardar, abrir y nuevo a sus respectivas acciones. FileMenu contiene un campo de texto de entrada editable que permite al usuario tipear el nombre de un archivo usando el teclado.
El elemento Directory se usa en el archivo FileMenu.qml y notifica al elemento FileDialog que el directorio refrescó su contenido. Esta notificación se realiza en el manejador de señal, onDirectoryChanged.
In FileMenu.qml:
Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
Para mantener la simplicidad de nuestra aplicación, el diálogo de archivo siempre estará visible y no mostrará nombres de archivos no válidos, los cuales no tienen una extensión .txt en sus nombres.
In FileDialog.qml:
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
El elemento FileDialog mostrará el contenido de un directorio al leer su propiedad tipo lista llamados archivos. Los archivos se usan como el modelo de un elemento GridView, el cual muestra ítems de datos en una grilla de acuerdo a un delegado. El delegado maneja la apariencia del modelo y nuestro diálogo de archivo simplemente creará una grilla con el texto centrado en el medio. Hacer click en el nombre del archivo resultará en la apariencia del rectángulo resaltando el nombre del archivo. El elemento FileDialog se notifica siempre que se emite la señal notifyRefresh, recargando los archivos en el directorio.
In FileMenu.qml:
Button{
id: newButton
label: "New"
onButtonClick:{
textArea.textContent = ""
}
}
Button{
id: loadButton
label: "Load"
onButtonClick:{
directory.filename = textInput.text
directory.loadFile()
textArea.textContent = directory.fileContent
}
}
Button{
id: saveButton
label: "Save"
onButtonClick:{
directory.fileContent = textArea.textContent
directory.filename = textInput.text
directory.saveFile()
}
}
Button{
id: exitButton
label: "Exit"
onButtonClick:{
Qt.quit()
}
}
Nuestro FileMenu ahora puede conectarse con sus respectivas acciones. El objeto saveButton transferirá el texto de TextEdit a la propiedad fileContent del directorio, y copia su nombre de archivo de la entrada de texto editable. Finalmente, el botón llama a la función saveFile(), guardando el archivo. La función sloadButton tiene una ejecución similar. También, la acción New vacía el contenido del elemento TextEdit.
Más aún, los botones EditMenu se conectan a la funciones TextEdit para copiar, pegar y seleccionar todo el texto en el editor de texto.
Finalización del Editor de Texto
La aplicación puede funcionar como un simple editor de texto, capaz de aceptar texto y guardar el texto en un archivo. El editor de texto puede también cargar desde un archivo y realizar manipulación de texto.