Programmation d'IHM - Panneau
From OFSET Wiki
Contents |
[edit] Panneau ?
Un panneau est une zone qui permet de regrouper des morphs. Un panneau se caractérise principalement par la gestion de ses dimensions et de son espace intérieur.
Ses dimensions et son placement peuvent être :
- fixes, sans gestion de l'espace
- ou bien calculés automatiquement
- relativement au morph contenus, avec un placement tabulaire, les morphs contenus se placent alors régulièrement les uns par rapport aux autres.
- relativement au morph contenant, avec un placement proportionnel, les morphs contenus se placent de façon autonome dans un sous-espace indiqué par rapport à la surface du panneau dans lequel ils sont insérés;
Tout morph peut être utilisé comme panneau. Historiquement, la classe AlignmentMorph a été introduite spécifiquement mais la gestion de l'arrangement intérieur est mis en oeuvre dans la classe Morph et AlignmentMorph ne contient plus que quelques méthodes de classe utilitaires comme AlignmentMorph class>>newRow ou AlignmentMorph class>>newColumn.
Dans le wiki de Squeak (en anglais) on trouve pas mal d'informations sur les panneaux [1]. Beaucoup des informations que vous pouvez lire ici sont issues de cette source. Pour la compréhension des alignements tabulaires, on peut consulter le projet MorphLayoutArticle produit par Ted Kaehler et Andreas Raab. Ce projet se charge dans une version Squeak3.7.
[edit] Positions et dimensions fixes
Comme le montre le code suivant, on peut tout simplement ajouter un morph dans un autre en indiquant des dimensions explicitement avec le message #extent: et sa position dans le monde avec le message #position:.
dim0 "MUIDAlignmentTest new dim0" | alig top | alig := Morph new extent: 30 @ 30. top := Morph new extent: 80 @ 70. top position: 10 @ 10. "<-- position en decalage du morph orange" top color: Color orange. top addMorph: alig. top openInWorldLe résultat est présenté ci-contre.
On remarque que, bien que le morph bleu soit ajouté dans le morph orange, le morph bleu est visuellement à l'extérieur de l'orange. Par contre, si on déplace le morph orange, on déplace bien son contenu, le morph bleu, aussi. L'envoi du message #position: à un morph provoque le déplacement du morph et des morphs contenus. Le décalage observé entre nos deux morphs existe parce-que le message #position: a été envoyé au morph orange avant l'ajout du morph bleu.
Quand on ajoute un morph dans un autre sans indiquer de position avec le message #position:, le morph est ajouté en position absolue 0@0. Si on ajoute un second morph, alors il recouvre le premier, toujours en position 0@0.
La taille du morph orange peut être changée (à l'aide du halo par exemple) sans conséquence vis à vis du bleu. De même, la taille du bleu peut être changée sans conséquence vis à vis de l'orange.
Dans Squeak, certaines IHM sont construites de cette façon (voir le panneau compilation de l'éditeur de Smacc) mais ce n'est pas à conseiller, l'utilisation de positions fixes rend difficile la maintenance et l'adaptabilité d'un tel code.
Voici par exemple le code d'un morph contenant d'autres morphs dont la position est calculée de façon à constituer une ligne régulière :
alig0 "MUIDAlignmentTest new alig0" | alig sm | alig := Morph new. alig color: Color orange. 1 to: 3 do: [:i | alig addMorphBack: (sm := self cellMorphNamed: i printString). sm position: (Point x: i - 1 * sm width y: 0)]. alig openInWorld
Les sous-morphs sont créés par la méthode suivante :
cellMorphNamed: aString | m | m := Morph new color: Color lightBlue; borderColor: Color black; borderWidth: 1; extent: 20 @ 20; yourself. m addMorphCentered: (StringMorph contents: aString font: Preferences windowTitleFont). ^ m
Le résultat ci-contre montre que la largeur du morph orange contenant est inférieure à celle des morphs contenus ce qui n'est généralement pas souhaité. De plus, on ne peux pas facilement changer les positions des morphs contenus ou changer leur taille sans intervenir dans le code. La réactivité du morph est très limité et son amélioration nécessite beaucoup de codage lourd et non maintenable. Par exemple, pensez au modifications nécessaires pour que les morphs contenus soient tous séparés par un espace élastique qui augmente ou diminue suivant la taille du contenant orange. Ou encore, si on veut que la taille du contenant soient toujours en cohérence vis à vis des morphs contenus.
[edit] Placement tabulaire
Un alignement tabulaire gère les morphs contenus organisés régulièreremnt en ligne ou en colonne. L'espace est divisé en cellules avec des propriétés identiques. Le comportement d'un alignement tabulaire est géré par un TableLayout affecté au morph contenant:
m := Morph new; m layoutPolicy: TableLayout new.
[edit] Un premier essai
Voici le code d'une methode qui crée un Morph et lui ajoute trois morphs tout simples :
alig1 "MUIDAlignmentTest new alig1" | alig | alig := Morph new. alig layoutPolicy: TableLayout new. alig listDirection: #leftToRight. "les morphs contenus sont ajoutés de la gauche vers la droite" 1 to: 3 do: [:i | alig addMorphBack: (self cellMorphNamed: i printString)]. alig openInWorld
L'image ci-contre montre le résultat de l'évaluation de MUIDAlignmentTest new alig1.
On constate qu'on obtient le même résultat que précédemment mais avec moins d'effort puisqu'on ne se soucie plus des positions des morphs contenus.
[edit] Positionnement relatif des morphs contenus
La méthode Morph>>#listDirection: permet d'indiquer dans quel sens ajouter les morphs contenus. Suivant la valeur du symbole argument, on dispose des quatre possibilités suivantes :
- #leftToRight : de la gauche vers la droite
- #rightToLeft : de la droite vers la gauche
- #topToBottom : du haut vers le bas (valeur par défaut) et
- #bottomToTop : du bas vers le haut
Par exemple, avec #bottomToTop on a le résultat montré ci-contre.
On remarque que lorsque les limites du contenant ne sont pas respectées : l'ajout du morph continue dans la même direction même si on atteint le bord du contenant.
Morph>>#wrapDirection: permet d'indiquer le comportement du remplissage quand l'espace horizontal ou vertical est totalement occupé. La direction qui en découle doit être orthogonale à celle indiquée avec #listDirection:. Ici encore, les valeurs possibles à passer en argument sont #leftToRight, #rightToLeft, #topToBottom ou #bottomToTop. On peut indiquer #none si on ne veut pas de changement de direction (comportement par défaut).
alig1bis "MUIDAlignmentTest new alig1" | alig | alig := Morph new. alig layoutPolicy: TableLayout new. alig listDirection: #leftToRight. "les morphs contenus sont ajoutés de la gauche vers la droite" alig wrapDirection: #bottomToTop. "on continu du bas vers le haut quand le bord est atteint" 1 to: 5 do: [:i | alig addMorphBack: (self cellMorphNamed: i printString)]. alig openInWorld
On constate sur le résultat montré ci-contre que le bord vertical de droite est bien respecté puisque qu'on passe #bottomToTop à #wrapDirection:. Par contre, la limite représentée par le bord supérieur n'est à son tour pas respectée (le 5 ième morph contenu dépasse).
[edit] Les dimensions d'un panneaux
[edit] Taille calculée à partir du contenu
Dans l'exemple précédent, on remarque que le morph contenant est de largeur inférieure à la largeur des morphs contenus. Très souvent, on désire que la taille du contenant s'adapte automatiquement au contenu. Pour cela on envoie le message #hResizing: ou #vResizing: avec comme argument #shrinkWrap pour respectivement s'adapter horizontalement ou verticalement aux morphs contenus.
alig2
"MUIDAlignmentTest new alig1"
| alig |
alig := Morph new.
alig layoutPolicy: TableLayout new.
alig listDirection: #leftToRight. "les morphs contenus sont ajoutés de la gauche vers la droite"
alig hResizing: #shrinkWrap. "la largeur de alig est calculée à partir de celle des morphs contenus"
1
to: 3
do: [:i | alig
addMorphBack: (self cellMorphNamed: i printString)].
alig openInWorld
Avec
...
alig hResizing: #shrinkWrap.
alig vResizing: #shrinkWrap.
...
on obtient un morph contenant dont la largeur et la hauteur sont calculées à partir des morphs contenus, il est ainsi complètement masqué.
[edit] Taille calculée à partir de celle du contenant
On peut faire en sorte qu'un Morph occupe en proportion une partie de l'espace occupé par le morph dans lequel il est inséré, en utilisant le symbole #fillSpace comme argument au message #hResizing: ou #vResizing:. Si le receveur est le seul contenu du morph contenant, alors, avec #hResizing:, le contenu occupe tout l'espace horizontal du contenant. Si le receveur est contenu avec un second morph, alors l'espace horizontal est proportionnel, soit 50% de la largeur du contenant pour l'un et 50% pour l'autre.
Voici l'exemple précédent repris en ajoutant un morph contenant supplémentaire :
alig3 "MUIDAlignmentTest new alig3" | yellow blue | yellow := Morph new. yellow extent: 150 @ 100. yellow color: Color yellow. yellow layoutPolicy: TableLayout new. blue := Morph new. blue layoutPolicy: TableLayout new. blue hResizing: #spaceFill. blue listDirection: #leftToRight. blue wrapDirection: #toTopBottom. 1 to: 10 do: [:i | blue addMorphBack: (self cellMorphNamed: i printString)]. yellow addMorph: blue. yellow openInWorld. yellow layoutChanged
Le Morph blue est programmé de sorte qu'il occupe tout l'espace horizontal du morph contenant yellow. Le résultat est montré ci-contre.
Voici un autre exemple avec trois morphs, un bleu, un rouge et un gris, contenus dans un morph jaune. On observe que les morphs contenus ont tous la même largeur et que cette proportion de 1/3 par sous-morph est conservée si on change la taille du morph jaune (à partir du bouton du halo par exemple).
alig4 "MUIDAlignmentTest new alig4" | yellow blue red gray | yellow := Morph new. yellow extent: 150 @ 100. yellow color: Color yellow. yellow layoutPolicy: TableLayout new. yellow listDirection: #leftToRight. blue := Morph new. blue color: Color blue. red := Morph new. red color: Color red. gray := Morph new. gray color: Color veryLightGray. blue hResizing: #spaceFill. red hResizing: #spaceFill. gray hResizing: #spaceFill. yellow addAllMorphs: (Array with: blue with: red with: gray). yellow openInWorld. yellow layoutChanged
La proportion de l'espace horizontal ou vertical occupé par chaque sous-morph peut être spécifiée. Ainsi, on peut faire en sorte que le morph bleu occupe 50% de la largeur du morph contenant jaune, le rouge 20% et le gris les 30% restant. Pour cela, on utilise la méthode #spaceFillWeight: :
alig4 "MUIDAlignmentTest new alig4" | yellow blue red gray | ... blue hResizing: #spaceFill; spaceFillWeight: 50.0. red hResizing: #spaceFill; spaceFillWeight: 20.0. gray hResizing: #spaceFill; spaceFillWeight: 30.0. ...
[edit] Taille modifiable
Le comportement par défaut est de considérer la taille comme indiquée explicitement soit avec le message #extent:, soit indirectement en utilisant le bouton jaune du halo. Ce comportement peut être forcé en envoyant le message #hResizing: ou vResizing: au morph avec le symbole #rigid comme valeur d'argument. C'est la seule façon d'autoriser le morph à être redimensionné manuellement ou par programme.
[edit] Répartition des sous-morphs
[edit] Espacement régulier
L'espace entre le bord d'un panneau et les cellules intérieures peut être modifié par l'envoi du message #layoutInset: au panneau; l'espace entre les cellules peut être modifié par l'envoi du message #cellInset: au panneau.
| conteneur rect1 rect2 rect3 |
conteneur := AlignmentMorph newRow
color: Color black;
layoutInset: 50 @ 0;
cellInset: 50 @ 0;
hResizing: #shrinkWrap;
vResizing: #shrinkWrap.
rect1 := RectangleMorph new extent: 50 @ 20; color: Color red.
rect2 := RectangleMorph new extent: 50 @ 20; color: Color blue.
rect3 := RectangleMorph new extent: 50 @ 20; color: Color green.
conteneur
addMorph: rect1 ;
addMorph: rect2;
addMorph: rect3;
openInWorld.
[edit] Disposition des sous-morphs
On peut contrôler la disposition des sous-morphs les un par rapport aux autres avec #listCentering:. Par exemple, si les sous-morphs sont organisés horizontalement, on peut indiquer de les caler tout à gauche, tout à droite, centré ou justifiés (répartis régulièrement dans l'espace disponible). Si les sous-morphs sont organisés verticalement, cela devient tout en haut, tout en bas, centré ou justifiés. Les valeurs d'argument à passer lors de l'envoi du message #listCentering: au morph contenant sont respectivement:
- #topLeft pour tout à gauche ou tout en haut
- #bottomRight pour tout à droite ou tout en bas
- #center pour centré
- #justified pour justifié
[edit] Placement proportionnel
Ici, on va dimensionner et placer un morph en utilisant le morph contenant comme repère. Le contenu peut, par exemple, coller aux bords du contenant. On parle aussi de gestion proportionnelle de l'espace occupé par le contenu.
Pour utiliser le placement proportionnel, il faut l'indiquer au morph contenant en lui envoyant un ProportionalLayout via le message #layoutPolicy::
| m | m := Morph new. m layoutPolicy: ProportionalLayout new.
[edit] Placement simple
Pour intégrer un morph dans un autre en indiquant explicitement les dimensions relatives et le comportement des bords du morph contenu par rapport au morph contenant on envoie le message #addMorph:fullFrame: au morph contenant. Le premier argument est le morph à ajouter et le second indique un cadre. Le cadre doit être une instance de LayoutFrame, ses coordonnées sont indiquées en proportion (valeur comprise entre 0 et 1) en utilisant le morph contenant comme repère. Voici un exemple qui ajoute un Morph blanc dans un Morph noir avec le résultat montré ci-contre :
| m sm | m := Morph new. m color: Color white. m layoutPolicy: ProportionalLayout new. sm := Morph new. sm color: Color black. m addMorph: sm fullFrame: (LayoutFrame fractions: (0.5 @ 0.2 corner: 0.8 @ 1)). m openInWorld.
Les coordonnées du rectangle occupé par le morph blanc sont calculées à partir des fractions utilisées pour créer le LayoutFrame. Le premier point (0.5 @ 0.2) donne la fraction en x et y du point haut gauche alors que le second point (0.8 @ 1) donne la fraction en x et y du point bas droit. Le point haut gauche se situe bien au milieu (0.5) de l'axe horizontal et verticalement à 20% du haut du morph noir (0.2).
En effet, le cadre est créé en envoyant le message #fractions: à la classe LayoutFrame. L'argument est un rectangle. Les coordonnées indiquées pour le rectangle sont à comprendre de la façon suivante :
- le point origin du rectangle (0.5 @ 0.2) représente les fractions pour le point haut gauche. x et y indiquent respectivement, la fraction de la largeur et la fraction de la hauteur par rapport à la largeur et la hauteur du contenant; (pour placer le coin haut gauche du contenu au centre du contenant on indiquerait 0.5 @ 0.5).
- le point corner (0.8 @ 1) représente les fractions pour le point bas droit. de même, x et y indiquent respectivement, la fraction de la largeur et la fraction de la hauteur par rapport à la largeur et la hauteur du contenant; (pour placer le coin bas droit du contenu au centre du contenant on indiquerait 0.5 @ 0.5)
Pour occuper tout l'espace disponible dans le rectangle noir, on aurait :
m addMorph: sm fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 1)).
On remarque que la proportion indiquée lors de l'ajout est invariable si on fait varier la taille du morph contenant. On peut ainsi obtenir l'impression que la bordure d'un morph contenu est collé à la bordure de son contenant.
[edit] Placement avec espaces de taille fixe
Le placement proportionnel permet d'indiquer un décalage fixe, positif ou négatif. C'est surtout utile pour figer certains espaces, horizontalement, verticalement ou les deux, même en cas de modification de la taille du morph contenant.
Par exemple, la plupart des IHM comprennent une fenêtre contenant un espace réservé au menu, à des barres de boutons ou des zones de message. Ces espaces sont généralement collés à un des bords du contenant, tout en haut, à la base ou sur un des cotés. En cas de changement de la taille de la fenêtre, il est très désagréable de voir la hauteur de sa barre de bouton ou de son menu changer proportionnellement. Par contre, pour une barre horizontale, on désire souvent que les bords de gauche ou droite collent au contenant. Pour éviter la modification de la hauteur en cas de redimensionnement de la fenêtre, on peut indiquer une taille fixe pour la hauteur du menu ou la barre de boutons.
Un espace vertical ou horizontal fixe se traduit par l'indication d'un offset en x ou en y et pour le point haut-gauche ou bas-droit. On peut donc indiquer quatre offsets. Pour cela, on utilise la méthode #LayoutFrame>>fractions:offsets: . Le premier argument est un rectangle pour les proportions comme vu précédemment, le second argument est aussi un rectangle mais interprété différemment par le gestionnaire de placement :
- le point origin du rectangle représente les offsets pour le point haut gauche. x et y indiquent respectivement, la taille de l'espace fixe horizontal et la taille de l'espace fixe vertical; (pour assurer un espace fixe horizontal positif de au moins 10 points et un espace fixe vertical positif de au moins 5 points, on indiquerait 10 @ 5).
- le point corner représente les offsets pour le point bas droit. x et y indiquent respectivement, la taille de l'espace fixe horizontal et la taille de l'espace fixe vertical; (pour assurer un espace fixe horizontal négatif de au moins 10 points et un espace fixe vertical negatif de au moins 5 points, on indiquerait -10 @ -5).)
Exemple:
m addMorph: sm fullFrame: (LayoutFrame fractions: (0 @ 0 corner: 1 @ 1)
offsets: (10 @ 5 corner: -10 @ -5)).







