Introduction
In my previous article (about the Game of Life), I made a common mistake of expecting an object to be instantiated by the time the code reached the form re-size event. This worked fine during my tests, but as you will read in the comments below the article, chengkc found the null reference exception. The problem went away when chengkc changed the DPI setting back to the default of 96 DPI (was 120 DPI). This article tries to shed light on this phenomenon.
About DPI
You can reach this setting via:
- Display Properties,
- Settings tab,
- Advanced button,
- General tab.
This property page allows the user to increase the size of the desktop and task-bar icons by changing the DPI from 96 to 120. There is no apparent reason why this setting will affect the way WinForms load.
The test plan
We want to detect the sequence of events that are raised during a form start-up. By looking at the generated list of events and comparing the list for each circumstance, we should be able to gather important information. This information can then be used to understand why we need to be careful about our assumptions regarding form events.
The code
Each event handler will have to add a string to a list.
private List<string> _Events = new List<string>();
protected void Form1_Activated(object sender, EventArgs e)
{
_Events.Add("Form1_Activated");
}
protected void Form1_Deactivate(object sender, EventArgs e)
{
_Events.Add("Form1_Deactivate");
}
protected void Form1_Enter(object sender, EventArgs e)
{
_Events.Add("Form1_Enter");
}
And the easiest way to hookup these events is by using the form designer.
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.SuspendLayout();
this.Deactivate += new System.EventHandler(this.Form1_Deactivate);
this.Activated += new System.EventHandler(this.Form1_Activated);
this.Enter += new System.EventHandler(this.Form1_Enter);
this.ResumeLayout(false);
this.PerformLayout();
}
However, during the tests with the first version of this application, it was noted that some events do not fire as expected. It appears the designer does not fire all relevant events; probably, some events are suppressed by the "SuspendLayout
" method.
To ensure we analyze this properly, the event hookup code was tested using two additional scenarios. The three scenarios are as follows:
- Allow the designer to hookup these events.
- Hookup all relevant events before the call to "
InitializeComponent
".
- Hookup all relevant events after the call to "
InitializeComponent
".
This expanded our test plan to a minimum of six sets as test results (2 DPI states x 3 scenarios). To allow the reader to repeat the test cases on other configurations, the code was re-factored using inheritance. The CaptureForm
is our base class.
public class CaptureForm : Form
{
private List<string> _Events = new List<string>();
protected void Form1_Activated(object sender, EventArgs e)
{
_Events.Add("Form1_Activated");
}
protected void Form1_Deactivate(object sender, EventArgs e)
{
_Events.Add("Form1_Deactivate");
}
protected void Form1_Enter(object sender, EventArgs e)
{
_Events.Add("Form1_Enter");
}
public string GetRecordedEvents()
{
StringBuilder sb = new StringBuilder();
foreach (string eventText in _Events.ToArray())
{
sb.Append(eventText);
sb.Append("\r\n");
}
return sb.ToString();
}
}
The forms that handle our three scenarios are, firstly, FormBefore
:
public partial class FormBefore : CaptureForm
{
public FormBefore()
{
InitEventHandlers();
InitializeComponent();
}
private void InitEventHandlers()
{
this.SuspendLayout();
this.StyleChanged += new System.EventHandler(this.Form1_StyleChanged);
this.Deactivate += new System.EventHandler(this.Form1_Deactivate);
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
}
and then, FormAfter
:
public partial class FormAfter : CaptureForm
{
public FormAfter()
{
InitializeComponent();
InitEventHandlers();
}
private void InitEventHandlers()
{
this.SuspendLayout();
this.StyleChanged += new System.EventHandler(this.Form1_StyleChanged);
this.Deactivate += new System.EventHandler(this.Form1_Deactivate);
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
}
and lastly, FormDesigner
:
public partial class FormDesigner : CaptureForm
{
public FormDesigner()
{
InitializeComponent();
}
}
where FormDesigner
has the event hookups in the form designer code.
To drive the test, we create a main form called CaptureForm
with buttons, timers, and text boxes. On request, this main form will loop through each scenario and record the events. The list of events will appear side by side to facilitate easy comparison.
At the default 96 DPI, we get:
At 120 DPI, we get:
One other thing to mention is that in case a particular test produces a different result in a subsequent pass, we will record those events as well. However, the results seen above do not show more than our expected six sets.
Analysis
To analyze our results, I used a well known file compare tool.
Comparing 96 vs. 120 DPI using designer hookup:
Comparing 96 vs. 120 DPI using custom hookup before the designer code:
Comparing 96 vs. 120 DPI using custom hookup after the designer code:
From these comparisons, we see that for 120 DPI, extra events are raised. More specifically, the re-size event occurs before any other events. If a programmer would have code in the re-size event handler that depended on a state set in one of the other events, an exception or logical bug is effected. While developing and unit testing using 96 DPI, no apparent problems occur. Once deployed, some users will experience problems due to different configurations.
Conclusion
The first point to make is, the assumptions we make about the sequence of form events. This article may help to make the reader more careful about events. Using conditional statements or late referenced properties may alleviate the problem, but this article is not a focus on better design patterns.
The second and final point to make is that the test cases were limited to one changeable aspect of computer configuration (DPI). Using the application, I leave you with a test platform to scrutinize other configuration settings that may affect form events. Please post your findings below.
History
- Initial version dated 31 May 2008.