devxlogo

Adding Smart Tags to Windows Forms Controls

Adding Smart Tags to Windows Forms Controls

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.

Table 1. Use these values to set the properties of the three ComboBox controls.
ControlPropertyValue
UserControl1Size230,32
cbbField1AutoCompleteModeSuggestAppend
cbbField1AutoCompleteSourceListItems
cbbField1DropDownStyleDropDownList
cbbField2AutoCompleteModeSuggestAppend
cbbField2AutoCompleteSourceListItems
cbbField2DropDownStyleDropDownList
cbbYearAutoCompleteModeSuggestAppend
cbbYearAutoCompleteSourceListItems
cbbYearDropDownStyleDropDownList

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:

   Imports System.ComponentModel

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
You use the ControlDesigner class to extend the design mode behavior of user controls.

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).

?
Figure 4. Placing a DateControl: This is how the DateControl looks when you drag and drop it onto the form.
?
Figure 5. DateControl Properties and Events: These views of the Properties window show the DateControl's properties and events.

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.

To test the control, add a GroupBox control to Form1 and populate it with RadioButton controls as shown in Figure 6. You can see the code for the various RadioButtons in Listing 3.

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.

?
Figure 7. DateControl Views: The figure shows how the RadioButton options affect the display of the DateControl user control.
?
Figure 8. Balloon Error Message: Here's a how the balloon error message looks if users select 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:

You cannot set the properties of a user control directly; instead, you set them using a proxy property (which, in this case, is returned by the GetPropertyByName() function).
   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).

?
Figure 10. Smart Tag in Action: Here's the completed smart tag displaying the various control properties.
?
Figure 11. Setting the Date: Choosing to display today's date hides the "Set the initial date" item.

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.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist