// 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.Drawing2D; using System.Threading; using System.Windows.Forms; namespace AntdUI { internal class LayeredFormPopover : ILayeredFormOpacity { Popover.Config config; public override bool MessageEnable => true; public override bool MessageCloseSub => true; Form? form = null; public LayeredFormPopover(Popover.Config _config) { maxalpha = 255; config = _config; config.Control.SetTopMost(Handle); Font = config.Font ?? config.Control.Font; Helper.GDI(g => { var dpi = Config.Dpi; int sp = (int)Math.Round(8F * dpi), padding = (int)Math.Round(16 * dpi), padding2 = padding * 2; Padding = new Padding(padding); if (config.Content is Control control) { control.BackColor = Style.Db.BgElevated; control.ForeColor = Style.Db.Text; int w = (int)Math.Round(control.Width * dpi) + 2; control.Width = w; int h; if (_config.Title == null) { h = control.Height; rectContent = new RectangleF(padding, padding, w, control.Height); } else { using (var fontTitle = new Font(Font.FontFamily, Font.Size, FontStyle.Bold)) { var sizeTitle = g.MeasureString(config.Title, fontTitle, w); h = (int)Math.Round(sizeTitle.Height + sp + control.Height); rectTitle = new RectangleF(padding, padding, w, sizeTitle.Height + sp); rectContent = new RectangleF(rectTitle.X, rectTitle.Bottom, w, h - sizeTitle.Height - sp); } } tempContent = new Bitmap(control.Width, control.Height); Helper.DpiAuto(Config.Dpi, control); control.Size = new Size(tempContent.Width, tempContent.Height); control.DrawToBitmap(tempContent, new Rectangle(0, 0, tempContent.Width, tempContent.Height)); SetSize(w + padding2, h + padding2); } else if (config.Content is IList list) { rtext = true; if (_config.Title == null) { var _texts = new List(list.Count); float has_x = 0, max_h = 0; foreach (var txt in list) { if (txt.Call != null) hasmouse = true; var sizeContent = g.MeasureString(txt.Text, txt.Font ?? Font); float txt_w = sizeContent.Width + txt.Gap * dpi; _texts.Add(new float[] { padding + has_x, padding, txt_w }); if (max_h < sizeContent.Height) max_h = sizeContent.Height; has_x += txt_w; } var texts = new List(_texts.Count); for (int i = 0; i < _texts.Count; i++) { var txt = _texts[i]; texts.Add(new InRect(list[i], new RectangleF(txt[0], txt[1], txt[2], max_h))); } rectsContent = texts.ToArray(); rectContent = new RectangleF(padding, padding, has_x, max_h); SetSize((int)has_x + padding2, (int)max_h + padding2); } else { using (var fontTitle = new Font(Font.FontFamily, Font.Size, FontStyle.Bold)) { var sizeTitle = g.MeasureString(config.Title, fontTitle); var _texts = new List(list.Count); float has_x = 0, max_h = 0; foreach (var txt in list) { if (txt.Call != null) hasmouse = true; var sizeContent = g.MeasureString(txt.Text, txt.Font ?? Font); float txt_w = sizeContent.Width + txt.Gap * dpi; _texts.Add(new float[] { padding + has_x, padding + sizeTitle.Height + sp, txt_w }); if (max_h < sizeContent.Height) max_h = sizeContent.Height; has_x += txt_w; } var texts = new List(_texts.Count); for (int i = 0; i < _texts.Count; i++) { var txt = _texts[i]; texts.Add(new InRect(list[i], new RectangleF(txt[0], txt[1], txt[2], max_h))); } rectsContent = texts.ToArray(); int w = (int)Math.Ceiling(has_x > sizeTitle.Width ? has_x : sizeTitle.Width), h = (int)Math.Round(sizeTitle.Height + sp + max_h); rectTitle = new RectangleF(padding, padding, w, sizeTitle.Height + sp); rectContent = new RectangleF(rectTitle.X, rectTitle.Bottom, w, max_h); SetSize(w + padding2, h + padding2); } } } else { rtext = true; var content = config.Content.ToString(); if (_config.Title == null) { var sizeContent = g.MeasureString(content, Font); int w = (int)Math.Ceiling(sizeContent.Width), h = (int)Math.Round(sizeContent.Height); rectContent = new RectangleF(padding, padding, w, h); SetSize(w + padding2, h + padding2); } else { using (var fontTitle = new Font(Font.FontFamily, Font.Size, FontStyle.Bold)) { SizeF sizeTitle = g.MeasureString(config.Title, fontTitle), sizeContent = g.MeasureString(content, Font); int w = (int)Math.Ceiling(sizeContent.Width > sizeTitle.Width ? sizeContent.Width : sizeTitle.Width), h = (int)Math.Round(sizeTitle.Height + sp + sizeContent.Height); rectTitle = new RectangleF(padding, padding, w, sizeTitle.Height + sp); rectContent = new RectangleF(rectTitle.X, rectTitle.Bottom, w, h - sizeTitle.Height - sp); SetSize(w + padding2, h + padding2); } } } }); var point = config.Control.PointToScreen(Point.Empty); if (config.Offset is RectangleF rectf) SetLocation(config.ArrowAlign.AlignPoint(new Rectangle(point.X + (int)rectf.X, point.Y + (int)rectf.Y, (int)rectf.Width, (int)rectf.Height), TargetRect.Width, TargetRect.Height)); else if (config.Offset is Rectangle rect) SetLocation(config.ArrowAlign.AlignPoint(new Rectangle(point.X + rect.X, point.Y + rect.Y, rect.Width, rect.Height), TargetRect.Width, TargetRect.Height)); else SetLocation(config.ArrowAlign.AlignPoint(point, config.Control.Size, TargetRect.Width, TargetRect.Height)); } public override void LoadOK() { if (IsHandleCreated) { if (config.Content is Control control) { BeginInvoke(new Action(() => { LoadContent(control); })); } } if (config.AutoClose > 0) { ITask.Run(() => { Thread.Sleep(config.AutoClose * 1000); IClose(); }); } } Bitmap? tempContent; void LoadContent(Control control) { var flocation = new Point(TargetRect.Location.X + (int)rectContent.X, TargetRect.Location.Y + (int)rectContent.Y); var fsize = new Size((int)rectContent.Width, (int)rectContent.Height); form = new DoubleBufferForm(this, control) { FormBorderStyle = FormBorderStyle.None, Location = flocation, MaximumSize = fsize, MinimumSize = fsize, Size = fsize }; control.Disposed += (a, b) => { Close(); }; form.Show(this); form.Location = flocation; PARENT = form; config.OnControlLoad?.Invoke(); } protected override void OnClosing(CancelEventArgs e) { if (config.Content is Control control) { if (control.IsDisposed) { form?.Dispose(); base.OnClosing(e); return; } tempContent = new Bitmap(control.Width, control.Height); control.DrawToBitmap(tempContent, new Rectangle(0, 0, tempContent.Width, tempContent.Height)); form?.Hide(); } base.OnClosing(e); } protected override void Dispose(bool disposing) { if (config.Content is Control control) { control.Dispose(); form?.Dispose(); } base.Dispose(disposing); } RectangleF rectTitle, rectContent; InRect[]? rectsContent; bool rtext = false; bool hasmouse = false; #region 渲染 readonly StringFormat stringLeft = Helper.SF_Ellipsis(lr: StringAlignment.Near); readonly StringFormat stringCenter = Helper.SF_NoWrap(); public override Bitmap PrintBit() { var rect = TargetRectXY; var rect_read = new Rectangle(10, 10, rect.Width - 20, rect.Height - 20); Bitmap original_bmp = new Bitmap(rect.Width, rect.Height); using (var g = Graphics.FromImage(original_bmp).High()) { using (var path = DrawShadow(g, rect, rect_read)) { using (var brush = new SolidBrush(Style.Db.BgElevated)) { g.FillPath(brush, path); if (config.ArrowAlign != TAlign.None) g.FillPolygon(brush, config.ArrowAlign.AlignLines(config.ArrowSize, rect, rect_read)); } if (tempContent != null) g.DrawImage(tempContent, rectContent); } if (config.Title != null || rtext) { using (var brush = new SolidBrush(Style.Db.Text)) { using (var fontTitle = new Font(Font.FontFamily, Font.Size, FontStyle.Bold)) { g.DrawStr(config.Title, fontTitle, brush, rectTitle, stringLeft); } if (rtext) { if (config.Content is IList list && rectsContent != null) { for (int i = 0; i < list.Count; i++) { var txt = list[i]; if (txt.Fore.HasValue) { using (var fore = new SolidBrush(txt.Fore.Value)) { g.DrawStr(txt.Text, txt.Font ?? Font, fore, rectsContent[i].Rect, stringCenter); } } else g.DrawStr(txt.Text, txt.Font ?? Font, brush, rectsContent[i].Rect, stringCenter); } } else g.DrawStr(config.Content.ToString(), Font, brush, rectContent, stringLeft); } } } } return original_bmp; } Bitmap? shadow_temp = null; /// /// 绘制阴影 /// /// GDI /// 客户区域 /// 真实区域 GraphicsPath DrawShadow(Graphics g, Rectangle rect_client, RectangleF rect_read) { var path = rect_read.RoundPath((int)(config.Radius * Config.Dpi)); if (Config.ShadowEnabled) { if (shadow_temp == null || (shadow_temp.Width != rect_client.Width || shadow_temp.Height != rect_client.Height)) { shadow_temp?.Dispose(); shadow_temp = path.PaintShadow(rect_client.Width, rect_client.Height); } g.DrawImage(shadow_temp, rect_client, 0.2F); } return path; } #endregion #region 鼠标 protected override void OnMouseMove(MouseEventArgs e) { if (hasmouse && rectsContent != null) { foreach (var it in rectsContent) { if (it.Text.Call != null && it.Rect.Contains(e.Location)) { SetCursor(true); return; } } SetCursor(false); } base.OnMouseMove(e); } protected override void OnMouseClick(MouseEventArgs e) { if (hasmouse && rectsContent != null && e.Button == MouseButtons.Left) { foreach (var it in rectsContent) { if (it.Text.Call != null && it.Rect.Contains(e.Location)) { it.Text.Call(); return; } } } base.OnMouseClick(e); } #endregion class InRect { public InRect(Popover.TextRow text, RectangleF rect) { Text = text; Rect = rect; } public Popover.TextRow Text { get; set; } public RectangleF Rect { get; set; } } } }