Las ventanas con desplazamiento se usan para crear un área desplazable con otro widget dentro de él. Puedes insertar cualquier tipo de widget en una ventana desplazable, y será accesible, sin tener en cuenta su tamaño, mediante el uso de las barras de desplazamiento (scroll bars)
La siguiente función se usa para crear una nueva ventana desplazable.
scrolledWindowNew :: Maybe Adjustment -> Maybe Adjustment -> IO ScrolledWindow
El primer argumento es el ajuste de la dirección horizontal, y el segundo el de la dirección vertical.
Casi siempre se establecen a
Nothing
(nada) .
scrolledWindowSetPolicy :: ScrolledWindowClass self => self -> PolicyType -> PolicyType -> IO ()
Este establece la política a usar con respecto a las barras de desplazamiento verticales y
horizontales. El constructor PolicyAlways
muestra siempre la barra de desplazamiento,
PolicyNever
no la muestra nunca y
PolicyAutomatic
la muestra sólo si el tamaño de la página es mayor que la ventana.
Por defecto usa
PolicyAlways
.
A continuación puedes colocar tu objeto en la ventana con desplazamiento usando
containerAdd
si el objeto tiene una ventana asociada con él. Si no lo tiene,
necesitarás un Viewport
, aunque puedes añadir una automáticamente con:
scrolledWindowAddWithViewport :: (ScrolledWindowClass self, WidgetClass child) => self -> child -> IO ()
Si te olvidas del viewport
, GHCi produce un mensaje de
error si usas containerAdd
y no has debido hacerlo.
En el ejemplo empaquetamos una tabla con 100 botones toggle en
una ventana con desplazamiento. Implementa un programa de
'Yet Another Haskell Tutorial' (y otro tutorial de Haskell) de Hal Daumé III.
Este tutorial está disponible en forma gratuita en el web de Haskell.
En la página 43 hay un programita que permite adivinar un numero entre 1 y 100, seleccionado
al azar por la máquina, indicando si nuestra selección es menor, mayor o igual.
El número se genera con la función randomRIO
del módulo System.Random.
Nuestro ejemplo lo implementa con un interfaz gráfico.
En la ventana principal usamos una caja vertical para empaquetar
una etiqueta (para información del usuario), un separador horizontal, una
ventana con desplazamiento, un separador horizontal y una caja
horizontal que contiene dos botones de stock.
La ventana con desplazamiento se empaqueta con PackGrow
,
lo que le permite adaptarse a los cambios de tamaño de la ventana principal.
Los botones nuevo (new) y salir (quit) se empaquetan en los extremos
opuestos de la caja horizontal.
Los 100 botones se crean con:
buttonlist <- sequence (map numButton [1..100])
donde la función
numButton
se define como:
numButton :: Int -> IO Button numButton n = do button <- buttonNewWithLabel (show n) return button
Así, cada botón automáticamente obtiene el número apropiado como etiqueta.
Dentro de la ventana con desplazamiento creamos una tabla
de 10 por 10 para los 100 botones. Para posicionar los botones
usamos la función cross
, que se basa en List monad
.
Esta función, un modo sencillo de obtener un producto cartesiano
de dos o más listas, también está explicado en el tutorial ya citado.
cross :: [Int] -> [Int] -> [(Int,Int)] cross row col = do x <- row y <- col return (x,y)
La función
attachButton
parte de una tabla, un botón y una
tupla de coordenadas para colocar un botón en la tabla. (Repasa el
capítulo 3.3 para más información sobre empaquetado de tablas.)
attachButton :: Table -> Button -> (Int,Int) -> IO () attachButton ta bu (x,y) = tableAttachDefaults ta bu y (y+1) x (x+1)
Ahora, el siguiente segmento de código empaqueta todos los botones en
la tabla con buttonlist
según se ha descrito.
let places = cross [0..9] [0..9] sequence_ (zipWith (attachButton table) buttonlist places)
Cada vez que el usuario pulsa el botón play se genera un
número aleatorio, que habrá que ir comparando con la elección
del usuario. Pero el manejador de señales de Gtk2Hs onClicked
emplea un botón y una función sin parámetros y tiene un valor de tipo
IO ()
. Necesitamos algo así como una variable global,
y esta es aportada por el módulo Data.IORef. Ahora podemos usar los siguientes
snippets, en diferentes funciones, para inicializar, escribir y leer el
número aleatorio.
snippet 1 -- randstore <- newIORef 50 snippet 2 -- writeIORef rst rand snippet 3 -- rand <- readIORef rst
El primero obtiene una variable de tipo IORef Int
y la
inicializa al valor 50. La segunda se implementa con la función aleatoria
randomButton
:
randomButton :: ButtonClass b => Label -> IORef Int -> b -> IO (ConnectId b) randomButton inf rst b = onClicked b $ do rand <- randomRIO (1::Int, 100) writeIORef rst rand set inf [labelLabel := "Ready"] widgetModifyFg inf StateNormal (Color 0 0 65535)
y después usa el siguiente snippet, donde info
es la etiqueta que permite acceder a la información de usuario.
(Mira el capítulo 5.3 para los colores y como cambiarlos.)
De un modo parecido a la escritura del número aleatorio, la
función actionButton
implementa la lectura de
randstore.
Entonces compara el número obtenido
de la etiqueta del botón que ha sido pulsado, y muestra la
información en la etiqueta info
.
Finalmente debemos monitorizar los 100 botones para saber cual ha sido pulsado, si ha habido alguno.
sequence_ (map (actionButton info randstore) buttonlist)
Lo anterior es análogo a las combinaciones de sequence
_ y
map
que hemos usado, pero en este caso exactamente
uno de los 100 manejadores de señal será activado, en el momento en
que el usuario pulse un botón concreto.
A continuación tienes el código completo del ejemplo.
import Graphics.UI.Gtk import Data.IORef import System.Random (randomRIO) main:: IO () main= do initGUI window <- windowNew set window [ windowTitle := "Guess a Number", windowDefaultWidth := 300, windowDefaultHeight := 250] mb <- vBoxNew False 0 containerAdd window mb info <- labelNew (Just "Press \"New\" for a random number") boxPackStart mb info PackNatural 7 sep1 <- hSeparatorNew boxPackStart mb sep1 PackNatural 7 scrwin <- scrolledWindowNew Nothing Nothing boxPackStart mb scrwin PackGrow 0 table <- tableNew 10 10 True scrolledWindowAddWithViewport scrwin table buttonlist <- sequence (map numButton [1..100]) let places = cross [0..9] [0..9] sequence_ (zipWith (attachButton table) buttonlist places) sep2 <- hSeparatorNew boxPackStart mb sep2 PackNatural 7 hb <- hBoxNew False 0 boxPackStart mb hb PackNatural 0 play <- buttonNewFromStock stockNew quit <- buttonNewFromStock stockQuit boxPackStart hb play PackNatural 0 boxPackEnd hb quit PackNatural 0 randstore <- newIORef 50 randomButton info randstore play sequence_ (map (actionButton info randstore) buttonlist) widgetShowAll window onClicked quit (widgetDestroy window) onDestroy window mainQuit mainGUI numButton :: Int -> IO Button numButton n = do button <- buttonNewWithLabel (show n) return button cross :: [Int] -> [Int] -> [(Int,Int)] cross row col = do x <- row y <- col return (x,y) attachButton :: Table -> Button -> (Int,Int) -> IO () attachButton ta bu (x,y) = tableAttachDefaults ta bu y (y+1) x (x+1) actionButton :: ButtonClass b => Label -> IORef Int -> b -> IO (ConnectId b) actionButton inf rst b = onClicked b $ do label <- get b buttonLabel let num = (read label):: Int rand <- readIORef rst case compare num rand of GT -> do set inf [labelLabel := "Too High"] widgetModifyFg inf StateNormal (Color 65535 0 0) LT -> do set inf [labelLabel := "Too Low"] widgetModifyFg inf StateNormal (Color 65535 0 0) EQ -> do set inf [labelLabel := "Correct"] widgetModifyFg inf StateNormal (Color 0 35000 0) randomButton :: ButtonClass b => Label -> IORef Int -> b -> IO (ConnectId b) randomButton inf rst b = onClicked b $ do rand <- randomRIO (1::Int, 100) writeIORef rst rand set inf [labelLabel := "Ready"] widgetModifyFg inf StateNormal (Color 0 0 65535)