// 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.Drawing.Imaging; using System.Linq; using System.Threading; using System.Windows.Forms; namespace AntdUI { /// /// VirtualPanel 虚拟容器 /// [Description("VirtualPanel 虚拟容器")] [ToolboxItem(true)] [DefaultProperty("Items")] [DefaultEvent("ItemClick")] public class VirtualPanel : IControl, IEventListener { public VirtualPanel() { ScrollBar = new ScrollBar(this); new Thread(LongTask) { IsBackground = true }.Start(); } #region 属性 #region 数据 VirtualCollection? items; /// /// 数据 /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Description("集合"), Category("数据")] public VirtualCollection Items { get { items ??= new VirtualCollection(this); return items; } set => items = value.BindData(this); } #endregion int radius = 6; /// /// 圆角 /// [Description("圆角"), Category("外观"), DefaultValue(6)] public int Radius { get => radius; set { if (radius == value) return; radius = value; Invalidate(); } } #region 阴影 int shadow = 0; /// /// 阴影大小 /// [Description("阴影"), Category("外观"), DefaultValue(0)] public int Shadow { get => shadow; set { if (shadow == value) return; shadow = value; DisposeShadow(); LoadLayout(); } } Color? shadowColor; /// /// 阴影颜色 /// [Description("阴影颜色"), Category("阴影"), DefaultValue(null)] [Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))] public Color? ShadowColor { get => shadowColor; set { if (shadowColor == value) return; shadowColor = value; DisposeShadow(); LoadLayout(); } } int shadowOffsetX = 0; /// /// 阴影偏移X /// [Description("阴影偏移X"), Category("阴影"), DefaultValue(0)] public int ShadowOffsetX { get => shadowOffsetX; set { if (shadowOffsetX == value) return; shadowOffsetX = value; DisposeShadow(); LoadLayout(); } } int shadowOffsetY = 0; /// /// 阴影偏移Y /// [Description("阴影偏移Y"), Category("阴影"), DefaultValue(0)] public int ShadowOffsetY { get => shadowOffsetY; set { if (shadowOffsetY == value) return; shadowOffsetY = value; DisposeShadow(); LoadLayout(); } } float shadowOpacity = 0.1F; /// /// 阴影透明度 /// [Description("阴影透明度"), Category("阴影"), DefaultValue(0.1F)] public float ShadowOpacity { get => shadowOpacity; set { if (shadowOpacity == value) return; if (value < 0) value = 0; else if (value > 1) value = 1; shadowOpacity = value; Invalidate(); } } /// /// 阴影透明度动画使能 /// [Description("阴影透明度动画使能"), Category("阴影"), DefaultValue(false)] public bool ShadowOpacityAnimation { get; set; } float shadowOpacityHover = 0.3F; /// /// 悬停阴影后透明度 /// [Description("悬停阴影后透明度"), Category("阴影"), DefaultValue(0.3F)] public float ShadowOpacityHover { get => shadowOpacityHover; set { if (shadowOpacityHover == value) return; if (value < 0) value = 0; else if (value > 1) value = 1; shadowOpacityHover = value; Invalidate(); } } TAlignMini shadowAlign = TAlignMini.None; [Description("阴影方向"), Category("阴影"), DefaultValue(TAlignMini.None)] public TAlignMini ShadowAlign { get => shadowAlign; set { if (shadowAlign == value) return; shadowAlign = value; DisposeShadow(); LoadLayout(); } } void DisposeShadow() { if (items == null || items.Count == 0) return; if (shadow_dir_tmp.Count == 0) return; lock (shadow_dir_tmp) { foreach (var item in shadow_dir_tmp) item.Value.Dispose(); shadow_dir_tmp.Clear(); } } #endregion int gap = 0; /// /// 间距 /// [Description("间距"), Category("外观"), DefaultValue(0)] public int Gap { get => gap; set { if (gap == value) return; gap = value; LoadLayout(); Invalidate(); } } #region 为空 bool isEmpty = false; [Description("是否显示空样式"), Category("外观"), DefaultValue(false)] public bool Empty { get; set; } = false; string? emptyText; [Description("数据为空显示文字"), Category("外观"), DefaultValue(null)] public string? EmptyText { get => emptyText; set { if (emptyText == value) return; emptyText = value; Invalidate(); } } [Description("数据为空显示图片"), Category("外观"), DefaultValue(null)] public Image? EmptyImage { get; set; } #endregion #region 布局 bool wrap = true; /// /// 换行 /// [Description("换行"), Category("布局"), DefaultValue(true)] public bool Wrap { get => wrap; set { if (wrap == value) return; wrap = value; LoadLayout(); Invalidate(); } } bool waterfall = false; /// /// 瀑布流 /// [Description("瀑布流"), Category("布局"), DefaultValue(false)] public bool Waterfall { get => waterfall; set { if (waterfall == value) return; waterfall = value; LoadLayout(); Invalidate(); } } TAlignItems alignitems = TAlignItems.Start; /// /// 侧轴(纵轴)对齐方式 /// [Description("侧轴(纵轴)对齐方式"), Category("布局"), DefaultValue(TAlignItems.Start)] public TAlignItems AlignItems { get => alignitems; set { if (alignitems == value) return; alignitems = value; LoadLayout(); Invalidate(); } } TJustifyContent justifycontent = TJustifyContent.Start; /// /// 主轴(横轴)对齐方式 /// [Description("主轴(横轴)对齐方式"), Category("布局"), DefaultValue(TJustifyContent.Start)] public TJustifyContent JustifyContent { get => justifycontent; set { if (justifycontent == value) return; justifycontent = value; LoadLayout(); Invalidate(); } } TAlignContent aligncontent = TAlignContent.Start; /// /// 没有占用交叉轴上所有可用的空间时对齐容器内的各项(垂直) /// [Description("没有占用交叉轴上所有可用的空间时对齐容器内的各项(垂直)"), Category("布局"), DefaultValue(TAlignContent.Start)] public TAlignContent AlignContent { get => aligncontent; set { if (aligncontent == value) return; aligncontent = value; LoadLayout(); Invalidate(); } } #endregion bool pauseLayout = false; [Browsable(false), Description("暂停布局"), Category("行为"), DefaultValue(false)] public bool PauseLayout { get => pauseLayout; set { if (pauseLayout == value) return; pauseLayout = value; if (!value) { LoadLayout(); Invalidate(); } } } /// /// 滚动条 /// [Browsable(false)] public ScrollBar ScrollBar; #endregion #region 布局 protected override void OnSizeChanged(EventArgs e) { CellCount = -1; LoadLayout(); base.OnSizeChanged(e); } public void LoadLayout() { if (IsHandleCreated) { if (items == null || items.Count == 0) { ScrollBar.Value = 0; return; } if (pauseLayout) return; var controls = new List(items.Count); foreach (var it in items) { it.SHOW = false; if (it.Visible) controls.Add(it); } if (controls.Count > 0) { isEmpty = false; int val = HandLayout(controls); ScrollBar.SetVrSize(val); } else isEmpty = true; } } internal int CellCount = -1; int HandLayout(List items) { var _rect = ClientRectangle; if (_rect.Width == 0 || _rect.Height == 0) return 0; ScrollBar.SizeChange(_rect); var rect = _rect.PaddingRect(Padding); return Helper.GDI(g => { int gap = (int)Math.Round(Gap * Config.Dpi), use_x = rect.X, use_y = rect.Y + gap, last_len = 0, max_height = 0; int shadow = (int)(Shadow * Config.Dpi), shadow2 = shadow * 2, r = (int)(radius * Config.Dpi); var rows = new List(); var tmps = new List(items.Count); foreach (var it in items) { var size = it.Size(g, new VirtualPanelArgs(this, rect, r)); it.WIDTH = size.Width; it.HEIGHT = size.Height; } if (waterfall) { if (CellCount == -1) { var counts = new List(items.Count); int count = 0; foreach (var it in items) { if (use_x + it.WIDTH >= rect.Width) { use_x = rect.X; use_y += max_height + gap; if (count > 0) counts.Add(count); count = 0; } use_x += it.WIDTH + gap; count++; } use_x = rect.X; use_y = rect.Y + gap; if (counts.Count > 0) CellCount = counts.Max(); } } foreach (var it in items) { if (tmps.Count > 0 && use_x + it.WIDTH >= rect.Width) { rows.Add(new RItem(use_x, use_y, max_height, tmps, true)); tmps.Clear(); use_x = rect.X; use_y += max_height + gap; max_height = 0; } if (max_height < it.HEIGHT) max_height = it.HEIGHT; if (it is VirtualShadowItem virtualShadow) { it.RECT.Width = it.WIDTH - shadow2; it.RECT.Height = it.HEIGHT - shadow2; it.RECT.X = use_x + shadow; it.RECT.Y = use_y + shadow; virtualShadow.RECT_S.Width = it.WIDTH; virtualShadow.RECT_S.Height = it.HEIGHT; virtualShadow.RECT_S.X = use_x; virtualShadow.RECT_S.Y = use_y; } else { it.RECT.Width = it.WIDTH; it.RECT.Height = it.HEIGHT; it.RECT.X = use_x; it.RECT.Y = use_y; } use_x += it.WIDTH + gap; last_len = use_y + it.HEIGHT + gap; it.SHOW = true; tmps.Add(it); } if (tmps.Count > 0) { rows.Add(new RItem(use_x, use_y, max_height, tmps)); last_len = use_y + max_height + gap; } tmps.Clear(); #region 布局 if (last_len > rect.Height) rect.Height = last_len; switch (justifycontent) { case TJustifyContent.Start: break; case TJustifyContent.End: foreach (var row in rows) { int x = (rect.Width - row.use_x + gap); HandLayout(rect, row.cel, x, 0); } break; case TJustifyContent.SpaceBetween: foreach (var row in rows) { if (row.cel.Count > 1) { int totalCount = row.cel.Count, totalWidth = 0; if (CellCount == -1 || CellCount <= row.cel.Count) totalWidth = row.cel.Sum(a => a.RECT.Width); else { totalCount = CellCount; totalWidth = row.cel.Sum(a => a.RECT.Width) + (CellCount - row.cel.Count) * row.cel[0].RECT.Width; } int sp = (rect.Width - totalWidth) / (totalCount - 1); int ux = rect.X; foreach (var item in row.cel) { HandLayout(rect, item, ux - item.RECT.X, 0); ux += item.RECT.Width + sp; } } } break; case TJustifyContent.SpaceEvenly: if (waterfall) { foreach (var row in rows) { int totalCount = row.cel.Count, totalWidth = 0; if (CellCount == -1 || CellCount <= row.cel.Count) totalWidth = row.cel.Sum(a => a.RECT.Width); else { totalCount = CellCount; totalWidth = row.cel.Sum(a => a.RECT.Width) + (CellCount - row.cel.Count) * row.cel[0].RECT.Width; } int sp = (rect.Width - totalWidth) / (totalCount + 1), ux = rect.X + sp; foreach (var item in row.cel) { HandLayout(rect, item, ux - item.RECT.X, 0); ux += item.RECT.Width + sp; } } } else { foreach (var row in rows) { if (row.cel.Count > 1) { int totalCount = row.cel.Count, totalWidth = totalWidth = row.cel.Sum(a => a.RECT.Width); int sp = (rect.Width - totalWidth) / (totalCount + 1), ux = rect.X + sp; foreach (var item in row.cel) { HandLayout(rect, item, ux - item.RECT.X, 0); ux += item.RECT.Width + sp; } } else { int x = (rect.Width - row.use_x + gap) / 2; HandLayout(rect, row.cel, x, 0); } } } break; case TJustifyContent.SpaceAround: foreach (var row in rows) { if (row.cel.Count > 1) { int totalWidth = row.cel.Sum(a => a.RECT.Width); int availableSpace = rect.Width - totalWidth, spaceBetweenItems = availableSpace / row.cel.Count, spaceAroundItems = availableSpace / (row.cel.Count + 1), ux = rect.X + spaceAroundItems / 2; foreach (var item in row.cel) { HandLayout(rect, item, ux - item.RECT.X, 0); ux += item.RECT.Width + spaceBetweenItems; } } else { int x = (rect.Width - row.use_x + gap) / 2; HandLayout(rect, row.cel, x, 0); } } break; case TJustifyContent.Center: default: foreach (var row in rows) { int x = (rect.Width - row.use_x + gap) / 2; HandLayout(rect, row.cel, x, 0); } break; } switch (aligncontent) { case TAlignContent.Start: break; case TAlignContent.End: int yEnd = rect.Height - GetTotalHeight(rows); foreach (var row in rows) HandLayout(rect, row.cel, 0, yEnd); break; case TAlignContent.SpaceBetween: if (rows.Count > 1) { int totalHeight = GetTotalHeight(rows); int sp = (rect.Height - totalHeight) / (rows.Count - 1); int uy = rect.Y; foreach (var row in rows) { foreach (var item in row.cel) HandLayout(rect, item, 0, uy - item.RECT.Y); uy += row.h + sp; } } break; case TAlignContent.SpaceEvenly: if (rows.Count > 1) { int totalHeight = GetTotalHeight(rows); int sp = (rect.Height - totalHeight) / (rows.Count + 1), uy = rect.Y + sp; foreach (var row in rows) { foreach (var item in row.cel) HandLayout(rect, item, 0, uy - item.RECT.Y); uy += row.h + sp; } } else { int yCenter2 = (rect.Height - GetTotalHeight(rows)) / 2; foreach (var row in rows) HandLayout(rect, row.cel, 0, yCenter2); } break; case TAlignContent.SpaceAround: if (rows.Count > 1) { int Height = GetTotalHeight(rows); int availableSpace = rect.Height - Height, spaceBetweenItems = availableSpace / rows.Count, spaceAroundItems = availableSpace / (rows.Count + 1), uy = rect.Y + spaceAroundItems / 2; foreach (var row in rows) { foreach (var item in row.cel) HandLayout(rect, item, 0, uy - item.RECT.Y); uy += row.h + spaceBetweenItems; } } else { int yCenter2 = (rect.Height - GetTotalHeight(rows)) / 2; foreach (var row in rows) HandLayout(rect, row.cel, 0, yCenter2); } break; case TAlignContent.Center: default: int yCenter = (rect.Height - GetTotalHeight(rows)) / 2; foreach (var row in rows) HandLayout(rect, row.cel, 0, yCenter); break; } if (waterfall) last_len = WaterfallLayout(rect, rows); else { switch (alignitems) { case TAlignItems.Start: break; case TAlignItems.End: foreach (var row in rows) { foreach (var cel in row.cel) { int y = row.h - cel.RECT.Height; HandLayout(rect, cel, 0, y); } } break; case TAlignItems.Center: default: foreach (var row in rows) { foreach (var cel in row.cel) { int y = (row.h - cel.RECT.Height) / 2; HandLayout(rect, cel, 0, y); } } break; } } #endregion return last_len + gap * 2; }); } #region 瀑布流 int WaterfallLayout(Rectangle rect, List rows) { switch (justifycontent) { case TJustifyContent.Start: WaterfallLayoutStart(rect, rows); break; case TJustifyContent.End: WaterfallLayoutEnd(rect, rows); break; case TJustifyContent.Center: default: WaterfallLayoutCenter(rect, rows); break; } int last_h = 0; foreach (var row in rows) { foreach (var item in row.cel) { if (item is VirtualShadowItem shadowItem) { if (last_h < shadowItem.RECT_S.Bottom) last_h = shadowItem.RECT_S.Bottom; } else if (last_h < item.RECT.Bottom) last_h = item.RECT.Bottom; } } return last_h + Padding.Bottom + gap; } void WaterfallLayoutStart(Rectangle rect, List rows) { var celdir = new Dictionary(rows[0].cel.Count); for (int i = 1; i < rows.Count; i++) { RItem row_new = rows[i], row_old = rows[i - 1]; if (row_old.cel.Count >= row_new.cel.Count) { for (int j = 0; j < row_new.cel.Count; j++) { VirtualItem item_new = row_new.cel[j], item_old = row_old.cel[j]; if (item_old.HEIGHT < row_old.h) { int xc = row_old.h - item_old.HEIGHT; if (celdir.ContainsKey(j)) celdir[j] += xc; else celdir.Add(j, xc); } if (celdir.TryGetValue(j, out int tmpY)) HandLayout(rect, item_new, 0, -tmpY); } } } } void WaterfallLayoutEnd(Rectangle rect, List rows) { var celdir = new Dictionary(rows[0].cel.Count); for (int i = 1; i < rows.Count; i++) { RItem row_new = rows[i], row_old = rows[i - 1]; if (row_old.cel.Count == row_new.cel.Count) { for (int j = 0; j < row_new.cel.Count; j++) { VirtualItem item_new = row_new.cel[j], item_old = row_old.cel[j]; if (item_old.HEIGHT < row_old.h) { int xc = row_old.h - item_old.HEIGHT; if (celdir.ContainsKey(j)) celdir[j] += xc; else celdir.Add(j, xc); } if (celdir.TryGetValue(j, out int tmpY)) HandLayout(rect, item_new, 0, -tmpY); } } else if (row_old.cel.Count > row_new.cel.Count) { for (int j = 0; j < row_new.cel.Count; j++) { int rj = row_old.cel.Count - row_new.cel.Count + j; VirtualItem item_new = row_new.cel[j], item_old = row_old.cel[rj]; if (item_old.HEIGHT < row_old.h) { int xc = row_old.h - item_old.HEIGHT; if (celdir.ContainsKey(rj)) celdir[rj] += xc; else celdir.Add(rj, xc); } if (celdir.TryGetValue(rj, out int tmpY)) HandLayout(rect, item_new, 0, -tmpY); } } } } void WaterfallLayoutCenter(Rectangle rect, List rows) { var celdir = new Dictionary(rows[0].cel.Count); for (int i = 1; i < rows.Count; i++) { RItem row_new = rows[i], row_old = rows[i - 1]; if (row_old.cel.Count == row_new.cel.Count) { for (int j = 0; j < row_new.cel.Count; j++) { VirtualItem item_new = row_new.cel[j], item_old = row_old.cel[j]; if (item_old.HEIGHT < row_old.h) { int xc = row_old.h - item_old.HEIGHT; if (celdir.ContainsKey(j)) celdir[j] += xc; else celdir.Add(j, xc); } if (celdir.TryGetValue(j, out int tmpY)) HandLayout(rect, item_new, 0, -tmpY); } } else if (row_old.cel.Count > row_new.cel.Count) { #region 挑选最短的插入 var hasi = new List(row_new.cel.Count); foreach (var item_new in row_new.cel) { int y = -1, rj = 0; for (int j = 0; j < row_old.cel.Count; j++) { if (hasi.Contains(j)) continue; if (row_old.cel[j].RECT.Y < y || y == -1) { rj = j; y = row_old.cel[j].RECT.Y; } } hasi.Add(rj); var item_old = row_old.cel[rj]; if (item_old.HEIGHT < row_old.h) { int xc = row_old.h - item_old.HEIGHT; if (celdir.ContainsKey(rj)) celdir[rj] += xc; else celdir.Add(rj, xc); } if (celdir.TryGetValue(rj, out int tmpY)) { HandLayout(rect, item_new, item_old.RECT.X - item_new.RECT.X, -tmpY); } } #endregion } } } #endregion int GetTotalHeight(List rows) { int totalHeight = 0; foreach (var row in rows) totalHeight += row.h; return totalHeight; } void HandLayout(Rectangle rect, List d, int x, int y) { if (x == 0 && y == 0) return; foreach (var item in d) { if (item.WIDTH == rect.Width) { if (y != 0) { if (item is VirtualShadowItem virtualShadow2) virtualShadow2.RECT_S.Offset(0, y); item.RECT.Offset(0, y); } } else { if (item is VirtualShadowItem virtualShadow2) virtualShadow2.RECT_S.Offset(x, y); item.RECT.Offset(x, y); } } } void HandLayout(Rectangle rect, VirtualItem d, int x, int y) { if (d.WIDTH == rect.Width) x = 0; if (x == 0 && y == 0) return; if (d is VirtualShadowItem virtualShadow2) virtualShadow2.RECT_S.Offset(x, y); d.RECT.Offset(x, y); } class RItem { public RItem(int usex, int usey, int height, List cell, bool _wrap = false) { use_x = usex; use_y = usey; h = height; cel = new List(cell); wrap = _wrap; } public bool wrap { get; set; } public int use_x { get; set; } public int use_y { get; set; } public int h { get; set; } public List cel { get; set; } } #endregion #region 渲染 protected override void OnPaint(PaintEventArgs e) { if (items == null || items.Count == 0 || isEmpty) { if (Empty) PaintEmpty(e.Graphics.High(), ClientRectangle); return; } var g = e.Graphics.High(); int sy = ScrollBar.Value; var rect = ClientRectangle; rect.Offset(0, sy); g.TranslateTransform(0, -sy); int r = (int)(radius * Config.Dpi); foreach (var it in items) { if (it.SHOW) { it.SHOW_RECT = rect.Contains(rect.X, it.RECT.Y) || rect.Contains(rect.X, it.RECT.Bottom); if (it.SHOW_RECT) { if (it is VirtualShadowItem virtualShadow) DrawShadow(virtualShadow, g, r); it.Paint(g, new VirtualPanelArgs(this, it.RECT, r)); } } else it.SHOW_RECT = false; } g.ResetTransform(); ScrollBar.Paint(g); if (Config.Animation && BlurBar != null) _event.Set(); base.OnPaint(e); } StringFormat stringCenter = Helper.SF_NoWrap(); void PaintEmpty(Graphics g, Rectangle rect) { using (var fore = new SolidBrush(Style.Db.Text)) { string emptytext = EmptyText ?? Localization.Provider?.GetLocalizedString("NoData") ?? "暂无数据"; if (EmptyImage == null) g.DrawStr(emptytext, Font, fore, rect, stringCenter); else { int _gap = (int)(gap * Config.Dpi); var size = g.MeasureString(emptytext, Font); RectangleF rect_img = new RectangleF(rect.X + (rect.Width - EmptyImage.Width) / 2F, rect.Y + (rect.Height - EmptyImage.Height) / 2F - size.Height, EmptyImage.Width, EmptyImage.Height), rect_font = new RectangleF(rect.X, rect_img.Bottom + _gap, rect.Width, size.Height); g.DrawImage(EmptyImage, rect_img); g.DrawStr(emptytext, Font, fore, rect_font, stringCenter); } } } #region 模糊标题 public Control? BlurBar = null; ManualResetEvent _event = new ManualResetEvent(false); void LongTask() { while (true) { if (_event.Wait()) return; if (items != null && items.Count > 0 && BlurBar != null) { int sy = ScrollBar.Value; int BlurBarHeight = BlurBar.Height; if (sy > BlurBarHeight) { sy -= BlurBarHeight; var rect = ClientRectangle; var bmp = new Bitmap(rect.Width, BlurBarHeight); using (var g = Graphics.FromImage(bmp).HighLay()) { rect.Offset(0, sy); g.TranslateTransform(0, -sy); int r = (int)(radius * Config.Dpi); foreach (var it in items) { if (it.SHOW && it.SHOW_RECT) { if (it is VirtualShadowItem virtualShadow) DrawShadow(virtualShadow, g, r); it.Paint(g, new VirtualPanelArgs(this, it.RECT, r)); } } g.ResetTransform(); using (var brush = new SolidBrush(Color.FromArgb(45, BlurBar.BackColor))) { g.FillRectangle(brush, 0, 0, bmp.Width, bmp.Height); } } Helper.Blur(bmp, BlurBarHeight * 6); IBlurBar(BlurBar, bmp); } else IBlurBar(BlurBar, null); } else if (BlurBar != null) IBlurBar(BlurBar, null); _event.Reset(); } } void IBlurBar(Control BlurBar, Bitmap? bmp) { Invoke(new Action(() => { BlurBar.BackgroundImage?.Dispose(); BlurBar.BackgroundImage = bmp; })); } protected override void Dispose(bool disposing) { BlurBar = null; _event?.Dispose(); base.Dispose(disposing); } #endregion #region 主题变化 protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); this.AddListener(); } public void HandleEvent(EventType id, object? tag) { switch (id) { case EventType.THEME: if (Config.Animation && BlurBar != null) _event.Set(); break; } } #endregion Dictionary shadow_dir_tmp = new Dictionary(); /// /// 绘制阴影 /// void DrawShadow(VirtualShadowItem it, Graphics g, float radius) { if (shadow > 0) { string id = it.RECT_S.Width.ToString() + "_" + it.RECT_S.Height.ToString(); lock (shadow_dir_tmp) { if (!shadow_dir_tmp.ContainsKey(id)) { int shadow = (int)(Shadow * Config.Dpi); using (var path = new Rectangle(shadow, shadow, it.RECT.Width, it.RECT.Height).RoundPath(radius, shadowAlign)) { shadow_dir_tmp.Add(id, path.PaintShadow(it.RECT_S.Width, it.RECT_S.Height, shadowColor ?? Style.Db.TextBase, shadow)); } } if (shadow_dir_tmp.TryGetValue(id, out var shadow_temp)) { using (var attributes = new ImageAttributes()) { var matrix = new ColorMatrix(); if (it.AnimationHover) matrix.Matrix33 = it.AnimationHoverValue; else if (it.Hover) matrix.Matrix33 = shadowOpacityHover; else matrix.Matrix33 = shadowOpacity; attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); g.DrawImage(shadow_temp, new Rectangle(it.RECT_S.X + shadowOffsetX, it.RECT_S.Y + shadowOffsetY, it.RECT_S.Width, it.RECT_S.Height), 0, 0, shadow_temp.Width, shadow_temp.Height, GraphicsUnit.Pixel, attributes); } } } } } #endregion #region 鼠标 /// /// 点击项时发生 /// [Description("点击项时发生"), Category("行为")] public event VirtualItemEventHandler? ItemClick = null; VirtualItem? MDown = null; protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); MDown = null; if (ScrollBar.MouseDown(e.Location)) { if (items == null || items.Count == 0) return; OnTouchDown(e.X, e.Y); int x = e.X, y = e.Y + ScrollBar.Value; foreach (var it in items) { if (it.SHOW && it.SHOW_RECT && it.CanClick && it.RECT.Contains(x, y)) { MDown = it; return; } } } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (ScrollBar.MouseMove(e.Location) && OnTouchMove(e.X, e.Y)) { if (items == null || items.Count == 0) return; int x = e.X, y = e.Y + ScrollBar.Value; int count = 0, hand = 0; foreach (var it in items) { if (it.SHOW && it.SHOW_RECT && it.CanClick && it.RECT.Contains(x, y)) { hand++; if (!it.Hover) { it.Hover = true; count++; SetHover(it, true); } } else { if (it.Hover) { it.Hover = false; count++; SetHover(it, false); } } } if (count > 0) Invalidate(); SetCursor(hand > 0); } } void SetHover(VirtualItem it, bool value) { if (Enabled && ShadowOpacityAnimation && shadow > 0 && shadowOpacityHover > 0 && it is VirtualShadowItem virtualShadow && shadowOpacityHover > shadowOpacity) { if (Config.Animation) { virtualShadow.ThreadHover?.Dispose(); virtualShadow.AnimationHover = true; float addvalue = shadowOpacityHover / 12F; if (value) { virtualShadow.ThreadHover = new ITask(this, () => { virtualShadow.AnimationHoverValue = virtualShadow.AnimationHoverValue.Calculate(addvalue); if (virtualShadow.AnimationHoverValue >= shadowOpacityHover) { virtualShadow.AnimationHoverValue = shadowOpacityHover; return false; } Invalidate(); return true; }, 20, () => { virtualShadow.AnimationHover = false; Invalidate(); }); } else { virtualShadow.ThreadHover = new ITask(this, () => { virtualShadow.AnimationHoverValue = virtualShadow.AnimationHoverValue.Calculate(-addvalue); if (virtualShadow.AnimationHoverValue <= shadowOpacity) { virtualShadow.AnimationHoverValue = shadowOpacity; return false; } Invalidate(); return true; }, 20, () => { virtualShadow.AnimationHover = false; Invalidate(); }); } } else Invalidate(); } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (ScrollBar.MouseUp() && OnTouchUp()) { if (MDown != null) { int x = e.X, y = e.Y + ScrollBar.Value; if (MDown.RECT.Contains(x, y)) ItemClick?.Invoke(this, new VirtualItemEventArgs(MDown, e)); } } MDown = null; } 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() { if (items == null || items.Count == 0) return; int count = 0; foreach (var it in items) { if (it.Hover) { it.Hover = false; count++; SetHover(it, false); } } if (count > 0) Invalidate(); } protected override void OnMouseWheel(MouseEventArgs e) { ScrollBar.MouseWheel(e.Delta); base.OnMouseWheel(e); } protected override bool OnTouchScrollX(int value) => ScrollBar.MouseWheelX(value); protected override bool OnTouchScrollY(int value) => ScrollBar.MouseWheelY(value); #endregion } public class VirtualCollection : iCollection { public VirtualCollection(VirtualPanel it) { BindData(it); } internal VirtualCollection BindData(VirtualPanel it) { action = render => { if (render) { it.CellCount = -1; it.LoadLayout(); } it.Invalidate(); }; return this; } } public abstract class VirtualShadowItem : VirtualItem { internal Rectangle RECT_S; internal ITask? ThreadHover = null; internal float AnimationHoverValue = 0.1F; internal bool AnimationHover = false; } public abstract class VirtualItem { public bool Visible { get; set; } = true; public bool CanClick { get; set; } = true; public bool Hover { get; set; } public object? Tag { get; set; } public abstract Size Size(Graphics g, VirtualPanelArgs e); public abstract void Paint(Graphics g, VirtualPanelArgs e); internal bool SHOW = false; internal bool SHOW_RECT = false; internal Rectangle RECT; internal int WIDTH; internal int HEIGHT; } public class VirtualPanelArgs : EventArgs { public VirtualPanelArgs(VirtualPanel panel, Rectangle rect, int radius) { Panel = panel; Rect = rect; Radius = radius; } public VirtualPanel Panel { get; private set; } public Rectangle Rect { get; private set; } public int Radius { get; private set; } } }