Clipboard SpyAn 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 SpyAs 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.
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.
procedure TForm1.FormCreate(Sender: TObject) ; begin NextInChain := SetClipboardViewer(Handle) ; end;
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.
... var Form1: TForm1; NextInChain : THandle; implementation uses ClipBrd; ...
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.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;
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.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;
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 TForm1.FormDestroy(Sender: TObject) ; begin ChangeClipboardChain(Handle, NextInChain) ; end;
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.
procedure WMDrawClipboard(var Msg: TMessage) ; message WM_DRAWCLIPBOARD; procedure WMChangeCBChain(var Msg: TMessage) ; message WM_CHANGECBCHAIN;