// 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.Imaging; using System.Windows.Forms; namespace AntdUI { /// /// Rate 评分 /// /// 评分组件。 [Description("Rate 评分")] [ToolboxItem(true)] [DefaultProperty("Value")] [DefaultEvent("ValueChanged")] public class Rate : IControl { #region 属性 Color fill = Color.FromArgb(250, 219, 20); [Description("颜色"), Category("外观"), DefaultValue(typeof(Color), "250, 219, 20")] public Color Fill { get => fill; set { if (fill == value) return; fill = value; icon_active?.Dispose(); icon_active = null; Invalidate(); } } /// /// 支持清除 /// [Description("支持清除"), Category("行为"), DefaultValue(false)] public bool AllowClear { get; set; } /// /// 是否允许半选 /// [Description("是否允许半选"), Category("行为"), DefaultValue(false)] public bool AllowHalf { get; set; } = false; int count = 5; /// /// Star 总数 /// [Description("Star 总数"), Category("外观"), DefaultValue(5)] public int Count { get => count; set { if (count == value) return; count = value; OnSizeChanged(EventArgs.Empty); Invalidate(); } } float _value = 0; /// /// 当前值 /// [Description("当前值"), Category("数据"), DefaultValue(0F)] public float Value { get => _value; set { if (_value == value) return; _value = value; if (rect_stars.Length > 0) { int _value_ = (int)_value; for (int i = 0; i < rect_stars.Length; i++) { bool _active = _value_ > i, _active_ = _value > i; rect_stars[i].Animatio(_active_, rect_stars[i].hover, _active_ && !_active); } } Invalidate(); ValueChanged?.Invoke(this, new FloatEventArgs(_value)); } } /// /// Value 属性值更改时发生 /// [Description("Value 属性值更改时发生"), Category("行为")] public event FloatEventHandler? ValueChanged; /// /// 自定义每项的提示信息 /// [Description("自定义每项的提示信息"), Category("数据"), DefaultValue(null)] public string[]? Tooltips { get; set; } string? character = null; /// /// 自定义字符 /// [Description("自定义字符"), Category("外观"), DefaultValue(null)] public string? Character { get => character; set { if (character == value) return; character = value; icon?.Dispose(); icon_active?.Dispose(); icon = icon_active = null; Invalidate(); } } #endregion #region 渲染 Bitmap? icon = null, icon_active = null; protected override void OnPaint(PaintEventArgs e) { var rect = ClientRectangle.PaddingRect(Padding); if (rect.Width == 0 || rect.Height == 0 || count < 1) return; int size = rect.Height; var g = e.Graphics.High(); #region 初始化位图 if (icon == null || icon.Width != size) { icon?.Dispose(); icon = SvgExtend.SvgToBmp(character ?? SvgDb.IcoStar, size, size, Style.Db.FillSecondary); } if (icon_active == null || icon_active.Width != size) { icon_active?.Dispose(); icon_active = SvgExtend.SvgToBmp(character ?? SvgDb.IcoStar, size, size, fill); } if (icon == null || icon_active == null) { icon = new Bitmap(size, size); icon_active = new Bitmap(size, size); using (var font = new Font(Font.FontFamily, size, Font.Style)) { var font_size = g.MeasureString(character, font).Size(); int bmp_size = (font_size.Width > font_size.Height ? font_size.Width : font_size.Height); using (var bmp_diy = new Bitmap(bmp_size, bmp_size)) using (var bmp_diy_active = new Bitmap(bmp_size, bmp_size)) { Rectangle rect_diy = new Rectangle(0, 0, bmp_size, bmp_size), rect_icon = new Rectangle(0, 0, size, size); using (var s_f = Helper.SF()) { using (var g2 = Graphics.FromImage(bmp_diy).HighLay()) { using (var brush = new SolidBrush(Style.Db.FillSecondary)) { g2.DrawStr(character, font, brush, rect_diy, s_f); } } using (var g2 = Graphics.FromImage(bmp_diy_active).HighLay()) { using (var brush = new SolidBrush(fill)) { g2.DrawStr(character, font, brush, rect_diy, s_f); } } } using (var g2 = Graphics.FromImage(icon).High()) { g2.DrawImage(bmp_diy, rect_icon); } using (var g2 = Graphics.FromImage(icon_active).High()) { g2.DrawImage(bmp_diy_active, rect_icon); } } } } #endregion for (int i = 0; i < rect_stars.Length; i++) { var it = rect_stars[i]; if (it.AnimationActive) { int readvalue = (int)((it.rect.Width - it.rect_i.Width) * it.AnimationActiveValueS), readsize = it.rect_i.Width + readvalue, readsize2 = readvalue / 2; var rect_ = new Rectangle(it.rect_i.X - readsize2, it.rect_i.Y - readsize2, readsize, readsize); g.DrawImage(icon, rect_); using (var attributes = new ImageAttributes()) { var matrix = new ColorMatrix { Matrix33 = it.AnimationActiveValueO }; attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); if (it.half) g.DrawImage(icon_active, new Rectangle(rect_.X, rect_.Y, rect_.Width / 2, rect_.Height), 0, 0, icon_active.Width / 2, icon_active.Height, GraphicsUnit.Pixel, attributes); else g.DrawImage(icon_active, rect_, 0, 0, icon_active.Width, icon_active.Height, GraphicsUnit.Pixel, attributes); } } else if (it.hover) { if (it.half) { g.DrawImage(icon, it.rect); g.DrawImage(icon_active, new Rectangle(it.rect.X, it.rect.Y, it.rect.Width / 2, it.rect.Height), new Rectangle(0, 0, icon_active.Width / 2, icon_active.Height), GraphicsUnit.Pixel); } else g.DrawImage(icon_active, it.rect); } else if (it.active) { if (it.half) { g.DrawImage(icon, it.rect_i); g.DrawImage(icon_active, new Rectangle(it.rect_i.X, it.rect_i.Y, it.rect_i.Width / 2, it.rect_i.Height), new Rectangle(0, 0, icon_active.Width / 2, icon_active.Height), GraphicsUnit.Pixel); } else g.DrawImage(icon_active, it.rect_i); } else g.DrawImage(icon, it.rect_i); } } #endregion #region 坐标 RectStar[] rect_stars = new RectStar[0]; protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); var rect = ClientRectangle.PaddingRect(Padding); if (rect.Width == 0 || rect.Height == 0 || count < 1) return; int size = rect.Height, msize = size - (int)(size * 0.8F), msize2 = msize / 2, gap = (int)(8F * Config.Dpi), t_size = size + gap; var list = new List(count); int _value_ = (int)_value; for (int i = 0; i < count; i++) { bool _active = _value_ > i, _active_ = _value > i; var it = new RectStar(this, new Rectangle(rect.X + t_size * i, rect.Y, t_size, size), new Rectangle(rect.X + t_size * i, rect.Y, size, size), msize, msize2) { active = _active_, }; if (_active_ && !_active) it.half = true; list.Add(it); } rect_stars = list.ToArray(); if (autoSize) { if (InvokeRequired) { Invoke(new Action(() => { Width = list[list.Count - 1].rect.Right; })); } else Width = list[list.Count - 1].rect.Right; } } class RectStar { Rate rate; public RectStar(Rate _rate, Rectangle _rect_mouse, Rectangle _rect, int msize, int msize2) { rate = _rate; rect_mouse = _rect_mouse; rect = _rect; rect_i = new Rectangle(_rect.X + msize2, _rect.Y + msize2, _rect.Width - msize, _rect.Height - msize); } public Rectangle rect_mouse { get; set; } public Rectangle rect { get; set; } public Rectangle rect_i { get; set; } #region 动画 /// /// 是否移动 /// internal bool hover = false; /// /// 是否激活 /// internal bool active = false; internal bool half = false; internal float AnimationActiveValueO = 0; internal float AnimationActiveValueS = 0; internal bool AnimationActive = false; ITask? ThreadActive = null; internal bool Animatio(bool _active, bool _hover, bool _half) { if (active == _active && hover == _hover) { if (half != _half) half = _half; rate.Invalidate(); return false; } bool active_old = active, hover_old = hover; active = _active; hover = _hover; half = _half; if (Config.Animation) { ThreadActive?.Dispose(); AnimationActive = true; var t = Animation.TotalFrames(10, 100); if (active || hover) { if (active && hover) { ThreadActive = new ITask((i) => { AnimationActiveValueS = AnimationActiveValueO = Animation.Animate(i, t, 1F, AnimationType.Ball); rate.Invalidate(); return true; }, 10, t, () => { AnimationActive = false; AnimationActiveValueS = AnimationActiveValueO = 1; rate.Invalidate(); }); } else { if (active_old && hover_old) { //仅缩小 ThreadActive = new ITask((i) => { AnimationActiveValueS = 1F - Animation.Animate(i, t, 1F, AnimationType.Ball); rate.Invalidate(); return true; }, 10, t, () => { AnimationActive = false; AnimationActiveValueS = 0; AnimationActiveValueO = 1; rate.Invalidate(); }); } else { ThreadActive = new ITask((i) => { AnimationActiveValueO = Animation.Animate(i, t, 1F, AnimationType.Ball); rate.Invalidate(); return true; }, 10, t, () => { AnimationActive = false; AnimationActiveValueS = 0; AnimationActiveValueO = 1; rate.Invalidate(); }); } } } else { if (active_old && !hover_old) { //仅不透明 ThreadActive = new ITask((i) => { AnimationActiveValueO = 1F - Animation.Animate(i, t, 1F, AnimationType.Ball); rate.Invalidate(); return true; }, 10, t, () => { AnimationActive = false; AnimationActiveValueS = AnimationActiveValueO = 0; rate.Invalidate(); }); } else { ThreadActive = new ITask((i) => { AnimationActiveValueS = AnimationActiveValueO = 1F - Animation.Animate(i, t, 1F, AnimationType.Ball); rate.Invalidate(); return true; }, 10, t, () => { AnimationActive = false; AnimationActiveValueS = AnimationActiveValueO = 0; rate.Invalidate(); }); } } } else rate.Invalidate(); return true; } #endregion } #endregion #region 自动大小 bool autoSize = false; [Browsable(true)] [Description("自动宽度"), Category("外观"), DefaultValue(false)] public override bool AutoSize { get => autoSize; set { if (autoSize == value) return; autoSize = value; if (value) OnSizeChanged(EventArgs.Empty); } } #endregion #region 鼠标 #region 提示 TooltipForm? tooltipForm = null; string? tooltipText = null; void ShowTips(RectangleF dot_rect, string text) { 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 = TAlign.Top, }); tooltipForm.Show(this); } else { tooltipForm.SetText(rect, tooltipText); } } void CloseTips() { tooltipForm?.IClose(); tooltipForm = null; } #endregion protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (setvalue) { setvalue = false; return; } for (int i = 0; i < rect_stars.Length; i++) { var it = rect_stars[i]; bool hover = it.rect_mouse.Contains(e.Location); if (hover) { bool half = false; if (AllowHalf) half = new Rectangle(it.rect.X, it.rect.Y, it.rect.Width / 2, it.rect.Height).Contains(e.Location); it.Animatio(true, true, half); for (int j = 0; j < rect_stars.Length; j++) { if (i != j) rect_stars[j].Animatio(j < i, false, false); } if (Tooltips != null && Tooltips.Length > i) ShowTips(it.rect, Tooltips[i]); else CloseTips(); return; } } //全部都没激活 _Leave(); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); _Leave(); } void _Leave() { CloseTips(); int _value_ = (int)_value; for (int i = 0; i < rect_stars.Length; i++) { bool _active = _value_ > i, _active_ = _value > i; rect_stars[i].Animatio(_active_, false, _active_ && !_active); } } bool setvalue = false; protected override void OnMouseClick(MouseEventArgs e) { if (e.Button == MouseButtons.Left) { for (int i = 0; i < rect_stars.Length; i++) { if (rect_stars[i].rect_mouse.Contains(e.Location)) { float old = AllowClear ? _value : -10; var it = rect_stars[i]; if (AllowHalf) { if (new Rectangle(it.rect.X, it.rect.Y, it.rect.Width / 2, it.rect.Height).Contains(e.Location)) { float valuef = i + 0.5F; if (valuef == old) { Value = 0; setvalue = true; _Leave(); } else Value = valuef; return; } } int value = i + 1; if (value == old) { Value = 0; setvalue = true; _Leave(); } else Value = value; return; } } } base.OnMouseClick(e); } #endregion protected override void Dispose(bool disposing) { icon?.Dispose(); icon_active?.Dispose(); base.Dispose(disposing); } } }