- Components
- Design-Time Integration Basics
- Extender Property Providers
- Type Converters
- UI Type Editors
- Custom Designers
- Where Are We?
So far the discussion has focused on the properties implemented by a control for itself. TimeZoneModifier, an example of such a property, allows the clock control to be configured to display the time in any time zone. One way to use this feature is to display the time in each time zone where your organization has offices. If each office were visually represented with a picture box, you could drag one clock control for each time zone onto the form, manually adjusting the TimeZoneModifier property on each clock control. The result might look like Figure 9.15.
Figure 9.15. Form with Multiple Time Zones
This works quite nicely but could lead to real estate problems, particularly if you have one clock control 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 9.16 shows what this might look like.
Figure 9.16. One Provider Control for Each Client Control
Another approach is to have a single clock control 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 avoid. Figure 9.17 illustrates this approach.
Figure 9.17. 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 clock control without forcing the developer to write additional property update code. .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 provided by an extender component, like the clock control, on other components in the same container, like picture boxes. Extender properties are useful whenever a component needs data from a set of other components in the same host. For example, WinForms itself provides several extender components, including ErrorProvider, HelpProvider, and ToolTip. In the case of the ToolTip component, it makes a lot more sense to set the ToolTip property on each control on a form than it does to try to set tooltip information for each control using an editor provided by the ToolTip component 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 9.18.
Figure 9.18. One Provider Control for All Client Controls, Accessed with a Property Set
Exposing an extender property from your control requires that you first use ProvidePropertyAttribute to declare the property to be extended:
[ProvidePropertyAttribute("TimeZoneModifier", typeof(PictureBox))] public class ClockControl : Control { ... }
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:
class ClockControl : ..., IExtenderProvider { bool IExtenderProvider.CanExtend(object extendee) { // Don't extend self if( extendee == this ) return false; // Extend suitable controls return( (extendee is PictureBox) || (extendee is Panel) ); } ... }
As you saw in Figure 9.18, the provider supports one or more extendee controls. Consequently, the provider control must be able to store and distinguish one extendee's property value from that of another. It does this in the Get<PropertyName> and Set<PropertyName> methods, in which PropertyName is the name you provided in ProvidePropertyAttribute. Then GetTimeZoneModifier simply returns the property value when requested by the Property Browser:
public class ClockControl : Control, IExtenderProvider { // Mapping of components to numeric timezone offsets HashTable timeZoneModifiers = new HashTable(); public int GetTimeZoneModifier(Control extendee) { // Return component's timezone offset return int.Parse(timeZoneModifiers[extendee]); } ... }
SetTimeZoneModifier has a little more work to do. Not only does it put the property value into a new hash table for the extendee when provided, but it also removes the hash table entry when the property is cleared. Also, with the sample TimeZoneModifier property, you need to hook into each extendee control's Click event, unless the control isn't using the extender property. SetTimeZoneModifier is shown here:
class ClockControl : ..., IExtenderProvider { HashTable timeZoneModifiers = new HashTable(); ... public void SetTimeZoneModifier(Control extendee, object value) { // If property isn't provided if( value == null ) { // Remove it timeZoneModifiers.Remove(extendee); if( !this.DesignMode ) { extendee.Click -= new EventHandler(extendee_Click); } } else { // Add the offset as an integer timeZoneModifiers[extendee] = int.Parse(value); if( !this.DesignMode ) { extendee.Click += new EventHandler(extendee_Click); } } } }
As with other properties, you can affect the appearance of an extender property in the Property Browser by adorning the Get<PropertyName> method with attributes:
class ClockControl : ..., IExtenderProvider { [ Category("Behavior"), Description("Sets the timezone difference from the current time"), DefaultValue("") ] public int GetTimeZoneModifier(Control extendee) { ... } ... }
These attributes are applied to the extendee's Property Browser view.
With all this in place, you can compile your extender component to see the results. Extended properties will appear in the extendee component's properties with the following naming format:
<ExtendedPropertyName> on <ExtenderProviderName>
Figure 9.19 shows the TimeZoneModifier extender property behaving like any other property on a PictureBox control.
Figure 9.19. Extended Property in Action
If a property is set and is not the default value, it is serialized to InitializeComponent(), as a SetTimeZoneModifier method call, and grouped with the extendee component:
void InitializeComponent() { ... this.clockControl1.SetTimeZoneModifier(this.pictureBox1, -11); ... }
Extender properties allow a component to add to the properties of other components in the same host. In this way, the developer can keep the data with the intuitive component, which is not necessarily the component that provides the service.