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. ****************************************
*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. ******************************************
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.
This.SaveEnv() && sauve l'environnement This.SetEnv() && initialise le nouvel environnement
This.RestoreEnv() && restaurer l'environnement
* 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
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
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.
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"
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
Top = 80 Left = 64 Height = 29 Width = 94 Cancel = .T. Caption = "Cancel" Name = "Command1"
ThisForm.Text1.Value = "" ThisForm.Visible = .F.
Top = 80 Left = 208 Height = 29 Width = 94 Caption = "OK" Default = .T. Name = "Command2"
ThisForm.Visible = .F.
ControlSource = "ThisForm.Parent.InputVal" Height = 24 Left = 16 Top = 40 Width = 336 Name = "Text1"
BackColor = RGB(192,192,192) Caption = "Entrez une valeur..." Height = 18 Left = 16 Top = 14 Width = 337 Name = "Label1"
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:
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
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>
<tt>LPARAMETERS cName, cClass, llDroits</tt>
Propriété à ajouter: Droits ( .F. par défaut )
<tt>LPARAMETER llDroits This.Droits = llDroits</tt>
*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>
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!
LParameters cForm, cControl lcDroit ="This.MaSecurite."+ALLTRIM(cForm)+ALLTRIM(cControl)+".Droits" RETURN &lcDroit</tt>
Convaincus?