花哨的 Windows Forms






4.93/5 (126投票s)
为您的应用程序带来全新/酷炫的外观和感觉。
目录
引言
您是否厌倦了标准的 Windows Forms,并希望为您的应用程序添加一些炫酷的界面?那么,请继续阅读本文,您将发现实现起来有多么容易。XCoolForm 是一个库,它允许您绘制花哨的标题栏、边框、标题栏按钮、状态栏等。它附带了一些预制的样式,并且您可以轻松创建自己的样式。它还附带了大量的属性,因此可以满足每个用户的需求。我使用的图标是免费的;我从 http://www.iconarchive.com 下载了它们,并从 http://www.dafont.com 获取了Visitor 字体。
背景
这个项目的想法在一年前就产生了,那时我已经在使用 GDI+ 方面有了足够的经验,所以我开始着手实现它。我的下一个目标是用 WPF 重新实现这个项目,只要它提供了一些非常好的功能,并且大部分工作都可以通过 XAML 完成。
使用代码
要开始使用 XCoolForm 库,您应该遵循这三个简单的步骤
- 将引用添加到 XCoolForm 库。
- 使您的窗体继承自
XCoolForm
而不是Form
。 - 通过添加图标持有器按钮、菜单图标、状态栏项目等来定制您的窗体。
结构
元素
Border
要正确渲染 3D 边框,您需要提供六种颜色(两种用于外边框,四种用于内边框)。然后,绘制例程将遍历颜色数组并构建边框。如果您想要一个简单的、扁平的边框,那么您只需要提供一种颜色。
XCoolForm 还支持圆角和倾斜边框(见下图)。您可以使用 Radius
和 Inclination
属性指定圆角或倾斜度。
标题栏
吸引用户的第一个元素是标题栏。因此,为标题栏提供时尚的功能非常重要。XCoolForm 提供了一整套属性,允许用户配置标题栏的最精细细节。您可以选择六种不同的样式和几种标题栏类型来控制标题栏的形状,设置背景图像、标题等。在下一节中,将解释最重要的标题栏样式和类型。
标题栏样式
XCoolForm 在渲染过程中支持六种标题栏样式。我将很快添加新的样式。
-
高级标题栏样式
-
矩形填充标题栏样式
-
纹理填充标题栏样式
-
线性渐变标题栏样式
-
光泽样式
-
无
标题栏使用渐变混合和光泽渲染。您需要指定从中绘制标题栏的五种颜色。这可以通过将颜色列表分配给 TitleBarMixColors
属性来完成。请注意,如果颜色数量不等于五,将抛出异常。
使用此样式时,标题栏区域被分成两个矩形,使用开始和结束渐变颜色填充。
标题栏使用自定义纹理填充。可以是任何有效的图像文件。
使用自定义开始/结束渐变颜色填充的基本标题栏样式。
标题栏的上部区域填充有平滑的光泽。
设置为此样式时,仅渲染内边框和外边框。
标题栏类型
注意,当 TitleBarType
属性设置为所需的类型时,图标区域和标题栏按钮框将根据所选类型自动调整。
-
矩形
-
圆角
-
Angular
标题栏按钮
标题栏按钮绘制在按钮框内,该按钮框根据按钮的宽度和高度自动调整大小。XCoolForm 支持标题栏按钮悬停时的三种样式。关闭、最大化和最小化按钮的符号使用像素艺术绘制。对于下一个版本,我计划添加对自定义位图的支持,然后可以使用它们作为按钮符号。
-
像素风格
-
无
-
顶部光泽
-
全填充
- 首先,按钮使用纯色填充。
- 接下来,渲染光泽。
- 最后,绘制顶部光泽。
-
MacOS 风格
设置为 Pixeled
样式时,XCoolForm 提供三种不同的填充模式
仅使用高亮颜色高亮显示按钮符号。
按钮悬停时绘制顶部光泽。
此样式涉及三个步骤
标题栏按钮具有 MacOS 的外观和感觉。当按钮悬停时,将绘制符号和底部光泽。
图标持有器
图标持有器是一种快速访问工具栏,您可以在其中放置程序中的快捷方式或常用操作。如图所示,它由一系列图标组成。当鼠标悬停在图标持有器按钮上时,将显示框架,包括图像、描述和标题。
-
标题
-
描述
-
Panel
-
Image
用于标识持有器按钮的标题。
关于持有器按钮的简短描述。
用于按钮标题、描述和图像的容器。您可以使用 FrameAlpha
属性指定透明度。
面板的背景图像。
状态栏
如图所示,状态栏由不同的元素构成。状态栏的背景使用简单的渐变填充,使用 StatusStartColor
和 StatusEndColor
属性。为了给状态栏提供光泽效果,上部区域填充有光泽。然后,渲染状态栏项目并用分隔符绑定。您还可以提供背景图像。最后要评论的元素是尺寸抓手。它通过重叠多个矩形来绘制,以使其具有 3D 外观。
实现
UML 图
类
下表包含 XCoolForm 库中类的简要描述。有些类还有内部类,但我没有在此列出。有关详细信息,请参阅库源代码。
类名 | 描述 |
X3DBorderPrimitive |
提供用于构建 3D 边框的绘制路线。还实现了平面边框功能。 |
XTitleBarButton |
表示标题栏按钮。在此处实现了绘制过程,包括全填充、光泽等。 |
XTitleBar |
它实现了边框绘制、标题栏填充、背景图像绘制、对齐等方法。 |
XTitleBarIconHolder |
它还包含一个 XHolderButton 类,用于在鼠标悬停时正确绘制渐变面板。 |
XStatusBar |
用于绘制项目、背景图像、尺寸抓手、状态栏填充和光泽的绘制方法。 |
XCoolFormHelper |
用于绘制圆角矩形和构建颜色混合的一些实用方法。 |
XAntiAlias |
用于平滑绘图。它实现了 IDisposable 接口。 |
XmlThemeLoader |
用于从 XML 文件加载主题的实用类。 |
代码片段
FillTitleBar
是用于标题栏填充的主要方法。基本上,它使用 LinearGradientBrush
和 PathGradientBrush
类来执行绘制逻辑。首先,我们必须通过调用 BuildTitleBarShape
来确定选择了哪个标题栏类型,它将返回描述形状的 GrapichsPath
对象。
private void FillTitleBar(
Graphics g,
Rectangle rcTitleBar
)
{
GraphicsPath XTitleBarPath = new GraphicsPath();
XTitleBarPath = BuildTitleBarShape(rcTitleBar);
using (XAntiAlias xaa = new XAntiAlias(g))
{
#region Fill titlebar
switch (m_eTitleBarFill)
{
case XTitleBarFill.AdvancedRendering:
using (LinearGradientBrush lgb = new LinearGradientBrush(
rcTitleBar,
m_TitleBarMix[0],
m_TitleBarMix[4],
LinearGradientMode.Vertical))
{
lgb.InterpolationColors = XCoolFormHelper.ColorMix(
m_TitleBarMix,
true
);
g.FillPath(
lgb,
XTitleBarPath
);
}
#region Draw titlebar glow
using (GraphicsPath XGlow = new GraphicsPath())
{
XGlow.AddEllipse(
rcTitleBar.Left,
rcTitleBar.Bottom / 2 + 4,
rcTitleBar.Width,
rcTitleBar.Height
);
using (PathGradientBrush pgb = new PathGradientBrush(XGlow))
{
pgb.CenterColor = Color.White;
pgb.SurroundColors =
new Color[] { Color.FromArgb(0, 229, 121, 13) };
g.SetClip(XTitleBarPath);
g.FillPath(
pgb,
XGlow
);
g.ResetClip();
}
}
#endregion
break;
case XTitleBarFill.Texture:
if (m_TitleBarTexture != null) {
using (TextureBrush tb = new TextureBrush(m_TitleBarTexture))
{
g.FillPath(
tb,
XTitleBarPath
);
}
}
break;
case XTitleBarFill.LinearRendering:
RectangleF rcLinearFill = XTitleBarPath.GetBounds();
g.SetClip(XTitleBarPath);
using (LinearGradientBrush lgbLinearFill = new LinearGradientBrush(
rcLinearFill,
m_clrFillStart,
m_clrFillEnd,
LinearGradientMode.Vertical
))
{
g.FillRectangle(
lgbLinearFill,
rcLinearFill
);
}
g.ResetClip();
break;
case XTitleBarFill.UpperGlow:
RectangleF rcGlow = XTitleBarPath.GetBounds();
g.SetClip(XTitleBarPath);
rcGlow.Height /= 2;
using (LinearGradientBrush lgbUpperGlow = new LinearGradientBrush(
rcGlow,
m_clrUpperFillStart,
m_clrUpperFillEnd,
LinearGradientMode.Vertical
))
{
g.FillRectangle(
lgbUpperGlow,
rcGlow
);
}
g.ResetClip();
break;
case XTitleBarFill.RectangleRendering:
RectangleF rcDownRect = XTitleBarPath.GetBounds();
RectangleF rcUpRect = XTitleBarPath.GetBounds();
g.SetClip(XTitleBarPath);
rcUpRect.Height /= 2;
using (LinearGradientBrush lgbUpperRect = new LinearGradientBrush(
rcUpRect,
m_clrUpperFillStart,
m_clrUpperFillEnd,
LinearGradientMode.Vertical
))
{
lgbUpperRect.WrapMode = WrapMode.TileFlipY;
g.FillRectangle(
lgbUpperRect,
rcUpRect
);
}
rcDownRect.Height = rcDownRect.Height / 2;
rcDownRect.Y = rcUpRect.Bottom;
using (LinearGradientBrush lgbDwnRect = new LinearGradientBrush(
rcDownRect,
m_clrFillStart,
m_clrFillEnd,
LinearGradientMode.Vertical
))
{
g.FillRectangle(
lgbDwnRect,
rcDownRect
);
}
g.ResetClip();
break;
}
#endregion
从 FillTitleBar
方法调用的方法返回使用圆弧和线条构建的 GraphicsPath
对象。
private GraphicsPath BuildTitleBarShape(
Rectangle rc
)
{
GraphicsPath e = new GraphicsPath();
switch (m_eTitleBarType)
{
case XTitleBarType.Rounded:
e.AddArc(
rc.Left,
rc.Top,
rc.Height,
rc.Height,
90,
180);
e.AddLine(
rc.Left + rc.Height / 2,
rc.Top,
rc.Right,
rc.Top
);
e.AddArc(
rc.Right,
rc.Top,
rc.Height,
rc.Height,
-90,
180);
e.AddLine(
rc.Right,
rc.Bottom,
rc.Left + rc.Height / 2,
rc.Bottom);
break;
case XTitleBarType.Angular:
e.AddLine(
rc.Left,
rc.Bottom,
rc.Left + 20,
rc.Top);
e.AddLine(
rc.Left + 20,
rc.Top,
rc.Right,
rc.Top);
e.AddLine(
rc.Right,
rc.Top,
rc.Right - 20,
rc.Bottom
);
e.AddLine(
rc.Right - 20,
rc.Bottom,
rc.Left,
rc.Bottom
);
break;
case XTitleBarType.Rectangular:
e.AddRectangle(rc);
break;
}
return e;
}
RenderHolderButtons
将遍历 XHolderButton
的集合,当按钮悬停时,将调用 BuildHolderButtonFrame
方法来绘制面板。此方法还计算标题和描述文本的位置,并将其绘制出来。
public void RenderHolderButtons(
int x,
int y,
Graphics g
)
{
int lX = x;
Rectangle rcIcon = new Rectangle();
RectangleF rcImage = new RectangleF();
RectangleF rcFrame = new RectangleF();
foreach (XHolderButton xbtn in m_xhBtn)
{
if (xbtn.ButtonImage != null)
{
xbtn.Left = lX;
xbtn.Top = y + 1;
rcIcon = new Rectangle(
lX,
y + 1,
xbtn.ButtonImage.Size.Width,
xbtn.ButtonImage.Size.Height
);
if (xbtn.Hot)
{
using (XAntiAlias xaa = new XAntiAlias(g))
{
using (GraphicsPath XHolderBtnPath =
BuildHolderButtonFrame(rcIcon, 100, 40))
{
using (LinearGradientBrush lgb = new LinearGradientBrush(
XHolderBtnPath.GetBounds(),
Color.FromArgb(xbtn.FrameAlpha, xbtn.FrameStartColor),
Color.FromArgb(xbtn.FrameAlpha, xbtn.FrameEndColor),
LinearGradientMode.Vertical
))
{
g.FillPath(
lgb,
XHolderBtnPath
);
}
rcFrame = XHolderBtnPath.GetBounds();
}
int lFrameImageWidth = 0;
if (xbtn.FrameBackImage != null)
{
// draw frame image:
rcImage = new RectangleF(
rcFrame.Right - xbtn.FrameBackImage.Width,
rcFrame.Bottom - xbtn.FrameBackImage.Height,
xbtn.FrameBackImage.Width,
xbtn.FrameBackImage.Height
);
g.DrawImage(xbtn.FrameBackImage, rcImage);
lFrameImageWidth = xbtn.FrameBackImage.Height;
}
// draw caption / description:
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
g.DrawString(
xbtn.XHolderButtonCaption,
xbtn.XHolderButtonCaptionFont,
new SolidBrush(xbtn.XHolderButtonCaptionColor),
rcFrame.Left + 2,
rcIcon.Bottom + 4
);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Near;
sf.Trimming = StringTrimming.EllipsisCharacter;
sf.FormatFlags = StringFormatFlags.LineLimit;
float fCaptionWidth = g.MeasureString(xbtn.XHolderButtonCaption,
xbtn.XHolderButtonCaptionFont).Height;
RectangleF rcDescription = new RectangleF(
rcFrame.Left + 2,
rcIcon.Bottom + fCaptionWidth,
rcFrame.Width,
rcFrame.Height - lFrameImageWidth);
g.DrawString(
xbtn.XHolderButtonDescription,
xbtn.XHolderButtonDescriptionFont,
new SolidBrush(xbtn.XHolderButtonDescriptionColor),
rcDescription,
sf);
}
}
// draw button:
g.DrawImage(
xbtn.ButtonImage,
rcIcon
);
xbtn.ButtonRectangle = rcIcon;
// update position:
lX += rcIcon.Width + 2;
}
}
}
为了找到悬停的按钮,HitTestHolderButton
方法将返回按钮的索引。
public int HitTestHolderButton(
int x,
int y,
Rectangle rcHolder
)
{
int lBtnIndex = -1;
if (x >= rcHolder.Left && x <= rcHolder.Right)
{
XHolderButton btn = null;
for (int i = 0; i < m_xhBtn.Count; i++)
{
btn = m_xhBtn[i];
if (y >= 4 && y <= btn.ButtonRectangle.Bottom)
{
if (x >= btn.Left)
{
if (x < btn.Left + btn.ButtonRectangle.Width)
{
lBtnIndex = i;
break;
}
}
}
}
}
return lBtnIndex;
}
FillButton
方法在按钮框区域内绘制按钮。在全填充模式下,需要将绘制裁剪在按钮的边界内。LinearGradientBrush
和 PathGradientBrush
也用于执行绘制过程。
private void FillButton(
Rectangle rcBtn,
Graphics g,
Color clrStart,
Color clrEnd,
GraphicsPath XBoxClip
)
{
switch (m_eFillMode)
{
case XButtonFillMode.UpperGlow:
rcBtn.Height = 3;
using (LinearGradientBrush lgb = new LinearGradientBrush(
rcBtn,
clrStart,
clrEnd,
LinearGradientMode.Vertical))
{
g.FillRectangle(
lgb,
rcBtn
);
}
break;
case XButtonFillMode.FullFill:
// restrict drawing inside button box / rectangle:
g.SetClip(XBoxClip);
g.SetClip(rcBtn, CombineMode.Intersect);
#region Fill button
using (SolidBrush sb = new SolidBrush(clrStart))
{
g.FillRectangle(
sb,
rcBtn
);
}
#endregion
using (XAntiAlias xaa = new XAntiAlias(g))
{
#region Fill shine
using (GraphicsPath XBtnGlow = new GraphicsPath())
{
XBtnGlow.AddEllipse(rcBtn.Left - 5, rcBtn.Bottom -
rcBtn.Height / 2 + 3, rcBtn.Width + 11, rcBtn.Height + 11);
using (PathGradientBrush pgb = new PathGradientBrush(XBtnGlow))
{
pgb.CenterColor = Color.FromArgb(235, Color.White);
pgb.SurroundColors = new Color[] { Color.FromArgb(0, clrEnd) };
pgb.SetSigmaBellShape(0.8f);
g.FillPath(
pgb,
XBtnGlow
);
}
}
#endregion
#region Fill upper glow
rcBtn.Height = rcBtn.Height / 2 - 2;
using (LinearGradientBrush lgb = new LinearGradientBrush(
rcBtn,
Color.FromArgb(80, Color.White),
Color.FromArgb(140, Color.White),
LinearGradientMode.Vertical))
{
using (GraphicsPath XGlowPath =
XCoolFormHelper.RoundRect((RectangleF)rcBtn, 0, 0, 4, 4))
{
lgb.WrapMode = WrapMode.TileFlipXY;
g.FillPath(
lgb,
XGlowPath
);
}
}
#endregion
}
// reset clipping back:
g.ResetClip();
break;
}
主题
使用 XmlThemeLoader
类从 XML 配置文件加载主题。首先,使用 TargetForm
属性设置目标窗体。然后,您可以调用 ApplyTheme
方法来应用您的主题。XCoolForm 附带了五个主题,我希望制作更多主题。此外,我很乐意看到其他用户制作的主题。
-
灰色主题
-
蓝色冬季主题
-
深色系统主题
-
动物王国主题
-
情人节主题
-
白色主题
-
黑色主题
-
标准 Windows 主题
-
Vista 主题
-
MacOS 主题
-
外星人主题
-
Adobe Media Player 主题
如图所示,您可以使用标准的 Windows Forms 控件来构建您的用户界面。
很久以前,我构建了一个进程监控工具,所以我使用了 XCoolForm 和一些用户控件来创建自定义界面,如图所示。
历史
- 2009 年 2 月 26 日:初始版本。
- 2009 年 3 月 14 日
- 添加了新主题(Vista、标准 Windows、Adobe Media Player、外星人、黑色、白色、Mac OS)。
- 添加了边框类型:圆角和倾斜。
- 添加了 Mac 标题栏按钮样式。
- 添加了新的按钮框填充样式。
- 修复了各种问题。
- 即将推出:Office 2007 Ribbon 标题栏/按钮样式 + 更多炫酷功能/主题。