Lu76Fer
XLDnaute Occasionnel
Bonjour aux vacanciers, les autres retournez au turbin 😄,
Création de l'évènement MouseClic
Feuille "Test1" du fichier joint
La solution proposée consiste à différer l'appel à un pseudo évènement MouseClic qui permet de déterminer s'il s'agit d'un clic gauche ou droit à partir d'une variable de module RightClic positionné sur Vrai dans BeforeRightClick. Voici le code pour une feuille renommée S_Test1 dans l'explorateur de projet sous VBE:
⚠️ il est possible qu'en ajoutant une feuille puis la renommant que celle-ci ne soit pas reconnue ! Dans ce cas sauvegardez et rouvrir le classeur, il s'agit d'un défaut d'initialisation.
Les évènements se déclenchent dans cette ordre sur un clic droit :
Dans cette illustration on remarquera qu'il existe certaines limitations contraintes par le déclenchement de l'évènement SelectionChange :
Feuille "PlateauDeJeu" du fichier joint
La sélection permet par défaut d'encadrer une zone et une simple suppression <Suppr>, détruira tout le texte de la zone. Annuler une opération est complexe à gérer,
il est préférable d'empêcher une sélection de plusieurs cellules qui n'a pas lieu d'être pour un jeu de plateau !
Ce code permet de ramener la sélection de plusieurs cellules vers la cellule active et ne "confond pas" une cellule fusionnée avec plusieurs cellules.
Voici un modèle avec l'utilisation d'une variable LockUserEvts qui pourra être déclaré en variable public au niveau d'un module utilisateur pour avoir une portée (Scope) plus large (toutes les feuilles du classeur) ou simplement dans la feuille :
⚠️Il est important de reprendre la même condition dans BeforeRightClick que dans SelectionChange, à savoir : If Not (Intersect([Gameboard], ActiveCell) Is Nothing)
🤖 Faites un petit test pour vérifier que tout fonctionne correctement ... Notamment, vous pouvez vous amuser à essayer de traverser le plateau avec les touches fléchées directionnelle du clavier ...
⚠️ Mon conseil : si vous désirez coder quelque chose de technique au sein d'un projet, adopté la méthode scientifique et prenez le temps de partir d'un classeur vierge pour bien cibler votre sujet sans qu'il soit pollué par le code de votre projet et par la même sans polluer votre projet.
Correction et finitions
Feuille "SuperPlateau" du fichier joint
SelectionChange->SelectionChange->SelectionChange->MouseClick->MouseClick->MouseClick
Pour que mon jeu de plateau ressemble d'avantage à un jeu je vais lui rajouter à minima un titre et un fond d'écran :
🎁 Pour changer le fond d'écran, j'ai un soucis dans l'interface, du coup j'utilise la fonction SetImgBckGrd présente dans le module Tools du classeur ci-joint.
Conclusion
Cet article vise d'abord ceux qui aiment créer des petits jeux Excel mais aussi pour tout ceux qui apprécient de soigner l'ergonomie de leur interface et enfin tous les codeurs en recherche d'astuces techniques. Ici, je n'utilise pas de fonction système mais seulement du code standard et les outils Excel.
Création de l'évènement MouseClic
Feuille "Test1" du fichier joint
A partir d'un évènement d'une feuille SelectionChange, il est possible de créer un évènement clic gauche sur la grille et à partir de BeforeRightClick un évènement clic droit sur la grille, même s'il faut contourner certaines limites et en accepter d'autres ...
En effet, le clic droit sur une cellule génère tout d'abord un évènement SelectionChange puis successivement BeforeRightClick. Il est donc nécessaire de pouvoir distinguer les deux sans quoi un évènement BeforeRightClick génèrera toujours un évènement SelectionChange au préalable !
La solution proposée consiste à différer l'appel à un pseudo évènement MouseClic qui permet de déterminer s'il s'agit d'un clic gauche ou droit à partir d'une variable de module RightClic positionné sur Vrai dans BeforeRightClick. Voici le code pour une feuille renommée S_Test1 dans l'explorateur de projet sous VBE:
VB:
Dim RightClic As Boolean
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.OnTime Now, "S_Test1.MouseClic"
End Sub
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
RightClic = True
Cancel = True
End Sub
'Pseudo grid event : 'clic' on mouse
Sub MouseClic()
If RightClic Then
RightAction
RightClic = False
Else
LeftAction
End If
End Sub
Sub RightAction()
ActiveCell = "Droit"
End Sub
Sub LeftAction()
ActiveCell = "Gauche"
End Sub
Les évènements se déclenchent dans cette ordre sur un clic droit :
- SelectionChange (Ajout de l'évènement MouseClic via OnTime)
- BeforeRightClick (RightClic = True, Cancel = True empêche l'affichage du menu contextuel)
- MouseClic (Si RightClic = True Alors Clic Droit Sinon Clic Gauche)
En faite, la méthode OnTime permet d'ajouter dans une file d'attente l'appel à la procédure MouseClic comme un évènement à traiter (Now=Immédiatement), du coup chacune des procédures ci-dessus est exécutée séquentiellement. Pour le clic gauche, c'est pareil mais sans passer par BeforeRightClick.
A noté que les procédures RightAction() et LeftAction() sur un projet pourront être renommées puis positionnées dans un module de code.
Dans cette illustration on remarquera qu'il existe certaines limitations contraintes par le déclenchement de l'évènement SelectionChange :
- La première c'est qu'il ne s'effectue que lorsque l'on relâche le bouton contrairement au clic droit qui se déclenche "tout de suite" (position pressé). Cela est propre à cet évènement qui ne permet pas seulement de sélectionner une cellule mais aussi une zone. Il faut faire avec ...
- La deuxième c'est qu'il ne se déclenche pas si la zone que l'on active est déjà active ! Sur la deuxième partie, on définira un modèle ou seule une zone sera concernée par les évènements Clic souris et on repositionnera chaque fois la cellule active en dehors de cette zone de façon à pouvoir cliquer plusieurs fois sur la même cellule.
Feuille "PlateauDeJeu" du fichier joint
Pour réaliser un jeu de plateau dans un système aussi ouvert qu'un tableur Excel, il faut brider certaines interactions utilisateur pour éviter que l'utilisateur ne puisse détraquer votre jeu par erreur. Personnellement, laisser la possibilité à l'utilisateur de changer l'apparence n'est pas un problème et par expérience l'utilisation de protection sur la feuille est très complexe à coder avec le risque d'avoir un blocage à l'ouverture ... Je vais donc vous proposer des techniques simples pour protéger l'essentiel sans contraindre le joueur.
La sélection permet par défaut d'encadrer une zone et une simple suppression <Suppr>, détruira tout le texte de la zone. Annuler une opération est complexe à gérer,
il est préférable d'empêcher une sélection de plusieurs cellules qui n'a pas lieu d'être pour un jeu de plateau !
VB:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Then If IsNull(Target.MergeCells) Or Not (Target.MergeCells) Then ActiveCell.Select
End Sub
Ensuite le code de votre jeu peu générer des évènements SelectionChange en utilisant par exemple le collage sur une zone ou la méthode Select pour modifier des cellules (💩💩), il faut donc mieux verrouiller les évènements utilisateurs. Pour cela, il faut que toutes vos interactions avec l'utilisateur passe par une macro présente dans le module de Feuille et avant chaque appel à une fonction de votre code vous pourrez gérer le verrou. Il existe la propriété Application.EnableEvents que je déconseille car elle s'applique de façon beaucoup trop large et peut engendrer un plantage de votre Session.
Voici un modèle avec l'utilisation d'une variable LockUserEvts qui pourra être déclaré en variable public au niveau d'un module utilisateur pour avoir une portée (Scope) plus large (toutes les feuilles du classeur) ou simplement dans la feuille :
VB:
'Module Utilisateur
Public LockUserEvts As Boolean
'Module de Feuille
Sub BtnAction() 'Exemple d'une macro associée à un bouton
LockUserEvts = True
Call Traitement()
LockUserEvts = False
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If LockUserEvts Then Exit Sub
' . . .
A cette variable il est nécessaire d'ajouter une variable interne à la feuille PrvAC qui mémorisera la position de la cellule précédemment sélectionnée pour pouvoir revenir sur la position précédente lorsque l'on clic sur le plateau et voici le code global :
VB:
'[Gameboard], [BtnClear], [BtnRandom] : Named areas (Range("Gameboard") equivalent : [Gameboard])
Dim LockUserEvts As Boolean, RightClic As Boolean '*** Peuvent être déplacées en variable public dans un module utilisateur ***
Private PrvAC As Range 'Previous Active Cell
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If LockUserEvts Then Exit Sub
'If Target.Cells.Count > 1 Then If IsNull(Target.MergeCells) Or Not (Target.MergeCells) Then ActiveCell.Select: Exit Sub 'Replace selection with active cell
If Target.Cells.Count > 1 Then If IsNull(Target.MergeCells) Or Not (Target.MergeCells) Then GoBack: Exit Sub 'Selection canceled
If Not (Intersect([Gameboard], ActiveCell) Is Nothing) Then _
Application.OnTime Now, "S_GameBoard.MouseClic": Exit Sub
If Not (Intersect([BtnClear], ActiveCell) Is Nothing) Then Application.OnTime Now, "'S_GameBoard.BtnAction 1'": Exit Sub
If Not (Intersect([BtnRandom], ActiveCell) Is Nothing) Then Application.OnTime Now, "'S_GameBoard.BtnAction 2'": Exit Sub
Set PrvAC = ActiveCell
End Sub
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
If LockUserEvts Then Exit Sub
If Not (Intersect([Gameboard], ActiveCell) Is Nothing) Then RightClic = True
Cancel = True
End Sub
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean) 'Lock Double Click
Cancel = True
End Sub
'Pseudo grid event : 'clic' on mouse
Sub MouseClic()
LockUserEvts = True
If RightClic Then 'Right Clic(Event ButtonDown)
RightAction
RightClic = False
Else 'Clic(Event ButtonUp)
LeftAction
End If
LockUserEvts = False
GoBack 'After action
End Sub
Sub GoBack() 'Goto previous Active Cell
Application.Goto IIf(PrvAC Is Nothing, [A1], PrvAC)
End Sub
Sub BtnAction(nb As Integer) 'Simulate form button. I named : "CellButton"
LockUserEvts = True
GoBack 'Before action
On nb GoSub ClearGameboard, FillRandomCases
LockUserEvts = False
Exit Sub
ClearGameboard: ClearGameboard: Return
FillRandomCases: FillRandomCases: Return
End Sub
Sub LeftAction() 'Fill case in red
ActiveCell.Interior.ColorIndex = 3
End Sub
Sub RightAction() 'Fill case in blue
ActiveCell.Interior.ColorIndex = 5
End Sub
Dans SelectionChange, j'ai ajouté des pseudo-boutons [BtnClear] et [BtnRandom] pour avoir des petites fonctions en plus pour interragir avec le plateau. J'utilise la méthode Intersect() pour savoir si l'utilisateur à cliqué sur un bouton ou le plateau mais il y a d'autres façon de coder cela et si on clic en dehors de ces zones, il faut affecter la position actuelle ActiveCell dans PrvAC. A noté qu'évidemment on verrouille les évènements utilisateurs pour les procédures différées BtnAction() et MouseClic.
La procédure GoBack() permet de revenir sur la position précédente ou la cellule [A1] si elle n'est pas été initialisée. Ici j'utilise la méthode Goto() car les méthodes Activate() ou Select() peuvent engendrer des déplacements ératiques avec les touches fléchées si on a des cellules fusionnées.
On ajoute l'évènement BeforeDoubleClick pour le bloqué et éviter de se retrouver éjecté dans une autre cellule en mode saisie : Cancel = True. Il n'est pas possible de le gérer dans MouseClic car l'évènement BeforeDoubleClick se déclenche après...
🤖 Faites un petit test pour vérifier que tout fonctionne correctement ... Notamment, vous pouvez vous amuser à essayer de traverser le plateau avec les touches fléchées directionnelle du clavier ...
Du coup vous pourriez constater que des cellules en dehors du plateau se retrouvent colorées en rouge ! Vous avez peut-être déjà deviné d'où peut venir le problème. Nous allons voir dans une 3ème partie comment corriger cela en basculant sur la dernière feuille.
⚠️ Mon conseil : si vous désirez coder quelque chose de technique au sein d'un projet, adopté la méthode scientifique et prenez le temps de partir d'un classeur vierge pour bien cibler votre sujet sans qu'il soit pollué par le code de votre projet et par la même sans polluer votre projet.
Correction et finitions
Feuille "SuperPlateau" du fichier joint
Pour comprendre d'où vient l'anomalie, il faut regarder comment se déclenchent les évènements lorsque l'on presse en continue une flèche de direction :
SelectionChange passe en priorité et du coup ActiveCell ne correspond plus à la bonne cellule quand le 1er MouseClick s'exécute. Il faut donc passer en paramètre la position ActiveCell et on prendra l'adresse texte car il n'est pas possible de passer un objet par une commande de script :
VB:
'Dans la procédure : SelectionChange
Application.OnTime Now, "S_GameBoard.MouseClic"
' * * * D E V I E N T * * * (Encadré avec ')
Application.OnTime Now, "'S_SuperBoard.MouseClic """ & ActiveCell.Address & """'"
'Puis conversion dans MouseClic(£sAC as String)
Action Range(£sAC) 'Il ne faut plus utiliser ActiveCell ou Selection
Pour que mon jeu de plateau ressemble d'avantage à un jeu je vais lui rajouter à minima un titre et un fond d'écran :
Concernant le titre, il est préférable de le protéger pour qu'il ne soit pas accidentellement changé. Pour cela, j'utilise dans le menu Données, la Validation des données. Dans l'onglet Option, choisir Personnalisé puis j'entre la Formule FAUX pour tout interdire. Ensuite je personnalise le message d'erreur dans l'onglet Alerte d'erreur.
En codant sous Excel, comme tout le monde n'a pas la même version, il faut être soucieux de la compatibilité de son code. Il y a des méthodes qui restent présentes dans les versions plus récentes par soucis de compatibilité ascendante mais qui peuvent présenter des problèmes et qu'il faudrait mieux éviter : c'est le cas pour la méthode ScreenUpdating qui peut provoquer des ralentissements croissants et persistant au cour de la session.
Par contre, elle est très efficace sur les anciennes versions et du coup je propose une procédure qui s'utilise de la même façon et tient compte de la version que vous utilisez :
VB:
Sub RefreshScreen(isActive As Boolean)
Static graphicsMotorType% 'Old Graphics Motor for version < 15 (before Excel 2013)
If graphicsMotorType = 0 Then graphicsMotorType = IIf(CInt(Replace(Application.version, ".", ",")) < 15, 1, 2)
If graphicsMotorType = 2 Then Application.ScreenUpdating = isActive
If isActive Then DoEvents
End Sub
'Utilisation :
RefreshScreen False
'Code ...
RefreshScreen True
Souvent on utilise, pour créer un jeu, une feuille cachée et il faut bien faire attention à la valeur affectée à la propriété Visible. Si on la change manuellement dans l'éditeur VBE par xlSheetHidden, elle bascule sur xlSheetVeryHidden ! Il faut alors sélectionner à nouveau la feuille et insister en la positionnant sur xlSheetHidden ! Si vous ne le saviez pas c'est très important et cela peut vous éviter de mystérieux plantage de Session. La feuille sera cachée mais vous pourrez y accéder par le code sans changer sa visibilité (si la feuille est nommé F_HideSheet) : F_HideSheet.Range("A1") = "Feuille cachée". Il faut bannir xlSheetVeryHidden !!
Conclusion
Il est vrai que mon Jeu de Plateau serait prévu pour jouer à la souris mais il faut qu'il soit assez robuste pour supporter une mauvaise manipulation d'un joueur ...
Par contre, il est vain d'essayer de le rendre intouchable par un tas de protection car si l'utilisateur fait des transformations et le rend inutilisable via les commandes du menu, dans ce cas on peut considérer que c'est l'utilisateur qui saborde lui-même son jeu. C'est ce qui me plaît dans Excel, créer des prototypes fonctionnelles et instantanément utilisables. Une appli à la fois libre et sous-contrôle !