3

I have a tall listbox with a variable number of items. It won't always be full. I know when a user doesn't select an item by this code:

if ( lstbox.ItemIndex = -1 ) then
  ShowMessage('here');

But this does not work when I an Item is selected and then I click in the 'whitespace' of the listbox. How do I detect that sort of situation?

NGLN
  • 43,011
  • 8
  • 105
  • 200
Paul
  • 95
  • 6
  • I noticed this recently myself in XE5. I'm curious if this is a relatively recent change in behavior in TListboxes, because I swear it used to be that if you clicked in the whitespace whether an item was selected or not, the value of .ItemIndex would be -1. Now it ignores the click if an item is already selected. – David Schwartz Sep 23 '14 at 01:01

1 Answers1

7

You can do this in a number of ways. One would be in the OnMouseDown event using the X and Y parameters to that event, which are the client co-ordinates in the listbox at which the user clicked:

procedure TMyForm.ListBox1MouseDown(Sender: TObject;
                                    Button: TMouseButton;
                                    Shift: TShiftState;
                                    X,  Y: Integer);
begin
  if TListbox(Sender).ItemAtPos(Point(X, Y), TRUE) <> -1 then
    // item was clicked
  else
    // 'whitespace' was clicked
end;

But this won't affect the behaviour in any OnClick event. If you need to perform this test in OnClick then you need to obtain the mouse position and convert it to the listbox client co-ordinates before doing the same test:

procedure TMyForm.ListBox1Click(Sender: TObject);
var
  msgMousePos: TSmallPoint;
  mousePos: TPoint;
begin
  // Obtain screen co-ords of mouse at time of originating message
  //
  // Note that GetMessagePos returns a TSmallPoint which we need to convert to a TPoint
  //  in order to make further use of it

  msgMousePos := TSmallPoint(GetMessagePos);  

  mousePos := SmallPointToPoint(msgMousePos);
  mousePos := TListbox(Sender).ScreenToClient(mousePos);

  if TListbox(Sender).ItemAtPos(mousePos, TRUE) <> -1 then
    // item clicked
  else
    // 'whitespace' clicked
end;

NOTE: GetMessagePos() obtains the mouse position at the time of the most recently observed mouse message (which should in this case be the message that originated the Click event). However, if your Click handler is invoked directly then the mouse position returned by GetMessagePos() is likely to have little or no relevance to the processing in the handler. If any such direct invocation might sensibly utilise the current mouse position, then this may be obtained using GetCursorPos().

GetCursorPos() is also much more straightforward to use as it obtains the mouse position in a TPoint value directly, avoiding the need to convert from TSmallPoint:

GetCursorPos(mousePos);

Either way, the fact that your handler is dependent upon mouse position in any way makes directly invoking this event handler problematic so if this is a consideration then you might wish to isolate any position independent response in the event handler into a method that can be explicitly invoked if/when required, independently of the mouse interaction with the control.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • There's also CursorPos of the global Mouse. – Sertac Akyuz Sep 22 '14 at 21:20
  • It should be GetMessagePos rather than GetCursorPos. Same distinction as GetKeyState and GetAsyncKeyState. But bear in mind the possibility on OnClick firing for some reason other than a mouse event. – David Heffernan Sep 22 '14 at 21:38
  • Unless the OnClick event has been invoked directly, in which case the message loop observed mouse position will potentially be massively incorrect, where-as GetCursorPos() has a very remote chance of being slightly off. GetMessagePos() is appropriate when implementing a direct response to a message, but in a VCL event handler you do not have that certainty. – Deltics Sep 22 '14 at 22:30
  • If OnClick is fired outside of a mouse event, cursor position is not meaningful. That's the second part of my comment. If OnClick is fired in response to a mouse event then GetMessagePos should be used. But never GetCursorPos. You want the program to respond to where you clicked, not where you've got to when the program gets round to responding. Rob's answer here explains it quite well: http://stackoverflow.com/questions/826914/how-do-i-get-the-coordinates-of-the-mouse-when-a-control-is-clicked – David Heffernan Sep 23 '14 at 04:38
  • The OP should consider using GetMessagePos() and these comments should cause them so to do. Just in case, I shall update the answer to highlight the option. – Deltics Sep 23 '14 at 07:24
  • In fact, a refactoring suggestion occurred to me, dealing with the fact that whichever method they choose, if direct invocation of the Click handler is a concern then the position independent aspects of the response should be factored out, leaving the Click handler to server solely as an event responder (good practice in any event, but worth highlighting I think). – Deltics Sep 23 '14 at 07:41
  • GetCursorPos() takes a TPoint, GetMessagePos() takes a Cardinal, can I convert that to a TPoint? – Paul Sep 23 '14 at 13:13
  • @Paul - GetMessagePos does not take anything. You can cast its return to a TSmallPoint, and then use SmallPointToPoint to convert it to a TPoint. – Sertac Akyuz Sep 23 '14 at 16:58
  • Thanks for the update and the comments. I was as you say hoping primarily to get the asker to think about the issue. – David Heffernan Sep 23 '14 at 19:25
  • @Sertac - I updated the answer with the corrected usage of GetMessagePos(), something I forgot when replacing the previous GetCursorPos() example. – Deltics Sep 23 '14 at 21:02