Biblioteca Gráfica Gtk2Hs

De Wikihaskell
Saltar a: navegación, buscar
Interfaz gráfica
Biblioteca sobre interfaces gráficas para Haskell basada en Gtk
Lenguaje Haskell
Biblioteca Gtk2Hs
Autores <a href="/wikihaskell/index.php/Usuario:PerezLunaPablo" title="Usuario:PerezLunaPablo">Pablo Pérez Luna</a>
<a href="/wikihaskell/index.php/Usuario:PuertaAragonSergio" title="Usuario:PuertaAragonSergio">Sergio Puerta Aragón</a>
<a href="/wikihaskell/index.php/Usuario:TocinoCollantesJoseMaria" title="Usuario:TocinoCollantesJoseMaria">José María Tocino Collantes</a>

Gtk2Hs es una biblioteca de GUI para Haskell basada en Gtk. Gtk es un juego de herramientas extenso y maduro de multi-plataforma para crear interfaces gráficas. Gtk+ es fundamentalmente una biblioteca de componentes llamados widgets, que se organizan en una jerarquía orientada a objetos.

En Gtk2Hs esto se hace dando a cada widget tanto un tipo como una clase de tipo (type y type class) de Haskell. De este modo el sistema de tipos de Haskell está rigurosamente preservado, y el programador tiene todas las ventajas de la comprobación de tipos del compilador y del interprete de Haskell de Glasgow (ghc).

Mediante la siguiente guía, intentaremos que el programador medio de Haskell tenga todo lo necesasrio para que pueda cubrir los interfaces de usuario más comunes.

Contenido

Qué necesitamos Instalar

Para el uso de la biblioteca gtk2hs, es necesario que instalemos ghc, que es el compilador de Haskell creado por la Universidad de Glasgow (Glasgow Haskell Compilation system)

<a rel="nofollow" class="external text" href="http://www.haskell.org/ghc/">Glasgow Haskell Compiler</a>

<a rel="nofollow" class="external text" href="http://www.haskell.org/ghc/distribution_packages.html">Distintas distribuciones de Ghc</a>

<a rel="nofollow" class="external text" href="http://www.haskell.org/gtk2hs/">Biblioteca Gtk2hs</a>

Instalación desde repositorio usando Synaptic

En primer lugar, iniciamos Synaptic, y en el cuadro superior de “búsqueda rápida” escribimos ghc.

Seleccionamos los paquetes ghc6 (compilador en sí), ghc6-doc (documentación) y ghc6-prof. El gestor de dependencias también marcará el paquete libghc6-mtl-doc. Éstos son los paquetes mínimos para el funcionamiento del compilador de Haskell, aunque hay muchos más.

Seleccionamos la opción de Aplicar y dejamos que Synaptic instale y configure los paquetes seleccionados.

Ahora pasamos a instalar la biblioteca GTK2HS. Para ello, vamos a realizar los mismos pasos: en primer lugar, buscamos el término gtk2hs.

Existen muchos paquetes de gtk2hs, tantos como distintos tipos de bibliotecas gráficas. Nosotros hemos probado con la básica y funciona bien. No obstante, si fuera necesario instalar algún paquete más, lo actualizaríamos en esta wiki.

Seleccionamos los siguientes paquetes: gtk2hs-doc (documentación) y libghc-gtk-dev (biblioteca en sí). Éste último paquete tiene como dependencias a los siguientes paquetes: libghc6-cairo-dev y libghc6-glib-dev. Los marcamos para instalarlos y le damos a Aplicar

Con esto ya tendríamos instalados tanto el compilador ghc como la biblioteca gtk2hs.

Instalación desde la línea de comandos

Iniciamos una terminal y escribimos lo siguiente:

use apt-get update && apt-get install ghc6 ghc6-prof ghc6-doc

Con ésto actualizaríamos el repositorio e instalaríamos los paquetes básicos necesarios para que funcione el compilador Ghc

Para instalarnos la biblioteca gtk2hs, debemos descargarnos el archivo necesario a nuestra distribución (en nuestro caso Ubuntu), copiarlo en nuestro directorio home y desempaquetarlo.

Abrimos una terminal y vamos al directorio donde lo hemos desempaquetado:

$ cd ~/gtk2hs-0.10.1/

Ahora debemos instalar las dependencias del paquete Gtk2hs:

$ sudo apt-get install build-essential libghc6-mtl-dev libglib2.0-dev build-dep libghc6-gtk-dev

Ahora ejecutaremos el script de configuración (pondremos hcflags con un valor de O2 para optimizar el código en la biblioteca, pero será más lento)

$ ./configure --with-hcflags=O2

Después, el paquete necesita ser construido e instalado:

$ sudo make
$ sudo make install

Con esto ya estará instalado Gtk2hs

Para asegurarnos que está instalado de forma correcta, vamos al directorio de demostración dentro del directorio fuente de Gtk2hs:

$ cd ~/gtk2hs-0.10.1/demo/hello/

Compilamos el fichero que se encuentra allí y lo ejecutamos:

$ make
$ ./helloworld

Si obtiene algún error, no está instalado correctamente. Si no, y todo está correcto, deberíamos ver una ventana con el famoso mensaje "Hello World"


Instalación desde Windows

Para instalar Gtk2Hs para Windows debemos seguir las siguientes instrucciones:

  1. Debemos tener instalado el compilador <a rel="nofollow" class="external text" href="http://haskell.org/ghc/download.html">GHC 6.6.1.</a>, aunque si tenemos otra versión, podemos trabajar con una versión más antigua de Gtk2Hs (Por ejemplo, la versión 0.9.11 funciona con la versión 6.4.2 de GHC). También podemos optar por crearlo desde su código fuente (ver apartado siguiente <a href="#Construcci.C3.B3n_desde_c.C3.B3digo">Construcción desde código</a>)
  2. Descargar y ejecutar el instalador <a rel="nofollow" class="external text" href="http://downloads.sourceforge.net/gtk2hs/gtk2hs-0.9.12.exe">gtk2hs-0.9.12.exe</a>
  3. Puedes optar por instalar el código de la demo y la documentación de la API

Con esto tendremos instalada la biblioteca Gtk2Hs bajo Windows, por lo que podremos compilar y ejecutar programas en Haskell que usen Gtk2Hs.

Construcción desde código

Las instrucciones para instalarlo desde el código fuente bajo Windows son las siguientes:

  1. Debemos tener instalado <a rel="nofollow" class="external text" href="http://haskell.org/ghc/download.html">GHC</a>
  2. Debemos instalar MinGW y MSYS desde <a rel="nofollow" class="external text" href="http://www.mingw.org/">mingw.org</a>. MinGW provee una versión de gcc y cabeceras de desarrollo para Windows. MSYS es un shell al estilo de Unix que necesitaremos para ejecutar el programa de configuración y el proceso de construcción. Si prefiere usar cygwin, puede que funcione, pero no ha sido probado todavía.
  3. Instalar Gtk+ desde <a rel="nofollow" class="external text" href="http://gladewin32.sourceforge.net/modules/wfdownloads/viewcat.php?cid=1">gladewin32.sourceforge.net</a>.
    1. Se puede usar cualquier versión, pero la versión 2.10 es la recomendada actualmente. Además, tiene una apariencia de ventanas, especialmente en Windows XP.
    2. Se debe instalar Gtk+ en un directorio que no tenga espacios en su nombre (Por ejemplo, no bajo “C:/Program Files/”).
    3. Cuando esté instalando Gtk+, debe seleccionar “Register Environment Variables” e instalar “Devel headers/libraries”.
    4. Glade es una herramienta con una interfaz de usuario visual. No es necesario instalarla, pero es bueno si tiene que desarrollar sus propias interfaces.
  4. Descargar el archivo fuente de Gtk2Hs en el directorio donde se quiere construir, y entonces abrimos una ventana del shell MSYS y escribimos lo siguiente:
$ cd [directorio donde lo hemos descargado]
$ tar -xzf gtk2hs-0.9.11.tar.gz
$ cd gtk2hs-0.9.11
$ ./configure --prefix=C:/Gtk2Hs
$ make
$ make install
$ make installcheck #Esto es opcional, ésto lo que hace es compilar todas las demos

Con esto ya seremos capaces de compilar y ejecutar programas de Haskell que usen Gtk2Hs

Hay que hacer notar que el prefijo pasado a ./configure no puede ser un path del estilo de MinGW, sino que debe usar los separadores de directorios del estilo de Unix '/' en vez de los normales de Windows '\'

Compilando nuestros programas

Para poder compilar nuestros programas, debemos utilizar el compilador de Haskell de Glasgow (Glasgow Haskell Compiler) GHC, que debemos utilizar de la siguiente manera.

$ ghc --make nombreFichero.hs -o salida

Una vez que tenemos compilada nuestro programa debemos ejecutarlo con ayuda de la siguiente orden:

$ ./salida

Listo podremos comprobar los resultados obtenidos por nuestro problema.

Primeros Pasos con Gtk2Hs

Empezaremos creando un pequeña interfaz simple y sencilla, para ver como funciona esta biblioteca. Si queremos obtener más información podremos ojear un poco la referencia de la API.

¡Hola Mundo!

En primer lugar empezaremos haciendo un programa sencillo, en el que crearemos una ventana que nos saludará, es una versión con interfaz gráfica del famoso programa de Hola Mundo. A continuación podremos observar un el código que permite generar el programa y la salida gráfica que genera:

import Graphics.UI.Gtk

saludo :: (ButtonClass o) => o -> IO ()
saludo b = set b [buttonLabel := "Hola Mundo"]

main :: IO ()
main = do
 initGUI
 window <- windowNew
 button <- buttonNew
 set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
             containerChild := button, containerBorderWidth := 10]
 onClicked button (saludo button)
 onDestroy window mainQuit
 widgetShowAll window
 mainGUI
<a href="/wikihaskell/index.php/Archivo:Holamundo.png" class="image"><img alt="" src="/wikihaskell/images/thumb/7/75/Holamundo.png/180px-Holamundo.png" width="180" height="195" class="thumbimage" /></a>
<a href="/wikihaskell/index.php/Archivo:Holamundo.png" class="internal" title="Aumentar"><img src="/wikihaskell/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a>
Salida Ejemplo - ¡Hola Mundo!

Para este ejemplo concreto hemos utilizado un Widget, en este caso un botón. Existe la posibilidad de establecer las propiedades de un Widget, a través de la función general set. Gracias al atributo containerChild de la ventana, podemos estblecer un vinculo entre nuestro botón y la ventana principal. Analizaremos por parte el código empleado.

saludo :: (ButtonClass o) => o -> IO ()
saludo b = set b [buttonLabel := "Hola Mundo"]

Es una función muy simple, que recibe como parámetro un botón al que le cambiaremos la etiqueta del mismo, para que su mensaje sea Hola Mundo.

Por otro lado, todos los programa en Gtk2Hs deben comenzar por do:

initGUI

Con las dos lineas de código siguiente, estamos creando dos objetos, uno de ellos es la ventana principal de la aplicación, y el otro objeto es el botón con el que podremos interactuar. Este botón no tendrá escrito ningún mensaje en su etiqueta, para que solo en el momento del click, asociar el valor Hola Mundo a esta etiqueta.

window <- windowNew
button <- buttonNew

Llegados a este punto, nos encargaremos de establecer las dimensiones de la ventana, asociar el botón que hemos creado, a esta ventana, e indicar un pequeño margen, para que el tamaño del botón sea inferior, al total ocupado por la ventana.

set window [windowDefaultWidth := 200, windowDefaultHeight := 200,containerChild := button, containerBorderWidth := 10]

Luego asociamos al evento click del botón, la función Saludo, que se encarga de colocar en el contenido de la etiqueta del botón, el mensaje Hola Mundo.

onClicked button (Saludo button)

Antes de finalizar, deberemos mostrar la ventana, y asociar el fin de la ejecución del main, al evento que se produce, cuando se cierra la ventana.

onDestroy window mainQuit
widgetShowAll windows

La última línea del main, se encarga de activar el bucle principal del programa

mainGUI

esta línea podremos observarla en todas las palicaciones de Gtk2Hs. Una vez que el programa alcanza este punto, el programa se queda a la escucha de eventos que pudieran suceder.

Empaquetado de componentes (Widgets)

Antes de nada, debemos entender cual es el concepto de "Empaquetado". En el primer ejemplo que hemos propuesto, el Hola Mundo! con interfaz gráfica, hemos utilizado unicamente un componente (Widget), por lo que pudimos asignar el widget de manera sencilla a la ventana, con ayuda del set y el containerChild.

 set window [windowDefaultWidth := 200, windowDefaultHeight := 200,containerChild := button, containerBorderWidth := 10]

Pero con esto tenemos un pequeño problema. Cuando queramos colocar más de un Widget en una ventana, ¿cómo controlamos la posición de cada uno de ellos?. Esta es la función del empaquetado.

Teoría de las cajas de empaquetado

Para realizar este empaquetado, normalmente se realiza creando cajas donde se introducirán estos componentes. Existen contenedores invisibles de Widgets, que se utilizan para realizar este empaquetado. Existen dos tipos de contenedores

  • Contenedor de caja horizontal. Cuando los Widgets son insertados en este tipo de contenedores, estos van siendo insertados horizontalmente de izquierda a derecha o de derecha a izquierda, en función de la llamada utilizada.
  • Contenedor de caja vertical. En este tipo de cajas, los widgets van empaquetandose de arriba hacia abajo o viceversa, en función de la llamada utilizada, igual que ocurría en los contenedores de caja horizontal.

Para crear estos contenedores utilizaremos hBoxNew y vBoxNew para crear una caja horizontal y vertical respectivamente. Ambos usan un parámetro de tipo Bool y otro de tipo Int. El primero de estos parámetros indica que entre cada widget existen espacios iguales si el valro recibido es True y el segundo de los parámetros indica el espacio, en número de pixels, a colocar entre los cada uno de los objetos insertados.

Para colocar los objetos dentro de estos contenedores utilizaremos dos funciones:

  • boxPackStart. Esta función introduce los objetos de arriba hacia abajo en los contenedores verticales y de izquierda a derecha en los contenedores horizontales.
  • boxPackEnd. Con esta función estaremos introduciendo los objetos de abajo hacia arriba en los contenedores verticales y de izquierda a derecha en los horizontales.

Es posible colocar contenedores, dentro de otros contenedores, para obtener un dinamismo mucho mayor.

Ejemplo de empaquetado

Veamos un ejemplo de empaquetado, en el que utilizamos un contenedor horizontal junto con la salida que genera:

import Graphics.UI.Gtk

main :: IO ()
main = do
 initGUI
 window  <- windowNew
 hbox    <- hBoxNew True 10
 btn1 <- buttonNewWithLabel "Botón 1"
 btn2 <- buttonNewWithLabel "Botón 2"
 btn3 <- buttonNewWithLabel "Botón 3"
 set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
             containerBorderWidth := 10, containerChild := hbox]
 boxPackStart hbox btn1 PackGrow 0
 boxPackStart hbox btn2 PackGrow 0
 boxPackStart hbox btn3 PackGrow 0
 onDestroy window mainQuit
 widgetShowAll window
 mainGUI
<a href="/wikihaskell/index.php/Archivo:Empaquetado.png" class="image"><img alt="" src="/wikihaskell/images/thumb/1/14/Empaquetado.png/180px-Empaquetado.png" width="180" height="144" class="thumbimage" /></a>
<a href="/wikihaskell/index.php/Archivo:Empaquetado.png" class="internal" title="Aumentar"><img src="/wikihaskell/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a>
Salida Ejemplo - Empaquetado

Las funciones boxPackStart y boxPackEnd, reciben además dos parámetros, el tercero, indica como se comportan los widgets cuando se modifica el tamaño de la ventana, y sus valores pueden ser:

  • PackGrow -> Los componentes adaptan su tamaño a la ventana.
  • PackNatural -> Los componentes mantienen su tamaño y posición.
  • PackRepel -> Los componentes serán espaciados por ambos lados, pero manteniendo su tamaño.

El último de los parámetros indica el espacio que se dejará entre este componente y sus vecinos.

Empaquetar mediante tablas

Existe otro método para empaquetar: las tablas. Con las tablas, vamos a usar una cuadrícula (grid) en la que podemos colocar los widgets, que ocuparán tantos espacios como especifiquemos.

Para crear una tabla se usará la función tableNew (Nueva tabla). El primer argumento será el número de filas y el segundo el número de columnas. El tercer argumento establece la manera en la que se da tamaño a las cajas de la tabla. Si lo establecemos a True, las cajas se crean al tamaño del mayor widget de la tabla, y todas iguales. Si lo establecemos a False, el tamaño de las cajas de la tabla estará determinado por el widget más alto de su fila, y el widget más ancho de su columna.

tableNew :: Int -> Int -> Bool -> IO Table

Las filas y columnas se numeran desde 0 hasta n, donde n es el número especificado en la llamada a tableNew. Así si especificas filas = 2 y columnas = 2, la distribución tendrá el aspecto siguiente:

 0          1          2
0+----------+----------+
 |          |          |
1+----------+----------+
 |          |          |
2+----------+----------+

El sistema de coordenadas comienza en la esquina superior izquierda.

Para colocar un widget en una tabla, usamos la función tableAttach, que tiene las siguientes opciones:

tableAttach :: (TableClass self, WidgetClass child)
 => self            -- self         - La tabla.
 -> child           -- child        - El widget a añadir.
 -> Int             -- leftAttach   - El número de la columna para situar el lado izquierdo 
                    --                del widget a colocar.
 -> Int             -- rightAttach  - El número de la columna para situar el lado derecho
                    --                del widget a colocar.
 -> Int             -- topAttach    - El número de la fila donde situar la parte superior del
                    --                widget a colocar.
 -> Int             -- bottomAttach - El número de la fila donde situar la parte inferior del
                    --                widget a colocar.
 -> [AttachOptions] -- xoptions     - Se emplea para indicar las propiedades del widget hijo
                    --                cuando la tabla modifica su tamaño (horizontales)
 -> [AttachOptions] -- yoptions     - Igual que xoptions, exceptuando que el campo determina
                    --                el comportamiento vertical
 -> Int             -- xpadding     - Un valor entero que especifica el espacio en blanco a
                    --                izquierda y derecha de la tabla del widget que se añade.
 -> Int             -- ypadding     - El espacio arriba y abajo del widget.
 -> IO ()

El primer argumento, (self), es la tabla que has creado y el segundo (child), es el widget que quieres colocar en la tabla.

Los argumentos izquierdo y derecho (leftAttach, rightAttach) indican dónde debe ponerse el widget, y cuántas cajas usar. xoptions y yoptions se usan para indicar las opciones de empaquetado y la lista puede contener más de una para permitir múltiples opciones.


Las posibles opciones son:

  • Fill :Si la caja de la tabla es mayor que el widget, y se especifica Fill, el widget se expandirá hasta ocupar todo el espacio disponible.
  • Shrink: Si el widget de la tabla tiene disponible menos espacio del que necesita (normalmente porque el usuario ha cambiado el tamaño de la tabla), los widgets deberían ser empujados de la parte inferior de la tabla y desaparecerían. Si se especifica Shrink los widgets de reducirán con la tabla.
  • Expand: Esto originará que la tabla se expanda hasta usar todo el espacio disponible en la ventana.

El padding funciona como en las cajas, dejando un área libre alrededor del widget, del tamaño especificado en píxeles.

También tenemos tableSetRowSpacing y tableSetColSpacing. Estas funciones colocan espacios en la fila (row) o columna (col) indicada.

tableSetRowSpacing :: TableClass self=> self-> Int -> Int -> IO ()
tableSetColSpacing :: TableClass self => self -> Int -> Int -> IO ()

El primer argumento Int es la fila/columna y el segundo es el espacio en píxeles. En el caso de las columnas, el espacio se sitúa a la derecha de la columna y en las filas, bajo la fila.

Puedes fijar un espacio fijo para todas las filas y/o las columnas con:

tableSetRowSpacings :: TableClass self=> self-> Int -> IO ()

y:

tableSetColSpacings :: TableClass self => self -> Int -> IO ()

Hay que tener en cuenta que con estas llamadas, la última fila y la última columna no consiguen ningún espacio.


Ejemplo de empaquetado con tabla

Vamos a hacer una ventana con tres botones en una tabla 2x2. Los primeros dos botones se colocarán en la fila de arriba. Un tercer botón (Quit) se colocará en la fila de abajo, ocupando ambas columnas. Este es el código fuente junto con la salida mostrada:

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  window  <- windowNew
  set window [windowTitle := "Table", containerBorderWidth := 20,
              windowDefaultWidth := 150, windowDefaultHeight := 100]
  table   <- tableNew 2 2 True
  containerAdd window table
  button1 <- buttonNewWithLabel "On"
  onClicked button1 (buttonSwitch button1)
  tableAttachDefaults table button1 0 1 0 1
  button2 <- buttonNewWithLabel "Off"
  onClicked button2 (buttonSwitch button2)
  tableAttachDefaults table button2 1 2 0 1
  button3 <- buttonNewWithLabel "Quit"
  onClicked button3 mainQuit
  tableAttachDefaults table button3 0 2 1 2
  onDestroy window mainQuit
  widgetShowAll window
  mainGUI 

buttonSwitch :: Button -> IO ()
buttonSwitch b = do
  txt <- buttonGetLabel b
  let newtxt = case txt of
                 "Off" -> "On"
                 "On"  -> "Off"
  buttonSetLabel b newtxt  
<a href="/wikihaskell/index.php/Archivo:Botones.png" class="image"><img alt="" src="/wikihaskell/images/thumb/3/3d/Botones.png/180px-Botones.png" width="180" height="151" class="thumbimage" /></a>
<a href="/wikihaskell/index.php/Archivo:Botones.png" class="internal" title="Aumentar"><img src="/wikihaskell/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a>
Salida Ejemplo - Empaquetado con tablas

La función buttonSwitch se asocia a ambos botones de la fila superior. La función buttonGetLabel es un ejemplo de cómo conseguir un atributo de un widget usando un método estándar. Hay una alternativa más general get (análoga a set) que toma un widget y un atributo. En el ejemplo anterior sería:

 txt <- get b buttonLabel

con el mismo resultado.

Otros Componentes Disponibles

Trabajando con el componente Botón

Existen diferentes maneras de crear un botón:

  • buttonNewWithLabel nombreEtiqueta
  • buttonNewWithMnemonic nombreEtiqueta
  • buttonNew

Dependiendo de las características iniciales que queramos que posea nuestro boton usaremos, buttonNew cuando queramos un boton con ningún valor inicial. Cuando usamos buttonNewWithLabel nombreEtiqueta, estaremos creando un botón en la que aparacerá como etiqueta, el nombre que pasamos como parámetro nombreEtiqueta. Por último podremos utilizar el constructor buttonNewWithMnemonic nombreEtiqueta para tener un boton con en la que aparecerá como etiqueta, el nombre que pasamos como parámetros nombreEtiqueta. Dicho esto podemos hacer una pregunta evidente:

¿Qué diferencia existe entre <b>buttonNewWithLabel</b> y <b>buttonNewWithMnemonic</b>?.

Pues muy sencillo cuando usamos buttonNewWithMnemonic la cadena que pasamos como parámetro puede contener el carácter '_' delante de cualquier carácter. En ese caso dicho carácter se utilizará como tecla de acceso rápido para hacer click sobre ese botón. Veamos un ejemplo:

import Graphics.UI.Gtk

main :: IO ()
main = do
initGUI
window  <- windowNew
vbox    <- vBoxNew True 10
hbox    <- hBoxNew True 10
nombre  <- labelNew (Just "Mi nombre es:")
btnLlamar <- buttonNewWithMnemonic "L_lamar"
set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
             containerBorderWidth := 10, containerChild := vbox]
 
image <- imageNewFromFile "charlie.png"
boxPackStart hbox nombre PackNatural 0
boxPackStart hbox image PackNatural 0
boxPackStart vbox hbox PackNatural 0
boxPackStart vbox btnLlamar PackNatural 0

onClicked btnLlamar (labelSetText nombre "Mi nombre es: Charlie Brown!!")

onDestroy window mainQuit
widgetShowAll window
mainGUI
<a href="/wikihaskell/index.php/Archivo:Paso1.jpg" class="image"><img alt="" src="/wikihaskell/images/thumb/2/24/Paso1.jpg/180px-Paso1.jpg" width="180" height="335" class="thumbimage" /></a>
<a href="/wikihaskell/index.php/Archivo:Paso1.jpg" class="internal" title="Aumentar"><img src="/wikihaskell/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a>
Antes de pulsar
<a href="/wikihaskell/index.php/Archivo:Paso2.jpg" class="image"><img alt="" src="/wikihaskell/images/thumb/a/a8/Paso2.jpg/180px-Paso2.jpg" width="180" height="260" class="thumbimage" /></a>
<a href="/wikihaskell/index.php/Archivo:Paso2.jpg" class="internal" title="Aumentar"><img src="/wikihaskell/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a>
Tras pulsar

En primer lugar hemos creado dos contenedores uno vertical (vbox) y otro horizontal (hbox)

vbox    <- vBoxNew True 10
hbox    <- hBoxNew True 10

A continuación crearemos un componente etiqueta y un componente botón. La etiqueta la hemos creado con su texto justificado (Just) y el valor de su etiqueta es "Mi nombre es:". El botón aparecera con la etiqueta "Llamar", con acceso rápido mediante ALT + l.

nombre  <- labelNew (Just "Mi nombre es:")
btnLlamar <- buttonNewWithMnemonic "L_lamar"

Estableceremos en la ventana como contenedor principal, el contenedor vertical.

set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
             containerBorderWidth := 10, containerChild := vbox]

Con el siguiente fragmento de codigo estamos cargando una imagen desde un fichero, en nuestro caso la imagen de Carlitos.

image <- imageNewFromFile "charlie.png"

Una vez creados todos los objetos necesarios, procederemos al empaquetado de los mismos, detallados en el apartado <a href="#Empaquetado_de_componentes_.28Widgets.29">empaquetados</a>

boxPackStart hbox nombre PackNatural 0
boxPackStart hbox image PackNatural 0
boxPackStart vbox hbox PackNatural 0
boxPackStart vbox btnLlamar PackNatural 0

Por último asociaremos al evento click del botón la sentencia que permitirá establecer el nombre de nuestro personaje.

onClicked btnLlamar (labelSetText nombre "Mi nombre es: Charlie Brown!!")

Evento relacionados con el componente Botón

Es posible asociar una sentencia a determinados eventos que posee el componente botón, estos eventos son bastante intuitivos y son los siguientes:

  1. onPressed - Este evento se produce justo cuando se pulsa el botón.
  2. onReleased - Este evento se produce cuando se deja de pulsar el botón.
  3. onClicked - Este evento se produce cuando se hace un click sobre el botón (pulsar y soltar).
  4. onEnter - Este evento se dispara cuando el cursor entra en la zona delimitada por el botón.
  5. onLeave - Este evento se dispara cuando el cursor sale de la zona delimitada por el botón.

Otros tipos especiales de botones

Toggle Buttons

Otro tipo de botones alternativos son los Toggle Buttons. Estos botones son muy parecidos a los botones normales, que hemos explicado previamente. La diferencia radica en que estos botones poseen dos estados que van alternando. Este cambio se produce cada vez que uno de estos botones es pulsado. El modo de crear un botón Toogle es el siguiente:

  • toggleButtonNewWithLabel nombreEtiqueta
  • toggleButtonNewWithMnemonic nombreEtiqueta
  • toggleButtonNew

El funcionamiento de cada uno de estos constructores es similar a su equivalente para los botones normales. Existe un par de funciones útiles para los Toggle Buttons, que son toggleButtonGetActive y toggleButtonSetActive. La primera función recibe como único parámetro el botón del que se quiere comprobar su estado, que será True si está pulsado y False si no lo está. La función toggleButtonSetActive recibe dos parámetro, el primero es el botón al que se le quiere cambiar el estado y el segundo True o False, dependiendo del estado que queramos establecerle a dicho botón.

Como evento especial estos botones tienen el evento onToggled, que se activa cuando se produce un cambio entre los estados de un botón.

Check Buttons

Los botones Check, son una instancia de la clase de los botones toggle (ToggleButtonClass), y por tanto heredan muchas de sus propiedades. Para crear un objeto de este tipo, utilizaremos los siguientes constructores, cuyo funcionalidad es la misma que la que hemos ido viendo hasta el momento.


  • checkButtonNewWithLabel nombreEtiqueta
  • checkButtonNewWithMnemonic nombreEtiqueta
  • checkButtonNew
Radio Buttons

Este tipo de botones se comportan de forma similar a los Check Buttons, salvo por un pequeño matiz. Los Radio buttons pueden agruparse de modo que dentro del mismo grupo solo pueda estar seleccionado uno en cada momento, es decir, es muy útil cuando se quieren usar opciones excluyentes.

Para crear una Radio Button podemos usar una de los siguientes constructores:

  • radioButtonNew
  • radioButtonNewWithLabel nombreEtiqueta
  • radioButtonNewWithMnemonic nombreEtiqueta

Estos constructores se utilizan de forma similar a los anteriores ya mostrados. Cuando queramos crear un nuevo Radio button, asociado a un grupo utilizaremos los siguientes constructores, que reciben un parámetro más (radioButton), que indica el botón al que está asociado el nuevo radio button para hacer un grupo.

  • radioButtonNewFromWidget radioButton
  • radioButtonNewWithLabelFromWidget radioButton nombreEtiqueta
  • radioButtonNewWithMnemonicFromWidget radioButton nombreEtiqueta

Cada vez que se produce un cambio en el botón seleccionado dentro de un grupo, se producirán dos eventos onToggled, uno cuando se desactive el que estaba seleccionado y otro cuando se active el nuevo botón seleccionado.

Al igual que en los Toggle Buttons, podremos utilizar la función toggleButtonSetActive para indicar el estado de un botón determinado.

Etiquetas

Para crear una nueva etiqueta, hay que usar las siguientes instrucciones:

labelNew :: Maybe String -> IO Label
labelNewWithMnemonic :: String -> IO Label

Con la segunda función, si algún caracter de la cadena está precedido de un guión bajo (o subrayado), se subraya al representarlo. Si necesitas un carácter guión bajo en una etiqueta, debes usar "__" (dos subrayados). El primer caracter subrayado que aparece representa un atajo de teclado que denominamos mnemónico. Cuando se pulsa esa tecla, el widget disponible que contiene esa etiqueta (un botón, por ejemplo) se activa.

El widget al que queremos asociar un nmemónico se puede establecer con labelSetMnemonicWidget.

Para cambiar el texto de la etiqueta después de haber sido creada, o para conseguir el texto de la misma, puedes usar las funciones:

labelSetText :: LabelClass self => self -> String -> IO ()
labelGetLabel :: LabelClass self => self -> IO String

o, por supuesto, las funciones genéricas set o get. El espacio necesario para la nueva cadena (string) sería ajustado automáticamente si fuera preciso. Se pueden producir etiquetas multilínea, colocando saltos de línea en la cadena de la etiqueta. La justificación del texto en las etiquetas multilínea se realiza con la función:

labelSetJustify :: LabelClass self => self -> Justification -> IO ()

donde el tipo Justification puede usar alguno de estos cuatro constructores:

*JustifyLeft Izquierda
*JustifyRight Derecha
*JustifyCenter Centrado
*JustifyFill Ambos

El widget etiqueta es también capaz de fijar los saltos de línea en el texto automáticamente. Esto se puede activar usando:

labelSetLineWrap :: LabelClass self => self -> Bool -> IO ()

Si quieres que la etiqueta esté subrayada, puedes establecer un patrón para la etiqueta:

labelSetPattern :: LabelClass self => self -> [Int] -> IO ()

La lista de Ints (enteros) marca las partes del texto de la etiqueta que se subrayarán alternando con los caracteres que no se subrayarán. Por ejemplo, [3, 1, 3] significa que se subrayarán los tres primeros caracteres, el siguiente, no y los tres siguientes, sí. Puedes hacer, también, que se pueda seleccionar el texto de la etiqueta, de modo que el usuario pueda copiarlo y pegarlo, además de usar algunas opciones de formateo.

Abajo hay un ejemplo que ilustra algunas de estas funciones. Usa el widget Frame para demostrar mejor los estilos de la etiqueta. Un Frame no es más que un ornamento, igual que un HSeparator y un VSeparator pero en este caso rodea al widget y es una instancia de Container. Por tanto, el widget que encuadra debe ser añadido con containerAdd. Un Frame puede tener una etiqueta para transmitir información sobre su contenido.

Como todas las etiquetas del ejemplo tienen un Frame, se define una función myLabelWithFrameNew, que devuelve una tupla. Gtk2Hs es 100% Haskell, por lo que puedes usar todos los tipos de datos y característas de Haskell. Las justificaciones son obvias pero sólo se refieren a las líneas de dentro de la etiqueta. Así que, para justificar a la derecha label2, necesitas miscSetAlignment tal y como se muestra más abajo. Los últimos dos widgets en la caja horizontal de la izquierda se empaquetan con boxPackEnd en vez del usual boxPackStart. La etiqueta botón demuestra el uso de un mnemónico como atajo. Pulsar Alt-C en el teclado provoca el mismo efecto que pulsar el botón con el ratón.

import Graphics.UI.Gtk
main:: IO ()
main = do
  initGUI
  window  <- windowNew
  set window [windowTitle := "Labels", containerBorderWidth := 10]
  mainbox <- vBoxNew False 10
  containerAdd window mainbox
  hbox    <- hBoxNew True 5
  boxPackStart mainbox hbox PackNatural 0
  vbox1   <- vBoxNew False 10
  vbox2   <- vBoxNew False 0
  boxPackStart hbox vbox1 PackNatural 0
  boxPackStart hbox vbox2 PackNatural 0 
  (label1,frame1) <- myLabelWithFrameNew
  boxPackStart vbox1 frame1 PackNatural 0
  labelSetText label1 "Penny Harter"
  (label2,frame2) <- myLabelWithFrameNew
  boxPackStart vbox1 frame2 PackNatural 0
  labelSetText label2 "broken bowl\nthe pieces\nstill rocking"
  miscSetAlignment label2 0.0 0.0
  hsep1           <- hSeparatorNew
  boxPackStart vbox1 hsep1 PackNatural 10 

  (label3,frame3) <- myLabelWithFrameNew
  boxPackStart vbox1 frame3 PackNatural 0
  labelSetText label3 "Gary Snyder"    
  (label4,frame4) <- myLabelWithFrameNew
  boxPackStart vbox1 frame4 PackNatural 0
  labelSetText label4 "After weeks of watching the roof leak\nI fixed it tonight\nby moving a single board"
  labelSetJustify label4 JustifyCenter 
  (label5,frame5) <- myLabelWithFrameNew
  boxPackStart vbox2 frame5 PackNatural 0
  labelSetText label5 "Kobayashi Issa"
  (label7,frame7) <- myLabelWithFrameNew
  boxPackEnd vbox2 frame7 PackNatural 0
  labelSetText label7 "only one guy and\nonly one fly trying to\nmake the guest room do"
  labelSetJustify label7 JustifyRight
  (label6,frame6) <- myLabelWithFrameNew
  boxPackEnd vbox2 frame6 PackNatural 10
  labelSetText label6 "One Guy"
  frameSetLabel frame6 "Title:"
  labelSetPattern label6 [3, 1, 3] 
  button      <- buttonNew
  boxPackEnd mainbox button PackNatural 20
  buttonlabel <- labelNewWithMnemonic "Haiku _Clicked"
  containerAdd button buttonlabel
  widgetShowAll window
  onClicked button (putStrLn "button clicked...")
  onDestroy window mainQuit
  mainGUI


myLabelWithFrameNew :: IO (Label,Frame)
myLabelWithFrameNew = do
  label <- labelNew Nothing
  frame <- frameNew
  containerAdd frame label
  frameSetShadowType frame ShadowOut
  return (label, frame)

Flechas y Tooltips

El widget Arrow (flecha) dibuja una cabeza de flecha, apuntando en una dirección y con un estilo seleccionable. Igual que el widget etiqueta, no emite señales.

Sólo hay dos funciones para manipular un widget Arrow:

arrowNew :: ArrowType -> ShadowType -> IO Arrow
arrowSet :: ArrowClass self => self -> ArrowType -> ShadowType -> IO ()

El ArrowType tiene cinco constructores:

  • ArrowUp Arriba
  • ArrowDown Abajo
  • ArrowLeft Izquierda
  • ArrowRight Derecha
  • ArrowNone Ninguno


El ShadowType (tipo de sombra) también tiene cinco constructores:

  • ShadowIn Dentro
  • ShadowOut Fuera
  • ShadowEtchedIn Grabado dentro
  • ShadowEtchedOut Grabado fuera
  • ShadowNone Sin sombra


Los Tooltips son esas pequeñas frases que surgen cuando dejas el puntero sobre un botón u otro widget durante unos segundos.

Los widgets que no reciben eventos (los que no tienen su propia ventana) no funcionan con los tooltips.

Esta primera llamada creará un tooltip nuevo. Sólo necesitas llamarla una vez para crear un conjunto de tooltips.

tooltipsNew :: IO Tooltips

Después, para cada widget, usa:

tooltipsSetTip :: (TooltipsClass self, WidgetClass widget)
 => self -> widget -> String -> String -> IO ()

El primer argumento es el tooltip que ya has creado, seguido por el widget para el que quieres el tooltip y el texto que quieres que aparezca. El último argumento es una cadena de texto que puede usarse como su identificador.

Puedes activar o desactivar los mensajes asociados a un Tooltips mediante:

tooltipsEnable :: TooltipsClass self => self -> IO ()
tooltipsDisable :: TooltipsClass self => self -> IO ()

Veamos un sencillo ejemplo en el que aparecen 4 flechas, en las que al dejar el raton sobre ellas, aparecen las etiquetas de Tooltips.

<a href="/wikihaskell/index.php/Archivo:Tooltips.png" class="image" title="RTENOTITLE"><img alt="RTENOTITLE" src="/wikihaskell/images/e/e5/Tooltips.png" width="339" height="234" /></a>

Este seria el código:

 import Graphics.UI.Gtk
 main :: IO ()
 main = do
  initGUI
  window <- windowNew
  set window [windowTitle := "Arrow Tips",
              windowDefaultWidth := 200,
              windowDefaultHeight := 200, containerBorderWidth := 20]

  table <- tableNew 5 5 True
  containerAdd window table

  button1 <- buttonNew
  button2 <- buttonNew
  button3 <- buttonNew
  button4 <- buttonNew

  tableAttachDefaults table button1 0 1 2 3
  tableAttachDefaults table button2 2 3 0 1
  tableAttachDefaults table button3 4 5 2 3
  tableAttachDefaults table button4 2 3 4 5

  tlt <- tooltipsNew

  arrow1 <- arrowNew ArrowLeft ShadowEtchedIn
  containerAdd button1 arrow1
  tooltipsSetTip tlt button1 "Oeste" "T1"

  arrow2 <- arrowNew ArrowUp ShadowEtchedOut
  containerAdd button2 arrow2
  tooltipsSetTip tlt button2 "Norte" "T2"

  arrow3 <- arrowNew ArrowRight ShadowEtchedIn
  containerAdd button3 arrow3
  tooltipsSetTip tlt button3 "Este" "T3"

  arrow4 <- arrowNew ArrowDown ShadowEtchedOut
  containerAdd button4 arrow4
  tooltipsSetTip tlt button4 "Sur" "T4"

  tooltipsEnable tlt
  widgetShowAll window
  onDestroy window mainQuit
  mainGUI

Diálogos, Stock Items y barras de Progreso

Un diálogo es un ejemplo de widget compuesto. Consta de una ventana, una parte superior que es una caja vertical, y un área de acción que es una caja horizontal que suele constar de uno o varios botones. Normalmente ambas partes están separadas por un separador horizontal.

El widget Dialog puede usarse para mensajes pop-up (surgen de la aplicación) al usuario, u otras tareas similares. Las funciones básicas necesarias son:

dialogNew :: IO Dialog
dialogRun :: DialogClass self => self -> IO ResponseID

Se pueden añadir botones al area de acción con:

dialogAddButton :: DialogClass self => self -> String -> ResponseId -> IO Button

Cualquier widget puede ser añadido de un modo semejante con dialogAddActionWidget.

El String en dialogAddButton puede ser el texto del botón, pero como los diálogos se suelen usar en situaciones estándar, un StockItem suele ser más apropiado.

Los StockItems son recursos conocidos en Gtk2Hs, como los IconSets estándar. Un usuario puede definir los suyos, pero hay muchos predefinidos en el módulo Graphics.UI.Gtk.General.StockItems.

Tienen un identificador, StockId, que es un tipo sinónimo de String. Con este identificador, un widget (normalemente un botón) con el texto y el icono apropiados se selecciona automáticamente.

Si se usa un StockId al añadir un botón al diálogo, se puede usar un constructor ResponseId predefinido con los botones (ResponseId no es un String). Las respuestas pueden construirse con ResponseUser Int.

Siempre que se pulsa un botón de diálogo, su respuesta se pasa a la aplicación llamante a través de dialogRun. Según la documentación de la API de Gtk2Hs, dialogRun se bloquea en un bucle recursivo hasta que al diálogo o bien emite la señal de respuesta, o es destruido. El modo por defecto es modal, lo que indica que el usuario no puede acceder a ninguna otra ventana (de esta aplicación) mientras dialogRun esté esperando una respuesta.

Las barras de progreso se emplean para mostrar el estado de una operación en curso.

progressBarNew :: IO ProgressBar

A pesar de que sólo hay un tipo, hay dos modos diferentes de usar una barra de progreso. Si se conoce la cantidad de tarea realizada, la fracción (entre 0.0 y 1.0 incluido) se puede establecer con:

progressBarSetFraction :: ProgressBarClass self => self -> Double -> IO ()

Esto origina que la barra de progreso se "llene" con la cantidad indicada (entre 0.0 y 1.0). Para marcar el progreso, esta función se debe llamar cada cierto tiempo durante la operación. Si no se conoce la parte de la operación completada, la barra puede moverse de atrás hacia adelante con:

progressBarPulse :: ProgressBarClass self => self -> IO ()

También esta función debe ser llamada repetidamente, para mostrar que la actividad sigue en marcha. Hay otras muchas funciones para controlar la presentación de una barra de progreso, como la orientación, texto adicional, etc. La aplicación, sin embargo, no es trivial debido a que las barras de progreso se aplican normalmente con temporizadores u otras funciones para dar la ilusión de multitarea.Con Haskell concurrente puedes usar hilos(threads) y comunicación entre hilos.

En el siguiente ejemplo simulamos una actividad usando timeoutAdd, que ejecuta una función repetidamente con el intervalo especificado en milisegundos. La función se pasa a timeoutAdd y debe devolver un valor del tipo IO Bool. Cuando el valor es true, el temporizador se vuelve a activar, cuando es falso, se para. La prioridad de timeoutAdd es priorityDefault de tipo Priority.

timeoutAdd :: IO Bool -> Int -> IO HandlerId

En el ejemplo, definimos una función showPulse, que causa que la barra de progreso que compruebe y que siempre devuelva IO True. El paso, la cantidad que se moverá el indicador por la barra, se establece a 1.0 con progressBarSetPulseStep.

<a href="/wikihaskell/index.php/Archivo:GTK2HsBarraprogreso.png" class="image" title="RTENOTITLE"><img alt="RTENOTITLE" src="/wikihaskell/images/f/f6/GTK2HsBarraprogreso.png" width="297" height="116" /></a>

El ejemplo es un poco atípico en el uso del diálogo, ya que lo usamos para mostrar el progreso tras la pulsación del botón "aplicar". Para cerrar la aplicación, el diálogo debe ser destruido, destruyendo la ventana. Los botones de cerrar y cancelar no funcionan después de que "aplicar" haya sido seleccionada. Si han sido seleccionados antes de "aplicar", la aplicación se cierra. Esto se realiza comprobando la respuesta de dialogRun.

Si el widget de diálogo se destruye, se llama a mainQuit. Como se mencionó arriba, un Dialog consta de una ventana y dos cajas. Se debe acceder a las cajas a través de funciones especiales, y la barra de progreso se empaqueta en la parte superior con dialogGetUpper. Los botones en un diálogo son visibles por defecto, pero los widgets de la parte superior, no. Un Dialog es una instancia de la WindowClass, y por tanto, podemos ponerle el título y/o definir su tamaño si queremos.

Una característica trivial a tener en cuenta: Un widget sólo puede ser visible si su padre es visible. Así, para mostrar la barra de progreso, usamos widgetShowAll en la caja vertical y no widgetShow en la barra de progreso.

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI

  dia <- dialogNew
  set dia [windowTitle := "Time Flies"]
  dialogAddButton dia stockapply  Responseapply
  dialogAddButton dia stockCancel ResponseCancel
  dialogAddButton dia stockClose  ResponseClose

  pr <- progressBarNew
  progressBarSetPulseStep pr 1.0

  upbox <- dialogGetUpper dia
  boxPackStart upbox pr PackGrow 10
  widgetShowAll upbox

  answer <- dialogRun dia
  if answer == Responseapply 
     then do tmhandle <- timeoutAdd (showPulse pr) 500
             return ()
     else widgetDestroy dia

  onDestroy dia mainQuit
  mainGUI

showPulse :: ProgressBar -> IO Bool
showPulse b = do progressBarPulse b
                 return True

Entrada de texto y barras de estado

El widget Entry (Entrada de texto) permite que el texto sea tecleado y mostrado en una simple caja de línea de texto. Hay una gran cantidad de teclas que funcionan por defecto. Además, el usuario puede cambiar entre el modo de inserción y el modo de sobreescritura pulsando la tecla Insert.

Para crear un nuevo widget Entry, usa la siguiente función.

entryNew :: IO Entry

Para reemplazar o tomar el texto que está en el widget Entry:

entrySetText :: EntryClass self => self -> String -> IO ()
entryGetText :: EntryClass self => self -> IO String

Si no queremos que se pueda modificar el contenido de un widget Entry porque alguien escriba en él, podemos cambiar su estado editable. También podemos modificar su visibilidad (p. ej. para passwords), el número máximo de caracteres (0 si no hay máximo), si la entrada tiene un marco, o no, el número de caracteres para el que se va a dejar espacio, y algunos otros atributos. También es posible usar completado de texto (consultar EntryCompletion en la documentación de la API). Los atributos de Entry, accesibles por get y set, son:

 entryEditable :: EntryClass self => Attr self Bool  -- Por defecto True

 entryVisibility :: EntryClass self => Attr self Bool  -- Por defecto True

 entryMaxLength :: EntryClass self => Attr self Int -- 0 sin máximo, límite 66535

 entryHasFrame :: EntryClass self => Attr self Bool -- Por defecto False

 entryWidthChars :: EntryClass self => Attr self Int -- Por defecto -1, sin espacios


El tipo Entry es una instancia de EditableClass (Clase editable) y muchos de sus métodos y atributos están definidos allí. Algunos especialmente útiles son:

 editableInsertText :: EditableClass self => self -> String -> Int -> IO Int

 editableDeleteText :: EditableClass self -> Int -> Int -> IO ()

 editableSelectRegion :: EditableClass self => self -> Int -> Int -> IO ()

 editableDeleteSelection :: EditableClass self -> IO ()

donde los parámetros de tipo Int denotan las posiciones apropiadas de inicio y de finalización. El usuario también puede cortar, copiar y pegar a/desde el clipboard.

 editableCutClipboard :: EditableClass self => self -> IO ()

 editableCopyClipboard :: EditableClass self => self -> IO ()

 editablePasteClipboard :: EditableClass self => self -> IO ()

Todas estas toman la posición actual del cursor. Puedes obtener y establecer dicha posición con:

 editableGetPosition :: EditableClass self => self -> IO Int

 editableSetPosition :: EditableClass self => self -> Int

El cursor se muestra antes del caracter con el índice indicado en el widget. El valor debe ser menor o igual al número de caracteres en el widget. El valor -1 indica que el cursor debe posicionarse después del último caracter en la entrada.

La clase Editable tiene unas señales que usan funciones de orden mayor (no las estudiamos aquí). El widget Entry tiene una señal que se envia después que el usuario pulsa la tecla Enter:

onEntryActivate :: EntryClass ec => ec -> IO () -> IO (ConnectId ec)


También hay señales que se envían cuando el texto se corta, copia o pega, o cuando el usuario cambia del modo insertar a sobreescribir.

Las barras de estado (Status Bars) son widgets simples usados para mostrar un mensaje de texto. Mantienen una pila de los mensajes que se les han enviado, de modo que al mostrar el mensaje actual, vuelven a mostrar los mensajes de texto anteriores. Por defecto el usuario puede modificar su tamaño.

Para poder permitir que diferentes partes de una aplicación usen la misma barra de estado para mostrar los mensajes, el widget de la barra de estado emplea ContextIds que sirven para identificar diferentes "usuarios". Se muestra el mensaje en la parte superior de la pila, independientemente de su contexto. Los mensajes se almacenan con un criterio último en llegar,primero en salir, y no ordenados por identificador de contexto.

La barra de estado se crea con:

statusbarNew :: IO Statusbar

Para generar un nuevo ContextId utilizo la siguiente función, con un String usado como descripción textual del contexto:

statusbarGetContextId :: StatusbarClass self => self -> String -> IO ContextId

Aquí hay tres funciones que pueden operar en las barras de estado:

 statusbarPush :: StatusbarClass self => self -> ContextId -> String -> IO MessageId

 statusbarPop :: StatusbarClass self => self -> ContextId -> IO ()

 statusbarRemove :: StatusbarClass self => self -> ContextId -> MessageId -> IO ()

La primera, statusbarPush, se emplea para añadir un mensaje nuevo a la barra de estado. Devuelve un MessageId, que puede ser pasado más tarde a statusbarRemove para eliminar el mensaje en el ContextId y MessageId de la pila de la barra de estado. La función statusbarPop elimina el mensaje más alto de la pila dentro del identificador de contexto aportado.

Las barras de estado, como las barras de progreso, se usan para mostrar mensajes al usuario sobre alguna operación en ejecución. En el ejemplo inferior, simulamos esta operación comprobando si el texto que envía el usuario (pulsando Enter) es el mismo que su inverso, y enviando el resultado a la pila. El usuario puede ver los resultados pulsando el botón de información, que muestra la pila de mensajes. La primera vez, la pila está vacía, así que el botón está sombreado, usando:

widgetSetSensitivity :: WidgetClass self => self -> Bool -> IO ()

Fíjate que la barra de estado no sería la primera opción aquí, ya que no se comprueba si la pila está vacía, pero el ejemplo muestra como se aplica. El manejador del cambio de tamaño de la ventana de la barra de estado no está muy claro, pero está ahí, abajo a la derecha.

<a href="/wikihaskell/index.php/Archivo:Entradatexto.png" class="image" title="RTENOTITLE"><img alt="RTENOTITLE" src="/wikihaskell/images/7/74/Entradatexto.png" width="307" height="102" /></a>

import Graphics.UI.Gtk

main :: IO ()
main= do
  initGUI
  window <- windowNew
  set window [windowTitle := "Text Entry", containerBorderWidth := 10]

  vb <- vBoxNew False 0
  containerAdd window vb

  hb <- hBoxNew False 0
  boxPackStart vb hb PackNatural 0

  txtfield <- entryNew
  boxPackStart hb txtfield PackNatural 5
  button <- buttonNewFromStock stockInfo
  boxPackStart hb button PackNatural 0

  txtstack <- statusbarNew
  boxPackStart vb txtstack PackNatural 0
  id <- statusbarGetContextId txtstack "Line"

  widgetShowAll window
  widgetSetSensitivity button False

  onEntryActivate txtfield (saveText txtfield button txtstack id)
  onPressed button (statusbarPop txtstack id)
  onDestroy window mainQuit
  mainGUI

saveText :: Entry -> Button -> Statusbar -> ContextId -> IO ()
saveText fld b stk id = do
    txt <- entryGetText fld
    let mesg | txt == reverse txt = "\"" ++ txt ++ "\""  ++
                                    " is equal to its reverse"
             | otherwise =  "\"" ++ txt ++ "\""  ++
                            " is not equal to its reverse"
    widgetSetSensitivity b True
    msgid <- statusbarPush stk id mesg
    return ()

Un ejemplo Práctico

Hasta ahora hemos comentado como funcionan de manera independiente determinados componentes de la biblioteca Gtk2Hs y hemos puesto un ejemplo de su uso. Ahora ha llegado el momento de realizar un ejemplo que englobe varios componentes y que nos permita conocer el potente uso de esta biblioteca, con unos conocimientos básicos y un poco de imaginación. En este caso intentaremos crear una "calculadora" de frases, esta calculadora será sencillita y nos permitirá, dada una frase, determinar la longitud de la misma, el número de letras de la frase y el número de palabras que compone la frase. Veamos el código de ejemplo (Para mayor facilidad hemos colocado comentarios en el código que ayudan a comprender que estamos haciendo):

import Graphics.UI.Gtk

--Funciones Auxiliares
numLetras :: String -> Int
numLetras [] = 0
numLetras (x:xs) = if x == ' ' then 0 + numLetras xs else 1 + numLetras xs

longitud :: String -> Int
longitud [] = 0
longitud (x:xs) = 1 + longitud xs

numPalabras :: String -> Int
numPalabras [] = 1
numPalabras (x:xs) = if x == ' ' then 1 + numPalabras xs else 0 + numPalabras xs

calculo :: Entry -> RadioButton -> RadioButton -> RadioButton -> Label -> IO () 
calculo txt b1 b2 b3 lab = do
	--Obtenemos el estado de pulsado de cada radio button (solo puede estar uno pulsado)
	b1Pulsado <- toggleButtonGetActive b1
	b2Pulsado <- toggleButtonGetActive b2
	b3Pulsado <- toggleButtonGetActive b3
	--Obtenemos la frase introducida por el usuario
	texto <- entryGetText txt
	--imprimir devuelve la solución según el botón pulsado
	let imprimir | texto == "" = "No ha introducio ninguna frase"
		     | b1Pulsado = "El número de letras es: " ++ show (numLetras texto)
		     | b2Pulsado = "El número de palabras es: " ++ show (numPalabras texto) 
		     | otherwise = "La longitud de la cadena:" ++ show (longitud texto)
	labelSetText lab imprimir
	return ()

--Función Principal
main :: IO ()
main = do
initGUI

--Creamos la Ventana
window  <- windowNew
--Creamos un contenedor vertical y otro horizontal
vbox    <- vBoxNew True 10
hbox    <- hBoxNew True 10
--Creamos un cuadro de texto
txtEntrada <- entryNew
--Cremos dos etiquetas una para el enunciado y otra para escribir
--la solución
lblMensaje  <- labelNew (Just "Introduzca una cadena:")
lblSolucion <- labelNew (Just "")
--Creamos el botón para ver la solución
btnCalcular <- buttonNewWithLabel "Calcular"
--Creamos los Radio buttons
optNumLetras <-radioButtonNewWithLabel "Número de letras"
optNumPalabras <- radioButtonNewWithLabelFromWidget optNumLetras "Número de palabras"
optLongitud <- radioButtonNewWithLabelFromWidget optNumLetras "Longitud de Cadena"

--Establecemos parámetros de la ventana
set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
             containerBorderWidth := 10, containerChild := vbox]

--Colocamos los componentes
boxPackStart hbox optLongitud PackNatural 0
boxPackStart hbox optNumPalabras PackNatural 0
boxPackStart hbox optNumLetras PackNatural 0
boxPackStart vbox hbox PackNatural 0
boxPackStart vbox lblMensaje PackNatural 0
boxPackStart vbox txtEntrada PackNatural 0
boxPackStart vbox lblSolucion PackNatural 0
boxPackStart vbox btnCalcular PackNatural 0

--Asociamos una acción al evento click del botón
onClicked btnCalcular (calculo txtEntrada optNumLetras optNumPalabras optLongitud lblSolucion)

onDestroy window mainQuit
widgetShowAll window
mainGUI


El resultado que podemos apreciar tras compilar el código es el siguiente:

<a href="/wikihaskell/index.php/Archivo:EjemploGtk2Hs.png" class="image" title="Salida Ejemplo - Uso de Varios Componentes"><img alt="Salida Ejemplo - Uso de Varios Componentes" src="/wikihaskell/images/9/96/EjemploGtk2Hs.png" width="510" height="233" /></a>

Referencias

[<a rel="nofollow" class="external text" href="http://www.haskell.org/gtk2hs/">Biblioteca GTK2Hs</a>]

<a _fcknotitle="true" href="Category:Biblioteca_Gtk2Hs">Biblioteca_Gtk2Hs</a>
Herramientas personales