Extender Property Providers






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, with appropriate configuration of the TimeZoneModifier property on each AlarmClockControl.

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 shows what it might look like.

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 illustrates this approach.

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.

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.

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, 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 shows the TimeZoneModifier extender property behaving like any other property on a PictureBox control.

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.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows