Introduction
Well here it is, a seemlessly useless control. There is nothing for the user of this control, but for the developer this is a control that is rich in design-time features. This article will go over many features of design-time developing with Visual Studio .NET 2005. The topics that will be covered include smart tags, menu items, adornments, and design-time interaction. To show you these various features, I have designed a 'target control', this is a control that will, as you may have guessed, create a target on a form. This target will be a multi-colored group of circles centered around a point at the center of the control. Through the design-time features discussed, you will be able to change the width of a circle by dragging portions of a circle, add circles through right-click menus, and change the colors used in the control through smart tags.
Control Designer
The key concept for giving a user control design-time features is the control designer. There are many designers available for various forms of user controls. Most designers can be found in the System.Windows.Forms.Design
namespace, although some designers can also be found in the System.ComponentModel.Design
namespace. Two common designers are the ParentControlDesigner
and the ControlDesigner
which can be found in the System.Windows.Forms.Design
namespace. For the purpose of this article, we will use the ControlDesigner
since our control will not be parent to other user controls.
The ControlDesigner
gives us the capability of adding a multitude of features to a control during design-time, many of which include:
ActionList
s - Give support for smart tags
Verb
s - Give the capability for right-click menus
Adornment
s - Additional method that allows us to add painting to our control
WndProc
- Allows you to encapsulate all events that take place during a user controls' design time events
- Other basic properties:
EnableDragRect
- Allows you to disable the drag rectangle that is by default painted on a user-control
ParticipatesWithSnapLines
- Allows you to disable the use of SnapLines which is a new feature to the Visual Studio .NET 2005 IDE
SelectionRules
- Allows you to set how your control can interact with the form designer--whether you can move a control, and how the control can be selected
- Other features - There are a few other less important features that may not be covered by this article
Smart Tags
Smart tags are fairly simple to incorporate into a user control's design time features. As stated previously, ActionList
s give us the ability to use smart tags, so that is where I will begin to explain. An ActionList
is basically a group of methods and properties of a control that you would want to change dynamically during design-time.
To use an ActionList
, you need to create a class inherited from a DesignerActionList
in the System.ComponentModel.Design
namespace. There is only one overridable method that is absolutely required to use smart tags with your control. That method which will be overridden will be the initialization of the DesignerActionList
. With the following code snippet, I am overriding the base classes new
functionality so that I can get a handle of the Target control that this ActionList
will be interacting with, the dtc
(DesignTimeControl
) variable is a local variable of a Target Control.
public Target_ActionsList(IComponent component)
: base(component)
{
this.dtc = component as Target;
this.designerActionUISvc =
GetService(typeof(DesignerActionUIService))
as DesignerActionUIService;
}
After the initialization is complete and we have a handle to the Target control, we can start adding methods and properties that will be seen on the smart tags for the Target control. Adding methods and properties to a smart tag is easy, but adding methods and properties that will affect the actual code is a bit more difficult. The following code snippet has a method called GetPropertyByName
this will give us a handle to the PropertyDescriptor
of a particular property so that we can effectively edit its properties.
private PropertyDescriptor GetPropertyByName(String propName)
{
PropertyDescriptor prop;
prop = TypeDescriptor.GetProperties(dtc)[propName];
if (null == prop)
throw new ArgumentException(
"Matching Target property not found!",
propName);
else
return prop;
}
Once we get the PropertyDescriptor
of a property, we will be able to get and set the value of a property of the Target control. The following example shows two public
properties and one public
method that will be shown on the smart tags for the Target control. Note how the GetPropertyByName
method is used to set the values for the dtc
or Target control.
public Color Color1
{
get { return this.dtc.Color1; }
set { GetPropertyByName("Color1").SetValue(dtc, value); }
}
public Color Color2
{
get { return this.dtc.Color2; }
set { GetPropertyByName("Color2").SetValue(dtc, value); }
}
public void InvertTargetColors()
{
Color tmp = dtc.Color1;
Color1 = dtc.Color2;
Color2 = tmp;
}
To complete the use of smart tags, you must override the ActionLists
property in the ControlDesigner
. You will create a new
instance of the inherited ActionList
and add the list to a DesignerActionListCollection
which in the following example is called actionLists
.
public override DesignerActionListCollection ActionLists
{
get
{
if (null == actionLists)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(
new Target_ActionsList(this.Component));
}
return actionLists;
}
}
Right-Click Menu Items
Verb
s in the ControlDesigner
give us the ability to add any necessary menu items that we see fit in the right-click menu of the control. To begin adding right-click menu items, we must first create a new
instance of the DesignerVerbCollection
to the controls designer. After we have created an instance of the DesignerVerbCollection
, we will add a DesignerVerb
to the collection. In the snippet below, we add two menu items to the right-click menu and add two event handlers that will handle when the two menu items are clicked. When interacting with the Target control, we must make sure that we use the GetPropertiesByName
method to edit the properties for the control, or a change in the properties will not affect the run-time of the control.
private DesignerActionListCollection actionLists;
public Target_Designer()
{
dvc.Add(new DesignerVerb("Invert Colors of Target",
new EventHandler(InvertColors)));
dvc.Add(new DesignerVerb("Add Circle", new EventHandler(AddCircle)));
}
Adornments
Adornments is a fancy term basically meaning that you can add more paint events to the control that is being designed. OnPaintAdornments
is an overridable method in the ControlDesigner
that will allow you to paint anything you want on a control that is only seen during design-time. A few uses for adornments include copyright notices. For this control, I have the text "This is a Target Control" painted on the control when the control is active. The following code example shows how I accomplished this. The text
variable contains the text that is to be displayed on the Target control, you can easily replace the text with a copyright notice for your control.
protected override void OnPaintAdornments(PaintEventArgs e)
{
if (mouseover)
{
string text = "This control is a Target Control";
e.Graphics.FillRectangle(new SolidBrush(Color.LightYellow),
new Rectangle(0, this.Control.Height -
(int)e.Graphics.MeasureString(text, this.Control.Font).Height - 3,
(int)e.Graphics.MeasureString(text, this.Control.Font).Width + 3,
(int)this.Control.Font.Height));
e.Graphics.DrawRectangle(new Pen(Color.Black),
new Rectangle(0, this.Control.Height -
(int)e.Graphics.MeasureString(text, this.Control.Font).Height - 3,
(int)e.Graphics.MeasureString(text, this.Control.Font).Width + 3,
(int)this.Control.Font.Height));
e.Graphics.DrawString(text, this.Control.Font,
new SolidBrush(Color.Black), 0, this.Control.Height -
(int)e.Graphics.MeasureString(text, this.Control.Font).Height - 3);
}
}
WndProc
The WndProc
of the ControlDesigner
works the same way as if the control was working on a regular form, but anything that is included with the ControlDesigner
's WndProc
will not affect the run-time of the control. That is unless you use the GetPropertiesByName
method to set a property for the control. With the target control, I have implemented a sort of complicated way to change the dimensions of each circle on the target during design-time. The result of what I will explain is a square on four corners of each circle, when the left or top square is dragged the circle will decrease in size, when the right and bottom square is dragged the circle will increase in size. To accomplish this I drew squares, in the WndProc
, on four sides of each circle in the Target control. I then made a collection of the possible points that each square can take up and what circle belongs with what points. I then intercept the WM_MOUSEMOVE
event and check the coordinates of the mouse cursor, if the cursor is in the collection of points, I will resize the specified circle.
Conclusion
I do hope this article will help many programmers in creating more manageable user controls that will both be appealing to the user and easy to manage during design time.
History
- 22nd March, 2006: Initial version