I want to monitor clipboard changes (only text). I try to find some solutions and I found the following questions on StackOverflow:
But my problem/requirement is that I don't want to add a dependency to Windows.Forms nor WPF (my app is a console app). I try to look at available methods in user32.dll. I wrote a code that creates a window and window styles. Override a WndPrc method to a custom one and then add a listener to clipboard changes. WndProcFunction was invoked 4 times for the following messages:
But when I change the content of a clipboard, message of type WM_CLIPBOARDUPDATE nor any message that would fit into WndProcFunction was sent. I try to use an "old API" (SetDashboardViewer
) but it changes nothing. Code looks as follows:
using PInvoke;
...
internal static class User32Ext
{
[DllImport("user32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
[DllImport("kernel32.dll")]
internal static extern uint GetLastError();
}
class Program
{
private static IntPtr _nextInChain = IntPtr.Zero;
internal static unsafe IntPtr WndProcFunction(IntPtr hwnd, User32.WindowMessage windowMessage, void* wParam1, void* lParam1)
{
if (windowMessage == User32.WindowMessage.WM_CREATE)
{
var listener = User32Ext.AddClipboardFormatListener(hwnd);
var result = User32.OpenClipboard(window);
//_nextInChain = User32Ext.SetClipboardViewer(hwnd);
}
if (windowMessage == User32.WindowMessage.WM_CLIPBOARDUPDATE)
{
var pointerToText = User32.GetClipboardData_IntPtr(1);
var text = Marshal.PtrToStringAnsi(pointerToText);
Console.WriteLine(text);
}
if (windowMessage == User32.WindowMessage.WM_DRAWCLIPBOARD)
{
if (_nextInChain != IntPtr.Zero)
{
User32.SendMessage(_nextInChain, windowMessage, wParam1, lParam1);
}
}
if (windowMessage == User32.WindowMessage.WM_CHANGECBCHAIN)
{
_nextInChain = hwnd;
//send message...
}
if (windowMessage == User32.WM_DESTROY)
{
//chain msg
User32Ext.RemoveClipboardFormatListener(hwnd);
}
return hwnd; //success
}
static void Main(string[] args)
{
unsafe
{
var hInstance = Marshal.GetHINSTANCE(typeof(Program).Module);
string name = "Test";
User32.WNDCLASSEX wndClassEx = new User32.WNDCLASSEX
{
cbSize = Marshal.SizeOf(typeof(User32.WNDCLASSEX)),
style = User32.ClassStyles.CS_GLOBALCLASS,
cbClsExtra = 0,
cbWndExtra = 0,
hbrBackground = IntPtr.Zero,
hCursor = IntPtr.Zero,
hIcon = IntPtr.Zero,
hIconSm = IntPtr.Zero,
lpszMenuName = null,
hInstance = hInstance,
lpfnWndProc = new User32.WndProc(WndProcFunction)
};
var stringPtr = Marshal.StringToHGlobalAuto(name);
wndClassEx.lpszClassName_IntPtr = stringPtr;
var register = User32.RegisterClassEx(ref wndClassEx);
var window = User32.CreateWindowEx(
User32.WindowStylesEx.WS_EX_TRANSPARENT,
name,
"Test Window",
0,
0, 0, 0, 0,
IntPtr.Zero,
IntPtr.Zero,
wndClassEx.hInstance,
IntPtr.Zero
);
User32.SetClipboardData(13, stringPtr);
...
}
}
}
Is there something I'm still missing in the code, some additional listener? I look at error codes after each call to a method(s) from user32.dll. But all succeed without any failure.
A message loop is required for receiving messages. If you don't want a window you can create a message-only window which enables you to send and receive messages.
It is not visible, has no z-order, cannot be enumerated, and does not receive broadcast messages. The window simply dispatches messages.
In C#, at the end of your main
function, it will like this:
MSG msg;
while (User32.GetMessage(out msg, IntPtr.Zero, 0, 0) != 0)
{
User32.TranslateMessage(ref msg);
User32.DispatchMessage(ref msg);
}
Refer to MSG, Creating a Message Loop.