XL 2021 VBA - Recevoir un signal d'une autre Application

  • Initiateur de la discussion Initiateur de la discussion Dudu2
  • Date de début Date de début

Boostez vos compétences Excel avec notre communauté !

Rejoignez Excel Downloads, le rendez-vous des passionnés où l'entraide fait la force. Apprenez, échangez, progressez – et tout ça gratuitement ! 👉 Inscrivez-vous maintenant !

Dudu2

XLDnaute Barbatruc
Bonjour,

Existe-t-il un moyen pour une application source d'envoyer "quelque chose" (un signal) à une autre application cible qui déclenche chez elle un évènement ?

Je vois dans l'API la fonction SendMessage ou ce genre de chose mais rien pour en exploiter l'envoi. Ça m'échappe.

J'ai essayé un SendKeys de l'application source vers l'application cible (après l'avoir activée avec un SetForegroundWindow) qui a défini un Application.Onkey sur la clé envoyée. Ça fonctionne sauf évidemment quand l'application cible est en mode édition sur cellule auquel cas la clé arrive en cellule et n'est pas interceptée par le OnKey.
Mais envoyer une clé ça reste assez artisanale. J'aimerais un truc plus évolué.
 
Solution
Bonjour,

Cette version semble fonctionner sur des fichiers multiples lancés en /x dans un .bat.
Grâce aux SaveSetting / GetSetting de @Dranreb, elle a subit de drastiques simplifications et gère la sérialisation de la fusion des instances si besoin. Du moins je le pense !

Toutefois, chez moi, j'ai toujours eu un problème de processus EXCEL.EXE résiduel sans qu'aucun classeur ne soit plus ouvert.
Ça m'a toujours gêné et un peu énervé car ce processus résiduel (à tuer par un .bat) empêche l'ouverture d'un nouveau classeur.
Je n'ai jamais compris d'où ça venait.
Ce phénomène se produit à coup sûr chez moi lorsqu'après fusion d'instances, je ferme tous les classeurs de l'instance unique restante.

J'ai donc ajouté...
Je comprends pas pourquoi tu dis ça @dysorthographie, bien sûr que ça compte !
Je me souviens bien des pipes en Unix / C et j'aurais bien aimé le faire en VBA.
Au poste #12 j'ai proposé d'utiliser un véhicule de communication, en l'occurrence une base de données sqlite.

Tu pouvais par ce système transféré du vbscript entre les deux fichiers. Restait à implanter dans la mesure du possible la réception {Cible}des ordres sqlite avec une Tempo.

Mais les ordres pouvaient prendre un autre forum avec un select case au niveau de la cible pour interpréter les ordres.

Personnellement quand je travaillais encore je requête du vbscript qui sexecutait sur la cible mais Microsoft a bridé "Microsoft Script Control" et il faut maintenant bricoler la base de registre il me semble.
 
Dernière édition:
Tu pouvais par ce système transféré du vbscript entre les deux fichiers. Restait à implanter dans la mesure du possible la réception {Cible}des ordres sqlite avec une Tempo.
C'est sans doute une solution, du moins une solution phrasée.
Mais si je n'ai pas donné suite c'est que pour moi c'est tellement sibyllin, c'est un peu comme si tu me parlais de l'olifant de Roland pour passer des infos entre les instances. Je n'ai pas tes connaissances en la matière. De plus y a une boucle derrière pour la récupération non ?
 
Hello ,
Dudu2, tu as dû oublier dans la discussion Forcer Excel à n'ouvrir qu'une seule instance le post #15 de mromain basé sur mon post #13 qui liste toutes les instances d'Excel, qui récupère l'objet Application de chaque Instance et qui liste les classeurs ouverts dans les instances.
En récupérant l'objet Application de chaque Instance, on peut alors déclencher un événement dans un classeur de cette instance :
Par exemple en activant le classeur :
VB:
#If VBA7 Then
  Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" ( _
    ByVal hwnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long

  Private Declare PtrSafe Function FindWindowExA Lib "user32" ( _
    ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, _
    ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
#Else
  Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
    ByVal hwnd As Long, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long

  Private Declare Function FindWindowExA Lib "user32" ( _
    ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
    ByVal lpszClass As String, ByVal lpszWindow As String) As Long
#End If

Sub Test()
  Dim xl As Application, Wb
  For Each xl In GetExcelInstances()
    Debug.Print "Instance de: " & xl.ActiveWorkbook.FullName
    For Each Wb In xl.Workbooks
       Debug.Print "Classeur :", Wb.Name
       If xl.ActiveWorkbook.FullName <> ActiveWorkbook.FullName Then
               Wb.Activate
              ' xl.ActiveWorkbook.Worksheets(1).Range("A1").Select
              ' xl.ActiveWorkbook.Worksheets(1).Range("ZZ1").Select
              ' xl.ActiveWorkbook.Worksheets(1).Range("ZZ1").Value = "coucou de " & ActiveWorkbook.Name
            
       End If
    Next Wb
    Debug.Print "============================="
  Next
End Sub

Public Function GetExcelInstances() As Collection
  Dim guid&(0 To 3), acc As Object, hwnd, hwnd2, hwnd3
  guid(0) = &H20400: guid(1) = &H0: guid(2) = &HC0: guid(3) = &H46000000
  Set GetExcelInstances = New Collection
  Do
    hwnd = FindWindowExA(0, hwnd, "XLMAIN", vbNullString)
    If hwnd = 0 Then Exit Do
    hwnd2 = FindWindowExA(hwnd, 0, "XLDESK", vbNullString)
    hwnd3 = FindWindowExA(hwnd2, 0, "EXCEL7", vbNullString)
    If AccessibleObjectFromWindow(hwnd3, &HFFFFFFF0, guid(0), acc) = 0 Then
       On Error Resume Next
       GetExcelInstances.Add acc.Application, CStr(acc.Application.hwnd) ' sans doublon
       On Error GoTo 0
    End If
  Loop
End Function

et dans le code du classeur cible :
Code:
Private Sub Workbook_Activate()
 Debug.Print "WorkBook Activate"
End Sub
On peut aussi utiliser l'événement Change ou SelectionChange d'une feuille du classeur cible (en commentaire dans le code).
En écrivant dans une cellule du classeur cible on peut ainsi transmettre une information au classeur Cible;
Dans le code de la feuille ciblée du classeur cible :
Code:
Private Sub Worksheet_Change(ByVal Target As Range)
 Debug.Print "Change", Target.Value
 'Target.Value = ""
End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
 Debug.Print "SelectionChange"
End Sub

Ami calmant, J.P
 
Bonjour @jurassic pork,

Je crois que je maîtrise la détection des Instances Excel.
Je l'ai bien améliorée pour récupérer les PID et les Windows.
Vois le classeur joint ListExcelInstances.xlsm

Quant à déclencher un évènement dans une instance #1 (par exemple), il faut que cet évènement soit identifiable en tant qu'évènement déclenché par l'instance #2 (par exemple).

2 remarques:
  1. Or l'activation d'un classeur n'a rien de spécifique du déclenchement par l'instance #2 et peut être le résultat d'une action utilisateur.

  2. J'ai aussi pensé au Worksheet_SelectionChange et surtout au Worksheet_Change qui permettrait de placer une valeur spécifique identifiant le Change comme venant de l'instance #2. Mais on ne sait jamais selon la protection de la feuille si on peut le faire ou pas.
J'ai donc choisi l'évènement Resize de la fenêtre ce qui m'a causé beaucoup beaucoup d'ennuis car je crois avoir tout essayé dans les APIs et tenté toutes les méthodes possibles et imaginables. J'y ai passé un temps fou.

Pour identifier le Resize comme venant de l'instance #2, je l'ai fait d'un nombre de pixels prédéterminé en Width et Height (-20 pixels).
Bien sûr je tiens compte des dimensions minimales d'une fenêtre pour jouer avec ces -20 pixels.

J'aurais aimé ne réduire que de 1 pixel mais en dessous de 19 pixels (chez moi) ça ne provoque pas l'évènement Resize.

A noter que lorsque la fenêtre de l'instance #1 est minimisée, je dois la restorer et la réduis en Width et Height (-22 pixels) pour savoir que je dois la re-minimiser après le Merge.

La probabilité qu'un Resize identique en Width et Height (-20 ou -22) vienne de l'utilisateur n'est pas nulle bien qu’extrêmement faible mais ça n'a pas d'impact car s'il n'y a pas d'instances à fusionner, on sait que ce Resize ne vient pas d'une instance #2.

De cette manière tout reste parfaitement évènementiel et il n'y a pas de boucle de surveillance à mettre en place.
 
Dernière édition:
J'ai aussi pensé au Worksheet_SelectionChange et surtout au Worksheet_Change qui permettrait de placer une valeur spécifique identifiant le Change comme venant de l'instance #2. Mais on ne sait jamais selon la protection de la feuille si on peut le faire ou pas.
Tu noteras que dans mon exemple j'écris dans la cellule ZZ1 qui est très rarement utilisée dans une feuille. Si la feuille est protégée , il suffit que cette cellule soit déverrouillée pour pouvoir écrire dedans.
 
Certes...
Et si toutes les cellules sont verrouillées, ce qui est le cas par défaut, ça ne passe pas.
On parle là de fusion des instances. On n'a aucune prise sur les classeurs de l'utilisateur.
Il y a un truc que je ne comprend pas dans ton système : il va bien falloir que tu interviennes dans les classeurs des utilisateurs pour gérer ce qu'ils reçoivent par une autre instance.
 
Ce n'est pas directement au niveau du ou des classeurs que j'interviens.
Ça se joue sur l'ActiveWindow de l'instance qui est effectivement une Window de classeur mais le classeur ne m'intéresse pas.

Je te passe le code que j'ai finalisé (du moins je le crois avant découverte de problèmes ?) qui est à la fois relativement simple mais que j'ai mis des heures et des heures à mettre au point suite à des essais divers et variés et souvent infructueux.
1749463232473.gif


Comme je l'ai dit avant c'est 100% évènementiel basé sur l'évènement d'Application WindowResize() ce qui induit quelques difficultés dues au fait que c'est un WindowResize() "artificel" qui provoque la fusion, WindowResize() "artificiel" qu'il faut évidemment d'abord reconnaître puis corriger pour revenir en l'état initial.

Edit: je pense qu'il y a encore des problèmes lorsque plus d'1 instance est ajoutée rapidement à l'instance existante.
Il faut que je sérialise mais je ne sais pas comment à cette heure..
 
Dernière édition:
J'essaie d'utiliser le Application.WindowResize dans l'instance #1 sur des valeurs particulières (à déterminer) pour déclencher la fusion.
Pour l'instant je n'arrive pas à "Resizer" la fenêtre à partir de l'instance #2 bien que je connaisse son Handle.
Avec SetWindowExtEx ça ne déclenche rien.
Je vais essayer d'autres APIs.
Pour le userforme il est possible de déclencher l'évenement Resize par le message WM_SIZE
Sendmessage h, WM_SIZE, 0, 0

d'autres astuces sont possibles mais cela implique l'utisation des contoles ayant handle et supportent le message WM_SETTEXT qui déclenchera l'evenment change cette derniere astuce est utile si on souhaite recevoir un message depuis une application externe ..
 
Bonjour @Rheeem,

Merci pour ces infos. Cependant cette fusion des instances se fait dans un contexte où on ne contrôle rien des classeurs utilisateurs.
Si tu as en tête une méthode indépendante pour déclencher un évènement d'une instance à partir d'une autre instance, bien sûr je suis preneur.

La technique que j'ai utilisée avec un évènement standard (WindowResize) d'une Classe Application n'est pas simple à gérer mais je n'en vois aucune autre utilisable permettant de transmettre une information spécifique (ici la variation prédéfinie du Resize) nécessaire pour identifier cet évènement comme venant d'une autre instance et pas simplement d'une action de l'utilisateur.

Pour la sérialisation des fusions d'instances multiples, je pense que je vais utiliser les SaveSetting et GetSetting de @Dranreb, si ce sont des trucs suffisamment rapides. Le principe étant que tant qu'une fusion est en court on fait attendre la ou les suivantes. De plus la fusion doit se limiter aux classeurs de l'instance en cours de fusion identifiée par son ProcessId, un bon candidat pour les Setting. Mais c'est tout sauf simple.
 
Dernière édition:
La technique la plus répandue est assez simple c'est de creer une fenêtre cachée pour recevoir les notifications même celles des applications externes mais en Excel cette méthode souffre du même problème que les hooks car on doit lui fournir l'adresse d'une fonction qui va recevoir tous les messages donc il y a le risque des crashs ,alors on peut contourner ca par l’utilisation des contrôles standards qui utilisent les messages de windows.
 
Dernière édition:
Ok merci, je vais regarder ça même si les SaveSetting / GetSetting de @Dranreb m'ont permis de considérablement simplifier le code car j'ai pu y mettre des infos et simplifier le Resize en le libérant de ses spécificités de reconnaissance.
 
Dernière édition:
Bonjour,

Cette version semble fonctionner sur des fichiers multiples lancés en /x dans un .bat.
Grâce aux SaveSetting / GetSetting de @Dranreb, elle a subit de drastiques simplifications et gère la sérialisation de la fusion des instances si besoin. Du moins je le pense !

Toutefois, chez moi, j'ai toujours eu un problème de processus EXCEL.EXE résiduel sans qu'aucun classeur ne soit plus ouvert.
Ça m'a toujours gêné et un peu énervé car ce processus résiduel (à tuer par un .bat) empêche l'ouverture d'un nouveau classeur.
Je n'ai jamais compris d'où ça venait.
Ce phénomène se produit à coup sûr chez moi lorsqu'après fusion d'instances, je ferme tous les classeurs de l'instance unique restante.

J'ai donc ajouté dans la Classe Application une sécurité pour dézinguer le processus résiduel lors de la désactivation du dernier classeur de l'Application.

Le fichier est en ressource.
 
Dernière édition:
- Navigue sans publicité
- Accède à Cléa, notre assistante IA experte Excel... et pas que...
- Profite de fonctionnalités exclusives
Ton soutien permet à Excel Downloads de rester 100% gratuit et de continuer à rassembler les passionnés d'Excel.
Je deviens Supporter XLD

Discussions similaires

Retour