介绍
这么长时间以来,我一直在尝试寻找一个类似于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;
放置这些静态变量是为了让您可以给它们另一个值。例如,如果您将图像放在外部文件中,这将非常有用。顺便说一下,这些是图片:
它们被用来占用(磁盘上)最小的空间。正如我所说,我必须绘制填充/身体图像多次,因为拉伸它会在上面放置一个透明渐变。
设置这么多设置会带来很多可能性:一个面包屑可能会有/可能没有孩子;可能/可能没有复选框;可能/可能没有图像;可能/可能没有文本。为此,有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