Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Connaissez vous la classe Application - French
Gérald Santerre, January 1, 2001
Cet article est un extrait de ce que vous pouvez trouver sur ma home page http://www.total.net/~gersan. Ce site est avant tout un site maison, il est sans prétentions. Si vous y trouvez quelque chose qui vous intéresse, prenez le! BTW, it's all in french!
Summary
Cet article est un extrait de ce que vous pouvez trouver sur ma home page http://www.total.net/~gersan. Ce site est avant tout un site maison, il est sans prétentions. Si vous y trouvez quelque chose qui vous intéresse, prenez le! BTW, it's all in french!
Description
Cet article est un extrait de ce que vous pouvez trouver sur ma home page http://www.total.net/~gersan. Ce site est avant tout un site maison, il est sans prétentions. Si vous y trouvez quelque chose qui vous intéresse, prenez le! BTW, it's all in french!

Allons-y avec un peu de classe!

La sortie de la version 3 de FoxPro nous a apporté le concept des classes, ceux qui avait fait du C++ avant, étaient déjà familiés avec ce concept. Les autres se sont cependant retrouvés avec une nouvelle technique à aprivoiser. Cela fait déjà plus d'un an que cette version est sortie et tout le monde doit avoir fait des douzaines d'objets dans le class designer. Mais avez-vous fait un "objet application"?

À quoi peut bien servir la classe application?

Bien employée, cette classe devient le coeur de votre application, c'est le noyeau central sur lequel viennent s'accrocher toutes sortes d'autres objets ainsi que des propriétés et des méthodes utiles à votre programme. Par exemple, vous pouvez définir des méthodes qui initialiseront l'environnement lors du init() et qui restaureront l'environnement de départ lors du destroy() de la classe.

Pour faire cela, vous pouvez utiliser le class designer ou tout simplement l'éditeur pour créer cette classe par code seulement. Nous allons supposer que vous utilisez le class designer et créer une classe application de base.

Ouvrez le class designer et créez une nouvelle classe basé sur la superclasse "custom". Appelons notre classe Application, et plaçons la dans la librairie AppLib.vcx. Ajoutons trois nouvelles méthodes: SaveEnv(), SetEnv() et RestoreEnv(). Ajoutons maintenant une propriété: NomMem que nous allons initialiser avec la valeur par défaut quot;environ.mem", cette propriété contient le nom du fichier mémoire ou seront sauvées les valeurs de l'environnement de départ. Ce fichier n'a pas besoin d'exister avant, puisqu'il sera créé automatiquement par la commande SAVE TO...

Voici le code à placer dans les nouvelles méthodes:

SaveEnv() sauver l'environnement

LOCAL lcNomMem
*
lcNomMem = This.NomMem && Nom du fichier mémoire
*
memEclusive = SET("EXCLUSIVE")
memDeleted = SET("DELETED")
memDate = SET("DATE")
*
*... && Ajoutez ici tous les <tt><i>SET</tt></i> que vous désirez conserver
*
SAVE TO &lcNomMem ALL LIKE mem* && Sauver le tout dans le fichier mem
*
RETURN .T.
****************************************
SetEnv() initialiser le nouvel environnement
*Peut être laissé vide ou contenir vos paramêtres
*d'environnement préférés. Il est malgré tout
*préférable de l'initialiser dans le cadre d'une application
*précise pour avoir le code sous le nez et le modifier à volonté.
*
SET EXCLUSIVE ON
SET DECIMAL TO 4
SET DATE FRENCH
*
*...fabriquez votre environnement
*
RETURN .T.
******************************************
RestoreEnv() restaurer l'environnement initial
LOCAL lcNomMem
*
lcNomMem = This.NomMem && Nom du fichier mémoire
*
RESTORE FROM &lcNomMem ADDITIVE
*
SET EXCLUSIVE &memExclusive
SET DELETED &memDeleted
SET DATE &memDate
*
*...restaurez tous les SET sauvés dans SaveEnv()
*
RETURN .T.
Pour que tout cela fonctionne correctement, il faut bien sur appeler ces méthodes. Dans le init(), ajoutez les lignes suivantes:
This.SaveEnv()
&& sauve l'environnement
This.SetEnv() && initialise le nouvel environnement
et dans le destroy() de la classe ajoutez ceci:
This.RestoreEnv()
&& restaurer l'environnement
Vous avez maintenant une classe application de base sur laquelle vous pourez bâtir vos programme sans avoir à vous soucier de l'environnement. Voici comment appeler cette classe pour pouvoir s'en servir. Dans le fichier de départ de votre application, ajoutez ceci:
* on suppose que vous avez fait un SET DEFAULT et SET PATH
* sur les bons répertoires
SET CLASSLIB TO AppLib.vcx ADDITIVE
*
oApp = CREATEOBJECT("Application")
*
*... votre code
*
RELEASE oApp
Vous avez créé l'objet oApp de classe application, cet objet en s'initialisant a sauvegardé tout l'environnement dans le fichier environ.mem et le ramènera lors de sa destruction, à la fin du programme. Facile, n'est-ce pas? Plus sur oApp...

Pourquoi la classe application?

Si vous avez utilisé des objets OLE ( *.ocx ) dans vos fenêtres, vous avez surement remarqué que celles-ci sont très longues à s'afficher. Cela est dù au fait que l'objet OLE doit s'initialiser et, comme c'est un objet externe, cela demande un certain temps. Si l'utilisateur appelle cette fenêtre plus d'une fois, vous pouvez être certain que le téléphone va sonner!

Le problème peut être contourné facilement grâce à la classe application utilisée comme container. Changez la fenêtre fautive en classe à l'aide de la commande Save as class du menu. Vous pourez ainsi l'ajouter à l'objet oApp lors de l'initialisation du programme, votre fenêtre sera initialisée au tout début de l'application et sera ensuite accédée de façon quasi instantanée par l'utilisateur puisque vous allez jouer seulement avec la propriété Visible de l'objet fenêtre pour l'afficher ou la cacher, cette façon de faire vous évitera de décharger l'objet OLE de la mémoire à chacunes des fermetures de l'écran. Voici une façon de mettre en oeuvre ce principe:

* on suppose que vous avez fait un SET DEFAULT et SET PATH
* sur les bons répertoires
SET CLASSLIB TO AppLib.vcx ADDITIVE
SET CLASSLIB TO FeneOLE.vcx ADDITIVE
*
oApp = CREATEOBJECT("Application")
*
* ajouter la fenêtre OLE qui appartient à
* la classe FenetreOLE en lui donnant le nom oFeneOLE
oApp.AddObject("oFeneOLE","FenetreOLE")
*
* afficher la fenetre
oApp.oFeneOLE.Visible = .T. && la méthode .Show() fait la même job!
*
*... votre code
*
* il est même inutile de faire un release explicite
* sur notre fenêtre, puisqu'elle fait partie de oApp!
RELEASE oApp
Vous devez aussi changer le ThisForm.Release() du bouton de fermeture de votre écran pour y mettre un ThisForm.Visible=.F., sinon cela ne sert à rien de faire tout ça!

Encore plus sur oApp!

Vous n'êtes pas encore convaincus?

Voici un autre exemple qui devrait vous convaincre. FoxPro possède la fonction MessageBox(), qui mime le comportement des boites de message standards de Windows. Cependant, il manque la fonction InputBox(), que l'on retrouve entre autre dans Visual Basic.

InputBox

Voici comment bâtir cette fenêtre. Dans le class designer créez une classe basée sur la superclasse "form" que vous appellerez InputBx. Ensuite modifiez les propriétés suivantes:

*-Propriétés de InputBx
Height = 119
Width = 368
AutoCenter = .T.
BackColor = RGB(192,192,192)
BorderStyle = 2
Caption = "InputBox"
Closable = .F.
ControlBox = .F.
MaxButton = .F.
MinButton = .F.
WindowType = 1
SizeBox = .F.
ZoomBox = .F.
Name = "inputbx"
dans la méthode Show() de l'écran mettez ceci:
LPARAMETERS nStyle,cPrompt,cTitre,cDefault
*- nStyle = Toujours 1 (Modal) , inclus pour garder le paramêtre
*- par défaut de Show()
*- cPrompt = Texte à afficher au dessus du text box
*- cTitre = Titre de la boite InputBox
*- cDefault = Texte par défaut à placer dans le text box
nStyle = 1
IF TYPE("cPrompt") = "C"
   This.Label1.Caption = cPrompt
ELSE
   This.Label1.Caption = "Entrez une valeur..."
ENDIF
IF TYPE("cTitre") = "C"
   This.Caption = cTitre
ELSE
   This.Caption = "InputBox"
ENDIF
IF TYPE("cDefault") = "C"
   This.Text1.Value = cDefault
ELSE
   This.Text1.Value = ""
ENDIF
RETURN
Propriétés du bouton cancel
Top = 80
Left = 64
Height = 29
Width = 94
Cancel = .T.
Caption = "Cancel"
Name = "Command1"
dans le click() du bouton cancel mettez ceci
ThisForm.Text1.Value = ""
ThisForm.Visible = .F.
Propriétés du bouton ok
Top = 80
Left = 208
Height = 29
Width = 94
Caption = "OK"
Default = .T.
Name = "Command2"
dans le Click() du bouton ok mettez ceci
ThisForm.Visible = .F.
Propriétés du text box (portez attention au ControlSource)
ControlSource = "ThisForm.Parent.InputVal"
Height = 24
Left = 16
Top = 40
Width = 336
Name = "Text1"
Propriétés de l'étiquette
BackColor = RGB(192,192,192)
Caption = "Entrez une valeur..."
Height = 18
Left = 16
Top = 14
Width = 337
Name = "Label1"
Vous avez maintenant votre classe InputBx. Voici maintenant comment la transformer en objet et l'inclure dans oApp.
  • Vous devez ouvrir votre classe oApp dans le class designer.
  • Ajoutez la propriété "InputVal" et initialisez la avec une chaine vide ( ="" ), c'est le ControlSource du TextBox de la fenêtre InputBox.
  • Dans le Init() ajoutez la ligne suivante: This.AddObject("InputBox","InputBx")
C'est tout! Pour utiliser l'InputBox vous n'avez plus qu'à placer le code suivant où bon vous semble:

oApp.InputBox.Show(1,"message","titre","valeur par défaut")
cValeurDeRetour = oApp.InputVal

La valeur de retour est du type "chaine de caractère", à charge pour vous de la convertir selon vos besoins.

Encore plus de classes!

Vous n'êtes pas encore vraiment convaincus?!? Ça vous prend quoi!...?

Voici un autre exemple qui devrait vous convaincre(bis). Avez-vous déjà implanté des mécanismes de sécurité dans les menus ou les écrans de vos applications?

On m'a demandé de laisser les utilisateurs définir eux-mêmes les profils d'utilisateurs avec des droits d'accès différents aux boutons dans les écrans ou pour les options de menu. Avec la méthode que j'employait auparavant, le programme devait, lors de chacuns des accès au menu (par exemple) vérifier les droits d'accès pour chacunes des options protégées de ce menu. Pour un menu contenant une vingtaine d'options protégées cela repésentait une attente de 1 ou 2 secondes (plus sur des machines lentes) avant que le dit menu veuille bien s'ouvrir...Une éternité, lorsque l'on est habitué à une réponse instantannée!

Cette lenteur provenait du fait que chacuns des profils étant sauvé dans une table, lors du login, le profil de l'utilisateur était chargé et conservé dans un tableau en mémoire. Une fonction (GetRigths, en fait une méthode de ma classe oApp) se chargeait de scanner le tableau et de retourner .T. ou .F. selon les droits de l'utilisateur courant. L'impact sur le temps de réponse du menu est facile à comprendre quand on sait que les "SKIP FOR" sont réévalués à chacuns des accès au menu et que le menu contient une vingtaine de
SKIP FOR GetRigths(MenuOuForm,OptionOuControle).

Comment optimiser le code pour accélérer les accès aux menus? Il y a en fait deux solutions envisageables, la première est de déclarer une variable globale pour chacuns des accès à protéger. Mais, je n'aime pas les variables globales en trop grand nombre (on parle ici d'une cinquantaines de variables), le risque de confusion devient de plus en plus grand à chaque fois que l'on déclare une variable de plus.

L'autre solution est d'utiliser les classes. Et c'est celle-ci que je vais exposer ici.

La table des droits d'accès comporte les champs suivants:

  • Profil(C 15, le nom du profil d'utilisateur)
  • Form (C 20, le nom du menu ou de l'écran)
  • Control (C 20, le nom de l'option de menu ou du controle)
  • Acces (L, le droit d'accès à ce controle ou option de menu)
Exemple du contenu de cette table:

ProfilFormControlAcces
AdministrateurMenuClient.T.
AdministrateurMenuFournisseur.T.
AdministrateurClientNouveau.T.
VisiteurClientSupprimer.F.

Lors du login, le programme doit aller dans cette table extraire le profil correspondant à l'utilisateur. Avec l'ancienne méthode, cela se résumait à ces quelques lignes:

SELECT Form,Control,Acces FROM Securite WHERE Securite.Profil = lcProfilUtilisateur;
 INTO ARRAY aTemp
ensuite, après validation avec _TALLY, je copiais le tableau dans une propriété de ma classe application (je n'aime pas les variables publiques...) et la méthode appelée GetRigths() se chargeait de retourner les droits après un scan dans le tableau.

La nouvelle technique est légèrement plus complexe, mais aussi plus efficace. Avant tout, il faut créer deux classes, la classe sécurite et la classe acces. Les deux classes sont basées sur la superclasse Custom et inclus dans une librairie d'objets.

La classe securite

Dans le Init()

<tt>lcProfilUtilisateur
=This.Parent.ProfilUser

SELECT Form,Control,Acces ;
FROM Securite ;
WHERE Securite.Profil = lcProfilUtilisateur ;
INTO ARRAY aTemp

IF _TALLY<> 0
   FOR i = 1 TO _TALLY
      lcObjName = ALLTRIM(aTemp[i,1])+ALLTRIM(aTemp[i,2])
      llDroits = aTemp[i,3]
      && vérifier si l'objet n'existe pas déjà
      IF TYPE("This."+lcObjName) <> "O"
         This.AddObject(&lcObjName,"Acces",llDroits)
      ELSE
         lcObj = "This."+lcObjName+".Droits"
         &lcObj = llDroits
      ENDIF
   ENDFOR
ELSE
   && message d'erreur ou rien du tout!
ENDIF</tt>
Dans la méthode AddObject() il faut ajouter un paramêtre aux 2 déjà présents
<tt>LPARAMETERS cName, cClass, llDroits</tt>
La classe acces

Propriété à ajouter: Droits ( .F. par défaut )

Dans le Init()

<tt>LPARAMETER llDroits
This.Droits = llDroits</tt>
Et voilà! Maintenant voyons ce que nous avons et comment cela fonctionne. Dès que l'utilisateur se "log" dans le système, on ajoute l'objet securite à la classe application avec l'instruction suivante:
*Vérifier si l'objet n'existe pas déjà
<tt>IF TYPE("This.MaSecurite") = "O"
   This.RemoveObject("MaSecurite")
ENDIF
This.AddObject("MaSecurite","Securite")</tt>
Lors de l'ajout de l'objet sécurité, la méthode Init() est appelée et celle-ci à son tour procède à l'ajout des "objets" droits d'accès dans une boucle. Voilà pour l'initialisation, mais comment ça marche?

GetRigths():

La méthode GetRigths() doit être légérement modifiée pour que le tout fonctionne.

*exemple d'appel: SKIP FOR oApp.GetRigths("Menu","Clients")

LParameters cForm, cControl
lcDroit ="This.MaSecurite."+ALLTRIM(cForm)+ALLTRIM(cControl)+".Droits"
RETURN &lcDroit</tt>
Pas de SCAN, pas de tableau... le temps de réponse du menu est devenu presqu'instantané. Puisque la consultation d'une propriété est beaucoup plus rapide que la recherche dans un tableau. De plus je n'ai pas eu à changer autre chose dans mon application puisque j'ai conservé l'appel de la méthode GetRigths et que cette dernière retourne toujours .T. ou .F. comme avant!

Convaincus?

Gérald Santerre, Gerald Santerre enrg.
Gérald Santerre joined the Universal Thread team in November 2001. After the completion of his programming course, he began working for a small shop with FoxPro Windows. A couple of months later, Microsoft launched Visual FoxPro 3 and he started his journey in the world of OOP. He worked for the National Bank of Canada Intranet doing Web programming with VFP. Since January 2001, he became self employed and he has designed some applications for the Nortel Network's, Bombardier's Intranet site and for other customers as well. He is always open to new contract opportunities, so feel free to contact him!
More articles from this author
Gérald Santerre, January 14, 2002
The type of small binary file that come to mind was the cursor files. These littles files that we must copy somewhere on the user machine during install. If you have icons or bitmaps, it's not a problem, the files can be build into the executable. But the cursor must reside on the disk somewhere nea...
Gérald Santerre, February 1, 2002
Gérald Santerre, from the Universal Thread, has interviewed Whil Hentzen, from Hentzenwerke Corporation in mid January. We initiated some topics on the company, the Visual FoxPro book publishing and the Great Lakes Great Database Workshop conference. Who is Whil Hentzen? (Looking over shou...
Gérald Santerre, February 1, 2002
Gérald Santerre, from the Universal Thread, has interviewed Claudio Lassala, from FoxTotal Network in mid January. Claudio Lassala joined the Universal Thread team in January 2002. He has been recently awarded as a VFP MVP. He is involved in various areas such as being the co-editor of the UTMag/Rap...
Gérald Santerre, March 25, 1998
This drawa a partial circle or pie in Visual FoxPro. You can pass the current positions of the center of the circle to trace, the radius of the circle, the start angle, the width of the angle to draw, the line width and a parameter to detect if you want to draw a pie or not.
Gérald Santerre, March 1, 2002
Gérald Santerre, from the Universal Thread, has interviewed John Petersen, president and founder of Main Line Software, Inc., a Philadelphia, Pennsylvania based software application and database design firm. MLSI development platforms include Visual FoxPro, Visual Basic, Access, and SQL-Server in mi...
Gérald Santerre, June 1, 2002
As you may have noticed, Microsoft has recently launched a new community program named ".Net Code Wise Community". Universal Thread has been chosen as a founding member of this program. It was in this context that we have interviewed Josh Levine, the Business Development Manager for MSCOM who helped...
Gérald Santerre, January 1, 2002
Monday, 31 December 2001, I was on my way to go to the new year party with my wife's family and I read a mail from Michel, between other things I see this little sentence: "BTW, have you prepared your profile for the UT Magazine?". Duh! Pardon?... "Sorry, I have send a mes...
Gérald Santerre, June 22, 1997
To start the default web browser and load a location(web page) you can use the Win API function ShellExecute().
Gérald Santerre, June 1, 2002
You spent a lot of time on the Universal Thread on a regular basis. You have noticed that the site is always on evolution. But, do you know who is behind the scene to constantly enhance your experience online? Who is Michel Fournier? After having graduated in computer science in 1987, I m...