11

I've inherited a Winforms application that does a lot of long running calls into the application server from the UI thread, so the UI stays unresponsive, unusable, unclosable for quite some time. (Which makes me really go AAAAAAAAARGH!)

I plan to move the server calls to a background thread and have the UI disabled - but movable & closable - while the background thread does its work.

So what would be the best way to inhibit user input to my application? I'm thinking along the lines of "modal progress dialog", but I'd prefer a solution that does not force me to throw visuals into the face of the user (some server operations run within less than 500ms so a dialog is not optimal ...)

Is there any way in Winforms to prevent the user from launching actions or changing data in the application while letting through a few select things (resize, show, hide and family and the user closing the window)? I'd prefer a way that does not make me access every UI element in my forms and set it to disabled ... there are quite a lot of them and that application really has a "hacked in the UI designer until it shows flashy things" style of source code. No way of refactoring EVERY smelly thing until release date ...

Oh, by the way, this App lives in .net framework 2

froh42
  • 5,190
  • 6
  • 30
  • 42
  • Why dont you just disable the button that initiated the long running call? – Gulzar Nazim Mar 15 '09 at 18:14
  • The UI is a data entry form, the long running call could be a "save" involving lots of domain objects. I want to prevent the user from changing any data in the form while save is running for example. By the way THIS ( http://stuffthathappens.com/blog/2008/03/05/simplicity ) is SO true ... – froh42 Mar 15 '09 at 18:36
  • when do you want to enable back the form? if you try to do that on "_RunWorkerCompleted" event you will get an exception because you are not allowed (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx - first note) . You can only do it manually for each item in the form or for first container. – Edd Oct 13 '11 at 13:53
  • @froh42 StuffThatHappens.com now gives _Failed to load page content_, I assume in honour of its name. It is still [available on Wayback machine](https://web.archive.org/web/20090218035454/http://stuffthathappens.com/blog/2008/03/05/simplicity/). – R. Schreurs May 12 '17 at 14:15

6 Answers6

6

The only way I know of is to get some/all controls as disabled. However, depending on how the controls are laid out, it isn't necessary to set every control as disabled - when a container control is disabled, then all of its children are disabled, too. For example, if you have a GroupBox with some controls, if you set the GroupBox to disabled, then all of the controls within the GroupBox will be disabled, too.

Andy
  • 30,088
  • 6
  • 78
  • 89
  • is not possible : Cross-thread operation not valid: Control 'groupBox2' accessed from a thread other than the thread it was created on. :( – Edd Oct 13 '11 at 14:09
  • 1
    @Edd You'll need to disable the UI from the UI thread, not the background thread. Then it will work. – Andy Oct 13 '11 at 15:39
  • you are right, but how do you enable it back when process is completed in the background worker? how do you signal to the UI form to enable? because from the completed event, you can't. – Edd Oct 14 '11 at 12:17
  • You have to do it on the UI thread. You can either use Control.Invoke(), or the events provided by BackgroundWorker (depending on how you're doing the work in the first place). – Andy Oct 14 '11 at 16:27
5

Choose some upper level container control (might be the form itself), and set it's Enabled property to false. All controls placed on that control will be disabled, which will prevent them from receiving input.

driis
  • 161,458
  • 45
  • 265
  • 341
2

The problem with simply enabling/disabling buttons is that UI events will still get added to the messagequeue while the task is running....

Say you have a Search button and a Reset button. When you click Search, you disable both buttons. The search runs for a while. If the user clicks Reset, even while it's disabled, the search will reset immediately upon completing.

The quick and dirty way around this is to add an

Application.DoEvents();

call immediately before re-enabling the buttons. DoEvents() will process all the click events on the controls, which will be ignored for controls that are still disabled.

Handprint
  • 444
  • 2
  • 4
  • 13
1

On the Win32 API level, there's a EnableWindow function you can disable any window hand with (you need to get the underlying handle of your main window, of course, and of any other top level windows). I haven't tested it in WinForms, though. (I'm pretty sure there are other answers on the User32 level, but I can't remember the API calls right now.)

Note: this doesn't grey out controls, which decreases its utility from a usability point of view (the user just sees a 'frozen application'). It gets marginally better if you at least set an hourglass cursor.

However, this is a quick-and-dirty way of dealing with asynchronous processing. Do you really need to lock the user out of your entire application? Perhaps it's just a few functions that should be banned while a request is underway?

Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
0

I'm answering an old thread here, but I saw this question and remembered an old WinForm app of mine where I had the same problem of disabling a form while still allowing it to be resized and moved etc. I loaded up my code and found the solution was to:

  1. Place a Panel control on the form and give the Dock property a value of DockStyle.Fill.
  2. Instead of placing all the form's constituent top-level controls directly into the form, put them into the Panel control.
  3. Now for the magical bit - when you want to disable the form's controls, simply set the Panel's Enabled property to false. When you do this, all controls contained by the Panel control will become disabled too, but the form itself is still enabled, and therefore resizable etc.. To restore the state of all controls, simply set the Enabled property back to true.

Strictly speaking, you don't have to use a Panel control, any Container Control should exhibit the same behaviour. Even a PictureBox control will do the trick!

Kev
  • 1,832
  • 1
  • 19
  • 24
0

Unfortunately, with WinForms, you'll probably need to disable/enable the buttons in a single shared method. This is one of the advantages WPF brings over windows forms - it's much easier to handle these situations.

As for not blocking your UI - you'll need to move your calls to an asynchronous programming model. In your case, I'd recommend taking a look at the ThreadPool.

You'll want to do something like have the button disable your controls, call out your operation on the thread pool, then reenable them on completion.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373