Crumb -面包屑状的嵌套按钮

Image 1

介绍

这么长时间以来,我一直在尝试寻找一个类似于Ubuntu软件中心的breadcrumb控件,但我就是找不到,所以我决定自己去做一个。所以,在几个小时的工作之后,这就是它!

背景

这张图片显示了我的想法是从哪里来的。

使用的代码

这个类继承了System.Windows.Forms。控件类,而不是来自System.Windows.Forms。按钮,但行为和大多数功能是相同的。System.Windows.Forms.Vercas。Crumb类具有以下特性:

NameDescription显示在按钮上的文本。按钮上显示的图像。文本对齐按钮上文本的对齐方式。图像在按钮上的对齐方式。textimagealign确定图像和文本是否都将按照文本的对齐方式进行对齐,并并排显示。checkbox确定是否应该在按钮上绘制复选框以显示其已选中的属性。否则,按钮将显示有焦点或无焦点。确定单击按钮时是否会更改其选中属性。checked确定按钮是集中显示还是选中了按钮上显示的复选框。获取巢中碎屑的索引。孩子:在水流之后的碎屑。

嵌套行为允许最多检查一个碎屑。在示例表单中,有一个对CrumbClick默认事件的订阅,该事件将始终在嵌套中保留选中的crumb。顺便说一下,截图中蓝色的按钮是选中的碎屑。

我还必须补充一点,背景图像被绘制了多次,以实现背景,因为,正如下面指出的,它没有正常拉伸,右边是透明的。

现在是技术部分…具体来说,就是绘制按钮。嗯,因为我缺乏绘画技巧,我决定使用图像。

static Image Left_Edge = Properties.Resources.crumb_left_end;
static Image Body = Properties.Resources.crumb_body;
static Image Right_Edge = Properties.Resources.crumb_right_end;
static Image Right_Triangle = Properties.Resources.crumb_right_point;

static Image Selected_Left_Edge = Properties.Resources.selected_crumb_left_end;
static Image Selected_Body = Properties.Resources.selected_crumb_body;
static Image Selected_Right_Edge = Properties.Resources.selected_crumb_right_end;
static Image Selected_Right_Triangle = Properties.Resources.selected_crumb_right_point;

static Image Hovered_Left_Edge = Properties.Resources.hovered_crumb_left_end;
static Image Hovered_Body = Properties.Resources.hovered_crumb_body;
static Image Hovered_Right_Edge = Properties.Resources.hovered_crumb_right_end;
static Image Hovered_Right_Triangle = Properties.Resources.hovered_crumb_right_point;

static Image Clicked_Left_Edge = Properties.Resources.clicked_crumb_left_end;
static Image Clicked_Body = Properties.Resources.clicked_crumb_body;
static Image Clicked_Right_Edge = Properties.Resources.clicked_crumb_right_end;
static Image Clicked_Right_Triangle = Properties.Resources.clicked_crumb_right_point;

放置这些静态变量是为了让您可以给它们另一个值。例如,如果您将图像放在外部文件中,这将非常有用。顺便说一下,这些是图片:

Image 2

它们被用来占用(磁盘上)最小的空间。正如我所说,我必须绘制填充/身体图像多次,因为拉伸它会在上面放置一个透明渐变。

设置这么多设置会带来很多可能性:一个面包屑可能会有/可能没有孩子;可能/可能没有复选框;可能/可能没有图像;可能/可能没有文本。为此,有DefaultSize覆盖属性:

protected override Size DefaultSize
{
    get
    {
        var w = (c == null ? (this.Controls.Count == 0 ? 3 : 15) : 
            Math.Max(15, c.Width)) + (this.CheckBox ? 24 : 0) + 
            (this.img != null ? img.Width : 0) + 
            (!string.IsNullOrEmpty(this.Text) ? 
            TextRenderer.MeasureText(this.Text, this.Font).Width : 0) + 
            (this.Parent is Crumb ? 13 : 0);
        return new Size(this.Controls.Count > 0 ? w : 
                        Math.Max(w, this.Width), 24);
    }
}

你可能已经注意到了,我把所有的可能性都考虑进去了。最重要的是,如果crumb有一个子节点,它将自动调整大小,因为我有一些bug。文本的字体也很重要。文本被测量,大小也根据它计算。

控件中有一个名为CrumbClick的事件。此事件通过嵌套的碎屑重定向自身。所以任何点击的碎屑会启动所有碎屑的事件。订阅第一个crumb事件将实际上帮助您跟踪所有的crumb。该事件是按标准的,并具有一个EventArgs类称为CrumbClickEventArgs,它有以下属性:

索引嵌套中被单击的碎屑的索引。点击了SenderThe crumb。在被点击之前是否检查了crumb。获取或设置事件后碎屑的检查状态。如果ChecksOnClick为真,则没有任何效果。ChecksOnClickGets或设置crumb是否应该在单击时切换其检查状态。如果这个属性为真,那么不管您在事件参数中说什么,crumb的检查状态总是会转移到另一个状态。

此事件通过订阅子节点的CrumbClick事件传递给父节点。事件的代码是这样的:

EventHandler<crumbclickeventargs> childClick = 
     new EventHandler<crumbclickeventargs>(c_Click);
static void c_Click(object sender, CrumbClickEventArgs e)
{
    if ((sender as Crumb).Parent is Crumb) 
       { ((sender as Crumb).Parent as Crumb).OnCrumbClick(e); }
}

事件被重新调用,但这次是在父屑上。保留事件参数。

我还提到过,巢里只有一个碎屑可以被检查。这应该是模拟一个选择系统。

public Boolean Checked
{
    get
    {
        return chk;
    }
    set
    {
        if (!nocc)
        {
            nocc = true;

            Crumb cr = this.Child;
            while (cr != null) { cr.Checked = false; cr = cr.Child; }
            cr = this.Parent as Crumb;
            while (cr != null && cr is Crumb)
                { cr.Checked = false; cr = cr.Parent as Crumb; }

            nocc = false;
        }
        chk = value;

        Refresh();
    }
}

nocc是一个静态布尔值,用来声明其他的碎屑不应该更新它们在巢中的同伴,因此会避免溢出异常。

现在,绘图部分:

public static float dc(PaintEventArgs e, Color foreColor, float x = 0f, 
       string text = "", Image img = null, bool clicked = false, 
       bool hovered = false, bool chk = false, bool chkbox = false, 
       float width = 0f, Font font = null, bool tai = true, 
       ContentAlignment ta = ContentAlignment.MiddleCenter, 
       ContentAlignment ia = ContentAlignment.MiddleLeft, 
       bool pt = false, bool ch = true)
{
    if (font == null) { font = SystemFonts.MessageBoxFont; }
    width = Math.Max((ch ? 15 : 3) + (chkbox ? 24 : 0) + 
           (img != null ? img.Width : 0) + (!string.IsNullOrEmpty(text) ? 
           TextRenderer.MeasureText(text, font).Width : 0) + (pt ? 13 : 0), width);
    
    if (clicked)
    {
        e.Graphics.DrawImage(Crumb.Clicked_Left_Edge, x, 0);
        for (int i = (int)x + Crumb.Clicked_Left_Edge.Width; i <= 
                      x + width - (ch ? Crumb.Clicked_Right_Triangle : 
                      Crumb.Clicked_Right_Edge).Width; i++)
            e.Graphics.DrawImage(Crumb.Clicked_Body, i, 0);
        e.Graphics.DrawImage(ch ? Crumb.Clicked_Right_Triangle : 
          Crumb.Clicked_Right_Edge, x + width - (ch ? 
          Crumb.Clicked_Right_Triangle : Crumb.Clicked_Right_Edge).Width, 0);
    }
    else if (hovered)
    {
        e.Graphics.DrawImage(Crumb.Hovered_Left_Edge, x, 0);
        for (int i = (int)x + Crumb.Hovered_Left_Edge.Width; i <= 
                   x + width - (ch ? Crumb.Hovered_Right_Triangle : 
                   Crumb.Hovered_Right_Edge).Width; i++)
            e.Graphics.DrawImage(Crumb.Hovered_Body, i, 0);
        e.Graphics.DrawImage((ch ? Crumb.Hovered_Right_Triangle : 
           Crumb.Hovered_Right_Edge), x + width - (ch ? 
           Crumb.Hovered_Right_Triangle : Crumb.Hovered_Right_Edge).Width, 0);
    }
    else if (chk && !chkbox)
    {
        e.Graphics.DrawImage(Crumb.Selected_Left_Edge, x, 0);
        for (int i = (int)x + Crumb.Selected_Left_Edge.Width; i <= 
                      x + width - (ch ? Crumb.Selected_Right_Triangle : 
                      Crumb.Selected_Right_Edge).Width; i++)
            e.Graphics.DrawImage(Crumb.Selected_Body, i, 0);
        e.Graphics.DrawImage((ch ? Crumb.Selected_Right_Triangle : 
                   Crumb.Selected_Right_Edge), x + width - (ch ? 
                   Crumb.Selected_Right_Triangle : 
                   Crumb.Selected_Right_Edge).Width, 0);
    }
    else
    {
        e.Graphics.DrawImage(Crumb.Left_Edge, x, 0);
        for (int i = (int)x + Crumb.Left_Edge.Width; i <= x + width - 
                   (ch ? Crumb.Right_Triangle : Crumb.Right_Edge).Width; i++)
            e.Graphics.DrawImage(Crumb.Body, i, 0);
        e.Graphics.DrawImage((ch ? Crumb.Right_Triangle : Crumb.Right_Edge), 
             x + width - (ch ? Crumb.Right_Triangle : Crumb.Right_Edge).Width, 0);
    }

    if (chkbox)
    {
        var st = chk ? (clicked ? 
          System.Windows.Forms.VisualStyles.CheckBoxState.CheckedPressed : 
          System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal) : 
          (clicked ? System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedPressed : 
          System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);

        var sz = CheckBoxRenderer.GetGlyphSize(e.Graphics, st);

        CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point((int)(x + 
           (pt ? 13 : 0) + (24 - sz.Height) / 2), (24 - sz.Height) / 2), st);
    }

    if (tai)
    {
        dit(e, foreColor, x + (pt ? 13 : 0), ta, text, font, chkbox, width, ia, img);
    }
    else
    {
        di(e, x, img, ia, chkbox, width);
        dt(e, foreColor, x + (pt ? 13 : 0), ta, text, font, chkbox, width);
    }

    return width;
}

这里我绘制按钮的组件图像,然后是文本/图像。不要哭,没有那么难。它只是需要做很多检查,以确保一切都很酷。在最后一行中,有三种不同的方法:dit(绘制图像文本)、di(绘制图像)和dt(绘制文本)。它们都是静态方法,需要很多变量来决定在哪里绘制。以下是守则:

private static void dt(PaintEventArgs e, Color foreColor, float x = 0f, 
        ContentAlignment txta = ContentAlignment.MiddleCenter, 
        string text = "", Font font = null, 
        bool chkbox = false, float width = 0f)
{
    if (!string.IsNullOrEmpty(text))
    {
        PointF p = new PointF();

        var s = e.Graphics.MeasureString(text, font);

        switch (txta)
        {
            case ContentAlignment.BottomCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                        width) - 15 - s.Width) / 2, 21 - s.Height);
                break;
            case ContentAlignment.BottomLeft:
                p = new PointF(x + (chkbox ? 24 : 3), 21 - s.Height);
                break;
            case ContentAlignment.BottomRight:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15) - s.Width, 21 - s.Height);
                break;

            case ContentAlignment.MiddleCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15 - s.Width) / 2, (24 - s.Height) / 2);
                break;
            case ContentAlignment.MiddleLeft:
                p = new PointF(x + (chkbox ? 24 : 3), (24 - s.Height) / 2);
                break;
            case ContentAlignment.MiddleRight:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15) - s.Width, (24 - s.Height) / 2);
                break;

            case ContentAlignment.TopCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15 - s.Width) / 2, 3);
                break;
            case ContentAlignment.TopLeft:
                p = new PointF(x + (chkbox ? 24 : 3), 3);
                break;
            case ContentAlignment.TopRight:
                p = new PointF(x + ((chkbox ? 
                    (width - 24) : width) - 15) - s.Width, 3);
                break;
        }

        using (Brush b = new SolidBrush(foreColor))
            e.Graphics.DrawString(text, font, b, p);
    }
}

这里我根据TextAlign属性绘制文本:

private static void di(PaintEventArgs e, float x = 0f, Image img = null, 
        ContentAlignment imga = ContentAlignment.MiddleLeft, 
        bool chkbox = false, float width = 0f)
{
    if (img != null)
    {
        PointF p = new Point();

        switch (imga)
        {
            case ContentAlignment.BottomCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                        width) - 15 - img.Width) / 2, 21 - img.Height);
                break;
            case ContentAlignment.BottomLeft:
                p = new PointF(x + (chkbox ? 24 : 3), 21 - img.Height);
                break;
            case ContentAlignment.BottomRight:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                        width) - 15) - img.Width, 21 - img.Height);
                break;

            case ContentAlignment.MiddleCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                        width) - 15 - img.Width) / 2, (24 - img.Height) / 2);
                break;
            case ContentAlignment.MiddleLeft:
                p = new PointF(x + (chkbox ? 24 : 3), (24 - img.Height) / 2);
                break;
            case ContentAlignment.MiddleRight:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                        width) - 15) - img.Width, (24 - img.Height) / 2);
                break;

            case ContentAlignment.TopCenter:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15 - img.Width) / 2, 3);
                break;
            case ContentAlignment.TopLeft:
                p = new PointF(x + (chkbox ? 24 : 3), 3);
                break;
            case ContentAlignment.TopRight:
                p = new PointF(x + ((chkbox ? (width - 24) : 
                               width) - 15) - img.Width, 3);
                break;
        }

        e.Graphics.DrawImage(img, p);
    }
}

这里我根据ImageAlign属性绘制图像:

private static void dit(PaintEventArgs e, Color foreColor, float x = 0f, 
        ContentAlignment txta = ContentAlignment.MiddleCenter, 
        string text = "", Font font = null, bool chkbox = false, 
        float width = 0f, 
        ContentAlignment imga = ContentAlignment.MiddleLeft, Image img = null)
{
    if (!string.IsNullOrEmpty(text))
    {
        if (img != null)
        {
            if (!string.IsNullOrEmpty(text))
            {
                float w = 0, h = 0, ht = 0;

                var s = e.Graphics.MeasureString(text, font);

                switch (txta)
                {
                    case ContentAlignment.BottomCenter:
                        w = ((chkbox ? (width - 24) : width) - 15 - 
                              s.Width - img.Width) / 2; h = 21 - 
                              img.Height; ht = 21 - s.Height;
                        break;
                    case ContentAlignment.BottomLeft:
                        w = chkbox ? 24 : 3; h = 21 - img.Height; ht = 21 - s.Height;
                        break;
                    case ContentAlignment.BottomRight:
                        w = ((chkbox ? (width - 24) : width) - 15) - 
                              s.Width - img.Width; h = 21 - 
                              img.Height; ht = 21 - s.Height;
                        break;

                    case ContentAlignment.MiddleCenter:
                        w = ((chkbox ? (width - 24) : width) - 15 - 
                              s.Width - img.Width) / 2; h = (24 - img.Height) / 2; 
                              ht = (24 - s.Height) / 2;
                        break;
                    case ContentAlignment.MiddleLeft:
                        w = chkbox ? 24 : 3; h = (24 - img.Height) / 2; 
                                     ht = (24 - s.Height) / 2;
                        break;
                    case ContentAlignment.MiddleRight:
                        w = ((chkbox ? (width - 24) : width) - 15) - s.Width - 
                              img.Width; h = (24 - img.Height) / 2; 
                              ht = (24 - s.Height) / 2;
                        break;

                    case ContentAlignment.TopCenter:
                        w = ((chkbox ? (width - 24) : width) - 15 - 
                              s.Width - img.Width) / 2; h = ht = 3;
                        break;
                    case ContentAlignment.TopLeft:
                        w = chkbox ? 24 : 3; h = ht = 3;
                        break;
                    case ContentAlignment.TopRight:
                        w = ((chkbox ? (width - 24) : width) - 15) - 
                              s.Width - img.Width; h = ht = 3;
                        break;
                }

                w += x;

                e.Graphics.DrawImage(img, w, h);

                using (Brush b = new SolidBrush(foreColor))
                    e.Graphics.DrawString(text, font, b, w + img.Width, ht);
            }
        }
        else
        {
            dt(e, foreColor, x, txta, text, font, chkbox, width);
        }
    }
    else
    {
        di(e, x, img, imga, chkbox, width);
    }
}

这里我根据TextAlign属性绘制图像和文本。同样,这个混乱被用在控件的OnPaint(…)方法上:

protected override void OnPaint(PaintEventArgs e)
{
    Crumb.dc(e, this.ForeColor, 0, Text, this.img, this.clicked, 
             this.hovered, this.chk, this.chkbox, this.c == null ? 
             this.Width : (this.Width - this.c.Width), this.Font, 
             this.tai, this.txta, this.imga, this.Parent is Crumb, 
             this.Controls.Count > 0);
    base.OnPaint(e);
}

这里我传递了dc绘制面包屑所需的参数。

同样,dit方法只在TextImageAlign属性设置为true时使用。

在绘制按钮(不是复选框、文本或图像)时,有4个(或3个)可能的选择(取决于是否有复选框):

    ClickedHoveredChecked/Selected(如果没有复选框)(如果有,复选框将显示已选中/未选中)正常

当嵌套一个屑,尖的(右)边的父被画在子上面。这可以在子组件的绘制事件的订阅中找到。的代码是:

PaintEventHandler childPaint = new PaintEventHandler(c_Paint);
//Really, this is required for unsubscribing from the event.
//Does it sound right to unsubscribe a NEW DELEGATE from an event?

static void c_Paint(object sender, PaintEventArgs e)
{
    var c = sender as Crumb;

    if (c.Parent != null && c.Parent is Crumb)
    {
        var p = c.Parent as Crumb;
        dc(e, Color.Black, -25f,  38f, hovered: 
           p.hovered, clicked: p.clicked, chk: p.chk, chkbox: p.chkbox);
    }
}

我真的在寻找一种更快的绘图方式,因为,在这个例子中,我在选择面包屑时得到了一些剪切。

的兴趣点

我已经学会了那个图形。在拉伸糟透了。

历史

  • 11/28/2010 -首次发布。
  • 12/1/2010 -更新的文章与代码讨论。

请告诉我你发现的bug !此外,如果有些事情似乎不太好,在低评价之前问我!

本文转载于:http://www.diyabc.com/frontweb/news14567.html

原文地址:https://www.cnblogs.com/Dincat/p/13488370.html