Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change mouse cursor when over certain components without affecting other cursor-setting code

I'm using an ancient precursor to the DevExpress QuantumGrid (MasterView) in Delphi XE2 and would like certain cells to effectively act as hyperlinks (change the mouse cursor from crDefault to crHandPoint when over them and trigger an action on click).

The configuration of the grid component is such that individual cells are not their own component, and I will need to find the cell from the mouse cursor coordinates and set the cursor from there.

I think I need to set a few events on my grid object to achieve this, but I'm a little uncomfortable about how these events will interact with code that sets the cursor to an hourglass when doing long-running operations (currently handled using IDisposible to set the cursor back to original when finished) and want to double-check whether there's a better way of doing this before I get started and then find a tonne of edge-cases that leave the mouse cursor in the wrong state.

I think I need to override:

  • omMouseMove - get XY co-ordinates and set the cursor to hand/arrow
  • onMouseDown - get XY co-ordinates and 'activate' hyperlink if present (possibly revert to arrow? The hyperlink will usually open a new window and the code called may change the cursor to an hourglass)
  • onMouseLeave - reset cursor to arrow (this event isn't actually exposed, so think I'll need to handle messages manually)

This kind of functionality comes as default on a TButton, but I couldn't see in the VCL how it's achieved at first glance, and may be a feature of the underlying Windows control.

like image 341
Matt Allwood Avatar asked Aug 28 '26 23:08

Matt Allwood


1 Answers

This is a scenario I would prefer. The cursor is set from the WM_SETCURSOR message handler and backend work signalled by a flag. Link click is then handled from the MouseDown method override. Note that the cursor is changed only for this control (when the mouse cursor hovers the control). In pseudocode:

type
  THitCode =
  (
    hcHeader,
    hcGridCell,
    hcHyperLink { ← this is the extension }
  );

  THitInfo = record
    HitRow: Integer;
    HitCol: Integer;
    HitCode: THitCode;
  end;

  TMadeUpGrid = class(TGridAncestor)
  private
    FWorking: Boolean;
    procedure DoStartWork;
    procedure DoFinishWork;
    procedure UpdateCursor;
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    function GetHitTest(X, Y: Integer): THitInfo; override; 
  end;

implementation

procedure TMadeUpGrid.DoStartWork;
begin
  FWorking := True;
  UpdateCursor;
end;

procedure TMadeUpGrid.DoFinishWork;
begin
  FWorking := False;
  UpdateCursor;
end;

procedure TMadeUpGrid.UpdateCursor;
begin
  Perform(CM_CURSORCHANGED, 0, 0); { ← triggers WM_SETCURSOR handler if needed }
end;

procedure TMadeUpGrid.WMSetCursor(var Msg: TWMSetCursor);
var
  P: TPoint;
  HitInfo: THitInfo;
begin
  { the mouse is inside the control client rect, inherited call here should
    "default" to the Cursor property cursor type }
  if Msg.HitTest = HTCLIENT then
  begin
    GetCursorPos(P);
    P := ScreenToClient(P);
    HitInfo := GetHitTest(P.X, P.Y);
    { if the mouse is hovering a hyperlink or the grid backend is working }
    if FWorking or (HitInfo.HitCode = hcHyperLink) then
    begin
      { here you can setup the "temporary" cursor for the hyperlink, or
        for the working grid backend }
      if not FWorking then
        SetCursor(Screen.Cursors[crHandPoint])
      else
        SetCursor(Screen.Cursors[crHourGlass]);
      { tell the messaging system that this message has been handled }
      Msg.Result := 1;
    end
    else
      inherited;
  end
  else
    inherited;
end;

procedure TMadeUpGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  HitInfo: THitInfo;
begin
  if Button = mbLeft then
  begin
    HitInfo := GetHitTest(X, Y);
    { the left mouse button was pressed when hovering the hyperlink, so set
      the working flag, trigger the WM_SETCURSOR handler "manually" and do the
      navigation; when you finish the work, call DoFinishWork (from the main
      thread context) }
    if HitInfo.HitCode = hcHyperLink then
    begin
      DoStartWork;
      DoSomeNavigation(HitInfo.HitRow, HitInfo.HitCol);
    end;
  end;
end;

function TMadeUpGrid.GetHitTest(X, Y: Integer): THitInfo;
begin
  { fill the Result structure properly }
end;
like image 170
Victoria Avatar answered Sep 01 '26 04:09

Victoria