Extender Property Providers


The discussion to this point has focused on the properties implemented by a component for itself. One example of such a property, TimeZoneModifier, allows AlarmClockControl to be time zone sensitive, allowing it to display the time in each time zone where an organization has offices. This allows each office to be visually represented with a picture box and an AlarmClockControl, as illustrated in Figure 11.11, with appropriate configuration of the TimeZoneModifier property on each AlarmClockControl.

Figure 11.11. Form with Multiple Time Zones (See Plate 16)


This works quite nicely but could lead to real estate problems, particularly if you have one AlarmClockControl for each of the 24 time zones globally and consequently 24 implementations of the same logic on the form. If you are concerned about resources, this also means 24 system timers. Figure 11.12 shows what it might look like.

Figure 11.12. One Provider Control for Each Client Control


Another approach is to have a single AlarmClockControl and update its TimeZoneModifier property with the relevant time zone from the Click event of each picture box. This is a cumbersome approach because it requires developers to write the code associating a time zone offset with each control, a situation controls are meant to help you avoid. Figure 11.13 illustrates this approach.

Figure 11.13. One Provider Control for All Client Controls, Accessed with Code


A nicer way to handle this situation is to provide access to a single implementation of the AlarmClockControl without forcing developers to write additional property update code. And .NET offers extender property support to do just this, allowing components to extend property implementations to other components.

Logically, an extender property is a property exposed by an extender provider, such as the AlarmClockControl, to other components in the same container, such as picture boxes. Extender properties are useful whenever a component needs data from a set of other components in the same host. For example, Windows Forms itself provides several extender provider components, including ErrorProvider, HelpProvider, and ToolTip. With respect to ToolTip, it makes a lot more sense to set the ToolTip property on each component than it does to set tool tip information for all components using an editor provided by ToolTip itself.

In our case, by implementing TimeZoneModifier as an extender property, we allow each picture box control on the form to get its own value, as shown in Figure 11.14.

Figure 11.14. One Provider Control for All Client Controls, Accessed with a Property Set


Such a solution allows us to create a form with a single AlarmClockControl that services multiple other controls, as illustrated in Figure 11.15.

Figure 11.15. AlarmClockControl Servicing Multiple Other PictureBox Controls


Exposing an extender property from your component requires that you first use the ProvideProperty attribute to declare the property to be extended:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : ScrollableControl, ... {...}


The first parameter to the attribute is the name of the property to be extended. The second parameter is the receiver type, which specifies the type of object to extend, such as PictureBox. Only components of the type specified by the receiver can be extended. If you want to implement a more sophisticated algorithm, such as supporting picture boxes and panels, you must implement the IExtenderProvider CanExtend method:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ...   public bool CanExtend(object extendee) {     // Don't extend self     if( extendee == this ) return false;     // Extend suitable controls     return ((extendee is PictureBox) ||             (extendee is Panel));   }   ... }


As illustrated in Figure 11.15, an extender provider can support one or more extendee components. Consequently, the extender provider must be able to store and distinguish one extendee's property value from that of another. It does this in the GetPropertyName and SetPropertyName methods, where PropertyName is the name you provided in the ProvideProperty attribute. Then, GetTimeZoneModifier simply returns the property value when requested by the Properties window:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ...   // Mapping of components to numeric time zone offsets   Hashtable timeZoneModifiers = new Hashtable();   ...   public int GetTimeZoneModifier(Control extendee) {    // Return component's time zone offset    return Convert.ToInt32(this.timeZoneModifiers[extendee]);   }   ... }


SetTimeZoneModifier has a little more work to do. First, it stores an extender provider property value on behalf of every extendee that chooses to set it. Second, it removes the value when an extendee chooses to unset it. Both actions operate over the same hashtable as used by GetTimeZoneModifier:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ...   int timeZoneModifier = 0;   ...   public void SetTimeZoneModifier(Control extendee, object value) {     // If property isn't provided     if( value == null ) {       // Remove it       this.timeZoneModifiers.Remove(extendee);     }     else {       // Add the time zone modifier as an integer       this.timeZoneModifiers[extendee] = Convert.ToInt32(value);     }   }   ... }


When an extender property value has been set for one or more extendees, the clock control needs to make sure the property values are applied for each extendee. To do this, the extender provider is notified that the extendee is currently active in some shape or form, and this is why SetTimeZoneModifier registers an extendee's Click event to be handled by the AlarmClockControl:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ...   public void SetTimeZoneModifier(Control extendee, object value) {     // If property isn't provided     if( value == null ) {       // Remove it       timeZoneModifiers.Remove(extendee);       if( !this.DesignMode ) this.extendee.Click -= extendee_Click;     }     else {       // Add the time zone modifier as an integer       timeZoneModifiers[extendee] = Convert.ToInt32(value);       if( !this.DesignMode ) this.extendee.Click += extendee_Click;     }   }   ...   void extendee_Click(object sender, System.EventArgs e) {     // Update the time zone     this.timeZoneModifier = this.GetTimeZoneModifier((Control)sender);   }   ...   protected override void OnPaint(PaintEventArgs e) {     ...     // Get specified date/time if control is in design time,     // or current date/time if control is in run time     DateTime now;     if( this.DesignMode ) {       // Get pretty date/time for design time       now = new DateTime(2005, 12, 31, 15, 00, 20, 0);     }     else {       // Get current date/time and apply the time zone modifier       now = DateTime.Now.AddHours(timeZoneModifier);     }     ...   } }


As with other properties, you can affect the appearance of an extender property in the Properties window by adorning the GetPropertyName method with attributes:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ...   [Category("Behavior")]   [Description("Sets the time zone difference from the current time.")]   [DefaultValue(0)]   public int GetTimeZoneModifier(Control extendee) {...}   public void SetTimeZoneModifier(Control extendee, object value) {...}   ... }


These attributes are applied to the extendee's Properties window view after compilation. Extended properties appear as an entry in the extendee component's Properties Window view with the following default naming format:

ExtendedPropertyName on ExtenderProviderName


This format, however, may not be as readable as you'd like, and it is inconsistent with the vast majority of property names you'll find in the Properties window. Luckily, you can use the DisplayName attribute to override the default name with something prettier:

// AlarmClockControl.cs [ProvideProperty("TimeZoneModifier", typeof(PictureBox))] partial class AlarmClockControl : IExtenderProvider, ... {   ... [Category("Behavior")] [Description("Sets the time zone difference from the current time.")] [DefaultValue(0)] [DisplayName("TimeZoneModifier")] public int GetTimeZoneModifier(Control extendee) {...} public void SetTimeZoneModifier(   Control extendee, object value) {...}  ... }


Note that for extender properties, you must adorn the GetPropertyName method with the DisplayName attribute. Figure 11.16 shows the TimeZoneModifier extender property behaving like any other property on a PictureBox control.

Figure 11.16. Extended Property in Action


Now that we've made the TimeZoneModifier property pretty, users will be so attracted to it that they'll be drawn to the Properties window to change it. If the changed value is not the default value, it is serialized to InitializeComponent, although this time as a SetTimeZoneModifier method call on the extender provider component, which is actually grouped with the extendee component:

// MultipleTimeZonesForm.Designer.cs partial class MultipleTimeZonesForm {   ...   void InitializeComponent() {     ...     // sydneyPictureBox     this.sydneyPictureBox.Name = "sydneyPictureBox";     this.sydneyPictureBox.Size = new System.Drawing.Size(117, 184);     this.alarmClockControl.SetTimeZoneModifier(       this.sydneyPictureBox, 10);     ...   } }


Extender properties allow one componentthe extenderto add properties to another component, the extendee. However, even though logically the extendee gets one or more additional properties, storage and property access are managed by the extender.




Windows Forms 2.0 Programming
Windows Forms 2.0 Programming (Microsoft .NET Development Series)
ISBN: 0321267966
EAN: 2147483647
Year: 2006
Pages: 216

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net