Les interfaces graphiques avec OCaml
Formation
En Semi-présenciel Paris
Avez-vous besoin d'un coach de formation?
Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.
Description
-
Typologie
Formation
-
Méthodologie
En semi-présentiel
-
Lieu
Paris
Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.
Les sites et dates disponibles
Lieu
Date de début
Date de début
Les Avis
Le programme
Bonjour à tous !
Quand on veut faire un programme convivial, on s'efforce en général de concevoir une interface graphique de qualité. Parfois même, dans une équipe, certaines personnes sont chargées tout spécialement de cet aspect du programme. Or, la bibliothèque standard d'OCaml offre un choix limité... puisqu'elle ne propose que Tcl/Tk (via le module LablTk). Peut-être savez-vous déjà que le résultat n'est pas très agréable à regarder (voyez ocamlbrowser par exemple ou, pour citer quelque chose de très différent, aMSN). Fort heureusement, une interface GTK (un binding en anglais) pour OCaml a été réalisée, pour notre plus grand bonheur : c'est LablGTK. Et c'est de cette bibliothèque que nous allons parler un peu ici.
Attention : ce tutoriel n'est ni une introduction à OCaml ni une introduction à GTK. Il est donc conseillé de le lire avec quelques notions en poche. Songez plutôt : ce n'est qu'un mini-tuto... il ne peut pas y avoir vraiment beaucoup de choses dedans. ;)
Quelques rappels sur OCamlLa version 3 d'OCaml a introduit plusieurs nouveaux concepts « avancés » dont LablGTK se sert beaucoup. On dit généralement qu'ils sont « avancés » dans la mesure où il n'est absolument pas indispensable de les aborder dans le cadre d'une introduction au langage. Tout ceci m'a donc incité à commencer ce tutoriel par une brève présentation de ces quelques points. Ceux qui savent déjà tout des arguments nommés, des arguments optionnels et des variants polymorphes peuvent passer d'emblée à la deuxième partie du tutoriel.
Les arguments nommésSi vous utilisez déjà OCaml, vous avez probablement déjà utilisé les modules String et Unix. Si vous regardez attentivement la liste des modules de la bibliothèque standard, vous verrez qu'il existe également des modules StringLabels et UnixLabels.
C'est vrai ça ! Mais, à quoi peuvent-ils bien servir vu qu'ils contiennent exactement les mêmes fonctions que String et Unix ?
En fait, la grande différence entre ces modules réside dans la présence d'étiquettes (ou labels en anglais), que j'appellerai « arguments nommés » dans la suite de ce tutoriel (je crois que c'est la bonne traduction, mais comme je les ai toujours connus comme étant des labels, je suis un peu démuni ^^ ).
Bon, d'accord, il y a de petites différences... mais ça ne change pas le code. Alors à quoi cela peut-il bien servir ?
Eh bien, les arguments nommés permettent de préciser la nature de certains arguments lorsque leur type n'est pas très explicite, ou quand il y a vraiment beaucoup d'arguments. Je vais vous donner un exemple. Considérez cette fonction sans grand intérêt :
let boucle min max step f = let rec loop = function | i when i > max -> () | i -> f i; loop (i + step) in loop minSi vous la tapez dans l'interpréteur, vous verrez que sa signature n'utilise que des types simples... mais qu'on ne sait pas trop, en la voyant, à quoi correspondent tous ces entiers :
val boucle : int -> int -> int -> (int -> 'a) -> unitNous rencontrons donc un premier problème : celui de la lisibilité. On aimerait bien que la seule signature d'une fonction suffise à nous en faire comprendre le fonctionnement (c'est ce qui rend précieux des outils tels que ocamlbrowser et ocamldoc). Or, dans le cas présent, les types ne nous sont d'aucun secours. Nous allons donc écrire quelque chose comme ça :
let boucle ~min ~max ~step f = let rec loop = function | i when i > max -> () | i -> f i; loop (i + step) in loop minL'ajout d'un tilde (~) devant min, max et step permet d'associer un nom à chacun des arguments dans la signature de la fonction :
val boucle : min:int -> max:int -> step:int -> (int -> 'a) -> unitMême si cela vous plaît et qu'il vous prend l'envie d'en utiliser partout dans vos développements futurs, notez quand même que cela n'est pas sans conséquence sur le typage (notamment pour les applications partielles, mais je n'en dis pas plus).
L'utilisation des arguments nommés permet aussi d'écrire les arguments dans n'importe quel ordre (ou presque). Par exemple, ceci fonctionne très bien :
# boucle ~step:2 ~max:5 ~min:1 print_int ;; 135- : unit = () #Notez que si vous indiquez tous les arguments d'une fonction, autrement dit si vous ne faites pas d'application partielle, vous pouvez vous abstenir de préciser les étiquettes des arguments.
Une petite information sur la syntaxe, pour clore cette partie : notez que lorsqu'une variable porte le même nom qu'un argument nommé, la notation habituelle ~nom:variable peut être abrégée en ~nom.
let min = 2 and max = 8 in boucle ~min ~max ~step:2 print_int Les arguments optionnelsAvec une valeur par défautLes arguments optionnels sont aussi des arguments nommés, mais ils introduisent une propriété supplémentaire puisqu'ils permettent de rendre un argument facultatif (avec, éventuellement, une valeur par défaut). Voici d'abord un exemple avec des valeurs par défaut :
let boucle ?(min = 0) ?(max = 100) ?(step = 1) f = let rec loop = function | i when i > max -> () | i -> f i; loop (i + step) in loop minLa signature de la fonction est aussi lisible qu'avec les arguments nommés, mais, chose nouvelle, il n'est plus nécessaire d'indiquer tous les paramètres (min, max et step). Notez que les tildes (~) ont été remplacés par des points d'interrogation (?).
val boucle : ?min:int -> ?max:int -> ?step:int -> (int -> 'a) -> unitC'est simple non ? Oui ? Eh bien non ! Il y a quelques subtilités à comprendre. Je vais vous les présenter, et il faut vraiment bien les saisir pour utiliser ensuite LablGTK en toute sérénité.
Sans valeur par défautQue se passe-t-il maintenant si nous ne mettons pas de valeur par défaut ? Eh bien, c'est une bonne question... qui me permet de vous présenter une première subtilité. S'il n'y a pas de valeur par défaut, l'argument est de type 'a option dans le corps de la fonction. C'est logique, mais il faut le savoir. Un exemple pour illustrer ce comportement :
let weird_is_lower ?x y = match x with | None -> true | Some x -> y < xJ'ai qualifié cette fonction de curieuse (weird) parce qu'elle n'est pas vraiment très utile. Son seul mérite est de montrer que le paramètre x est bien de type 'a option.
Utilisation des arguments optionnelsPuisqu'un exemple vaut mieux qu'un long discours, je vous propose quelques lignes de code à essayer dans l'interpréteur... pour découvrir la deuxième subtilité.
# let incr ?(step = 1) x = x + step;; val incr : ?step:int -> int -> int = <fun> # incr 1 ;; - : int = 2 # incr ~step:2 1 ;; - : int = 3 # incr ?step:(Some 3) 1 ;; - : int = 4 #C'est bien si vous avez trouvé ! Un argument optionnel utilisé avec un tilde (~) sert à indiquer une valeur de type 'a. Un argument optionnel utilisé avec un point d'interrogation (?) sert à indiquer une valeur de type 'a option.
Variants polymorphesVous savez sans doute que le type somme d'OCaml permet de créer de nouveaux types dont la manipulation est grandement facilitée par le filtrage. Par exemple, on peut construire un module Carte dans lequel est défini le type suivant :
type t = As | Roi | Dame | Valet | Valeur of intLe principal reproche que l'on peut formuler à l'encontre de ce type réside dans le manque de flexibilité qui apparaît lorsqu'on veut s'en servir dans un autre module. On doit en effet utiliser la notation longue Carte.As ou Carte.Dame. Il y a derrière tout ça la notion d'espace de nom (namespace en anglais), mais il est inutile d'en dire plus ici.
Je ne suis ni « pour » ni « contre » cette notation longue, car elle présente des avantages indéniables. En revanche, dans le cas de LablGTK, où certains types sont conçus pour être accessibles dans tous les autres modules (par exemple GtkStock.id qui définit les icônes prédéfinies de GTK), il vaut mieux remplacer le type somme habituel par des variants polymorphes. La présence de nombreux modules n'est alors plus un obstacle à l'utilisation d'un type donné.
C'est pourquoi LablGTK utilise exclusivement les variants polymorphes.
De quoi s'agit-il ?
Les variants polymorphes sont conçus pour être accessibles tels quels en dehors du module où ils sont définis. Ils ont bien d'autres caractéristiques (détaillées dans le manuel de référence), mais nous ne nous y attarderons pas. Voyons plutôt à quoi ressemble le type Carte.t lorsqu'il est réécrit avec des variants polymorphes :
type t = [ `As | `Roi | `Dame | `Valet | `Valeur of int ]Le caractère qui introduit un variant polymorphe est un accent grave (AltGr + 7).
Comme je l'ai dit plus haut, la bibliothèque LablGTK utilise beaucoup les variants polymorphes pour définir des groupes de valeurs possibles (par exemple pour une fenêtre : `CENTER centre la fenêtre sur l'écran, `CENTER_ON_PARENT centre la fenêtre par rapport à son parent, et ainsi de suite). Voici d'autres exemples :
le type GtkStock.id contient les icônes prédéfinies de GTK ;
le type Gtk.Tags contient de très nombreux types à base de variants polymorphes... qui ont été ainsi regroupés parce qu'on peut s'en servir sans se soucier de leur module d'origine.
Si vous voulez en savoir plus sur ce sujet, et que vous lisez l'anglais, ne manquez pas le chapitre Labels and variants du manuel de référence, que vous trouverez ici.
Contenu de la bibliothèquePour bien comprendre comment est organisée la bibliothèque LablGTK, nous allons la présenter en restreignant peu à peu notre champ d'observation. Nous partirons donc d'une vue d'ensemble pour nous pencher ensuite sur l'organisation d'une petite partie de la bibliothèque.
Organisation en modulesModules principauxLablGTK est une grosse bibliothèque. De nombreux modules ont été créés pour en faciliter l'utilisation. Ceux qui nous intéressent le plus pour le moment sont tous préfixés par la lettre G seule. Voici ce que contiennent ces modules :
Module
Contenu
Noms GTK
GWindow
Fenêtres et boîtes de dialogue
GtkWindow, GtkDialog, GtkMessageDialog, GtkColorSelectionDialog...
GButton
Boutons et barres d'outils
GtkButton, GtkToggleButton, GtkRadioButton, GtkColorButton...
GMenu
Menus et barre de menus
GtkMenuShell, GtkMenu, GtkMenuItem...
GTree
Vues arborescentes
GtkTreeView, GtkTreeStore, GtkListStore...
GMisc
Divers widgets (GtkLabel, GtkImage...)
GtkLabel, GtkImage, GtkStatusbar...
GRange
Barres de progression, barres de défilement
GtkScrollbar, GtkProgressBar, GtkRuler...
GBin
Conteneurs à un enfant unique
GtkScrolledWindow, GtkEventBox, GtkHandleBox...
GText
Zone de texte
GtkTextView, GtkTextBuffer, GtkTextIter...
GSourceView
Code source
GtkSourceView, GtkSourceBuffer, GtkSourceLanguage...
GPack
Boîtes et conteneurs
GtkHBox, GtkVBox, GtkTable, GtkButtonBox...
GEdit
Divers widgets éditables
GtkSpinButton, GtkCombo, GtkComboBox...
GData
Gestion des données
GtkClipboard, GtkTooltips...
GFile
Sélecteur de fichiers
GtkFileChooser, GtkFileFilter...
GAction
Nouveaux outils de création de...
Avez-vous besoin d'un coach de formation?
Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.
Les interfaces graphiques avec OCaml