Skip to content

Instantly share code, notes, and snippets.

@rhom6us
Created December 19, 2018 03:05
Show Gist options
  • Save rhom6us/75a6a3a896768dab4ff73ca4dbccd3fe to your computer and use it in GitHub Desktop.
Save rhom6us/75a6a3a896768dab4ff73ca4dbccd3fe to your computer and use it in GitHub Desktop.
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