Biblioteca WxHaskell

De Wikihaskell
Saltar a: navegación, buscar
WxHaskell
Interfaz gráfica de usuario con WxHaskell
Lenguaje Haskell
Biblioteca WxHaskell
Autores Miguel López Morales
Juan Carlos Ríos Legupín
Victor Manuel Macías Ariza

WxHaskell es un enlace Haskell a la biblioteca GUI WxWidget. La biblioteca Graphics.UI.WX es utilizada para escribir interfaces gráficas de usuario y está construida sobre Graphics.UI.WXCore, núcleo de la interfaz de WxWidget.

WxWidget es una biblioteca escrita en C++ que es portable a través de las principales plataformas GUI, incluyendo GTK, Windows, X11 y MacOS X. Además, es una biblioteca madura (está en desarrollo desde 1992) que soporta un amplio rango de widgets con el nativo 'look-and-feel'. Tiene una comunidad muy activa (clasificado entre los 25 proyectos más activos en sourceforge).

Contenido

Instalación

Para hacer uso de la biblioteca wxHaskell necesitamos tener instalado el compilador GHC y el paquete Cabal

Instalando GHC

Para instalar GHC podemos hacerlo mediante la ejecución del siguiente comando en un terminal:

   sudo aptitude install ghc

Instalando Cabal

Para instalar Cabal podemos seguir las instrucciones descritas aquí.

Instalando wxHaskell

Una vez instalado el compilador GHC y el paquete Cabal instalaremos la biblioteca mediante los siguientes comandos:

   sudo cabal install wxcore --global
   sudo cabal install wx

Compilación y ejecución

Para compilar nuestros programas es tan fácil como ejecutar en la línea de comandos la siguiente instrucción:

   ghc --make Nombre_Archivo.hs -o Nombre_Ejecutable

Una vez compilado nuestro programa lo podemos ejecutar mediante la siguiente orden:

   ./Nombre_Ejecutable

Hola Mundo

Aquí mostramos el programa básico en Haskell "Hola Mundo".

module Main where

main :: IO ()
main = putStr "¡Hola Mundo!"

El programa compilará bien, pero no es atractivo. Para aplicar una interfaz gráfica, se debe importar Graphics.UI.WX, que es la biblioteca wxHaskell. Graphics.UI.WXCore tiene algunas extensiones más que no vamos a necesitar ahora. Para iniciar una nueva interfaz gráfica de usuario, se hace uso de la orden start gui. En este caso, gui es el nombre de una función que usaremos para construir la interfaz. Aquí se muestra:

module Main where

import Graphics.UI.WX

main :: IO ()
main = start gui

gui :: IO ()
gui = do

Para establecer un marco, se utiliza frame. El tipo de frame es: [Prop (Frame ())] -> IO (Frame ()). Recibe una lista de "propiedades de frame" y devuelve el frame correspondiente. Una propiedad es una combinación de un atributo y un valor. Lo que realizaremos ahora es el título de la ventana. El título se encuentra en el atributo text y su tipo es (Textual w) => w Attr String. Es un atributo de tipo String. Su código es:

gui :: IO ()
gui = do
  frame [text := "¡Hola Mundo!"]

El operador (:=) consta de un atributo y un valor. Observar que frame devuelve un IO (Frame ()). Se puede cambiar el tipo de gui para IO (Frame ()), pero es más conveniente añadirle return(). Ahora tenemos nuestra propia interfaz gráfica de usuario (GUI), que consiste en una ventana con el título "¡Hola Mundo!". El código es el siguiente:

Hola.png
module Main where

import Graphics.UI.WX

main :: IO ()
main = start gui

gui :: IO ()
gui = do
  frame [text := "¡Hola Mundo!"]
  return ()

Controles

Etiqueta de Texto

Hasta ahora todo es muy sencillo, pero vamos a ir introduciendo novedades. Una de ellas se trata de etiquetas de texto. Para ello emplearemos staticText que se encuentra en la biblioteca Graphics.UI.WX.Controls. La función staticText recibe una ventana como argumento, y una lista de propiedades. La ventana la tenemos en Graphics.UI.WX.Frame. Se puede apreciar que un marco es simplemente un sinónimo de un tipo especial de ventana. El código modificado es el siguiente.

HolaStaticText.jpg
gui :: IO ()
gui = do
  f <- frame [text := "¡Hola Mundo!"]
  staticText f [text := "Hola StaticText"]
  return ()

El texto es un atributo de un objeto StaticText.

Botón

Avanzando un poco más añadiremos un botón. No vamos a realizar eventos con él, ya que se tratará más adelante, pero algo hará cuando se realice un click sobre él.

Un botón es un objeto de control, al igual que staticText. Se encuentra en Graphics.UI.WX.Controls.

Necesitamos otra vez una ventana y una lista de propiedades. Vamos a utilizar frame de nuevo. text es también un atributo de botón:

Holabutton.jpg
gui :: IO ()
gui = do
  f <- frame [text := "¡Hola Mundo!"]
  staticText f [text := "Hola StaticText"]
  button f [text := "Hola Button"]
  return ()

Cárgalo con el compilador GHC. Puede comprobarse que no ha salido bien, ya que se muestra una etiqueta por encima del botón. Este problema de diseño se arreglará más tarde.

Layout

La razón por la cual la etiqueta y el botón se solapan, se debe a que no tenemos establecido un layout para nuestro frame. Éstos son creados usando funciones que se encuentran en Graphics.UI.WXCore.Layout. No se tiene que importar Graphics.UI.WXCore para usarlos.

La documentación nos dice que podemos devolver un miembro de la clase widget en un layout usando la función widget. Las ventanas también son miembros de la clase widget. Aunque puede parecer que de momento solamente tenemos una ventana (frame), en realidad tenemos más, ya que los controles definidos en Graphics.UI.WX.Controls son un tipo especial de éstas. A continuación, modificaremos un poco el código para, posteriormente, usar los layouts:

gui :: IO ()
gui = do
  f <- frame [text := "¡Hola Mundo!"]
  st <- staticText f [text := "¡Hola Texto Estático!"]
  b <- button f [text := "¡Hola Botón!"]
  return ()

Ahora podemos usar widget st y widget b para crear un layout del texto y el botón. El layout es un atributo del frame, como se muestra aquí:

gui :: IO ()
gui = do
  f <- frame [text := "¡Hola Mundo!"]
  st <- staticText f [text := "¡Hola Texto Estático!"]
  b <- button f [text := "¡Hola Botón!"]
  set f [layout := widget st]
  return ()

La función set se explicará en el capítulo sobre atributos. Si probamos el código anterior, vemos como aparece el texto, pero no ocurre lo mismo con el botón. Necesitamos una manera de combinar los dos controles. Para este objetivo, se usan los combinadores de layout. Ellos toman un entero y un lista de layouts. Podemos hacer una lista con los controles de texto y del botón. Con el entero controlamos el espacio entre los elementos de la lista:

HolaTextoEstatico.jpg
gui :: IO ()
gui = do
  f <- frame [text := "¡Hola Mundo!"]
  st <- staticText f [text := "¡Hola Texto Estático!"]
  b <- button f [text := "¡Hola Botón!"]
  set f [layout := 
          row 0 [widget st, widget b]
        ]
  return ()

Atributos

La función set, el atributo text, todo ello está explicado y detallado en el sistema de atributos de wxHaskell.

Configuración y modificación de los atributos

En un programa wxHaskell, puede establecer las propiedades de los widgets de dos maneras:

  1. Durante la creación de: f <- frame [ text := "¡Hola Mundo!" ]
  2. Utilizando la función set: set f [ layout := widget st ]

La función set tiene dos argumentos: uno de cualquier tipo w, y el otro es una lista de propiedades de w. En wxHaskell, estos serán los widgets y las propiedades de estos widgets. Algunas propiedades sólo se pueden establecer durante su creación, como la alineación de un textEntry, pero se pueden configurar la mayoría de los demás en cualquier función IO en el programa, siempre y cuando tengas una referencia de él.

Además de establecer las propiedades, también se pueden obtener. Esto se hace mediante la función get. Aquí se muestra un ejemplo simple:

gui :: IO ()
gui = do
  f <- frame [ text := "¡Hola Mundo!" ]
  st <- staticText f []
  ftext <- get f text
  set st [ text := ftext]
  set f [ text := ftext ++ " y ¡Hola de nuevo!" ]


Se observa la declaración de tipo que se coge. w -> Attr w a -> IO a. text es un atributo de String, así que tenemos una cadena de E/S (IO String) que se puede unir a ftext. La última línea edita el texto del marco (frame). Podemos sobrescribir las propiedades utilizando (: =) en cualquier momento con set. Esto nos lleva a escribir una función modificadora:

modify :: w -> Attr w a -> (a -> a) -> IO ()
modify w attr f = do
  val <- get w attr
  set w [ attr := f val]

En primer lugar se obtiene el valor y luego se establece de nuevo después de aplicar la función.

El constructor (:~) se puede utilizar en set, porque recibe un atributo y una función. El resultado es una propiedad, en la que el valor original es modificado por la función. De tal manera que:

gui :: IO ()
gui = do
  f <- frame [ text := "¡Hola Mundo!" ]
  st <- staticText f []
  ftext <- get f text
  set st [ text := ftext]
  set f [ text :~ ++ " y ¡Hola de nuevo!" ]

Aquí se podría utilizar funciones anónimas con notación lambda.


Hay dos operadores más que podemos utilizar para establecer o modificar las propiedades: (::=) y (::~). Estos hacen lo mismo que (: =) y (:~), exceptuando una función que espera un tipo w -> orig, donde w es de tipo widget, y orig es el tipo de "valor" original (a en el caso de (: =), y a -> a en caso de (:~)). Como sólo se han encontrado los atributos de los tipos no-IO, y el widget necesario en la función es generalmente útil solo en los IO-blocks.

Dónde encontrar los atributos

Para visualizar los atributos que tiene Button, hay que ir a Graphics.UI.WX.Controls, y hacer click en el enlace que dice "Button" (aquí). Verás que Button es un sinónimo de tipo de una clase especialControl, y una lista de funciones que pueden ser usadas para crear un botón. Después de cada función hay una lista de "instancias". Para la función normal de Button, la lista es Commanding -- Textual, Literate, Dimensions, Colored, Visible, Child, Able, Tipped, Identity, Styled, Reactive, Paint. Esta es la lista de clases de las cuales Button es una instancia. Esto significa que hay algunas funciones de clases específicas disponibles para Button. Textual, por ejemplo, añade las funciones text y appendText. Si un widget es una instancia de la clase Textual, significa que tiene un atributo de text.

Hay que tener en cuenta que mientras StaticText no tiene una lista de instancias, sigue siendo de Control, el cual es sinónimo de algún tipo de Windows, y si miramos la clase Textual, se dice que Windows es una instancia de éste. Se trata de un error en la documentación.

Los atributos de un marco(frame) se pueden encontrar en Graphics.UI.WX.Frame. Aquí existe otro error en la documentación: indica HasImage, en vez de Pictured, como una instancia de Frame. Esto era cierto en una versión anterior de wxHaskell. Aparte de esto, tenemos Form, Closeable, Framed -- Textual, Literate, Dimensions, Colored, Visible, Child, Able, Tipped, Identity, Styled, Reactive, Paint. Ya hemos visto Textual y Form. Todas las instancias de Form tiene un atributo de layout.

Dimensions añade (entre otros) el atributo clientSize. Este es un atributo de tipo Size, el cuál puede construirse con sz. El atributo de layout también puede cambiar el tamaño. Si quieres utilizar clientSize hay que fijarlo después de layout.

Colored agrega el color y los atributos bgcolor.

Able agrega el atributo booleano enabled. Se puede utilizar para activar o desactivar los elementos de cierta forma, que a menudo se muestra como una opción en gris.

Estos son solo algunos atributos, hay un muchos más. En la documentación se detallan todos.

Eventos

Hay algunas clases que merecen especial atención. Ellas son Reactive y Commanding. Como se puede apreciar en la documentación de estas clases, poseen eventos, lo que hace que sean muy interesantes. La clase Commanding añade el evento command. Usaremos un botón para mostrar el manejador de evento.

Aquí tenemos una simple GUI con un botón y un texto estático:

Evento.jpg
gui :: IO ()
gui = do
  f <- frame [ text := "Manejador de evento" ]
  st <- staticText f [ text := "No has pulsado el botón todavía." ]
  b <- button f [ text := "¡Púlsame!" ]
  set f [ layout := column 25 [ widget st, widget b ] ]

Ahora cambiaremos el texto estático cuando se presione el botón. Necesitamos añadir la función on:

b <- button f [ text := "¡Púlsame!"
                , on command := 
                ]

El tipo de on: Event w a -> Attr w a. command es de tipo Event w (IO ()), así que necesitamos una función IO. Esta función es llamada manejador de evento. Aquí la tenemos:

gui :: IO ()
gui = do
  f <- frame [ text := "Manejador de evento" ]
  st <- staticText f [ text := "No has pulsado el botón todavía." ]
  b <- button f [ text := "¡Púlsame!"
                , on command := set st [ text := "¡Has pulsado el botón!" ]
                ]
  set f [ layout := column 25 [ widget st, widget b ] ]

Referencias

Página Oficial Wxhaskell

Documentación Wxhaskell

Herramientas personales