Programmation d'IHM - Des boutons simples
From OFSET Wiki
Un bouton simple est un objet graphique qui réagit (exécute une action) au clique de souris. Ils sont souvent utilisés comme raccourcis pour exécuter les actions accessibles via les menus. Ils peuvent aussi provoquer le surgissement de menus.
Dans Squeak, n'importe quel morph peut être utilisé comme bouton. Cependant, on dispose de la classe PluggableButtonMorph qui simplifie l'utilisation des boutons. Ce chapitre montre comment utiliser cette classe.
Contents |
[edit] Un bouton en deux lignes de code
L'action est stockée dans un bloc, le message #value est envoyé au bloc quand on clique sur le bouton :
| b |
b := PluggableButtonMorph
on: [Transcript show: 'bouton clique!'; cr]
getState: nil
action: #value.
b label: 'C''est ici qu''on clique!'.
b openInWorld
[edit] La classe MUIDPluggableButtonMorphTest
Voici tout d'abord le code de la classe MUIDPluggableButtonMorphTest : MUIDPluggableButtonMorphTest.st. Importez cette classe et exécutez le code suivant :
MUIDPluggableButtonMorphTest new
Le morph montré dans la figure ci-contre s'ouvre. Nous allons voir comment est programmé ce test. Le point d'entrée est la méthode MUIDPluggableButtonMorphTest>>#initialize qui crée un panneau horizontal, y ajoute simplement les boutons et l'ouvre :
MUIDPluggableButtonMorphTest>>initialize
super initialize.
frame := AlignmentMorph newRow. "creation du panneau horizontal"
frame extent: 350 @ 80. "on fixe sa taille pour bien répartir les boutons"
frame color: Color white. "c'est vert pâle par défaut..."
#(#button0 #button1 #button2 #button3 #button4 #button5)
do: [:btn |
"on ajoute un séparateur transparent entre les boutons"
frame addMorphBack: AlignmentMorph newVariableTransparentSpacer.
"on ajoute un bouton"
frame addMorphBack: (self perform: btn).
"encore un séparateur "
frame addMorphBack: AlignmentMorph newVariableTransparentSpacer].
"on ouvre le panneau"
frame openInWorld
Bon, ce qui nous intéresse maintenant, c'est la construction proprement dite des boutons. La classe PluggableButtonMorph comprend plusieurs méthodes de classe pour construire des boutons (et une methode exemple aussi ...).
Ici, on utilise PluggableButtonMorph class>>#on:getState:action:label:. Les arguments 2, 3 et 4 sont des symboles sélecteurs ou nil. Si non nil, chacun de ces sélecteurs correspond à un message que comprend l'objet passé en premier argument. La classe de cet objet (ou une de ces superclasses) met donc en oeuvre les méthodes correspondantes :
- le premier sélecteur (pour getState:) correspond à une méthode qui retourne un booléen relatif à l'état de l'objet; nous verrons que l'apparence du bouton (au moins sa couleur) peut dépendre de cet état;
- le second sélecteur (pour action:) correspond à la méthode exécutée lorsqu'on clique sur le bouton;
- le troisième sélecteur (pour label:) correspond à la méthode qui retourne le contenu du bouton (un label tout simplement ou bien un morph pour avoir une icône par exemple).
Dans notre test, cette méthode est utilisée dans MUIDPluggableButtonMorphTest>>#button0 ... MUIDPluggableButtonMorphTest>>#button4.
[edit] Créer un bouton
La classe utilisée est PluggableButtonMorph. Ce bouton vert, est obtenu de la façon suivante :
MUIDPluggableButtonMorphTest>>button0 | btn | btn := PluggableButtonMorph on: self getState: nil action: #buttonAction0 label: #buttonLabel0. ^ btn
L'état du bouton n'est pas géré (nil comme second argument). Le receveur est self, le MUIDPluggableButtonMorphTest receveur comprend donc les messages #buttonAction0 et #buttonLabel0 pour, respectivement, l'action à exécuter et pour le label du bouton.
MUIDPluggableButtonMorphTest>>buttonLabel0 ^ 'OK' MUIDPluggableButtonMorphTest>>buttonAction0 Transcript show: 'Action\' withCRs
[edit] Le bouton classique
L'apparence est ici conventionnelle. Sur le fond, rien ne change. On ajuste seulement la couleur et la nature du bord.
La particularité un peu surprenante c'est la valeur #raised passée pour la couleur du bord. Ce n'est pas une couleur mais un type de bordure. Le truc c'est que l'argument de borderColor: peut être soit une couleur, soit un symbole correspondant à un type de bordure. En fait, l'indication d'un style pour une bordure fait que sa couleur est calculée pour donner l'illusion du volume. La conséquence est qu'on ne peut pas indiquer un style de bordure et une couleur. C'est soit l'un, soit l'autre.
Pour connaître tous les styles de bordure disponibles, on regarde la classe BorderStyle, plus particulièrement ses constructeurs (coté méthodes de classe donc), on a les méthodes #raised, #inset, #complexAltInset....
Avec une largeur de bordure de deux points, on obtient un look classique :
MUIDPluggableButtonMorphTest>>button1 | btn | btn := PluggableButtonMorph on: self getState: nil action: #buttonAction1 label: #buttonLabel1. btn borderColor: #raised. btn borderWidth: 2. btn offColor: Color veryLightGray. ^ btn MUIDPluggableButtonMorphTest>>buttonAction1 Transcript show: 'Action\' withCRs MUIDPluggableButtonMorphTest>>buttonLabel1 ^ 'Cliquer ici'
[edit] Les boutons améliorés
Un PluggableButtonMorph peut être construit pour présenter un morph à la place du label simple "chaîne de caractères". Ainsi, on peut avoir un bouton avec une chaîne en gras ou en italique en utilisant un StringMorph. En toute généralité, on peut placer n'importe quel morph dans un bouton. On dispose donc de beaucoup de souplesse pour affiner l'apparence.
[edit] Utiliser un StringMorph
Pour un label en gras, en italique ou en souligné ou encore pour utiliser une couleur de texte la méthode responsable du label peut retourner un StringMorph. Voici par exemple la méthode utilisée pour le label du bouton "STOP" rouge gras ci-dessus :
redBoldStopButtonLabel | sm | sm := StringMorph contents: 'STOP' font: Preferences windowTitleFont emphasis: TextEmphasis bold emphasisCode. sm color: Color red. ^ sm
[edit] Gérer l'état
[edit] Gestion minimale
| off | on |
|---|---|
| |
L'apparence du bouton peut refléter d'un état booléen. Pour cela, le constructeur doit indiquer un sélecteur (deuxième argument) pour accéder à la valeur, true ou false, de l'état dans le modèle (l'objet passé en premier argument). Voici l'exemple du bouton2 qui gère simplement l'état : si false alors le fond est gris, sinon, le fond est blanc. On peut indiquer ces deux couleurs en initialisant la couleur on et la couleur off du bouton en lui envoyant le message #onColor:offColor:. Le premier argument est la couleur de fond utilisée lorsque l'état du bouton est true, Le second argument est la couleur de fond utilisée lorsque l'état du bouton est false.
L'état est ici stocké dans la variable d'instance state de MUIDPluggableButtonMorphTest. Voici l'accesseur pour cet état :
MUIDPluggableButtonMorphTest>>state ^ state ifNil: [state := true]
Voici la construction du bouton2 avec l'indication du sélecteur pour l'état et l'initialisation des couleurs on et off :
MUIDPluggableButtonMorphTest>>button2 | btn | btn := PluggableButtonMorph on: self getState: #state action: #buttonAction2 label: #buttonLabel2. btn onColor: Color veryLightGray offColor: Color white. btn borderColor: #complexAltRaised. btn borderWidth: 2. ^ btn
Il ne reste plus qu'à inverser cet état lorsqu'on clique sur le bouton. Pour indiquer à la vue dépendante que l'état à changé, on utilise self changed: #state (regardez dans la méthode PluggableButtonMorph>>#update: pour comprendre).
MUIDPluggableButtonMorphTest>>buttonAction2 state := state not. self changed: #state MUIDPluggableButtonMorphTest>>buttonLabel2 ^ self redBoldStopButtonLabel
[edit] Gérer le label en fonction de l'état
| off | on |
|---|---|
| |
Le label peut lui aussi dépendre de l'état. Pour cela, dans la méthode qui retourne le label, il suffit de tester l'état et de retourner le label correspondant. C'est ce qui est fait pour le bouton3 :
MUIDPluggableButtonMorphTest>>buttonLabel3 self state ifTrue: [^ self redBoldStopButtonLabel] ifFalse: [^ self greenItalicStartButtonLabel]
La méthode qui met en oeuvre l'action du bouton inverse l'état et indique à la vue que le label a changé :
MUIDPluggableButtonMorphTest>>buttonAction3 state := state not. self changed: #buttonLabel3
[edit] Des boutons icône
| off | on |
|---|---|
| | |
| |
On a vu qu'un bouton peut présenter un label morph. Ce morph peut tout aussi bien être une image (un SketchMorph) :
- le bouton4 présente une image représentant classiquement un enregistrement de fichier; en état off, l'image est claire, en état on, un fantôme de l'image est présenté;
- le bouton5 présente une image représentant habituellement un répertoire ouvert ou fermé suivant l'état; on exploite donc deux images pour le bouton.
Voici le code des méthodes qui retournent l'image pour, respectivement, le bouton4 et le bouton5 :
MUIDPluggableButtonMorphTest>>buttonLabel4 | offForm | self state ifTrue: [^ SketchMorph withForm: self fileSaveForm] ifFalse: [offForm := self fileSaveForm. offForm replaceColor: Color transparent withColor: Color veryLightGray. offForm colorsUsed do: [:c | offForm replaceColor: c withColor: (c alphaMixed: 0.3 with: Color veryLightGray)]. ^ SketchMorph withForm: offForm] MUIDPluggableButtonMorphTest>>buttonLabel5 self state ifTrue: [^ SketchMorph withForm: self folderRedOpenForm] ifFalse: [^ SketchMorph withForm: self folderRedForm]
Il ne reste plus qu'à voir comment programmer les méthodes MUIDPluggableButtonMorphTest>>#fileSaveForm, MUIDPluggableButtonMorphTest>>#folderRedOpenForm et MUIDPluggableButtonMorphTest>>#folderRedForm qui retournent un Form utilisé pour construire un SketchMorph. Et bien c'est dans la FAQ du développeur.
[edit] Faire surgir un menu
On peut associer un menu à un bouton de deux façons :
- soit comme un menu en plus de l'action; l'action branchée sur le bouton représente alors l'action par défaut et les autres sont accessibles via le menu;
- soit l'action elle même consiste en la construction et l'ouverture d'un menu; c'est le cas des boutons inclus dans les barres de menu par exemple.
[edit] Un menu en plus de l'action
Le bouton du milieu (ou de droite suivant le système d'exploitation) fait surgir le menu associé au bouton lors de sa construction. Voici le constructeur du bouton3 modifié pour disposer d'un tel menu. Il suffit d'utiliser la méthode de classe PluggableButtonMorph class>>#on:#getState:#action:#label:#menu:. Le dernier argument est un symbole sélecteur qui correspond à une méthode dans la classe du modèle (classe du premier argument) qui prend un MenuMorph en argument. Cette méthode complète et retourne le menu :
MUIDPluggableButtonMorphTest>>button3
| btn |
btn := PluggableButtonMorph
on: self
getState: #state
action: #buttonAction3
label: #buttonLabel3
menu: #button3Menu:. "<-- le sélecteur pour le menu (un argument)"
btn borderWidth: 0.
btn borderColor: Color black.
btn onColor: Color transparent offColor: Color transparent.
^ btn
MUIDPluggableButtonMorphTest>>button3Menu: aMenu
#(#- #('Avancer' #doFoward) #('Reculer' #doBackward) #- #('Stop' #doStop) )
do: [:item | item == #-
ifTrue: [aMenu addLine]
ifFalse: [aMenu
add: item first
target: self
selector: item second]].
^ aMenu
[edit] Bouton de barre de menu
Là, c'est l'action elle même qui construit un menu et le fait surgir. Voici l'action du bouton1 modifiée pour construire et faire surgir un menu :
MUIDPluggableButtonMorphTest>>buttonAction1
| menu |
menu := MenuMorph new.
#(#('faire' #do) #('refaire' #redo) )
do: [:item | menu
add: item first
target: self
selector: item second].
menu popUpInWorld

