Smart Tags (Tasks) in your UserControl / Custom control
Always wondered how you create the handy little Tasks (often called Smart-Tags) that allow you to change common properties with ease?
Here is an explanation on how to create various types of Smart-Tags, including an example (VS2008).
If you want to add Smart-Tags to your custom control, or your UserControl, you need to create a custom Designer class for that control.
For the purpose of this 'tutorial', I will use a custom control that inherits from the TextBox control, without having any special custom functions. Therefore, it behaves just like any ordinary TextBox. I have called it customTextBox.
vb.net Code:
Public Class customTextBox
Inherits TextBox
End Class
To even be able to use any Designer functions/methods etc, you first need to add a Reference to System.Design (right-click the solution icon in the Solution Explorer and choose Add Reference..., then choose System.Design in the list).
Now it's time to start on our Designer class. Create a new Class and call it customTextBoxDesigner (or something equally descriptive).
Let's first import a few important namespaces, and make our class inherit from the default ControlDesigner:
(You can also inherit from the ParentControlDesigner for example, which allows your control to Parent other controls, like a Panel, Groupbox etc)
vb.net Code:
Imports System.Windows.Forms.Design
Imports System.ComponentModel.Design
Imports System.ComponentModel
Public Class customTextBoxDesigner
Inherits ControlDesigner
End Class
Now, we need one last class, inheriting from the DesignerActionList. I always find it useful to have this class in the same file as the Designer class, so we can make it Friend instead of Public: you will not need this class outside of the Designer class so making it Friend is sufficient.
Because we are inheriting from the DesignerActionList, this class requires a constructor (Sub New), something Visual Studio will tell you if you forget it.
We will need a reference to our custom control in this last class, so it is useful to be able to pass our control as a reference when we create a New instance of this class. Therefore, we need the constructor to accept a component (IComponent) argument, which will hold our custom control.
We then cast this component to our custom control and store it in a private reference variable (txt).
We will also need a DesignerActionUIService reference to be able to Refresh our Smart-Tag panel when that is required.
vb.net Code:
Friend Class TextBoxActionList
Inherits DesignerActionList
Private txt As customTextBox
Private txt As customTextBox
Private designerActionSvc As DesignerActionUIService
Now, let's go back to our Designer class again. The DesignerActionList class we have just created merely creates the different items on our Smart-Tag panel (we have yet to do that), but we still need to tell the Designer class of our custom control that it should actually use these items.
To do that, we need to Override the ActionLists property, and instead of returning the Mybase.ActionLists (which contain the default ActionListItems), we return a custom DesignerActionListCollection containing our custom DesignerActionList.
There's not much to explain about this other than we need to make sure the ActionListCollection is not empty before returning it. If it is, we need to create a New instance of our collection:
vb.net Code:
Imports System.Windows.Forms.Design
Imports System.ComponentModel.Design
Imports System.ComponentModel
Public Class customTextBoxDesigner
Inherits ControlDesigner
Private _actionListCollection As DesignerActionListCollection
Public Overrides ReadOnly Property ActionLists() As System.ComponentModel.Design.DesignerActionListCollection
Get
If _actionListCollection Is Nothing Then
_actionListCollection = New DesignerActionListCollection()
The Designer class is now finished. Note that there is MUCH more you can do with a custom Designer class (see for example my WizardControl and my Custom TabControl in my signature), but if all you require is Smart-Tags, this is it.
Let's go back to our DesignerActionList (which I've called TextBoxActionList class (should be in the same file).
We still need to tell the designer which properties and methods should be in the Smart-Tag panel. This will obviously depend on your requirements, so I have added a few different examples in my attached project. I am only going to describe the process for one property and one method here though, since it is the same every time.
You may have noticed from other Smart-Tags that there are different kinds of properties: there are textboxes you can change, there are checkboxes, comboboxes, Font selectors, etc, etc, etc. The great thing about this is, you don't need to worry about that at all! Visual Studio understands which type of 'control' (on the Smart-Tag panel) is required for each Type of your property. If you create a Boolean property, it will create a Checkbox. If you create a String property, a Textbox will appear. If you create a DockStyle property, a complete 'Dock control' will appear.
The most common property of a TextBox is obviously its Text property, so we need a Property (of type String) that gets and sets the Text property of our customTextBox (txt):
vb.net Code:
Public Property Text() As String
Get
Return txt.Text
End Get
Set(ByVal value As String)
txt.Text = value
End Set
End Property
(As I've said, if you require a Font property for example, just make it of type Font and you will get a button on the Smart-Tag panel that allows you to browse through Font styles.)
Besides properties, you can also put methods (that will appear as a hyperlink) on the Smart-Tag panel. Clicking these will run a piece of code that can do whatever you want (such as change the size of a control, or more commonly Dock/Undock it into the parent container).
The Dock/Undock method hyperlink on a Smart-Tag usually changes its text to "Undock in parent container." when the control is Docked, and to "Dock in parent container." when the control is Undocked. We will get to that later. For now, let's just create the method that will Dock or Undock our control. One thing is important to allow the text to change: we need to refresh the DesignerActionService (designerActionSvc.Refresh) otherwise the text won't change until you close and re-open the Smart-Tag panel.
vb.net Code:
Public Sub OnDock()
If txt.Dock = DockStyle.Fill Then
txt.Dock = DockStyle.None
Else
txt.Dock = DockStyle.Fill
End If
designerActionSvc.Refresh(txt)
End Sub
(Continued in next post....)
Last edited by NickThissen; Jul 29th, 2009 at 03:51 PM.
Re: Smart-Tags (Tasks) in your UserControl / Custom control
Finally, we need to Override the GetSortedActionItems function that returns a collection of DesignerActionItems (the items on the Smart-Tag panel). Instead of returning the default collection, we create a new DesignerActionItemCollection, and add new Headers (New DesignerActionHeaderItem), properties (New DesignerActionPropertyItem and methods (New DesignerActionMethodItem). At the end, we return our new collection.
But first, we need to determine if our Dock/Undock method needs to display either "Dock" or "Undock" in its display title. This is simple enough with a normal If/Else construction. Just save the desired text into a String, which you pass as the Display title to the DesignerActionMethodItem.
The DesignerActionHeaderItems are easy enough: they only require one parameter, the displayName which is simply the name they will display on the Smart-Tag panel. These will be our categories, so take care to type them correctly.
The DesignerActionPropertyItems need a few more parameters:
- memberName: the name of the property you have created above.
- displayName: the name that will be displayed on the panel.
- category: the name (displayName) of the category (Header) you (may) have created. Leave this empty ("") to put it outside of any categories).
- description: the description that will show in a ToolTip if you hover your mouse over this item.
The DesignerActionMethodItems need one more parameter (the first one), namely the actionList property. You should pass the current DesignerActionList (Me) to this property.
Finally, we return this new collection:
vb.net Code:
Public Overrides Function GetSortedActionItems() As System.ComponentModel.Design.DesignerActionItemCollection
items.Add(New DesignerActionPropertyItem("Text", "Text", "Category 1", "Gets or sets the Text of the customTextBox."))
items.Add(New DesignerActionPropertyItem("Font", "Font", "Category 1", "Gets or sets the Font of the customTextBox."))
items.Add(New DesignerActionPropertyItem("BackColor", "BackColor", "Category 1", "Gets or sets the BackColor of the customTextBox."))
items.Add(New DesignerActionPropertyItem("MultiLine", "Multi-line", "Category 1", "Gets or sets the Multi-line property of the customTextBox."))
'Add the methods
items.Add(New DesignerActionMethodItem(Me, "OnClear", "Clear text", "Category 2", "Clears the text in the customTextBox."))
If txt.Multiline Then
items.Add(New DesignerActionMethodItem(Me, "OnMakeSquare", "Make a square", "Category 2", "Changes the width or height of the customTextBox to resemble a square."))
items.Add(New DesignerActionMethodItem(Me, "OnDock", str, "Category 2", "Docks or undocks the customTextBox in the parent container."))
End If
'Return the ActionItemCollection
Return items
End Function
(Note that I have added a few more items in this piece of code, which is straight from my example)
We are almost finished! The last thing we need to do (the thing I always forget ) is to tell our custom control or UserControl to actually use our Designer class! To do this, we need to add a System.ComponentModel.Designer attribute at the top of our class, to which we pass the type of our custom Designer class as a parameter:
If you now Build your solution, drop the customTextBox on your form, and click the little arrow in the top-right corner, the Smart-Tags should pop up!
I have attached an example project (for VS2008 or higher), and in the next post I have posted the entire code for the Designer class (including the DesignerActionList class), so you shouldn't have any trouble incorporating this into your own control!
Still, if you have any questions, suggestions, or otherwise, just leave me a message here and I'll try to see if I can help you.
Enjoy it..!
Code:
Imports System.Windows.Forms.Design
Imports System.ComponentModel.Design
Imports System.ComponentModel
Public Class customTextBoxDesigner
Inherits ControlDesigner
Private _actionListCollection As DesignerActionListCollection
Public Overrides ReadOnly Property ActionLists() As System.ComponentModel.Design.DesignerActionListCollection
Get
If _actionListCollection Is Nothing Then
_actionListCollection = New DesignerActionListCollection()
_actionListCollection.Add(New TextBoxActionList(Me.Control))
End If
Return _actionListCollection
End Get
End Property
End Class
Friend Class TextBoxActionList
Inherits DesignerActionList
Private txt As customTextBox
Private designerActionSvc As DesignerActionUIService
Public Sub New(ByVal component As IComponent)
MyBase.New(component)
txt = DirectCast(component, customTextBox)
designerActionSvc = CType(GetService(GetType(DesignerActionUIService)), DesignerActionUIService)
End Sub
#Region " Properties to display in the Smart-Tag panel "
Public Property Text() As String
Get
Return txt.Text
End Get
Set(ByVal value As String)
txt.Text = value
End Set
End Property
Public Property Font() As Font
Get
Return txt.Font
End Get
Set(ByVal value As Font)
txt.Font = value
End Set
End Property
Public Property BackColor() As Color
Get
Return txt.BackColor
End Get
Set(ByVal value As Color)
txt.BackColor = value
End Set
End Property
Public Property MultiLine() As Boolean
Get
Return txt.Multiline
End Get
Set(ByVal value As Boolean)
txt.Multiline = value
designerActionSvc.Refresh(txt)
End Set
End Property
#End Region
#Region " Methods to display in the Smart-Tag panel "
Public Sub OnClear()
txt.Clear()
End Sub
Public Sub OnMakeSquare()
If txt.Multiline Then
If txt.Width >= txt.Height Then
txt.Height = txt.Width
Else
txt.Width = txt.Height
End If
designerActionSvc.Refresh(txt)
End If
End Sub
Public Sub OnDock()
If txt.Dock = DockStyle.Fill Then
txt.Dock = DockStyle.None
Else
txt.Dock = DockStyle.Fill
End If
designerActionSvc.Refresh(txt)
End Sub
#End Region
Public Overrides Function GetSortedActionItems() As System.ComponentModel.Design.DesignerActionItemCollection
Dim str As String
Dim items As New DesignerActionItemCollection
If txt.Dock = DockStyle.Fill Then
str = "Undock in parent container."
Else
str = "Dock in parent container."
End If
'Add a few Header Items (categories)
items.Add(New DesignerActionHeaderItem("Category 1"))
items.Add(New DesignerActionHeaderItem("Category 2"))
'Add the properties
items.Add(New DesignerActionPropertyItem("Text", "Text", "Category 1", "Gets or sets the Text of the customTextBox."))
items.Add(New DesignerActionPropertyItem("Font", "Font", "Category 1", "Gets or sets the Font of the customTextBox."))
items.Add(New DesignerActionPropertyItem("BackColor", "BackColor", "Category 1", "Gets or sets the BackColor of the customTextBox."))
items.Add(New DesignerActionPropertyItem("MultiLine", "Multi-line", "Category 1", "Gets or sets the Multi-line property of the customTextBox."))
'Add the methods
items.Add(New DesignerActionMethodItem(Me, "OnClear", "Clear text", "Category 2", "Clears the text in the customTextBox."))
If txt.Multiline Then
items.Add(New DesignerActionMethodItem(Me, "OnMakeSquare", "Make a square", "Category 2", "Changes the width or height of the customTextBox to resemble a square."))
items.Add(New DesignerActionMethodItem(Me, "OnDock", str, "Category 2", "Docks or undocks the customTextBox in the parent container."))
End If
'Return the ActionItemCollection
Return items
End Function
End Class