Implementing an Extender Provider

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 9ClickProviderEvent1.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 Getname and Setname 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 Setname and Getname 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.152.166