IExtenderProvider interfaces provide and extend the behaviors of existing classes. For example, you are probably familiar with the ToolTip control. You need to place only one ToolTip control on a Windows Form to extend every control on that form (including the form itself) to include a ToolTip property. This coordination is created by implementing a class that implements IExtenderProvider and applying the ProvidePropertyAttribute to that class.
While thinking about a good, practical example for an extender provider, I was searching the Web and was surprised to encounter Phillip Davis's "Extended Interfaces for Toolbars" at http://www.codeproject.com/cs/menu/extendedtoolbar.asp. Davis reminded me that buttons on the Windows Forms toolbar aren't individuated to the extent that each button has its own click event. As a result, programmers have to use some kind of conditional logic in the ButtonClick event to determine which button was actually clicked. Clearly this situation should be rectified in future versions of .NET, but for now it is a suitable candidate for using an extender provider.
Borrowing and modifying Davis's solution, the objective is to implement an IExtenderProvider interface that permits us to track individual toolbar button Click events. Literally we are providing Click events to toolbar buttons without inheriting from ToolBar and ToolBarButton , but rather by extending the controls as they exist. Listing 9.17 demonstrates an implementation of an IExtenderProvider interface that allows you to associate a menu item with a toolbar button, borrowing the menu item's Click event.
Listing 9.17 Implementing an IExtenderProvider Interface
1: Imports System.ComponentModel 2: Imports System.Drawing 3: Imports System.Windows.Forms 4: 5: <ProvideProperty("MenuItem", GetType(ToolBarButton)), _ 6: ToolboxBitmap("..\Chapter 9\ClickProvider\Event1.bmp"), _ 7: Description("Provides a Click event for a ToolBarButton."), _ 8: Category("Events")> _ 9: Public Class ClickEventProvider 10: Inherits Component 11: Implements IExtenderProvider 12: 13: Private hashtable As hashtable = New hashtable() 14: Private ToolBar As ToolBar = Nothing 15: 16: Public Function CanExtend( _ 17: ByVal extendee As Object) As Boolean _ 18: Implements IExtenderProvider.CanExtend 19: 20: Return TypeOf extendee Is ToolBarButton 21: End Function 22: 23: Public Function GetMenuItem( _ 24: ByVal button As ToolBarButton) _ 25: As MenuItem 26: 27: If (hashtable.Contains(button)) Then 28: Return CType(hashtable(button), _ 29: MenuItem) 30: End If 31: 32: Return Nothing 33: End Function 34: 35: Public Sub SetMenuItem( _ 36: ByVal button As ToolBarButton, _ 37: ByVal item As MenuItem) 38: 39: If (ToolBar Is Nothing) Then 40: ToolBar = button.Parent 41: AddHandler button.Parent.ButtonClick, _ 42: AddressOf ToolbarHandler 43: End If 44: 45: If (hashtable.Contains(button)) Then 46: hashtable(button) = item 47: Else 48: hashtable.Add(button, item) 49: End If 50: End Sub 51: 52: Private Sub ToolbarHandler(ByVal sender As Object, _ 53: ByVal e As ToolBarButtonClickEventArgs) 54: 55: If (hashtable.Contains(e.Button)) Then 56: 57: CType(hashtable(e.Button), _ 58: MenuItem).PerformClick() 59: 60: End If 61: End Sub 62: 63: End Class
If we want the extender to be accessible from the Toolbox, we can inherit from the Component class as demonstrated in line 10. Line 11 shows the Implements statement for the IExtenderProvider interface.
IExtenderProvider requires that you implement CanExtend , which answers the question, "Can this IExtenderProvider extend a specific type?" The ClickEventProvider class returns a Boolean for CanExtend by testing the type of the extendee against the ToolBarButton type. (If you wanted to implement a general extender like the ToolTip provider, you might test the extendee against a more general type like the Control class.)
We are extending a class by defining properties in the IExtenderProvider interface that will be associated with whatever class we associate this extender with. The ProvidePropertyAttribute in line 5 indicates the name of the property to add to the extendee; the second argument indicates that kind of objects that will be extended. Rather than actually implementing a property, we need to implement Get name and Set name methods , where name is the name we passed as the first argument to the ProvidePropertyAttribute . Thus, since we passed MenuItem (line 5), we need to implement GetMenuItem and SetMenuItem ; the extendee will be provided with a MenuItem by this extender.
SetMenuItem (lines 35 through 50) is defined to associate our local ToolbarHandler with this extender. ToolBar raises a single Click event regardless of the button clicked. That Click event will occur here. (Keep in mind that events in .NET are multicast, so we can have the ToolbarHandler here and other consumers can add their event handlers too.) The second half of SetMenuItem uses the specific ToolBarButton instance as a key into a local hashtable and assigns the specific MenuItem instance to the hashtable index referred to by the button key. The indexing takes into account that the button may have been added once already (see line 45).
GetMenuItem (lines 23 through 33) checks whether the internal collection contains the argument ToolBarButton . If it does, MenuItem is returned. If not, Nothing is returned. By using the hashtable and button instances as keys we can associate a distinct MenuItem with each ToolBarButton .
Finally, ToolbarHandler (lines 52 through 61) plays the role of event handler for the toolbar itself. Because the clicked button is passed to ToolbarHandler , we normally would use the case or conditional statement in such a handler. In this instance we index the internal hashtable with the clicked button (line 55) and use the returned instance of MenuItem to invoke the PerformClick method (lines 57 and 58).
Unfortunately this solution to the ToolBarButton Click event problem is not wholly satisfactory, although Davis's conception is clever. The problem is twofold. First, the ToolBarButton should expose events relevant to a button, and second, the implementation in Listing 9.17 requires that there be a MenuItem for every ToolBarButton . However, we know from experience that there is seldom a one-to-one mapping between ToolBarButton instances and MenuItem instances.
Let's set aside the obvious chicanery of creating menu items that are invisible and disabled to support our implementation. We want and deserve tools that mitigate the need for such machinations, and the limited number and completeness of controls is something that needs to be addressed. There is at least one alternate solution. We could implement the extender provider to implement an actual Click event. Instead of tracking menu item and button associations, we could track toolbar buttons and event handlers. Intuitively this makes more sense. The difficulty here is that we cannot perform event handler assignments in the Properties window for Visual Basic .NET (although we can in C#). If we elected to implement the IExtenderProvider using event handlers, we would need to call the Set name and Get name methods directly, as shown below.
ClickEventProvider1.SetClick(ToolBarButton1, _ AddressOf MenuItem2_Click)
This too clearly highlights the presence of IExtenderProvider and is less intuitive and convenient than simply modifying a property. An alternate ClickEventProvider can be found in the ExtenderProviderDemo.sln file. Both the menu item and the event handler implementations are available for download.