Introduction
This is not a complex article or topic, and attempts to solve an issue I just came across and thought others might be coming by as well. A quick search on everyone's favourite search engine yielded no results, so here I am typing up my own solution.
The issue is quite straight forward - say you're in a scenario where you need to disable a dialog or form because you're waiting for something to happen on another thread, and do not want the user interacting with anything in the meantime.
A good example of this would be a login dialog that asks for credentials to an SQL Server database, and upon clicking on "Ok" the login is attempted. If, for example, the server name specified was wrong (and you're using the default 30 second connection timeout), the user would have to wait for 30 seconds until an error message is displayed.
While the connection is being established, you want to make sure that the user does not continue interacting with your screen and that all controls are effectively disabled during this time. The easy way to do this would be to simply set the form's Enabled
flag to false
, but that would be nasty.
The thing is - if your application is properly multi threaded, and the connection attempt is happening behind the scenes, your dialog will remain unlocked during this time and the user should be able to move it around. If you disable the entire screen, you can no longer move the form around, which leads the user to believe things are frozen.
The Solution
The solution to this would be quite simple. Just disable all controls on the screen, one by one, and then you're set. Right? Well, almost. You would also have to consider that some controls might already be disabled on the screen, and upon disabling and subsequently re-enabling the screen, you'd want those controls to remain disabled. Going back to the login sample, disabled controls might be the user name and password fields if you're using NT authentication.
Then there's also the issue of some controls possibly containing others (such as group boxes, panels, etc.), and their child controls would have to be considered as well.
To achieve this, a dynamic pair of methods are needed. One, to catalog all controls on your screen (storing their names and Enabled states) and disable them, and another, which uses the catalog built by the first method to put everything back the way it was.
The UIHelper
class has these two methods, and they are quite simple.
The GetControlStatesAndDisable()
method takes as a single parameter the Controls
collection you want to disable (such as Form.Controls
). It catalogs and returns the names and Enabled
states of all controls in it (and recursively calls itself in order to go down the tree of any sub Controls lists such as for group boxes or panels), and then disables all the controls in this list.
The RestoreControlStates()
method is called after your work is complete, and takes as parameters the same Controls collection used earlier as well as the catalog created by the first method.
For example, assuming you're currently within a Form
class:
SortedList<string, bool> controlStates =
UIHelper.GetControlStatesAndDisable(this.Controls);
UIHelper.RestoreControlStates(mForm1.Controls, controlStates);
Using the Sample Code
The sample code attached shows how this helper class works. It is comprised of two forms - one containing of several test controls and container controls (with their own child controls), and next to each control is a button letting you enable or disable it.
The second form is used to enable or disable all of form1
by using the helper class and methods. Note that no matter how you enable or disable individual controls on form1
, enabling or disabling them through the buttons on form2
(and therefore the helper class) always returns them to their original Enabled states.
The two methods are as follows (and can be found in UIHelper.cs in the attachment to this article):
public static SortedList<string, bool> GetControlStatesAndDisable
(Control.ControlCollection controlCollection)
{
SortedList<string, bool> controlStates = new SortedList<string, bool>();
for (int ctr = 0; ctr < controlCollection.Count; ctr++)
{
controlStates.Add(controlCollection[ctr].Name, controlCollection[ctr].Enabled);
foreach (KeyValuePair<string, bool> pair in GetControlStatesAndDisable
(controlCollection[ctr].Controls))
{
controlStates.Add(pair.Key, pair.Value);
}
controlCollection[ctr].Enabled = false;
}
return controlStates;
}
public static void RestoreControlStates
(Control.ControlCollection controlCollection, SortedList<string, bool> controlStates)
{
for (int ctr = 0; ctr < controlCollection.Count; ctr++)
{
if (controlStates.ContainsKey(controlCollection[ctr].Name))
{
controlCollection[ctr].Enabled = controlStates[controlCollection[ctr].Name];
}
RestoreControlStates(controlCollection[ctr].Controls, controlStates);
}
}
Points of Interest
I did notice, however, that disabling and re-enabling a container control does in fact retain all the individual Enabled states of its child controls, so there may be no need to traverse the control collections and store (or restore) their individual Enabled states. This also works when enabling or disabling the entire form, but that then freezes the form and makes it look like the app is frozen or has crashed. I have left the recursive code in the sample anyway, just in case it comes in handy for another scenario I have not thought of.
I hope this little tidbit comes in handy to someone else, and as always, comments and suggestions are welcome.
History
- Jan 01 2010 - Initial publication
- Jan 02 2010 - Added the
UIHelper
code into the article