65.9K
CodeProject 正在变化。 阅读更多。
Home

花哨的 Windows Forms

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (126投票s)

2009年2月26日

CPOL

7分钟阅读

viewsIcon

579374

downloadIcon

51308

为您的应用程序带来全新/酷炫的外观和感觉。

XCoolForms

目录

引言

您是否厌倦了标准的 Windows Forms,并希望为您的应用程序添加一些炫酷的界面?那么,请继续阅读本文,您将发现实现起来有多么容易。XCoolForm 是一个库,它允许您绘制花哨的标题栏、边框、标题栏按钮、状态栏等。它附带了一些预制的样式,并且您可以轻松创建自己的样式。它还附带了大量的属性,因此可以满足每个用户的需求。我使用的图标是免费的;我从 http://www.iconarchive.com 下载了它们,并从 http://www.dafont.com 获取了Visitor 字体。

背景

这个项目的想法在一年前就产生了,那时我已经在使用 GDI+ 方面有了足够的经验,所以我开始着手实现它。我的下一个目标是用 WPF 重新实现这个项目,只要它提供了一些非常好的功能,并且大部分工作都可以通过 XAML 完成。

使用代码

要开始使用 XCoolForm 库,您应该遵循这三个简单的步骤

  1. 将引用添加到 XCoolForm 库。
  2. 使您的窗体继承自 XCoolForm 而不是 Form
  3. 通过添加图标持有器按钮、菜单图标、状态栏项目等来定制您的窗体。

结构

元素

Border

要正确渲染 3D 边框,您需要提供六种颜色(两种用于外边框,四种用于内边框)。然后,绘制例程将遍历颜色数组并构建边框。如果您想要一个简单的、扁平的边框,那么您只需要提供一种颜色。

XCoolForm 还支持圆角和倾斜边框(见下图)。您可以使用 RadiusInclination 属性指定圆角或倾斜度。

圆角边框

倾斜边框

标题栏

吸引用户的第一个元素是标题栏。因此,为标题栏提供时尚的功能非常重要。XCoolForm 提供了一整套属性,允许用户配置标题栏的最精细细节。您可以选择六种不同的样式和几种标题栏类型来控制标题栏的形状,设置背景图像、标题等。在下一节中,将解释最重要的标题栏样式和类型。

标题栏样式

XCoolForm 在渲染过程中支持六种标题栏样式。我将很快添加新的样式。

  • 高级标题栏样式

  • 标题栏使用渐变混合和光泽渲染。您需要指定从中绘制标题栏的五种颜色。这可以通过将颜色列表分配给 TitleBarMixColors 属性来完成。请注意,如果颜色数量不等于五,将抛出异常。

  • 矩形填充标题栏样式

  • 使用此样式时,标题栏区域被分成两个矩形,使用开始和结束渐变颜色填充。

  • 纹理填充标题栏样式

  • 标题栏使用自定义纹理填充。可以是任何有效的图像文件。

  • 线性渐变标题栏样式

  • 使用自定义开始/结束渐变颜色填充的基本标题栏样式。

  • 光泽样式

  • 标题栏的上部区域填充有平滑的光泽。

  • 设置为此样式时,仅渲染内边框和外边框。

标题栏类型

注意,当 TitleBarType 属性设置为所需的类型时,图标区域和标题栏按钮框将根据所选类型自动调整。

  • 矩形

  • 圆角

  • Angular

标题栏按钮

标题栏按钮绘制在按钮框内,该按钮框根据按钮的宽度和高度自动调整大小。XCoolForm 支持标题栏按钮悬停时的三种样式。关闭、最大化和最小化按钮的符号使用像素艺术绘制。对于下一个版本,我计划添加对自定义位图的支持,然后可以使用它们作为按钮符号。

  • 像素风格

  • 设置为 Pixeled 样式时,XCoolForm 提供三种不同的填充模式

    • 仅使用高亮颜色高亮显示按钮符号。

    • 顶部光泽

    • 按钮悬停时绘制顶部光泽。

    • 全填充

    • 此样式涉及三个步骤

      1. 首先,按钮使用纯色填充。
      2. 接下来,渲染光泽。
      3. 最后,绘制顶部光泽。

  • MacOS 风格

  • 标题栏按钮具有 MacOS 的外观和感觉。当按钮悬停时,将绘制符号和底部光泽。

图标持有器

图标持有器是一种快速访问工具栏,您可以在其中放置程序中的快捷方式或常用操作。如图所示,它由一系列图标组成。当鼠标悬停在图标持有器按钮上时,将显示框架,包括图像、描述和标题。

  1. 标题

  2. 用于标识持有器按钮的标题。

  3. 描述

  4. 关于持有器按钮的简短描述。

  5. Panel

  6. 用于按钮标题、描述和图像的容器。您可以使用 FrameAlpha 属性指定透明度。

  7. Image

  8. 面板的背景图像。

状态栏

如图所示,状态栏由不同的元素构成。状态栏的背景使用简单的渐变填充,使用 StatusStartColorStatusEndColor 属性。为了给状态栏提供光泽效果,上部区域填充有光泽。然后,渲染状态栏项目并用分隔符绑定。您还可以提供背景图像。最后要评论的元素是尺寸抓手。它通过重叠多个矩形来绘制,以使其具有 3D 外观。

实现

UML 图

下表包含 XCoolForm 库中类的简要描述。有些类还有内部类,但我没有在此列出。有关详细信息,请参阅库源代码。

类名 描述
X3DBorderPrimitive 提供用于构建 3D 边框的绘制路线。还实现了平面边框功能。
XTitleBarButton 表示标题栏按钮。在此处实现了绘制过程,包括全填充、光泽等。
XTitleBar 它实现了边框绘制、标题栏填充、背景图像绘制、对齐等方法。
XTitleBarIconHolder 它还包含一个 XHolderButton 类,用于在鼠标悬停时正确绘制渐变面板。
XStatusBar 用于绘制项目、背景图像、尺寸抓手、状态栏填充和光泽的绘制方法。
XCoolFormHelper 用于绘制圆角矩形和构建颜色混合的一些实用方法。
XAntiAlias 用于平滑绘图。它实现了 IDisposable 接口。
XmlThemeLoader 用于从 XML 文件加载主题的实用类。

代码片段

FillTitleBar 是用于标题栏填充的主要方法。基本上,它使用 LinearGradientBrushPathGradientBrush 类来执行绘制逻辑。首先,我们必须通过调用 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 方法在按钮框区域内绘制按钮。在全填充模式下,需要将绘制裁剪在按钮的边界内。LinearGradientBrushPathGradientBrush 也用于执行绘制过程。

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 主题

  • 如图所示,您可以使用标准的 Windows Forms 控件来构建您的用户界面。

  • Vista 主题

  • MacOS 主题

  • 外星人主题

  • 很久以前,我构建了一个进程监控工具,所以我使用了 XCoolForm 和一些用户控件来创建自定义界面,如图所示。

  • Adobe Media Player 主题

历史

  • 2009 年 2 月 26 日:初始版本。
  • 2009 年 3 月 14 日
    • 添加了新主题(Vista、标准 Windows、Adobe Media Player、外星人、黑色、白色、Mac OS)。
    • 添加了边框类型:圆角和倾斜。
    • 添加了 Mac 标题栏按钮样式。
    • 添加了新的按钮框填充样式。
    • 修复了各种问题。
  • 即将推出:Office 2007 Ribbon 标题栏/按钮样式 + 更多炫酷功能/主题。
© . All rights reserved.