Programmation d'IHM - La fenêtre

From OFSET Wiki

Jump to: navigation, search

Contents

[edit] Ouverture

La classe relative aux fenêtres est SystemWindow. Pour créer une fenêtre vide :

SystemWindow new 

Bon, vous n'avez rien vu, c'est normal, la fenêtre a bien été créée mais non ouverte, c'est-à-dire, non stockée dans le monde visuel de squeak. Pour l'ouvrir et permettre sa manipulation via la souris, il faut lui envoyer le message "openInWorld" :

SystemWindow new openInWorld

On peut aussi utiliser "openInHand", la différence est que la fenêtre s'ouvre directement sous le pointeur de la souris. Ainsi, elle peut être directement placée : pour la poser quelque part, il suffit de cliquer avec le bouton gauche.

SystemWindow new openInHand

[edit] Titre

[edit] Nommer la fenêtre

Quand on crée une fenêtre vide, son titre standard est Untitled Window. Pour le changer, on envoie le message setLabel: à la fenêtre :

SystemWindow new setLabel: 'ma premiere fenetre, youpi!'; openInWorld


Mais on a mieux, au lieu de créer la fenêtre (systemWindow new) et ensuite de lui indiquer son titre (setlabel:), on peut directement demander à la classe de créer la fenêtre avec son titre :

(SystemWindow labelled: 'ma premiere fenetre, youpi!') openInWorld


On peut récuperer le titre d'une fenêtre en lui envoyant le message label. Voici un exemple qui ouvre une fenêtre et affiche son titre sur le transcript. Pour voir ce qui se passe, il faut au préalable ouvrir le transcript (worldmenu/open/transcript).

| win |
(win := SystemWindow labelled: 'ma premiere fenetre, youpi!') openInWorld.
Transcript show: win label; show: String cr "afficher le titre sur le transcript"


[edit] Changer le titre

On peut changer le titre d'une fenêtre après sont ouverture. Voici un exemple qui ouvre une fenêtre avec un titre et qui change le titre après 2 secondes :

| win |
(win := SystemWindow labelled: 'ma premiere fenetre, youpi!') openInWorld.
(Delay forSeconds: 2) wait. "attendre pendant 2 secondes"
win setLabel: 'mon nouveau titre'. "changer le titre"

[edit] Un modèle et une vue

Jusqu'à présent, on a créé des fenêtres isolées sans rapport avec une utilisation particulière. Il est temps de considérer le niveau d'une application avec un modèle et une vue. Dans les exemples précédents, le titre de la fenêtre est uniquement dépendant de la vue : on crée la fenêtre avec un titre et on peut éventuellement demander à cette vue de modifier le titre. Maintenant, qu'en est-il si le titre est dépendant de l'état de l'application ? Le titre est dans ce cas calculé pas le modèle à partir de la valeur de ces variables d'instances.

Prenons l'exemple d'une application qui permet de lire un fichier texte et d'afficher son contenu dans une fenêtre. Il est utile que le titre de la fenêtre reflète l'état de l'éditeur et soit construit à partir du nom du fichier dont le contenu est affiché. Ici, le modèle est la classe qui met en oeuvre l'éditeur. On la nomme MUIDSimpleEditor1. Pour ne pas compliquer plus que nécessaire, on ne s'intéresse qu'à une IHM avec une fenêtre toute simple et à la gestion du titre (on a pas encore d'éditeur de texte).

Le code suivant permet de créer un éditeur (ouverture d'une fenêtre et saisie d'un nom de fichier dans un dialogue standard).

| muidsw |
(muidsw := MUIDSimpleEditor1 new) open.
muidsw askForFilename

Voici la première version de notre éditeur: pour l'instant, avec une seule variable d'instance pour porter le nom du fichier.

Object subclass: #MUIDSimpleEditor1
       instanceVariableNames: 'filename'
       classVariableNames: 
       poolDictionaries: 
       category: 'MUIDoc'

Voici deux accesseurs pour le nom du fichier: un en lecture et l'autre qui modifie le nom du fichier courant :

filename
   "accesseur qui retourne le nom du fichier courant"
    ^filename
       ifNil: [filename := ]
setNewFilename: aFilename 
   "change le nom du fichier"
   filename := aFilename.
   self changedFilename


Voici la méthode qui permet à la vue de récupérer le titre de la fenêtre. Le titre est construit en concaténant la chaîne constante 'MUID: ' avec le nom du fichier en cours d'édition.

labelString
   "méthode standard qui retourne le titre d'une fenêtre. voir
   SystemWindow>>update: "
   ^ 'MUID: ', self filename

Voici les méthodes pour construire et ouvrir la fenêtre. Pour la construction, on crée une fenêtre toute simple. Le modèle (l'éditeur) associé à la fenêtre est indiqué par l'envoi du message model:. Ainsi, la vue a connaissance de son modèle et peut en récupérer des informations (le titre par exemple).

buildMainWindow
   "construction de la fenêtre"
   | wdw |
   (wdw := SystemWindow new) model: self.
   self changedFilename.
   ^ wdw
open
   "construit et ouvre la fenetre"
   ^ self buildMainWindow openInWorld
changedFilename
   self changed: #relabel

Voici la méthode qui permet de saisir simplement un nom de fichier et d'affecter ce nom à la variable d'instance adéquate du modèle.

askForFilename
   "Demande à l'utilisateur le nom du fichier dont le contenu est à
   afficher"
   | newFilename |
   (newFilename := FillInTheBlank request: 'Fichier à lire' initialAnswer: self fileName) isEmpty
       ifTrue: [^ self].
   self setNewFilename: newFilename.

Réexecutez le code suivant :

| muidsw |
(muidsw := MUIDSimpleEditor1 new) open.
muidsw askForFilename


Vous observez que, à sa création, le fenêtre a pour titre "MUID:". Lorsque vous saisissez et acceptez le nom du fichier, vous observez que la vue est automatiquement mise à jour par le changement du titre.

[edit] Comment cela fonctionne

Regardez dans SystemWindow>>update:. Vous constatez que la fenêtre met à jour son titre si elle reçoit le message update: avec comme argument, le symbole #relabel. C'est justement ce qui est provoqué par la méthode changedFilename. Cette dernière indique au modèle que quelque chose a changé et que ce quelque chose est repéré par le symbole #relabel. Indirectement, la conséquence est que toutes les vues sur le modèle reçoivent le message update: avec #relabel comme valeur d'argument.

Pour comprendre, sachant que SystemWindow est une sous classe de MorphicModel, regardez les méthodes Object>>changed: et MorphicModel>>model:. Tout objet qui reçoit le message changed: envoie le message update: aux objets dépendants. Tout MorphicModel qui reçoit le message model: est mis en liaison avec son modèle et le modèle est déclaré comme dépendant du MorphicModel. Ici, model: est envoyé à la vue dans buildMainWindow et changed: est envoyé au modèle dans changedFilename.

[edit] Taille

[edit] Modifier la taille d'une fenêtre ouverte

Pour modifier dynamiquement la taille d'une fenêtre, on lui envoie le message extent: avec un point comme argument (x spécifie la largeur et y la hauteur). Pour créer un point à partir des coordonnées, on peut envoyer le message x:y: à la classe Point ou utiliser la notation avec @. Voici un exemple qui test l'égalité de deux points créés suivant les deux possibilités :

Transcript show: (Point x:100 y: 100) = (100@100)

Après exécution, vous obtenez l'affichage de true sur le Transcript.

Maintenant, testez le code suivant :

| win |
win := SystemWindow new openInWorld.
(Delay forSeconds: 2) wait. "attendre pendant 2 secondes"
win extent: 100@100

Vous observez l'ouverture d'une fenêtre avec une taille par défaut et, après 2 secondes, la modification de sa taille provoquée par l'envoi du message extent:.

Maintenant, testez ceci :

 SystemWindow new extent: 100@100; openInWorld

Vous avez bien votre fenêtre ouverte mais sa taille n'est pas celle escomptée. C'est normal. Si vous suivez l'exécution de ce code via le debugger, vous constaterez que openInWorld modifie indirectement la taille de la fenêtre (avec une taille par défaut attribuée par la classe RealEstateAgent). La taille indiquée (100@100) est affectée à la nouvelle fenêtre, cependant elle est écrasée avec la taille par défaut lorsque la fenêtre reçoit le message openInWorld.

[edit] Taille initiale d'une vue

Dans une application fenêtrée, l'attribution de la taille initiale d'une fenêtre s'effectue via une requète au modèle. La taille est le point retourné lors de l'envoi de initialExtent au modèle.

Revenons à la classe MUIDSimpleEditor1, pour la taille initiale, il suffit donc d'ajouter la méthode d'instance initialExtent

initialExtent
   ^ 200@200

Testez :

 MUIDSimpleEditor1 open

[edit] Récupérer la taille d'une fenêtre

Il suffit d'envoyer le message extent :

Testez :

| wdw |
wdw := MUIDSimpleEditor1 new open.
Transcript show: wdw extent

[edit] Couleur

La couleur d'une fenêtre est modifiable de plusieurs façons : on peut indiquer directement la couleur désirée à la fenêtre ou se servir des mécanismes d'initialisation des couleurs précablés via les préférences. Quand vous modifiez la couleur d'une fenêtre, vous observez que les bords et la barre supérieure (celle avec les boutons standards et le menu) sont de la couleur voulue. Par contre, l'intérieur de la fenêtre est beaucoup plus clair. C'est normal. Pour comprendre, regardez le code de la méthode SystemWindow>>paneColor:. En effet, le message correspondant est envoyé à la fenêtre lors de son ouverture.

[edit] Modification directe

Il suffit d'envoyer le message setWindowColor: à la fenêtre. Testez :

| wdw |
 wdw := SystemWindow new openInWorld.
 wdw setWindowColor: Color orange.

Joli non ?

Pour modifier la couleur du cadre, envoyez le message borderColor: à la fenêtre :

 | wdw |
 wdw := SystemWindow new openInWorld.
 wdw setWindowColor: Color orange.
 wdw borderColor: Color black

[edit] Initialisation de la couleur par le modèle

Le modèle peut indiquer une couleur par défaut utilisée par la fenêtre. Il suffit de mettre en oeuvre la méthode defaultBackgroundColor dans la classe du modèle. Elle retourne la couleur utilisée lors de l'ouverture de la fenêtre (voir SystemWindow>>paneColor).

Revenons à la classe MUIDSimpleEditor1, pour la couleur initiale, il suffit donc d'ajouter la méthode d'instance defaultBackgroundColor:

defaultBackgroundColor
   ^ Color orange

Maintenant testez ce code :

| wdw |
wdw := MUIDSimpleEditor1 new open.
Transcript show: wdw color

Sur le transcript, vous voyez s'afficher la transcription RGB de la couleur.

[edit] Initialisation par les préférences

Les préférences de Squeak sont mises en oeuvre par la classe Preferences. Elle contient une table des couleurs indexée par les applications. Ici, notre application c'est l'éditeur mise en oeuvre par la classe MUIDSimpleEditor1. Regardez la classe Preferences, elle n'a que des méthodes de classe. En effet, comme vous le montre le résultat du code suivant, cette classe n'a pas d'instance. Les préférences sont gérées par la classe comme des informations globales.

Preferences allInstances

Pour initialiser une couleur par défaut pour votre application, il faut que votre classe MUIDSimpleEditor1 mette en oeuvre la méthode windowColorSpecification qui retourne une instance de WindowColorSpec. Cette instance, unique dans le système, sera stockée dans la table des préférences pour votre application.

Voici un exemple (comme méthode de classe de MUIDSimpleEditor1)

windowColorSpecification
   "Answer a WindowColorSpec object that declares my preference"
   ^ WindowColorSpec
       classSymbol: self name asSymbol                     "le nom de classe (le symbole) qui met en oeuvre l'application"
       wording: self name asString                         "le nom de votre application"
       brightColor: Color lightBlue                        "la couleur vive"
       pastelColor: Color lightBlue muchLighter            "la couleur pâle"
       helpMessage: 'Editeur tout simple'                  "un message d'aide (pour les bulles)"

Vous remarquez qu'on doit indiquer deux couleurs, une vive et l'autre pâle. On se rappellera de ça, on y reviendra un peu après.

Maintenant, vous devez installer cet préférence. Pour cela, on peut envoyer le message installMissingWindowColors à la classe Preferences. En général, on place ce code dans la méthode initialize de la classe de l'application (méthode de classe). voici la méthode initialize de la classe MUIDSimpleEditor1 :

initialize
     Preferences installMissingWindowColors

Profitez en pour exécuter ce code et voilà, c'est fait, la couleur pour les fenêtres de votre application est installée dans les préférences. Un petit détour par le menu général de squeak (le world menu), open/appearance/window colors... et hop, vous constatez que votre application est présente. De plus, vous pouvez interactivement changer les couleur préférées en cliquant sur le rectangle de couleur de votre appli. Dans ce dialogue, vous constatez que vous pouvez globalement choisir des couleurs vives ou pâles, ce qui vous explique maintenant le pourquoi des deux couleurs pour instancier le WindowColorSpec (je n'ai encore jamais vu de squeaker utiliser les couleurs pâles...).

Avant d'ouvrir votre éditeur, vous devez retirer votre méthode MUIDSimpleEditor1>>defaultBackgroundColor. En effet, c'est la version mise en oeuvre dans la classe Object qui récupère la couleur par les préférences :

Object>>defaultBackgroundColor
   "Answer the color to be used as the base window color for a
   window whose model is an object of the receiver's class"
   ^ Preferences windowColorFor: self class name

[edit] Menu

Toute fenêtre est associée à un menu construit dynamiquement lorsqu'on clique sur le bouton en haut à gauche.

Image:SystemWindow-menu.png

Il est possible d'ajouter des entrées spécifiques à votre application dans ce menu standard. Pour cela, il suffit de mettre en oeuvre la méthode addModelItemsToWindowMenu: dans la classe du modèle du SystemWindow. L'argument passe est une instance MenuMorph.

Voici un exemple qui ajoute deux entrées au menu, une pour la lecture d'un fichier et l'autre pour afficher un à propos. Cet méthode est implantée dans la classe MUIDSimpleEditor1 de notre application exemple :

 addModelItemsToWindowMenu: aMenu 
   "Add model-related items to the supplied window menu"
   aMenu addLine.               "ajouter un séparateur"
   aMenu
       add: 'lire un fichier'   "label de l'entrée dans le menu"
       target: self             "le receveur de l'action"
       action: #askForFilename. "le message à envoyer au receveur"
   aMenu addLine.
   aMenu
       add: 'à propos ...'
       target: self
       action: #about

Les méthodes correspondantes (askForFilename et about) doivent être mise en oeuvre dans la classe du modèle. On a déjà vu askForFilename, voici le code de about :

 about
   self inform: 'MUIDSimpleEditor1:\Exemple d''éditeur pour apprendre Squeak et les morphs' withCRs
Personal tools