COURSE of the MONTH

ListBox Like Delphi's Code Complete

I'm trying to make a ListBox decendent that works like Delphi's Code Complete/Insight TPopupListBox. I am not concerned with _when_ the ListBox should popup, but _how_ to make the ListBox popup and work properly.

I know that for this to work properly the ListBox should have the windows desktop set as its parent (the same way that Delphi's does), and an "invisible" window/WindowProc assigned with AllocateHWND; also it should SetFocus to the associated Memo immediately after appearing. But, I am stuck after that point.

The real problem seems to be how to hide/show the ListBox. The SetWindowPos API does not seem to be very effective -- when the ListBox does get shown, if at all, it contains items but none of the items' text/images are visible.

Can anyone show me the right way to make this work?

* Also, I have tried using a Form (with fsStayOnTop set), but this creates additional focus problems (e.g. hiding the Form/ListBox when the mouse is clicked elsewhere), so I would prefer to avoid this method.

Who is Participating?

If desktop is used as a parent for TPopupListBox, it won't receive WM_DRAWITEM
message, therefore not firing OnDrawItem event. Only VCL parent will dispatch message
to our component. Any windowed control could be used for this; I used TwinControl, but
any of its ancestors will do fine too.

>... always have AutoScroll as false, would it be ok to set PopListBox's parent to Application.MainForm or Form1?

Sure it would. I didn't use it only because Form1 will automatically show scroll bars if
TPopupListBox exceeds its height. But if you switch AutoScroll off, then this is not a
problem, and it would certainly be more elegant solution then a "dummy parent" :).

> Unfortunately, the AppMessage procedure did not work, the ListBox still remained onscreen even when I switched to another application.

Did you assigned AppMessage to Application.OnMessage event in TForm1.FormCreate?
I forgot to explicate this, sorry:

> Would setting/unsetting the mouse capture to the PopupListBox be a better way to hide it when the user clicks somewhere else (don't know how to implement this though)?

No it wouldn't - we need a mechanism to know when mouse is clicked anywhere outside
our PopupListBox. Once we know this, all that remains is hiding it (if it's visible, of course).
Alternative to using application-level message handler is using mouse hook, but this certainly
seems like an over-kill for our problem.
Also, there is always a moronic solution of TTimer and periodical mouse button-down check.

What is Delphi's Code Complete/Insight TPopupListBox url?
Since I don't know about this component, I'm not sure exactly what you need.
But, from the rest of your description, I think this code might help you out:

There is no download URL for Delphi's TPopupListBox. If you open the Delphi IDE and make the Code Complete ListBox appear, you will see with a utility like Spy++ that the Code Complete ListBox's class name is "TPopupListBox". (Note: this is also the class name of the drop down ListBox that is used with Delphi's object inspector).

The code you supplied does place the ListBox "on the desktop", but it does not solve my problem of how to manage hiding/showing the list.

Bascially what I am looking for is a owner-draw ListBox that will function (popup) in the same manner as the Code Complete ListBox from the Delphi IDE.

> …. but when the ListBox is style is lbOwnerDrawFixed or lbOwnerDrawVariable, none of the Items get drawn

You are absolutely right, sorry I didn’t test my code thoroughly enough.
The problem is that out control doesn’t get WM_DRAWITEM message due to the fact
that its parent is desktop. So, solution is simply setting its parent to any VCL control.
The most logical choice would be Form1, but this could create problems if forms AutoScroll
property is set. You could dynamically switch it on/off but this would make the code clumsy.

So, I decided creating dummy control that is used as a parent. Not too elegant, but works…
OK, on to the code, these are the changes:

> For example, if the user clicks somewhere else on the form, on another control, or on the form's non-client area --- what to do?

Well, it’s pretty straightforward: you just have to "catch" this. There are number of options,
but the easiest one, IMHO, is simply intercepting mouse clicks on application level and then
hiding our control if it’s not click target. The code that does this is below:

// In the implementation:
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
with Msg, p do // Hide p if necessary
if Visible and (Hwnd<>Handle) then
if (message=WM_LBUTTONDOWN) or (message=WM_RBUTTONDOWN) or
(message=WM_NCLBUTTONDOWN) or (message=WM_NCRBUTTONDOWN) or
(message=CM_DEACTIVATE) then Visible:=FALSE;
end;

0

camouAuthor Commented: 2002-07-10

Cynna,

Using the "Dummy" control seems to work (have no idea why, though). But I was wondering.. since my main form (the one used with the PopupListBox) will always have AutoScroll as false, would it be ok to set PopListBox's parent to Application.MainForm or Form1..?

Unfortunately, the AppMessage procedure did not work, the ListBox still remained onscreen even when I switched to another application. BTW, I'm using Delphi4 if that matters.

Would setting/unsetting the mouse capture to the PopupListBox be a better way to hide it when the user clicks somewhere else (don't know how to implement this though)?

Regards,

camou

0

camouAuthor Commented: 2002-07-11

Cynna,

Thanks for all the help! I had forgot to set Application.OnMessage := AppMessage; in my main form's OnCreate event. Everything works fine now. :)

Also, using the application message handler you suggested was much better for me than the alternative I was looking into - using a mouse hook.

FWIW though, I would be keen to see how Borland implemented their "PopupListBox" - whether or not they are using a mouse hook or not.