Windows Forms 2.0 Programming (Microsoft .NET Development Series)
As you may have noticed, the code that's eventually serialized to InitializeComponent is laid out as an alphabetically ordered sequence of property sets, grouped by object. Order may become problematic if your component exposes range-dependent properties, such as Min/Max or Start/Stop pairs. For example, the clock control also has two dependent properties: PrimaryAlarm and BackupAlarm. (The Alarm property was split into two for extra-sleepy people who work too hard writing books.) Internally, the clock control instance initializes the two properties 15 minutes apart, starting from the current date and time: DateTime primaryAlarm = DateTime.Now; DateTime backupAlarm = DateTime.Now.AddMinutes(15); Both properties should check to ensure that the values are valid: public DateTime PrimaryAlarm { get { return this.primaryAlarm; } set { if( value >= this.backupAlarm ) { throw new ArgumentOutOfRangeException( "Primary alarm must be before Backup alarm"); } ... } } public DateTime BackupAlarm { get { return this.backupAlarm; } set { if( value < this.primaryAlarm ) { throw new ArgumentOutOfRangeException( "Backup alarm must be after Primary alarm"); } ... } }
With this dependence checking in place, at design time the Properties window shows an exception in an error dialog if an invalid property is entered, as shown in Figure 11.10. Figure 11.10. Invalid Value Entered into the Properties Window
This error dialog is great at design time, because it shows the developer the relationship between the two properties. However, there's a problem when the properties are serialized into InitializeComponent alphabetically: // AlarmClockControl.Designer.cs partial class AlarmClockControl { ... void InitializeComponent() { ... this.alarmClockControl.BackupAlarm = new System.DateTime(2005, 12, 8, 23, 21, 37, 607); this.alarmClockControl.PrimaryAlarm = new System.DateTime(2005, 12, 8, 23, 8, 0, 0); ... } } Notice that even if the developer sets the two alarms properly, as soon as BackupAlarm is set and is checked against the value of PrimaryAlarm, a run-time exception results if BackupAlarm comes before the default PrimaryAlarm value. To avoid this, you'll want to ensure that the component is notified when its properties are being set from InitializeComponent in "batch mode" so that they can be validated all at the same time at the end. Implementing the ISupportInitialize interface provides this capability, with two notification methods to be called before and after initialization: namespace System.ComponentModel { interface ISupportInitialize { void BeginInit(); void EndInit(); } }
When a component implements this interface, calls to BeginInit and EndInit are serialized to InitializeComponent: // AlarmClockControl.Designer.cs partial class AlarmClockControl { ... void InitializeComponent() { ... ((System.ComponentModel.ISupportInitialize) (this.alarmClockControl)).BeginInit(); ... this.alarmClockControl.BackupAlarm = new System.DateTime(2005, 12, 8, 23, 21, 37, 607); this.alarmClockControl.PrimaryAlarm = new System.DateTime(2005, 12, 8, 23, 8, 0, 0); ... ((System.ComponentModel.ISupportInitialize) (this.alarmClockControl)).EndInit(); ... } }
The call to BeginInit signals the entry into initialization batch mode, a signal that is useful for turning off value checking: // AlarmClockControl.cs partial class AlarmClockControl : ISupportInitialize, ... { ... bool initializing = false; void BeginInit() { this.initializing = true; } public DateTime PrimaryAlarm { get {...} set { if( !this.initializing ) { /* check value */ } ... } } public DateTime BackupAlarm { get {...} set { if( !this.initializing ) { /* check value */ } ... } } }
Placing the appropriate logic into EndInit performs batch validation: // AlarmClockControl.cs partial class AlarmClockControl : ISupportInitialize, ... { ... void EndInit() { // Check alarm values if( this.primaryAlarm >= this.backupAlarm ) { throw new ArgumentOutOfRangeException( "Primary alarm must be before Backup alarm"); } this.initializing = false; } ... }
EndInit also turns out to be a better place to avoid the timer's Tick event, which currently fires once every second during design time. Although the code inside the Tick event handler doesn't run at design time (because it's protected by a check of the DesignMode property), it would be better to not even start the timer at all until run time. However, because DesignMode can't be checked in the constructor, a good place to check it is in the EndInit call, which is called after all properties have been initialized at run time or at design time: // AlarmClockControl.cs partial class AlarmClockControl : ISupportInitialize, ... { ... void EndInit() { if( !this.DesignMode ) { ... // Initialize timer this.timer.Interval = 1000; this.timer.Tick += new System.EventHandler(this.timer_Tick); this.timer.Enabled = true; } ... } ... }
InitializeComponent nicely guarantees that ISupportInitialize.EndInit is invoked after your component's child components have initialized. ISupportInitializeNotification
ISupportInitialize, however, doesn't guarantee that it will be invoked before or after other components have completed their own initialization. This could be an issue when your component initialization is dependent on another component being initialized. For example, some controls, such as DataGridView, have a DataSource property, which is usually a reference to another component hosted by the form, such as a BindingSource.[5] In these situations, you can implement ISupportInitializeNotification on your component to provide initialization information to dependent components: [5] DataGridView and BindingSource are explored in depth in Chapter 16: Data Binding Basics and Chapter 17: Applied Data Binding. namespace System.ComponentModel { interface ISupportInitializeNotification : ISupportInitialize { // Properties bool IsInitialized { get; } // Events event EventHandler Initialized; }
ISupportInitializeNotification exposes the IsInitialized property, which dependent components can check to see whether your component has initialized. If IsInitialized returns true, the dependent components initialize as usual. If false is returned, they register with the Initialized event to be notified when the component they're depending on initializes. If AlarmClockControl anticipates that other components will be dependent on it for initialization, it implements ISupportInitializeNotification: // AlarmClockControl.cs partial class AlarmClockControl : ISupportInitializeNotification, ... { ... #region ISupportInitializeNotification Members public event EventHandler Initialized; public bool IsInitialized { get { return !this.initializing; } } #endregion #region ISupportInitialize public void BeginInit() { this.initializing = true; } public void EndInit() { ... this.initializing = false; // Notify dependent components if( this.Initialized != null ) { this.Initialized(this, EventArgs.Empty); } } #endregion ... } The code on a dependent component that uses this implementation would look something like this: public class MyDependentComponent : Component, ISupportInitialize { #region ISupportInitialize Members public void BeginInit() {...} public void EndInit() { ISupportInitializeNotification notifier = this.alarmClockControl as ISupportInitializeNotification; // Is the component we're depending on initialized? if( (notifier != null) && !notifier.IsInitialized ) { // If not, ask it to let us know when it does, so we // can complete our own initialization as per normal notifier.Initialized += this.notifier_Initialized; } else { // Initialize as per normal ... } } #endregion void notifier_Initialized(object sender, EventArgs e) { if( sender == this.alarmClockControl ) { // Initialize as per normal ... // Unregister event ISupportInitializeNotification notifier = sender as ISupportInitializeNotification; if( notifier != null ) { notifier.Initialized -= this.notifier_Initialized; } } } } As you've seen, the Windows Forms Designer and the Properties window provide all kinds of declarative and programmatic design-time help to augment a component's design-time experience, including establishing how a property is categorized and described, how its state is serialized to the InitializeComponent method, and how the initialization process can be coordinated in special scenarios. |
Категории