6

In Delphi 6, I could change the Mouse Cursor for all forms using Screen.Cursor:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Screen.Cursor := crHourglass;
end;

I am searching the equivalent in Firemonkey.

Following function does not work:

procedure SetCursor(ACursor: TCursor);
var
  CS: IFMXCursorService;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXCursorService) then
  begin
    CS := TPlatformServices.Current.GetPlatformService(IFMXCursorService) as IFMXCursorService;
  end;
  if Assigned(CS) then
  begin
    CS.SetCursor(ACursor);
  end;
end;

When I insert a Sleep(2000); at the end of the procedure, I can see the cursor for 2 seconds. But the Interface probably gets freed and therefore, the cursor gets automatically resetted at the end of the procedure. I also tried to define CS as a global variable, and add CS._AddRef at the end of the procedure to prevent the Interface to be freed. But it did not help either.

Following code does work, but will only work for the main form:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Application.MainForm.Cursor := crHourGlass;
end;

Since I want to change the cursor for all forms, I would need to iterate through all forms, but then the rollback to the previous cursors is tricky, as I need to know the previous cursor for every form.

My intention:

procedure TForm1.Button1Click(Sender: TObject);
var
  prevCursor: TCursor;
begin
  prevCursor := GetCursor;
  SetCursor(crHourglass); // for all forms
  try
    Work;
  finally
    SetCursor(prevCursor);
  end;
end;
Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67

2 Answers2

4

You'd have to implement your own cursor service that makes it possible to enforce a certain cursor.

unit Unit2;

interface

uses
  FMX.Platform, FMX.Types, System.UITypes;

type
  TWinCursorService = class(TInterfacedObject, IFMXCursorService)
  private
    class var FPreviousPlatformService: IFMXCursorService;
    class var FWinCursorService: TWinCursorService;
    class var FCursorOverride: TCursor;
    class procedure SetCursorOverride(const Value: TCursor); static;
  public
    class property CursorOverride: TCursor read FCursorOverride write SetCursorOverride;

    class constructor Create;
    procedure SetCursor(const ACursor: TCursor);
    function GetCursor: TCursor;
  end;

implementation

{ TWinCursorService }

class constructor TWinCursorService.Create;
begin
  FWinCursorService := TWinCursorService.Create;

  FPreviousPlatformService := TPlatformServices.Current.GetPlatformservice(IFMXCursorService) as IFMXCursorService; // TODO: if not assigned

  TPlatformServices.Current.RemovePlatformService(IFMXCursorService);
  TPlatformServices.Current.AddPlatformService(IFMXCursorService, FWinCursorService);
end;

function TWinCursorService.GetCursor: TCursor;
begin
  result :=  FPreviousPlatformService.GetCursor;
end;

procedure TWinCursorService.SetCursor(const ACursor: TCursor);
begin
  if FCursorOverride = crDefault then
  begin
    FPreviousPlatformService.SetCursor(ACursor);
  end
  else
  begin
    FPreviousPlatformService.SetCursor(FCursorOverride);
  end;
end;


class procedure TWinCursorService.SetCursorOverride(const Value: TCursor);
begin
  FCursorOverride := Value;
  TWinCursorService.FPreviousPlatformService.SetCursor(FCursorOverride);
end;

end.

MainUnit:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  TWinCursorService.CursorOverride := crHourGlass;
  try
    Sleep(2000);
  finally
    TWinCursorService.CursorOverride := crDefault;
  end;
end;
Sebastian Z
  • 4,520
  • 1
  • 15
  • 30
  • This doesn't help. Yes it allows a custom cursor. But it doesn't change any of the behaviour that rinntech is reporting. -1 – David Heffernan Mar 24 '15 at 12:18
  • @Sebastian Z: Thanks for this idea. I have tried to implement it this way: http://pastebin.com/rYND0eYQ . It does change the mouse cursor for every form, and its children, like buttons. Alas, I have the same problem like in David's solution. It seems like the GUI events like cursor-update are only triggered when the OnClick function is finished. Even `Application.ProcessMessages` did not help. Maybe it is not possible in FireMonkey to change the GUI/Cursor while being in an GUI-driven Event (OnClick). – Daniel Marschall Mar 24 '15 at 12:56
  • Have you tried calling `PreviousPlatformService.SetCursor(CursorOverride);` when setting `CursorOverride`? – Sebastian Z Mar 24 '15 at 13:03
  • I don't understand the voting here. I despair sometimes. @Sebastian I think you need to work a little harder to understand the question and try to realise that you are answering a completely different question to the one that was asked here. – David Heffernan Mar 24 '15 at 13:41
  • @rinntech: I've taken your implementation from pastebin and improved it so that it should update the cursor immediately. This works for me. David: I'm not sure how I answered a different question. I had exactly the same problem and solved it by implementing my own cursor service. – Sebastian Z Mar 24 '15 at 20:00
  • @Sebastian Thanks for the update! It seems to work. I have created 2 Forms with buttons, and I also gave one element a different cursor. When I click the "Work" button, the cursor changes to an hourglass for 2 seconds, for all forms and all children (also the ones with changed cursor property). When the work is done, the mouse cursor is resetted, and also the elements with changed cursor have their old cursor back. – Daniel Marschall Mar 25 '15 at 10:12
  • @DavidHeffernan: What do you mean with non-standard cursor? About Sabastian's post (revision 2015-03-24 19:50), is there something wrong, because you are sceptic about the custom CursorService - maybe I have overseen a disadvantage while I tested it? Please let me know. – Daniel Marschall Mar 25 '15 at 10:12
  • He changed the answer. It's fine now. – David Heffernan Mar 25 '15 at 10:15
1

The IFMXCursorService is how the FMX framework manages cursors. It is not intended for your use. The mechanism that you are meant to use is the form's Cursor property.

This means that you will need to remember the cursor for each form in order to restore it. I suggest that you use a dictionary to do that. Wrap the functionality up into a small class and then at least the pain is localized to the implementation of that class. You can make the code at the call site reasonable.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you. The idea with the dictionary is good. This is my current code: http://pastebin.com/KC3AedTJ . It works for Delphi 6, but does not work with a XE7 Firemonkey project. The code should work for VCL **AND** FMX projects. It is really annoying that there is no compiler switch for detecting if the project is FMX or not. --- Question #2: Why does the code `CS.SetCursor(ACursor); Sleep(5000);` has the expected result (for 5 seconds)? – Daniel Marschall Mar 24 '15 at 11:08
  • 1
    Because the framework calls `SetCursor` itself when you do things like mouse over a window. So it simply undoes whatever changes you make. – David Heffernan Mar 24 '15 at 11:25
  • Thanks for that explanation. This makes sense. Alas, `Form.Cursor` doesn't seem to be the best solution. First, it does only set the cursor on the form, but not on its children (buttons etc), and for some reason, the cursor does not get changed, when I am working in the main thread, e.g. using `Sleep(2000)` , and `Application.HandleMessages` does not work. – Daniel Marschall Mar 24 '15 at 12:12
  • `TForm.Cursor` is how you are expected to change cursor. – David Heffernan Mar 25 '15 at 07:22