A. The Clipboard

Page 435
  V. APPENDIXES  
Page 437
  A. The Clipboard  
   
  As you know, the clipboard is a Windows feature that enables us to pass data between applications. Visual Basic has a Clipboard object that allows VB programmers to use the clipboard in VB programs. Nevertheless, I can think of two really good reasons for using the Win32 API clipboard functions.  
 
  The Windows Clipboard  
   
  One reason is that the VB Clipboard object is part of the Visual Basic object model (vb5.olb and vb6.olb), not the Visual Basic for Applications object model (vba5.dll and vba6.dll). Accordingly, the Clipboard object is not available when programming in VBA, such as when programming in Microsoft Office. There have been many times when I wanted to use the Clipboard object when programming in Microsoft Word, Excel, or Access, but this does not seem possible without using the Win32 API clipboard functions. (Naturally, my first thought was to simply add a reference to the vb6.olb object library to the VBA project. However, when I tried this with Word 97, I was rewarded with a General Protection Fault. Moreover, the system would not let me back into my document, so I had to reboot the computer!)  
   
  The second reason for using API clipboard functions is to create a clipboard viewer, using Visual Basic. One of the most useful little utilities is a clipboard viewer that will collect multiple items for pasting into programs. (As you know, the standard clipboard viewer saves only the last item copied to the clipboard.)  
   
  We will address both of these issues in this appendix.  
   
  As you know, the clipboard can hold data of several different formats at the same time. We will restrict our attention to text, however. The various forms of text are represented by the following symbolic constants.  
Page 438
 
  Public Const CF_TEXT = 1   ' ANSI text
Public Const CF_OEMTEXT = 7
Public Const CF_UNICODETEXT = 13
 
   
  Note that it is only possible (and only necessary) to place one text format on the clipboard at a time (or one bitmap format, and so on). Windows will convert between text formats based on an application's paste request. For instance, if we place CF_TEXT (ANSI text) on the clipboard and another application asks for CF_UNICODETEXT, Windows will convert the ANSI text to Unicode for the request.  
   
  Copying Text to the Clipboard  
   
  The process for copying text to the clipboard is described as follows:  
   
  1. Allocate memory for the data (GlobalAlloc).  
   
  2. Lock the memory (GlobalLock).  
   
  3. Copy the data to the memory (CopyMemory).  
   
  4. Unlock the memory (GlobalUnlock).  
   
  5. Open the clipboard (OpenClipboard).  
   
  6. Clear its current contents (EmptyClipboard).  
   
  7. Set the clipboard data (SetClipboardData).  
   
  8. Close the clipboard (CloseClipboard).  
   
  Here are the details.  
   
  Allocate memory for the data  
   
  The first step is to allocate memory for the data using the GlobalAlloc function:  
 
  HGLOBAL GlobalAlloc(
  UINT uFlags,    // allocation attributes
  DWORD dwBytes   // number of bytes to allocate
);
 
   
  Incidentally, while it is true that the current documentation states that GlobalAlloc " is provided only for compatibility with 16-bit versions of Windows," we must use this function here, so this statement is not strictly true.  
   
  In VB, we can write:  
 
  Declare Function GlobalAlloc Lib "kernel32" ( _
   ByVal uFlags As Long, _
   ByVal dwBytes As Long _
) As Long
 
   
  The uFlags parameter must be set to the constant:  
 
  GMEM_SHARE Or GMEM_MOVEABLE  
Page 439
   
  To zero out the allocated memory, we can also include the GMEM_ZEROINIT constant, so we will use the following definitions:  
 
  Public Const GMEM_SHARE = &H2000&
Public Const GMEM_MOVEABLE = &H2
Public Const GMEM_ZEROINIT = &H40
Public Const FOR_CLIPBOARD = GMEM_MOVEABLE Or GMEM_SHARE Or GMEM_ZEROINIT
 
   
  Note that the GlobalAlloc function returns a handle to the allocated memory. This is not the same as a pointer to (address of) the memory. The reason we get a handle rather than a pointer is that Windows may move the memory to another location at some point (the memory is allocated as GMEM_MOVEABLE, as required by the clipboard functions).  
   
  Lock, copy, and unlock  
   
  However, before we can use the memory block, we do need a pointer to that block. This requires that we ask Windows to temporarily lock the location of the memory. To get a pointer, we call GlobalLock:  
 
  LPVOID GlobalLock(
  HGLOBAL hMem   // handle to the global memory object
);
 
   
  or, in VB,  
 
  Declare Function GlobalLock Lib "kernel32" (
   ByVal hMem As Long _
) As Long
 
   
  The next step is to copy the data intended for the clipboard to the allocated memory. (If this data is text, it must be null terminated.) The CopyMemory function will do nicely here. (See the upcoming example.) The pointer returned by GlobalLock is the source address that is required by CopyMemory. Once the data is copied into the allocated clipboard memory, we must unlock that memory, using Global-Unlock:  
 
  BOOL GlobalUnlock(
  HGLOBAL hMem   // handle to the global memory object
);
 
   
  or, in VB:  
 
  Declare Function GlobalUnlock Lib "kernel32" ( _
   ByVal hMem As Long _
) As Long
 
   
  Note that this function requires the handle to the memory block, not the pointer. If for some reason we were careless enough to misplace the handle, we can retrieve it from the pointer, using GlobalHandle:  
 
  HGLOBAL GlobalHandle(
  LPCVOID pMem    // pointer to the global memory block
);
 
Page 440
   
  Open, empty, set, and close  
   
  Now we are ready for the clipboard API functions. The next step is to open the clipboard with OpenClipboard:  
 
  BOOL OpenClipboard(
  HWND hWndNewOwner   // handle to window opening clipboard
);
 
   
  or, in VB:  
 
  Declare Function OpenClipboard Lib "user32" ( _
   ByVal hwnd As Long _
) As Long
 
   
  This function requires the handle to a window that will own the clipboard data. The function returns False if another application has opened the clipboard. This is worth checking for.  
   
  Next, we set the clipboard data using SetClipboardData:  
 
  HANDLE SetClipboardData(
  UINT uFormat  // clipboard format
  HANDLE hMem   // data handle
);
 
   
  or, in VB:  
 
  Declare Function SetClipboardData Lib "user32" ( _
   ByVal uFormat As Long, _
   ByVal hMem As Long _
) As Long
 
   
  Note that hMem is the handle to the memory block, not the pointer. The uFormat parameter is a symbolic constant that describes the clipboard format.  
   
  Finally, we close the clipboard:  
 
  BOOL CloseClipboard(VOID)  
   
  or, in VB:  
 
  Declare Function CloseClipboard Lib "user32" () As Long  
   
  There are a few points about this process that we should emphasize:  
   
  Do not forget to unlock the memory block before passing it to the clipboard.  
   
  It is important not to leave the clipboard open any longer than is absolutely necessary.  
   
  After calling SetClipboardData, the memory block no longer belongs to our application, so we should not access that memory. The handle and the pointer should be considered invalid. Windows will clean up any memory when it is no longer needed. We should not free this memory using GlobalFree (or any other method).  
Page 441
   
  An Example  
   
  Let us give it a try. The following procedure places text on the clipboard:  
 
  Sub CopyTextToClipboard(sText As String)

Dim hMem As Long, pMem As Long

hMem = GlobalAlloc(FOR_CLIPBOARD, LenB(sText))
pMem = GlobalLock(hMem)
CopyMemory ByVal pMem, ByVal sText, LenB(sText)
GlobalUnlock hMem

If OpenClipboard(Me.hwnd) = 0 Then
   MsgBox "Clipboard opened by another application."
Else
   EmptyClipboard
   SetClipboardData CF_TEXT, hMem
   CloseClipboard
End If

End Sub
 
   
  Pasting Text from the Clipboard  
   
  The process for retrieving text from the clipboard is described as follows:  
   
  1. Determine whether the clipboard contains data in text format (IsClipboardFormatAvailable).  
   
  2. Open the clipboard (OpenClipboard).  
   
  3. Get a handle to the global memory containing the data (GetClipboardData).  
   
  4. Lock the memory (GlobalLock).  
   
  5. Copy the text from the clipboard's memory block to memory belonging to the application (CopyMemory).  
   
  6. Unlock the clipboard's memory (GlobalUnlock).  
   
  7. Close the clipboard (CloseClipboard).  
   
  The following function returns any text on the clipboard. Note the use of Global-Size to determine the size of the clipboard's memory block used to hold the text.  
 
  Function PasteTextFromClipboard() As String

Dim hMem As Long, pMem As Long
Dim lMemSize As Long
Dim sText As String

' Check for text on clipboard
If IsClipboardFormatAvailable(CF_TEXT) = 0 Then
   MsgBox "No text on clipboard", vbInformation
   PasteTextFromClipboard = ""
 

Page 442
 
     Exit Function
End If

' Open clipboard
If OpenClipboard(Me.hwnd) = 0 Then
   MsgBox "Clipboard open by another application.", vbExclamation
Else
   hMem = GetClipboardData(CF_TEXT)
   ' If no text, close clipboard and exit
   If hMem = 0 Then
      CloseClipboard
      MsgBox "No text on clipboard", vbInformation
      Exit Function
   Else
      ' Get memory pointer
      pMem = GlobalLock(hMem)
      ' Get size of memory
      lMemSize = GlobalSize(hMem)
      ' Allocate local string
      sText = String$(lMemSize, 0)
      ' Copy clipboard text
      CopyMemory ByVal sText, ByVal pMem, lMemSize
      ' Unlock clipboard memory
      GlobalUnlock hMem
      ' Close clipboard
      CloseClipboard
      ' Return text
      PasteTextFromClipboard = Trim0(sText)
   End If
End If

End Function
 
   
  Other Interesting Clipboard Functions  
   
  Here are a few other clipboard functions that you may want to investigate.  
 
  CountClipboardFormats
Returns the number of clipboard formats currently on the clipboard
 
 
  EnumClipboardFormats
Enumerates the current clipboard formats
 
 
  GetClipboardOwner
Returns the handle of the current clipboard owner
 
 
  GetOpenClipboardWindow
Returns the handle of the window that has opened the clipboard
 
   
  Example: Creating a Clipboard Viewer  
   
  A clipboard viewer is a window that receives notification of changes in the clipboard. At any given time, there may be more than one active clipboard viewer, but  
Page 443
   
  Windows will send notifications only to the viewer that was last installed in the clipboard viewer chain, so it is the responsibility of each installed viewer to pass notification messages to the next viewer in the chain.  
   
  To install a clipboard viewer, we can just call the SetClipboardViewer function:  
 
  HWND SetClipboardViewer(
  HWND hWndNewViewer   // handle to clipboard viewer window
);
 
   
  Here hWndNewViewer is the handle of the new viewer. The return value (if successful) is the handle of the current clipboard viewer (before the function call), which thus becomes the second viewer in the chain. This value must be saved, because it is the window handle to which the new viewer must pass along clipboard messages.  
   
  Once the viewer is installed, it will receive WM_DRAWCLIPBOARD messages whenever the clipboard changes. At this time, the viewer can (in its window procedure) retrieve the clipboard data.  
   
  Calling ChangeClipboardChain will cause Windows to remove a clipboard viewer from the viewer chain. The syntax is:  
 
  BOOL ChangeClipboardChain(
  HWND hWndRemove,  // handle to window to remove
  HWND hWndNewNext  // handle to next window
);
 
   
  The hWndRemove parameter should be set to the handle of the viewer to be removed, and the hWndNewNext parameter should be set to the handle of the viewer that follows it in the chain. A call to this function prompts Windows to send a WM_CHANGECBCHAIN message to the current viewer. Note that this may or may not be the viewer that called ChangeClipboardChain or even the viewer that is to be removed. In fact, any application can remove any clipboard viewer if it knows that viewer's handle as well as the handle of the next viewer in the chain.  
   
  The parameters to this message are:  
 
  wParam = hWndRemove
lParam = hWndNewNext
 
   
  That is, the parameters to ChangeClipboardChain are passed along to the window procedure.  
   
  This issue is a bit subtle, so we should discuss it more carefully. Figure A-1 shows a clipboard chain with four viewers.  
   
  Suppose an application calls ChangeClipboardChain:  
 
  ChangeClipboardChain hWndX, hWnd(X+1)  
   
  to remove ViewerX (where X = 1, 2, 3, or 4).  
Page 444
   
  0444-01.gif  
   
  Figure A-1.
A clipboard viewer chain
 
   
  As a result, Windows removes ViewerX from the clipboard chain and sends a WM_CHANGECBCHAIN message to Viewer1 (the current viewer). There are three possibilities to consider.  
   
  If ViewerX is Viewer1, then it is Viewer1 that has been removed. Windows now thinks Viewer2 is the current viewer and so no further action is necessary. In particular, Viewer1 does not need to process the WM_CHANGECBCHAIN message, but note that it would do no harm for it to pass the message along the chain to Viewer2. Note also that Viewer1 can tell if it is the one being removed by checking to see if hWndX (= hWndRemove = wParam) is its own handle.  
   
  If ViewerX is Viewer2, then Viewer1 needs to do a bit of processing. The reason is that Viewer1 holds a handle to the next viewer in the chain (Viewer2). Since this next viewer is the one that has been removed, this handle is no longer the handle to the next viewer in the chain. So Viewer1 just needs to point its variable hNextViewer to Viewer3 as follows:  
 
  If wParam = hNextViewer Then   ' wParam = hWndRemove = hWnd2
  hNextViewer = 1Param      ' lParam = hWnd3
End If
 
   
  Finally, if ViewerX is neither the current viewer (Viewer1) nor the next one in line (Viewer2), then Viewer1 must pass the message along to Viewer2, so it can go through this little bit of terpsichorean maneuvering.  
   
  Note that we can simplify the code a bit by passing the message along without processing in all cases except where ViewerX is Viewer2, as in the following code:  
 
  If wParam = hNextViewer Then
   ' Unlink viewer
   hNextViewer = lParam
Else
   ' Just pass the message along
   SendMessage hNextViewer, WM_CHANGECBCHAIN, wParam, 1Param
End If
 
Page 445
   
  In summary, all there is to creating a clipboard viewer is to:  
   
  1. Call SetClipboardViewer.  
   
  2. Process the WM_CHANGECBCHAIN and WM_DRAWCLIPBOARD messages.  
   
  Of course, under VB, we need to subclass a window so that we can process these messages!  
   
  The accompanying CD includes the rpiClipViewer application that hooks into the clipboard chain and places all text that is copied to the clipboard into a listbox (actually, it is currently set to keep only the last 100 items). Thus, if we need to retrieve an item placed on the clipboard a few ''copies" ago, it is there for the asking. Figure A-2 shows the main window for rpiClipViewer.  
   
  0445-01.gif  
   
  Figure A-2.
A clipboard viewer
 
   
  The text box at the bottom shows the item selected in the listbox, but with the carriage returns expanded for easier reading. Double-clicking on an item in the listbox (or using the ENTER key) will send that item to the clipboard for pasting.  
   
  The Load event for this form is:  
 
  Private Sub Form_Load()

Dim hnd As Long

' Check for running viewer and switch if it exists
hnd = FindWindow("ThunderRT6FormDC", "rpiClipViewer")
If hnd <> 0 And hnd <> Me.hwnd Then
 

Page 446
 
     SetForegroundWindow hnd
   End
End If

BecomeClipboardViewer

bAddToList = True

Me.Show

End Sub
 
   
  This code shows one way to determine whether there is already a current instance of the clipboard viewer running. If so, the application just switches to that instance. We discussed the issue of testing for a running application in Chapter 11, Processes. This approach is a bit sneaky, so let us review it briefly.  
   
  One approach to testing for a running application is to use FindWindow to see if a window of the appropriate caption exists. However, as soon as the Load event fires, such a window will exit, so the code above just returns the handle of itself! The way to circumvent this problem is to set the main form's caption at design time to something other than its final value, say rpiClipView instead of rpiClipViewer. Then we can change the caption in the form's Activate event:  
 
  Private Sub Form_Activate()
Me.Caption = "rpiClipViewer"
End Sub
 
   
  Which is not fired until after the Load event's code is executed! Note also the use of the END statement to terminate the Load event if the application is already running.  
   
  The BecomeClipboardViewer function just subclasses the listbox and calls SetClipboardViewer:  
 
  Sub BecomeClipboardViewer()

' Subclass command button
Subclass
If Not bIsSubclassed Then
   MsgBox "Subclass failed", vbCritical
   Exit Sub
End If

' Install button as clipboard viewer
hNextViewer = SetClipboardViewer(lstItems.hwnd)

End Sub
 
   
  To do the subclassing, we use SetWindowLong:  
 
  Sub Subclass()

' Subclass button
 

Page 447
 
  hPrevWndProc = SetWindowLong(lstItems.hwnd, GWL_WNDPROC, AddressOf WindowProc)
If hPrevWndProc <> 0 Then
   bIsSubclassed = True
End If

End Sub
 
   
  The Unload event calls the following function to unhook the clipboard viewer:  
 
  Sub UnbecomeClipboardViewer()

ChangeClipboardChain lstItems.hwnd, hNextViewer
RemoveSubclass

End Sub
 
   
  Here is the window procedure for the clipboard viewer (listbox):  
 
  Public Function WindowProc(ByVal hwnd As Long, ByVal iMsg As Long, _
   ByVal wParam As Long, ByVal lParam As Long) As Long

Dim sItem As String

Select Case iMsg

   Case WM_DRAWCLIPBOARD

      ' Get clipboard text and put it into text box
      sItem = PasteTextFromClipboard
      If sItem <> " " Then frmClipViewer.txtCurrent = sItem
      If sItem <> " " And bAddToList Then
         ' Add item to listbox
         cItems = cItems + 1
         frmClipViewer.lstItems.AddItem sItem, 0
         ' If exceeded maximum, remove last item
         If cItems > MAX_ITEMS Then
            frmClipViewer.lstItems.RemoveItem MAX_ITEMS - 1
            cItems = cItems - 1
         End If
         ' Select item
         If frmClipViewer.lstItems.ListCount >= 1 Then
            frmClipViewer.lstItems.Selected(0) = True
         End If
         ' Update label
         frmClipViewer.lblItemCount = cItems & " items"
      End If

      ' Send message to next clipboard viewer
      If hNextViewer <> 0 Then
         SendMessage hNextViewer, WM_DRAWCLIPBOARD, wParam, lParam
      End If

      Exit Function

   Case WM_CHANGECBCHAIN
 

Page 448
 
        ' Check to see which viewer is being removed.
      ' Is it the next one in line?
      ' wParam contains handle of viewer being removed.
      If wParam = hNextViewer Then
         hNextViewer = lParam
      Else
         SendMessage hNextViewer, WM_CHANGECBCHAIN, wParam, lParam
      End If

      Exit Function

End Select
' Call original window procedure
WindowProc = CallWindowProc(hPrevWndProc, hwnd, iMsg, wParam, lParam)

End Function


WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net