Édition des objets d'un canevas
Ph.Leroy
2019-01-07
Définition des objets d'un canevas
Un canevas permet de présenter facilement des objets actifs, il se compose d'une déclaration de canevas comprenant une liste de composant.
Include "widget.sch"; Object cv0 Canvas, "", 10, 10, 400, 300, begin bt1 button, "Premier", 10, 10, 120, 20; bt2 button, "Second", 10, 10, 120, 20; end;
On obtient une vue comme celle-ci.
Il s'agit bien d'un canevas puisque on dessine son fond d'écran dans une call-back. Par contre les boutons sont standards ils sont juste placés dans le canevas.
Synoptic Test( )
Var w; h;
begin
on do begin
cv0.SetPaintFunction( cnPaint);
end
on evsize do begin
getwindowsize( w, h);
cv0.Place( 0, 0, w, h);
end
end
Le code est simple, on définit la fonction de dessin dans la clause d'initialisation. On profite de l'événement evSize pour replacer le canevas bien centré dans la surface de la fenêtre.
Définition de widgets
Un widget est un objet sisal associé à un élément de dialogue homme-machine.
Chaque widget dérive de la classe widget qui définit sa position, sa taille et les autres attributs standards comme un texte associé, une visibilité et même un style.
En général on définit un constructeur (create) et un peintre (paint) dans la déclaration de la classe.
Prenons l'exemple de la création d'un nœud d'un graphe.
mynode class widget begin color; // Couleur d'affichage create( text, x, y, w, h, color); paint( cdc dc); print(); end;
Donc un mynode est un widget; il définit le create qui a les paramètres standards des déclarations d'objets de synoptiques et ses paramètres propres comme sa couleur.
Object cv0 Canvas, "", 10, 10, 400, 300, begin bt1 button, "Premier", 10, 10, 120, 20; mn2 mynode, "Second", 10, 10, 120, 120, rgb(192,192,255); end;
Les méthodes associées sont par exemple:
Function mynode.create( libel_, x_, y_, w_, h_, color_) // Les parametres sont renommes pour ne pas masquer les attributs de widget begin widget.create( libel_, x_, y_, w_, h_); // On appelle directement la méthode de la classe mere color = color_; style = NO_BORDER | SS_LEFT; end Function mynode.paint( cdc dc) var wa; ha; begin cdc.setpen( RGB(0,0,0), 1, 1); cdc.setbrush( color); cdc.drawRectangle( 0, 0, w, h); // Un rectangle beu clair cdc.setFont( LaFonte, LaTaille); cdc.GetTextExtent( text, wa, ha); cdc.DrawText( text, (w-wa)/2.0, 2); // Le nom de l'objet affiche en haut au milieu cdc.DrawLine( 0, ha, w, ha); end Function mynode.print() // donne le texte correspondant a la declaration de l'objet // Utilise en autre par le debogueur et l'editeur des objets d'IHM Var str; rouge; vert; bleu; begin color2RGBA( color, rouge, vert, bleu); return """:text:"", ":x:", ":y:", ":w:", ":h:", RGB( ":rouge: ", ":vert:", ":bleu:")"; end
Édition des objets d'IHM
Lorsque on définit une interface homme-machine plus complexe, par exemple un synoptique avec des réservoirs et des tuyaux, on peut se trouver dans une situations où cela devient difficile d'aligner ses widgets.
Sisal permet de déplacer ces widgets dans la vue du synoptique et de pouvoir sauvegarder ces coordonnées mises à jour.
Pour activer cette édition des objets, c'est très simple il suffit, après s'être connecté en tant qu'administrateur, d'appuyer sur la touche "Control" du clavier quand on fait une action à la souris.
| Action | Actions |
|---|---|
| Ctrl + Clic gauche | Sélection ou désélection d'un objet, le widget sélectionné est encadré d'un cadre coloré, sa déclaration est affichée |
| Glisser-déplacer | En cliquant au centre du cadre de sélection puis en glissant, on peut déplacer le widget sélectionné |
| + | En faisant la même chose prés d'un des coins on peut redimensionner le widget |
| Ctr + Clic droit | Apparition d'un menu contextuel permettant de copier, couper ou coller le widget sélectionné |
| + Enregistrer | Enregistrement dans un fichier texte ( monsynoptique.synobj ) de toutes les déclarations du synoptique mise à jour; dans un monde idéal il faudrait que l'on mette à jour directement le fichier source (monsynoptique.syn), mais cela est difficile à faire en raison des commentaires qui peuvent être n'importe où. |
Par exemple, on a deux widgets
On sélectionne celui de gauche
On déplace celui de gauche
Et on sauvegarde les déclarations d'objets
Dans le fichier généré, on a les déclarations avec les positions mises à jour.
tc0 TabControl, "", 0, 0, 386, 246, , , "Swiss", 12, begin
tsExplor TabSheet[ 1, 1, 10, 10, 10, 32], "View", 0, 0, 386, 246, begin
cn0 Canvas, "", 5, 5, 366, 204, NO_BORDER, begin
rd1 mynode, "Node1", 20, 20, 48, 32, RGB( 255, 255, 0);
rd2 mynode, "Node2", 220, 120, 48, 32, RGB( 255, 155, 0);
hw1 mylink, "", 67, 34, 153, 102, {{ 0, 0}, { 66, 0}, { 66, 100}, { 155, 100}};
end;
end;
tsLog TabSheet[ 1, 1, 10, 10, 10, 32], "Log", 0, 0, 386, 246, begin
lbLog ListBox, "", 5, 5, 366, 204, SB_VERTICAL | BS_MULTILINE;
end;
end;
Bon il y a quelques erreurs comme la valorisation des attributs déduits des tableaux d'alignement ( TabSheet[ 1, 1, 10, 10, 10, 32]), mais on a nos coordonnées mises à jour et on peut faire une insertion dans notre fichier source, suivi d'une réindentation.
Avec "Enregistrer" ce sont toutes les déclarations qui sont transmises, y compris celles qui sont définies hors du canevas.
Widgets à poignées
Souvent on est amené à représenter des liens matérialisant une relation entre deux entités, par exemple un tuyau dans une raffinerie; cela ne peut être un widget simple car à chaque déplacement d'une extrémité on serait obligé de mettre à jour le code de la méthode de peinture du widget (méthode paint).
On a déjà vu que l'on pouvait "attraper" un widget dessiné dans un canevas à cinq endroits particuliers, au centre et à chacun de ses coins; ce endroits sont des poignées.
Pour un lien, par exemple, on va pouvoir définir d'autres points particuliers comme les points d'inflexion du lien. Ces points vont pouvoir être sélectionnés et déplacés; ce sont des poignées que l'on va identifier dans une liste de points qui va être échangées entre le canevas dessiné et les méthodes des objets dessinés dans le canevas.
Un widget à poignée est un widget qui offre deux méthodes supplémentaires, qui permettent au synoptique de connaître toutes les poignées du widget (méthode gethandles()) et de mettre à jour la liste de ces poignées (méthode sethandles). Il est alors possible au synoptique en cours d'édition de détecter un clique à proximité d'une de ces poignées, de la sélectionner et de pouvoir la faire glisser pour la placer au bon endroit.
mylink class widget begin handles list; // Liste des poignees d'edition create( libel, x, y, w, h, hnd list); // On attend une liste de point paint( mydc dc); print(); // Capable de retourner la declaration d'un objet gethandles(); sethandles( hnd list); messagex(); end;
On introduit une nouvelle classe de widget "mylink"; elle dispose d'un attribut supplémentaire "handles" qui est une liste de points ( un point est ici une liste de deux entiers x et y) et les deux méthodes "gethandles" et "sethandles".
On va pouvoir déclarer ce widget à poignée.
hw1 mylink, "", 67, 34, 153, 102, {{ 0, 0}, { 66, 0}, { 66, 100}, { 155, 100}};
On repère nos paramètres habituels (x, y, w, et h) et le nouveau paramètre qui est une liste de 4 points ( 0x0, 66x0, 66x100 et 100x66). On remarque aussi que les coordonnées x et y sont absolues ( c'est à dire relative au canevas) et celles des poignées sont relatives au widget lui-même (si on déplace le widget, les coordonnées des poignées ne sont pas modifiées).
En sélectionnant hw1 on obtient cette vue.
Enfin en cliquant à proximité d'une poignée on va pouvoir déplacer cette poignée.
La sauvegarde et la copie conserveront ce nouvel emplacement de poignée.
Vu du coté du code, cela est très simple; lorsque l'utilisateur sélectionne un widget, le synoptique appelle la méthode "gethandles", il obtient la liste des poignées et connaît déjà les cinq poignées de tout widget.
Quand l'utilisateur clique dans le widget sélectionné, le synoptique identifie la poignée la plus proche et la sélectionne, tout glissement induit entraînera le déplacement effectif de cette poignée; lorsque utilisateur relâche le bouton, le synoptique transmet à l'objet sélectionné la liste mise à jour des poignées en appelant "sethandles" et redessine tous les widgets. Notre widget affiche alors sa poignée déplacée.
function mylink.create( libel_, x_, y_, w_, h_, hnd list)
begin
widget.create( libel_, x_, y_, w_, h_);
handles = hnd;
end
function mylink.paint( cdc dc)
var iter; prime list; seconde list;
begin
cdc.setpen( RGB( 64, 64, 255), 2, 3);
cdc.setbrush( RGB( 64, 255, 64), 0);
for iter in handles do begin
if prime.size() == 0 then prime = value( iter);
else begin
seconde = value( iter);
cdc.DrawLine( int( prime[0]), int(prime[1]), int(seconde[0]), int(seconde[1]));
prime = seconde;
end
end
end
function mylink.print()
// Retourne la déclaration utilisée par le debogueur avec la liste des poignées
// "", 10, 10, 100, 90, { { 0, 0}, { 20, 0}, { 20, 20}, { 40, 20}, {40, 40}}
Var str; initie; pt;
begin
str = """, ":x:", ":y:", ":w:", ":h:", {";
initie = null;
for pt in handles do begin
if initie == null then initie = 1;
else str = str:", ";
str = str:value(pt);
end
str = str:"}";
return str;
end
function mylink.gethandles()
begin
return handles;
end
function mylink.sethandles( hnd list)
begin
handles = hnd;
end
Il est à noter que l'on peut rajouter ou supprimer des poignées.
Et le code exemple n'a même pas besoin d'être modifié.