Reputation: 181
I have a problem. I am trying to use the ClipboardMonitor for my C# Application. The goal is to monitor every change in the clipboard. To start monitoring:
AddClipboardFormatListener(this.Handle);
To stop the listener:
RemoveClipboardFormatListener(this.Handle);
And the override WndProc() method:
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
IDataObject iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Text))
{
ClipboardMonitor_OnClipboardChange((string)iData.GetData(DataFormats.Text));
}
break;
default:
base.WndProc(ref m);
break;
}
}
And of course the DLL import:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
But when putting a breakpoint at the method call ClipboardMonitor_OnClipboardChange
and changing the clipboard, I never get the method called.
How can I change my code so that I receive a WM_ message notifying me that the clipboard has changed?
Upvotes: 9
Views: 5938
Reputation: 212
Just to add to theB's excellent answer ...
The WM_DRAWCLIPBOARD message is an older message used after setting up a "Clipboard Viewer Window." This dates back at least to Windows 95. Starting with Windows Vista and Windows Server 2008, the "Clipboard Format Listener" API was added.
The old Clipboard Viewer Window required more work:
Add your window into the chain of objects receiving clipboard messages by calling the SetClipboardViewer() API.
You receive a WM_CHANGECBCHAIN message when another window in the chain adds or removes itself from the chain, and you are required to process that message by passing it on to the next window in the chain with the SendMessage() API.
When you receive the WM_DRAWCLIPBOARD message, you also need to pass the message along to the next object (hwnd) in the chain, using the SendMessage() API. Then you can process the changed contents of the Clipboard.
When you close your program, you need to remove it from the chain, using the ChangeClipboardChain() API.
The newer Clipboard Format Listener only requires:
Start monitoring the clipboard by calling the AddClipboardFormatListener() API.
Wait for the WM_CLIPBOARDUPDATE message to notify you of changed clipboard contents.
Before closing your program, stop the monitoring by calling RemoveClipboardFormatListener().
All issues relating to the chain of notification are handled automatically by Windows.
Another key difference: WM_DRAWCLIPBOARD is a sent (non-queued) message, which will have to be processed by your program immediately. WM_CLIPBOARDUPDATE is a posted (queued) message, which will be added to a queue, where it will be handled after your program is finished executing its current process.
This means that when the WM_DRAWCLIPBOARD message comes through, your program will have to process it immediately, even if you are in the middle executing code. For example, let's say you've created a clipboard monitor that allows you to save and retrieve past clips, and load those past clips to the clipboard. You've decided to use the older SetClipboardViewer() API, which will trigger the WM_DRAWCLIPBOARD message when the clipboard changes. When you execute the code to load the old clip back into the clipboard, you have just changed the clipboard contents, which will trigger the message. If you are stepping through your C# code line by line in Visual Studio, the Clipboard.SetText() method will trigger the message and you will be taken immediately back to your WndProc() method, starting that whole process all over again. When the WndProc() method is finished, you will continue on to the next line of code after Clipboard.SetText().
If instead, you use the newer AddClipboardFormatListener() API, you can use the Clipboard.SetText() method also, but it won't trigger an immediate message. The WM_CLIPBOARDUPDATE message is added to a queue, and it won't be processed by your program until your code is finished executing. If you are stepping through the code line by line, you can execute the Clipboard.SetText() method and simply move on to the next line. When your program finishes whatever it is doing, you will then be taken back to the WndProc() method to deal with the WM message. Here is some Microsoft documentation about message queuing and routing: Windows Message Routing
There will be times when our program will need to treat WM messages in different ways, depending on what has triggered the message. If the end user copies a new clip into the clipboard, we will want to process that WM message. If the end user uses our program to load a saved old clip back into the clipboard, we will want our program to ignore the WM message that results from that. One way to ignore a WM message is to create a public class integer property called "Ignore". Every time our code changes the clipboard contents (instead of the user copying something new), we FIRST increment (increase) the Class.Ignore property. We add an If statement in the WndProc() method to only process a WM clipboard message if the ignore property is zero. If Class.Ignore is greater than zero, WndProc() will ignore the message. Every time it ignores a message, it also decrements (decreases) the Class.Ignore integer. That is how the program can know if the WM_ message was generated from copying a new clip, or from an old clip being re-loaded into the clipboard.
Just remember that the newer AddClipboardFormatListener() API requires Windows Vista (Client machine) or Windows Server 2008 (server machine) or later. But according to this article in August, 2021, only about 0.5% or less of Windows users are still on XP or older.
Here is a writeup for implementing the Clipboard Format Listener in C#:
Monitor for clipboard changes using AddClipboardFormatListener
And here is the Microsoft Documentation (in C++) for anyone interested:
Microsoft Documentation on monitoring the clipboard
Best of luck to everyone in their coding.
Upvotes: 5
Reputation: 339
SharpClipboard as a library could be of more benefit as it encapsulates the same features into one fine component library. You can then access its ClipboardChanged
event and detect various data-formats when they're cut/copied.
You can choose the various data-formats you want to monitor:
var clipboard = new SharpClipboard();
clipboard.ObservableFormats.Texts = true;
clipboard.ObservableFormats.Files = true;
clipboard.ObservableFormats.Images = true;
clipboard.ObservableFormats.Others = true;
Here's an example using its ClipboardChanged
event:
private void ClipboardChanged(Object sender, ClipboardChangedEventArgs e)
{
// Is the content copied of text type?
if (e.ContentType == SharpClipboard.ContentTypes.Text)
{
// Get the cut/copied text.
Debug.WriteLine(clipboard.ClipboardText);
}
// Is the content copied of image type?
else if (e.ContentType == SharpClipboard.ContentTypes.Image)
{
// Get the cut/copied image.
Image img = clipboard.ClipboardImage;
}
// Is the content copied of file type?
else if (e.ContentType == SharpClipboard.ContentTypes.Files)
{
// Get the cut/copied file/files.
Debug.WriteLine(clipboard.ClipboardFiles.ToArray());
// ...or use 'ClipboardFile' to get a single copied file.
Debug.WriteLine(clipboard.ClipboardFile);
}
// If the cut/copied content is complex, use 'Other'.
else if (e.ContentType == SharpClipboard.ContentTypes.Other)
{
// Do something with 'e.Content' here...
}
}
You can also find out the application that the cut/copy event occurred on together with its details:
private void ClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEventArgs e)
{
// Gets the application's executable name.
Debug.WriteLine(e.SourceApplication.Name);
// Gets the application's window title.
Debug.WriteLine(e.SourceApplication.Title);
// Gets the application's process ID.
Debug.WriteLine(e.SourceApplication.ID.ToString());
// Gets the application's executable path.
Debug.WriteLine(e.SourceApplication.Path);
}
There are also other events such as the MonitorChanged
event which listens whenever clipboard-monitoring is disabled, meaning that you can enable or disable monitoring the clipboard at runtime.
In addition to all this, since it's a component, you can use it in Designer View by dragging-and-dropping it to a Windows Form, making it super easy for anyone to customize its options and work with its inbuilt events.
SharpClipboard seems to be the very best option for clipboard-monitoring scenarios in .NET.
Upvotes: 4
Reputation: 6728
The problem is that you're handling the wrong window message. Quoting the documentation for AddClipboardFormatListener
:
When a window has been added to the clipboard format listener list, it is posted a
WM_CLIPBOARDUPDATE
message whenever the contents of the clipboard have changed.
With that knowledge, change the code to:
const int WM_CLIPBOARDUPDATE = 0x031D;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_CLIPBOARDUPDATE:
IDataObject iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Text))
{
string data = (string)iData.GetData(DataFormats.Text);
}
break;
default:
base.WndProc(ref m);
break;
}
}
Upvotes: 12