Search code examples
delphimousecursor-positiondelphi-10.4-sydney

How to get the coordinates of the mouse pointer inside a control


I have a TPopupMenu where one of the items should (after doing other stuff) select the item of the TChecklistBox (CategoryFilter) the mouse cursor is at. Currently I do this with the following code, but this is inaccurate as it selects the next or 2ndnext item!

var
  ClickedItem: Integer;
  CategoryFilter: TChecklistBox;
begin
  CategoryFilter.CheckAll(cbUnchecked, FALSE, FALSE);
  ClickedItem := CategoryFilter.ItemAtPos(Mouse.CursorPos.Subtract(CategoryFilter.ClientOrigin), TRUE);
  if ClickedItem > -1 then
    CategoryFilter.Checked[ClickedItem] := TRUE;
end;

How can I fix this and get the correct item, the mouse is over?


Solution

  • The important thing here is that the mouse's screen coordinates (x, y) at the time you right click a list item are generally not the same as the mouse's screen coordinates (x', y') at the time you click (or otherwise invoke) the menu item. I mean, often you move the mouse to the menu item in order to click it. So you need to save the coordinates at mouse down time.

    Fortunately, the VCL does this for you. The popup menu's PopupPoint property contains these coordinates.

    So you can do

    procedure TForm1.MenuItem1Click(Sender: TObject);
    begin
      var LIdx := CheckListBox1.ItemAtPos
        (
          CheckListBox1.ScreenToClient(PopupMenu1.PopupPoint),
          True
        );
      if LIdx <> -1 then
        CheckListBox1.Checked[LIdx] := True;
    end;
    

    Actually, I do essentially this myself:

    Screenshot of a dialog box with a check list box with a context menu.

    Please note that you also should make sure that right-clicking an item also selects (not checks) it. It's poor UX otherwise.

    If you do so, you can, as a bonus, simply use the ItemIndex property to find the clicked item, so you don't need PopupPoint at all:

    for var i := 0 to Count - 1 do
      Checked[i] := i = ItemIndex; // check only this one
    

    (Here Self is the check list box, because I have created my own check list box descendant with all these enhancements built in. That's the right thing to do, obviously.)

    I also make double-clicking an item toggle its checkbox, make ^A check all, make ^C copy the selected item to clipboard, add convenient functions to obtain the array of checked strings and/or objects, etc.