Aligning Windows Forms custom controls to text baselines using C#
One of the nice things about the Visual Studio WinForms
designers are the guidelines it draws onto design surfaces,
aiding you in perfectly positioning your controls. These
guidelines are known internally as snap lines, and by
default each visual component inheriting from Control
gets
four of these, representing the values of the control's Margin
property.
A problem arises when you have multiple controls that have different heights, and contain a display string - in this case aligning along one edge isn't going to work and will probably look pretty ugly. Instead, you more than likely want to align the different controls so that the text appears on the same line.
Fortunately for us developers, the designers do include this
functionality - just not by default. After all, while all
controls have a Text
property, not all of them use it, and how
could the default designers know where your owner-draw control
is going to paint text?
The image above shows a Label
, ComboBox
and Button
control
all aligned along the text baseline (the magenta line). We can
achieve the same thing by creating a custom designer.
Creating the designer
The first thing therefore is to create a new class and inherit
from System.Windows.Forms.Design.ControlDesigner
. You may also
need to add a reference to System.Design
to your project
(which rules out Client Profile targets).
.NET conventions generally recommend that you put these types of classes in a sub-namespace called Design.
So, assuming I had a control named BetterTextBox
, then the
associated designer would probably look similar to the
following.
using System.Windows.Forms.Design;
namespace DesignerSnapLinesDemo.Design
{
public class BetterTextBoxDesigner : ControlDesigner
{
}
}
If you use a tool such as Resharper to fill in namespaces, note that by default it will try and use
System.Web.UI.Design.ControlDesigner
which unsurprisingly won't work for WinForms controls.
Adding a snap line
To add or remove snap lines, we override the SnapLines
property and manipulate the list it returns. There are only a
few snap lines available, the one we want to add is Baseline
For the baseline, you'll need to calculate where the control will draw the text, taking into consideration padding, borders, text alignments and of course the font. My previous article retrieving font and text metrics using C# describes how to do this.
public override IList SnapLines
{
get
{
IList snapLines;
int textBaseline;
SnapLine snapLine;
snapLines = base.SnapLines;
textBaseline = this.GetTextBaseline(); // Font ascent
// TODO: Increase textBaseline by anything else that affects where your text is rendered, such as
// * The value of the Padding.Top property
// * If your control has a BorderStyle
// * If you reposition the text vertically for centering etc
snapLine = new SnapLine(SnapLineType.Baseline, textBaseline, SnapLinePriority.Medium);
snapLines.Add(snapLine);
return snapLines;
}
}
Note: Resharper seems to think the
SnapLines
property can return a null object. At least for the base WinFormsControlDesigner
, this is not true and it will always return a list containing every possible snapline except forBaseLine
Linking the designer to your control
You can link your custom control to your designer by decorating
your class with the System.ComponentModel.DesignerAttribute
.
If your designer type is in the same assembly as the control (or
is referenced), then you can call it with the direct type as
with the following example.
[Designer(typeof(BetterTextBoxDesigner))]
public class BetterTextBox : Control
{
}
However, if the designer isn't directly available to your
control, all is not lost - the DesignerAttribute
can also take
a string value that contains the assembly qualified designer
type name. Visual Studio will then figure out how to load the
type if it can.
[Designer("DesignerSnapLinesDemo.Design.BetterTextBoxDesigner, DesignerSnapLinesDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
public class BetterTextBox : Control
{
}
After rebuilding the project, you'll find that your control now uses your designer rather than the default.
I seem to recall that when using older versions of Visual Studio once the IDE had loaded my custom designer contained in a source code project it seemed to cache it. This meant that if I then changed the designer code and recompiled, it wouldn't be picked up unless I restarted Visual Studio. I haven't noticed that happening in VS2015, so either I'm imagining the whole thing, or it was fixed. Regardless, if you get odd behaviour in older versions of VS, a restart of the IDE might be just what you need.
The following image shows a zoomed version of the
BetterTextbox
(which is just a garishly painted demo control
and so is several lies for the price of one) showing all three
controls are perfectly aligned to the magenta BaseLine
guideline.
Bonus Chatter: Locking down how the control is sized
The default ControlDesigner
allows controls to be resized
along any edge at will. If your control automatically sets its
height or width to fit its contents, then this behaviour can be
undesirable. By overriding the SelectionRules
property,
you can define how the control can be processed. The following
code snippet shows an example which prevents the control from
being resized vertically, useful for single-line text box style
controls.
public override SelectionRules SelectionRules
{
get { return SelectionRules.Visible | SelectionRules.Moveable | SelectionRules.LeftSizeable | SelectionRules.RightSizeable; }
}
Update History
- 2016-07-21 - First published
- 2020-11-21 - Updated formatting
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
DesignerSnapLinesDemo.zip
|
Sample project for the Aligning Windows Forms custom controls to text baselines using C# blog post. |
19/04/2019 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
Cris M.
#
Thanks for uploading the sample files!