INTERMEDIATE  Ask the VB Pro


Draw Bitmaps on Device Contexts

by Karl E. Peterson

 


Q.  Create a Clipboard Bitmap
I'm trying to populate a custom PowerPoint palette bar with buttons of arbitrary RGB colors. I can choose built-in images, but if I want something else, I have to paste it into the button face from the clipboard. It's no big deal to do that on the fly, but getting the goods up to the clipboard in the first place seems to be a real challenge. There might be as many as 100 of these buttons to do at a crack. I can always open a presentation, draw a rectangle, copy it, paste it to the button face, and boogie onward, but that's painfully slow! I wouldn't mind littering a directory with a jillion little metafiles if I had some way of reading them directly to the clipboard and pasting from there. Do you have any ideas?

ABOUT THIS COLUMN
Ask the VB Pro provides you with free advice on programming obstacles, techniques, and ideas. Read more answers from our crack VB pros. You can submit your questions, tips, or ideas on the site, or access a comprehensive database of previously answered questions.

A.  Unfortunately, Office VBA doesn't support the VB Clipboard object, so at first this might seem problematic. My recommendation: Build the button face bitmaps in memory, then place them on the clipboard by going directly to the API. Learning to draw on memory-based device contexts is extremely valuable; you can leverage this skill in hundreds of situations.

Ideally, you'll want to add a standard code (BAS) module to your project and create a single function that does all the work. Pass this function the width, height, fill color, and border color for the toolbar button color patch you'd like. On return, a usable bitmap will be on the clipboard, ready to paste. I've provided an example function, ClipPatch, which should get you started (see Listing 1).

Start by creating a memory-based device context (DC) compatible with the screen DC. You can obtain an hDC for the screen by calling GetDC on the return from GetDesktopWindow. Pass this hDC to CreateCompatibleDC to establish your memory DC. Consider a DC to be analogous to an artist's easel—you can't paint until you add a suitable canvas. In this case, a bitmap of the proper size is required. Create the bitmap compatible with the screen by passing the screen's hDC and the desired dimensions to CreateCompatibleBitmap, then call SelectObject to select the hBmp into the hDC.

You now have a canvas, but you need the tools with which to paint. You need to select a proper brush, just as an artist would. You may select only one graphics object from each of several types into a DC at any given time. Call CreateSolidBrush, passing the desired fill color, to retrieve a brush handle you then select into the DC using SelectObject. Likewise, you need a pen to outline the color patch; use CreatePen and SelectObject to add this tool to your arsenal. Calls to SelectObject return the handle to the object you're replacing. It's extremely important that you hang onto these handles; you'll need them later to avoid a resource leak.

Now you're ready to start painting. A single call to the Rectangle API provides the answer in your case, but realize that you have the full GDI at your disposal now, and you can create a bitmap as elaborate as need be. After you paint your bitmap, it's time to clean up a bit. In general, you must destroy each graphics object you've created to avoid a resource leak. Use SelectObject to restore the previous pen and brush to the memory DC, then call DestroyObject on the created pen and brush. You have big plans for the created bitmap, so don't destroy it. Use SelectObject to remove it from the memory DC, then call DeleteDC to destroy the memory DC and ReleaseDC to release the screen DC.

Make a few more API calls and you're done. Call OpenClipboard to obtain access to the clipboard, then EmptyClipboard to clear it. Use SetClipboardData to place the bitmap on the clipboard, then call CloseClipboard to shut things down. The best location to test this sort of routine is with a full copy of VB. You can call the routine from a form, using random color and size values:


Private Sub Timer1_Timer()
   Dim Size  As Long, Fill As Long
   ' Create a random-colored bitmap 
   ' with black border, and place on 
   ' clipboard.
   Size = Rnd * 100 + 100
   Fill = RGB(Rnd * 255, Rnd * 255, _
      Rnd * 255)
   Call ClipPatch(Size * 1.5, Size, _
      Fill, vbBlack)
   ' Select bitmap into form, just to 
   ' see that it worked.
   Me.Cls
   Me.Picture = _
      Clipboard.GetData(vbCFBitmap)
End Sub

I'm sure this seems like a lot of effort to get a bitmap on the clipboard, but I'm just as sure that once you become comfortable with this sort of code, only your imagination will limit what you do with this newfound skill.


Q.  Show Listbox Data That Doesn't Fit
I have an application that uses several intrinsic VB listboxes. The problem is that, in some cases, the item is too long to be displayed in the existing width of the listbox. Are you aware of an available control that uses the same basic properties and methods as VB's listbox, but provides the capability to display "column tips" when the mouse is over a specific item in the list that is too wide to be displayed fully? I would prefer a simple drop-in replacement for the VB listbox instead of a third-party tool that might have this capability but features an object model totally different from VB's listbox.

A.  The short answer is no, because I don't often find the need to go beyond what I can do with "the basics." You have several choices, given just what ships in the VB box. One solution you might have already rejected, but is common practice in these situations, is to add a horizontal scrollbar to the listbox. This is a well-documented technique, with examples in Microsoft Knowledge Base article ID Q192184, as well as on many VB-related Web sites (see Links).

Another option is to set the listbox's ToolTipText property on the fly as the mouse moves over the listbox. You can determine the text under the cursor easily by dividing the Y mouse coordinate by the height of each item in the list, and adding the TopIndex value. Obtain the item height by sending LB_GETITEMHEIGHT to the listbox with SendMessage (see Listing 2).


Q.  Define Your Own Messages
Is it possible to define my own messages for the SendMessage API? And, if so, is there a range of values reserved for this purpose?

A.  Yes. Typically, you can create user-defined message values by adding an integer value to WM_USER (&H400). The range of 0 to WM_USER-1 is restricted to the system. You are free to use any value in the range of WM_USER to &H7FFF (see Links).

You can also use RegisterWindowMessage, which generates a value guaranteed to be unique within the system (see Links). This strategy is commonly employed when communicating between applications, or when publishing a message-based interface to your application.

Code each application to pass the same string to RegisterWindowMessage. On the first call, the system generates the unique message value. Subsequent calls, from any application running, retrieve this same value from the system (see Listing 3).


Q.  Icons for Form-Less Applications
How can I assign the icon of a VB executable without including a form in my project?

A.  Windows normally uses the lowest-numbered icon resource in your EXE for this purpose. But, no matter how you define your resource file, VB always assigns the ugly default form icon as number 1 if you don't assign a different one in the Project Properties dialog. However, making this assignment is somewhat of a problem if you have no forms in the application. The cheap way out is to include an empty form whose only purpose is to supply an icon.

If you try to work around VB's lame insistence on including the default icon by assigning a resource-based icon an ID of 1, VB won't even include your icon in the compiled project. The best method is to assign a string ID to an icon in your application's resource file, because Windows sorts these lower than strictly numeric IDs. To ensure it sorts to the head of the list, you might want to use a string such as "AAA" for the icon ID.


Karl E. Peterson is a GIS analyst with a regional transportation planning agency and serves as a member of the Visual Basic Programmer's Journal Technical Review and Editorial Advisory Boards. Online, he's a Microsoft MVP and a section leader on several VBPJ forums. Find more of Karl's VB samples at www.mvps.org/vb.

 
Links
HOWTO: Add a Horizontal Scroll Bar to a Visual Basic ListBox

WM_USER

• RegisterWindowMessage

Original Code
  Get the code for this article here.
Updated Code
  ClipEx