Rapport de message :*
 

Re: Appels de procédures et portée de variables

Titre du sujet : Re: Appels de procédures et portée de variables
par myDearFriend! le 13/01/2008 23:57:48

Re dss,

Citation :
dss a dit :
Je vois que tu t'es fortement investi sur mon projet en comblant bon nombre de mes lacunes et je t'en suis fortement reconnaissant, d'autant que ta disponibilité à me fournir les explications utiles et nécessaires a été constante et importante.

Tout d'abord, merci de ta remarque car l'objectif du présent site est effectivement avant tout d'apporter les explications utiles et nécessaires à chacun pour une meilleure compréhension et une bonne maitrise de son propre projet. Le tout n'est pas de fournir un code tout prêt à un demandeur, mais de s'assurer qu'il saura le comprendre, voire l'adapter à ses besoins futures. C'est en tout cas dans cette intention que j'ai créé cet endroit...

Citation :
dss a dit :
Une chose avant l'envoi de ce nouveau programme sur laquelle je bute encore : l'emploi de la déclaration Static au niveau de la variable encours . Quelle est son intérêt et sa portée et surtout l'impact des valeurs qu'elle prend à True ou False et son incidence dans le déroulement de la procédure ; je n'ai pas vraiment compris même si j'ai lu par ailleurs que l'emploi de ce type de variable permet de mémoriser la valeur précédente : je n'ai quand même pas compris sa fonction dans le programme.

Prenons un exemple simple :
Dans le module de code d'une feuille de calcul, j'ai cette procédure :
Private Sub Worksheet_Change(ByVal Target As Range)
    If 
Target.Address "$A$1" Then
        Target
.Value Target.Value 1
    End 
If
End Sub
En résumé, l'objectif ici est d'ajouter 1 à la valeur de A1 dès que cette cellule est modifiée par l'utilisateur (utilisation de l'évènement Worksheet_Change permettant de "capter" la modification de cellule par le user).
En réalité, ce code est une anomalie et est inccorrect. Pourquoi ?

Si tu le testes, tu vas vite t'apercevoir que ce code rentre dans une boucle sans fin ! (pour interrompre, appuyer sur Echap)
Car une fois la cellule modifiée par l'utilisateur (qui rentre une valeur numérique), la ligne "Target.Value = Target.Value + 1" va elle-même déclencher une nouvelle fois l'évènement Change(), qui va lui aussi déclencher une nouvelle fois ce même code, etc...

Pour contourner ce genre de situation, il y a plusieurs façon de faire.
[list=1]
[*]Utilisation de Application.EnableEvents = False / True qui permet de désactiver les évènements de VBA :
On pourrait l'employer comme ceci :
Private Sub Worksheet_Change(ByVal Target As Range)
    If 
Target.Address "$A$1" Then
        Application
.EnableEvents False
        Target
.Value Target.Value 1
        Application
.EnableEvents True
    End 
If
End Sub
Là, la procédure semble correcte puisque l'évènement est désactivé juste avant de modifier la valeur de cellule puis réactivé ensuite.
Mais je déconseille fortement d'utiliser cette méthode ! Si par un malheureux hasard, ton code plante entre les 2 instructions, ton classeur perdra sa gestion d'évènements VBA et plus aucune procédure évènementielle ne se déclenchera automatiquement pendant que l'utilisateur continuera de travailler... bonjour les dégâts !
Dans l'exemple ci-dessus, ça sera le cas si l'utilisateur saisit une valeur non numérique en A1 : le code va planter sur la ligne "Target.Value = Target.Value + 1" pour incompatibilité de type et la propriété EnableEvents ne retrouvera jamais sa valeur True...

[*]Utilisation d'une variable de portée Module :
On peut aussi contourner le problème comme suit (tu trouveras le plus souvent cette solution dans les forums de discussions) :
Option Explicit
Dim EnCours 
As Boolean

Private Sub Worksheet_Change(ByVal Target As Range)
    If 
EnCours True Then Exit Sub
    
If Target.Address "$A$1" Then
        EnCours 
True
        Target
.Value Target.Value 1
        EnCours 
False
    End 
If
End Sub
Le principe de contournement est simple :
[list]
[*]Une variable boolean (valeur True ou False) est mise à True (ici elle se nomme "EnCours") juste avant l'instruction fautive qui entraine la boucle non souhaitée.
[*]En tête de procédure, un contrôle est fait sur la valeur de cette variable : si True, alors... passe ton chemin ! (Exit Sub)
[*]Juste après l'instruction fautive, on redonne une valeur False à cette variable de contrôle.

Pour que ça puisse fonctionner, on a déclaré cette variable en tête de module afin qu'elle puisse conserver toujours sa valeur entre les différents appels de l'évènement (ça ne marcherait pas si on la déclarait à l'intérieur même de la procédure).
[/list]
[*]Utilisation d'une variable déclarée Static :
Il s'agit exactement du même principe que pour la variable déclarée en tête de module.
Le mot clé Static (pour une variable déclarée à l'intérieur d'une procédure) permet toutefois à cette variable de conserver sa valeur même si on quitte la dite procédure et bien que celle-ci n'ait pas une portée Module.
Option Explicit

Private Sub Worksheet_Change(ByVal Target As Range)
Static 
EnCours As Boolean
    
If EnCours True Then Exit Sub
    
If Target.Address "$A$1" Then
        EnCours 
True
        Target
.Value Target.Value 1
        EnCours 
False
    End 
If
End Sub

[/list]

Alors variable Static ou variable de portée Module ?
Pour moi, la clé du problème se situe non seulement au niveau de la durée de vie de la variable mais aussi, et surtout, au niveau de la portée que l'on veut donner à la dite variable.

Une variable Public ou Private déclarée au niveau Module trouve son utilité uniquement si elle doit être partagée entre plusieurs procédures. Par exemple, si je décidais de chronométrer la durée d'ouverture d'un classeur, je déclarerais une variable vTemps en tête de module de l'objet ThisWorkbook, je lui affecterais l'heure courante dans l'évènement Open() et je réutiliserais sa valeur dans l'évènement BeforeClose() du même objet pour calculer la durée de la session.

Dans le cas cité plus haut (et également dans ton projet), il s'agit uniquement de permettre à la variable de conserver sa valeur, une seule procédure est concernée ici et la valeur n'est pas partagée entre plusieurs évènements. La portée étant donc restreinte, une simple variable déclarée Static à l'intérieur même de cette procédure, suffit à répondre à la seule contrainte de durée de vie.

Personnellement, je privilégie l'utilisation des variables Static quand j'en ai la possibilité. Je les trouve beaucoup plus faciles à gérer. Pour moi, plus la portée de la variable est restreinte, et plus il devient facile d'en maîtriser le contenu. On réduit d'autant les risques de changement de valeur inattendu à un autre endroit dans le code...

On peut ainsi déclarer des variables Static avec le même nom dans plusieurs procédures différentes. Chacune de ses variables reste donc indépendante et conserve sa propre valeur dans sa propre procédure. Moi, je trouve ça pratique...

Par ailleurs, même si je suis dans l'incapacité de le prouver, j'ai dans l'idée qu'une variable Static consomme moins de ressources qu'une variable déclarée en tête de module.

Cordialement,