Created
December 19, 2018 03:05
-
-
Save rhom6us/75a6a3a896768dab4ff73ca4dbccd3fe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Interop; | |
// Start Visual Studio | |
// File->New->Project->C#->WPF Application | |
// Replace MainWindow.Xaml.cs with this code | |
namespace WpfApp1 | |
{ | |
/// <summary> | |
/// Interaction logic for MainWindow.xaml | |
/// </summary> | |
public partial class MainWindow : Window | |
{ | |
IntPtr _hook; | |
IntPtr _hwnd; | |
NativeMethods.CBTProc _callback; | |
TextBox _txtStatus; | |
CheckBox _chkBox; | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
this.WindowState = WindowState.Maximized; | |
// create an instance of the delegate that | |
// won't be garbage collected to avoid: | |
// Managed Debugging Assistant 'CallbackOnCollectedDelegate' :** | |
// 'A callback was made on a garbage collected delegate of type | |
// 'WpfApp1!WpfApp1.MainWindow+NativeMethods+CBTProc::Invoke'. | |
// This may cause application crashes, corruption and data loss. | |
// When passing delegates to unmanaged code, they must be | |
// kept alive by the managed application until it is guaranteed | |
// that they will never be called.' | |
_callback = this.CallBack; | |
_hook = NativeMethods.SetWindowsHookEx( | |
NativeMethods.HookType.WH_MOUSE, | |
_callback, | |
instancePtr: IntPtr.Zero, | |
threadID: NativeMethods.GetCurrentThreadId()); | |
this.Closed += MainWindow_Closed; | |
this.Loaded += MainWindow_Loaded; | |
} | |
private void MainWindow_Loaded(object sender, RoutedEventArgs e) | |
{ | |
try | |
{ | |
_txtStatus = new TextBox() | |
{ | |
Margin = new Thickness(10, 0, 0, 0), | |
MaxLines = 60, | |
IsReadOnly = true, | |
VerticalScrollBarVisibility = ScrollBarVisibility.Auto | |
}; | |
_chkBox = new CheckBox() | |
{ | |
Content = "Divert mouse messages", | |
ToolTip = "try to move the mouse around and click on the window controls\r" + | |
"You can reenable the mouse by using the keyboard\r" + | |
"(spacebar when the chkbox has focus) to uncheck this option\r"+ | |
"Notice that tooltips don't show when checkbox is checked" | |
}; | |
var btn = new Button() | |
{ | |
Content = "Click Me", | |
ToolTip = "Now you see me", | |
Width = 150, | |
HorizontalAlignment = HorizontalAlignment.Left | |
}; | |
btn.Click += (ob, eb) => | |
{ | |
AddStatusMsg("Thanks, I needed that"); | |
}; | |
var sp = new StackPanel() | |
{ | |
Orientation = Orientation.Vertical | |
}; | |
sp.Children.Add( | |
new Label() | |
{ | |
Content = "Move the mouse around" | |
}); | |
sp.Children.Add(_chkBox); | |
sp.Children.Add(btn); | |
sp.Children.Add(_txtStatus); | |
this.Content = sp; | |
AddStatusMsg("Starting"); | |
} | |
catch (Exception ex) | |
{ | |
this.Content = ex.ToString(); | |
} | |
} | |
public void AddStatusMsg(string msg) | |
{ | |
Dispatcher.BeginInvoke(new Action( | |
() => | |
{ | |
var txt = DateTime.Now.ToString("MM/dd/yy HH:mm:ss:fff ") + msg; | |
_txtStatus.AppendText(txt + "\r\n"); | |
_txtStatus.ScrollToEnd(); | |
} | |
)); | |
} | |
private void MainWindow_Closed(object sender, EventArgs e) | |
{ | |
if (_hook != IntPtr.Zero) | |
{ | |
NativeMethods.UnhookWindowsHookEx(this._hook); | |
} | |
} | |
private IntPtr CallBack(int code, IntPtr wParam, IntPtr lParam) | |
{ | |
var wind = Window.GetWindow(this); | |
if (_hwnd == null) | |
{ | |
_hwnd = (new WindowInteropHelper(wind)).EnsureHandle(); | |
} | |
var mouseInfo = Marshal.PtrToStructure<NativeMethods.MOUSEHOOKSTRUCT>(lParam); | |
AddStatusMsg($"{code} wParam {wParam} lParam {lParam} {mouseInfo}"); | |
if (_chkBox.IsChecked == true) | |
{ | |
return new IntPtr(1); // non-zero indicates don't pass to target | |
} | |
return NativeMethods.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); | |
} | |
private static class NativeMethods | |
{ | |
[StructLayout(LayoutKind.Sequential)] | |
public struct MOUSEHOOKSTRUCT | |
{ | |
public POINT pt; // Can't use System.Windows.Point because that has X,Y as doubles, not integer | |
public IntPtr hwnd; | |
public uint wHitTestCode; | |
public IntPtr dwExtraInfo; | |
public override string ToString() | |
{ | |
return $"({pt.X,4},{pt.Y,4})"; | |
} | |
} | |
#pragma warning disable 649 // CS0649: Field 'MainWindow.NativeMethods.POINT.Y' is never assigned to, and will always have its default value 0 | |
public struct POINT | |
{ | |
public int X; | |
public int Y; | |
} | |
#pragma warning restore 649 | |
// from WinUser.h | |
public enum HookType | |
{ | |
WH_MIN = (-1), | |
WH_MSGFILTER = (-1), | |
WH_JOURNALRECORD = 0, | |
WH_JOURNALPLAYBACK = 1, | |
WH_KEYBOARD = 2, | |
WH_GETMESSAGE = 3, | |
WH_CALLWNDPROC = 4, | |
WH_CBT = 5, | |
WH_SYSMSGFILTER = 6, | |
WH_MOUSE = 7, | |
WH_HARDWARE = 8, | |
WH_DEBUG = 9, | |
WH_SHELL = 10, | |
WH_FOREGROUNDIDLE = 11, | |
WH_CALLWNDPROCRET = 12, | |
WH_KEYBOARD_LL = 13, | |
WH_MOUSE_LL = 14 | |
} | |
public enum HookCodes | |
{ | |
HC_ACTION = 0, | |
HC_GETNEXT = 1, | |
HC_SKIP = 2, | |
HC_NOREMOVE = 3, | |
HC_NOREM = HC_NOREMOVE, | |
HC_SYSMODALON = 4, | |
HC_SYSMODALOFF = 5 | |
} | |
public enum CBTHookCodes | |
{ | |
HCBT_MOVESIZE = 0, | |
HCBT_MINMAX = 1, | |
HCBT_QS = 2, | |
HCBT_CREATEWND = 3, | |
HCBT_DESTROYWND = 4, | |
HCBT_ACTIVATE = 5, | |
HCBT_CLICKSKIPPED = 6, | |
HCBT_KEYSKIPPED = 7, | |
HCBT_SYSCOMMAND = 8, | |
HCBT_SETFOCUS = 9 | |
} | |
public delegate IntPtr CBTProc(int code, IntPtr wParam, IntPtr lParam); | |
[DllImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool UnhookWindowsHookEx(IntPtr hookPtr); | |
[DllImport("user32.dll")] | |
public static extern IntPtr CallNextHookEx(IntPtr hookPtr, int nCode, IntPtr wordParam, IntPtr longParam); | |
[DllImport("user32.dll")] | |
public static extern IntPtr SetWindowsHookEx(HookType hookType, CBTProc hookProc, IntPtr instancePtr, uint threadID); | |
[DllImport("kernel32.dll")] | |
public static extern uint GetCurrentThreadId(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment