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);
}
}
}
Related
I am building an application using UIAutomation which labels certain widgets on a typical Microsoft Windows user interface, e.g. Windows Explorer, control panel. I am using appropriate threading with UIA based on a Microsoft blog example and everything is working well. The labelling of the widgets is triggered by certain events and one of these events is a mouse click using a low level mousehook code shown below. The problem I have is that when the UI is updated on a mouseclick the mouse movement becomes juddery (as in not smooth) while the UI is updating. So what springs to mind is that the mouse action event needs to run in a different thread. Although I have been programming for quite a few years it has been off and on so I am far from confident when it comes to such threading issues. I have tried the code below in the mouse auction event but it makes no difference. Any help greatly appreciated. I would particularly appreciate example code with an explanation in particular.
// Setup global mouse hook to react to mouse clicks under certain conditions, see event handler
MouseHook.Start();
MouseHook.MouseAction += new EventHandler(MouseHook_MouseAction);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
namespace myapplication
{
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);
}
}
}
private void MouseHook_MouseAction(object sender, EventArgs e)
{
//my attempt at threading solution
Thread th = new Thread(() =>
{
UpdateUI();
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
I try to redirect the XButton 1 and 2 (Side Buttons of the mouse) to specific Visual Studio actions.
When I press the XButton1 I want to compile the project / build it.
This action is bound to F6 by default.
When I press the XButton2 I want to switch between code and design view (WinForms). This is bound to F7.
After several attempts using using Visual Studio built-in tools, I created the following script using AutoHotKey:
XButton2::
IfWinActive Microsoft Visual Studio
{
Send {F7}
return
}
XButton1::
IfWinActive Microsoft Visual Studio
{
Send {F6}
return
}
However I am wondering if someone knows a native way of achieving the same with Visual Studio 2015?
Solution
The main idea is registering a global mouse hook and handling desired mouse event and run a visual studio command. To do so:
Start by creating a Visual Studio Package project.
Register a global mouse hook using SetWindowsHookEx by passing WH_MOUSE_LL and handle desired mouse event, for example WM_XBUTTONDOWN. Perform registration when the solution loads.
Run desired Visual Studio Command using DTE.ExecuteCommand passing suitable command, for example Build.BuildSolution:
var dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
dte.ExecuteCommand("Build.BuildSolution");
Don't forget to unhook using UnhookWindowsHookEx when the solution closed.
Note:
To find the command that you need, go to Tools → Options → Environment → KeyBoard and find the command which you need.
You will find lots of resources about how to register a global mouse hook like this which I changed a bit and used for test. At the end of the post you can find full source code.
In Visual Studio 2013 Add ins are deprecated, so while you can do the same using a Visual Studio Add-in project but it's better to do it using VSPackages.
Implementaion
Start by creating a Visual Studio Package project and change the code of package to the code which I post here. Also add the classes which I used for global mouse hook and windows API to the solution.
Package
Here is the full code for my Package:
using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[Guid(GuidList.guidVSPackage1PkgString)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.SolutionExists)]
public sealed class VSPackage1Package : Package
{
public VSPackage1Package() { }
EnvDTE.DTE dte;
protected override void Initialize()
{
base.Initialize();
dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
dte.Events.SolutionEvents.Opened += SolutionEvents_Opened;
dte.Events.SolutionEvents.AfterClosing += SolutionEvents_AfterClosing;
}
void SolutionEvents_AfterClosing() { MouseHook.Stop(); }
void SolutionEvents_Opened()
{
MouseHook.Start();
MouseHook.MouseAction += MouseHook_MouseAction;
}
void MouseHook_MouseAction(object sender, EventArgs e)
{
dte.ExecuteCommand("Build.BuildSolution");
}
}
Windows API messages, structures and methods
using System;
using System.Runtime.InteropServices;
public class Win32
{
public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
public const int WH_MOUSE_LL = 14;
public enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200, WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT { public int x; public int y; }
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc 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);
}
Global Mouse Hook
Since I don't have XButton in my mouse, I handled WM_RBUTTONDOWN event.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class MouseHook
{
public static event EventHandler MouseAction = delegate { };
private static Win32.LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Start() { _hookID = SetHook(_proc); }
public static void Stop() { Win32.UnhookWindowsHookEx(_hookID); }
private static IntPtr SetHook(Win32.LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
var handle = Win32.GetModuleHandle(curModule.ModuleName);
return Win32.SetWindowsHookEx(Win32.WH_MOUSE_LL, proc, handle, 0);
}
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 &&
Win32.MouseMessages.WM_RBUTTONDOWN == (Win32.MouseMessages)wParam)
{
Win32.MSLLHOOKSTRUCT hookStruct =
(Win32.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam,
typeof(Win32.MSLLHOOKSTRUCT));
MouseAction(null, new EventArgs());
}
return Win32.CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
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.
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.
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