Clipboard Spy
An application can be notified of changes in the data stored in the Windows clipboard by registering itself as a Clipboard Viewer.Clipboard viewers use two API calls and several messages to communicate with the Clipboard viewer chain. SetClipboardViewer adds a window to the beginning of the chain and returns a handle to the next viewer in the chain. ChangeClipboardChain removes a window from the chain.
When a clipboard change occurs, the first window in the clipboard viewer chain is notified via the WM_DrawClipboard message and must pass the message on to the next window. To do this, our application must store the next window along in the chain to forward messages to and also respond to the WM_ChangeCBChain message which is sent whenever any other clipboard viewer on the system is added or removed to ensure the next window along is valid.
Delphi Clipboard Spy
As a demo project we'll create a simple clipboard viewer application that displays the text in the Windows clipboard in a Memo component. The content of a memo is updated every time when the text in clipboard changes (format: cf_text).To attach our application to the clipboard viewer notification chain we call the SetClipboardViewer function in the OnCreate event handler procedure of the main form.
procedure TForm1.FormCreate(Sender: TObject) ;
begin
NextInChain := SetClipboardViewer(Handle) ;
end;
The NextInChain variable is of THandle type, declared as global in the form1's unit. NextInChain represents our program as the next window in the clipboard viewer chain.
...
var
Form1: TForm1;
NextInChain : THandle;
implementation
uses ClipBrd;
...
To perform a certain task whenever the contents of the clipboard changes we need to receive (and respond) to a WM_DrawClipboard message. The following procedure checks whether there is a "new" text stored in a clipboard and if so pastes this text to a memo component on a form. In any case after responding to WM_DrawClipboard message application must pass it to the next clipboard viewer.
procedure TForm1.WMDrawClipboard(var Msg:TMessage) ;
begin
if Clipboard.HasFormat(cf_text) then
begin
Memo1.Lines.Clear;
Memo1.PasteFromClipboard
end
else
begin
//do something with other clipboard formats
end;
//pass the message on to the next window
if NextInChain <> 0 then
SendMessage(NextInChain, WM_DrawClipboard, 0, 0)
end;
It may seem that this is all we have to code, but... The WM_ChangeCBChain messages (chain fixing functions) needs to be handled every time any other clipboard viewer chain is changed. The message brings with it the information about the window being removed and the next window in the clipboard viewer chain.
procedure TForm1.WMChangeCBChain(var Msg: TMessage) ;
var
Remove, Next: THandle;
begin
Remove := Msg.WParam;
Next := Msg.LParam;
with Msg do
if NextInChain = Remove then
NextInChain := Next
else if NextInChain <> 0 then
SendMessage(NextInChain, WM_ChangeCBChain, Remove, Next)
end;
Finally, when we are ready to terminate our application we have to call the ChangeClipboardChain API function to remove our window from the chain of clipboard viewers. Naturally in the OnDestroy event handler:
procedure TForm1.FormDestroy(Sender: TObject) ;
begin
ChangeClipboardChain(Handle, NextInChain) ;
end;
I have merely forgot this one: we, of course, have to declare all those message handling procedure in the, let's say, private part of the forms interface section:
procedure WMDrawClipboard(var Msg: TMessage) ; message WM_DRAWCLIPBOARD;
procedure WMChangeCBChain(var Msg: TMessage) ; message WM_CHANGECBCHAIN;
To see this code in real action, compile and run this project. Try cutting and copying some text to the clipboard...whoa, the text is displayed in our application window! We have produced a real clipboard textual spy.
