Introduction
Sometimes it is necessary to visually separate controls in a dialog.
GroupBox
is one solution, but it is not appropriate if you just
want a horizontal 3D line with a label like in some Options dialogs in Visual
Studio. One possible solution is to create an additional empty label with
BorderStyle
set to Fixed3D
and Height
set
to 2. However, this approach is cumbersome because you must take care of control
placement order. Moreover, if you are going to change the label text (e.g., when
localizing the application) or text alignment, you'll have to resize/reposition
controls. Therefore, I have created this simple label that takes care of such
details.
Background
Details regarding custom control creation has been explained elsewhere (e.g.,
in excellent step-by-step tutorial Divider Panel - A
tutorial on creating a custom Windows Forms control from Start to Toolbox).
Therefore, I will not go through them but only outline some specific points.
LabelWithDivider
control inherits the Label
class,
overriding only its OnPaint
method. In this method, line is drawn
first, and then base class' OnPaint
method is called that takes
care of drawing the text:
protected override void OnPaint(PaintEventArgs e) {
PlaceLine(e.Graphics);
base.OnPaint(e);
}
The order of method calls is actually not important since
PlaceLine
method calculates position and length of the line taking
in account the text length as well as its horizontal and vertical
alignments:
protected void PlaceLine(Graphics g) {
SizeF textSize = g.MeasureString(this.Text, this.Font);
int x0 = 0;
int x1 = this.Width;
switch (GetHorizontalAlignment()) {
case HorizontalAlignment.Left:
x0 = (int)textSize.Width + m_gap;
break;
case HorizontalAlignment.Right:
x1 = this.Width - (int)textSize.Width - m_gap;
break;
case HorizontalAlignment.Center:
x1 = (this.Width - (int)textSize.Width) / 2 - m_gap;
break;
}
int y = (int)textSize.Height / 2;
if (TextAlign == ContentAlignment.MiddleLeft
|| TextAlign == ContentAlignment.MiddleCenter
|| TextAlign == ContentAlignment.MiddleRight)
y = this.Height / 2;
else if (TextAlign == ContentAlignment.BottomLeft
|| TextAlign == ContentAlignment.BottomCenter
|| TextAlign == ContentAlignment.BottomRight)
y = this.Height - (int)(textSize.Height / 2) - 2;
Draw3DLine(g, x0, y, x1, y);
if (TextAlign == ContentAlignment.TopCenter
|| TextAlign == ContentAlignment.MiddleCenter
|| TextAlign == ContentAlignment.BottomCenter) {
x0 = (this.Width + (int)textSize.Width) / 2 + m_gap;
x1 = this.Width;
Draw3DLine(g, x0, y, x1, y);
}
}
protected HorizontalAlignment GetHorizontalAlignment() {
if (TextAlign == ContentAlignment.TopLeft
|| TextAlign == ContentAlignment.MiddleLeft
|| TextAlign == ContentAlignment.BottomLeft) {
if (RightToLeft == RightToLeft.Yes)
return HorizontalAlignment.Right;
else
return HorizontalAlignment.Left;
}
if (TextAlign == ContentAlignment.TopRight
|| TextAlign == ContentAlignment.MiddleRight
|| TextAlign == ContentAlignment.BottomRight) {
if (RightToLeft == RightToLeft.Yes)
return HorizontalAlignment.Left;
else
return HorizontalAlignment.Right;
}
return HorizontalAlignment.Center;
}
protected void Draw3DLine(Graphics g, int x1, int y1, int x2, int y2) {
g.DrawLine(SystemPens.ControlDark, x1, y1, x2, y2);
g.DrawLine(SystemPens.ControlLightLight, x1, y1+1, x2, y2+1);
}
m_gap
in the above code is the optional gap between text and
line, which can be set to a non-zero value by Gap
property:
public int Gap {
get { return m_gap; }
set {
m_gap = value;
Invalidate();
}
}
Note that setter calls Invalidate
to ensure control is redrawn
in the visual designer on value change.
Using the code
Control has been placed into class library (LabelWithDivider.dll) and
all required attributes are set so that control can be added to toolbox and
simply dragged and dropped onto a form. Gap
property is available
in Appearance category. And last but not least: LabelWitDivider
is
placed into System.Windows.Forms
namespace.
History
- November 12, 2004: ver. 1.0
- November 13, 2004: ver. 1.1:
LeftToRight
bugfix.