// 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.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace AntdUI.Chat { /// /// MsgList 好友消息列表 /// /// 好友消息列表。 [Description("MsgList 好友消息列表")] [ToolboxItem(true)] public class MsgList : IControl { #region 属性 MsgItemCollection? items; /// /// 数据集合 /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Description("数据集合"), Category("数据")] public MsgItemCollection Items { get { items ??= new MsgItemCollection(this); return items; } set => items = value.BindData(this); } /// /// 滚动条 /// [Browsable(false)] public ScrollBar ScrollBar; #endregion #region 渲染 protected override void OnPaint(PaintEventArgs e) { if (items == null || items.Count == 0) return; var rect = ClientRectangle; if (rect.Width == 0 || rect.Height == 0) return; var g = e.Graphics.High(); int sy = ScrollBar.Value; g.TranslateTransform(0, -sy); using (var font_text = new Font(Font.FontFamily, Font.Size * 0.9F)) using (var font_time = new Font(Font.FontFamily, Font.Size * 0.82F)) { foreach (MsgItem it in items) PaintItem(g, it, rect, sy, font_text, font_time); } g.ResetTransform(); ScrollBar.Paint(g); base.OnPaint(e); } StringFormat SFBage = Helper.SF(); StringFormat SFL = Helper.SF_ALL(lr: StringAlignment.Near); StringFormat SFR = Helper.SF_ALL(lr: StringAlignment.Far); void PaintItem(Graphics g, MsgItem it, Rectangle rect, float sy, Font font_text, Font font_time) { it.show = it.Show && it.Visible && it.rect.Y > sy - rect.Height && it.rect.Bottom < ScrollBar.Value + ScrollBar.ReadSize + it.rect.Height; if (it.show) { if (it.Select) { using (var brush = new SolidBrush(Color.FromArgb(0, 153, 255))) { g.FillRectangle(brush, it.rect); } using (var brush = new SolidBrush(Color.White)) { try { g.DrawStr(it.Name, Font, brush, it.rect_name, SFL); g.DrawStr(it.Text, font_text, brush, it.rect_text, SFL); g.DrawStr(it.Time, font_time, brush, it.rect_time, SFR); } catch { } } } else { if (it.Hover) { using (var brush = new SolidBrush(Style.Db.FillTertiary)) { g.FillRectangle(brush, it.rect); } } using (var brush = new SolidBrush(ForeColor)) { try { g.DrawStr(it.Name, Font, brush, it.rect_name, SFL); g.DrawStr(it.Text, font_text, brush, it.rect_text, SFL); g.DrawStr(it.Time, font_time, brush, it.rect_time, SFR); } catch { } } } if (it.Icon != null) { g.PaintImg(it.rect_icon, it.Icon, TFit.Cover, 0, true); if (it.Count > 0) { if (it.Count > 99) { var badgesize = g.MeasureString("99+", font_time).Size(); var rect_badge = new Rectangle(it.rect_icon.Right - badgesize.Width + badgesize.Width / 3, it.rect_icon.Y - badgesize.Height / 3, badgesize.Width, badgesize.Height); using (var path = rect_badge.RoundPath(6 * Config.Dpi)) { g.FillPath(Brushes.Red, path); } g.DrawStr("99+", font_time, Brushes.White, rect_badge, SFBage); } else if (it.Count > 1) { var badgesize = g.MeasureString(it.Count.ToString(), font_time).Size(); int badge_size = badgesize.Width > badgesize.Height ? badgesize.Width : badgesize.Height, xy = badge_size / 3; var rect_badge = new Rectangle(it.rect_icon.Right - badge_size + xy, it.rect_icon.Y - xy, badge_size, badge_size); g.FillEllipse(Brushes.Red, rect_badge); g.DrawStr(it.Count.ToString(), font_time, Brushes.White, rect_badge, SFBage); } else { int badge_size = it.rect_time.Height / 2, xy = badge_size / 3; var rect_badge = new Rectangle(it.rect_icon.Right - badge_size + xy, it.rect_icon.Y - xy, badge_size, badge_size); g.FillEllipse(Brushes.Red, rect_badge); } } } } } public MsgList() { ScrollBar = new ScrollBar(this); } protected override void Dispose(bool disposing) { ScrollBar.Dispose(); base.Dispose(disposing); } #endregion #region 鼠标 protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (ScrollBar.MouseDown(e.Location)) { if (items == null || items.Count == 0) return; foreach (MsgItem it in Items) { if (it.Visible && it.Contains(e.Location, 0, ScrollBar.Value, out _)) { it.Select = true; // 触发ItemSelected事件 OnItemSelected(it); return; } } } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); ScrollBar.MouseUp(); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (ScrollBar.MouseMove(e.Location)) { if (items == null || items.Count == 0) return; int count = 0, hand = 0; foreach (MsgItem it in Items) { if (it.show) { if (it.Contains(e.Location, 0, ScrollBar.Value, out var change)) { hand++; } if (change) count++; } } SetCursor(hand > 0); if (count > 0) Invalidate(); } else ILeave(); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); ScrollBar.Leave(); ILeave(); } protected override void OnLeave(EventArgs e) { base.OnLeave(e); ScrollBar.Leave(); ILeave(); } void ILeave() { SetCursor(false); if (items == null || items.Count == 0) return; int count = 0; foreach (MsgItem it in Items) { if (it.Hover) count++; it.Hover = false; } if (count > 0) Invalidate(); } protected override void OnMouseWheel(MouseEventArgs e) { ScrollBar.MouseWheel(e.Delta); base.OnMouseWheel(e); } #endregion #region 事件 public event ItemSelectedEventHandler? ItemSelected; protected virtual void OnItemSelected(MsgItem selectedItem) { ItemSelected?.Invoke(this, new MsgItemEventArgs(selectedItem)); } #endregion #region 布局 protected override void OnFontChanged(EventArgs e) { var rect = ChangeList(); ScrollBar.SizeChange(rect); base.OnFontChanged(e); } protected override void OnSizeChanged(EventArgs e) { var rect = ChangeList(); ScrollBar.SizeChange(rect); base.OnSizeChanged(e); } internal Rectangle ChangeList() { var rect = ClientRectangle; if (items == null || items.Count == 0) return rect; if (rect.Width == 0 || rect.Height == 0) return rect; int y = 0; Helper.GDI(g => { var size = (int)Math.Ceiling(g.MeasureString(Config.NullText, Font).Height); int item_height = (int)Math.Ceiling(size * 3.856), gap = (int)Math.Round(item_height * 0.212), spilt = (int)Math.Round(gap * 0.478), gap_name = (int)Math.Round(gap * 0.304), gap_desc = (int)Math.Round(gap * 0.217), name_height = (int)Math.Round(item_height * 0.185), desc_height = (int)Math.Round(item_height * 0.157), image_size = item_height - gap * 2; using (var font_time = new Font(Font.FontFamily, Font.Size * 0.82F)) { foreach (MsgItem it in items) { it.PARENT = this; int time_width = 0; if (!string.IsNullOrEmpty(it.Time)) time_width = (int)Math.Ceiling(g.MeasureString(it.Time, font_time).Width); it.SetRect(new Rectangle(rect.X, rect.Y + y, rect.Width, item_height), time_width, gap, spilt, gap_name, gap_desc, image_size, name_height, desc_height); if (it.Visible) y += item_height; } } }); ScrollBar.SetVrSize(y); return rect; } #endregion } public class MsgItemCollection : iCollection { public MsgItemCollection(MsgList it) { BindData(it); } internal MsgItemCollection BindData(MsgList it) { action = render => { if (render) it.ChangeList(); it.Invalidate(); }; return this; } } public class MsgItem : NotifyProperty { public MsgItem() { } public MsgItem(string name) { _name = name; } public MsgItem(string name, Bitmap? icon) { _name = name; _icon = icon; } Image? _icon = null; /// /// 图标 /// [Description("图标"), Category("外观"), DefaultValue(null)] public Image? Icon { get => _icon; set { if (_icon == value) return; _icon = value; OnPropertyChanged("Icon"); } } string _name; /// /// 名称 /// [Description("名称"), Category("外观")] public string Name { get => _name; set { if (_name == value) return; _name = value; OnPropertyChanged("Name"); } } string? _text = null; /// /// 文本 /// [Description("文本"), Category("外观"), DefaultValue(null)] public string? Text { get => _text; set { if (_text == value) return; _text = value; OnPropertyChanged("Text"); } } int count = 0; /// /// 消息数量 /// [Description("消息数量"), Category("外观"), DefaultValue(0)] public int Count { get => count; set { if (count == value) return; count = value; OnPropertyChanged("Count"); } } string? time = null; /// /// 时间 /// [Description("时间"), Category("外观"), DefaultValue(null)] public string? Time { get => time; set { if (time == value) return; time = value; Invalidates(); } } bool visible = true; /// /// 是否显示 /// [Description("是否显示"), Category("外观"), DefaultValue(true)] public bool Visible { get => visible; set { if (visible == value) return; visible = value; Invalidates(); } } /// /// 用户定义数据 /// [Description("用户定义数据"), Category("数据"), DefaultValue(null)] public object? Tag { get; set; } void Invalidate() { PARENT?.Invalidate(); } void Invalidates() { if (PARENT == null) return; PARENT.ChangeList(); PARENT.Invalidate(); } internal bool show { get; set; } internal bool Show { get; set; } /// /// 是否移动 /// internal bool Hover = false; internal bool select = false; [Description("是否选中"), Category("外观"), DefaultValue(false)] public bool Select { get => select; set { if (select == value) return; select = value; if (value && PARENT != null) { foreach (MsgItem it in PARENT.Items) { if (it != this) it.select = false; } } Invalidate(); } } internal MsgList? PARENT { get; set; } internal void SetRect(Rectangle _rect, int time_width, int gap, int spilt, int gap_name, int gap_desc, int image_size, int name_height, int desc_height) { rect = _rect; int text_width = _rect.Width - image_size - gap - spilt * 2; rect_icon = new Rectangle(_rect.X + gap, _rect.Y + gap, image_size, image_size); rect_name = new Rectangle(rect_icon.Right + spilt, rect_icon.Y + gap_name - gap_desc, text_width - time_width, name_height + gap_desc * 2); rect_time = new Rectangle(rect_name.Right, rect_name.Y, time_width, rect_name.Height); rect_text = new Rectangle(rect_name.X, rect_icon.Bottom - gap_desc - desc_height - gap_desc, text_width, desc_height + gap_desc * 2); Show = true; } internal Rectangle rect { get; set; } internal bool Contains(Point point, int x, int y, out bool change) { if (rect.Contains(new Point(point.X + x, point.Y + y))) { change = SetHover(true); return true; } else { change = SetHover(false); return false; } } internal bool SetHover(bool val) { bool change = false; if (val) { if (!Hover) change = true; Hover = true; } else { if (Hover) change = true; Hover = false; } return change; } internal Rectangle rect_name { get; set; } internal Rectangle rect_time { get; set; } internal Rectangle rect_text { get; set; } internal Rectangle rect_icon { get; set; } } }