花哨的 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 标题栏/按钮样式 + 更多炫酷功能/主题。


