Respond to multiple KeyDown events - c#

I'm making a simple WinForm car race game. I've got two objects - cars, and they move on the form when key is pressed (Form1KeyDown_Event).
The only thing is, that when one player press a key, the other player cannot press his key (nothing happens). But when the first player releases the key, second player can press one his keys and normally control his car.
How can I listen for two player keys simultaneously? Should I use threads and have each car on its own thread?

Here's a simple example of what you can do in order to listen to several keys at the same time, using the keyup and keydown events instead.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WinFormTest {
public partial class Form1 : Form {
private readonly IDictionary<Keys, bool> downState;
public Form1() {
InitializeComponent();
downState = new Dictionary<Keys, bool>();
downState.Add(Keys.W, false);
downState.Add(Keys.D, false);
KeyDown += remember;
KeyUp += forget;
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
Timer timer = new Timer() { Interval = 100 };
timer.Tick += updateGUI;
timer.Start();
}
private void remember(object sender, KeyEventArgs e) {
downState[e.KeyCode] = true;
}
private void forget(object sender, KeyEventArgs e) {
downState[e.KeyCode] = false;
}
private void updateGUI(object sender, EventArgs e) {
label1.Text = downState[Keys.W] ? "Forward" : "-";
label2.Text = downState[Keys.D] ? "Right" : "-";
}
}
}

You might want to investigate going lower-level and using windows hooks to detect keyboard events. This requires P/Invoking into native methods, but is pretty straight-forward. The hook you'd want is WH_LL_KEYBOARD. Details can be found at pinvoke.net.
You'd need a bit of boilerplate, but it's as close to the keyboard events as you can reasonably expect to get:
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
public delegate IntPtr LowLevelKeyboardProc(int, IntPtr, KBDLLHOOKSTRUCT);
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idhook, LowLevelKeyboardProc proc, IntPtr hMod, uint threadId);
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (var curProc = Process.GetCurrentProcess())
using (var curMod = curProc.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curMod.ModuleName), 0u);
}
}
public IntPtr MyKeyboardHook(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT keyboardInfo)
{
if (code < 0)
{
return CallNextHookEx(IntPtr.Zero, wParam, ref keyboardInfo);
}
// Do your thing with the keyboard info.
return CallNextHookEx(IntPtr.Zero, code, wParam, ref keyboardInfo);
}
Make sure to unhook your handler when your app stops needing it. The KBDLLHOOKSTRUCT encapsulates all the info Windows will give you about a keyboard event; details of its members can be found at MSDN.
One detail of this kind of hook is that it gets executed on the thread that registered it, so make sure you take note of that, and don't set it on the UI thread if it's going to do anything long-running.

Related

Make keyboard hook async using SetWindowsHookEx on c#

I have a keyboard hook that works great but has one issue I can't resolve. I want the hook to run on async and I'm not able to do it. Normally I use it like this
private void button1_Click(object sender, EventArgs e)
{
var res = new InterceptKeys();
res.startHook();
}
When I click button 1 the hooks starts and works correctly. But when i click the button 2 ...
private void button2_Click(object sender, EventArgs e)
{
Thread.Sleep(100000) //simulates an operation that keeps form busy
}
Now the hook doesn't work anymore and have to wait for the sleep to finish. My question is, it is possible to make the hook async? I tried to call it like a task, but the hook doesn't work.
public class InterceptKeys
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hwnd, StringBuilder ss, int count);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private IntPtr _hookID = IntPtr.Zero;
public void startHook()
{
if(_hookID == IntPtr.Zero)
{
_hookID = SetHook();
}
}
public void endHook()
{
UnhookWindowsHookEx(_hookID);
}
private IntPtr SetHook()
{
return SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, IntPtr.Zero, 0);
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
int cont = 0;
Dictionary<cKeyLogger.VK, Tuple<int, string>> combinacions = new Dictionary<cKeyLogger.VK, Tuple<int, string>> { { cKeyLogger.VK.F3, new Tuple<int, string>(2,"{r}{UP}") }, { cKeyLogger.VK.F4, new Tuple<int, string>(2, "{R}{DOWN}") } };
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
cKeyLogger.VK vkCode = (cKeyLogger.VK)Marshal.ReadInt32(lParam);
string window = ActiveWindowTitle();
if(wParam == (IntPtr)WM_KEYDOWN)
{
Console.WriteLine(vkCode + " _ " + window);
if (window.ToLower().Contains("excel")) //just applies to excel to do some testing
{
//Do something
}
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private static string ActiveWindowTitle()
{
//Create the variable
const int nChar = 256;
StringBuilder ss = new StringBuilder(nChar);
//Run GetForeGroundWindows and get active window informations
//assign them into handle pointer variable
IntPtr handle = IntPtr.Zero;
handle = GetForegroundWindow();
if (GetWindowText(handle, ss, nChar) > 0) return ss.ToString();
else return "";
}
}
I'd be wary of trying to tackle this problem by moving the hook to a separate thread. If you use a separate thread for the keyboard hook, when your code eventually hits the point where it needs to do something here:
if (window.ToLower().Contains("excel")) //just applies to excel to do some testing
{
//Do something
}
If there is a chance that the UI thread is still going to be blocked at this point, the code to "do something" is still going to be delayed. That delay in calling CallNextHookEx will actually get your keyboard hook removed by the OS.
I try to visualise hooks as chain links, when your application installs a hook it has a duty to do what it needs to as fast as possible before calling CallNextHookEx so that the next application can process the hook and Windows messages can keep flowing. If the OS detects an application is destabilising this it will remove the hook.
My question to you is: can move the blocking process from the UI thread into a separate thread? Rather than trying to move the hook into a separate thread. I've written many applications which use hooks and this is the way I've always done it.
Hope this helps.

Get Keyboard input for Word process in VSTO AddIn

I am trying to listen to keyboard input in my Word AddIn with the MouseKeyboardActivityMonitor Nugget. When I register the KeyboardHookListener I am able to receive every keyboard input on every programm except Word.
Is this maybe couse of some Word internal protection or am I missing something?
I have Windows 7 64bit and Word 2016 32bit.
k_keyListener = new KeyboardHookListener(new GlobalHooker());
k_keyListener.Enabled = true;
k_keyListener.KeyDown += new System.Windows.Forms.KeyEventHandler(hook_OnKeyDown);
public void hook_OnKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
log.Info("Pressed key: " + e.KeyCode.ToString());
}
I don't use the Global Hooker and my code works. I explicitly tested it in Word (and know it works in Excel, PowerPoint, Access, etc).
For what its worth, Microsoft is forever worried about Office app hacks and its possible your security software could actually be the reason. It is a KeyLogger after all and susceptible to being labelled a virus injection attack.
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//enable keyboard intercepts
KeyboardHooking.SetHook();
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
//disable keyboard intercepts
KeyboardHooking.ReleaseHook();
}
}
Add this Keyboard class:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WordAddInKeyHandler
{
class KeyboardHooking
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod,
uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public delegate int LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
//declare the mouse hook constant.
//For other hook types, you can obtain these values from Winuser.h in the Microsoft SDK.
private const int WH_KEYBOARD = 2; // mouse
private const int HC_ACTION = 0;
private const int WH_KEYBOARD_LL = 13; // keyboard
private const int WM_KEYDOWN = 0x0100;
public static void SetHook()
{
// Ignore this compiler warning, as SetWindowsHookEx doesn't work with ManagedThreadId
#pragma warning disable 618
_hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
#pragma warning restore 618
}
public static void ReleaseHook()
{
UnhookWindowsHookEx(_hookID);
}
//Note that the custom code goes in this method the rest of the class stays the same.
//It will trap if BOTH keys are pressed down.
private static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
else
{
if (nCode == HC_ACTION)
{
Keys keyData = (Keys)wParam;
// CTRL + SHIFT + 7
if ((BindingFunctions.IsKeyDown(Keys.ControlKey) == true)
&& (BindingFunctions.IsKeyDown(Keys.ShiftKey) == true)
&& (BindingFunctions.IsKeyDown(keyData) == true) && (keyData == Keys.D7))
{
// DO SOMETHING HERE
}
// CTRL + 7
if ((BindingFunctions.IsKeyDown(Keys.ControlKey) == true)
&& (BindingFunctions.IsKeyDown(keyData) == true) && (keyData == Keys.D7))
{
// DO SOMETHING HERE
}
}
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
}
public class BindingFunctions
{
[DllImport("user32.dll")]
static extern short GetKeyState(int nVirtKey);
public static bool IsKeyDown(Keys keys)
{
return (GetKeyState((int)keys) & 0x8000) == 0x8000;
}
}
}
If you have time you can check why the Global Hooker isn't working (specifically with Word) by comparing the Global Hooker source code to mine.
Reference to my answer here: https://stackoverflow.com/a/10257266/495455 also see the answer by Govert the author of XNA.

Considering multiple events

I have a C# application which uses multiple event handlers which are triggered by various events. As an example:
Global mouse hook event
Global Key hook event which is filtered (the handler only triggers with certain keypresses)
Active window change global hook
Structure Changed application hook (this is an UIAAutomation event where different structure changed event/handlers are created for each application window when it becomes the active window (so, for example, if the application is Internet Explorer then paging down the browser, clicking on another website link are examples of the structure changing on the Internet Explorer application instance)
All these events (running on background threads, e.g. MTA) have the effect of updating the UI window of my application. All of them are working as they should.
My issue is that with certain circumstances multiple events are triggered at the same time. So for example it is feasible that the following events are all triggered within 1 second (such a scenario would occur if I clicked on a different active window):
Global Change of active window
Global Mouse Hook
Application structure changed.
In different circumstances (e.g. different active applications) one event is preferred above the other as the defining event which is ruled by a set of conditions (Booleans) . I do not want to act on more than 1 event at a particular period of time.
What is the best programming mechanism for considering the events triggered and deciding, by a set of conditions, which one to act on? Of course this all has to be done within a very quick period of time (e.g. one second or less). The events if triggered will all occur within this period of time.
I hope this makes sense and if not please ask for clarification. Incidentally the reason I would like to update my UI of my application by a certain defining event is that the information that my UI presents will be slightly different depending on which event is fired (mainly due to the slight difference in timing when the differing events are triggered). Incidentally the time taken to trigger a particular event will vary depending on the action taken (e.g. clicking on a different active window). Sometimes one event type is quicker than another but then in different circumstances a different event type can be the quickest event (or the slowest event triggered)
Thank you for both the answers below very much appreciated. I will check out the System.Reactive library in the first place as it sounds purpose-built for the task.
Microsoft's Reactive Framework (NuGet "System.Reactive") can do this kind of ting superbly and very powerfully. You can do some things very simply, but some queries - especially those dealing with time - can be quite complicated.
Here's a sample of what you might do:
var event1 = new Subject<int>();
var event2 = new Subject<int>();
var query =
event1.Merge(event2).Buffer(TimeSpan.FromSeconds(1.0));
query.Subscribe(xs => Console.WriteLine($"\"{String.Join(", ", xs)}\""));
event1.OnNext(42);
Thread.Sleep(3000);
event2.OnNext(43);
Thread.Sleep(500);
event1.OnNext(44);
That outputs:
"42"
""
""
"43, 44"
""
""
Note that it produces the "43, 44" at the same time even though the events fire 500ms apart.
I've been using code like below to prevent conflicts in event handling :
class Program
{
enum EVENTS
{
EVENT1,
EVENT2,
EVENT3,
EVENT4,
}
static void Main(string[] args)
{
}
static void EventHandler(EVENTS myEvent)
{
Object thisLock = new Object();
lock (thisLock)
{
switch (myEvent)
{
case EVENTS.EVENT1 :
break;
case EVENTS.EVENT2:
break;
case EVENTS.EVENT3:
break;
case EVENTS.EVENT4:
break;
}
}
}
}
Here is a simplified version of my code with several global hooks which are triggered by change of active window and a left mouse click. If you left click on a different active window both the mouse click event and change of active window event will be triggered. If you could show me example code of using the Reactive namespace to deal with the 2 events when they are triggered within milliseconds of each other I would greatly appreciate it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Testing_multiple_events
{
public partial class Form1 : Form
{
int activeWindowCount = 1;
int activeMouseClickCount = 1;
public Form1()
{
InitializeComponent();
// set up the global hook event for change of active window
GlobalEventHook.Start();
GlobalEventHook.WinEventActive += new EventHandler(GlobalEventHook_WinEventActive);
// Setup global mouse hook to react to mouse clicks under certain conditions, see event handler
MouseHook.Start();
MouseHook.MouseAction += new EventHandler(MouseHook_MouseAction);
}
private void GlobalEventHook_WinEventActive(object sender, EventArgs e)
{
richTextBox1.AppendText("Active Window Change Global Hook Triggered: " + activeWindowCount + "\r\n");
activeWindowCount++;
}
private void MouseHook_MouseAction(object sender, EventArgs e)
{
richTextBox1.AppendText("Global MouseHook Triggered: " + activeMouseClickCount + "\r\n");
activeMouseClickCount++;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Testing_multiple_events
{
public static class GlobalEventHook
{
[DllImport("user32.dll")]
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
public static event EventHandler WinEventActive = delegate { };
public static event EventHandler WinEventContentScrolled = delegate { };
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject,
int idChild, uint dwEventThread, uint dwmsEventTime);
private static WinEventDelegate dele = null;
private static IntPtr _hookID = IntPtr.Zero;
public static void Start()
{
dele = new WinEventDelegate(WinEventProc);
_hookID = SetWinEventHook(Win32API.EVENT_SYSTEM_FOREGROUND, Win32API.EVENT_OBJECT_CONTENTSCROLLED, IntPtr.Zero, dele, 0, 0, Win32API.WINEVENT_OUTOFCONTEXT);
}
public static void stop()
{
UnhookWinEvent(_hookID);
}
public static void restart()
{
_hookID = SetWinEventHook(Win32API.EVENT_SYSTEM_FOREGROUND, Win32API.EVENT_OBJECT_CONTENTSCROLLED, IntPtr.Zero, dele, 0, 0, Win32API.WINEVENT_OUTOFCONTEXT);
}
public static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (eventType == Win32API.EVENT_SYSTEM_FOREGROUND)
{
WinEventActive(null, new EventArgs());
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Testing_multiple_events
{
public static class MouseHook
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public static event EventHandler MouseAction = delegate { };
private const int WH_MOUSE_LL = 14;
private enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200,
WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
public static void Start()
{
_hookID = SetHook(_proc);
}
public static void stop()
{
UnhookWindowsHookEx(_hookID);
}
private static LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
private static IntPtr SetHook(LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
IntPtr hook = SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle("user32"), 0);
if (hook == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
return hook;
}
}
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && (MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam || MouseMessages.WM_RBUTTONDOWN == (MouseMessages)wParam ||
MouseMessages.WM_MOUSEWHEEL == (MouseMessages)wParam))
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
MouseAction(null, new EventArgs());
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
}

MS Word's Add-in TextChange Event in C#

I have a Microsoft Word Add-in that find the similar words in a text (But When I click a button !)
My question is : how to call a function when user typed words ?
In other word , i want an event like "TextChange" or "Keypress" when user typing to get the current word and process it and get it's similar words.
Somethings Like this :
private void TextChangeEventOfCurrentActiveDocument(object sender, System.EventArgs e)
{
...
}
Any Other idea that i can get new words that user typed ?
Thanks.
Finally after a long time, i create this add-in by using Windows hooks.
(Special thanks to #Reg Edit)
Here the entire code that i wrote and this work nice for me. (some optional section of code was removed.)
ThisAddIn.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Word;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace PersianWords
{
public partial class ThisAddIn
{
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 static IntPtr SetHook(HookProcedure procedure)
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
return SetWindowsHookEx(WH_KEYBOARD_LL, procedure, GetModuleHandle(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);
if (pointerCode == 162 || pointerCode == 160)
{
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
string pressedKey = ((Keys)pointerCode).ToString();
//Do some sort of processing on key press
var thread = new Thread(() =>
{
MyClass.WrdApp.CustomizationContext = MyClass.WrdApp.ActiveDocument;
//do something with current document
});
thread.Start();
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
hookId = SetHook(procedure);
MyClass.WrdApp = Application;
MyClass.WrdApp.CustomizationContext = MyClass.WrdApp.ActiveDocument;
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
UnhookWindowsHookEx(hookId);
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
Sorry to say, but there is no such event. Nothing is getting close to it either.
So you are stuck with a button, or check the contents every once in a while using some sort of timer (the Timer class might be an option).
You could use Windows hooks to intercept keystrokes from another window (Word in this case).
Alternatively, the Word Application has a WindowSelectionChange event, which won't fire on typing, but will fire if the user moves the cursor with an arrow key or clicks a word. This would allow you to react to a word being clicked, rather than the user having to move somewhere else on the screen to click a button.

Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#

I stitched together from code I found in internet myself WH_KEYBOARD_LL helper class:
Put the following code to some of your utils libs, let it be YourUtils.cs:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MYCOMPANYHERE.WPF.KeyboardHelper
{
public class KeyboardListener : IDisposable
{
private static IntPtr hookId = IntPtr.Zero;
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
try
{
return HookCallbackInner(nCode, wParam, lParam);
}
catch
{
Console.WriteLine("There was some error somewhere...");
}
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
private IntPtr HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
}
else if (wParam == (IntPtr)InterceptKeys.WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, false));
}
}
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
public event RawKeyEventHandler KeyDown;
public event RawKeyEventHandler KeyUp;
public KeyboardListener()
{
hookId = InterceptKeys.SetHook((InterceptKeys.LowLevelKeyboardProc)HookCallback);
}
~KeyboardListener()
{
Dispose();
}
#region IDisposable Members
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
#endregion
}
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;
public static int WM_KEYDOWN = 0x0100;
public static int WM_KEYUP = 0x0101;
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
public class RawKeyEventArgs : EventArgs
{
public int VKCode;
public Key Key;
public bool IsSysKey;
public RawKeyEventArgs(int VKCode, bool isSysKey)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
}
Which I use like this:
App.xaml:
<Application ...
Startup="Application_Startup"
Exit="Application_Exit">
...
App.xaml.cs:
public partial class App : Application
{
KeyboardListener KListener = new KeyboardListener();
private void Application_Startup(object sender, StartupEventArgs e)
{
KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
Console.WriteLine(args.Key.ToString());
// I tried writing the data in file here also, to make sure the problem is not in Console.WriteLine
}
private void Application_Exit(object sender, ExitEventArgs e)
{
KListener.Dispose();
}
}
The problem is that it stops working after hitting keys a while. No error is raised what so ever, I just don't get anything to output after a while. I can't find a solid pattern when it stops working.
Reproducing this problem is quiet simple, hit some keys like a mad man, usually outside the window.
I suspect there is some evil threading problem behind, anyone got idea how to keep this working?
What I tried already:
Replacing return HookCallbackInner(nCode, wParam, lParam); with something simple.
Replacing it with asynchronous call, trying to put Sleep 5000ms (etc).
Asynchronous call didn't make it work any better, it seems stop always when user keeps single letter down for a while.
You're creating your callback delegate inline in the SetHook method call. That delegate will eventually get garbage collected, since you're not keeping a reference to it anywhere. And once the delegate is garbage collected, you will not get any more callbacks.
To prevent that, you need to keep a reference to the delegate alive as long as the hook is in place (until you call UnhookWindowsHookEx).
The winner is: Capture Keyboard Input in WPF, which suggests doing :
TextCompositionManager.AddTextInputHandler(this,
new TextCompositionEventHandler(OnTextComposition));
...and then simply use the event handler argument’s Text property:
private void OnTextComposition(object sender, TextCompositionEventArgs e)
{
string key = e.Text;
...
}
IIRC, when using global hooks, if your DLL isn't returning from the callback quick enough, you're removed from the chain of call-backs.
So if you're saying that its working for a bit but if you type too quickly it stops working, I might suggest just storing the keys to some spot in memory and the dumping the keys later. For an example, you might check the source for some keyloggers since they use this same technique.
While this may not solve your problem directly, it should at least rule out one possibility.
Have you thought about using GetAsyncKeyState instead of a global hook to log keystrokes? For your application, it might be sufficient, there's lots of fully implemented examples, and was personally easier to implement.
I have used the Dylan's method to hook global keyword in WPF application and refresh hook after each key press to prevent events stop firing after few clicks . IDK, if it is good or bad practice but gets the job done.
_listener.UnHookKeyboard();
_listener.HookKeyboard();
Implementation details here
I really was looking for this. Thank you for posting this here.
Now, when I tested your code I found a few bugs. The code did not work at first. And it could not handle two buttons click i.e.: CTRL + P.
What I have changed are those values look below:
private void HookCallbackInner to
private void HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
}
}
}
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using FileManagerLibrary.Objects;
namespace FileCommandManager
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
readonly KeyboardListener _kListener = new KeyboardListener();
private DispatcherTimer tm;
private void Application_Startup(object sender, StartupEventArgs e)
{
_kListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
private List<Key> _keysPressedIntowSecound = new List<Key>();
private void TmBind()
{
tm = new DispatcherTimer();
tm.Interval = new TimeSpan(0, 0, 2);
tm.IsEnabled = true;
tm.Tick += delegate(object sender, EventArgs args)
{
tm.Stop();
tm.IsEnabled = false;
_keysPressedIntowSecound = new List<Key>();
};
tm.Start();
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
var text = args.Key.ToString();
var m = args;
_keysPressedIntowSecound.Add(args.Key);
if (tm == null || !tm.IsEnabled)
TmBind();
}
private void Application_Exit(object sender, ExitEventArgs e)
{
_kListener.Dispose();
}
}
}
this code work 100% in windows 10 for me :)
I hope this help u

Categories