Q. Find the Color of That
Invisible Pixel
Is a method available to get the color of a pixel that would be displayed if the picture were visible? For example, say I have a hidden PictureBox; can I tell what the color value of a pixel would be if the PictureBox were not hidden?
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. If the PictureBox control has its AutoRedraw property set to True, you can find the color of any pixel easily using the control's Point method. You must make sure that the coordinates passed are in the same scale as the control's ScaleMode property. That said, VB's Point and PSet methods are notorious for how slow they are, compared to their API counterparts.
The fastest way to get this information, if you need to obtain the color values of many pixels, is to copy the picturethe bitmap, actuallyonto a device context (DC) you've created in memory. The basic outline for such a task includes creating the DC, selecting the bitmap into the DC, calling GetPixel to retrieve the colors at any specified coordinates, selecting the bitmap back out of the DC, and finally destroying the created DC. I'm betting you'll want access to the image colors over a period of time, instead of all at once. This chore cries out for a rich class-based encapsulation, if that's the case.
Four years ago, I wrote about obtaining a DC for a standard Picture object (see Resources). The class I developed for that column, CPictureDC, works for this task, with only one caveat: CPictureDC was designed to accept the bitmap handle exposed by standard Picture objects once, and only once:
Dim pic As New CPictureDC
pic.hBitmap = Picture1.Picture.Handle
CPictureDC assumes the DCs you'll be working with are compatible with the current display. During the Initialize event, CPictureDC obtains the hWnd of the desktop using GetDesktopWindow, and passes this handle to GetDC to obtain a handle to the desktop's DC. Complete the initialization with a call to CreateCompatibleDC, passing the just-obtained desktop hDC, and finally a call to ReleaseDC to release the desktop DC. At this point, CPictureDC is the owner of a memory-based DC that contains a 1-by-1 pixel monochrome default bitmap (see Listing 1).
The write-once design of CPictureDC's hBitmap property is purely for safety. Repetitive GDI operations create the worst memory leaks unless you take exacting care to not misplace any resource handles; the write-once design serves to reduce potential carnage in that regard. When a client passes a bitmap handle to the hBitmap property, hBitmap selects the bitmap into the DC using SelectObject after using a call to GetObject that both verifies the passed value was actually a true bitmap handle and obtains some useful property data, such as height and width.
Now that your image is within a memory DC, calling the GetPixel API provides the color for any position within the image. Be sure to use pixel coordinates:
nColor = GetPixel(pic.hDC, X, Y)
Tear down CPictureDC in a two-step process: Use SelectObject to remove the borrowed bitmap from the memory DC, and destroy the DC with DeleteDC. Failing to follow these steps causes a memory leak.
Earlier, I mentioned a caveat. As written, CPictureDC prevents you from using the same source Picture object for multiple purposes. If the source were visible, it would act transparent during repaints, because the once-contained bitmap has been yanked out into another DC. To solve this problem, extend the class to make a copy of the passed bitmap, instead of consuming what's passed.
Q. Giving Away Bitmaps
I'm experimenting with the clipboard API functions, and am perplexed about something. The docs on SetClipboardData state that the system owns the passed object after the call, which implies I can't pass the .Picture.Handle property exposed by standard Picture objects. How can I generate an image copy that I can give away freely to this or other API calls?
A. The timing of your question is impeccable. The answer lies in extending the CPictureDC class discussed in the previous question to a more robust CMemoryDC class that exposes Picture properties, which allow a client to assign and retrieve standard Picture objects, as well as their handles. Duplication also requires that the class create bitmaps at will and to size. First, look at what modifications are needed to the basic class structure.
The Initialize event is similar, with but two additions. After creating the memory DC, Initialize sets the default bitmap dimensions to 1-by-1 and calls a new private method. RecreateBitmap obtains handles to the desktop, just like Initialize, but uses them to create a bitmap with a call to CreateCompatableBitmap, and selects this bitmap into the memory DC (see Listing A).
CMemoryDC must track the source of the bitmap at all times within its memory DC. After selecting the bitmap out of the memory DC, RecreateBitmap checks the m_UserBmp flag to determine whether to destroy the existing bitmap with DeleteObject. This flag is always set to False in RecreateBitmap, and to True in its companion routine, UserBitmap.
When a client sets the hBitmap property directly, hBitmap calls the UserBitmap method, a private member of the class. UserBitmap is similar to the code used in CPictureDC, except that UserBitmap must decide whether to delete the existing bitmap when selecting a new one into the memory DC. Additionally, you must extend both the Width and the Height properties to be read-write, rather than read-only, so they too must each call RecreateBitmap whenever the client assigns one of them.
As with Initialize, Terminate looks familiar. There's only one big difference in Terminate: It now optionally decides not to destroy the bitmap if the m_TermKills flag isn't set. This is the ticket to your solution, but is also potentially the source of much heartburn if you don't handle it carefully. If you don't delete these handles yourself, or pass them to APIs that delete them, they become a memory leak. Be careful.
To see how the actual copy is accomplished, look at the new Picture Set property. When your client passes a StdPicture object to the Picture method, this method creates a new instance of CMemoryDC and assigns the passed StdPicture.Handle to this new class's hBitmap property. The Picture method then reads the Width and Height property values of the new class and assigns them to the internal width and height tracking variables of the current class. Calling RecreateBitmap resizes the current class's bitmap to match the new class, and a call to BitBlt can then complete the copy. The important point here is that you don't retain a reference to the passed StdPicture object.
Now for the grand finale. The Picture Get property actually contains an API of the sort you ask about. OleCreatePictureIndirect creates a Picture object that can destroy its contained image automatically when the Picture object is destroyed, if you set the third parameter, fOwn, to True. Otherwise, it is your responsibility to track this Picture object's lifetime and destroy the bitmap at the appropriate time. Obviously, the autodestruct option is preferable for this design.
Again, you use a temporary new instance of CMemoryDC to complete your objective. Set its Width and Height to match the current instance, and set TerminateKillsBitmap to False to prevent the class from destroying the bitmap in its Terminate event. Call BitBlt to copy the current bitmap to the new class's memory DC, then destroy the class after saving the handle to its bitmap. This leaves you with a bitmap you can pass to OleCreatePictureIndirect, transferring ownership of it to the system.
Download a demo using CMemoryDC (see Figure 1).
Q. Drop a DataCombo
I like to have ComboBox controls drop when they receive focus. I have a procedure I've used for years that calls a routine that sends CB_SHOWDROPDOWN to a ComboBox from the control's GotFocus event. The trouble is, this routine isn't working with DataCombo controls. SendMessage returns zero, but the dropdown does nothing. I've tried using SendKeys with Alt-Down, but that causes the dropdown to flop back up if the user clicks on the down arrow when giving focus to the control. What can I do?
A. You're definitely on the right track. As you've found out, authors of custom controls are under no obligation to respond to all the messages their base classes might respond to. In this case, using SendKeys makes sense, but you first do a couple quick tests to ensure you employ this workaround only in the desired circumstances.
You can modify your generic routine so it works with both ComboBox and DataCombo controls. One key point is that if the user clicks on the control's down arrow to transfer focus, you shouldn't use SendKeys to drop the list. If you call SendKeys in this case, the list would pop back up, not stay down.
Combo controls are composed of several windows. If you call GetCursorPostion, followed by WindowFromPoint using the cursor coordinates, the returned hWnd will be equal to the control's hWnd property only when the cursor is over the down arrow portion of the control. You can use GetAsyncKeyState to determine whether the left mouse button is currently down. Together, these two pieces of information give you the data you need to determine whether to make the SendKeys call (see Listing 2).
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.
|