In April, 2001 Mr. Daniel Görlich wrote in the "borland.public.cppbuilder.vcl.components.using" newsgroup that he was writing an astronomy application that has a "night vision" color scheme. But when he clicked on the scrollbar of a dropped-down ComboBox, the scrollbar flashed briefly into reverse video, spoiling the night vision color choice. In fact, that's how all Windows scrollbars behave.
I wondered if it was possible to subclass the drop-down (that is, the ComboLBox classname window) and intercept the scrollbar clicks that caused the flashing. This turns out to be slightly harder than it might seem, because the ComboLBox is not a child of the control itself, and there's only one way to get its' Handle. (Note that the VCL has a property, TCustomComboBox::ListHandle. But it's only set [undocumented] for the Style=csSimple .)
"Fishface" kindly showed how to learn the Handle. Once you have that, you can subclass the drop-down ComboLBox, and intercept the mouse messages. It turns out that while the box is dropped-down, it captures the mouse, so you have to be prepared to receive coordinates that are not within the ComboLBox itself. Naturally, the proper default behavior of such a click is to close-up the drop-down.
This is a very primitive "graphics" project: If the mouse is clicked inside the combobox drop-down, the coordinates are checked to see if they fall (horizontally) inside the scrollbar. If they do, they are then checked to see if they fall (vertically) within the scrollbar but not on the thumb or the up and down arrows. If so, the appropriate WM_VSCROLL message is sent to the ComboLBox window, and the mouse-down is discarded. (Rather than posting a .gif of a scrollbar, I'm counting on you to take a look at the scrollbar on your browser to see what I mean.) This is a little easier to do in later versions of Windows, but I limited myself to the use of GetScrollInfo(), a Win95 API.
This project does not have much real utility for the average user. But I learned a lot about ComboBoxes and subclassing. The next step will be to use MakeObjectInstance() to eliminate the drawback discussed in the comments below. I decided to post this *without* MakeObjectInstance() because it's a little easier to see what's happening this way. In any case, I haven't yet written the improved code!
I found this MSDN reference invaluable, and it's very well written:
"Scroll Bar Controls in Win32" by Nancy Winnick Cluts
Search for the title if the link has been moved, which happens all the time at MSDN.
I've incorporated several improvements suggested by Mr. Görlich.
In the nitecomb.h file:
// ----------------------------------------------
#ifndef nitecombH
class TNiteCombo : public TComboBox
{
private:
HWND lboxhandle;
protected:
virtual void __fastcall WndProc(TMessage &Msg);
public:
__fastcall TNiteCombo (TComponent* Owner);
__fastcall ~TNiteCombo();
__published:
};
#define nitecombH
// ----------------------------------------------
#endif
In the nitecomb.cpp file:
// ---------------------------------------------- #include#pragma hdrstop #include "NiteComb.h" WNDPROC NCB_FOriginalLBoxProc; LRESULT __fastcall NCB_LBoxProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return NCB_LBoxProc(hWnd, uMsg, wParam, lParam); } // Our WndProc for the subclassed drop-down list box static LRESULT __fastcall NCB_LBoxProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { #define LB_EDGE 1 // Control border pixels static int retval; // Windows return code static int x,y; // Mouse params from LParam static RECT rect; // ComboLBox rectangle static int ht,wd; // Height and Width of LBox static SCROLLINFO sinfo; // Vert scrollbox of ComboLBox // The following values are computed live here, rather than calculated // and saved in TNiteCombo. Harold Howe warns that you cannot de- // reference non-static class members in a WinProc. The other reason for // *not* saving them is that the drop-down list box could be different the // next time it drops down. For example, it might have too few items to have // a vertical scroll bar at all. static int vsbwd; // Vert scrollbox width static int vsbaht; // Vert scrollbox arrow button ht static int vslash; // Lower arrow shadow below - Wider than the // shadow below the upper arrow button! static int thbht; // Our computed thumb height static int mspos; // Max reportable Scroll nPos static int topsb; // computed Top of thumb static int botsb; // computed Bot of thumb static int mxabv; // Max possible Top of thumb LRESULT lResult; // Return value of this WndProc if (uMsg == WM_LBUTTONDBLCLK) uMsg = WM_LBUTTONDOWN; // Treat both clicks the same way if(!(GetWindowLong(hWnd,GWL_STYLE)&WS_VSCROLL)) // Does LB have V sclbar? uMsg = NULL; // No, use default WndProc lResult=false; // Indicates NOT SET IN while block while(uMsg==WM_LBUTTONDOWN) { // Windows 95 SCROLLINFO functions used // Win98 SCROLLBARINFO provides better data if no // Win95 support required sinfo.cbSize=sizeof(SCROLLINFO); sinfo.fMask=SIF_ALL; retval=GetScrollInfo(hWnd,SB_VERT,&sinfo); if(!retval) { // Abandon if no scroll bar info break; } GetWindowRect(hWnd, &rect); // The ComboLBox ht=rect.bottom-rect.top+1; // height wd=rect.right-rect.left+1; // width x= LOWORD(lParam); // Current mouse position y= HIWORD(lParam); mspos=sinfo.nMax-(sinfo.nPage-1); // from MS SDK docs // LB_EDGE added below to indicate top/bot edge of each arrow button vsbwd=GetSystemMetrics (SM_CXVSCROLL)+LB_EDGE; vsbaht=GetSystemMetrics(SM_CYVSCROLL)+LB_EDGE; vslash=GetSystemMetrics(SM_CYEDGE); if (sinfo.nMax - sinfo.nMin == 0) // This should not happen, since this code not // executed unless the drop-down list HAS a VScroll. // But must avoid divide by 0 at all costs. thbht=((ht-2*vsbaht)*sinfo.nPage); else thbht=((ht-2*vsbaht)*sinfo.nPage)/(sinfo.nMax-sinfo.nMin); mxabv=ht-2*vsbaht-thbht+LB_EDGE; // Thumb at max (bot of scroll) if (mspos == 0) // Scale space above thumb // This should not happen, since this code not // executed unless the drop-down list HAS a VScroll. topsb=vsbaht+mxabv*sinfo.nPos+LB_EDGE; else topsb=vsbaht+mxabv*sinfo.nPos/mspos+LB_EDGE; botsb=topsb+thbht; // Add height of thumb // Determine where the mouse is. The message // ..goes here, even if the mouse is outside the // ..ComboLBox! if (x<(wd-vsbwd) // Mouse down, left of V scrollbar or || x > wd || y > ht // ..outside the ComboLBox or || y < vsbaht // ..on scrl bar up arrow || y > (ht-vsbaht-vslash)) // ..on dn arrow? { // Yes, so default processing break; } else // Mouse on V scrl bar, BETWEEN { // ..up and down arrow buttons if (y > topsb && // If mouse is ON thumb, normal wnproc y < botsb) { break; } // Here if mouse is above or below V scroll thumb if (y > botsb) // Below thumb SendMessage(hWnd, WM_VSCROLL,(WPARAM) SB_PAGEDOWN ,0 ); else // Above thumb SendMessage(hWnd, WM_VSCROLL,(WPARAM) SB_PAGEUP ,0 ); lResult = true; break; // Don't use default WndProc } } if(!lResult) // If not set inside the while block, default Proc is wanted lResult = CallWindowProc ((FARPROC)NCB_FOriginalLBoxProc, hWnd, uMsg, wParam, lParam); return lResult; } // ---------------------------------------------- // Constructor __fastcall TNiteCombo::TNiteCombo(TComponent* Owner) : TComboBox(Owner) { lboxhandle = NULL; // Not yet known Parent = (TWinControl*)Owner; // Optional Style = csDropDown; // Optional } // ---------------------------------------------- // Destructor __fastcall TNiteCombo::~TNiteCombo() { if(lboxhandle) // If & only if the ComboLBox was subclassed // (In case the drop down was NEVER SHOWN...) // Set the window proc back to the original function SetWindowLong(lboxhandle, GWL_WNDPROC, (LONG) NCB_FOriginalLBoxProc); } // ------------------------------------------------ // Override default TComboBox::WndProc // to catch the message that carries the HWND of // the combobox drop-down list as its' LParam void __fastcall TNiteCombo::WndProc(TMessage &Msg) { if(Msg.Msg == WM_CTLCOLORLISTBOX && !lboxhandle) // i.e. onceonly { Msg.Result=false; if(lboxhandle) return; // Only once through lboxhandle=reinterpret_cast<HWND>(Msg.LParam); // Call GetWindowLong to retrieve the original window proc NCB_FOriginalLBoxProc = (WNDPROC) GetWindowLong(lboxhandle, GWL_WNDPROC); // This variable is the flaw in the design of this component. There is only *one* // NCB_FOriginalLBoxProc. The same variable will be used when each instance of the // component subclasses the drop-down. It happens that the code works, but that's // because every instance of a TComboBox uses the same code (I suppose it's in // comctl32.dll) for the WndProc of the ComboLBox, and the same value goes into // this variable. // Call SetWindowLong to assign the new window proc for the ComboLBox SetWindowLong(lboxhandle, GWL_WNDPROC, (LONG)ControlWndProc); // Improved default WndProc code by Daniel Görlich forces repaint // if color is changed while the combobox is onscreen, i.e. // process WM_CTLCOLORLISTBOX message: TComboBox::MainWndProc(Msg); } else TComboBox::WndProc(Msg); } // ----------------------------------------------- #pragma package(smart_init)
|