© Jean-Pierre ANGHEL - 2004
FOX + RUBY
=
FXRuby
Par l’exemple
Quelles
connaissances faut-il avoir ?
Présentation
des composants visuels.
Présentation
des aménageurs d'espace.
Les
fenêtres et tout ce qui va avec.
Les
commentaires et substitutions
Exemple
général sur les boutons.
Les boutons
avec flèches incorporées
Les
expressions conditionnelles
Le composant
"Boîte de texte" et divers gadgets :
Les boîtes
à lister, les répertoires :
Les
variables globales prédéfinies
Eh oui, encore un. Pourquoi ? parce qu' il y
a pléthore de bouquins pour les sujets connus et très peu sur les sujets peu …
porteurs, dirons-nous. Parce que le Français est généralement viscéralement
fermé à la langue anglaise, ce qui le prive de pas mal de connaissances et
d'évolutions (Combien ont arrêté de faire des macros sur le tableur de Monsieur
Bill depuis qu'elles sont en anglais ?) et que "le" livre sur le
sujet qui nous préoccupe est dans la langue de Shakespeare (c'est comme ça
qu'on dit et du coup on dirait que c'est ce brave homme qui a inventé l'anglais
!)
Faire découvrir un langage puissant et très
intéressant : Ruby, facilement mis en œuvre à l'aide d'une librairie graphique
également originale: Fox.
Qu'est-ce
que Ruby ?
Ruby est un
nouveau (pas tant que ça comme on le verra plus loin) langage de
programmation pour écrire de petits
(pas si petits comme on etc…) programmes appelés par les anglais des scripts. C'est un langage interprété.
C'est aussi un langage totalement orienté objet. C'est un langage qui a été
porté sur de nombreuses plates-formes. Et c'est enfin un langage entièrement gratuit.
Qu'est-ce que Fox ?
Fox
est un outil de développement graphique construit en C++. Il offre un large
éventail de contrôles et autres composants graphiques. Il a aussi été porté sur
nombre de plates-formes et se remarque par sa rapidité et sa facilité
d'utilisation. Et c'est de plus une interface entièrement gratuite.
A qui s'adresse ce livre ?
A ceux qui ont tout de même quelques rudiments
de programmation. A tous ceux qui veulent écrire de courts programmes, qui
veulent apprendre la programmation OO (orientée objet) en utilisant une
interface graphique, qui ne veulent pas enrichir encore plus Monsieur
Bill, qui n'ont pas de budget à
consacrer à un énième langage "visuel" ou qui ne veulent pas pirater (si, si , y'en a !) etc… Les chevronnés
n'apprendront certainement pas grand-chose, voire rien du tout, mais celui qui
est interessé par les "bidouilles" sur son ordinateur et ne sait
comment les faire y trouvera une réponse. A propos de bidouille, une question à
ceux qui programment : comment feriez-vous pour remplacer la chaîne
"truc" par la chaîne "machin" dans tous les fichiers d'un
même répertoire, munis des extensions "c" et "h" et
sauvegarder les anciens fichiers avec l'extension '.bak' ? Voici le résultat
tel qu'il est présenté dans le guide de l'utilisateur Ruby :
ruby -i.bak -pe 'sub
"truc","machin"' *.[ch]
Ruby ça "marche" sur quoi ?
Ruby fonctionne à peu près sur toutes les
plates-formes. MSDOS, Windows 9x, OS2, Unix, Mac (livré avec l'OS). Et sur
Linux . Ce qui est encourageant pour les petits budgets du paragraphe précédent
: ils peuvent avoir l' OS et les programmes pour pas un sou. En parlant de
petit budget les enseignants vont certainement se reconnaître, mais tant pis
(ou tant mieux !). Ce qui ne veut pas dire d'ailleurs que Ruby, Fox etc.. soient en quelque sorte l'informatique
du pauvre. Loin de là cette idée car, comme vous le verrez en l'étudiant, il
n'a absolument rien à envier, mais alors rien du tout, aux autres langages payants.
Et en conclusion de cette introduction,
comme il est certain qu' un langage
dédié uniquement au "script" n'intéresserait que des spécialistes, il
est urgent de dire que Ruby est accompagné de deux interfaces graphiques, Tcl/Tk et Fox, qui permettent de faire (presque) du Visual machin-chose. Et
comme on ne peut pas tout faire, ce livre est axé sur la programmation Ruby avec Fox, sous Windows.
Les langages Perl,
Python, Smalltalk existaient déjà et Ruby est un peu un mélange de tout ça, en mieux ça va de soi.
Ruby est né au Japon en 1993 des envies d'un programmeur d'avoir un langage "à sa main" dirons-nous. Yukihiro Matsumoto est son nom (Matz pour les intimes). Il aura fallu attendre trois ans pour que ce langage prenne forme et se développe au Japon. Il faudra encore attendre quelques années pour qu' il soit traduit en anglais, et l'an 2000 pour un premier livre occidental. Et en français il n'y en a qu'un , celui de chez O'Reilly "Ruby in a nutshell" (encore de l'anglais !, mais uniquement dans le titre heureusement).
Pourquoi le nom de Ruby ? tout simplement parce
qu'il existait déjà Perl … astucieux n'est-il pas?
L'interface graphique Tcl/Tk existait sur de nombreuses plates-formes jusqu'à ce que Jeroen van der Zijp se dise in petto (il y a de l' anglais partout, je peux bien mettre un peu d'italien, non ?), que cette extension graphique avait vieilli et qu'il était temps d'en construire une autre portable sur toutes les plates-formes. Ce qu'il fit et fort bien en créant Fox sur une idée qui naquit au printemps de 1997. Fox est une librairie graphique développée en C++ pour rendre la programmation simple, efficace et indépendante de la plate-forme. Deux des aspects les plus spectaculaires de cette librairie se situent d'une part dans la communication directe entre composants et d'autre part dans le gestionnaire d'arrangement des composants entre-eux et dans une fenêtre, sans précison de coordonnées.
Lyle Johnson adapta Fox à Ruby, et continue d'ailleurs à le faire, pour le bonheur de tous.
La plupart des exemples de ce livre sont inspirés
des programmes en anglais disponibles sur le Net. Mais comment expliquer le
fonctionnement d'un bouton si ce n'est en en créant un tout seul dans une
simple fenêtre ? Hormis les titres en français, le code est évidemment le même
pour la suite.
Ruby pour
Windows est disponible sur Internet sur le site suivant : http://www.pragmaticprogrammer.com/ruby/downloads/ruby-install.html
Dans la version Windows le fichier s'installe automatiquement. A noter que sur NT, 2000 et XP il faut se loger en administrateur pour renseigner le chemin d'accès (le 'path' anglais)
Le site officiel de ruby est http://www.ruby-lang.org
Pour des informations supplémentaires voir sur http://www.rubycentral.com
Comme livre il n'existe à ce jour, en dehors de
celui cité plus haut, que la "bible" de David Thomas et Andrew Hunt :
"Programming Ruby : The Pragmatic Programmer's Guide" chez
Addison-Wesley. Disons aussi que ce livre est en accès libre sur Internet sur http://www.pragmaticprogrammer.com/ruby/downloads/book.html
Tout ceci est évidemment, et malheureusement, en
anglais.
Avec celui
que l'on veut, mais il vaut mieux en avoir un qui reconnaîsse la syntaxe, les
mots clefs de Ruby comme Scite qui
fonctionne sous Windows. De plus lui aussi est non seulement gratuit mais
dispose d' une multitude de langages. Vraiment inutile de s'en priver.
Scite est d'ailleurs fourni avec la distribution
Windows précédemment citée.
En dehors
de l'éditeur de texte pour taper des programmes entiers, il y a la possibilité de se servir de IRB (Interactive ruby), un utilitaire
en ligne de commande. IRB est très utile pour tester quelques lignes de code
sans utiliser le mode graphique, Ruby fonctionne alors dans une fenêtre DOS.
IRB est bien entendu fourni avec la distribution de Ruby pour Windows.
Il vaut
mieux connaître un langage objet classique. Si on programme déjà dans un des
langages de "script" cités plus haut, ou bien avec C++, pas de problèmes d'adaptation . Si on vient
de Pascal ou de Delphi, ça sera un peu plus dur mais rien d'insurmontable
surtout avec les composants visuels de Fox.
Outre le
fait que Ruby et Fox soient gratuits, il existe un autre avantage, énorme, qui
est celui de la stabilité. En effet sont disponibles sur le Net les versions
stables ainsi que celles à l'essai pour
les bêta-testeurs ou les curieux. Les versions en cours de modification sont
les versions dont le deuxième chiffre est impair. La version en cours au moment
de la rédaction du présent ouvrage est la 1.8 qui est "vieille" d’un an environ. C'est tout de même un
avantage énorme d'avoir un logiciel garanti sans bogues, ou tout du moins dans le domaine du raisonnable.
Evidemment ceci n'est malheureusement possible qu'avec des logiciels libres,
les lois du marché étant impitoyables et stupides : achèteriez-vous le dernier
modèle de voiture uniquement pour la frime, si vous deviez sortir la boîte à
outils à tous les feux rouges à cause d'un bogue dans le moteur ? Mais le monde
est ainsi fait et les gens continueront d'acheter la dernière version mise sur
le marché par monsieur Bill, parce qu'on leur a fait croire qu'elle est encore
meilleure que la précédente. Et les mêmes paieront sans barguigner les
rectificatifs et corrections de bogues un mois plus tard…
D'accord, on
appelle ça maintenant des "widgets" (de 'Windows' et 'gadgets'). Ca
fait bien, très à la page, et ça fait nager le débutant pendant quelque temps
jusqu'à ce qu' un initié ou un magazine lui dévoile le secret. De toutes façons
ne dites pas à un bouton qu'il n'est
qu' un simple gadget, il ne vous répondra même pas.
Nous verrons donc dans les chapitres suivants les
composants visuels de Fox un à un, mais commençons donc par les présentations:
Les fenêtres
Les cadres
Les boutons
Les cases à cocher
Les boutons radios
Les canevas
Les potentiomètres
Les boîtes de dialogue
Les fenêtres MDI (Multiple Documents Interface)
Les fenêtres modales ou non
Les boîtes à grouper
Les boîtes de saisie
Les onglets
Les feuilles de calcul
Etc...etc...
Tout au long de cet ouvrage vont alterner les
présentations de composants visuels et
le B.A. BA de Ruby. Car il va de soi que si Ruby peut fonctionner sans FOX,
l'inverse n'est pas vrai.
Nous avons dit plus haut qu'il n'était pas nécessaire de préciser les coordonnées des composants dans une fenêtre ou à l'intérieur d'un autre composant, comme c'est le cas dans d'autres langages ou interfaces graphiques. En effet, la programmation visuelle était une révolution mais le programmeur passe son temps à aligner ses composants, le plus souvent au pixel près, et l'utilisateur peut tout démolir en choisissant une autre fonte par exemple. Dans Fox ce sont les gestionnaires d'aspect qui se chargent de tout ce travail, à l'aide de quelques constantes indiquant la position souhaitée, et le résultat visuel est plus que satisfaisant comme vous allez le voir bientôt. Il est bien évident que si l'utilisateur redimensionne l'image, l'aspect restera le même pour tous les composants.
·
FXPacker . L' "emballeur" place
ses composants enfants (c'est à dire ceux qui vont dépendre de lui pour
l'emplacement et les commandes) dans son rectangle intérieur en se débrouillant
pour les placer le long de ses quatre côtés.
·
FXTopWindow . C'est
la fenêtre toute simple qui fonctionne comme le composant ci-dessus mais en
respectant les commandes de placement des composants. Pour de simples dialogues
ou sous-fenêtres, c'est le composant le
plus simple.
·
FXHorizontalFrame . C'est
un cadre qui place ses enfants de la gauche vers la droite, ou inversement. Les
composants s'afficheront suivant l'ordre de leur déclaration dans le programme.
·
FXVerticalFrame . La
même chose que le composant horizontal, mais en vertical.
·
FXMatrix . La
matrice place ses enfants en lignes et colonnes.Elle peut travailler soit comme
orienté-colonnes, soit orienté-lignes, le deuxième cas étant la normale.
·
FXGroupBox. Il
est comme FXPacker, mais offre en plus un élégant cadre autour de ses enfants
avec un titre optionnel. Si les enfants sont des boutons-radio il n'en accepte
qu'un de coché à la fois.
·
FXSplitter . Le "diviseur"
divise un espace en deux parties, horizontalement ou verticalement et permet à
l'utilisateur d'agrandir l'une d'elles.
On trouvera en annexe B la description des constantes de présentation pour
tous ces gestionnaires.
A tout seigneur tout honneur, la fenêtre est reine. Où mettre des composants si ce n'est dans une fenêtre. Commençons donc par la fenêtre toute simple et par un exemple tout aussi simple pour ne pas effaroucher le futur utilisateur.
***************************************************************************
1-
require 'fox'
2-
include Fox
3-
monApp = FXApp.new
4-
maFenetre = FXMainWindow.new(monApp, "Coucou",
nil,nil,DECOR_ALL,0,0,200,20)
5-
monApp.create
6-
maFenetre.show
7-
monApp.run
***************************************************************************
Explications
:
Ligne 1 : on indique que la librairie graphique Fox
est nécessaire.
Ligne 2 : n'est pas obligatoire, mais permet d'avoir
disponibles tous les noms déclarés dans le module Fox.
Ligne 3 : une instance d' application est créée par
appel de la méthode new.
Ligne 4 : on crée une instance de fenêtre et on lui passe en paramètre le nom de l'application (une variable pointeur) , le texte entre guillemets devant figurer sur le bandeau supérieur de la dite fenêtre ici, "Coucou" . Viennent ensuite 3 autres paramètres dont nous parlerons plus tard. Puis le point d'ancrage de la fenêtre, ici x=0 et y=0, donc la fenêtre est en haut à gauche de l'écran, les coordonnées débutant effectivement à cet endroit-là. Enfin 2 autres nombres qui sont la largeur et la hauteur de la fenêtre en pixels.
Ligne 5 : on
crée l'application elle-même.
Ligne 6 : par défaut toutes les fenêtres en FXRuby
sont invisibles, il nous faut donc montrer maFenetre, ce que fait la méthode
.show.
Ligne 7 : il n'y a plus qu'à lancer en appelant la
méthode 'run' qui va boucler jusqu'à ce que l'on quitte le programme : et que
ça roule !…
Si
tout a bien fonctionné vous obtenez une image semblable à celle-ci :
![]()
Fig 1 - Coucou1.png
Vous
remarquerez que pour une fois il n'y a pas de "Hello, world", ça
change…
Vous remarquerez aussi que la petite fenêtre est
ancrée dans le coin supérieur gauche de l'écran, peut-être même l'avez-vous
cherchée si la définition de votre écran est importante. Pas de panique, nous
verrons ce que l'on peut faire.
Il n'y a pas grand chose d'autre à dire pour le moment si ce n'est qu' une grosse partie des initialisations de constantes est faite dans la classe FXWindow.
La
version complète de la méthode 'new' est la suivante :
FXMainWindow.new(app, title, icon=nil,
miniIcon=nil, opts=DECOR_ALL,
x=0, y=0, width=0, height=0, padLeft=0,
padRight=0,
padTop=0, padBottom=0, hSpacing=4, vSpacing=4)
Les
paramètres 'app' et 'title' désignent respectivement un pointeur sur
l'application et le titre de la fenêtre. Les autres paramètres sont facultatifs,
nous en parlerons plus loin.
Tout
d'abord, posons-nous la question de savoir ce qu'est un objet.
"Objets inanimés avez vous donc une âme ?"
En programmation non seulement les objets ne sont
pas forcément inanimés mais ils savent faire de naissance beaucoup de choses,
comme se déplacer, tourner, se cacher, réapparaître etc.. à condition toutefois
qu'on leur dise quoi faire, sinon ils ne font rien…
Imaginez un bloc de code dans lequel se trouvent des données modifiables (des variables) et du code actif sous forme de procédures ou de fonctions pour agir sur ces données. Et bien le tout forme ce qu'on appelle un "objet". Histoire de changer et bien marquer la différence avec la programmation procédurale, les variables deviennent des "Attributs" en Ruby (et "Propriétés" dans d'autres langages), les procédures et les fonctions se réunissent et deviennent des "Méthodes". Bref tout ce bloc de code se retrouve dans une zone mémoire et n'en bouge plus, mais tout est là prêt à l'action. Imaginons que nous ayons un objet "Tasse". Contrairement à votre tasse du petit déjeuner, qui ne sait pas faire grand chose d'elle même, celle-ci a été dotée de méthodes lui permettant par exemple de se déplacer, de se renverser etc… et d'attributs lui indiquant sa capacité, sa température maximale d'utilisation, sa couleur etc… Mais un tel objet n'est en quelque sorte qu'un modèle, une matrice inopérante, une 'Classe'. Pour utiliser un tel objet il faut passer par son clône, qu'on appelle 'instance', une copie "vivante" en quelque sorte, mise au monde par la méthode 'new'. Pour indiquer l'utilisation de tel ou tel attribut ou bien de telle ou telle méthode, nous indiquons le nom de l'instance suivit d'un point puis du nom de l'attribut ou de la méthode :
maTasse = tasse.new # création de l'instance
maTasse.couleur=rouge
# attribution de la couleur à l'attribut 'couleur'
maTasse.tourne(90) # ordre de tourner de 90 degrés.
En Ruby il
n'y a pas de déclaration préalable de variables. Ces dernières ne sont pas
typées, c'est à dire n'acceptant qu'un seul type de données. La mémoire est
gérée automatiquement, si un objet est détruit il n'est pas nécessaire de
libérer la mémoire.
En Ruby tout est objet (y compris un simple nombre). Bien entendu on peut créer ses propres objets, et les faire "descendre" d'un objet existant. Le nouvel objet va hériter des attributs et méthodes de son ascendant.
Un module
est une partie de code qu'on ne charge en mémoire que si nécessaire, sauf bien
entendu les modules qui font tourner la mécanique et sont chargés d'office.
'require'
est une méthode du module 'kernel', c'est à dire du noyau même de Ruby, qui
indique que le module dont le nom suit est obligatoire.
Nous avons vu que pour créer une instance d'objet il fallait faire appel à la méthode 'new'; en FXRuby il y a une distinction faite entre l'instanciation d'un objet et sa création dans le système. Pour créer l'objet Windows associé à l'objet FXRuby déjà construit, nous appelons la méthode 'create'.
Pour ceux
qui ont commencé à programmer en Pascal disons qu'une 'Classe' est tout
simplement une déclaration de 'Type',
une 'instance' équivaut à une variable ('Var'), un objet c'est un
'Record' dans lequel on a mis des procédures et des fonctions, 'require'
remplace la clause 'Uses', et enfin un module ressemble fort à une 'Unit'.
Les boutons et autres composants
qui vont avec.
Une fenêtre sans rien qui bouge à l'intérieur, c'est à dire quelque chose d'actif, ce n'est pas très pratique, ni très utile. Nous allons donc lui rajouter un bouton.
***************************************************************************
1-
require 'fox'
2-
include Fox
3-
monApp = FXApp.new
4-
maFenetre = FXMainWindow.new(monApp, "Coucou2",nil,nil,DECOR_ALL,0,0,200,20)
4 bis - monBouton =
FXButton.new(maFenetre, "Cliquez donc
ici !")
5-
monApp.create
6-
maFenetre.show
7-
monApp.run
***************************************************************************
Il suffit de rajouter une instance de bouton en ligne 4bis en lui passant en paramètres 'maFenetre' et l' intitulé du bouton. Et comme de cliquer dessus ne produit aucun effet, il ne reste qu' à rajouter une commande :
4 ter -
monBouton.connect(SEL_COMMAND) {exit}
Même les plus réfractaires à l' anglais auront
compris qu'avec le mot 'exit' , le bouton vous fait tout quitter. Alors
contemplez-le bien , avant de cliquer dessus comme il vous y invite.
![]()
Fig 2 - Coucou2.png
Ouais, mais moi j' ai l'habitude d'avoir une icône
sur mon bouton ! c'est plus joli !
D'accord, d'accord on y va :
***************************************************************************
1- require 'fox'
2- include Fox
3- monApp = FXApp.new("Coucou3", "FoxTest")
4- main = FXMainWindow.new(monApp, "Hello", nil, nil, DECOR_ALL)
# Charge une icône .PNG qui sera attachée au bouton. Notons que le second
# argument de la méthode .new nécessite seulement un flux d'octets (par
# exemple une chaîne); ici nous lisons les octets à partir d'un fichier sur
# le disque.
5- icon = FXPNGIcon.new(monApp, File.open("fox.png", "rb").read)
# On construit le bouton en tant qu'enfant de la fenêtre principale.
6- FXButton.new(main, "&Coucou!\tOllé, FOX c'est super!\nCliquez sur l'icone pour quitter 'monApp'.", icon, monApp, FXApp::ID_QUIT, ICON_UNDER_TEXT|JUSTIFY_BOTTOM)
7- FXTooltip.new(monApp)
8- monApp.create
9- main.show(PLACEMENT_SCREEN)
10- monApp.run
***************************************************************************
Ouh là là, tout a changé. Mais non, pas tant que ça,
et puis nous ne sommes pas là pour rigoler. Voyons les changements :
·
Ligne
3 : on découvre que les paramètres de FXApp.new sont facultatifs. Le premier
désigne le nom de l'application et le second la clef vendeur, c'est à dire le
copyright ou tout ce que vous voulez. Ces deux chaînes sont en relation avec la
partie de la base de registres Windows dédiée à Fox . Nous y reviendrons plus
loin.
· Ligne 4 : là aussi 2 nouveautés : le point d'ancrage et les dimensions de la fenêtre ne sont plus là. La fenêtre va se dimensionner automatiquement en fonction de son ou ses occupants, ici le bouton qui lui-même s'autodimentionnera en fonction des dimensions de l'image. Les deux 'nil' sont là pour signifier qu'il n'y a ni icône ni petite icône, mais par contre une option : DECOR_ALL qui signifie all of the above.
·
Ligne
5 : on charge une image à partir d'un fichier .png. Le paramètre "rb" n'a rien à voir avec l'extension
des fichiers Ruby. Voir plus loin dans "Un peu de Ruby".
·
Ligne
6 : là, on s'accroche :
- 1er paramètre,
on attache le bouton à la fenêtre. Le 'main' de la ligne 4.
- 2ème
paramètre le titre et dans la foulée, suivant le " \t" (de
tabulation), le texte d' une info- bulle.
- 3ème
paramètre le pointeur de l' icône du bouton.
- 4ème
paramètre le pointeur de l'application,
- et 5ème
paramètre la commande liée au bouton, passée ici directement avec l'objet dont
il dépend. Le signe '&' (esperluette pour les gens calés) précédant le 'C'
du mot Coucou, introduit un raccourci clavier pour le bouton, et l'appui
simultané des touches 'CTRL ' et 'c' équivaudra à un clic de souris sur le
bouton.
·
Ligne
7 : C'est ici qu'on fait la bulle, tool tip en anglais. L' info-bulle va
apparaître sous le curseur de la souris, à condition évidemment de laisser
celui-ci immobile une seconde au dessus du bouton.
Avez-vous remarqué qu'autrefois, lorsque les
ordinateurs étaient en mode texte, il y avait des intitulés dans des menus
déroulants (déroutants ?). Puis le mode graphique est arrivé avec son cortège
d'icônes plus ou moins explicites, plutôt moins d'ailleurs puisqu'on a jugé
indispensable de leur donner une explication supplémentaire à l'aide de 26
icônes connues depuis quelques années déjà et qui ont pour nom l'alphabet… Fox
résoud ce problème en rendant possible l'affichage d'une info-bulle explicative
sur chaque composant visuel et/ou par un affichage au bas de l'écran.
·
Ligne
9 : un paramètre de plus et on a la fenêtre au milieu de l'écran. C'est pas
beau ça ?
Le paramètre
en question est déclaré dans FXTopWindow qui a pour héritier FXMainWindow. Voir
annexe 'A' pour les détails.
Et comme résultat nous avons la figure suivante :

Fig 3 - Coucou3.png
A remarquer tout de même que le bouton est en fait l'image toute entière et ce parce que nous n'avons pas donné de dimensions particulières ni à la fenêtre ni au bouton dans leur initialisation.
Bref vous
vous êtes aperçu que certain paramètres sont facultatifs. Bravo, vous avez bien
deviné. La version intégrale se presente comme suit :
FXButton.new(parent, text, icon=nil,
target=nil, selector=0, opts=BUTTON_NORMAL, x=0, y=0, width=0, height=0,
padLeft=DEFAULT_PAD, padRight=DEFAULT_PAD, padTop=DEFAULT_PAD,
padBottom=DEFAULT_PAD)
Les
valeurs des paramètres portés dans la présentation ci-dessus sont des valeurs
par défaut.
DEFAULT_PAD
valant zéro.
Allez ! On va décortiquer tout ça paramètre par paramètre :
1-
'parent'
: C'est le parent. Généralement un cadre, lui-même fils d'un autre cadre ou
d'une fenêtre.
2-
'text'
: Le titre du bouton. En faisant précéder une lettre du caractère"&", on crée un raccourci
clavier. Par exemple "&Cliquez ici" souligne la lettre 'C' sur le
bouton et permet le raccourci 'Ctrl_C'. La tabulation "\t" en FXRuby
permet d'insérer le texte de l' info-bulle d'explication dans la foulée. Une
autre tabulation permet d'afficher une aide différente dans la barre d'état.
3-
'icon'
: Un pointeur vers un dessin.
4- 'target'
: La cible d'un éventuel message.
5-
'selector' : Soit on indique ici le message destiné à être émis par le
bouton, par exemple "FXApp::ID_QUIT"
soit on indique '0' (zéro) et on utilise la méthode 'connect', si par
exemple il y a un traitement supplémentaire à faire. Voir à ce sujet la
description du module Responder2.
6- 'opts' : Les options d'apparence et de
positionnement du bouton. Voir les constantes prédéfinies déclarées dans
FXWindow.
7 et 8 - coordonnées 'x' et 'y' du coin supérieur
gauche.
9 - 'width' : largeur du bouton.
10 - 'height' : hauteur du bouton.
11 à 14 - Comme dans FXMainWindow, il s'agit de la
largeur du passe-partout, largeur variable selon le côté. Le moyen le plus
efficace pour comprendre "comment ça marche", c'est d'essayer.
Ajoutez donc les paramètres d'ancrage et de dimensions dans la fenêtre
principale, avec une valeur nulle, et ceux du passe-partout : …DECOR_ALL,
0,0,0,0,10,10,10,10). Si vous avez bien suivi vous obtenez un passe-partout de
10 pixels de large. Nous pouvons faire de même avec le passe-partout de 'main',
les deux étant indépendants, les valeurs s'ajoutant entre elles.
Vous avez
sans aucun doute compris que les commentaires en Ruby se font à la suite d'un
caractère '#'. C'est exact. Il y a une autre façon de faire, pour les blocs de
texte qui comportent plusieurs lignes, qui consiste à commencer une ligne par
'=begin' et terminer le texte par une ligne '=end'.
En dehors
de la tabulation il existe d'autres substitutions précédées du caractère '\'
dans une chaîne de caractères délimitée par l'apostrophe double ("chaîne").
Ils viennent en majorité d'un héritage de l'utilisation autrefois des 20
premiers codes ASCII sur télétype :
\a : la sonnerie (Bell)
\b : retour arrière (Backspace)
\e : échappement (Escape)
\f : (Formfeed)
\n : changement de ligne (Newline ou
LineFeed)
\r : retour chariot (Return)
\s : espace (Space)
\t : tabulation (Tab)
\v : tabulation verticale (Vertical tab)
\nnn : nnn en octal
\xnn : nn en hexadécimal
\cx
: contrôle-x
\C-x : contrôle-x
\x : x
#{expr}
: valeur de expr
A noter aussi que pour afficher un caractère de contrôle dans une chaîne, il faut le faire précéder du caractère '\'. Ex : ' " ' qui délimite une chaîne, nécessite pour être imprimé dans une chaîne d'être comme ceci : ' \" ' . Et '\\' affiche donc '\'.
Il existe
aussi une syntaxe particulière en entrée pour les chaînes, tableaux et
expressions rationnelles. Tous ces littéraux débutent par le signe '%' suivi
d'un simple caractère identifiant le type de littéral.
%q chaîne entre apostrophes simples (').
%Q chaîne entre apostrophes doubles
(").
%w tableau de chaînes.
%r expression rationnelle (regular
expression).
%x ligne de commande (Shell command)
Dans la
lecture du fichier .PNG nous avons vu qu'il y avait passage de deux paramètres
dans 'file.open(…)'. Le premier est le nom du fichier, le deuxième
("rb") un des modes d'ouverture décrit dans le tableau ci-dessous :
|
Mode |
Signification |
|
``r'' |
En lecture seulement, démarre au début du fichier (mode par défaut). |
|
``r+'' |
En lecture / écriture, démarre au début du fichier. |
|
``w'' |
En écriture seulement, tronque le fichier existant à une longueur de zéro ou bien crée un nouveau fichier pour écrire. |
|
``w+'' |
En lecture/écriture, tronque le fichier existant à une longueur de zéro ou bien crée un nouveau fichier pour lire et écrire. |
|
``a'' |
En écriture seulement, démarre à la fin du fichier existant, sinon crée un nouveau fichier pour écire. |
|
``a+'' |
En lecture/écriture, démarre à la fin du fichier existant, sinon crée un nouveau fichier pour lire/écire. |
|
``b'' |
(DOS/Windows seulement) Mode fichier binaire (peut être joint à un des modes ci-dessus). |
Pour se rendre compte de l'aspect et du fonctionnement des divers boutons, rien de tel qu'un (bon) exemple. Alors allons-y avec "boutons.rbw" dont le listing suit :
***************************************************************************
require 'fox'
include Fox
class BoutonsWindow < FXMainWindow
def initialize(app)
# Appelle la classe de base
super(app, "Bouton Test", nil, nil, DECOR_ALL, 100, 100, 0, 0)
# Crée le gestionnaire de bulles
FXTooltip.new(self.getApp())
# Barre de statut
statusbar = FXStatusbar.new(self,
LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER)
statusbar.statusline.normalText="Prêt"
# Contrôles à droite
controls = FXVerticalFrame.new(self,
LAYOUT_SIDE_RIGHT|LAYOUT_FILL_Y|PACK_UNIFORM_WIDTH)
# Séparateur vertical
FXVerticalSeparator.new(self,
LAYOUT_SIDE_RIGHT|LAYOUT_FILL_Y|SEPARATOR_GROOVE)
# Contenant
contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_LEFT|FRAME_NONE|LAYOUT_FILL_X|LAYOUT_FILL_Y|PACK_UNIFORM_WIDTH,
0, 0, 0, 0, 20, 20, 20, 20)
# Construit une image en la chargeant depuis le disque
bigpenguin = loadIcon("bigpenguin.png")
# Le bouton
@button = FXButton.new(contents,
"&Ceci est un label multi-ligne\nun bouton pour montrer\n" +
"les possibilités de l'objet bouton.\t" +
"C'est aussi une info-bulle\n(qui peut aussi être multi-ligne).\t" +
"Ici un message d'aide pour la ligne de statut.",
bigpenguin,nil, 0,
FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y|LAYOUT_FIX_WIDTH|
LAYOUT_FIX_HEIGHT,0, 0, 300, 200)
checkButton = FXCheckButton.new(controls, "Style Toolbar \tCool style boutons \"poppy\"")
checkButton.connect(SEL_COMMAND) { |sender, sel, checked|
if checked
@button.buttonStyle |= BUTTON_TOOLBAR
@button.frameStyle = FRAME_RAISED
else
@button.buttonStyle &= ~BUTTON_TOOLBAR
@button.frameStyle = FRAME_RAISED|FRAME_THICK
end
}
group1 = FXGroupBox.new(controls, "Placement Horizontal",
GROUPBOX_TITLE_CENTER|FRAME_RIDGE)
FXRadioButton.new(group1, "Avant le texte").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition|ICON_BEFORE_TEXT) & ~ICON_AFTER_TEXT
}
FXRadioButton.new(group1, "Après le texte").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition|ICON_AFTER_TEXT) & ~ICON_BEFORE_TEXT
}
FXRadioButton.new(group1, "Centré").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition & ~ICON_AFTER_TEXT) & ~ICON_BEFORE_TEXT
}
group2 = FXGroupBox.new(controls, "Placement Vertical",
GROUPBOX_TITLE_CENTER|FRAME_RIDGE)
FXRadioButton.new(group2, "Au dessus du texte").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition|ICON_ABOVE_TEXT) & ~ICON_BELOW_TEXT
}
FXRadioButton.new(group2, "Sous le texte").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition|ICON_BELOW_TEXT) & ~ICON_ABOVE_TEXT
}
FXRadioButton.new(group2, "Centré").connect(SEL_COMMAND) {
@button.iconPosition =
(@button.iconPosition & ~ICON_ABOVE_TEXT) & ~ICON_BELOW_TEXT
}
group3 = FXGroupBox.new(controls, "Justification horizontale ",
GROUPBOX_TITLE_CENTER|FRAME_RIDGE)
FXRadioButton.new(group3, "Centré").connect(SEL_COMMAND) {
@button.justify &= ~JUSTIFY_HZ_APART
}
FXRadioButton.new(group3, "Gauche").connect(SEL_COMMAND) {
@button.justify = (@button.justify & ~JUSTIFY_HZ_APART) | JUSTIFY_LEFT
}
FXRadioButton.new(group3, "Droite").connect(SEL_COMMAND) {
@button.justify = (@button.justify & ~JUSTIFY_HZ_APART) | JUSTIFY_RIGHT
}
FXRadioButton.new(group3, "A part").connect(SEL_COMMAND) {
@button.justify |= JUSTIFY_HZ_APART
}
group4 = FXGroupBox.new(controls, "Justification verticale",
GROUPBOX_TITLE_CENTER|FRAME_RIDGE)
FXRadioButton.new(group4, "Centré").connect(SEL_COMMAND) {
@button.justify &= ~JUSTIFY_VT_APART
}
FXRadioButton.new(group4, "Haut").connect(SEL_COMMAND) {
@button.justify = (@button.justify & ~JUSTIFY_VT_APART) | JUSTIFY_TOP
}
FXRadioButton.new(group4, "Bas").connect(SEL_COMMAND) {
@button.justify = (@button.justify & ~JUSTIFY_VT_APART) | JUSTIFY_BOTTOM
}
FXRadioButton.new(group4, "A part").connect(SEL_COMMAND, method(:onCmdJustVerApart))
quitButton = FXButton.new(controls, "&Quitter", nil, nil, 0, FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X)
quitButton.connect(SEL_COMMAND) {
getApp().exit(0)
}
end
def onCmdJustVerApart(sender, sel, ptr)
@button.justify |= JUSTIFY_VT_APART
end
def loadIcon(filename)
FXPNGIcon.new(getApp(), File.open(filename, "rb").read)
end
def create
super
show(PLACEMENT_SCREEN)
end
end # fin d'initialize
def run
# Construit une application
application = FXApp.new("Bouton", "FoxTest")
# Construit la fenêtre principale
BoutonsWindow.new(application)
# Crée l'application
application.create
# Lance l'application
application.run
end
run
***************************************************************************
Décortiquons un peu tout cela. L'exemple, dont on voit le résultat ci-après, montre une fenêtre partagée verticalement en deux parties. Celle de gauche contient un gros bouton test qui va changer d'allure suivant l'état des boutons radios situés dans la partie droite. En un seul exemple nous allons donc voir toutes (ou presque) les possibilités des boutons sous FXRuby.
·
On
crée tout d'abord une nouvelle classe qui descend de 'FXMainWindow'. Toute fenêtre un tant soit peu compliquée
est plus facilement exploitable en dérivant d'une classe ancêtre.
·
La
méthode 'initialize' est une méthode qui est appelée automatiquement après l'appel de 'new', avec les paramètres
éventuels du dit 'new'. Elle permet donc d'y inclure tous les traitements que
l'on souhaite, ici pratiquement toute la description de la fenêtre. Cette
méthode doit être redéfinie dans les sous-classes.
· A l'intérieur du corps d'une méthode, un appel à 'super' agit exactement comme un appel à la méthode portant le même nom de l'objet ancêtre. S'il n'y a pas de paramètres fournis, ce seront les valeurs par défaut des paramètres de l'ancêtre qui seront utilisés.
super [ ( [
param
]*
[*array
] ) ] [
block
]
·
FXToolTip.new crée le gestionnaire des info-bulles.
Remarquons le passage du paramètre : 'self' représente la classe elle-même et
renvoie donc le pointeur de l'application par la méthode 'getApp'.
·
Sont
ensuite créées les variables représentant les divers composants nécessaires à
la construction de notre fenêtre exemple. A savoir dans l'ordre d'apparition
dans le programme :
Statusbar : la ligne de statut munie d'un coin de dragage (STATUSBAR_WITH_DRAGCORNER).
Après sa création, on modifie le texte par
défaut qui s'affiche dans la barre de statut. En lieu et place du
"Ready" anglais, nous mettons le "Prêt" français.
Controls : le cadre à droite contenant les boutons de réglage.
Contents : le cadre à
gauche contenant le gros bouton de test.
bigpenguin : l'icône du gros
bouton. Ici il est fait appel à la méthode 'loadIcon()'. Pour une icône à
charger, cela ne nécessite pas en fait de faire une méthode spécifique, mais
comme nous le verrons plus loin, s'il y en a plusieurs, la création d'une telle
méthode évite la répétition de code.
@button : le gros bouton de test(voir plus loin "Un
peu de Ruby").
Checkbutton : la case à cocher
indiquant si le gros bouton doit avoir une apparence normale ou bien
l'apparence d'un bouton de barre d'outils c'est à dire sans cadre apparent, le
cadre devenant visible lorsque le curseur de la souris passe dessus. A noter la
condition 'if… else… end' dans la
méthode 'connect'. A remarquer aussi le raccourci d'affectation (voir aussi
plus loin "Un peu de Ruby") :
@button.buttonStyle |=
BUTTON_TOOLBAR
qui
équivaut à @button.buttonStyle =
@button.buttonStyle |BUTTON_TOOLBAR
On voit pour la première fois l'utilisation de fonctions logiques et de comparaison de nombres bit à bit :
& pour le "ET" logique .
| pour le
"OU" logique.
~ pour obtenir le complément d'un nombre.
Group1,
group2, group3, group4 : les boîtes de
groupe, FXGroupBox, englobant les différents boutons radio, FXRadioButton.
QuitButton : le
bouton habituel pour quitter l'application.
·
Remarquez
dans le groupe 4, la puissance des raccourcis avec Ruby dans la création du
dernier bouton :
FXRadioButton.new(group4,
"Apart").connect(SEL_COMMAND,
method(:onCmdJustVerApart))
Dans la
foulée on fait "Objet.new.connect". Le passage de paramètres se faisant
par
l'appel d'une méthode déclarée
plus loin dans la définition de la classe.
·
Quelques
précisions sur la façon de faire passer les messages :
La
plupart des objets FOX envoient des messages qui ont quatre éléments importants
:
1.
L'expéditeur
du message est l'objet qui envoie le message. Dans ce cas l'instance de
FXButton est l'expéditeur 'sender'.
2.
Le
type de message est une constante entière prédéfinie qui indique quelle sorte
d'événement a eu lieu (c'est à dire le but de l'envoi du message). Dans ce cas
le type du message est 'SEL_COMMAND', qui indique que la commande associée à ce
composant doit être invoquée.
3.
L'identifiant
du message est une autre constante entière qui est utilisée pour différencier
les messages de même type. Par exemple le message qui rend visible une fenêtre
est un message 'SEL_COMMAND' avec l'identifiant FXWindow::ID_SHOW (où ID_SHOW est une constante définie dans la
classe FXWindow ). Un identifiant différent,
FXWindow::ID_HIDE, appelle une instance de FXWindow pour la rendre
invisible.
4.
La
donnée du message est un objet contenant une information spécifique. Si
l'expéditeur n'est pas utile, ni les données, on peut alors utiliser le
raccourci que nous avons vu dans l'exemple en début de chapitre.
FXRuby utilise une syntaxe inspiré de GTK (l'autre
interface graphique de Ruby). On peut attacher, comme nous venons de le voir
ci-dessus, un bloc à un composant en utilisant la méthode 'connect'. Par
exemple :
unBouton = FXButton.new(parent, "Cliquer
ici")
unBouton.connect(SEL_COMMAND) { |sender,
sel, ptr|
puts "Ouf !"
}
Une autre forme de la méthode 'connect' utilise soit une méthode ('method') soit une instance de procédure ('proc') comme second argument (sans lier un bloc), par exemple :
def pousse(sender, sel, ptr)
puts "Ouf !"
end
unBouton = FXButton.new(parent, "Cliquer
ici")
unBouton.connect(SEL_COMMAND,
method(:pousse))
Et
enfin la forme raccourcie déjà vue :
unBouton = FXButton.new(parent, "Cliquer
ici")
unBouton.connect(SEL_COMMAND) { puts
"Ouf !" }
Tout cela rappellera aux anciens programmeurs Pascal les déclarations de 'TurboVision' en beaucoup plus simples puisqu' il n'est nul besoin, même si cela est prévu, d'indiquer les positions du composant dans la fenêtre parent, FOX se chargeant de l'arrangement lui-même. L'astuce consiste à placer des cadres verticaux ou horizontaux dans les fenêtres, ceux-ci allant se placer automatiquement en fonction des paramètres.
-
1er
cadre vertical à droite (LAYOUT_SIDE_RIGHT). Il occupe toute la place
disponible en hauteur (LAYOUT_FILL_Y).
-
2ème
cadre à gauche (LAYOUT_SIDE_LEFT). Il occupe toute la place restante en largeur
comme en hauteur (LAYOUT_FILL_X | LAYOUT_FILL_Y)
Les boîtes à grouper vont se positionner automatiquement dans l'ordre de leur déclaration. Remarquons qu'elles n'ont pas de dimensions précisées. Le tout va décider de la largeur et de la hauteur du cadre parent. Il en est de même du gros bouton qu'on décide de centrer en largeur et en hauteur (LAYOUT_CENTER_X | LAYOUT_CENTER_Y) et de dimensions fixes de 300 X 200 pixels.Il impose donc une largeur au cadre de gauche qui se retrouve avec une hauteur imposée par le cadre de droite !
Et
voici ce que l'on obtient à l'écran :

Fig 2 - boutons.rbw
Remarquez la bulle d'aide affichée en surimpression
du bouton, et le texte d'aide dans la ligne de statut.
Nous avons donc découvert dans cet exemple les
composants suivants :
FXFrame.new(parent,
opts=FRAME_NORMAL, x=0, y=0, width=0, height=0, padLeft=DEFAULT_PAD,padRight=DEFAULT_PAD,
padTop=DEFAULT_PAD, padBottom=DEFAULT_PAD) {|theFrame| ...}
Les cadres servent à remplir les fenêtres et à
héberger à leur tour d'autres composants visuels.
Il y en a pour tous les goûts, et cela se décide
dans le paramètre 'opts' qui se décline en huit possibilités du 'sans cadre' au
'cadre normal'. Voir la liste en annexe dans FXWindow.
FXHorizontalFrame et FXVerticalFrame sont des cadres horizontaux (resp. verticaux) qui
placent les fenêtres enfants horizontalement (verticalement) selon les renseignements apportés par les
fenêtres enfants. Si ces composants ne possèdent pas de cible ('target') ni de
sélecteur, ils ont en plus des paramètres de dimensionnement, deux paramètres
indiquant la distance minimale souhaitée entre les composants enfants déclarés
dans la méthode 'new' ci-dessous par 'hSpacing' et 'vSpacing'. La valeur par
défaut est égale à '0'.
FXHorizontalFrame.new(parent, opts=0, x=0, y=0,
width=0, height=0, padLeft=DEFAULT_SPACING, padRight=DEFAULT_SPACING,
padTop=DEFAULT_SPACING,
padBottom=DEFAULT_SPACING, hSpacing=DEFAULT_SPACING, vSpacing=DEFAULT_SPACING)
FXVerticalSeparator et FXHorizontalSeparator sont, comme leur nom le laisse deviner, de
simples traits de séparation. Les paramètres de positionnement, de largeur et
hauteur sont à zéro, par défaut. Par défaut aussi, le nombre de pixels minimum
du tracé : 'pl', 'pr', 'pt',, 'pb' signifiants "left",
"right", "top" et "bottom". Dans le séparateur
vertical ce sont les pixels de haut en bas qui ont un minimum de 1 pixel.
FXVerticalSeparator.new (parent,
opts=SEPARATOR_GROOVE|LAYOUT_FILL_Y, x=0, y=0, w=0, h=0, pl=0, pr=0, pt=1,
pb=1)
FXGroupBox.new(parent, text,
opts=GROUPBOX_NORMAL, x=0, y=0, width=0,height=0, padLeft=DEFAULT_SPACING, padRight=DEFAULT_SPACING,
padTop=DEFAULT_SPACING, padBottom=DEFAULT_SPACING,
hSpacing=DEFAULT_SPACING,
vSpacing=DEFAULT_SPACING)
FXGroupBox produit un cadre surélevé ou en puits autour d'un groupe de composants visuels. Généralement un titre est placé à gauche pour clarifier les choses. Des radios boutons placés à l'intérieur d'une boîte à grouper ont automatiquement le comportement attendu, c'est à dire qu'un seul d'entre eux est coché à la fois. L'option 'opts' permet de justifier le titre à gauche, à droite ou au milieu. Voir détails en annexe.
FXCheckButton.new(parent, text, target=nil,
selector=0,
opts=CHECKBUTTON_NORMAL, x=0, y=0,width=0,
height=0,
padLeft=DEFAULT_PAD, padRight=DEFAULT_PAD,
padTop=DEFAULT_PAD, padBottom=DEFAULT_PAD)
Pas de surprises, les paramètres sont les mêmes à
part celui des options qui devient CHECKBUTTON_NORMAL, ce qui paraît tout de
même logique.
On devine facilement que les boutons-radio sont identiques aux cases à cocher à ceci près qu'ils sont reliés ensemble afin qu'un seul d'entre eux soit coché à un instant donné. Il suffit pour cela de les regrouper dans une boîte de groupe FXGroupBox.
FXRadioButton.new(parent, text, target=nil,
selector=0, opts=RADIOBUTTON_NORMAL,
x=0, y=0, width=0, height=0,
padLeft=DEFAULT_PAD, padRight=DEFAULT_PAD,
padTop=DEFAULT_PAD,
padBottom=DEFAULT_PAD)
Les boutons-bascule sont des boutons à deux états,
qui permettent de mettre un bouton au lieu de deux, lorsqu'ils ne peuvent être
ensemble sans que l'un d'eux soit désactivé. Par exemple si l'on veut ouvrir un
fichier, il n'est pas nécessaire d'avoir le bouton de fermeture.
FXToggleButton.new(p, text1, text2, icon1=nil,
icon2=nil, tgt=nil, sel=0, opts=TOGGLEBUTTON_NORMAL, x=0, y=0, w=0, h=0,
pl=DEFAULT_PAD, pr=DEFAULT_PAD,
pt=DEFAULT_PAD, pb=DEFAULT_PAD)
A part le fait que les textes et les icônes sont en double, le reste des paramètres est le même que dans un bouton normal. Voir les détails en annexe.
FXArrowButton.new(parent, target=nil,
selector=0, opts=ARROW_NORMAL, x=0, y=0, width=0, height=0,
padLeft=DEFAULT_PAD, padRight=DEFAULT_PAD, padTop=DEFAULT_PAD,
padBottom=DEFAULT_PAD)
Ils ont l'avantage d'être prévus. Hormis cela ils
n'ont rien de bien particulier et fonctionnent comme des boutons normaux munis
d'une icône.
La figure 3 montre quelques exemples de boutons, avec de gauche à droite un bouton normal, un bouton flèche, un bouton style barre à outils (sans le curseur souris dessus), un bouton normal désactivé, un bouton normal sans cadre et enfin un bouton-bascule.

Fig. 3 stylbout.png
FXToolTip.new(app, opts=TOOLTIP_NORMAL,
x=0, y=0, width=0, height=0)
Rien de bien spécial si ce n'est l'option éventuelle pour rendre l'info-bulle visible, tant que le bouton est survolé par le curseur souris ou bien dépendant de la longueur du texte, pour en permettre plus facilement la lecture. Voir détails en annexe.
Située en bas de la fenêtre, elle a pour fonction principale d'afficher le texte d'aide supplémentaire des info-bulles..
Sa déclaration est des plus simples :
FXStatusBar.new(p, opts=0, x=0, y=0, w=0, h=0, pl=3, pr=3, pt=2, pb=2, hs=4, vs=0)
Remarquons toutefois l'initialisation du passe-partout à une valeur non-nulle (2 et 3 pixels), ainsi que l'espace entre composants enfants (4 pixels).
Par ses attributs, la barre de statut nous donne accès à :
- FXDragCorner qui est le "coin de dragage", visible ou non, et dont on peut modifier la couleur.
- FXStatusLine qui est la ligne de statut proprement
dite, c'est à dire celle qui contient le texte à afficher. Dans l'exemple ci
dessus, nous modifions le texte de repos pour qu'il soit en français et nous
pouvons aussi changer, de la même façon, la couleur et la fonte. Voir les
attributs en Annexe A.
Les
types de variables avec Ruby sont au nombre de 5 :
-
variables
globales : elles débutent par le signe '$'.
-
variables locales
: elles débutent par une minuscule.
-
variables d'instance
: elles débutent par un '@'.
-
variables de classes
: elles débutent par un double arobase '@@'.
-
constantes : elles débutent par une majuscule.
Cette façon de faire permet donc de reconnaître
instantanément à quel type de variable on a à faire.
La portée des
variables.
On entend par là de quel endroit on peut les appeler
pour les lire ou bien y écrire.
-
Les
variables globales sont, comme leur nom l'indique, accessibles de n'importe
quel endroit d'un programme. Elles valent 'nil' si non initialisées.
-
Les
variables locales ont comme vision l'endroit où elles ont été déclarées. Ce qui
peut être dans une 'proc{...}', dans une méthode 'def...end,' une classe
'class...end'.
-
Les
variables d'instance ont une portée limitée aux objets qui se réfèrent à
'self'. Elles ont la valeur 'nil' jusqu'à leur initialisation. Elles ne sont
accessibles de l'extérieur que par des accesseurs. Chaque instance d'un objet
possède les mêmes variables d'instance et elles peuvent avoir des valeurs différentes.
-
Les
variables de classe sont uniques à l'intérieur d'une classe. Elles peuvent être
modifiées, mais sont communes à toutes les instances de la classe.
-
Les
constantes doivent bien sûr être initialisées et sont accessibles en dehors de
la classe.
Dans
les paramètres on remarque le signe '|'. Il s'agit du 'ou' de comparaison bit à
bit.
Voici
la liste des autres opérateurs de Ruby classés par ordre de précédence :
|
Méthode |
Opérateur |
Description |
|
OUI |
[ ] [ ]= |
Référence d'éléments, ensemble d'éléments |
|
OUI |
** |
Exponentiation |
|
OUI |
! ~ + -- |
Négation, complément, plus et moins unaire (les noms de méthodes pour les deux derniers sont +@ and -@) |
|
OUI |
* / % |
Multiplication, division, et modulo |
|
OUI |
+ -- |
Plus et moins |
|
OUI |
>> << |
Décalage de bits droit et gauche |
|
OUI |
& |
'Et' bit à bit |
|
OUI |
^ | |
`ou' exclusif et `ou' régulier bit à bit |
|
OUI |
<= < > >= |
Operateurs de comparaison, inf. ou égal, inf, sup., sup. ou égal. |
|
OUI |
<=> == === != =~
!~ |
Retourne '–1' si l'argument à gauche est <= à celui de droite, '0' si égalité, '1' si >=. Teste l'égalité. === teste l'égalité sans 'when'ou 'case'. Expression rationnelle. Retourne la position de la première occurrence dans une chaîne. (!= et !~ ne peuvent être définis comme méthodes) |
|
|
&& |
'ET' logique |
|
|
|| |
'OU' logique |
|
|
.. ... |
Intervalle (inclusif et exclusif) |
|
|
? : |
'if-then-else' ternaire |
|
|
= 0%= 0{ /= 0--= 0+= 0|= &= 0>>= 0<<= 0*= 0&&= 0||= 0**= |
Assignement |
|
|
Defined? |
Teste si le symbole est defini |
|
|
Not |
Négation logique |
|
|
or and |
Composition logique (ou, et) |
|
|
if unless while until |
Expressions conditionnelles et boucles (si, si non, tant que, jusqu'à ) |
|
|
Begin/end |
Bloc (partie de code) |
La colonne "Méthode" indique si
l'opérateur est une méthode. Rappelons que tout (ou presque) dans Ruby est
objet et possède donc des méthodes.
Notons enfin le raccourci syntaxique d'affectation
qui est fait du signe '=' précédé de l'opérateur idoine :
'variable +=
constante ' équivaut à 'variable = variable + constante'.
Les opérateurs concernés sont : +
, - , * , / , **, %, <<,>>,
&, |, ^, &&, ||.
Les autres langages ont des fonctions, des
procédures, des méthodes ou des routines mais en Ruby il n'y a que des méthodes
qui retournent une valeur.
Une méthode est définie en utilisant les mots clé
'def' et 'end'. Les noms de méthodes doivent débuter par une lettre minuscule.
On peut bien entendu déclarer des paramètres dans une méthode et même y
affecter des valeurs par défaut :
Ex:
def
mamethode # sans
paramètre.
# traitement
end
def mamethode (arg1, arg2, arg3) #
3 paramètres
# traitement
end
def mamethode (arg1="Jean",
arg2="Pierre", arg3="Paul") # 3 paramètres
initialisés
# traitement
end
La valeur de retour d'une méthode est la valeur de
la dernière expression calculée.
Cependant il existe une expression 'return' qui fait
quitter la méthode avec la valeur qui lui est passée en paramètre, par exemple
'return 1' retourne la valeur '1', ou 'nil' s'il n'y a pas de paramètre, ou
bien encore un tableau s'il y a plusieurs paramètres.
Les noms des méthodes qui fonctionnent comme des
questions sont souvent terminés par un '?'. ( ex. : '.toto?'). Les noms de
méthodes "dangereuses" ou qui modifient le destinataire peuvent être
terminés par un '!' (ex. : '.titi!').
Par
exemple, si a = ['chat','chien'] et b = ['chat','chien']
a.eql?(b) è true (teste la valeur)
a.equal?(b) è false (teste l'identifiant 'ID')
Par exemple dans la classe 'string', la méthode
'.chop' enlève le dernier caractère d'une chaîne et retourne une nouvelle
chaîne. Tandis que '.chop!' modifie directement la chaîne passée en paramètre
et retourne 'nil' si la chaîne est vide. Par exemple :
"string\n".chop è "string"
"string".chop è "strin"
"x".chop.chop è ""
"".chop è ""
tandis
que "".chop! è nil
Peut-être plus facile à comprendre : la méthode
'upcase' transforme les minuscules en majuscules
ch1="truc"
ch2=ch1.upcase
donne ch1="truc" et ch2="TRUC"
mais ch2=ch1.upcase!
donne ch1="TRUC" et ch2="TRUC".
On ne
devait pas savoir comment appeler une partie de l'écran sur laquelle on peut
soit dessiner, soit afficher une image. Le mot feuille étant déjà pris par les
tableurs, pour une fois on n'en a pas inventé un nouveau et le mot connu
surtout dans le monde de la tapisserie est alors apparu.
Contrairement au composant bouton, le canevas subit
plutôt qu'il opère.
FXCanvas.new(parent, target=nil,
selector=0, opts=FRAME_NORMAL, x=0, y=0, width=0, height=0)
Ce composant est l'occasion d'apprendre à dessiner
sur un canevas et de voir comment fonctionnent les messages sous Fox. Rien de
tel qu'un exemple simple et son décortiquage pour expliquer les divers
mécanismes.
Voici donc 'Gribouille', un tableau blanc à l'écran
pour dessiner à l'aide de la souris.
***************************************************************************
require 'fox'
require 'fox/colors'
include Fox
class GribouilleWindow < FXMainWindow
def initialize(app)
# Initialise en premier la classe de base.
super(app, "Gribouillage", nil, nil, DECOR_ALL,
0, 0, 320, 200)
# Construction d'un cadre horizontal pour contenir la fenêtre principale.
@contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y, 0, 0, 0, 0, 0, 0, 0, 0)
# Le panneau de gauche contient le canevas.
@canvasFrame = FXVerticalFrame.new(@contents,
FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,
0, 0, 0, 0, 10, 10, 10, 10)
# Place une étiquette au-dessus du canevas.
FXLabel.new(@canvasFrame, "Cadre de canevas", nil,
JUSTIFY_CENTER_X|LAYOUT_FILL_X)
# Ligne horizontale de séparation.
FXHorizontalSeparator.new(@canvasFrame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
# Trace le canevas.
@canvas = FXCanvas.new(@canvasFrame, nil, 0, (FRAME_SUNKEN|FRAME_THICK|
LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT))
@canvas.connect(SEL_PAINT) do |sender, sel, event|
FXDCWindow.new(@canvas, event) do |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
end
end
@canvas.connect(SEL_LEFTBUTTONPRESS) do
@canvas.grab
@mouseDown = true
end
@canvas.connect(SEL_MOTION) do |sender, sel, event|
if @mouseDown
# Récupère un contexte de périphérique pour le canevas.
dc = FXDCWindow.new(@canvas)
# Indique la couleur de fond.
dc.foreground = @couleurTrace
# Trace une ligne du point précédent au point actuel de la souris.
if @mirrorMode.value
cW = @canvas.width
cH = @canvas.height
dc.drawLine(cW-event.last_x, event.last_y,
cW-event.win_x, event.win_y)
dc.drawLine(event.last_x, cH-event.last_y,
event.win_x, cH-event.win_y)
dc.drawLine(cW-event.last_x, cH-event.last_y,
cW-event.win_x, cH-event.win_y)
end
dc.drawLine(event.last_x, event.last_y, event.win_x, event.win_y)
# On a tracé quelque chose, le canevas est donc sale.
@dirty = true
# Libère le contexte.
dc.end
end
end
@canvas.connect(SEL_LEFTBUTTONRELEASE) do |sender, sel, event|
@canvas.ungrab
if @mouseDown
# Récupère un contexte de périphérique pour le canevas.
dc = FXDCWindow.new(@canvas)
# Indique la couleur de fond.
dc.foreground = @couleurTrace
# Trace une ligne du point précédent au point actuel de la souris.
dc.drawLine(event.last_x, event.last_y, event.win_x, event.win_y)
# On a tracé quelque chose, le canevas est donc sale.
@sale = true
# Le bouton souris est relâché.
@mouseDown = false
# Libère le contexte.
dc.end
end
end
# Panneau de droite pour les boutons.
@buttonFrame = FXVerticalFrame.new(@contents,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,
0, 0, 0, 0, 10, 10, 10, 10)
# Etiquette au-dessus des boutons.
FXLabel.new(@buttonFrame, "Cadre boutons", nil,
JUSTIFY_CENTER_X|LAYOUT_FILL_X)
# Ligne horizontale de séparation.
FXHorizontalSeparator.new(@buttonFrame,
SEPARATOR_RIDGE|LAYOUT_FILL_X)
# Active ou désactive le mode miroir.
@mirrorMode = FXDataTarget.new(false)
FXCheckButton.new(@buttonFrame, "Miroir", @mirrorMode, FXDataTarget::ID_VALUE, CHECKBUTTON_NORMAL|LAYOUT_FILL_X)
# Bouton pour nettoyer le canevas.
clearButton = FXButton.new(@buttonFrame, "&Effacer", nil, nil, 0,
FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
0, 0, 0, 0, 10, 10, 5, 5)
clearButton.connect(SEL_COMMAND) do
FXDCWindow.new(@canvas) do |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(0, 0, @canvas.width, @canvas.height)
@sale = false
end
end
clearButton.connect(SEL_UPDATE) do |sender, sel, ptr|
# Cette procédure contient le message de mise à jour envoyé par le bouton d'effacement
# à sa cible. Chaque composant visuel de Fox reçoit un message (SEL-UPDATE) durant la phase de temps mort,
# lui demandant de se mettre à jour lui-même. Par exemple un bouton peut
# être actif ou inactif suivant l'état de l'application.
# Dans ce cas nous désactivons l'expéditeur (le bouton effacer) quand le canevas a été
# nettoyé, et le réactivons quand il a été utilisé (il est sale).
message = @sale ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
end
# Bouton pour quitter.
FXButton.new(@buttonFrame, "&Quitter", nil, app, FXApp::ID_QUIT,
FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
0, 0, 0, 0, 10, 10, 5, 5)
# Initialisation des autres variables.
@couleurTrace = FXColor::Red
@mouseDown = false
@sale = false
end
# Crée et montre la fenêtre principale.
def create
super # Cré la fenêtre.
show(PLACEMENT_SCREEN) # Montre la fenêtre.
end
end
if __FILE__ == $0
# Construit l'objet 'application'.
application = FXApp.new('Gribouillage', 'FoxTest')
# Construit la fenêtre principale.
scribble = GribouilleWindow.new(application)
# Crée l'application.
application.create
# Exécute l'application.
application.run
end
***************************************************************************
La partie
'initialize' ressemble en grande partie à celle du programme
"boutons.rbw". Le fait nouveau se situe dans la transmission des
commandes de la méthode 'connect' du canevas.
En effet,
lors du clic gauche de la souris sur le canevas, la variable @mousedown est
positionnée à 'true', et le canevas en position de saisie(méthode 'grab').
Si la
souris glisse, la commande change, et une ligne est traçée.
Enfin, lors
du relâchement de la souris, on trace le dernier trait, et on remet tout dans
l'ordre.
Remarquons
le mode miroir qui, s'il est en action, recopie simultanément le dessin en
cours de tracé symétriquement par rapport à l'axe des 'x', l'axe des 'y' et le
centre du canevas. Donc, à partir d'un tracé on en obtient quatre.
Notons l'initialisation de la variable 'message'
dans la méthode du bouton d'effacement :
Il s'agit ici de la version "rapide", appelée opérateur ternaire, de "if.. then .. else":
Expression-boooléenne ? expr1 : expr2
Retourne
expr1 si expression-booléenne est vrai,
sinon expr2.
Dans la création de l'application par la méthode 'new', nous retrouvons ces mystérieux paramètres passés sous forme de chaînes. Il a été dit précédemment qu'ils avaient une relation avec la base de registres de Window. En effet, FXRuby construit automatiquement une base de données d'initialisation de tous ses programmes. Pour retrouver ces données il faut appeler tout d'abord la base de registres en faisant 'Démarrer', puis 'Exécuter' et entrer 'Regedit' dans la boîte de dialogue. Une fois dans la base de registres choisir 'HKEY_CURRENT_USER' et faire descendre l'arborescence. Cliquer sur 'Software' et vous verrez apparaître le répertoire 'FoxTest' cité dans les exemples précédents. Chaque essai comportant la clef 'FoxTest' aura un sous-répertoire dans le répertoire 'FoxTest', portant le nom que vous aurez donné à votre application. Si vous faites des essais, par exemple d'aspect, avec les programmes exemples, pensez à détruire les fichiers relatifs à l'initialisation sinon vous risquez d'avoir des surprises et penser que votre programmation est inadéquate…
Bon ce n'est pas encore PaintShop Pro, d'accord,
mais en si peu de lignes ce n'est tout de même pas mal, non ?
Et
voilà le résultat :

Fig 3 . Gribouille.rbw
Après la méthode 'connect' vous avez sans doute
remarqué un 'do' mystérieux suivi d'un bloc de code. Les blocs de code peuvent
être associés à des appels de méthodes, et implémenter des itérateurs.
Les blocs de code sont simplement du code compris entre accolades '{', '}' ou bien entre
un 'do' …'end', comme vous avez pu le voir dans les exemples précédents, où
l'une ou l'autre version a été employée.
{puts "Coucou"} # ceci est un bloc
do #
objet.faitcela #
end #
Une fois qu'un bloc est créé on peut l'associer à
l'appel d'une méthode. Cette méthode peut faire appel au bloc passé en
paramètre, une ou plusieurs fois en utilisant le mot clé 'yield'.
Par exemple :
Def appelBloc
yield
yield
end
appelBloc {puts "Coucou"} # 'puts ' écrit ce qui le suit entre guillemets sur le
périphérique
# de sortie.
produit
: Coucou
Coucou
C'est comme si on passait le bloc { puts
"Coucou"} en paramètre à la méthode 'appelBloc'.
Le bloc peut débuter par une liste d'arguments entre des barres verticales '|' :
def appelBloc(p1)
if block_given?
yield(p1)
else
p1
end
end
appelBloc("no Block") => "no Block"
appelBloc("no Block") { |s| s.sub(/no /, '') } =>
"Block"
Dans le second exemple le bloc est appelé par 'yield' et signifie que pour chaque chaîne rencontrée, la première occurence de 'no ' sera remplacée par '', c'est à dire rien. Dans notre exemple il n'y a bien sûr qu'une chaîne, celle passée en paramètre, mais dans le cas de la lecture d'un fichier texte c'est chaque ligne qui serait soumise à l'ablation de 'no '.
Un itérateur est fait pour répéter (itérer) des
parties de code un certain nombre de fois. Contrairement à d'autres langages,
Ruby ne possède pas de boucle 'for i=x
to y do fairececi end'. Mais pas de panique, il y a les itérateurs.
Les blocs de code sont utilisés en Ruby pour
implémenter les itérateurs c'est à dire des méthodes qui retournent des
éléments de collections (par exemple des tableaux dont on verra la description
au chapitre 6).
a = %w(
chat chien oie boa) # crée
un tableau
a.each { |animal| puts animal } # itère le contenu
donne
:
chat
chien
oie
boa
La variable d'itération, ici 'animal', est mise
entre deux barres verticales.
Et, chose extraordinaire, les itérateurs sont inclus
dans nombre d'objets de Ruby et n'apparaissent donc que comme de simples
méthodes :
5.times { print "*" }
3.upto (6) { |i| print i }
('a'..'e').each { |char| print char }
produit
: *****3456abcde
'.each' signifie donc : pour chaque membre de ce qui
me précède, je vais faire ceci.
Ruby fournit de très nombreux itérateurs plus
interessants les uns que les autres. Notons au passage ceux pour les chaînes de
caractères : 'each, each_byte, each_line…'
Bien, il n'y a pas de boucle 'for do
end' mais il y a les itérateurs, somme toute beaucoup plus pratiques.
Restent les boucles conditionnelles au nombre de deux :
expression while expression-booléenne
Si expression est
autre chose qu'un bloc begin/end, exécute expression
zéro ou plusieurs fois
tant que
expression-booléenne est
'true' (vrai).
expression until expression-booléenne
Si expression est autre chose qu'un bloc begin/end, exécute expression zéro ou plusieurs fois tant que expression-booléenne est 'false' (faux).
Si expression
est un bloc de code, le bloc sera toujours exécuté au moins une fois.
Pour
sortir d'une boucle (infernale ça va de soi !) il y a quatre moyens :
-
'break' qui fait quitter la boucle immédiatement.
-
'next'
qui fait sauter au début de l'itération suivante.
-
'redo'
qui répète l'itération courante.
-
'retry'
qui fait tout reprendre au début.
Pas de 'for…' c'est vite dit; il y en a un et fort intéressant. Il s'agit du 'for' qui parcourt tous les membres d'une collection ou d'un tableau, par exemple.
for i in 0..5
fairececi
end
ou
bien
for i in
['un','deux','trois']
fairecela
end
Elles sont au nombre de deux 'if' et 'unless' :
if expression-booléenne [then]
faire ceci
elsif expression-booléenne [then]
faire ceci
else
faire ceci
end
unless expression-booléenne [then]
faire ceci
else
faire ceci
end
Les
expressions entre crochets, comme ici [then]
sont facultatives.
Le mot clé
'then' sépare le corps du code de la condition. Il n'est pas requis si le corps
démarre sur une nouvelle ligne. La valeur d'une expression 'if' ou 'unless' est
la valeur de la dernière expression évaluée où le code a été exécuté.
A noter que 'if' et 'unless' peuvent être employés
dans une syntaxe de modificateurs:
expression if expression-booléenne
expression unless expression-booléenne
Evalue
expression seulement si expression-booléenne est 'true' ('false'
pour 'unless')
'case'
sert à tester une suite de conditions :
i =
5
case
i
when 1..5
print "Entre 1 et 5"
when 6..10
print "Entre 6 et 10"
else
print "> 10"
end
Le
résultat sera : "Entre 1 et 5".
L'expression 'case' cherche une égalité en
commençant la recherche à la première comparaison en utilisant de façon interne
l'opérateur : '==='. Si l'égalité est trouvée la recherche est arrêtée et le bloc de code
qui suit est exécuté. 'case' retourne alors la valeur de la dernière expression
exécutée.Si aucune égalitén'est trouvée et qu' il existe une clause 'else',
c'est elle qui est exécutée, s' il n'y en a pas c'est la valeur 'nil' qui est
retournée.
Juste avant la ligne de lancement de l'application
de l'exemple précédent, nous trouvons une bizarrerie '__FILE__', qui est un mot
réservé. Attention les traits soulignés encadrant le mot 'FILE' sont doublés.
|
__FILE__ |
and |
def |
end |
in |
or |
self |
unless |
|
|
__LINE__ |
begin |
defined? |
ensure |
module |
redo |
super |
until |
|
|
BEGIN |
break |
do |
false |
next |
rescue |
then |
when |
|
|
END |
case |
else |
for |
nil |
retry |
true |
while |
|
|
alias |
class |
elsif |
if |
not |
return |
undef |
yield |
|
Ces
mots, puisqu'on les appelle réservés, ne sont pas utilisables comme noms de
variables locales, ni de constantes.
Dans
cette liste sont comprises les pseudo-variables :
'self' l'objet récepteur de la
méthode décrite.
'true' valeur "vraie"
'false' valeur "fause"
'nil' valeur indéfinie
'__FILE__
' nom du fichier source
courant
'__LINE__
' numéro de la ligne courante
dans le fichier source.
Quand à '$0' il s'agit d'une variable prédéfinie
représentant le "nom du programme en cours d'exécution". Il s'agit
donc d'une déclaration de sécurité.
Particularités
de FXRuby :
La librairie " fox/iterators.rb " ajoute
une méthode 'each' pour les classes suivantes :
FXComboBox, FXGLGroup, FXHeader, FXIconList, FXList,
FXListBox, FXTable, FXTreeItem, FXTreeList et FXTreeListBox. Le module
'Enumerable' est aussi présent dans chacune de ces classes.
Le bloc de paramètres passé à votre bloc de code
dépend de la classe. Par exemple une instance de FXList s'itère en donnant les
paramètres de FXListItem :
aList.each { |aListItem|
puts "texte pour cet item =
#{aListItem.getText()}"
}
alors
qu'une instance de FXComboBox nécessite deux paramètres, le texte de l'item
(une chaîne 'string') et les données de l'item :
aComboBox.each { |itemText, itemData|
puts "texte pour cet item =
#{itemText}"
}
Le
tableau suivant indique les paramètres pour les itérateurs de ces classes:
FXComboBox :
le texte de l'item (une chaîne 'string') et les données.
FXGLGroup : une instance de FXGLObject.
FXHeader : une instance de
FXHeaderItem.
FXIconList : une instance de FXIconItem.
FXList : une instance de
FXListItem .
FXListBox : le texte de l'item
('string'), une icône (une instance de FXIcon) et les données.
FXTreeItem : une instance de FXTreeItem.
FXTreeList : une instance de FXTreeItem.
FXTreeListBox : une instance de FXTreeItem.
Nous arrivons maintenant à la partie vraiment
relationnelle d'un programme, c'est à dire la communication avec l'utilisateur.
Les menus sont des composants auxquels nous sommes habitués depuis que les
programmes évolués existent, et il en est de même des boîtes de dialogues. Pour
ne rien changer à nos habitudes voici un nouvel exemple regroupant ces deux
concepts.
***************************************************************************
require 'fox'
include Fox
# Une petite boîte de dialogue pour nos tests.
class FXTestDialog < FXDialogBox
def initialize(owner)
# Invoque en premier la fonction 'initialize' de la classe de base.
super(owner, "Test de boîte de dialogue", DECOR_TITLE|DECOR_BORDER)
# Boutons du bas.
buttons = FXHorizontalFrame.new(self,
LAYOUT_SIDE_BOTTOM|FRAME_NONE|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,
0, 0, 0, 0, 40, 40, 20, 20)
# Séparateur.
FXHorizontalSeparator.new(self,
LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|SEPARATOR_GROOVE)
# Contenu.
contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_TOP|FRAME_NONE|LAYOUT_FILL_X|LAYOUT_FILL_Y|PACK_UNIFORM_WIDTH)
# Sous-menu, sans commande.
submenu = FXMenuPane.new(self)
FXMenuCommand.new(submenu, "Un")
FXMenuCommand.new(submenu, "Deux")
FXMenuCommand.new(submenu, "Trois")
# Menu.
menu = FXMenuPane.new(self)
FXMenuCommand.new(menu, "&Accepter", nil, self, ID_ACCEPT)
FXMenuCommand.new(menu, "A&nnuler", nil, self, ID_CANCEL)
FXMenuCascade.new(menu, "Sous-menu", nil, submenu)
FXMenuCommand.new(menu, "&Quitter\tCtl-Q", nil, getApp(), FXApp::ID_QUIT)
# Menu instantané (PopUp).
pane = FXPopup.new(self)
FXOption.new(pane, "Un", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Deux", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Trois", nil, nil, 0,JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Quatre", nil, nil,0,JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Cinq", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Six", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Sept", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Huit", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Neuf", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
FXOption.new(pane, "Dix", nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
# Menu option.
FXOptionMenu.new(contents, pane, (FRAME_RAISED|FRAME_THICK|
JUSTIFY_HZ_APART|ICON_AFTER_TEXT|LAYOUT_CENTER_X|LAYOUT_CENTER_Y))
# Bouton pour le menu instantané.
FXMenuButton.new(contents, "&Menu", nil, menu, (MENUBUTTON_DOWN|
JUSTIFY_LEFT|LAYOUT_TOP|FRAME_RAISED|FRAME_THICK|ICON_AFTER_TEXT|
LAYOUT_CENTER_X|LAYOUT_CENTER_Y))
# Accepter.
FXButton.new(buttons, "&Accepter", nil, self, ID_ACCEPT,
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
# Annuler.
FXButton.new(buttons, "A&nnuler", nil, self, ID_CANCEL,
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
end
end
# Sous-classe de la fenêtre principale.
class DialogTester < FXMainWindow
def initialize(app)
# Invoque en premier la fonction 'initialize' de la classe de base.
super(app, "Test Dialogues", nil, nil, DECOR_ALL, 0, 0, 400, 200)
# Info-bulles.
FXTooltip.new(getApp())
# Barre de menu.
menubar = FXMenubar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
# Séparateur.
FXHorizontalSeparator.new(self,
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|SEPARATOR_GROOVE)
# Menu fichier.
filemenu = FXMenuPane.new(self)
FXMenuCommand.new(filemenu, "&Quitter", nil, getApp(), FXApp::ID_QUIT, 0)
FXMenuTitle.new(menubar, "&Fichier", nil, filemenu)
# Contenu.
contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_TOP|FRAME_NONE|LAYOUT_FILL_X|LAYOUT_FILL_Y|PACK_UNIFORM_WIDTH)
# Bouton pour afficher un dialogue normal.
nonModalButton = FXButton.new(contents,
"Dialogue &Non-Modal ...\tAffiche une boîte de dialogue normale.", nil, nil, 0,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
nonModalButton.connect(SEL_COMMAND, method(:onCmdShowDialog))
# Bouton pour afficher un dialogue modal.
modalButton = FXButton.new(contents,
"Dialogue &Modal ...\tAffiche une boîte de dialogue modale", nil, nil, 0,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
modalButton.connect(SEL_COMMAND, method(:onCmdShowDialogModal))
# Construit une instance de boîte de dialogue.
@dialog = FXTestDialog.new(self)
end
# Montre le dialogue non-modal.
def onCmdShowDialog(sender, sel, ptr)
@dialog.show
end
# Montre le dialogue modal.
def onCmdShowDialogModal(sender, sel, ptr)
FXTestDialog.new(self).execute
return 1
end
# Démarrage.
def create
super
show(PLACEMENT_SCREEN)
end
end
def run
# Construit une application.
application = FXApp.new("Dialog", "FoxTest")
# Construit la fenêtre principale de l'application.
DialogTester.new(application)
# Crée l'application.
application.create
# Exécute l'application.
application.run
end
run
***************************************************************************
A l'exécution
une première fenêtre vous propose le choix entre un dialogue modal et un
dialogue non-modal. Qu'ès acò ? .
Réponse : un dialogue modal se trouve dans une
fenêtre qui ne se fermera, et même mieux ne laissera la place à aucune autre,
que s'il y a abandon de la part de l'utilisateur ou bien réponse à une question
posée. La fenêtre en question reste toujours sur le devant de l'écran et attend
obstinément une réponse.
Le dialogue non-modal permet d'aller faire un petit
tour dans les fenêtres ouvertes alentour sans se formaliser pour autant de
votre abandon.
Dans la partie 'initialize' une instance de la
fenêtre de dialogue est créée : '@dialog'. C'est cette instance qui va être
affichée lors d'un appel à la méthode 'onCmdShowDialog' qui appellera la
méthode '.show' de l'instance. Tandis que dans la méthode ' onCmdShowDialogModal' c'est
à la méthode '.execute' de l'instance de 'FXDialogBox' qu'il est fait appel.
Hormis cela l'aspect est le même, ce qui explique le
code unique des sous-fenêtres dans l'exemple ci-dessus.
En regardant les images suivantes on observe, en partant du haut de celles-ci, la mention 'Fichier'. Avant d'en arriver à sa description il nous faut parler de ses propriétaires. Le premier est 'menubar, une instance de FXMenuBar dont la déclaration complète est :
FXMenuBar.new(parent, opts=LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X,
x=0, y=0, width=0, height=0, padLeft=3,
padRight=3, padTop=2, padBottom=2,
hSpacing=DEFAULT_SPACING, vSpacing=DEFAULT_SPACING)
Le
propriétaire 'parent' est ici l'application elle-même ('app'), suivi dans les
paramètres des options habituelles et d'aspect.
La
barre de menu possède quant à elle un "titre de menu" tel que :
FXMenuTitle.new(parent, text, icon=nil,
popupMenu=nil, opts=0)
qui
commande par 'popupMenu' une sous-fenêtre de type FXMenuPane :
FXMenuPane.new(owner, opts=0)
qui
est propriétaire, pour finir, des commandes qui vont déclencher des actions :
FXMenuCommand.new(parent, text, icon=nil,
target=nil, selector=0, opts=0)
et des éventuels séparateurs d'enjolivure :
FXMenuSeparator.new(parent, opts=0)
Schématiquement
nous avons donc :
Barre
de menu => Titre => Sous-menus => Commandes
=> Séparateurs

La figure ci-dessus montre le menu instantané 'pane', cachant le menu propriétaire 'FXOptionMenu'. Ce dernier gardera la valeur qui sera cliquée dans le menu instantané, son deuxième paramètre étant en effet 'pane' . C'est un exemple qui montre bien la facilité avec laquelle s'opère le passage de données entre composants dans Fox. FXOptionMenu réagit donc comme une boîte à lister, tout en prenant moins de place, en affichant une valeur par défaut qui peut être modifiée par le choix d'une autre valeur choisie dans un menu instantané.

La figure ci-dessus montre le menu bouton
('FXMenuButton') qui fait apparaître les sous-fenêtres ('FXMenuPane) comprenant
les commandes des menus ('FXMenuCommand')
'Accepter' à 'Quitter' et le sous-menu "cascade" de 'Un' à
'Trois'.
Faites attention au composant FXMenuCascade qui, bien
que déclaré en même temps que les commandes ne passe pas les mêmes paramètres :
…
FXMenuCommand.new(menu,
"A&nnuler", nil, self, ID_CANCEL)
FXMenuCascade.new(menu,
"Sous-menu", nil, submenu)
…
Au lieu de 'self' c'est bien le sous-menu devant
apparaître qui est passé en paramètre, ici 'submenu'.
Le bouton, situé à droite de la boîte, avec une
flèche sur sa droite, déclanche donc l'apparition de sous-menus, qui à leur
tour peuvent faire de même, tandis que le bouton de gauche affiche une valeur.
Notre tour d'horizon des menus ne serait pas complet
sans les barres déplaçables, contenant des boutons, des menus...bref des
composants qui bougent avec la barre et se réorganisent suivant la forme de
cette dernière. Commençons donc par le propriétaire principal qu'est
FXToolBarShell dont la déclaration ne comporte que deux paramètres principaux :
'owner' et 'opts', vu que c'est lui le proprio... Hop, terminé, passons donc au
suivant . Il peut s'agir soit d'un FXMenubar, pour les menus conventionnels,
soit d'un FXToolbar pour les menus
composés de boutons à icône. FxMenubar a été vu plus haut, voyons donc
FXToolbar dont la déclaration est la suivante :
new(p,q,opts....)
dans le cas où il est rattaché à un
FXToolBarShell, et
new(p,opts...)
dans le cas
où il est non-flottant. Dans le premier cas le paramètre 'p' désigne, comme
vous vous en doutez, le propiétaire, et 'q' pointe vers son autre fenêtre
d'accueil, généralement un FXToolBarShell. En définitive FXToolBarShell n'est
qu'une simple sous-fenêtre, avec certes un comportement particulier, rattachée
à la fenêtre principale ...
Dans ces FXToolbar vont prendre place suivant leur
rôle, soit des FXMenuCommand, soit des boutons avec une icône. Reste que pour
manipuler ces composants il nous faut une poignée (avez-vous tenté d'ouvrir un
tiroir sans poignée, pas facile n'est ce pas ?), et c'est FXToolBarGrip qui s'y colle avec la définition
suivante :
new(parent,
target=nil, selector=0, opts...)
Généralement
parent et cible ne font qu'un.
Un
petit exemple pour concrétiser :
*****************************************************************************
require 'fox'
include Fox
class ToolWindow < FXMainWindow
def initialize(app)
super(app, "FOX barre d'outils", nil, nil, DECOR_ALL,
0, 0, 340, 200, 0, 0)
fileopenicon = getIcon("fileopen.png")
filesaveicon = getIcon("filesave.png")
paletteicon = getIcon("palette.png")
# Construit la barre de menus.
dragshell1 = FXToolbarShell.new(self, 0)
menubar = FXMenubar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|FRAME_RAISED)
FXToolbarGrip.new(menubar, menubar, FXMenubar::ID_TOOLBARGRIP,
TOOLBARGRIP_DOUBLE)
# Barre d'outils.
dragshell2 = FXToolbarShell.new(self, 0)
toolbar = FXToolbar.new(self, dragshell2, (LAYOUT_SIDE_TOP|
PACK_UNIFORM_WIDTH|PACK_UNIFORM_HEIGHT|FRAME_RAISED|LAYOUT_FILL_X))
FXToolbarGrip.new(toolbar, toolbar, FXToolbar::ID_TOOLBARGRIP,
TOOLBARGRIP_DOUBLE)
# Barre de statut.
statusbar = FXStatusbar.new(self,
LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER)
statusbar.statusline.normalText="Prêt"
# Menu fichier.
filemenu = FXMenuPane.new(self)
FXMenuTitle.new(menubar, "&Fichier", nil, filemenu)
# Boutons avec icône.
openBtn = FXButton.new(toolbar, "&Ouvrir\tOuvrir un fichier\tOuvre un fichier.", fileopenicon,
nil, 0, ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED)
saveBtn = FXButton.new(toolbar, "&Sauver\tSauvegarde\tSauve un fichier.", filesaveicon,
nil, 0, ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED)
# Couleur.
FXButton.new(toolbar, "&Couleurs\tCouleurs\tAffiche la boîte de dialogue couleurs.", paletteicon,nil,0,
ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED|LAYOUT_RIGHT)
# Entrées du menu "Fichier".
FXMenuCommand.new(filemenu, "&Ouvrir...\tCtl-O\tOuvrir un fichier.", fileopenicon)
FXMenuCommand.new(filemenu, "&Sauver...\tCtl-S\tSauver un fichier.", filesaveicon)
FXMenuCommand.new(filemenu, "&Quitter\tCtl-Q").connect(SEL_COMMAND, method(:onCmdQuit))
FXTooltip.new(getApp(), TOOLTIP_NORMAL)
end
def getIcon(filename)
FXPNGIcon.new(getApp(), File.open(filename, "rb").read)
end
def onCmdQuit(sender, sel, ptr)
getApp().exit(0)
end
def create
super # i.e. FXMainWindow::create()
show
end
end # classe
def run
application = FXApp.new("Boite outils", "FoxTest")
window = ToolWindow.new(application)
application.addSignal("SIGINT", window.method(:onCmdQuit))
application.create
application.run
end
run
*****************************************************************************
Et nous obtenons la fenêtre suivante :

tooltest.png
Remarquez dans les "FXToolBarGrip" le sélecteur qui correspond à la cible. Si la commande est en fait la même (ID_TOOLBARGRIP), son propriétaire doit être de la même classe que la cible (FXMenuBar et FXToolBar). Ca coule de source, mais cela va mieux en le disant.
Nous avons vu précédemment que les objets ont des
méthodes, et que le descendant d'un objet hérite de ces méthodes. Il peut
arriver que l'on ait besoin dans certains cas d'avoir un héritier qui sorte du
lot et doive avoir un comportement différent, tout en gardant le même nom de
méthode. Dans d'autres langages ceci n'est soit pas possible, soit nécessite
des précautions si ce n'est des prévisions à long terme en prévoyant ce cas
éventuel dans l'ancêtre lui-même. Ou bien aussi de créer de toutes pièces une
nouvelle classe. Avec Ruby, vous vous en doutiez n'est ce pas ?, pas de
problème, il y a ce que l'on appelle les méthodes singletons qui permettent de redéfinir une méthode à la volée :
Class oiseau
def modeLoco
print "vole"
end
end
pingouin
= oiseau.new
manchot
= oiseau.new
def manchot.modeLoco
print "ne vole pas"
end
pingouin.modeLoco => "vole"
manchot.modeLoco => "ne vole pas"
L'exemple ci-dessus n'est pas innocent, puisqu'il paraît que les Français (pas les Belges ?) confondent les pingouins du pôle Nord avec les manchots du pôle Sud…
Bien
entendu, il est tout à fait autorisé de redéfinir une classe :
Class
manchot< oiseau
def modeLoco
print
"ne vole pas""
end
end
Ce qui ne fera tout de même pas voler les manchots… Le signe '<' indique que 'manchot' est une nouvelle classe descendant de la classe 'oiseau' et hérite par conséquent de tout ce que possède 'oiseau'.
Vous avez pu voir, si votre sens de l'observation est toujours en éveil, que parfois, dans les exemples précédents, une variable était attribuée à une instance et parfois non. L'explication est simple. Si l'on n'a pas besoin d'accéder ni aux attributs ni aux méthodes, il n'est pas nécessaire d'attribuer une variable à une instance de composant. Seul inconvénient, le ramasse-miettes de Ruby, va détruire l'objet sitôt qu'il ne servira plus. En fait cela n'a aucune importance, puisqu'il se recréera au prochain appel et dans la transparence la plus totale pour l'utilisateur.
Nous avons déjà parlé des tableaux mais sans nous y attarder. Il est temps de voir plus en détail ce concept très utilisé en programmation.
En Ruby vous pouvez créer des tableaux très simplement en affectant des valeurs entre crochets à une variable :
monTableau = [] (ensemble vide)
monTableau =
[1,2,"trois", "quatre"]
Nous voyons que les valeurs peuvent être de plusieurs types. Nous pouvons utiliser les tableaux un peu comme des chaînes de caractères en les concaténant :
monTableau +
["cinq",6] contiendra [1,2,"trois",
"quatre","cinq",6]
en les multipliant :
monTableau*2
=[1,2,"trois",
"quatre","cinq",6,1,2,"trois",
"quatre","cinq",6]
On peut se référer aux éléments par un indice (qui commence à '0').
monTableau[0] = 1
monTableau[2,2] = ["trois","quatre"]
monTableau[-2,2] =
["cinq",6] (les indices négatifs vont de la droite vers la gauche)
monTableau[13] = nil
En
Ruby les tableaux, comme le reste
d'ailleurs, sont très "puissants" et possèdent une multitude de
méthodes. En voici quelques-unes :
·
'|'
pour l'union
["1","2",3"] |
["2","3","4"] =
["1","2","3","4"]
· '&' pour l'intersection [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] = [1, 3]
· '<<' ajoute à la fin [ 1, 2 ] << "c" << "d" = [1, 2, "c", "d"]
· '==" l'égalité [ "a", "b" ] == [ "a", "b", "c" ] = false
· '.clear' vide le tableau : a = [ "a", "b", "c" ], a.clear = []
·
'.include?' booléen : a = [ "a", "b",
"c" ]
·
a.include?("b") = true
·
a.include?("z") = false
·
'.index'
donne l'indice : a
= [ "a", "b", "c" ]
·
a.index("b") = 1
· a.index("z") = nil
· '.lenght' donne le nombre d'éléments : [ 1, 2, 3, 4, 5 ].length = 5
· '.sort' pour trier : a = [ "d", "a", "e", "c", "b" ] a.sort = ["a", "b", "c", "d", "e"]
Aucun rapport avec votre boucher. L'autre nom connu est le "dictionnaire". Plus simplement c'est comme un tableau à deux colonnes, où se trouvent une clef dans la première et une donnée dans la seconde. Si dans un tableau on accède aux éléments au moyen d'un indice, dans une table de hachage c'est par une clef. Une table de hachage se construit en Ruby avec des groupes de deux éléments entre accolades '{}', les éléments étant séparés par une flèche composée du signe '=' et de '>' soit '=>' :
h = {"a"=>100,
"b"=>200}
h["a"] renvoie 100
h["c"] renvoie nil
h["c"] = 300
agrandit la table de la clef "c" et de la donnée 300
Comme pour les tableaux, voici quelques méthodes :
·
'.clear'
vide la table.
·
'.default' donne une valeur à la valeur par défaut :
h.default = "Loupé !"h["d"] renvoie "Loupé"
·
'.delete'
supprime une clef et sa donnée : h.delete["c"]
·
'has_value?'
bolléen, renvoie 'true' si la valeur est présente dans les données :
h.has_value(100) renvoie 'true'
·
'.index'
retourne la clef pour une valeur donnée : h.index(200) renvoie "b".
·
'.lenght'
renvoie le nombre de paires de valeurs de la table : h.lenght renvoie 2.
·
'.sort'
trie la table.
·
'.to_s'
renvoie une chaîne avec les valeurs de la table : h.to_s renvoie "a100b200"
Gros chapitre que nous entamons là. Mais tout est si simple avec FOX, que nous n'hésitons pas à attaquer de front plusieurs composants, visuels ou non d'ailleurs.
La boîte de saisie, appelée parfois "InputBox" ou "TextBox" ou bien encore "EditBox" etc.. , se nomme en FOX "FXTextField". Sa déclaration est la suivante :
FXTextField.new(p,
numColumns, tgt=nil, sel=0, opts=TEXTFIELD_NORMAL, x=0, y=0, w=0, h=0,
pl=DEFAULT_PAD, pr=DEFAULT_PAD, pt=DEFAULT_PAD, pb=DEFAULT_PAD)
-
'p'
est le propriétaire.
-
'numcolums'
le nombre de caractères possible à l'affichage.
-
'tgt'
la cible des messages éventuels.
-
'sel'
le sélecteur
-
etc…
C'est tout bêtement une zone rectangulaire blanche dans laquelle l'utilisateur est amené à entrer du texte et des chiffres, ou bien uniquement à y lire une information non modifiable directement. FXTextField peut cependant se spécialiser par le choix de ses options 'opts'. Il peut être converti en boîte de saisie de nombres entiers, de nombres réels, de texte de longueur fixée ou bien encore en mode secret pour entrer un mot de passe.
Pour communiquer avec les autres composants il en existe un, non visuel celui-là, qui
sert d'estafette. Il s'agit de FXDataTarget qui permet à un composant comme
FXTextField d'être relié directement à une variable ou à un ou plusieurs
compères.
Rien de tel qu' un petit programme pour visualiser tout cela et
nous ferons le point après.
***************************************************************************
require 'fox'
include Fox
class ProgressWindow < FXMainWindow
def initialize(app)
# Initialise la classe de base.
super(app, "Test de progression", nil, nil, DECOR_ALL, 20, 20, 600,100)
# Crée une cible avec une valeur entière.
@intTarget = FXDataTarget.new(10)
# Barre de menu.
menubar = FXMenubar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
# Menu Fichier.
filemenu = FXMenuPane.new(self)
FXMenuCommand.new(filemenu, "Dialogue de
progression...").connect(SEL_COMMAND) do
@progressdialog.show(PLACEMENT_OWNER)
end
FXMenuCommand.new(filemenu,"&Quitter\tCtlQ",nil,getApp(),FXApp::ID_QUIT)
FXMenuTitle.new(menubar, "&Fichier", nil, filemenu)
# Crée une boîte d'information reliée à la valeur de @intTarget.
@progressdialog = FXProgressDialog.new(self, "Progression", "%
effectué...",PROGRESSDIALOG_CANCEL|DECOR_BORDER|DECOR_RESIZE)
@progressdialog.target = @intTarget
@progressdialog.selector = FXDataTarget::ID_VALUE
FXHorizontalSeparator.new(self,LAYOUT_SIDE_TOP|SEPARATOR_GROOVE|
LAYOUT_FILL_X)
# Une matrice pour bien aligner les composants.
matrix = FXMatrix.new(self, 7,
MATRIX_BY_COLUMNS|LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
FXLabel.new(matrix, "&Entier", nil,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|JUSTIFY_RIGHT|LAYOUT_FILL_ROW)
FXTextField.new(matrix, 10, @intTarget, FXDataTarget::ID_VALUE,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_ROW)
FXTextField.new(matrix, 10, @intTarget, FXDataTarget::ID_VALUE,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_ROW)
FXSlider.new(matrix, @intTarget, FXDataTarget::ID_VALUE,
LAYOUT_CENTER_Y|LAYOUT_FILL_ROW|LAYOUT_FIX_WIDTH, 0, 0, 100)
FXDial.new(matrix, @intTarget, FXDataTarget::ID_VALUE, (LAYOUT_CENTER_Y|LAYOUT_FILL_ROW|LAYOUT_FIX_WIDTH|DIAL_HORIZONTAL|DIAL_HAS_NOTCH),0, 0, 100)
FXSpinner.new(matrix, 5, @intTarget, FXDataTarget::ID_VALUE,
SPIN_CYCLIC|FRAME_SUNKEN|FRAME_THICK|LAYOUT_CENTER_Y|LAYOUT_FILL_ROW)
FXProgressBar.new(matrix, @intTarget, FXDataTarget::ID_VALUE,
(LAYOUT_CENTER_Y|LAYOUT_FILL_X|FRAME_SUNKEN|FRAME_THICK|
PROGRESSBAR_PERCENTAGE|LAYOUT_FILL_COLUMN|LAYOUT_FILL_ROW))
# Installe un accélérateur clavier de fermeture.
self.accelTable.addAccel(fxparseaccel("Ctl-Q"), getApp(),
MKUINT(FXApp::ID_QUIT, SEL_COMMAND))
end
# Méthode pour Quitter.
def onCmdQuit(sender, sel, ptr)
getApp.exit(0)
end
# Début.
def create
# Création de la fenêtre.
super
# Montre la fenêtre principale.
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
# Construit une application.
application = FXApp.new("Progression", "FoxTest")
# L'implémentation de "threads' actuels peut causer des problèmes avec cet exemple. On les désactive donc.
application.threadsEnabled = false
# Crée la fenêtre principale.
window = ProgressWindow.new(application)
# Poignée d'interruption pour quitter l'application d'une manière gracieuse
application.addSignal("SIGINT", window.method(:onCmdQuit))
# Création de l'application.
application.create
# Et roulez jeunesse !
application.run
end
*****************************************************************************

Et voici ce que vous devez obtenir à l'écran :
Nous voyons donc dans l'ordre et de gauche à droite : une étiquette, suivit de deux boîtes de saisie, puis un potentiomètre, un cadran, un compteur et enfin une barre de progression.
En cliquant sur le menu 'Fichier', puis sur la
sous-commande 'Dialogue de progression', vous verrez apparaître la fameuse
fenêtre d'information anti-stress vous
annonçant que tel programme se déroule normalement mais qu'il est un tout petit
peu long à s'exécuter…
En modifiant la valeur d'un seul composant on
modifie tous les autres, grâce à celui qui est invisible : FXDataTarget.
Dans l'ordre d'entrée en scène nous avons donc :
·
L'appel,
auquel nous sommes maintenant habitués, à 'super'.
·
La
création de FXDataTarget initialisé à la valeur '10' , ce qui va par la même
occasion initialiser les autres composants à cette valeur, vu qu'ils ont tous
'@intTarget' comme cible.
·
Vient
ensuite la description de la barre de menu
(Fichier à Dialogue… et Quitter)
·
L'initialisation
de la boîte d'information de la progression. Si vous avez la curiosité de
regarder dans l'appendice A, afin de rechercher les attributs de
FXProgressDialog initialisés dans les lignes suivantes ('.target' et
'.selector'), vous ne les trouverez pas tout de suite. N'oubliez pas que dans
Ruby et FOX tout est objet, et qu'un objet possède la particularité d'hériter de
ses ancêtres. Il va donc falloir remonter assez haut pour trouver l'origine de
ces attributs : FXProgressDialog => FXDialogBox => FXTopWindow =>
FXShell => FXComposite => FXWindow.
·
Le
séparateur horizontal, pour faire joli.
·
Un
nouveau composant : FXMatrix à qui il suffit d'indiquer le nombre de colonnes
désiré, les options habituelles de positionnement et le tour est joué, FXMatrix
va ranger ses enfants dans l'ordre et la discipline. Il faudrait un de ces
composants pour chaque classe de collège…
·
Et
voici les 7 composants qui seront tous enfants de 'matrix'. Blanche Neige et
les 7 nains quoi… Pour commencer FXLabel, déjà vu et revu, passons donc au
suivant.
·
FXTextField.
Le composant actif par excellence, puisqu'on peut y écrire dedans. Comme c'est
un composant générique capable de faire une foultitude de choses, il ressemble,
en plus petit, à un traitement de texte complet. Vous en trouverez la
description complète en Annexe A. Tout ce que nous allons en dire pour le
moment consiste en la description de son initialisation dans le programme. Donc
en dehors des paramètres de positionnements facultatifs nous avons dans new(p,
numColumns,tgt=nil,sel=0,pts=TEXTFIELD_NORMAL) le paramètre 'p' du propriétaire
(ici 'matrix' en personne), le nombre de colonnes ('numColumns') ici '10' pour
faire un peu large, la cible 'tgt' représentée par l'instance de FXDataTarget
qu'est '@intTarget'. La cible va se comporter comme un miroir et renvoyer à
tous les composants dont elle est la cible tout changement qui sera accompagné du
message 'ID_VALUE' qui est partie prenante du paramètre suivant, le sélecteur
'sel'. Les options de 'pts' sont là pour l'arrangement habituel du composant,
plus LAYOUT_FILL_ROW qui va remplir la cellule de la matrice. Encore plus
intéressants sont les attributs qui vont de la précision de la couleur du
texte, sa justification, sa longueur en passant par la position du caret (curseur texte). Précisons pour terminer qu'il
supporte les opérations couper, copier, coller du presse-papier.
·
Les
autres composants ont tous la même présentation de la méthode 'new' et le même ordre d'entrée des paramètres.
Nous allons donc uniquement décrire quelques-unes de leurs possibilités, les
plus curieux se réfèreront à l'Annexe A. (Finalement je n'aurais dû écrire que
l'Annexe A, diront certains). Bref le suivant est le potentiomètre FXSlider.
·
Le
prochain sur la liste est le cadran, FXDial. Un vu-mètre serait plus exact. Les
attributs accessibles ne sont pas aussi nombreux que ceux de FXTextField mais
citons tout de même celui définissant l'intervalle de valeurs prise en compte
:'.range', et celui renseignant la valeur à prendre en compte pourl'affichage :
'.value'.
·
Le
compteur, en nombre entier, FXSpinner, peut voir sa valeur augmenter ou
diminuer suivant la flèche cliquée, ou bien la valeur qu'il reçoit de
FXDataTarget, ou bien encore par le changement de la valeur contenue dans sa
fenêtre, laquelle est sélectionnable comme dans un FXTextField.
·
Arrive
enfin le dernier de la liste, la barre de progression FXProgressBar. La barre
peut être horizontale ou verticale, ou bien encore se transformer en cadran
circulaire grâce à son option 'opts'. Elle affiche aussi, toujours en fonction
des options, soit la valeur seule soit la valeur suivie du signe pourcentage.
·
Continuons
par un composant non visuel qui permet de programmer aisément la sortie du
programme, ou d'autres commandes, par l'intermédiaire du clavier. Il s'agit de
FXAccelTable. C'est une table qui contient
: des "messages" à envoyer à des "cibles" lorsque
une certaine combinaison de
"touches" est frappée. En bref nous avons utilisé la déclaration
suivante : addAccel(hotKey, target=nil, seldn=0, selup=0), 'hotKey' étant
"Ctrl-Q", 'target' étant l'application elle-même appelée par la
fonction 'getApp', et le sélecteur transformé par MKUINT qui est une
méthode du module 'FOX' qui transforme
les deux paramètres de 16 bits en un seul de 32.
·
Les
'threads'. Ce sont les différentes tâches lancées durant l'exécution d'un
processus et gérées, tant bien que mal, par le système d'exploitation qui se
veut du coup "multi-tâches". Au moment de l'écriture de ces lignes ,
le créateur de FXRuby pour Windows n'est pas satisfait du support de l'actuel
multi-tâches et par conséquent préfère prévenir toute panne éventuelle en le
déconnectant dans certains programmes.
·
Terminons
en beauté avec le bizarre "SIGINT". Les méthodes FXApp#addSignal
ainsi que FXApp#removeSignal, ont été améliorées pour accepter une chaîne ou un
entier comme premier argument. Si c'est une chaîne (par ex. "SIGINT' ou juste
"INIT") le code déterminera le numéro du signal correspondant pour
vous (comme la méthode Process.kill de la librairie standard de Ruby). Il
existe 3 formes d'appel pour intercepter les signaux du système d'information.
Chaque version possède aussi deux arguments optionnels (voir dans FXApp en
appendice).La plus ancienne spécifie la cible et l'identifiant du message :
aSignal =
getApp().addSignal("SIGINT", signalHandlerObj, ID_SIGINT)
La seconde prend
soit une méthode , soit une instance de procédure ('Proc') comme second
argument :
aSignal = getApp().addSignal("SIGINT", method(:signalHandlerMethod))
La dernière forme utilise un bloc de code comme poignée pour le signal :
aSignal = getApp().addSignal("SIGINT") { |sender, sel,
ptr|
#
traite le signal
}
Puisque nous en sommes aux boîtes d'entrée de texte et à la communication, essayons de simplifier, largement, l'exemple ci-dessus et de faire des tests variés. Après un traitement spartiate nous arrivons au code suivant de test1.rbw :
*****************************************************************************
require 'fox'
include Fox
class Test1Window < FXMainWindow
def initialize(app)
super(app, "Test_1", nil, nil, DECOR_ALL)
FXHorizontalSeparator.new(self,
LAYOUT_SIDE_TOP|SEPARATOR_GROOVE|LAYOUT_FILL_X)
matrix = FXMatrix.new(self, 3,
MATRIX_BY_COLUMNS|LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
FXLabel.new(matrix, "&Integer", nil,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|JUSTIFY_RIGHT|LAYOUT_FILL_ROW)
@t1 = FXTextField.new(matrix, 10, nil,0,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_ROW)
@t2 = FXTextField.new(matrix, 10, nil,0,
LAYOUT_CENTER_Y|LAYOUT_CENTER_X|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_ROW)
@t1.connect(SEL_COMMAND){
@t2.text=@t1.text*2
}
@t1.setFocus
# Démarrage
def create
super
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
application = FXApp.new("Test", "FoxTest")
window = Test1Window.new(application)
application.create
application.run
end
*****************************************************************************

Ici plus question d'échange de données évoluée et instantanée, mais de
communication de données d'un composant à un autre après un traitement
quelconque. @t1 envoie un message SEL_COMMAND et multiplie la valeur de sa zone
de texte par 2. Si vous êtes un tant soit peu curieux, vous avez déjà parcouru
l'Appendice A et vu que le message SEL_COMMAND de l'objet FXTextField est
envoyé lors de l'appui de la touche 'Enter' ou de la touche 'Tab'. Et nous
obtenons le résultat suivant :
La réponse doit vous paraître bizarre non ? comme c'est bizarre. L'explication vient du fait que Ruby traite les mots comme des nombres, et donc '* 2' signifie pour lui que l'on veut doubler l'entrée; il répète donc ce que l'on a tapé. Donc si l'entrée est 'AaBb' le texte de '@t2' sera 'AaBbAaBb'. Pour effectuer de vrais calculs il suffit de transformer la donnée chaîne en donnée nombre. Remplaçons donc la ligne suivant celle de '@t1.connect' par
'@t2.text = (@t1.text.to_i * 2).to_s
qui va tout faire rentrer dans l'ordre en transformant d'abord le texte en nombre entier ('.to_i'), en le multipliant par 2 et finalement en retransformant le tout en chaîne ('.to_s). Voici le résultat plus sympathique pour les matheux :

test2.png
Et pour que tout soit vraiment en ordre, ajoutons
aux options de '@t1' :
TEXTFIELD_INTEGER|, ce qui obligera à entrer uniquement des chiffres. Pour améliorer encore la
saisie, nous pouvons indiquer que nous voulons des réels en ajoutant
'TEXTFIELD_REAL|' ( au lieu de TEXTFIELD_INTEGER|, bien sûr) aux options et changer la
méthode '.to_i' en '.to_f' (float). Nous aurons donc accès à la lettre 'e'
(ou'E'), au point '.' (notation anglaise, la virgule ne fonctionnant pas), et
bien sûr à tous les chiffres.
La ligne @t1.setFocus force le focus sur '@t1' afin
d'avoir le caret sur ce composant dès l'apparition de la fenêtre.
Histoire de voir toutes les possibilités du composant FXTextField, ajoutons dans les options de '@t1' le mot 'TEXTFIELD_PASSWD|' ce qui permet de cacher l'entrée lors de la frappe.

Nous
pouvons encore améliorer le traitement et appeler une méthode :
@t2.text =
(mul(@t1.text.to_i)).to_s
def mul(n)
n *= 2
end
Afin
d'empêcher la saisie dans '@t2', il suffit de fixer son attribut 'editable' à
'false', après son initialisation ça va de soi, donc suite à la ligne
'@t1.setFocus' par exemple :
'@t2.editable = false'
- Si mot =
"to" alors 'mot * 3' ==>
"tototo"
- Pour concaténer des chaînes (ajouter l'une à l'autre), le signe '+' est de rigueur :
"to" + "to"
==> "toto"
si a = "to" alors a <<
"tem" ==> a sera égal à "totem"; '<<' modifie donc
la variable.
-
Pour extraire un élément de mot, on utilise les crochets '[]', Ruby traitant
les chaînes comme des tableaux. S'il
y a un seul chiffre, c'est le code du caractère qui est retourné :
a = "Bonjour'
a[1] = 111 c'est à dire le code du caractère
'o', l'indice débutant à zéro !
mais
par contre a[1,1] ="o" et
a[1,3] ="onj"
a[-3,2] = "ou" dans le cas de la marche
arrière on part de la droite et on débute à 1 et non à 0.
a[-5..-3]
= "njo" ici on utilise l'intervalle grâce au deux points '..'
a["on"]
retourne "on" parce que la correspondance existe.
a["zut"]
retourne 'nil'.
-
pour les comparaisons il y a
la méthode '<=>' qui retourne '1', '0', '-1'
"abcdef" <=> "abcde" ==> 1
"abcdef" <=> "abcdef" ==> 0
"abcdef" <=> "abcdefg" ==> -1
"abcdef" <=> "ABCDEF" ==> 1
-
le
test d'égalité '==' retourne un booléen 'true' ou 'false'
-
le
remplacemnt de caractères s'effectue par '[]=' :
a =
"Bonjour"
a[2,1]="xyz" ==>
"Boxyzjour"