-2

If I have a ButtonClick event which sets Cursor := crHourglass, Application.ProcessMessages, then use a TOpenDialog to choose a file, and then do something CPU-intensive, the cursor behaves differently depending on whether it is over the existing control when the Open Dialog closes. If the cursor is over the control then the cursor remains as an hourglass; if it's outside the application completely and then moved into the area while the intensive process is still taking place, the cursor remains as an arrow. One cannot click or do anything so it's confusing to the user to get an arrow but not be able to do anything with it.

Stepping through the debugger shows the Cursor is -11 everywhere it should be. Using Screen.Cursor instead of Cursor has the same effect.

Is there a solution?

procedure TMyForm.LoadButtonClick(Sender: TObject);
begin
  Cursor := crHourglass;
  Application.ProcessMessages;
  if OpenDialog.Execute then begin
    // Do something intensive
    // Cursor = crHourglass here but what is displayed is different
  end;
  Cursor := crDefault;
end;
GeoffM
  • 1,603
  • 5
  • 22
  • 34
  • 7
    For the love of god, when are you people going to stop calling ProcessMessages the moment your code doesn't behave exactly as you desire!!! – David Heffernan May 14 '13 at 18:50
  • Forgive me if I've missed something, but setting the cursor **without** calling ProcessMessages results in the cursor not actually changing until **after** the intensive work has finished. – GeoffM May 14 '13 at 19:00
  • 2
    You only appear to have solved a problem. But now you've got another problem. What happens if you click the button twice before the first event handler runs? And in any case, what you say is not true. In an event handler, assign to `Cursor` and call `Sleep`, and see what happens. – David Heffernan May 14 '13 at 19:04
  • The dangers of posting just the relevant bits of code! All the controls can be disabled but it's still not solving the problem. My only guess is that there is some event fired when the mouse moves into the window, but of course it's not processed because it's busy doing other things for a second or so. Which suggests that Rob's idea of passing it off to a separate thread would be the solution. – GeoffM May 14 '13 at 19:10
  • 2
    Well, you didn't seem to post the relevant code because I cannot reproduce what you describe. And Rob's right on all scores. Don't call `ProcessMessages`. Don't change cursor while file dialog is showing. And don't run long tasks on UI thread. – David Heffernan May 14 '13 at 19:16
  • It's not a long task as such - just about long enough that the user could try to click on something if (s)he's fast I guess... anyway, separate thread. – GeoffM May 14 '13 at 19:32

1 Answers1

8

First, make sure to set the cursor only while the CPU-intensive operation is active. Don't change the cursor while choosing a file — you never see any other programs do that, after all.

Second, when you say Cursor in your code, you're referring to the form's property. You might wish to use Screen.Cursor instead so that your entire program displays the same cursor.

Third, there's no need to call Application.ProcessMessages. Messages are going to be processed as soon as you display a dialog box anyway, and besides, there are no particular messages you need processed.

Finally, consider protecting the cursor changes with a try-finally block so that problems in your processing don't leave the cursor in the wrong state:

if OpenDialog.Execute then begin
  Screen.Cursor := crHourglass;
  try
    // TODO: something intensive
  finally
    Screen.Cursor := crDefault;
  end;
end;

If the operation really uses a lot of time, consider moving it off to another thread. Then you don't have to worry about the GUI being unresponsive, and so you won't have to change the cursor in the first place.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I stripped down the code to the bare minimum, KISS-principle, hence no checking/exception handling. As I say, I did try Screen.Cursor without any difference. Hmm, but you're right about the messages processing with the OpenDialog but it still doesn't solve the problem - when OpenDialog returns the cursor is an hourglass if the cursor is still within the calling window; an arrow if outside the window and moved into the window area. I also had the call to set the cursor inside the if statement without any difference. – GeoffM May 14 '13 at 19:03
  • 1
    You stripped it down to the point of removing the CPU-intensive code, too? Test that. Perhaps the program is too busy to handle mouse-move messages, and therefore too busy to change the cursor. – Rob Kennedy May 14 '13 at 19:16
  • Well, I didn't think the CPU-intensive bit was relevant. Maybe it is. Kind of damned if I do post it, as there'd be a couple of other units to pull in as well as the bits in this unit, and damned if I don't! Anyway, "too busy to change the cursor" [when moving into the window] does seem to be the issue as I remarked in my OP comment a few moments ago. – GeoffM May 14 '13 at 19:28
  • @RobKennedy You need need to handle mouse move messages. A simple `Cursor := crHourglass; Sleep(999999);` in an event handler results in the cursor changing. Most likely the `something intensive` just changes the cursor back! – David Heffernan May 14 '13 at 19:36