I'm trying to implement a background application in WPF that does some things only when right-clicking the top of the screen, so I tried hooking WH_MOUSE_LL, which works great. The problem is unhooking the callback when exiting the application, which works perfectly fine in C, but gives me ERROR_INVALID_HOOK_HANDLE in C# even though the hook handle passed to UnhookWindowsHookEx is exactly the same handle I got from SetWindowsHookEx (examples below).
EDIT: I just created a bare bones C# console application registering the same hook and instantly unregistering it and it works fine too, so I think WPF might cause the problems.
Here is the C# code that does not work, problem is in the destructor:
using System;
using System.Runtime.InteropServices;
public class LowLevelMouseHook {
#region Win32 API
private const int WH_MOUSE_LL = 14;
private const ulong WM_RBUTTONDOWN = 0x0204;
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate long LowLevelMouseProc (int nCode, UIntPtr wParam, IntPtr lParam);
private struct POINT {
long x;
long y;
}
private struct MSLLHOOKSTRUCT {
POINT pt;
int mouseData;
int flags;
int time;
UIntPtr dwExtraInfo;
}
[DllImport("User32.dll", EntryPoint = "CallNextHookEx", SetLastError = true)]
private static extern long CallNextHookEx (IntPtr hHook, int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("Kernel32.dll", EntryPoint = "GetLastError", SetLastError = true)]
private static extern uint GetLastError ();
[DllImport("Kernel32.dll", EntryPoint = "LoadLibraryW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibrary (string lpFileName);
[DllImport("User32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx (int idHook, Delegate lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("User32.dll", EntryPoint = "UnhookWindowsHookEx", SetLastError = true)]
private static extern byte UnhookWindowsHookEx (IntPtr hHook);
#endregion
// There shall only be one instance of this class
private static LowLevelMouseHook instance = null;
private IntPtr hHook;
private LowLevelMouseProc hProc; // If I don't store the delegate, I get some CallbackOnCollectedDelegate exception
private LowLevelMouseHook () {
this.hProc = new LowLevelMouseProc(MouseProc);
this.hHook = SetWindowsHookEx(WH_MOUSE_LL, this.hProc, LoadLibrary("User32.dll"), 0);
}
~LowLevelMouseHook () {
//
// PROBLEM:
// UnhookWindowsHookEx always returns 0 and GetLastError returns ERROR_INVALID_HOOK_HANDLE (1404)
// even though this.hHook is still the same value SetWindowsHookEx returned.
//
if (UnhookWindowsHookEx(this.hHook) == 0)
System.Windows.Forms.MessageBox.Show($"Error {GetLastError()} unhooking WH_MOUSE_LL", "UnhookWindowsHookEx Error");
LowLevelMouseHook.instance = null;
}
// Create new LowLevelMouseHook instance.
// This is called during the Startup-Event of Application.
public static void Init () {
LowLevelMouseHook.instance = new LowLevelMouseHook();
}
private static long MouseProc (int nCode, UIntPtr wParam, IntPtr lParam) {
if ((ulong)wParam == WM_RBUTTONDOWN) {
; // Things to do; that works fine
}
return CallNextHookEx(LowLevelMouseHook.instance.hHook, nCode, wParam, lParam);
}
}
Here is the C code that works perfectly fine:
#include <stdio.h>
#include <Windows.h>
FILE *file;
HHOOK hHook;
LRESULT CALLBACK LowLevelMouseProc_Test (int nCode, WPARAM wParam, LPARAM lParam) {
PMSLLHOOKSTRUCT pHookStruct = (PMSLLHOOKSTRUCT)lParam;
fprintf(file, "MouseProc_Test(nCode = %i, wParam = %lu, lParam->pt = (%i, %i))\n", nCode, wParam, pHookStruct->pt.x, pHookStruct->pt.y);
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
VOID CALLBACK TimerProc_Test (HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
PostQuitMessage(0);
}
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nShow) {
fopen_s(&file, "wm_mouse_ll.txt", "w");
hHook = SetWindowsHookExW(WH_MOUSE_LL, LowLevelMouseProc_Test, LoadLibrary("User32.dll"), 0);
if (hHook != 0) {
fprintf(file, "SetWindowsHookExW Success (hHook = 0x%p)\n", hHook);
SetTimer(NULL, 0, 5000, TimerProc_Test);
MSG msg = {0};
while (GetMessageW(&msg, NULL, 0, 0) != 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (UnhookWindowsHookEx(hHook) != 0)
fputs("UnhookWindowsHookEx Success\n", file);
else
fprintf(file, "UnhookWindowsHookEx Error %u\n", GetLastError());
} else {
fprintf(file, "SetWindowsHookExW Error %u\n", GetLastError());
}
fclose(file);
return 0;
}
Ok, I simply restructured LowLevelMouseHook a little bit and added a custom Main() method that looks like the one below. UnhookWindowsHookEx now succeeds.
public static class EntryPoint {
public static void Main () {
LowLevelMouseHook.Begin() // Here SetWindowsHookEx is called
App.Main() // Original entrypoint
LowLevelMouseHook.End() // Here UnhookWindowsHookEx is called
}
}
Related
I have a SaveFileDialog in my WPF Project.
If the SaveFileDialog is closed via the red 'x' in the corner, I want the dialog to close and the user to be returned to the main application.
However, if the SaveFileDialog is 'canceled', I want the entire application to close.
The only issue I am having is knowing if the user has pressed the red x or the cancel button. They both evaluate to false in the code below.
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text file (*.txt)|*.txt";
var dlg = saveFileDialog.ShowDialog();
if (dlg == true)
{
//When the user presses save
File.WriteAllLines(saveFileDialog.FileName, rowList);
ExitApp();
}
else if (dlg == false)
{
//This occurs when red x or cancel is pressed.
}
Is there any way to distinguish between the two in WPF?
The answer is short and simple:
"no"
See Microsoft documentation here
It is not so simple, but you can do it by using hooks and the SetWindowsHookEx method.
First of all we need a class for registering and unregistering our hook:
public class WinHooker
{
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private HookProc hookDelegate;
private int hookHandle;
private bool wasClosedButtonPressed;
private static WinHooker instance;
private WinHooker()
{
}
public static WinHooker Instance
{
get
{
if (instance == null)
{
instance = new WinHooker();
}
return instance;
}
}
public bool WasClosedButtonPressed
{
get
{
return wasClosedButtonPressed;
}
}
public void Register()
{
wasClosedButtonPressed = false;
hookDelegate = this.HookProcHandler;
hookHandle = SetWindowsHookEx(5,
hookDelegate,
IntPtr.Zero,
AppDomain.GetCurrentThreadId());
}
public void Unregister()
{
UnhookWindowsHookEx(hookHandle);
hookHandle = 0;
hookDelegate = null;
}
private int HookProcHandler(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (nCode == 8 && wParam == 0xF060)
{
wasClosedButtonPressed = true;
}
}
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}
}
As you can see I used 5 as the first parameter for the SetWindowsHookEx, since it correspond to the WH_CBT value (refer to the SetWindowsHookEx page). Moreover the values in the method HookProcHandler (i.e. nCode == 8 && wParam == 0xF060) can be retrived from here.
Now let's use our class in the code:
SaveFileDialog saveFileDialog = new SaveFileDialog();
WinHooker.Instance.Register();
saveFileDialog.ShowDialog();
WinHooker.Instance.Unregister();
if (WinHooker.Instance.WasClosedButtonPressed)
{
MessageBox.Show("Oh my God! What have you done??");
}
As you can understand WasClosedButtonPressed is set to true just if the user closes the dialog by clicking on the red 'x' in the corner. Otherwise it is false.
I hope it can help you.
The subject is all in the title. Sorry if it's a duplicate, I really couldn't find anything on SO.
Well, many thanks to Anatoliy Nikolaev for steering me in the right direction! That article helped me much and this answer was useful to make code from the article work. This is what I have now:
internal static class MouseHook
{
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private static int _mouseHookHandle;
private static HookProc _mouseDelegate;
private static event MouseUpEventHandler MouseUp;
public static event MouseUpEventHandler OnMouseUp
{
add
{
Subscribe();
MouseUp += value;
}
remove
{
MouseUp -= value;
Unsubscribe();
}
}
private static void Unsubscribe()
{
if (_mouseHookHandle != 0)
{
int result = UnhookWindowsHookEx(_mouseHookHandle);
_mouseHookHandle = 0;
_mouseDelegate = null;
if (result == 0)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
}
private static void Subscribe()
{
if (_mouseHookHandle == 0)
{
_mouseDelegate = MouseHookProc;
_mouseHookHandle = SetWindowsHookEx(WH_MOUSE_LL,
_mouseDelegate,
GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
0);
if (_mouseHookHandle == 0)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
}
private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
if (wParam == WM_LBUTTONUP)
{
if (MouseUp != null)
{
MouseUp.Invoke(null, new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
}
}
}
return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
}
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONUP = 0x0202;
[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;
}
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string name);
}
public delegate void MouseUpEventHandler(object sender, Point p);
And using it is pretty simple:
MouseHook.OnMouseUp += MouseHookMouseUp;
I am working on a task to restore and maximize a window from system tray when the second instance of the same application starts.
When the 2nd instance starts and fail to grab the mutex. It calls the following code to signal the first instance to show itself:
public static void ShowFirstInstance()
{
WinApi.PostMessage(
(IntPtr)WinApi.HWND_BROADCAST,
WM_SHOWFIRSTINSTANCE,
IntPtr.Zero,
IntPtr.Zero);
}
The message is registered using the following:
public static readonly int WM_SHOWFIRSTINSTANCE =
WinApi.RegisterWindowMessage("WM_SHOWFIRSTINSTANCE|{0}", 250);
I have following in the code behind of the window to catch the message and show the window:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == SingleInstance.WM_SHOWFIRSTINSTANCE)
{
WinApi.ShowToFront(hwnd);
}
return IntPtr.Zero;
}
When I test it out. Whenever the first instance hide in the system tray, the message never caught. Do I miss anything?
Thanks,
Here's how I've done this in the past:
App.xaml.cs:
private static readonly Mutex Mutex = new Mutex(true, "{" + YourGuidHere + "}");
//return true if other instance is open, allowing for UI cleanup/suspension while shutdown() is completed
private bool EnforceSingleInstance()
{
//try...catch provides safety for if the other instance is closed during Mutex wait
try
{
if (!Mutex.WaitOne(TimeSpan.Zero, true))
{
var currentProcess = Process.GetCurrentProcess();
var runningProcess =
Process.GetProcesses().Where(
p =>
p.Id != currentProcess.Id &&
p.ProcessName.Equals(currentProcess.ProcessName, StringComparison.Ordinal)).FirstOrDefault();
if (runningProcess != null)
{
WindowFunctions.RestoreWindow(runningProcess.MainWindowHandle);
Shutdown();
return true;
}
}
Mutex.ReleaseMutex();
}
catch (AbandonedMutexException ex)
{
//do nothing, other instance was closed so we may continue
}
return false;
}
WindowFunctions:
//for enum values, see http://www.pinvoke.net/default.aspx/Enums.WindowsMessages
public enum WM : uint
{
...
SYSCOMMAND = 0x0112,
...
}
//for message explanation, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx
private const int SC_RESTORE = 0xF120;
public static void RestoreWindow(IntPtr hWnd)
{
if (hWnd.Equals(0))
return;
SendMessage(hWnd,
(uint)WM.SYSCOMMAND,
SC_RESTORE,
0);
}
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd,
uint msg,
uint wParam,
uint lParam);
I'm showing PDF documents in my application using Acrobat ActiveX.
I want to know is it possible to disable right click on PDF documents?
My previous solution didn't work, so there's another one. You just need to set a mouse hook on the child window that receives mouse notifications.
public class AcrobatReader : AxAcroPDF
{
private const int WH_MOUSE_LL = 14;
private const int WM_RBUTTONDOWN = 0x0204;
private static LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public AcrobatReader()
{
_hookID = SetWindowsHookEx(WH_MOUSE_LL, _proc, FindWindow(null, "avpageview"), 0);
}
~AcrobatReader()
{
if (_hookID != IntPtr.Zero)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
}
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && (IntPtr)WM_RBUTTONDOWN == wParam)
{
return new IntPtr(1);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
Whilst playing around with resources in my visual studio 10 project, I came across a build action called "Splash Screen", which led me to find this article on the neat splash screen capabilities of WPF.
Hunting around, I found this MSDN article on making a splash screen in Windows Forms, but it's a different type of approach: rather than loading the splash screen using native code before the app loads, the WinForms version simply displays it whilst the main form is initialising.
Is there any way to achieve this superior type of splash screen in a WinForms app?
Yes. I did an implementation for our WPF application before it came packaged with .NET 3.5 SP1.
Basically, you create a Native Win32 window and display a BMP image whilst you are loading the assemblies and initialising your application. You can use other image formats, but BMP is preferred as it requires the least number of libraries loaded.
A quick google for "creating native splash window" came up with several articles. The most comprehensive that I found (from a quick scan) was by Bradley Grainger: Displaying a Splash Screen with C++. The article was written for WPF in mind, however the concept the same: create a native window, launch your application, close window.
I will have a look at the source for my implementation tomorrow at work and update my answer with an examples.
Until then, google and research the many examples already out there to get started.
UPDATE
As promised, below is a full example of implementing a splash screen (there is a bit to it).
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Interop;
namespace SplashScreen
{
public class SplashScreenManager
{
static SplashScreen _current = null;
static SplashScreenManager() {}
SplashScreenManager() { }
public static SplashScreen Create(Module module, int resourceID)
{
if (_current != null)
{
_current.Close();
_current.Dispose();
}
_current = new SplashScreen(module, resourceID);
return _current;
}
public static void Close()
{
if (_current == null)
return;
_current.Close();
_current.Dispose();
_current = null;
}
public static SplashScreen Current
{
get { return _current; }
}
}
public class SplashScreen : IDisposable
{
static bool IsClassRegistered = false;
static string WindowClassName = "SplashScreenWindowClass";
IntPtr _bitmapHandle = IntPtr.Zero;
int _bitmapHeight;
int _bitmapWidth;
bool _isClosed;
UnsafeNativeMethods.WndProc _splashWindowProcedureCallback;
IntPtr _windowHandle = IntPtr.Zero;
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal SplashScreen(Module module, int resourceID)
{
_bitmapHandle = UnsafeNativeMethods.LoadBitmap(Marshal.GetHINSTANCE(module), new IntPtr(resourceID));
_splashWindowProcedureCallback = new UnsafeNativeMethods.WndProc(SplashWindowProcedure);
}
public void Close()
{
if (_isClosed)
return;
_isClosed = true;
UnsafeNativeMethods.PostMessage(new HandleRef(this, _windowHandle), 0x10, IntPtr.Zero, IntPtr.Zero);
if (_bitmapHandle != IntPtr.Zero)
{
UnsafeNativeMethods.DeleteObject(_bitmapHandle);
_bitmapHandle = IntPtr.Zero;
}
}
public void Close(IntPtr handle)
{
if (_windowHandle != IntPtr.Zero)
UnsafeNativeMethods.SetForegroundWindow(handle);
Close();
}
void CreateWindow()
{
if (!IsClassRegistered)
RegisterClass();
if (IsClassRegistered)
{
CreateWindowInternal();
if (_windowHandle != IntPtr.Zero)
UnsafeNativeMethods.ShowWindow(_windowHandle, 5);
}
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
Close(IntPtr.Zero);
GC.SuppressFinalize(this);
}
void GetBitmapDimensions()
{
int cb = Marshal.SizeOf(typeof(UnsafeNativeMethods.BITMAP));
IntPtr lpvObject = Marshal.AllocCoTaskMem(cb);
UnsafeNativeMethods.GetObject(_bitmapHandle, cb, lpvObject);
UnsafeNativeMethods.BITMAP bitmap = (UnsafeNativeMethods.BITMAP)Marshal.PtrToStructure(lpvObject, typeof(UnsafeNativeMethods.BITMAP));
_bitmapWidth = bitmap.bmWidth;
_bitmapHeight = bitmap.bmHeight;
Marshal.FreeCoTaskMem(lpvObject);
}
void OnPaint(IntPtr hdc)
{
if (_bitmapHandle != IntPtr.Zero)
{
IntPtr ptr = UnsafeNativeMethods.CreateCompatibleDC(hdc);
IntPtr hgdiobj = UnsafeNativeMethods.SelectObject(ptr, _bitmapHandle);
UnsafeNativeMethods.BitBlt(hdc, 0, 0, _bitmapWidth, _bitmapHeight, ptr, 0, 0, 0xcc0020);
UnsafeNativeMethods.SelectObject(ptr, hgdiobj);
UnsafeNativeMethods.DeleteDC(ptr);
}
}
void CreateWindowInternal()
{
int systemMetrics = UnsafeNativeMethods.GetSystemMetrics(0);
int num4 = UnsafeNativeMethods.GetSystemMetrics(1);
uint dwStyle = 0x80000000;
uint dwExStyle = 0x188;
IntPtr moduleHandle = UnsafeNativeMethods.GetModuleHandle(null);
IntPtr desktopWindow = UnsafeNativeMethods.GetDesktopWindow();
_windowHandle = UnsafeNativeMethods.CreateWindowEx(dwExStyle, WindowClassName, "", dwStyle, (systemMetrics - _bitmapWidth) / 2, (num4 - _bitmapHeight) / 2, _bitmapWidth, _bitmapHeight, desktopWindow, IntPtr.Zero, moduleHandle, IntPtr.Zero);
}
void RegisterClass()
{
IntPtr moduleHandle = UnsafeNativeMethods.GetModuleHandle(null);
UnsafeNativeMethods.WNDCLASSEX lpwcx = new UnsafeNativeMethods.WNDCLASSEX();
lpwcx.cbSize = (uint)Marshal.SizeOf(typeof(UnsafeNativeMethods.WNDCLASSEX));
lpwcx.cbClsExtra = 0;
lpwcx.cbWndExtra = 0;
lpwcx.hbrBackground = IntPtr.Zero;
lpwcx.hCursor = IntPtr.Zero;
lpwcx.hIcon = IntPtr.Zero;
lpwcx.hIconSm = IntPtr.Zero;
lpwcx.hInstance = moduleHandle;
lpwcx.lpfnWndProc = _splashWindowProcedureCallback;
lpwcx.lpszClassName = WindowClassName;
lpwcx.lpszMenuName = null;
lpwcx.style = 0;
if (UnsafeNativeMethods.RegisterClassExW(ref lpwcx) != 0)
{
IsClassRegistered = true;
}
}
public void Show()
{
if (_windowHandle == IntPtr.Zero)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.IsBackground = true;
thread.Start();
}
}
[SuppressUnmanagedCodeSecurity]
IntPtr SplashWindowProcedure(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == 15)
{
UnsafeNativeMethods.PAINTSTRUCT lpPaint = new UnsafeNativeMethods.PAINTSTRUCT();
IntPtr hdc = UnsafeNativeMethods.BeginPaint(hWnd, out lpPaint);
OnPaint(hdc);
UnsafeNativeMethods.EndPaint(hWnd, ref lpPaint);
return IntPtr.Zero;
}
return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
void ThreadMethod()
{
if (_bitmapHandle != IntPtr.Zero)
{
GetBitmapDimensions();
CreateWindow();
MSG msg = new MSG();
while (UnsafeNativeMethods.GetMessage(ref msg, _windowHandle, 0, 0) > 0)
{
UnsafeNativeMethods.TranslateMessage(ref msg);
UnsafeNativeMethods.DispatchMessage(ref msg);
}
_windowHandle = IntPtr.Zero;
GC.KeepAlive(this);
}
}
}
[SuppressUnmanagedCodeSecurity]
internal sealed class UnsafeNativeMethods
{
// Fields
internal const int GWL_EXSTYLE = -20;
public const int LOGPIXELSX = 0x58;
public const int LOGPIXELSY = 90;
internal const uint MB_ICONASTERISK = 0x40;
internal const uint MB_ICONERROR = 0x10;
internal const uint MB_ICONEXCLAMATION = 0x30;
internal const uint MB_ICONHAND = 0x10;
internal const uint MB_ICONINFORMATION = 0x40;
internal const uint MB_ICONQUESTION = 0x20;
internal const uint MB_ICONWARNING = 0x30;
internal const uint MB_OK = 0;
internal const uint MB_OKCANCEL = 1;
internal const uint MB_SETFOREGROUND = 0x10000;
internal const uint MB_YESNO = 4;
internal const uint MB_YESNOCANCEL = 3;
internal const int SM_CXSCREEN = 0;
internal const int SM_CYSCREEN = 1;
public const int SPI_GETWORKAREA = 0x30;
internal const uint SRCCOPY = 0xcc0020;
public const int SW_HIDE = 0;
internal const int SW_SHOW = 5;
public const int SW_SHOWMAXIMIZED = 3;
public const int SW_SHOWMINIMIZED = 2;
public const int SW_SHOWNORMAL = 1;
internal const int WM_CLOSE = 0x10;
internal const uint WS_EX_TOOLWINDOW = 0x80;
internal const uint WS_EX_TOPMOST = 8;
internal const uint WS_EX_TRANSPARENT = 0x20;
internal const uint WS_EX_WINDOWEDGE = 0x100;
internal const uint WS_POPUP = 0x80000000;
UnsafeNativeMethods() {}
[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll")]
public static extern IntPtr ChildWindowFromPoint(IntPtr hWndParent, POINT Point);
[DllImport("user32.dll")]
public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll")]
internal static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("gdi32.dll")]
internal static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("gdi32.dll")]
internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("user32.dll", EntryPoint = "CreateWindowExW", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll")]
internal static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("gdi32.dll")]
internal static extern bool DeleteDC(IntPtr hdc);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("gdi32.dll")]
internal static extern bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
internal static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern IntPtr GetDC(HandleRef hWnd);
[DllImport("user32.dll")]
internal static extern IntPtr GetDesktopWindow();
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hDC, int index);
[DllImport("user32.dll", EntryPoint = "GetMessageW", CharSet = CharSet.Unicode, ExactSpelling = true)]
internal static extern int GetMessage([In, Out] ref MSG msg, IntPtr hWnd, int uMsgFilterMin, int uMsgFilterMax);
[DllImport("kernel32.dll", EntryPoint = "GetModuleHandleW", CharSet = CharSet.Unicode)]
internal static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("gdi32.dll")]
internal static extern int GetObject(IntPtr hgdiobj, int cbBuffer, IntPtr lpvObject);
[DllImport("user32.dll")]
internal static extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int GetWindowLong(IntPtr handle, int index);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll", EntryPoint = "LoadBitmapW", CharSet = CharSet.Unicode)]
internal static extern IntPtr LoadBitmap(IntPtr hInstance, IntPtr lpBitmapName);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[return: MarshalAs(UnmanagedType.U2)]
[DllImport("user32.dll")]
internal static extern short RegisterClassExW([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int SetWindowLong(IntPtr handle, int index, int dwNewLong);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
internal static extern bool TranslateMessage([In] ref MSG lpMsg);
[StructLayout(LayoutKind.Sequential)]
internal struct BITMAP
{
public int bmType;
public int bmWidth;
public int bmHeight;
public int bmWidthBytes;
public ushort bmPlanes;
public ushort bmBitsPixel;
public IntPtr bmBits;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PAINTSTRUCT
{
public IntPtr hdc;
public bool fErase;
public UnsafeNativeMethods.RECT rcPaint;
public bool fRestore;
public bool fIncUpdate;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public byte[] rgbReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public int Length;
public int Flags;
public int ShowCmd;
public UnsafeNativeMethods.POINT MinPosition;
public UnsafeNativeMethods.POINT MaxPosition;
public UnsafeNativeMethods.RECT NormalPosition;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WNDCLASSEX
{
public uint cbSize;
public uint style;
public UnsafeNativeMethods.WndProc lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
public IntPtr hIconSm;
}
internal delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
}
}
It is possible to use a non-WPF form for this, and it works quite happily. As an aside, it was a feature in VS2008 as well.