又一个 Vista 风格的 CommandLink
一个与操作系统无关的 CommandLink 控件。
引言
有很多很棒的 Vista 风格控件是用 .NET 编写的。所以这是又一个,一个命令链接按钮。我喜欢从头开始创建自定义控件,而不是依赖操作系统绘图库。因此,我们的 CommandLink 完全用 C# 代码绘制,使其与旧版本的 Windows 兼容。
CommandLink 的目标
当我着手编写这个控件时,我决定我想要一个具有 Vista 风格感觉的 CommandLink,但又不像默认 CommandLinks 那样精确复制。因此,我写了一个简单的目标列表,列出了要包含的元素
- 同一按钮内有两种不同的字体大小,即标题文本和描述文本。
- 左侧有一个图像/图标,与 Vista 的
CommandLink
不同,它可以垂直对齐到顶部、中间或底部。 - 默认情况下是混合的扁平外观,鼠标悬停时是渐变外观。
- 行为像一个按钮。
圆角矩形
在绘制实际控件之前,首先我们需要一个绘制圆角矩形的函数。由于按钮需要勾勒和填充圆角矩形,因此编写一个返回 GraphicsPath
的函数是最简单的
private static GraphicsPath RoundedRect(int width, int height, int radius)
{
RectangleF baseRect = new RectangleF(0, 0, width, height);
float diameter = radius * 2.0f;
SizeF sizeF = new SizeF(diameter, diameter);
RectangleF arc = new RectangleF(baseRect.Location, sizeF);
GraphicsPath path = new GraphicsPath();
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = baseRect.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = baseRect.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = baseRect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
绘制元素
因此,让我们分解一下 CommandLink 的视觉元素。唯一复杂的两种状态是悬停状态和按下状态。
悬停
使按钮弹出部分是一个简单的白色渐变,占按钮高度的三分之四。由于 LinearGradientBrush
的工作方式,有时如果渐变绘制区域比所需区域高 1 像素,渐变就会重新开始,导致控件中间出现一条丑陋的白色线。为了解决这个问题,我们在声明 LinearGradientBrush
后添加以下一行
WrapMode.TileFlipX
接下来是轮廓。它是使用上述函数生成的 3 像素半径的圆角矩形。颜色可以是 SystemColors.ColorDark
,或者如果你喜欢固定颜色,(189, 189, 189)
效果很好。
然后,我们需要一个内部轮廓。这个半径将是 2 像素,并位于坐标 (1, 1)
。颜色是略带透明的白色,alpha 值为 245。
我们将它们按顺序绘制,组合在一起,得到这样的效果
向下
这次背景是实心的,同样,它可以是系统颜色 (ControlLight
),或者 (234, 234, 234)
,如果你更喜欢固定颜色。
轮廓将与之前相同,只是颜色会更深,(167, 167, 167)
。
最后,内部轮廓的颜色也会变成深色(以产生阴影效果)。
最终的按下状态
高亮
用户应该能够分辨 CommandLink 何时被选中,即使是通过 Tab 键完成的。为了高亮显示选中的 CommandLink,我们只绘制一个内轮廓,颜色为 (192, 233, 243)
,这是一种浅蓝色。
前景 - 图像和文本
前景元素对于按钮的任何状态都将是相同的。绘制文本和图像实际上没有什么特别之处。描述文本将始终比标题文本小三号。字体可以更改,但默认字体是 Tahoma。为了使标题和描述文本的总尺寸居中,可以使用
SizeF headerLayout = g.MeasureString(headerText, this.Font);
SizeF descriptLayout = g.MeasureString(descriptionText, descriptFont);
//Merge the two sizes into one big rectangle
Rectangle totalRect = new Rectangle(0, 0,
(int)Math.Max(headerLayout.Width,
descriptLayout.Width),
(int)(headerLayout.Height +
descriptLayout.Height) - 4);
另外,这是控件在禁用时会发生变化的部分。文本只需更改颜色。然而,图像需要转换为灰度(如果尚未完成)。
需要重写的事件
有几个事件需要重写才能使 CommandLink 表现得像我们想要的
OnPaint
- 处理所有绘图方法;根据 CommandLink 的状态,执行相应的绘图例程。OnClick
- 由于用户控件不继承Button
类,如果我们想能够指定DialogResult
,则需要在此时手动处理行为。OnKeyPress
- 如果 CommandLink 被 Tab 键选中并且用户按下 Enter 键,则执行PerformClick
。OnGotFocus/OnLostFocus
- 刷新控件以绘制/移除浅蓝色高亮。OnMouse[…]
- 所有OnMouse
事件都只是更改一个变量以反映 CommandLink 的当前状态,并使控件重绘自身。OnEnabledChanged
- 设置正确的状态并重绘 CommandLink。(注意,不幸的是,此事件在设计时不会被调用,但在运行时工作正常。)
结论和改进
最后,我们得到一个具有按钮基本功能但外观像 Vista 风格 CommandLink 的控件。实现旨在兼容旧版本的 Windows,因此可以进行一些可选的改进。例如,不支持 Vista 控件的渐变淡入淡出,目前图像必须在左侧。