Programmation d'IHM - Des boutons simples

From OFSET Wiki

Jump to: navigation, search

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 :

Les six boutons simples de l'exemple
Les six boutons simples de l'exemple
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

Image:MUIDPluggableButtonMorphTest-button0.png

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

Image:MUIDPluggableButtonMorphTest-button1.png

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

Image:MUIDPluggableButtonMorphTest-button2-off.png

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
Image:MUIDPluggableButtonMorphTest-button2-off.png Image:MUIDPluggableButtonMorphTest-button2-on.png

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
Image:MUIDPluggableButtonMorphTest-button3-off.png Image:MUIDPluggableButtonMorphTest-button3-on.png

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
Image:MUIDPluggableButtonMorphTest-button4-off.png Image:MUIDPluggableButtonMorphTest-button4-on.png
Image:MUIDPluggableButtonMorphTest-button5-off.png Image:MUIDPluggableButtonMorphTest-button5-on.png

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
Personal tools