// 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 { /// /// Slider 滑动输入条 /// /// 滑动型输入器,展示当前值和可选范围。 [Description("Slider 滑动输入条")] [ToolboxItem(true)] [DefaultProperty("Value")] [DefaultEvent("ValueChanged")] public class Slider : IControl { #region 属性 Color? fill; /// /// 颜色 /// [Description("颜色"), Category("外观"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? Fill { get => fill; set { if (fill == value) return; fill = value; Invalidate(); } } /// /// 悬停颜色 /// [Description("悬停颜色"), Category("外观"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillHover { get; set; } /// /// 激活颜色 /// [Description("激活颜色"), Category("外观"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? FillActive { get; set; } int _minValue = 0; /// /// 最小值 /// [Description("最小值"), Category("数据"), DefaultValue(0)] public int MinValue { get => _minValue; set { if (value > _maxValue) return; if (_minValue == value) return; _minValue = value; Invalidate(); } } int _maxValue = 100; /// /// 最大值 /// [Description("最大值"), Category("数据"), DefaultValue(100)] public int MaxValue { get => _maxValue; set { if (value < _minValue || value < _value) return; if (_maxValue == value) return; _maxValue = value; Invalidate(); } } int _value = 0; /// /// 当前值 /// [Description("当前值"), Category("数据"), DefaultValue(0)] public int Value { get => _value; set { if (value < _minValue) value = _minValue; else if (value > _maxValue) value = _maxValue; if (_value == value) return; _value = value; ValueChanged?.Invoke(this, new IntEventArgs(_value)); Invalidate(); } } /// /// Value格式化时发生 /// [Description("Value格式化时发生"), Category("行为")] public event ValueFormatEventHandler? ValueFormatChanged; TooltipForm? tooltipForm = null; string? tooltipText = null; internal void ShowTips(int Value, RectangleF dot_rect) { var text = ValueFormatChanged == null ? Value.ToString() : ValueFormatChanged.Invoke(this, new IntEventArgs(Value)); if (text == tooltipText && tooltipForm != null) return; tooltipText = text; var _rect = RectangleToScreen(ClientRectangle); var rect = new Rectangle(_rect.X + (int)dot_rect.X, _rect.Y + (int)dot_rect.Y, (int)dot_rect.Width, (int)dot_rect.Height); if (tooltipForm == null) { tooltipForm = new TooltipForm(this, rect, tooltipText, new TooltipConfig { Font = Font, ArrowAlign = (align == TAlignMini.Top || align == TAlignMini.Bottom) ? TAlign.Right : TAlign.Top, }); tooltipForm.Show(this); } else tooltipForm.SetText(rect, tooltipText); } internal void CloseTips() { tooltipForm?.IClose(); tooltipForm = null; } TAlignMini align = TAlignMini.Left; /// /// 方向 /// [Description("方向"), Category("外观"), DefaultValue(TAlignMini.Left)] public TAlignMini Align { get => align; set { if (align == value) return; align = value; OnSizeChanged(EventArgs.Empty); Invalidate(); } } /// /// Value 属性值更改时发生 /// [Description("Value 属性值更改时发生"), Category("行为")] public event IntEventHandler? ValueChanged; /// /// 是否显示数值 /// [Description("是否显示数值"), Category("行为"), DefaultValue(false)] public bool ShowValue { get; set; } = false; int lineSize = 4; /// /// 线条粗细 /// [Description("线条粗细"), Category("外观"), DefaultValue(4)] public int LineSize { get => lineSize; set { if (lineSize == value) return; lineSize = value; Invalidate(); } } internal int dotSize = 10; /// /// 点大小 /// [Description("点大小"), Category("外观"), DefaultValue(10)] public int DotSize { get => dotSize; set { if (dotSize == value) return; dotSize = value; Invalidate(); } } internal int dotSizeActive = 12; /// /// 点激活大小 /// [Description("点激活大小"), Category("外观"), DefaultValue(12)] public int DotSizeActive { get => dotSizeActive; set { if (dotSizeActive == value) return; dotSizeActive = value; Invalidate(); } } /// /// 是否只能拖拽到刻度上 /// [Description("是否只能拖拽到刻度上"), Category("数据"), DefaultValue(false)] public bool Dots { get; set; } SliderMarkItemCollection? marks; /// /// 刻度标记 /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Description("刻度标记"), Category("数据"), DefaultValue(null)] public SliderMarkItemCollection Marks { get { marks ??= new SliderMarkItemCollection(this); return marks; } set => marks = value.BindData(this); } /// /// 刻度文本间距 /// [Description("刻度文本间距"), Category("外观"), DefaultValue(4)] public int MarkTextGap { get; set; } = 4; #endregion #region 渲染 internal Rectangle rect_read; protected override void OnPaint(PaintEventArgs e) { var padding = Padding; var _rect = ClientRectangle.PaddingRect(padding); int LineSize = (int)(lineSize * Config.Dpi), DotS = (int)((dotSizeActive > dotSize ? dotSizeActive : dotSize) * Config.Dpi), DotS2 = DotS * 2; if (align == TAlignMini.Top || align == TAlignMini.Bottom) { if (padding.Top > DotS || padding.Bottom > DotS) { if (padding.Top > DotS && padding.Bottom > DotS) rect_read = new Rectangle(_rect.X + (_rect.Width - LineSize) / 2, _rect.Y, LineSize, _rect.Height); else if (padding.Top > DotS) rect_read = new Rectangle(_rect.X + (_rect.Width - LineSize) / 2, _rect.Y, LineSize, _rect.Height - DotS); else rect_read = new Rectangle(_rect.X + (_rect.Width - LineSize) / 2, _rect.Y + DotS, LineSize, _rect.Height - DotS); } else rect_read = new Rectangle(_rect.X + (_rect.Width - LineSize) / 2, _rect.Y + DotS, LineSize, _rect.Height - DotS2); } else { if (padding.Left > DotS || padding.Right > DotS) { if (padding.Left > DotS && padding.Right > DotS) rect_read = new Rectangle(_rect.X, _rect.Y + (_rect.Height - LineSize) / 2, _rect.Width, LineSize); else if (padding.Left > DotS) rect_read = new Rectangle(_rect.X, _rect.Y + (_rect.Height - LineSize) / 2, _rect.Width - DotS, LineSize); else rect_read = new Rectangle(_rect.X + DotS, _rect.Y + (_rect.Height - LineSize) / 2, _rect.Width - DotS, LineSize); } else rect_read = new Rectangle(_rect.X + DotS, _rect.Y + (_rect.Height - LineSize) / 2, _rect.Width - DotS2, LineSize); } bool enabled = Enabled; Color color = enabled ? fill ?? Style.Db.InfoBorder : Style.Db.FillTertiary, color_dot = enabled ? fill ?? Style.Db.InfoBorder : Style.Db.SliderHandleColorDisabled, color_hover = FillHover ?? Style.Db.InfoHover, color_active = FillActive ?? Style.Db.Primary; var g = e.Graphics.High(); IPaint(g, _rect, enabled, color, color_dot, color_hover, color_active); this.PaintBadge(g); } internal virtual void IPaint(Graphics g, Rectangle rect, bool enabled, Color color, Color color_dot, Color color_hover, Color color_active) { float prog = ProgValue(_value); #region 线条 using (var path = rect_read.RoundPath(rect_read.Height / 2)) { using (var brush = new SolidBrush(Style.Db.FillQuaternary)) { g.FillPath(brush, path); if (AnimationHover) { using (var brush_hover = new SolidBrush(Helper.ToColorN(AnimationHoverValue, brush.Color))) { g.FillPath(brush_hover, path); } } else if (ExtraMouseHover) g.FillPath(brush, path); } if (prog > 0) { g.SetClip(RectLine(rect_read, prog)); if (AnimationHover) { using (var brush = new SolidBrush(color)) { g.FillPath(brush, path); } using (var brush = new SolidBrush(Helper.ToColor(255 * AnimationHoverValue, color_hover))) { g.FillPath(brush, path); } } else { using (var brush = new SolidBrush(ExtraMouseHover ? color_hover : color)) { g.FillPath(brush, path); } } g.ResetClip(); } } #endregion using (var brush = new SolidBrush(Style.Db.BgBase)) { PaintMarksEllipse(g, rect, rect_read, brush, color, LineSize); PaintEllipse(g, rect, rect_read, prog, brush, color_dot, color_hover, color_active, LineSize); } } readonly StringFormat s_f = Helper.SF_NoWrap(); internal RectangleF rectEllipse; internal void PaintEllipse(Graphics g, Rectangle rect, RectangleF rect_read, float prog, SolidBrush brush, Color color, Color color_hover, Color color_active, int LineSize) { int DotSize = (int)(dotSize * Config.Dpi), DotSizeActive = (int)(dotSizeActive * Config.Dpi); rectEllipse = RectDot(rect, rect_read, prog, DotSizeActive + LineSize); var rect_ellipse_rl = RectDot(rect, rect_read, prog, DotSize + LineSize); if (ShowValue && ExtraMouseDotHover) ShowTips(_value, rect_ellipse_rl); if (AnimationDotHover) { float value = ((DotSizeActive - DotSize) * AnimationDotHoverValue); using (var brush_shadow = new SolidBrush(color_active.rgba(.2F))) { g.FillEllipse(brush_shadow, RectDot(rect, rect_read, prog, DotSizeActive + LineSize + LineSize * 2 * AnimationDotHoverValue)); } using (var brush_dot = new SolidBrush(color_active)) { g.FillEllipse(brush_dot, RectDot(rect, rect_read, prog, DotSize + LineSize + value)); } g.FillEllipse(brush, RectDot(rect, rect_read, prog, DotSize + value)); } else if (ExtraMouseDotHover) { using (var brush_shadow = new SolidBrush(color_active.rgba(.2F))) { g.FillEllipse(brush_shadow, RectDot(rect, rect_read, prog, DotSizeActive + LineSize * 3)); } using (var brush_dot = new SolidBrush(color_active)) { g.FillEllipse(brush_dot, RectDot(rect, rect_read, prog, DotSizeActive + LineSize)); } g.FillEllipse(brush, RectDot(rect, rect_read, prog, DotSizeActive)); } else { if (AnimationHover) { using (var brush_dot_old = new SolidBrush(color)) using (var brush_dot = new SolidBrush(Helper.ToColor(255 * AnimationHoverValue, color_hover))) { var rect_dot = RectDot(rect, rect_read, prog, DotSize + LineSize); g.FillEllipse(brush_dot_old, rect_dot); g.FillEllipse(brush_dot, rect_dot); } } else { using (var brush_dot = new SolidBrush(ExtraMouseHover ? color_hover : color)) { g.FillEllipse(brush_dot, RectDot(rect, rect_read, prog, DotSize + LineSize)); } } g.FillEllipse(brush, RectDot(rect, rect_read, prog, DotSize)); } } internal void PaintMarksEllipse(Graphics g, Rectangle rect, RectangleF rect_read, SolidBrush brush, Color color, int LineSize) { if (marks != null && marks.Count > 0) { using (var fore = new SolidBrush(Style.Db.Text)) { int markTextGap = (int)(MarkTextGap * Config.Dpi); int size2 = LineSize, size = size2 * 2; foreach (var it in marks) { float uks = ProgValue(it.Value); if (!string.IsNullOrWhiteSpace(it.Text)) { if (it.Fore.HasValue) { using (var fore2 = new SolidBrush(it.Fore.Value)) { g.DrawStr(it.Text, Font, fore2, RectDotText(rect, rect_read, uks, markTextGap, g.MeasureString(it.Text, Font).Size()), s_f); } } else g.DrawStr(it.Text, Font, fore, RectDotText(rect, rect_read, uks, markTextGap, g.MeasureString(it.Text, Font).Size()), s_f); } using (var brush_dot = new SolidBrush(color)) { g.FillEllipse(brush_dot, RectDot(rect, rect_read, uks, size)); } g.FillEllipse(brush, RectDot(rect, rect_read, uks, size2)); } } } } #region 计算区域 internal float ProgValue(int val) { int max = _maxValue - _minValue; switch (align) { case TAlignMini.Top: case TAlignMini.Bottom: float h = rect_read.Height; return val >= _maxValue ? h : h * ((val - _minValue) * 1F / max); default: float w = rect_read.Width; return val >= _maxValue ? w : w * ((val - _minValue) * 1F / max); } } internal RectangleF RectLine(RectangleF rect, float prog) { switch (align) { case TAlignMini.Right: return new RectangleF(rect.X + rect.Width - prog, rect.Y, prog, rect.Height); case TAlignMini.Top: return new RectangleF(rect.X, rect.Y, rect.Width, prog); case TAlignMini.Bottom: return new RectangleF(rect.X, rect.Y + rect.Height - prog, rect.Width, prog); default: return new RectangleF(rect.X, rect.Y, prog, rect.Height); } } internal RectangleF RectDot(Rectangle rect, RectangleF rect_read, float prog, float size) { switch (align) { case TAlignMini.Right: return new RectangleF(rect_read.X + (rect_read.Width - prog - (size / 2F)), rect.Y + (rect.Height - size) / 2F, size, size); case TAlignMini.Top: return new RectangleF(rect.X + (rect.Width - size) / 2F, rect_read.Y + prog - size / 2F, size, size); case TAlignMini.Bottom: return new RectangleF(rect.X + (rect.Width - size) / 2F, rect_read.Y + (rect_read.Height - prog - (size / 2F)), size, size); default: return new RectangleF(rect_read.X + prog - size / 2F, rect.Y + (rect.Height - size) / 2F, size, size); } } internal RectangleF RectDotText(Rectangle rect, RectangleF rect_read, float prog, int gap, Size size) { switch (align) { case TAlignMini.Right: return new RectangleF(rect_read.X + (rect_read.Width - prog - size.Width / 2F), rect_read.Bottom + rect_read.Height + gap, size.Width, size.Height); case TAlignMini.Top: return new RectangleF(rect_read.Right + rect_read.Width + gap, rect_read.Y + prog - size.Height / 2F, size.Width, size.Height); case TAlignMini.Bottom: return new RectangleF(rect_read.Right + rect_read.Width + gap, rect_read.Y + (rect_read.Height - prog - size.Height / 2F), size.Width, size.Height); default: return new RectangleF(rect_read.X + prog - size.Width / 2F, rect_read.Bottom + rect_read.Height + gap, size.Width, size.Height); } } internal RectangleF RectDotH(Rectangle rect, Rectangle rect_read, float prog, int DotSize) { switch (align) { case TAlignMini.Right: return new RectangleF(rect_read.X + (rect_read.Width - prog - DotSize / 2F), rect.Y, DotSize, rect.Height); case TAlignMini.Top: return new RectangleF(rect.X, rect_read.Y + prog - DotSize / 2F, rect.Width, DotSize); case TAlignMini.Bottom: return new RectangleF(rect.X, rect_read.Y + (rect_read.Height - prog - DotSize / 2F), rect.Width, DotSize); default: return new RectangleF(rect_read.X + prog - DotSize / 2F, rect.Y, DotSize, rect.Height); } } #endregion #endregion #region 鼠标 protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { Value = FindIndex(e.X, e.Y, true); mouseFlat = true; } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (mouseFlat) { ExtraMouseDotHover = true; Value = FindIndex(e.X, e.Y, false); } else ExtraMouseDotHover = rectEllipse.Contains(e.X, e.Y); } internal int FindIndex(int x, int y, bool mark) { int max = _maxValue - _minValue; if (marks != null && marks.Count > 0) { if (Dots) { var rect = ClientRectangle; int DotSize = (int)(dotSize * Config.Dpi); var mark_list = new List(marks.Count); int i = 0; switch (align) { case TAlignMini.Right: foreach (var it in marks) mark_list.Add(rect_read.Width - (it.Value >= _maxValue ? rect_read.Width : rect_read.Width * ((it.Value - _minValue) * 1F / max))); i = FindNumber(x, mark_list); break; case TAlignMini.Top: foreach (var it in marks) mark_list.Add(it.Value >= _maxValue ? rect_read.Height : rect_read.Height * ((it.Value - _minValue) * 1F / max)); i = FindNumber(y, mark_list); break; case TAlignMini.Bottom: foreach (var it in marks) mark_list.Add(rect_read.Height - (it.Value >= _maxValue ? rect_read.Height : rect_read.Height * ((it.Value - _minValue) * 1F / max))); i = FindNumber(y, mark_list); break; default: foreach (var it in marks) mark_list.Add(it.Value >= _maxValue ? rect_read.Width : rect_read.Width * ((it.Value - _minValue) * 1F / max)); i = FindNumber(x, mark_list); break; } return marks[i].Value; } if (mark) { var rect = ClientRectangle; int DotSize = (int)(dotSize * Config.Dpi); foreach (var it in marks) { float uks = ProgValue(it.Value); var rect_dot = RectDotH(rect, rect_read, uks, DotSize); if (rect_dot.Contains(x, y)) return it.Value; } } } switch (align) { case TAlignMini.Right: float xr = 1F - ((x - rect_read.X) * 1.0F / rect_read.Width); if (xr > 0) return (int)Math.Round(xr * max) + _minValue; else return _minValue; case TAlignMini.Top: float yt = (y - rect_read.Y) * 1.0F / rect_read.Height; if (yt > 0) return (int)Math.Round(yt * max) + _minValue; else return _minValue; case TAlignMini.Bottom: float yb = 1F - ((y - rect_read.Y) * 1.0F / rect_read.Height); if (yb > 0) return (int)Math.Round(yb * max) + _minValue; else return _minValue; default: float xl = (x - rect_read.X) * 1.0F / rect_read.Width; if (xl > 0) return (int)Math.Round(xl * max) + _minValue; else return _minValue; } } internal int FindNumber(int target, List array) { int Index = 0; float Difference = int.MaxValue; for (int i = 0; i < array.Count; i++) { float difference = Math.Abs(target - array[i]); if (difference < Difference) { Difference = difference; Index = i; } } return Index; } bool mouseFlat = false; protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); mouseFlat = false; Invalidate(); } internal float AnimationHoverValue = 0F; internal bool AnimationHover = false; bool _mouseHover = false; internal bool ExtraMouseHover { get => _mouseHover; set { if (_mouseHover == value) return; _mouseHover = value; var enabled = Enabled; SetCursor(value && enabled); if (Config.Animation) { ThreadHover?.Dispose(); AnimationHover = true; if (value) { ThreadHover = new ITask(this, () => { AnimationHoverValue = AnimationHoverValue.Calculate(0.1F); if (AnimationHoverValue > 1) { AnimationHoverValue = 1; return false; } Invalidate(); return true; }, 10, () => { AnimationHover = false; Invalidate(); }); } else { ThreadHover = new ITask(this, () => { AnimationHoverValue = AnimationHoverValue.Calculate(-0.1F); if (AnimationHoverValue <= 0) { AnimationHoverValue = 0F; return false; } Invalidate(); return true; }, 10, () => { AnimationHover = false; Invalidate(); }); } } Invalidate(); } } internal float AnimationDotHoverValue = 0F; internal bool AnimationDotHover = false; bool _mouseDotHover = false; internal bool ExtraMouseDotHover { get => _mouseDotHover; set { if (_mouseDotHover == value) return; _mouseDotHover = value; if (!value) CloseTips(); if (Config.Animation) { ThreadHover?.Dispose(); ThreadHover = null; ThreadDotHover?.Dispose(); AnimationDotHover = true; if (value) { ThreadDotHover = new ITask(this, () => { AnimationDotHoverValue = AnimationDotHoverValue.Calculate(0.1F); if (AnimationDotHoverValue > 1) { AnimationDotHoverValue = 1; return false; } Invalidate(); return true; }, 10, () => { AnimationDotHover = false; Invalidate(); }); } else { ThreadDotHover = new ITask(this, () => { AnimationDotHoverValue = AnimationDotHoverValue.Calculate(-0.1F); if (AnimationDotHoverValue <= 0) { AnimationDotHoverValue = 0F; return false; } Invalidate(); return true; }, 10, () => { AnimationDotHover = false; Invalidate(); }); } } else Invalidate(); } } #region 动画 protected override void Dispose(bool disposing) { ThreadHover?.Dispose(); ThreadDotHover?.Dispose(); base.Dispose(disposing); } internal ITask? ThreadHover = null; ITask? ThreadDotHover = null; #endregion protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); ExtraMouseHover = true; } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); CloseTips(); ExtraMouseHover = ExtraMouseDotHover = false; } protected override void OnLeave(EventArgs e) { base.OnLeave(e); CloseTips(); ExtraMouseHover = ExtraMouseDotHover = false; } #endregion } public class SliderMarkItemCollection : iCollection { public SliderMarkItemCollection(IControl it) { BindData(it); } internal SliderMarkItemCollection BindData(IControl it) { action = render => { it.Invalidate(); }; return this; } } public class SliderMarkItem { int _value = 0; /// /// 文本 /// [Description("值"), Category("外观"), DefaultValue(0)] public int Value { get => _value; set { if (_value == value) return; _value = value; Invalidates(); } } Color? fore = null; /// /// 文本颜色 /// [Description("文本颜色"), Category("外观"), DefaultValue(null)] public Color? Fore { get => fore; set { if (fore == value) return; fore = value; Invalidates(); } } 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 Slider? PARENT { get; set; } void Invalidates() { if (PARENT == null) return; PARENT.Invalidate(); } public override string ToString() => _value + " " + text; } }