[RESOLVED] How to UseWaitCursor correctly
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 3 123 LastLast
Results 1 to 15 of 40

Thread: [RESOLVED] How to UseWaitCursor correctly

  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    [RESOLVED] How to UseWaitCursor correctly

    I have a Windows Forms app that does a lengthy calculation in a button's click handler. So I set the form's UseWaitCursor property to true when I start the calculation and reset it to false when I'm done.

    Sounds simple, but it doesn't work like it should. The wait cursor doesn't appear instantly, instead it takes time until I click on an arbitrary button (clicking on the form or other controls doesn't seem to have that effect), and even that doesn't always work the first time. Does anybody have a clue on how I could get rid of that annoying behaviour?

    TIA

  2. #2
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: How to UseWaitCursor correctly

    Ok, now that I have also encountered repainting problem with that app after writing my OP, I tried adding a call to Application::DoEvents() inside the loop. Now the wait cursor is shown correctly and the repainting problem is gone too. But now the normal cursor isn't always restored after the loop has finished. Sometimes it waits to restore the original cursor until I move the mouse. Now what then is this, and how can I get rid of it?

    Furthermore, I'm concerned about calling DoEvents(). I have never used it in VBA due to the warning about re-entry situations in the docs, and the MSDN article on the CLR function contains a waring that's quite similar. The suggested remedy is to move the time-consuming computation to a worker thread. This is no black magic to me either, because I have already done this in a quite similar situation in an MFC app. But for exatly that reason I know that it would considerably complicate things. Is there any less complicated way to make it a little safer?

  3. #3
    Join Date
    Jul 2002
    Posts
    2,494

    Re: How to UseWaitCursor correctly

    DoEvents is easy method allowing to keep UI responsive while long calculation is done. To have consistent results, you need to disable every control, possibly the whole form, while the loop is running, to prevent unexpected results, like closing the form or button click. Another way is to ignore any user command when long calculation is active, this can be done by using simple boolean flag. Of course, multithreading is the real thing, if you have a reason to use multithreading, do it and leave DoEvents to VB6 programmers.

  4. #4
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    DoEvents is easy method allowing to keep UI responsive while long calculation is done. To have consistent results, you need to disable every control, possibly the whole form, while the loop is running, to prevent unexpected results, like closing the form or button click.
    Would disabling the form automatically affect all the child controls or would I have to do that explicitly for every individual control?

    Another way is to ignore any user command when long calculation is active, this can be done by using simple boolean flag.
    This might be simpler if I can do that in a single, central place. Would a message filter (Application::AddMessageFiler()) be appropriate? And how would I distiguish messages related to user actions from those coming from the OS like WM_PAINT?

    Of course, multithreading is the real thing, if you have a reason to use multithreading, do it and leave DoEvents to VB6 programmers.
    Ok, maybe I'll finally pick that option once I have some spare time. This is only an experimental app anyway (although it has grown up to a really usable tool in the meantime), but OTOH, why not use these experimental apps to learn the real thing?

    But well, from the user's point of view, this isn't meant to be a multi-threaded app at all. Do I really gain something by doing the work in some detached thread while disabling all the controls anyway?

  5. #5
    Join Date
    Jul 2002
    Posts
    2,494

    Re: How to UseWaitCursor correctly

    Well-designed multithreaded program for this case (synchronous long loop) must show some kind of progress dialog with "Stop" button (if this necessary). Program with DoEvents should also allow to stop the process. The simplest way in both cases is to show some modal dialog with progress bar. If Stop is an option, it should react on the Stop button, and ignore any other commands. Without Stop, this dialog must ignore any user activity. In both cases, Paint messages are handled and application must redraw itself.
    Modal progress dialog automatically prevents any user interaction with its parent window, this solves the problem of unexpected commands.

  6. #6
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Cool Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    Modal progress dialog automatically prevents any user interaction with its parent window, this solves the problem of unexpected commands.
    This looks like it would solve the re-entry problem in an elegant manner and also would be another nice feature upgrade.

    I have added a progress dialog to the app that you can see in the attached image. Is that design "canonical"? It doesn't have a Stop button so far as I considered implementing it too complicated for now, and I suppose that I also would have to considerably change the way the stop button works when I change the way the progress dialog works.

    You see that the progress dialog still has an X button. I could easily get rid of all the others but I couldn't figure out how to get rid of this one. I suppose that deactivating (not hiding) the progress form would render that button irresponsive, but then the form would look disabled and it might have other adverse effects.

    Currently, the progress form is displayed using the Show() method from within the processing function that is a member of the main form class and gets called from a button's click handler. I suppose that already the fact that the Show() method returns instantly means that the dialog is not modal. I simply couldn't find a method like MFC's CDialog::DoModal() in the Form class. But OTOH the main form's title bar is displayed in the disabled state which could mean that the form is irresponsive to user activities, isn't it? At least repainting is working like it should.

    Ah, and I noticed that the setting of UseWaitCursor has no effect on the cursor when it is in the form's non-client area. Is that an indication of some problem?

    Processing time required for a 731 MB file is increased by about 67% by the progress processing. The progress bar's Increment() method is called every 512 bytes (might be too often but this is the most natural update frequency for the current execution structure) and the progress form's Text property is updated only when needed, IOW the percentage number actually changes. This might be a lot, but waiting is less boring when I can watch the progress dialog, and thus my subjective impression is even an improvement in processing speed!
    Attached Images Attached Images  
    Last edited by Eri523; August 26th, 2010 at 05:43 PM.

  7. #7
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,965

    Re: How to UseWaitCursor correctly

    You need to solve the basic problem of all apps that perform long running cpu intensive operations - that is, how to keep the UI responsive.

    The quick and dirty, tightly coupled difficult to maintain way is to put the long running code into the UI and pepper the code with Application.DoEvent calls. (as you can tell, there isn't any bias in my previous response).

    The proper way to do it where the user can start, stop, pause and resume the long running operation is to put the long running code into a worker thread (like BackgroundWorker) and update the ui with InvokeRequired.

    All you're updating, cursor, buttons failing to disable, etc issues are due to the fact that the long running operation doesn't allow the main UI thread to pump messages which prevents the operations from running that allow the cursor to change, the button to disable, or the form to repaint. These are classic symptoms when long running operations are put into the UI thread.

    Moving the long running operations into a worker thread requires more work to learn and implement, but once you learn how, it gets pretty easy. It also results in loosely coupled code that is easier to maintain. The code typically ends up being easier to unit test because the code performing the actual work isn't jammed in with the UI code.

  8. #8
    Join Date
    Jul 2002
    Posts
    2,494

    Re: How to UseWaitCursor correctly

    The required method is ShowDialog. To prevent user to close it, handle FormClosing event.
    To stop calculation, if necessary, set some boolean flag when user clicks the Stop button, and test for this flag after every call to DoEvents in the calculation loop.

  9. #9
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    The quick and dirty, tightly coupled difficult to maintain way is to put the long running code into the UI and pepper the code with Application.DoEvent calls. (as you can tell, there isn't any bias in my previous response).
    This is the way it is implemented right now. However, there is only a single call to Application::DoEvents() right at the end of the outer one of the two nested core loops, and that seems to be enough for now.

    All you're updating, cursor, buttons failing to disable, etc issues are due to the fact that the long running operation doesn't allow the main UI thread to pump messages which prevents the operations from running that allow the cursor to change, the button to disable, or the form to repaint. These are classic symptoms when long running operations are put into the UI thread.
    All these symptoms are gone now, but I'm still not completely satisfied yet.

    The proper way to do it where the user can start, stop, pause and resume the long running operation is to put the long running code into a worker thread (like BackgroundWorker) and update the ui with InvokeRequired.

    [...]

    Moving the long running operations into a worker thread requires more work to learn and implement, but once you learn how, it gets pretty easy. It also results in loosely coupled code that is easier to maintain. The code typically ends up being easier to unit test because the code performing the actual work isn't jammed in with the UI code.
    This is the way I want it to look when it's finished. I already had a brief look at the BackgroundWorker docs, but that's a lot stuff to read. I'm almost sure I'll come back with some more questions once I've finished reading (and maybe started implementing).

    The Stop button, BTW, is already there, although implemented the quick and dirty way. (See below.) I don't think there really is a need for Pause and Resume features in this simple app, however. (I can't even imagine what users should do with my app during a pause... )

    Quote Originally Posted by Alex F
    The required method is ShowDialog.
    Uh, how could that slip through!? It's right next to the Show() method I already used in the members list! And I have already used ShowDialog() with the OpenFileDialog class and could well have extrapolated from there.

    In fact, I already found ShowDialog() when I was working on another app, where I needed to use a form in the classic dialog box manner: to acquire user input. And that really could not have been done without that method in a reasonable way... But thank you for the pointer anyway, of course you couldn't have known that.

    To prevent user to close it, handle FormClosing event.
    I implemented it that way (this time after reading your tip, thanks), but it looks like a hack to me, as the button isn't really disabled, it just has no effect. But finally...

    To stop calculation, if necessary, set some boolean flag when user clicks the Stop button, and test for this flag after every call to DoEvents in the calculation loop.
    This is the way it is implemented right now. Works like a charm! And just by the way it takes away my concern about blocking the X button: I simply implemented the same semantics that the "literal" Cancel button has for the X button. Or is that non-canonical hack-style too?

    I really appreciate the assistance I get from both of you. Too bad I can't give you a positive rating yet again...
    Last edited by Eri523; August 28th, 2010 at 02:41 PM.

  10. #10
    Join Date
    Jul 2002
    Posts
    2,494

    Re: How to UseWaitCursor correctly

    Just side note: user may be surprised when some operation is stopped after pressing the Close button on the progress dialog.

  11. #11
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    Just side note: user may be surprised when some operation is stopped after pressing the Close button on the progress dialog.
    Actually, I was uncertain about that too. But how should I hande it then? Simply continue processing without the progress display (and the oppotunity to cancel the operation after that)?
    Last edited by Eri523; August 28th, 2010 at 08:53 AM.

  12. #12
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,965

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    Actually, I was uncertain about that too. But how should I hande it then? Simply continue processing without the progress display (and the oppotunity to cancel the operation after that)?
    Just add a Cancel button to the progress dialog. If the user presses the cancel button or the close 'X' button, ask the user of they wish to cancel the operation. If the operation is such that once started it can't be cancelled, then don't show a cancel button and disable the 'X' button.

  13. #13
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    Just add a Cancel button to the progress dialog.
    It already has one.

    If the user presses the cancel button or the close 'X' button, ask the user of they wish to cancel the operation.
    Nice idea, but I think I'll only implement that confirmation for the X button. Asking that after the user pressed the explicitly labeled Cancel button too much looks like some "Are you really sure?" kinda question to me.

    I'm afraid there is no variant of the standard message box with a Cancel and a Continue button. Using the Ok/Cancel variant and giving the Cancel button the meaning cancel the cancellation might be puzzling too. I think I'll settle for the Yes/No variant, that's pretty neutral.

    If the operation is such that once started it can't be cancelled, then don't show a cancel button and disable the 'X' button.
    The operation can be cancelled eaisly, that's no problem.

    Disabling the X button exactly was the problem I had with the variant of the progress dialog that didn't support cancellation: I couldn't find a way to do that. That's why Alex F suggested aborting the closing process in the FormClosing() handler.

  14. #14
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Resolved Re: How to UseWaitCursor correctly

    The X button issue is solved now. I found the solution "by accident" among the results of a forum search I did for a completely different reason.

    The solution is: Set the form's ControlBox property to false. The docs on that property say it's for disabling the form's system menu, which is gone anyway after setting FormBorderStyle to FixedDialog, so I never considered touching it. But it also hides the X button as some kind of side effect.

    Now it's time to tackle implementing the background worker...

  15. #15
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,965

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    It already has one.



    Nice idea, but I think I'll only implement that confirmation for the X button. Asking that after the user pressed the explicitly labeled Cancel button too much looks like some "Are you really sure?" kinda question to me.

    I'm afraid there is no variant of the standard message box with a Cancel and a Continue button. Using the Ok/Cancel variant and giving the Cancel button the meaning cancel the cancellation might be puzzling too. I think I'll settle for the Yes/No variant, that's pretty neutral.



    The operation can be cancelled eaisly, that's no problem.

    Disabling the X button exactly was the problem I had with the variant of the progress dialog that didn't support cancellation: I couldn't find a way to do that. That's why Alex F suggested aborting the closing process in the FormClosing() handler.
    The screen shot doesn't already have a cancel button. What you have is a close button (i.e the 'X' button). You can disable the 'X' button. Not sure how off the top of my head, but I'll look in google in a minute.

    The user prompt to verify canceling isn't like a "are you really sure?". It's pretty much the standard when cancelling a long running operation in this manner. If you don't have it, your users will be pissed when they thought the 'X' would merely hide the progress dialog.

    Btw, this isn't rocket science with regard to the standard message box buttons. Just use Yes/No and phrase the question accordingly. Something like "This will cancel the long running critical operation. You will have to rerun the long running critical operation again. Do you wish to cancel the long running critical operation?" "Yes" "No".

Page 1 of 3 123 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Azure Activities Information Page

Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center