GUIs con Haskell en entornos Windows (II)

Creando una Ventana, con algo de funcionalidad
Creamos una ventana, via codigo, con algunos componentes y manejamos algunos eventos
En la anterior entrada sobre el tema (Ver Aqui) vimos la estructura basica sobre como crear una ventana con Haskell y GTK3. En el presente vamos a profundizar acerca del tema y veremos como responder a eventos.
main :: IO ()
main = do
-- inicializamos Gtk
Gtk.init Nothing
-- creamos una ventana y asignamos el proceso de cierre a la destruccion de la ventana
win <- createWindow "Layouts Sample"
on win #destroy Gtk.mainQuit
-- creamos los widgets y los anadimos a la ventana
box <- createWidgets win
#add win box
-- mostramos la ventana
#showAll win
-- Gtk main loop
Gtk.main
Como ya hicimos en el anterior, inicializamos Gtk con init y procedemos a crear una ventana, para lo que nos hemos creado una funcion, createWindow a la que le pasamos el titulo que va tener la ventana en la barra de titulo.
-- creamos la ventana
createWindow :: MonadIO m => Text -> m Gtk.Window
createWindow title = do
win <- new Gtk.Window [ #title := title ]
liftIO $ return win
Como vimos en la anterior entrada la creamos a traves de la funcion new a la que le pasamos el widget que queremos crear, en este caso Window.
La funcion on a traves de la cual se declaran los eventos reside en el modulo Data.GI.Base.Signals y tiene la siguiente firma
on :: forall object info m. (GObject object, MonadIO m, SignalInfo info) => object -> SignalProxy object info -> HaskellCallbackType info -> m SignalHandlerId
Ademas de esta, existe la funcion after que encola el evento para ejecutarse despues el manejador por defecto y que tiene la misma firma.
La documentacion especifica que su uso basico se realiza del modo que ya hemos visto
on #evento $ do ...
Es posible tener una funcion para realizar el manejo del evento
handleEvent :: IO ()
handleEvent = do
return ()
on boton #clicked handleEvent
Para la creacion de widgets hemos creado una funcion a la que le pasamos un parametro del tipo Window con el unico proposito de dar el foco al boton Aceptar
-- creamos componentes
createWidgets :: MonadIO m => Gtk.Window -> m Gtk.Box
createWidgets win = do
-- main box
box <- createBox Enums.OrientationVertical 10
-- box para botones
boxbuttons <- createBox Enums.OrientationHorizontal 10
-- botones
aceptar <- new Gtk.Button [ #name := "btn_aceptar", #label := "Aceptar", #margin := 10 ]
cancelar <- new Gtk.Button [ #name := "btn_cancelar", #label := "Cancelar", #margin := 10 ]
-- box para componentes
boxcompo <- createBox Enums.OrientationVertical 5
label <- new Gtk.Label [ #label := "Sample Text"]
textbox <- new Gtk.Entry [ #marginLeft := 10, #marginRight := 10, #placeholderText := "Teclee algo y pulse aceptar..."]
-- Manejamos el evento clicked del boton aceptar
on aceptar #clicked $ do
-- Leemos el contenido del textbox para mostrarlo en la msgbox
ebuffer <- Gtk.entryGetBuffer textbox
text <- Gtk.entryBufferGetText ebuffer
-- Creamos una caja de mensajes
msg <- new Gtk.MessageDialog [ #text := text, #buttons := Enums.ButtonsTypeOk ]
Dlg.dialogRun msg -- la mostramos ...
Gtk.windowClose msg -- ... y la cerramos al hacer click en Ok
return ()
on cancelar #clicked $ do
-- Eliminamos el contenido del textbox
ebuffer <- Gtk.entryGetBuffer textbox
Gtk.entryBufferSetText ebuffer "" (-1)
return ()
-- anadimos los botones
#add boxbuttons aceptar
#add boxbuttons cancelar
-- anadimos el resto de widgets
#add boxcompo label
#add boxcompo textbox
-- anadimos las cajas a la caja principal
#add box boxcompo
#add box boxbuttons
Gtk.windowSetFocus win $ Just aceptar
liftIO $ return box
Por aqui vemos que pasan muchas cosas. Por un lado llamamos a una funcion para crear las cajas que contendran los componentes dentro de la ventana. Estas cajas son muy similares a los div del HTML y podemos crearlas de varios tipos: Horizontales, Verticales, en Grid. Aqui he optado por crear una funcion que recibe su orientacion y el margen que las va a separar
-- creamos un Box
createBox :: MonadIO m => Enums.Orientation -> Int32 -> m Gtk.Box
createBox orientation spacing = do
box <- if orientation == Enums.OrientationHorizontal then
new Gtk.Box [ #orientation := orientation, #spacing := spacing, #hexpand := True ]
else
new Gtk.Box [ #orientation := orientation, #spacing := spacing, #vexpand := True, #hexpand := True ]
liftIO $ return box
Lo unico de especial que tiene es que si la orientacion es Horizontal queremos que se expandan los componentes a todo el ancho de la caja.
Despues creamos los widgets, por un lado los botones y por otro lado una etiqueta de texto y una caja de texto.
A los botones de Aceptar y Cancelar les hemos dado algo de funcionalidad. Cuando pinchemos en Aceptar nos saldra un dialogo mostrandonos el texto insertado en la caja de texto y un boton para salir.
Cuando pichemos en Cancelar se borrara lo que hayamos insertado en la caja de texto.
-- Manejamos el evento clicked del boton aceptar
on aceptar #clicked $ do
-- Leemos el contenido del textbox para mostrarlo en la msgbox
ebuffer <- Gtk.entryGetBuffer textbox -- Se ha hecho asi por claridad
text <- Gtk.entryBufferGetText ebuffer -- Tambien vale text <- Gtk.entryBufferGetText $ Gtk.entryGetBuffer textbox
-- Creamos una caja de mensajes
msg <- new Gtk.MessageDialog [ #text := text, #buttons := Enums.ButtonsTypeOk ]
Dlg.dialogRun msg -- la mostramos ...
Gtk.windowClose msg -- ... y la cerramos al hacer click en Ok
return ()
Para leer el contenido de la caja de texto necesitamos, primero acceder a su buffer, cosa que hacemos a traves de la funcion entryGetBuffer, y, segundo, acceder al texto del buffer a traves de entryBufferGetText.
Despues creamos el dialogo, le asigamos propiedades y lo mostramos.
El evento para el boton Cancelar es similar
on cancelar #clicked $ do
-- Eliminamos el contenido del textbox
ebuffer <- Gtk.entryGetBuffer textbox
Gtk.entryBufferSetText ebuffer "" (-1)
return ()
Por ultimo anadimos los componentes a sus cajas y las cajas individuales a la caja principal, asignando el foco al boton Aceptar.
Conclusiones y proximos pasos
Para pequenos dialogos la opcion de crearlo todo a traves de codigo puede resultar la opcion mas comoda.
En la proxima entrega veremos como cargar componentes a traves disenados por GtkBuilder.
El ejemplo lo podeis encontrar en https://github.com/juanan-martin-lpz/gtklayoutsblog.
Un saludo y hasta la proxima.