Introduction
This article is intended to show you an easy way to centralize your logic when opening a Windows Form.
Background
It is common that when developing a Windows application you will want to perform either some type of operation or validation that will determine whether a form can be opened. Many times the same code will need to be executed in different places throughout your project. Obviously a good design will not litter the application with the same code over and over again. So, why not use the constructor of a form to perform any operation or validation logic prior to opening the form? Using the constructor of a form to do certain operations does have limitations. For instance, if you have FormA
and you perform all security validation(s) in the constructor, nothing will stop the developer from doing the following and making the form visible to the user:
FormA frm = new FormA();
FormA.Show();
In this example, putting security logic in the FormA
constructor is too early and we want to defer that logic to a later point in time. Putting the logic in the Show()
method would be the latest point prior to the form opening. Doing this also forces your code to be more defensive.
Another question someone may ask is why not put the security logic in the Activated
, Enter
or Load
events of the form? In some situations that would be fine, but the raising of these events actually infers the form may have been made visible to the user. Your code will not be very clean if you are to put the same logic into every event handler previously mentioned. If the form cannot be made visible to the user, for whatever reason, it is simpler to stop the process before the events are raised.
Assumptions
I am making a couple assumptions when proposing this solution:
- The code base you are working with has a base form where all of the forms in your project inherit from.
- The constructor is being used for initializing the presentation of the form.
- The constructor is not being used to perform any logic that will be used to determine whether the form will be opened.
- The constructor is not performing any logic that will be committed to any objects that are passed into the form's constructor. This is important because if the constructor is committing a new state to an object passed into the form's constructor, and the form will not be opened when the
Show()
or ShowDialog()
methods are called, you will be in a not so happy place.
Solution
To solve this problem, we will simply use the keyword new
to override the base implementation of Show()
and ShowDialog()
in the base form (BaseForm
) of our project. A derived Windows Form cannot override the Show()
and ShowDialog()
methods, so we will override the methods using the keyword new
, insert our own logic and then call to the base method.
public new virtual DialogResult ShowDialog() {
if (CanOpenForm)
return base.ShowDialog();
ShowFormError();
return DialogResult.None;
}
public new virtual DialogResult ShowDialog(IWin32Window owner) {
if (CanOpenForm)
return base.ShowDialog(owner);
ShowFormError();
return DialogResult.None;
}
public new virtual void Show() {
if (CanOpenForm) {
base.ShowDialog();
} else {
ShowFormError();
return;
}
}
protected virtual bool CanOpenForm {
get {
return true;
}
}
protected virtual void ShowFormError() {
ShowFormError("Form could not be opened.");
}
protected virtual void ShowFormError(string sErrorMessage) {
ShowFormError(sErrorMessage, "Form Open Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
protected virtual void ShowFormError(string sErrorMessage, string sCaption,
MessageBoxButtons buttons, MessageBoxIcon icon) {
MessageBox.Show(sErrorMessage, sCaption, buttons, icon);
}
Let's now review the above code. The Show()
and ShowDialog()
methods all check the CanOpenForm
property before the base method is called. You have the option of making a different CanOpenForm
property for each Show()
or ShowDialog()
method but more than likely using the same property will be good enough. Note that the CanOpenForm
property has been made virtual, this is so all derived forms can override this property and add form specific validation. Also note that I have made Show()
and ShowDialog()
virtual so you can completely override the validation logic on a derived form.
Note that in the overrides of ShowDialog()
, specifically return DialogResult.None
if CanOpenForm
failed. This is because base.ShowDialog()
by default will return DialogResult.None
. If the user were to press the "X" in the top right hand corner of a modal dialog form, DialogResult.Cancel
would be returned. We do not want to return that value because it would infer that the dialog form might have been made visible to the user when in actuality it had not.
Another neat thing you can do is to ensure that a form is being opened properly. For instance, let's say your project has a BaseForm
and BaseDialogForm
that inherits from the BaseFrom
. The BaseDialogForm
is meant to be a modal form that all other modal forms (with OK and Cancel buttons) will inherit from. Since this from is a modal form, you may want to override the Show()
method.
public override void Show() {
System.Diagnostics.Debug.Assert(false,
"All Dialog Box Forms should be modal.");
base.Show();
}
When overriding the base Show()
method, I simply send a debug assert to the developer saying that this method should not be used. Although the method still calls the base.Show()
, it is not overly obtrusive in its implementation. You have many other options:
- Force the issue by calling
System.Diagnostics.Debug.Fail
or do not call any method at all.
- Force the overridden
Show()
method to call ShowDialog()
.
- Anything else, there is no hard and fast rule, it is what ever makes sense.