又一个 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 控件的渐变淡入淡出,目前图像必须在左侧。




