Our Custom Panel Control
Introduction
This article discusses how to make a custom panel control like in Visual Studio 2008 for
a Windows Forms Application using the .NET Framework.
A sample application is available within the zip files.
Background
To create controls that provide entirely new functionality or combine existing user interface elements in a unique way, you can derive from
an existing control to override and enhance its functionality. In addition to that, owner-drawn controls use GDI+ drawing routines to
generate their interfaces from scratch. Because of this, they tend to inherit from a more basic class that's farther down the control
hierarchy, like System.Windows.Forms.Control
. Owner-drawn controls require the most work, and provide the most flexibility.
From .NET’s point of view, there’s no difference between deriving from the Control class and a higher-level control class like Panel.
If you derive directly from the Control
class, you are responsible for painting your control by hand in the OnPaint()
method
but if you derive from a class like Panel
, you inherit a fully functioning control, and need to add or customize
only the features that interest you. We will use the second one.
The Panel2008
control class provides a basic implementation of its base control class but we need to create specific
classes, properties, a few paint methods and a title string changed event for our control.
A few attributes can be applied to your custom control class declaration, rather than a specific property. These include two
attributes that set the default event and property (as described in the below table).
Basic Control Class Attributes
Attribute | Description |
DefaultEvent | When the application programmer double-clicks your control, Visual
Studio automatically adds an event handler for the default event. |
DefaultProperty | The DefaultProperty is the property that is highlighted in the Properties window by default the first time the control is selected. |
[Designer(typeof(Panel2008Designer))]
[DefaultEvent("TitleChanged"), DefaultProperty("Title")]
public partial class Panel2008 : Panel
{
}
Double Buffering
You can reduce flickering by preventing a control from drawing its background. If you do, your code must begin by painting a background
using one of the fill methods from the Graphics class. Otherwise, the original content remains underneath the new content.
To disable background painting, all you need to do is override the OnPaintBackground()
method for your control and do nothing.
In other words, you won’t call the base paint method.
protected override void OnPaintBackground(
System.Windows.Forms.PaintEventArgs pevent)
{
}
If you are filling a control with a background color, you should always follow this step, as it can improve performance
dramatically. Otherwise, your control window will flicker noticeably between the default background color and the color you paint every time
you redraw the control. In addition to that, instead of overriding the OnPaintBackground()
method, you can use the SetStyle()
method
and set the AllPaintingInWmPaint
style to true
. This tells the control window to ignore messages asking it to repaint its background.
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
Disabling the automatic background painting reduces flicker, but the flicker remains. To remove it completely, you can use a technique known as double buffering.
Setting the DoubleBuffered
property to true
is equivalent to setting the AllPaintingInWmPaint
and OptimizedDoubleBuffer
control
styles to true
. If you perform painting in OnPaintBackground()
as well as OnPaint()
, you should set
the OptimizedDoubleBuffer
property to true
but not set the DoubleBuffered
property. In addition, you might want
to set the ResizeRedraw
property to true
so that the control automatically invalidates itself if the size changes.
This is useful if the drawing logic uses calculations that depend on the control’s size. However, use it only if you need it.
We should set the required System.Windows.Forms.ControlStyles
flag to true
in the constructor of the control class.
The following code fragment shows exactly that:
#region Constructor
public Panel2008()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint | ControlStyles.ResizeRedraw |
ControlStyles.ContainerControl, true);
}
#endregion
Control Properties and Event Table
A few properties and a custom event from our panel control.
Property & Event | Description |
| Gets or Sets, the title string of the panel control. |
| You can change the color components of the panel bitmap[RGBA Colorizer for our panel control]. |
| Occurs when the title string on the control is changed. |
IPanelColorizer
I've created a new class to change our panel appearance by using its members. It implements the IPanelColorizer
interface.
public interface IPanelColorizer : IDisposable
{
bool IsColorizerEnabled { get; set; }
bool IsTransparencyEnabled { get; set; }
byte Red { get; set; }
byte Green { get; set; }
byte Blue { get; set; }
byte Alpha { get; set; }
ColorMatrixStyle PaintingStyle { get; set; }
}
Color Transformations
Color Manipulations Table with same parameters - Red: 255 Green: 0 Blue: 0 (Increased by Red) |
Default | at OnlyCorners mode | at AllPanel mode | | | |
|
Paint Method
protected virtual void DrawCornersAndText(Graphics gfx)
{
Rectangle destRect = this.ClientRectangle;
if (destRect.Width < 28 || destRect.Height < 44)
return;
using (Bitmap overlay = new Bitmap(destRect.Width, destRect.Height, PixelFormat.Format32bppRgb))
{
using (Graphics gr = Graphics.FromImage(overlay))
{
using (Brush brush = new SolidBrush(Color.White))
gr.FillRectangle(brush, 0, 0, overlay.Width, overlay.Height);
Bitmap cornerBmp = null;
for (int i = 0; i <= 7; i++)
{
switch (i)
{
case 0:
cornerBmp = Resources.TopLeft;
destRect = new Rectangle(0, 0, cornerBmp.Width, cornerBmp.Height);
gr.DrawImage(cornerBmp, destRect);
break;
case 1:
cornerBmp = Resources.TopMiddle;
destRect = new Rectangle(14, 0, overlay.Width - 28, cornerBmp.Height);
using (Brush brush = new TextureBrush(cornerBmp))
gr.FillRectangle(brush, destRect);
break;
case 2:
cornerBmp = Resources.TopRight;
destRect = new Rectangle(overlay.Width - cornerBmp.Width, 0, cornerBmp.Width,
cornerBmp.Height);
gr.DrawImage(cornerBmp, destRect);
break;
case 3:
cornerBmp = Resources.MiddleLeft;
destRect = new Rectangle(0, 30, cornerBmp.Width, overlay.Height - 44);
using (ImageAttributes attributes = new ImageAttributes())
{
attributes.SetWrapMode(WrapMode.TileFlipY);
gr.DrawImage(cornerBmp, destRect, 0, 0, cornerBmp.Width, cornerBmp.Height,
GraphicsUnit.Pixel,
attributes);
}
break;
case 4:
cornerBmp = Resources.MiddleRight;
destRect = new Rectangle(overlay.Width - cornerBmp.Width, 30, cornerBmp.Width,
overlay.Height - 44);
using (ImageAttributes attributes = new ImageAttributes())
{
attributes.SetWrapMode(WrapMode.TileFlipY);
gr.DrawImage(cornerBmp, destRect, 0, 0, cornerBmp.Width, cornerBmp.Height,
GraphicsUnit.Pixel,
attributes);
}
break;
case 5:
cornerBmp = Resources.BottomLeft;
destRect = new Rectangle(0, overlay.Height - cornerBmp.Height, cornerBmp.Width,
cornerBmp.Height);
gr.DrawImage(cornerBmp, destRect);
break;
case 6:
cornerBmp = Resources.BottomMiddle;
destRect = new Rectangle(14, overlay.Height - cornerBmp.Height, overlay.Width - 28,
cornerBmp.Height);
using (ImageAttributes attributes = new ImageAttributes())
{
attributes.SetWrapMode(WrapMode.TileFlipX);
gr.DrawImage(cornerBmp, destRect, 0, 0, cornerBmp.Width, cornerBmp.Height,
GraphicsUnit.Pixel,
attributes);
}
break;
case 7:
cornerBmp = Resources.BottomRight;
destRect = new Rectangle(overlay.Width - cornerBmp.Width,
overlay.Height - cornerBmp.Height,
cornerBmp.Width, cornerBmp.Height);
gr.DrawImage(cornerBmp, destRect);
break;
}
}
}
destRect = this.ClientRectangle;
float[][] jaggedMatrix;
if (panelColorizer.PaintingStyle == ColorMatrixStyle.AllPanel)
{
jaggedMatrix = new float[][]
{
new float[]{ panelColorizer.IsColorizerEnabled ? panelColorizer.Red / 255f : 1.0f ,
0.0f , 0.0f , 0.0f , 0.0f },
new float[]{ 0.0f , panelColorizer.IsColorizerEnabled ? panelColorizer.Green / 255f : 1.0f ,
0.0f , 0.0f , 0.0f },
new float[]{ 0.0f , 0.0f ,
panelColorizer.IsColorizerEnabled ? panelColorizer.Blue / 255f : 1.0f , 0.0f , 0.0f },
new float[]{ 0.0f , 0.0f , 0.0f ,
panelColorizer.IsTransparencyEnabled ? panelColorizer.Alpha / 255f : 1.0f , 0.0f },
new float[]{ panelColorizer.IsColorizerEnabled ? 0.2f : 0.0f ,
panelColorizer.IsColorizerEnabled ? 0.2f : 0.0f ,
panelColorizer.IsColorizerEnabled ? 0.2f : 0.0f , 0.0f , 1.0f }
};
}
else
{
jaggedMatrix = new float[][]
{
new float[]{ 1.0f , 0.0f , 0.0f , 0.0f , 0.0f },
new float[]{ 0.0f , 1.0f , 0.0f , 0.0f , 0.0f },
new float[]{ 0.0f , 0.0f , 1.0f , 0.0f , 0.0f },
new float[]{ 0.0f , 0.0f , 0.0f ,
panelColorizer.IsTransparencyEnabled ? panelColorizer.Alpha / 255f : 1.0f , 0.0f },
new float[]{ panelColorizer.IsColorizerEnabled ? panelColorizer.Red / 255f : 0.0f ,
panelColorizer.IsColorizerEnabled ? panelColorizer.Green / 255f : 0.0f ,
panelColorizer.IsColorizerEnabled ? panelColorizer.Blue / 255f : 0.0f , 0.0f , 1.0f }
};
}
ColorMatrix colorMatrix = new ColorMatrix(jaggedMatrix);
using (ImageAttributes attributes = new ImageAttributes())
{
ColorMap[] map = new ColorMap[1];
map[0] = new ColorMap();
map[0].OldColor = Color.Magenta;
map[0].NewColor = Color.Transparent;
attributes.SetRemapTable(map);
attributes.SetColorMatrix(
colorMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Default);
gfx.DrawImage(overlay, destRect, 0, 0, overlay.Width, overlay.Height, GraphicsUnit.Pixel,
attributes);
}
}
destRect.Inflate(-14, -4);
if (destRect.Width > 0)
{
using (Font captionFont = new System.Drawing.Font(this.Font.Name, 11, FontStyle.Bold, GraphicsUnit.Pixel))
{
using (Brush captionTextBrush = new SolidBrush(this.ForeColor))
using (StringFormat captionTextFormat = new StringFormat(StringFormatFlags.NoWrap))
{
captionTextFormat.Alignment = StringAlignment.Near;
captionTextFormat.Trimming = StringTrimming.EllipsisCharacter;
captionTextFormat.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Hide;
gfx.DrawString(title, captionFont,
captionTextBrush, destRect, captionTextFormat);
}
}
}
}
The ExpandableObjectConverter
Unfortunately, there’s no way to set a custom object subproperties at design time.To solve this problem, you need to create a custom type
converter, which is a specialized class that can convert a custom object to a string, and then convert the string back to a live custom object. If you don't use a type converter and looks at the Properties window, you’ll see a piece of static text that shows the result of calling ToString()
on the custom object.
A number of object properties are supported by Windows Forms controls. The best example is Font, which refers to a full-fledged Font object with properties like Bold, Italic, Name and so on. When you set the Font property in the Properties window, you don’t need to type all this information in a single, correctly formatted string. Instead, you can expand the Font
property by clicking the plus (+) box and editing all of the Font subproperties individually.
You can enable the same type of editing with your own custom object types. You actually have two choices—you can use the ExpandableObjectConverter directly, or you can create a custom type converter that derives from the ExpandableObjectConverter. If you use this approach, you’ll have the benefit of the string representation and the ability to expand the property to see subproperties.
The first step is to create a custom class that derives from the base class System.ComponentModel.ExpandableObjectConverter
, as shown here:
class ColorizerPanelConverter : ExpandableObjectConverter
{
}
After than, we override the required methods for our use.
TypeConverter Overridable Methods
Method | Description |
CanConvertFrom() | This method examines a data type, and returns true if the type converter can make the conversion from this data type to the custom data type. |
ConvertFrom() | This method performs the conversion from the supplied data type to the custom data type. |
CanConvertTo() | This method examines a data type, and returns true if the type converter can make the conversion from the custom object to this data type. |
ConvertTo() | This method performs the conversion from the custom data type to the requested data type. |
#region Override Methods
public override bool CanConvertTo(
ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
return ToString(value);
else
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return FromString(value);
else
return base.ConvertFrom(context, culture, value);
}
#endregion
Attaching a Type Converter
There are two ways to attach a type converter. The approach you
should use in most cases is to link the custom type to the type
converter by adding the TypeConverter
attribute to the class declaration.
[TypeConverter(typeof(ColorizerPanelConverter))]
public class ColorizerPanel : IPanelColorizer
{ ... }
And another option is to apply the TypeConverter
attribute to the property in your custom control.
[Description("You can change the color components of the
panel bitmap[RGBA Colorizer for our panel control]")]
[TypeConverter(typeof(ColorizerPanelConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Browsable(true)]
[Category("Appearance")]
public ColorizerPanel PanelColorizer
{
get { return panelColorizer; }
set
{
try
{
if (!value.Equals(panelColorizer))
{
panelColorizer.PanelColorizerChanged -= CONTROL_INVALIDATE_UPDATE;
panelColorizer = value;
panelColorizer.PanelColorizerChanged += new EventHandler(CONTROL_INVALIDATE_UPDATE);
Invalidate();
Update();
}
}
catch (NullReferenceException)
{
MessageBox.Show("Value cannot be null!, please enter a valid value.");
}
}
}
Editing properties of the ColorizerPanel class.
The UITypeEditor
The base UITypeEditor
class is found in the System.Drawing.Design
namespace. You can inherit from this class to create your custom type editors.
You associate a property with a type editor using the Editor
attribute. As with type converters, you can apply the Editor attribute to a class declaration or a property declaration.
To create a custom type editor, you must create a new class that derives from
System.Drawing.Design.UITypeEditor
. You can then override the four methods in this class, shown in the below table.
UITypeEditor Overridable Methods:
Method | Description |
EditValue() | Invoked when the property is edited. Generally, this is where you would create a special dialog box for property editing. |
GetEditStyle() | Specifies whether the type editor is a Drop-Down (provides a list of specially drawn choices), Modal (provides a dialog box for property selection), or None (no editing supported). |
GetPaintValueSupported() | Use this to return true if you are providing a PaintValue() implementation. |
PaintValue() | Invoked to paint a graphical thumbnail that represents the value in the Properties window. |
A Drop-Down Type Editor
A drop-down type editor shows a control in a drop-down box underneath
the property. The drop-down box is sized to fit the initial size of the
control you supply, but it will be resized if it can’t fit due to
screen size or window positioning. The best way to prepare the content
for the drop-down box is to create a user control. The type editor is
then responsible for showing that user control in the drop-down box.
DropDownPanel
The DropDownPanel
is a user control. It makes sense to keep it out of the toolbox. You can accomplish this by adding the ToolboxItem
attribute to the class declaration, and marking it false
:
[ToolboxItem(false)]
partial class DropDownPanel : UserControl
{
}
The real trick in this example is that the user control you create
for editing the property needs a way to receive information from the
custom control object. To make this easier, you should add a constructor
to your editing control that accepts all the information it needs.
Here’s the constructor code and the details for storing the constructor-supplied information:
#region Constructor
public DropDownPanel()
{
InitializeComponent();
}
public DropDownPanel(object value, IWindowsFormsEditorService editorService)
: this()
{
this.editorService = editorService;
if (value is ColorizerPanel)
{
this.colorizer = value as ColorizerPanel;
}
}
#endregion
Each track bar value can be changed by the user in the drop-down
control. So we need to create an event handler for each trackbar control.
private void TRACKBAR_VALUE_CHANGED(object sender, EventArgs e)
{
if (sender is TrackBar)
{
string result = null;
TrackBar ctrl = (TrackBar)sender;
if (ctrl.Value == 0)
result = "Min";
else if (ctrl.Value == 255)
result = "Max";
switch (ctrl.Name)
{
case "redTrackBar":
label1.Text = String.Format("Red: {0}",
result ?? ctrl.Value.ToString());
break;
case "greenTrackBar":
label2.Text = String.Format("Green: {0}",
result ?? ctrl.Value.ToString());
break;
case "blueTrackBar":
label3.Text = String.Format("Blue: {0}",
result ?? ctrl.Value.ToString());
break;
default:
label4.Text = String.Format("Alpha: {0}",
result ?? ctrl.Value.ToString());
break;
}
}
}
The next step is to develop the type editor that uses this user control. Here’s the class declaration:
class ColorizerPanelEditor : UITypeEditor
{
}
After than, we need to connect this type editor to the ColorizerPanel
class using the Editor
attribute. The following code snippet shows that how you can add this
attribute to your appropriate class declaration. As mentioned above, you
can also attach this type editor to the appropriate property using the same attribute.
[Editor(typeof(ColorizerPanelEditor), typeof(UITypeEditor))]
public class ColorizerPanel : IPanelColorizer
{ ... }
All you need to do now is fill in the type editor code. First, we choose the drop-down style and turn down thumbnails for our UITypeEditor. And finally, in the EditValue()
method you get the editor service, create an instance of the DropDownPanel
control, and add it to the Properties window using the IWindowsFormsEditorService.DropDownControl()
method, as shown here:
#region Override Methods
public override object EditValue
(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
IWindowsFormsEditorService editorService =
provider.GetService(typeof(IWindowsFormsEditorService))
as IWindowsFormsEditorService;
if (editorService != null)
{
using (DropDownPanel dropDownPanel =
new DropDownPanel(value, editorService))
{
editorService.DropDownControl(dropDownPanel);
value = dropDownPanel.Colorizer;
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return false;
}
#endregion
Shows the DropDownPanel control in the Properties window.
Panel2008Designer
Control designers allow you to manage the design-time behavior and
the design-time interface (properties and events) exposed by your
control. Although control designers are quite complex pieces of the
Windows Forms infrastructure, it’s not difficult to customize an
existing control designer to add new features.
You can derive a control designer to use with your custom controls. Why would you create your own designer?
- To add design-time conveniences, like context menu options and smart tags.
- To remove inappropriate events or properties from view (or add design-time-only events, properties and to create dynamic properties).
- To tailor the design-time appearance of the control so that it differs from the runtime appearance (for example, adding a border around an empty panel).
- To add support for controls that contain other controls (like the toolbar) or controls with special design-time needs (like menus).
At design time, the designer infrastructure attaches a designer to
each component as it is sited on a form. (If more than one instance of
the same component is added to a form, Visual Studio
will reuse the same designer for all instances.) Once this connection is
established, the control designer has the ability to take part in the
interaction between the developer and the control.
To create a new control designer that supports nested controls, begin by deriving a class from ParentControlDesigner
. The following code snippet shows how you can create a control designer for your controls.
public class Panel2008Designer : ParentControlDesigner
{
}
You can then add functionality to your control designer by overriding
the built-in methods. When you’re finished, you need to link the custom
control designer to the appropriate control. To do this, you apply the Designer
attribute to the control declaration and specify the appropriate designer type. Here’s an example that links the Panel2008Designer
to the Panel2008
control:
[Designer(typeof(Panel2008Designer))]
public partial class Panel2008 : Panel
{ ... }
Designers provide six methods from the IDesignerFilter
interface that you can override to filter properties, events, and attributes. These methods are listed in the below table.
IDesignerFilter Methods
Method | Description |
PostFilterAttributes | Override this method to remove unused or inappropriate attributes. |
PostFilterEvents | Override this method to remove unused or inappropriate events. |
PostFilterProperties | Override this method to remove unused or inappropriate properties. |
PreFilterAttributes | Override this method to add attributes. |
PreFilterEvents | Override this method to add events. |
PreFilterProperties | Override this method to add properties. |
Technically, the filtering methods allow you to modify a System.ComponentModel.TypeDescriptor
object
that stores the property, attribute, and event information for your
custom control. Visual Studio uses the information from this TypeDescriptor to determine what it makes available in the design-time environment.
Here’s an example that removes the inappropriate property specified in the following code dump.
protected override void PostFilterProperties(
System.Collections.IDictionary properties)
{
properties.Remove("BorderStyle");
base.PostFilterProperties(properties);
}
Important Note: As a general rule, always call the base method first in the PreFilterXxx()
methods and last in the PostFilterXxx()
methods. This way, all designer classes are given the proper opportunity to apply their changes. The ControlDesigner and ComponentDesigner use these methods to add properties like Visible, Enabled, Name, and Locked.
Smart Tags
Smart tags are the
pop-up windows that appear next to a control when you click the tiny
arrow in the corner.
Smart tags are similar to menus in that they have a list of items. However, these items can be commands (which are rendered like hyperlinks), or other controls like check boxes, drop-down lists and more. They can also include static descriptive text. In this way, a smart tag can act like a mini Properties window.
The following picture shows an example of the custom smart tag. It allows the developer to set a combination of Panel2008
properties.
A sample Smart Tag window
To create this smart tag, you need the following ingredients:
- A collection of DesignerActionItem objects: Each
DesignerActionItem
represents a single item in the smart tag. - An action list class: This class has two roles—it configures the collection of
DesignerActionItem
instances
for the smart tag and, when a command or change is made, it performs
the corresponding operation on the linked control. - A control designer: This hooks your action list up to the control so the smart tag appears at design time.
The Action List
Creating a smart tag is conceptually similar to adding designer verbs, you
override a method in your control designer and return a collection of
commands you want to support. (This list of commands is called an action list
.)
However, smart tags allow many more options than designer verbs, and
so the associated code is likely to be more complex. To keep it all
under control, it’s a good idea to separate your code by creating a
custom class that encapsulates your action list. This custom class
should derive from DesignerActionList
(in the System.ComponentModel.Design
namespace).
Here’s an example that creates an action list that’s intended for use with the Panel2008
control:
public class Panel2008ActionList : DesignerActionList
{
}
You should add a single constructor to the action list that requires
the matching control type. You can then store the reference to the
control in a member variable. This isn’t required, because the base ActionList class does provide a Component
property that provides access to your control. However, by using this
approach, you gain the convenience of strongly typed access to your
control.
#region Constructor
public Panel2008ActionList(Panel2008 control)
: base(control)
{
linkedControl = control;
designerService =
(DesignerActionUIService)GetService(typeof(DesignerActionUIService));
this.AutoShow = true;
}
#endregion
To create your smart tag, you need to build a DesignerActionItemCollection
that combines your group of DesignerActionItem
objects. Order is important in this collection, because Visual Studio will add the
DesignerActionItem
objects to the smart tag from top to bottom in the order they appear.
To build your action list, you override the DesignerActionList.GetSortedActionItems()
method, create the
DesignerActionItemCollection
, add each DesignerActionItem
to it, and then return the collection. Depending on the complexity of your smart tag, this may take several steps.
The first step is to create the headers that divide the smart tag
into separate regions. You can then add other items into these
categories, as shown here:
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection items = new DesignerActionItemCollection();
try
{
items.Add(new DesignerActionHeaderItem("Appearance"));
items.Add(new DesignerActionPropertyItem("Title", "Title", "Appearance",
"Sets, the title string on the control."));
items.Add(new DesignerActionMethodItem(this,
"IsColorizerEnabled", "Is Colorizer Enabled: " +
(linkedControl.PanelColorizer.IsColorizerEnabled ? "ON" : "OFF"), "Appearance",
"Determines whether the colorizer effect is enable or not for the panel bitmap.",
false));
items.Add(new DesignerActionMethodItem(this,
"IsTransparencyEnabled", "Is Transparency Enabled: " +
(linkedControl.PanelColorizer.IsTransparencyEnabled ? "ON" : "OFF"), "Appearance",
"Determines whether the transparency effect is visible or not for the panel bitmap.",
false));
items.Add(new DesignerActionHeaderItem("Information"));
items.Add(new DesignerActionTextItem("X: " + linkedControl.Location.X + ", " +
"Y: " + linkedControl.Location.Y, "Information"));
items.Add(new DesignerActionTextItem("Width: " + linkedControl.Size.Width + ", " +
"Height: " + linkedControl.Size.Height, "Information"));
}
catch (Exception ex)
{
MessageBox.Show("Exception while generating the action list panel for this Panel2008, "
+ ex.Message);
}
return items;
}
You still need to connect it to your control. To add this action-list to your control, you need to override the ActionLists
property in your custom designer, create a new DesignerActionListCollection
, and add the appropriate DesignerActionList
object entries. Your control designer handles the action item event,
generally by updating the associated control. Notice that the action
list isn’t created each time ActionList
is called instead, it’s cached it as private member variable to optimize performance.
public override DesignerActionListCollection ActionLists
{
get
{
if (actionLists == null)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(new Panel2008ActionList((Panel2008)Control));
}
return actionLists;
}
}
History
- June 19, 2012 - First release.