// COPYRIGHT (C) Tom. ALL RIGHTS RESERVED. // THE AntdUI PROJECT IS AN WINFORM LIBRARY LICENSED UNDER THE Apache-2.0 License. // LICENSED UNDER THE Apache License, VERSION 2.0 (THE "License") // YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE License. // YOU MAY OBTAIN A COPY OF THE LICENSE AT // // http://www.apache.org/licenses/LICENSE-2.0 // // UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE // DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. // SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND // LIMITATIONS UNDER THE License. // GITEE: https://gitee.com/antdui/AntdUI // GITHUB: https://github.com/AntdUI/AntdUI // CSDN: https://blog.csdn.net/v_132 // QQ: 17379620 using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Windows.Forms; namespace AntdUI { /// /// Breadcrumb 面包屑 /// /// 显示当前页面在系统层级结构中的位置,并能向上返回。 [Description("Breadcrumb 面包屑")] [ToolboxItem(true)] [DefaultProperty("Items")] [DefaultEvent("ItemClick")] public class Breadcrumb : IControl { #region 属性 int gap = 12; [Description("间距"), Category("外观"), DefaultValue(12)] public int Gap { get => gap; set { if (gap == value) return; gap = value; ChangeItems(); Invalidate(); } } Color? fore; /// /// 文字颜色 /// [Description("文字颜色"), Category("外观"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public new Color? ForeColor { get => fore; set { if (fore == value) fore = value; fore = value; Invalidate(); } } /// /// 激活文字颜色 /// [Description("激活文字颜色"), Category("外观"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? ForeActive { get; set; } int radius = 4; /// /// 圆角 /// [Description("圆角"), Category("外观"), DefaultValue(4)] public int Radius { get => radius; set { if (radius == value) return; radius = value; Invalidate(); } } BreadcrumbItemCollection? items; /// /// 获取列表中所有列表项的集合 /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Description("集合"), Category("数据"), DefaultValue(null)] public BreadcrumbItemCollection Items { get { items ??= new BreadcrumbItemCollection(this); return items; } set => items = value.BindData(this); } /// /// 点击项时发生 /// [Description("点击项时发生"), Category("行为")] public event BreadcrumbItemEventHandler? ItemClick = null; #region Change protected override void OnSizeChanged(EventArgs e) { ChangeItems(); base.OnSizeChanged(e); } protected override void OnMarginChanged(EventArgs e) { ChangeItems(); base.OnMarginChanged(e); } protected override void OnPaddingChanged(EventArgs e) { ChangeItems(); base.OnPaddingChanged(e); } protected override void OnFontChanged(EventArgs e) { ChangeItems(); base.OnFontChanged(e); } #endregion #region 布局 bool pauseLayout = false; [Browsable(false), Description("暂停布局"), Category("行为"), DefaultValue(false)] public bool PauseLayout { get => pauseLayout; set { if (pauseLayout == value) return; pauseLayout = value; if (!value) { ChangeItems(); Invalidate(); } } } Rectangle[] hs = new Rectangle[0]; internal void ChangeItems() { if (items == null || items.Count == 0) return; if (pauseLayout) return; var _rect = ClientRectangle.PaddingRect(Padding); if (_rect.Width == 0 || _rect.Height == 0) return; var rect = _rect.PaddingRect(Margin); hs = Helper.GDI(g => { var hs = new List(items.Count); var size_t = g.MeasureString(Config.NullText, Font).Size(); int sp = (int)(4 * Config.Dpi), sp2 = sp * 2, imgsize = (int)(size_t.Height * .8F), h = size_t.Height + sp, y = rect.Y + (rect.Height - h) / 2, y_img = rect.Y + (rect.Height - imgsize) / 2, _gap = (int)(gap * Config.Dpi); int x = 0, tmpx = 0; foreach (BreadcrumbItem it in items) { it.PARENT = this; if (it.Text == null || string.IsNullOrEmpty(it.Text)) { var Rect = new Rectangle(rect.X + x, y, imgsize + sp2, h); if (it.HasIcon) { it.Rect = it.RectText = Rect; it.RectImg = new Rectangle(Rect.X + sp, y_img, imgsize, imgsize); } else it.Rect = it.RectText = Rect; } else { var size = g.MeasureString(it.Text, Font).Size(); if (it.HasIcon) { int imgw = imgsize + sp2; var Rect = new Rectangle(rect.X + x, y, size.Width + sp + imgsize + sp2, h); it.Rect = Rect; it.RectImg = new Rectangle(Rect.X + sp, y_img, imgsize, imgsize); it.RectText = new Rectangle(it.RectImg.Right + sp, Rect.Y, Rect.Width - imgw - sp2, Rect.Height); } else it.Rect = it.RectText = new Rectangle(rect.X + x, y, size.Width + sp2, h); } x += it.Rect.Width + _gap; if (tmpx > 0) hs.Add(new Rectangle(tmpx - _gap + sp, y, _gap, h)); tmpx = x; } return hs.ToArray(); }); } #endregion #endregion #region 渲染 readonly StringFormat s_f = Helper.SF_ALL(); protected override void OnPaint(PaintEventArgs e) { if (items == null || items.Count == 0) return; var g = e.Graphics.High(); float _radius = radius * Config.Dpi; using (var brush = new SolidBrush(fore ?? Style.Db.TextSecondary)) using (var brush_active = new SolidBrush(ForeActive ?? Style.Db.Text)) { foreach (var it in hs) g.DrawStr("/", Font, brush, it, s_f); for (int i = 0; i < items.Count; i++) { var it = items[i]; if (it == null) continue; if (i == items.Count - 1) { //最后一个 PaintImg(g, it, brush_active.Color, it.IconSvg, it.Icon); g.DrawStr(it.Text, Font, brush_active, it.RectText, s_f); } else { if (it.Hover) { using (var path = it.Rect.RoundPath(_radius)) { using (var brush_hover = new SolidBrush(Style.Db.FillSecondary)) { g.FillPath(brush_hover, path); } } PaintImg(g, it, brush_active.Color, it.IconSvg, it.Icon); g.DrawStr(it.Text, Font, brush_active, it.RectText, s_f); } else { PaintImg(g, it, brush.Color, it.IconSvg, it.Icon); g.DrawStr(it.Text, Font, brush, it.RectText, s_f); } } } } this.PaintBadge(g); base.OnPaint(e); } bool PaintImg(Graphics g, BreadcrumbItem it, Color color, string? svg, Image? bmp) { if (svg != null) { if (g.GetImgExtend(svg, it.RectImg, color)) return false; } else if (bmp != null) { g.DrawImage(bmp, it.RectImg); return false; } return true; } #endregion #region 鼠标 protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (items == null || items.Count == 0) return; int hand = 0, change = 0; foreach (BreadcrumbItem it in items) { bool hover = it.Rect.Contains(e.Location); if (it.Hover != hover) { it.Hover = hover; change++; } if (it.Hover) hand++; } SetCursor(hand > 0); if (change > 0) Invalidate(); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); SetCursor(false); if (items == null || items.Count == 0) return; int change = 0; foreach (BreadcrumbItem it in items) { if (it.Hover) { it.Hover = false; change++; } } if (change > 0) Invalidate(); } protected override void OnLeave(EventArgs e) { base.OnLeave(e); SetCursor(false); if (items == null || items.Count == 0) return; int change = 0; foreach (BreadcrumbItem it in items) { if (it.Hover) { it.Hover = false; change++; } } if (change > 0) Invalidate(); } protected override void OnMouseClick(MouseEventArgs e) { base.OnMouseClick(e); if (items == null || items.Count == 0) return; for (int i = 0; i < items.Count; i++) { var it = items[i]; if (it != null && it.Rect.Contains(e.Location)) { ItemClick?.Invoke(this, new BreadcrumbItemEventArgs(it, e)); return; } } } #endregion } public class BreadcrumbItemCollection : iCollection { public BreadcrumbItemCollection(Breadcrumb it) { BindData(it); } internal BreadcrumbItemCollection BindData(Breadcrumb it) { action = render => { if (render) it.ChangeItems(); it.Invalidate(); }; return this; } } public class BreadcrumbItem { /// /// ID /// public string? ID { get; set; } Image? icon = null; /// /// 图标 /// [Description("图标"), Category("外观"), DefaultValue(null)] public Image? Icon { get => icon; set { if (icon == value) return; icon = value; Invalidates(); } } string? iconsvg = null; /// /// 图标SVG /// [Description("图标SVG"), Category("外观"), DefaultValue(null)] public string? IconSvg { get => iconsvg; set { if (iconsvg == value) return; iconsvg = value; Invalidates(); } } /// /// 是否包含图标 /// public bool HasIcon { get => IconSvg != null || Icon != null; } string? text = null; /// /// 文本 /// [Description("文本"), Category("外观"), DefaultValue(null)] public string? Text { get => text; set { if (text == value) return; text = value; Invalidates(); } } /// /// 用户定义数据 /// [Description("用户定义数据"), Category("数据"), DefaultValue(null)] public object? Tag { get; set; } internal bool Hover { get; set; } internal Rectangle Rect { get; set; } internal Rectangle RectImg { get; set; } internal Rectangle RectText { get; set; } internal Breadcrumb? PARENT { get; set; } void Invalidates() { if (PARENT == null) return; PARENT.ChangeItems(); PARENT.Invalidate(); } public override string? ToString() => text; } }