How to monitor the clipboard in Visual Basic
Posted March 11th, 2009 by Ryan Olbe Visual Basic How-To's Add commentsI've always been fascinated by clipboard monitoring programs, you know those little programs that sit in the background and record everything that gets copied to the windows clipboard. However, sometimes it's difficult to find these types of programs in search engines because they're known by so many different names. After spending a number of hours searching in Google and other various shareware sites I was able to compile this list of windows clipboard monitoring programs:
ClipX
Hamsin Clipboard
CLCL
Ditto
ArsClip
Clipdiary
Visual Clipboard
ClipGuru
Yankee Clipper III
Clipboard Recorder
ReClip
TrayClip
Clip-O-Matic
ClipMate
Clipboard Genie
Clipboard Helper
All of these programs do basically the same thing, they monitor and record the windows clipboard. But how exactly do they do it? I recently looked online for some type of guide or tutorial that could show me how it's done in Visual Basic but I couldn't find anything so I decided to write my own. Before writing this guide, I wanted to find out as much information as I could about the windows clipboard and more specifically how clipboard monitoring works. I found two great books on the subject, Programming Windows by Charles Petzold and Subclassing and Hooking with Visual Basic by Stephen Teilhet for Visual Basic specific information. Both of which are excellent and I highly suggest picking them up.
Here's a simple step-by-step guide that shows how to create your own Visual Basic program that can monitor what gets copied into the windows clipboard.
Download clipboard_monitor.zip (March 11, 2009)
How to monitor the windows clipboard in Visual Basic
1. First, create a new Visual Basic project and add a textbox and 3 buttons to a form like so. Name your textbox, Text1, your Exit Button, btnExit, your Copy button, btnCopy, and your Clear button, btnClear.
2. Then go into Code View and add this code to your form.
Private Sub btnExit_Click() Unload Me End Sub Private Sub btnClear_Click() Text1.Text = "" End Sub
3. Next, add this code for your Copy button.
Private Sub btnCopy_Click() If clipboard_is_hooked = True Then clipboard_is_hooked = unhook_clipboard(Me.hWnd) End If Clipboard.Clear Clipboard.SetText Text1.Text If clipboard_is_hooked = False Then Text1.Text = "" clipboard_is_hooked = hook_clipboard(Me.hWnd) End If End Sub
You may be thinking, why not just use Clipboard.SetText Text1.Text? It would be a lot easier and shorter. Well, in most cases you could do that, but in this case you can't because when the clipboard monitor is active it records everything that gets copied to the clipboard even when it's performed via your own code. If you had the letter "a" in your textbox and you clicked the Copy button you would see "aa". Why? Because the clipboard monitor says "you just performed a copy operation, here's what you copied", even though you performed that copy operation via code. To get around that, first you have to check if the clipboard is already being hooked. If it is, that means the clipboard is being monitored and everything that gets copied to the clipboard is being mirrored into the textbox. You have to unhook it first so that it can copy what's in the textbox then re-attach the hook afterwards.
Next, you copy what's in the textbox with "Clipboard.Clear" and "Clipboard.SetText Text1.Text" and test to see if the clipboard hook is turned off. If it is, clear the textbox and turn the hook back on. The reason you have to clear the textbox first is because when the clipboard hook is turned on for the first time it pastes whatever's currently in the clipboard to the textbox. In this case the entire contents of your textbox is in the clipboard and since you don't want two copies in there you have to clear it out first.
4. Next, add this code to your form to take care of your Form_Load and Form_Unload events.
Private Sub Form_Load() If clipboard_is_hooked = False Then clipboard_is_hooked = hook_clipboard(Me.hWnd) End If End Sub Private Sub Form_Unload(Cancel As Integer) If clipboard_is_hooked = True Then clipboard_is_hooked = unhook_clipboard(Me.hWnd) End If End Sub
5. Next, add a new Module to your project and paste in this code.
Option Explicit Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Public Declare Function SetClipboardViewer Lib "user32" (ByVal hWnd As Long) As Long Public Declare Function ChangeClipboardChain Lib "user32" (ByVal hWnd As Long, ByVal hWndNext As Long) As Long Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long Public Declare Function InitCommonControlsEx Lib "comctl32.dll" (iccex As tagInitCommonControlsEx) As Boolean Public Type tagInitCommonControlsEx lngSize As Long lngICC As Long End Type Public Const GWL_WNDPROC = (-4&) Public Const WM_SYSCOMMAND = &H112 Public Const WM_DRAWCLIPBOARD = &H308 Public Const WM_CHANGECBCHAIN = &H30D Public Const ICC_USEREX_CLASSES = &H200 Public old_window_procedure As Long Public next_clipboard_viewer As Long Public clipboard_is_hooked As Boolean Public frm1 As Form1 Public Sub Main() On Error GoTo 0 Dim iccex As tagInitCommonControlsEx iccex.lngSize = LenB(iccex) iccex.lngICC = ICC_USEREX_CLASSES InitCommonControlsEx iccex Set frm1 = New Form1 Load frm1 frm1.Show End Sub
This basically just declares your Win32 API functions and constants and enables Windows XP style controls. Also remember to set your Project's Startup Object to "Sub Main" and not "Form1" in your Project Properties window.
6. Next add this function to your module to turn on the actual clipboard hook.
Public Function hook_clipboard(ByVal window_handle As Long) As Boolean old_window_procedure = SetWindowLong(window_handle, GWL_WNDPROC, AddressOf new_window_procedure) next_clipboard_viewer = SetClipboardViewer(window_handle) If (old_window_procedure <> 0) Then hook_clipboard = True Else hook_clipboard = False End If End Function
This function uses SetWindowLong to specify a new window procedure called new_window_procedure and it's stores the address of the old window procedure in old_window_procedure. Then, it adds itself as a clipboard viewer using the SetClipboardViewer function and it puts the next window in the clipboard viewer chain in next_clipboard_viewer. If SetWindowLong was successful, old_window_procedure would be set to something that's not zero. If that's the case, this function returns true, otherwise it returns false.
7. Next, add this function to your module to turn off the clipboard hook.
Public Function unhook_clipboard(ByVal window_handle As Long) As Boolean If (next_clipboard_viewer <> 0) Then Call ChangeClipboardChain(window_handle, next_clipboard_viewer) End If If (old_window_procedure <> 0) Then Call SetWindowLong(window_handle, GWL_WNDPROC, old_window_procedure) End If unhook_clipboard = False End Function
This function first checks to see if next_clipboard_viewer was set. It it was, that means my window is part of the clipboard chain and I don't want it to be anymore so I remove it with the ChangeClipboardChain function. Then I check to see if old_window_procedure was set. If it was, that means I'm the one who set it in my hook_clipboard function and since I'm unhooking the clipboard now I need to reset my window's procedure back to what it originally was using SetWindowLong again.
8. Finally, add this code to your module to specify your new window procedure.
Public Function new_window_procedure(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case Msg
Case WM_CHANGECBCHAIN
If wParam = next_clipboard_viewer Then
next_clipboard_viewer = lParam
ElseIf (next_clipboard_viewer <> 0) Then
Call SendMessage(next_clipboard_viewer, Msg, wParam, lParam)
End If
Case WM_DRAWCLIPBOARD
If Clipboard.GetFormat(vbCFText) Then
frm1.Text1.Text = frm1.Text1.Text & Left$(Clipboard.GetText, 32767) & vbNewLine
End If
If (next_clipboard_viewer <> 0) Then
Call SendMessage(next_clipboard_viewer, Msg, wParam, lParam)
End If
End Select
new_window_procedure = CallWindowProc(old_window_procedure, hWnd, Msg, wParam, lParam)
End Function
This is the real heart of the program. It looks for the WM_CHANGECBCHAIN and WM_DRAWCLIPBOARD messages and processes them accordingly. In my program I just mirror everything that gets copied to the clipboard into a textbox but with a little work you could modify this to do any number of things. You can tell what kind of data is in the clipboard by using the Clipboard.GetFormat() function and comparing it with these values:
Constant Value Description --------------------------------------- vbCFBitmap 2 Bitmap (.BMP file) vbCFDIB 8 Device-independent bitmap vbCFEMetafile 14 Enhanced metafile (.EMF file) vbCFFiles 15 Filename list (Microsoft Windows Explorer) vbCFLink &HFFFFBF00 DDE conversation information vbCFMetafile 3 Metafile (.WMF file) vbCFPalette 9 Color palette vbCFRTF &HFFFFBF01 Rich Text Format (.RTF file) vbCFText 1 Text (.TXT file)
When dealing with the Clipboard you can also use these built-in VB Clipboard functions:
Clipboard.Clear() Clears the contents of the system Clipboard. Clipboard.GetData([Format]) As IPictureDisp Returns a graphic from the Clipboard object. Clipboard.GetFormat(Format As Integer) As Boolean Returns a value indicating whether an item on the Clipboard object matches a specified format. Clipboard.GetText([Format]) As String Returns a text string from the Clipboard object. Clipboard.SetData(Picture As IPictureDisp, [Format]) Puts a picture on the Clipboard object using the specified graphic format. Clipboard.SetText(Str As String, [Format]) Puts a text string on the Clipboard object using the specified Clipboard object format.
Download clipboard_monitor.zip (March 11, 2009)
Filelist
Form1.frm
Module1.bas

This is worth compilation.
Today, I spent more than 10 hours to write simple clipboard tool.
I am used FreeClip, Spartan... But I when try to copy a large access table, those programs make the process very slowly.
I don't need every format to be captured. I need text.
So, I tried to write my own simple tool.
I accomplished much... But I need to set shortcuts to paste directly. For example, if I hit Ctrl+Shift+2 I want to paste the last 2nd item.
To do this, I tired SendKeys. But sometimes working, sometimes don't.
And I began to search for some APIs for this... I found a lot of samples... Until now, I don't success.
For example this address is useful. http://www.vbforums.com/showthread.php?t=316373
I'll struggle a little more... I hope
And I'll examine your code.
I hope...
Thanks...