smart tag is a panel that displays next to a control and contains a list of commonly used properties. For example, in the Visual Studio (VS) editor, the Windows Forms DataGridView control uses a smart tag with the title “DataGridView Tasks” that displays a list of commonly used properties (see Figure 1). In .NET 2.0, developers can add smart tag features to their custom and user controls. This article walks you through a real world project that shows how to add your own smart tags to controls.
|Figure 1: Smart Tag in Visual Studio: Here’s a look at the DataGridView control’s smart tag.|
In this article, you’ll see how to create a custom date control. I initially built this control because a client specifically wanted an easy way to enter dates?without using the mouse to select a date. In addition, the client wanted the ability to customize the date display format?either numerically or alphabetically?to represent the month. On the surface this task looks easy to solve; simply add a couple of TextBox controls to the form and configure them to display day, month, and year accordingly. However, the client had numerous forms requiring date entry; therefore, a better solution was to create a user control that aggregates all the TextBox controls into one single control. The first part of this article discusses creating the user control, while the second part shows you how to add smart tag support to it.
Developing the User Control
Using VS 2005, create a new Windows Application project named SmartTag. This application will host the user control that you’ll see how to build in this article.
|Figure 2: Design Mode: The figure shows the design surface of the user control with controls added to it.|
Now, add a new Windows Control Library project to the existing solution. In Solution Explorer, right-click the SmartTag solution and select Add ( New Project?. Select the Windows Control Library template and name the project CustomDateControl. Click OK. The left portion of Figure 2 shows the default design surface of the user control (the default name is UserControl1.vb). Populate the design surface with three ComboBox controls as shown on the right side of Figure 2.
Set the properties of the three ComboBox controls with the values shown in Table 1.
The first two ComboBox controls will display either the day or month (depending on the user’s configuration) while the third ComboBox control will display the year. Users can click the ComboBox control to select a date or simply type the date using the keyboard.
|Editor’s Note: This article was first published in the July/August 2007 issue of CoDe Magazine, and is reprinted here by permission.|
Programming the User Control
With the design of the user control completed, you can now start coding. Switch to the code behind file for the user control (UserControl1.vb) and add the following namespace:
The System.ComponentModel namespace provides classes that are used to implement the run-time and design-time behavior of components and controls. In this case, you’ll be adding attributes to the control class and its properties to customize its behavior. You will see the effects of these attributes shortly.
Next, declare the Formats and Months enumerations to represent the format and months, respectively:
Public Enum Formats DDMMYYYY '---e.g. 29 02 2006--- DDMmmYYYY '---e.g. 12 Jan 2006--- MMDDYYYY '---e.g. 02 25 2006--- MmmDDYYYY '---e.g. Jan 15 2006--- End Enum Public Enum Months Jan = 1 Feb = 2 Mar = 3 Apr = 4 May = 5 Jun = 6 Jul = 7 Aug = 8 Sep = 9 Oct = 10 Nov = 11 Dec = 12 End Enum
Defining the Member Variables
Change the default class name from UserControl1 to DateControl and declare the member variables as shown below:
_ Public Class DateControl '---member variables--- Private _Format As Formats = Formats.DDMMYYYY Private _Day As Integer Private _Month As Integer Private _year As Integer Private _MinYear As Integer = 1918 Private _MaxYear As Integer = 2050 Private _DayControl As New ComboBox Private _MonthControl As New ComboBox '---event handler for error--- _ Public Event Err(ByVal str As String) End Class
Notice the following:
- The control fires the Err event if a validation error occurs, providing the control with a way to communicate the error to the user.
- The Category attribute causes the Err event to appear under the Date Settings category in the VS Properties window. The DescriptionAttribute specifies the message VS displays at the bottom of the properties window.
- The DefaultEvent attribute indicates the default event handler for this control. When you drop this control on the design surface of a Windows Form, double-clicking it will automatically cause VS 2005 to insert stub code for the Err event handler.
- The _DayControl and the _MonthControl reference the respective ComboBox controls representing the day and month.
Next, add a Format property to the class so users can select the date format. Depending on the format selected, the control uses the _DayControl and _MonthControl variables to reference the actual ComboBox controls representing the day and month. Also, when a user selects a format, the control calls the InitDates() method (defined shortly) to refresh the control dynamically to reflect the selected format:
_ Public Property Format() As Formats Get Return _Format End Get Set(ByVal value As Formats) _Format = value Select Case Me.Format Case Formats.DDMmmYYYY, Formats.DDMMYYYY _DayControl = cbbField1 _MonthControl = cbbField2 Case Formats.MMDDYYYY, Formats.MmmDDYYYY _DayControl = cbbField2 _MonthControl = cbbField1 End Select _DayControl.BackColor = Color.White _MonthControl.BackColor = Color.White InitDates() End Set End Property
The Day property lets users set the day of the date individually, using a numeric value from 1 to 31. Notice that if the day value is set to an invalid value, the control raises the Err event:
_ Public Property Day() As Integer Get Return _Day End Get Set(ByVal value As Integer) If value >= 1 And value <= 31 Then _Day = value _DayControl.Text = _Day Else RaiseEvent Err("Day is out of range.") End If End Set End Property
The Month property lets users set the month to a numeric value from 1 to 12. While it is similar to the Day property, you need to take special care when users select a format that displays the month alphabetically (such as Jan or Feb), because you must convert the numeric value of the month into its corresponding enumeration value:
_ Public Property Month() As Integer Get Return _Month End Get Set(ByVal value As Integer) If value >= 1 And value <= 12 Then _Month = value If Me.Format = Formats.DDMMYYYY Or _ Me.Format = Formats.MMDDYYYY Then _MonthControl.Text = _Month Else _MonthControl.Text = _ [Enum].GetName( _ GetType(Months), _Month) End If Else RaiseEvent Err( _ "Month is out of range.") End If End Set End Property
Creating the Year property is much more straightforward:
_ Public Property Year() As Integer Get Return _year End Get Set(ByVal value As Integer) _year = value cbbYear.Text = _year End Set End Property
In addition to properties for setting the day, month, and year, the control has a [Date] (the square brackets are because Date is a reserved word in VB) property to let users set the date using the Date class:
_ Public Property [Date]() As Date Get Try Dim d As New Date( _ Me.Year, Me.Month, Me.Day) Return d Catch ex As Exception Return Nothing End Try End Get Set(ByVal value As Date) Me.Day = value.Day Me.Month = value.Month Me.Year = value.Year End Set End Property
The MinYear and MaxYear properties set the first and last years to display:
_ Public Property MinYear() As Integer Get Return _MinYear End Get Set(ByVal value As Integer) _MinYear = value End Set End Property _ Public Property MaxYear() As Integer Get Return _MaxYear End Get Set(ByVal value As Integer) _MaxYear = value End Set End Property
As mentioned previously, the InitDates() method refreshes the control so it reflects the currently selected format:
Private Sub InitDates() '---day--- _DayControl.Items.Clear() For i As Integer = 1 To 31 _DayControl.Items.Add(i) Next '---month--- _MonthControl.Items.Clear() For i As Integer = 1 To 12 If Me.Format = _ Formats.DDMMYYYY Or Me.Format = _ Formats.MMDDYYYY Then _MonthControl.Items.Add(i) Else _MonthControl.Items.Add( _ [Enum].GetName( _ GetType(Months), i)) End If Next '---year--- cbbYear.Items.Clear() For i As Integer = Me.MinYear To Me.MaxYear cbbYear.Items.Add(i) Next '---display the set date--- _DayControl.SelectedIndex = Me.Day - 1 _MonthControl.SelectedIndex = Me.Month - 1 cbbYear.Text = Me.Year End Sub
The preceding method simply populates the ComboBox controls with the various dates so that the user can choose from them, and sets them to display the currently selected date.
Servicing the Events
When a user clicks the ComboBox control you need to set the day, month, or year correctly according to the selected format. You accomplish this by servicing the SelectedIndexChanged event for each of the three ComboBox controls (see Listing 1).
You must take special care for cases where the month is represented alphabetically?you need to convert the enumeration value to its corresponding numeric value.
When dealing with dates you must ensure that the date selected by the user is a valid one. For example, February never has more than 29 days, and only 28 days except in leap years. This control checks for date validity every time the ComboBox controls lose their focus. You can use the Date.DaysInMonth() method to find out the number of days in a particular month and year (see Listing 2).
|Figure 3. ToolBox: The figure shows the process of adding a new control to the ToolBox.|
If the selected date is invalid, the control displays a balloon message (using the ToolTip control).
Testing the Control
That's it! You can now test the control. Right-click the CustomDateControl project item in Solution Explorer and select "Build" to compile the user control into a DLL. After you have compiled the user control, add it to the ToolBox by right-clicking the "All Windows Forms" tab in the ToolBox, and then selecting "Choose Items?" (see Figure 3).
In the Choose Toolbox Items dialog box, click the Browse button and navigate to the binDebug folder of the CustomDateControl project. Select the CustomDateControl.dll, click Open, and then click OK. You should now be able to see the DateControl listed in the ToolBox.
Back in the SmartTag Windows application project, drag and drop the DateControl from the ToolBox onto the default Form1 (see Figure 4).
Right-click the DateControl on Form1 and select Properties to view its properties. Figure 5 shows the various DateControl properties and events listed under the Date Settings category (you must select the Categorized view in the Properties window to view this).
|Figure 6. Populating the Form: Here's a design-time view of the RadioButton controls for the sample form.|
The code below shows some examples of how you can configure the DateControl user control programmatically.
Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load '---e.g. 1--- With DateControl1 .Format = CustomDateControl.Formats.DDMMYYYY '---display today's date--- .Date = Today End With '---e.g. 2--- With DateControl1 .Format = CustomDateControl.Formats.MmmDDYYYY '---displays Sep 15 1991--- .Date = New Date(1991, 9, 15) End With '---e.g. 3--- With DateControl1 .Format = CustomDateControl.Formats.MMDDYYYY '---displays 12 16 2006--- .Day = 16 .Month = 12 .Year = 2006 End With End Sub
Press F5 to debug the Windows application. Figure 7 shows various views of the DateControl user control, while Figure 8 shows the balloon message the control displays if a user selects an invalid date.
Adding Smart Tags
Now that you have built your user control, you can add a smart tag to it. Adding a smart tag to the control allows developers to configure commonly used properties of the control without having to do so via the Properties window.
The first step is to add a reference to the System.Design.dll assembly to the CustomDateControl project. Right-click the CustomDateControl item in Solution Explorer and select Add Reference?. In the .NET tab, select the System.Design component and click OK.
Next, add the System.ComponentModel.Design namespace to UserControl1.vb using these two Imports statements:
Imports System.ComponentModel Imports System.ComponentModel.Design
Creating the Control Designer
Add a new class definition to the UserControl1.vb file and name the class DateControlDesigner. This class will inherit from the System.Windows.Forms.Design.ControlDesigner class:
Public Class DateControlDesigner Inherits _ System.Windows.Forms. _ Design.ControlDesigner End Class
You use the ControlDesigner class to extend the design-mode behavior of your user controls. In this case, you want to display a smart tag for the user control. Add the following member variables and property:
Public Class DateControlDesigner Inherits _ System.Windows.Forms.Design.ControlDesigner Private lists As _ DesignerActionListCollection Public Overrides ReadOnly _ Property ActionLists() _ As DesignerActionListCollection Get If lists Is Nothing Then lists = New _ DesignerActionListCollection() lists.Add(New DateControlActionList( _ Me.Component)) End If Return lists End Get End Property End Class
The DateControlDesigner class shown above contains a member variable called lists that holds a list of DesignerActionList objects. The DesignerActionList class provides the base class for types that define a list of items used to create a smart tag panel. This variable will contain a list of items that will eventually appear in the smart tag. You do this by creating an instance of the DateControlActionList class. Define the DateControlActionList class as follows, adding the new class definition to UserControl1.vb:
Public Class DateControlActionList Inherits System.ComponentModel. _ Design.DesignerActionList '---indicate whether to display ' today's date--- Private _DisplayTodayDate As Boolean '---reference to the user control Private myDateControl As DateControl '---reference to DesignerActionUIService Private designerActionSvc As _ DesignerActionUIService = Nothing End Class
The DateControlActionList class will create the list of designer action items available in the smart tag. Next, create the class constructor, which accepts a single argument?a reference to the user control:
Public Sub New(ByVal component As _ IComponent) MyBase.New(component) Me.myDateControl = component Me.designerActionSvc = _ CType(GetService(GetType( _ DesignerActionUIService)), _ DesignerActionUIService) End Sub
Add a function named GetPropertyByName() to retrieve the various properties of the user control:
Private Function GetPropertyByName( _ ByVal propName As String) As _ PropertyDescriptor Dim prop As PropertyDescriptor prop = TypeDescriptor. _ GetProperties( _ myDateControl)(propName) If prop Is Nothing Then Throw New ArgumentException( _ "Invalid property.", propName) Else Return prop End If End Function
Next, you need to declare the various action list items in the smart tag as properties (see Listing 4). To display all these properties as items in the smart tag, you will need to add them to the DesignerActionItemCollection object. You accomplish this by using the GetSortedActionItems() function, which you need to override in this class. The GetSortedActionItems() function populates the smart tag with the list of designer action items and arranges them in the order you specify within this function (see Listing 5).
|Figure 9. Smart Tag Items: The figure shows the association between smart tag items and the various parameters of the DesignerActionPropertyItem class.|
Note that setting a value for the DisplayTodayDate property causes the smart tag to refresh; Specifically, you hide the Date designer action item if the DisplayTodayDate property is set to True and display it when the property is False.
Figure 9 shows the various items in the smart tag and the relationships with their corresponding properties.
Finally, to associate the user control with the control designer, you need to add the Designer attribute to the DateControl class:
_ Public Class DateControl
Viewing the Smart Tag
To view the updated control, you need to rebuild the CustomDateControl project. Right-click the project item in Solution Explorer and select Rebuild. Now, if you examine the DateControl in Form1 (from the SmartTag project), you can click the arrow located on the top-right corner to display the smart tag (see Figure 10).
Try using the smart tag to set the various control properties. Observe that if you check the "Display today's date" checkbox, the "Set the initial date item" disappears (see Figure 11).
This article showed you how to define and use the various classes that associate a smart tag with a user control. As a control developer, consider incorporating smart tags in your controls to add value and simplify their use.