Introduction
This is a relatively simple control that illustrates some of the techniques used in developing custom controls. We will look at the way we hide properties that we don't want exposed through the designer, correctly listing our custom properties, and some other pointers.
Background
Custom controls and design-time support are a little daunting, the MSDN documentation is difficult to wade through and unless we are shown how to do something, it isn't always apparent. I've been developing custom controls for a few years now and I'm still learning the intricacies of custom control development and rich design-time support. Hopefully this article will shed some light on the dark world of control development.
Hiding Properties
Before I really get into the meat of the SignalStrengthMeter
, I want to delve into some control development subjects. The first of which is hiding properties at design time.
When you create a new custom control in Visual Studio, you inherit from the UserControl
class most of the time. You can elect to inherit from just about any control, even things like Buttons, Panels, radio buttons, etc. It's best to pick a control that most closely represents the control that you want to develop. The problem is that whatever control you decide to inherit from, you are bound to have a bunch of properties in the window that don't do anything, or make no sense to what you are developing.
That's where property hiding comes in. Every property has a bunch of design time metadata associated with it, you just need to change the metadata so it doesn't show up in the properties window. The only way (that I've found at least) is to create a new "hiding" property and assign the proper metadata to it, like:
[Browsable(false), DesignerSerializationVisibility
(DesignerSerializationVisibility.Hidden),
EditorBrowsable(EditorBrowsableState.Never)]
public new Image BackgroundImage
{
get { return base.BackgroundImage; }
set { base.BackgroundImage = value; }
}
There are a couple interesting notes, the first is that the property has the keyword "new
" in it before the type. This identifies to the compiler that you intend to "hide" the base type's implementation of that property (go ahead and remove it and try to compile, look at the error). Next are the Browsable
and DesignerSerializationVisibility
. Setting the Browsable
attribute with the initializer false
tells the Visual Studio designer that you don't want to show the property at design time. Setting the DesignerSerializationVisibility
to "Hidden
" tells the designer that you don't want to serialize (save) the settings of this property at design time.
Unfortunately this doesn't hide the property in code, it's still there bright as day. Even setting the access level to private
or protected
won't remove the member from code.
Describing Your Properties
This is one of the biggest problems you see with the homebrew controls you find in places like CodeProject, nobody takes the time to categorize or describe the controls so that you have a rich design time experience. Think of the poor developer that has to use your code!
There are 3 important settings to use when describing your data:
Browsable
(true)
Category
("Category Name")
Description
("Description")
Obviously there are a lot more, but those are the basics. You might also want to look into using the DefaultValue
property, which you can use to identify what the default is (so the value isn't Bolded in the properties window).
Here is an example of applying the three main attributes (and 2 others I'll describe below):
[Browsable(true), EditorBrowsable( EditorBrowsableState.Always),
Bindable( BindableSupport.Yes),
Category("Appearance"), Description("Number of bars in the signal monitor")]
public int NumberOfBars
{
get { return numberOfBars; }
set { numberOfBars = value; this.Invalidate(); }
}
You can see that I applied the Browsable
attribute so that it shows up in the properties window, the Category
attribute to place the property in the Appearance category in the properties window, and Description
so that when the user has the property selected, the description box at the bottom of the properties window shows a friendly description of the property.
I also used two other attributes I won't get into too much detail about, but you can set certain properties of your control to be "Advanced" properties, I always set this to always show, even though I can't find the place to hide advanced properties in Visual Studio. The second one describes the property as "bindable", meaning I'm allowing the application to bind to it for data driven applications (in my case I use them for skinning, but not in this project).
One last note about the Metadata; there is a metadata attribute called DisplayName
, where you can set the name of the property to anything you want, so you can turn "NoSignalThreshold
" into "No Signal Threshold". I would AVOID using this attribute. Why? Well if some developer down the line wants to extend your control, or base a new one off of your control, it makes it very difficult to hide the property if you have to search through Intellisense for it, when the actual property name doesn't match what is in the property window.
Custom Control Regions
I always hate the fact that setting a background as "transparent" really doesn't mean transparent, it just means it'll suck the background color off of its parent control (yes, a form is a control), and use that. I've written into this control the ability to set the control as truly transparent, by using a custom control region. You can take a look through the CalculateRegion()
function to see how I've implemented it, but basically you just need to draw a GraphicsPath
with the areas that you want, and leave everything else out.
This may have some performance implications since Windows now has to do some fancy clipping with complicated regions, but I think the visual payoff is worth it. The only problem is that if you set this control over another control (like a button), the user can click in between the regions and affect the control underneath it.
Using the Code
I'm not going to get into the implementation details heavily because there really isn't anything revolutionary about the code. It just draws a number of bars, calculating if they are filled or not, what the fill style is (Solid or Gradient), and what the color should be depending on the "quality" of the signal. Using the control is self explanatory, just drop the control on your form, set the MinValue
and MaxValue
properties, then set the value property.
You can set the orientation to be from right to left, left to right, top to bottom, and bottom to top. One thing I neglected to do was to make them top, bottom, left, or right justified, this is a possible future enhancement.
Points of Interest
UI development is a lot more art than skill, unfortunately I'm not very artful, there could be a lot of improvements to the look and feel of this control, go nuts!
History
- Version 1.0 - Initial release
- Version 1.1 - Bug fix:
BottomToTop
layout style didn't calculate region if set to Transparent