Laden...

[Gelöst] Excel 2013: Hook wird nicht aufgerufen

Erstellt von Ranger09 vor 9 Jahren Letzter Beitrag vor 8 Jahren 4.740 Views
R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 9 Jahren
[Gelöst] Excel 2013: Hook wird nicht aufgerufen

Hallo zusammen,

in meinem Excel-AddIn soll das Drücken verschiedener Tasten bestimmte Aktionen auslösen.
Diese Funktionalität habe ich über einen globalen Tastatur-Hook realisiert.
Unter Excel 2010 funktioniert alles problemlos, unter Excel 2013 dagegen wird der Hook nicht mehr aufgerufen.

Woran liegt das und wie muss ich einen Tastatur-Hook anpassen, damit er auch unter Excel 2013 lauffähig ist?

Gelöschter Account
vor 9 Jahren

Wie hast du den diesen Hook technisch realisiert? (MessageFilter, Global Hook oder direkt beim Keyboard vorbei geschaut?) Die Antwort dazu ist je nachdem eine andere.

Zusatzfrage: XL 2010 und XL 2013 laufen auf der gleichen OS Version ?
(Sonstige Fremdprogramme bitte hier immer schliessen. Hooks unter Windows verhalten sich kooperativ in einer Verarbeitungskette, d.h. sie verarbeiten und reichen danach weiter, wenn sie nix weiterreichen kriegen alle danach nix mehr mit. Evtl. ist die Tastenkombination von XL 2013 vorbelegt, das liesse sich leicht rauskriegen wenn du Excel den Fokus wegnimmst(gilt nicht für den Hook über MessageFilter))

R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 9 Jahren

Hmmm, ... bis gerade eben wusste ich noch gar nicht, dass ich einen Hook auf mehrere Arten realisieren kann, ich bin an dieser Ecke noch ein ziemlich blutiger Newbie. Worin liegt hier der Unterschied? Ich erwarte hier jetzt natürlich keine Hook-Schulung, ein guter Link würde mir schon viel weiter helfen 🙂

Ich poste wohl am besten einen Codeauszug:


private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
        
private static IntPtr hookId = IntPtr.Zero;
private delegate IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam);
private static HookProcedure procedure = HookCallback;
        
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProcedure lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{        
    hookId = SetHook(procedure);
}

private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
    UnhookWindowsHookEx(hookId);
}

private static IntPtr SetHook(HookProcedure procedure)
{
    using (Process process = Process.GetCurrentProcess())
    using (ProcessModule module = process.MainModule)
    return SetWindowsHookEx(WH_KEYBOARD_LL, procedure, retModuleHandle(module.ModuleName), 0);
}

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{    
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
    {
        int pointerCode = Marshal.ReadInt32(lParam);
        string pressedKey = ((Keys)pointerCode).ToString();
        Logger.Instance.Writeln(LoggerLevel.TRACE, "Pressed Key: " + pressedKey);

        if (pressedKey == "F3")
        {
            MeineMethode1();
        }

        if (pressedKey == "Escape")
       {
           MeineMethode2();
        }
    }
    return CallNextHookEx(hookId, nCode, wParam, lParam);
}

Excel 2010 läuft unter Windows XP, Excel 2013 unter Win7.

Zu Testzwecken protokolliere ich hier gerade jeden Tastendruck, nicht nur diejenigen, die ich auswerten will. Es wird nie ein Eintrag im Log-File erzeugt, wenn ich eine Taste in Excel drücke.
Allerdings:
Tasten, die in einer Drittwanwendung ausgelöst werden (z.B. in einem offenen Editor), führen zu einem Aufruf des Hooks und werden im Log-File protokolliert, aber eben nur diese.

R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 9 Jahren

Ich habe das AddIn nun zusätzlich in einer Win7-Umgebung mit Office 2010 installiert und getestet. Alle durch die Hook-Funktion ausgewerteten Tasten führen in Excel zu den gewünschten Aktionen, d.h. das Problem ist betriebssystemunabhängig.

Gelöschter Account
vor 9 Jahren

Mir fehlt gerade im Moment leider die Zeit um das Szenario unter Excel 2013, zu untersuchen ich habs aber auf meine Todo-Liste gesetzt.

Es scheint so das Excel 2013 die Tastatur Ereignisse einkassiert(also nicht weiter reicht wie du es mit CallNextHooEx ja richtiger Weiste tuhst)
Das passiert allerdings nur wenn Excel den Fokus hat, ansonsten interessiert sich Excel dafür -sinnvoller Weise- nicht.(Das schliesse ich aus deiner Beschreibung das Key Events in Fremdprogrammen zum gewünschten Ergebniss führen weil Excel ja nicht den Fokus hat und dann den Hook brav weiter reicht)

Ich persönlich würde jetzt so vorgehen mir Tastenkombinationen zu suchen die Excel nicht rausfiltert oder eine Implementierung via IMessageFilter verwenden. Das Konzept hierbei ist ein etwas anderes, du gehst nicht mehr direkt zum OS und setzt einen Hook sondern die Hauptnachrichtenschleife(DefWndProc) der HostAnwendung wird den Tastendruck dann an dich weiter reichen weil du dich hier dafür registriert hat. (Das Excel den Tastendruck danach nicht mehr ans System weiter reicht kann dir dann egal sein) Vor- oder Nachteil, je wie man es sieht: Der Tastendruck kommt dann nur noch an wenn Excel die derzeit fokusierte Anwendung ist.

R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 9 Jahren

Danke für den Link, dann werde ich mich jetzt mal mit der Umsetzung über MessageFilter beschäftigen.

Andere Tasten(-kombinationen) für meine Funktionalitäten zu suchen, bringt mich vermutlich nicht viel weiter, denn die Auswertung meines Log-Files zeigt, dass es völlig egal ist, welche Taste ich verwende, der Hook wird nie durchlaufen, wenn Excel den Fokus besitzt.

Gelöschter Account
vor 9 Jahren

Das dein Hook niemals aufgerufen wird, selbst wenn Excel sich für die gewünschte Tastenkombination nicht interessiert, verblüfft mich jetzt doch ein wenig. Das darf eigentlich nicht sein. Ich weiss natürlich nicht was du an Kombinationen getestet hast und ob Excel sich dafür vielleicht doch zuständig fühlte.

BTW: Die Win32 API hat eine Funktionalität zum einfangen eines systemweiten Hotkeys bereits eingebaut. Eine Beispiel-Implementierung findest auf MyCsharp.de hier: Hotkey-Klasse
Ich würde diesen alternativen Ansatz mal versuchen. Lässt sich auch relativ schnell einbinden und testen.

Ein Security Issue schliesse ich aus weil du ja sagst das dein Hook auch unter Win7/XL2013 greift solange Excel eben nicht den Fokus hat.

R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 8 Jahren

So, in der Zwischenzeit habe ich versucht, den globalen Hook durch einen MessageFilter zu ersetzen.

Hier habe ich nun aber dass Problem, dass ich den MessageFilter über die Methoden

System.Windows.Forms.Application.AddMessageFilter(myFilter);

und

System.Windows.Forms.Application.RemoveMessageFilter(myFilter);

hinzufügen bzw. entfernen muss.

Da ich aber eine Microsoft.Office.Interop.Excel.Application habe, lande ich wieder in einer Sackgasse. Gibt es eine Möglichkeit, den MessageFilter in meinem AddIn zu verwenden und wenn ja, wie?

R
Ranger09 Themenstarter:in
17 Beiträge seit 2014
vor 8 Jahren

Nach ausgiebigen Suchen in den endlosen Weiten des Internets bin ich nun auf die Information gestoßen, dass Office 2013, zumindest aber Word und Excel generell keine globalen Hooks aufrufen können. Das Problem scheint bei Microsoft bekannt zu sein, aber es gibt wohl noch keine Lösung dafür.
Nun habe ich meine ersten Gehversuche mit einem Thread-Hook gemacht, da hier das Problem wohl nicht auftritt und meinen Code wie folgt geändert:


private const int H_ACTION = 0;
private const int WH_KEYBOARD = 2;
private const long KB_TRANSITION_FLAG = 0x80000000;
private const int VK_F3 = 0x72;
private const int VK_ESCAPE = 0x1B;
        
private static IntPtr hookId = IntPtr.Zero;
private delegate IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam);
private static HookProcedure procedure = HookCallback;

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProcedure lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    hookId = SetHook(procedure);
}

private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
    UnhookWindowsHookEx(hookId);
}

private static IntPtr SetHook(HookProcedure procedure)
{   
    using (Process process = Process.GetCurrentProcess())
    using (ProcessModule module = process.MainModule)
        return SetWindowsHookEx(WH_KEYBOARD, procedure, GetModuleHandle(module.ModuleName), (uint)AppDomain.GetCurrentThreadId());
}

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode == H_ACTION)
    {
        if ((((long)lParam) & KB_TRANSITION_FLAG) == KB_TRANSITION_FLAG)
        {
            // Taste wurde losgelassen
            if (wParam == (IntPtr)VK_F3)
            {
                MeineMethode1();
            }

            if (wParam == (IntPtr)VK_ESCAPE)
            {
                MeineMethode2();
            }
        }
    }

    return CallNextHookEx(hookId, nCode, wParam, lParam);
}

Der Thread-Hook funktioniert nun wie erhofft auch unter Excel 2013 und ist wohl für meine Anforderung sowieso die bessere Wahl, da der Aufruf nur erfolgen soll, wenn Excel den Fokus hat.
😁

Anmerkung:
Bei der Eingabe des vierten Parameters 'AppDomain.GetCurrentThreadId()' beim Aufruf von SetWindowsHookEx verweist Microsoft darauf, dass diese Methode obsolet ist.
Übergebe ich wie vorgeschlagen 'System.Threading.Thread.CurrentThread.ManagedThreadId', so liefert mir SetWindowsHookEx immer 0.
Vielleicht hat ja jemand eine Ahnung, wieso das nicht funktioniert ...