Les fenêtres avec défilement sont utiles pour créer une zone déroulante avec un autre widget à l'intérieur. Vous pouvez insérer n'importe quel type de widget dans une fenêtre déroulante qui sera accessible quelle que soit sa taille en utilisant les barres de défilement.
La fonction suivante sert à créer une nouvelle fenêtre avec défilement.
scrolledWindowNew :: Maybe Adjustment -> Maybe Adjustment -> IO ScrolledWindow
Le premier argument est le réglage pour la direction horizontale et le deuxième est pour le réglage vertical. Ils sont, la plupart du temps définis à Nothing.
scrolledWindowSetPolicy :: ScrolledWindowClass self => self -> PolicyType -> PolicyType -> IO ()
Cette fonction sert à définir la politique d'affichage des barres de défilement horizontales et verticales. Le constructeur PolicyAlways affiche toujours les barres de défilement, PolicyNever ne les affichent jamais et PolicyAutomatic les affichent seulement si la taille de la page est plus grande que la fenêtre. La valeur par défaut est PolicyAlways.
Pour placer un objet dans la fenêtre avec défilement, on utilise containerAdd si cet objet a une fenêtre qui lui est associée. Si il n'en a pas, un Viewport est nécessaire, mais on peut en ajouter un automatiquement avec:
scrolledWindowAddWithViewport :: (ScrolledWindowClass self, WidgetClass child) => self -> child -> IO ()
Voici un exemple qui empile un tableau de 100 boutons bascules dans une fenêtre avec défilement. Cet exemple est tiré de 'Yet Another Haskell Tutorial' par Hal Daumé III. Ce tutoriel est librement disponible sur le site internet de Haskell. Ce programme laisse l'utilisateur trouver un nombre entre 1 et 100 en lui indiquant si le nombre qu'il indique est trop grand, trop petit ou correct. Le nombre aléatoire est généré avec la fonction randomRIO du module System.Random.
Cet exemple implémente ce programme avec une interface graphique.
Dans la fenêtre principale, on utilise une boite verticale pour empaqueter une étiquette (pour guider l'utilisateur) un séparateur horizontal, une fenêtre avec défilement, un séparateur horizontal et une boite horizontale pour deux boutons. La fenêtre avec défilement est empaquetée avec PackGrow pour qu'elle puisse être redimensionnée avec la fenêtre principale. Les boutons Play et Quit sont empaquetés aux bords opposés de la boite horizontale.
Les 100 boutons sont créés avec :
buttonlist <- sequence (map numButton [1..100])
La fonction numButton est définie par:
numButton :: Int -> IO Button
numButton n = do
button <- buttonNewWithLabel (show n)
return button
Chaque bouton a le numéro approprié comme étiquette.
À l'intérieur de la fenêtre avec défilement, on crée un tableau de 10 par 10 pour les 100 boutons. Pour positionner les boutons, on utilise la fonction cross.
cross :: [Int] -> [Int] -> [(Int,Int)]
cross row col = do
x <- row
y <- col
return (x,y)
La fonction attachButton prend un tableau, un bouton et un couple de coordonnées pour placer le bouton dans le tableau. (Voir Chapitre 3.3 pour plus d'informations sur l'empaquetage des tableaux).
attachButton :: Table -> Button -> (Int,Int) -> IO ()
attachButton ta bu (x,y) = tableAttachDefaults ta bu y (y+1) x (x+1)
Le morceau de code suivant empaquette tous les boutons dans le tableau avec buttonlist décrit précédemment.
let places = cross [0..9] [0..9]
sequence_ (zipWith (attachButton table) buttonlist places)
Chaque fois que l'utilisateur appuie le bouton Play, un nombre aléatoire est généré qui peut ensuite être comparé au choix de l'utilisateur. Mais le gestionnaire de signaux de Gtk2Hs onClicked prend un bouton et une fonction sans paramètre de type IO (). Il faut donc quelque chose qui ressemble à des variables globales qui sont apportées par le module Data.IORef. On peut alors utiliser les morceaux de code suivant dans différentes fonctions pour initialiser, écrire et lire le nombre aléatoire.
snippet 1 -- randstore <- newIORef 50
snippet 2 -- writeIORef rst rand
snippet 3 -- rand <- readIORef rst
Le premier reçoit une variable de type IORef Int et l'initialise à 50. Le second est implémenté dans la fonction 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)
la fonction actionButton implémente la lecture de randstore. Elle compare alors le nombre obtenu de l'étiquette du bouton qui a été cliqué et affiche l'information appropriée sur info.
Enfin, il faut surveiller tous les 100 boutons pour trouver celui qui a été pressé.
sequence_ (map (actionButton info randstore) buttonlist)
Le code ci-dessus est similaire aux autres combinaisons de sequence et map que l'on a déjà utilisé mais dans le cas présent, seul un des 100 signaux sera déclenché lorsque l'utilisateur presse ce bouton en particulier.
Le code complet de l'exemple est:
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)