// 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.Drawing2D;
using System.Threading;
using System.Windows.Forms;
namespace AntdUI
{
///
/// Tooltip 文字提示
///
/// 简单的文字提示气泡框。
[Description("Tooltip 文字提示")]
[ToolboxItem(true)]
public class Tooltip : IControl, ITooltip
{
#region 参数
int radius = 6;
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(6)]
public int Radius
{
get => radius;
set
{
if (radius == value) return;
radius = value;
Invalidate();
}
}
///
/// 箭头大小
///
[Description("箭头大小"), Category("外观"), DefaultValue(8)]
public int ArrowSize { get; set; } = 8;
///
/// 箭头方向
///
[Description("箭头方向"), Category("外观"), DefaultValue(TAlign.Top)]
public TAlign ArrowAlign { get; set; } = TAlign.Top;
///
/// 自定义宽度
///
[Description("自定义宽度"), Category("外观"), DefaultValue(null)]
public int? CustomWidth { get; set; }
#endregion
#region 渲染
readonly StringFormat s_c = Helper.SF_NoWrap(), s_l = Helper.SF(lr: StringAlignment.Near);
protected override void OnPaint(PaintEventArgs e)
{
var rect = ClientRectangle;
var g = e.Graphics.High();
MaximumSize = MinimumSize = this.RenderMeasure(g, out var multiline);
this.Render(g, rect, multiline, s_c, s_l);
base.OnPaint(e);
}
#endregion
#region 静态方法
///
/// Tooltip 文字提示
///
/// 所属控件
/// 文本
/// 箭头方向
public static Form? open(Control control, string text, TAlign ArrowAlign = TAlign.Top)
{
return open(new Config(control, text) { ArrowAlign = ArrowAlign });
}
///
/// Tooltip 文字提示
///
/// 所属控件
/// 文本
/// 偏移量
/// 箭头方向
public static Form? open(Control control, string text, Rectangle rect, TAlign ArrowAlign = TAlign.Top)
{
return open(new Config(control, text) { Offset = rect, ArrowAlign = ArrowAlign });
}
///
/// Tooltip 文字提示
///
/// 配置
public static Form? open(Config config)
{
if (config.Control.IsHandleCreated)
{
if (config.Control.InvokeRequired)
{
Form? form = null;
config.Control.Invoke(new Action(() =>
{
form = open(config);
}));
return form;
}
var popover = new TooltipForm(config.Control, config.Text, config);
popover.Show(config.Control);
return popover;
}
return null;
}
///
/// 配置
///
public class Config : ITooltipConfig
{
///
/// Tooltip 配置
///
/// 所属控件
/// 文本
public Config(Control control, string text)
{
Font = control.Font;
Control = control;
Text = text;
}
///
/// 所属控件
///
public Control Control { get; set; }
///
/// 偏移量
///
public object? Offset { get; set; } = null;
///
/// 字体
///
public Font? Font { get; set; }
///
/// 文本
///
public string Text { get; set; }
///
/// 圆角
///
public int Radius { get; set; } = 6;
///
/// 箭头大小
///
public int ArrowSize { get; set; } = 8;
///
/// 箭头方向
///
public TAlign ArrowAlign { get; set; } = TAlign.Top;
///
/// 自定义宽度
///
public int? CustomWidth { get; set; }
}
#endregion
}
internal class TooltipForm : ILayeredFormOpacity, ITooltip
{
readonly Control? ocontrol = null;
bool multiline = false;
public TooltipForm(Control control, string txt, ITooltipConfig component)
{
ocontrol = control;
control.Parent.SetTopMost(Handle);
Text = txt;
if (component.Font != null) Font = component.Font;
else if (Config.Font != null) Font = Config.Font;
ArrowSize = component.ArrowSize;
Radius = component.Radius;
ArrowAlign = component.ArrowAlign;
CustomWidth = component.CustomWidth;
Helper.GDI(g =>
{
SetSize(this.RenderMeasure(g, out multiline));
});
var point = control.PointToScreen(Point.Empty);
if (component is Tooltip.Config config)
{
if (config.Offset is RectangleF rectf) SetLocation(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(ArrowAlign.AlignPoint(new Rectangle(point.X + rect.X, point.Y + rect.Y, rect.Width, rect.Height), TargetRect.Width, TargetRect.Height));
else SetLocation(ArrowAlign.AlignPoint(point, control.Size, TargetRect.Width, TargetRect.Height));
}
else SetLocation(ArrowAlign.AlignPoint(point, control.Size, TargetRect.Width, TargetRect.Height));
control.LostFocus += Control_LostFocus;
control.MouseLeave += Control_LostFocus;
}
public TooltipForm(Control control, Rectangle rect, string txt, ITooltipConfig component)
{
ocontrol = control;
control.SetTopMost(Handle);
Text = txt;
if (component.Font != null) Font = component.Font;
else if (Config.Font != null) Font = Config.Font;
ArrowSize = component.ArrowSize;
Radius = component.Radius;
ArrowAlign = component.ArrowAlign;
CustomWidth = component.CustomWidth;
Helper.GDI(g =>
{
SetSize(this.RenderMeasure(g, out multiline));
});
SetLocation(ArrowAlign.AlignPoint(rect, TargetRect));
}
public void SetText(Rectangle rect, string text)
{
Text = text;
Helper.GDI(g =>
{
SetSize(this.RenderMeasure(g, out multiline));
});
SetLocation(ArrowAlign.AlignPoint(rect, TargetRect));
Print();
}
private void Control_LostFocus(object? sender, EventArgs e)
{
IClose();
}
#region 参数
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(6)]
public int Radius { get; set; } = 6;
///
/// 箭头大小
///
[Description("箭头大小"), Category("外观"), DefaultValue(8)]
public int ArrowSize { get; set; } = 8;
///
/// 箭头方向
///
[Description("箭头方向"), Category("外观"), DefaultValue(TAlign.Top)]
public TAlign ArrowAlign { get; set; } = TAlign.Top;
///
/// 自定义宽度
///
[Description("自定义宽度"), Category("外观"), DefaultValue(null)]
public int? CustomWidth { get; set; }
#endregion
#region 渲染
readonly StringFormat s_c = Helper.SF_NoWrap(), s_l = Helper.SF(lr: StringAlignment.Near);
public override Bitmap PrintBit()
{
var rect = TargetRectXY;
Bitmap original_bmp = new Bitmap(rect.Width, rect.Height);
using (var g = Graphics.FromImage(original_bmp).High())
{
this.Render(g, rect, multiline, s_c, s_l);
}
return original_bmp;
}
#endregion
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (ocontrol == null) return;
ocontrol.LostFocus -= Control_LostFocus;
ocontrol.MouseLeave -= Control_LostFocus;
}
}
[ProvideProperty("Tip", typeof(Control)), Description("提示")]
public partial class TooltipComponent : Component, IExtenderProvider, ITooltipConfig
{
public bool CanExtend(object target) => target is Control;
#region 属性
///
/// 字体
///
[Description("字体"), DefaultValue(null)]
public Font? Font { get; set; } = null;
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(6)]
public int Radius { get; set; } = 6;
///
/// 箭头大小
///
[Description("箭头大小"), Category("外观"), DefaultValue(8)]
public int ArrowSize { get; set; } = 8;
///
/// 箭头方向
///
[Description("箭头方向"), Category("外观"), DefaultValue(TAlign.Top)]
public TAlign ArrowAlign { get; set; } = TAlign.Top;
///
/// 自定义宽度
///
[Description("自定义宽度"), Category("外观"), DefaultValue(null)]
public int? CustomWidth { get; set; }
#endregion
readonly Dictionary dic = new Dictionary();
[Description("设置是否提示"), DefaultValue(null)]
[Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(UITypeEditor))]
public string? GetTip(Control item)
{
if (dic.TryGetValue(item, out string? value)) return value;
return null;
}
public void SetTip(Control control, string? val)
{
if (val == null)
{
if (dic.ContainsKey(control))
{
dic.Remove(control);
control.MouseEnter -= Control_Enter;
control.MouseLeave -= Control_Leave;
control.Leave -= Control_Leave;
}
return;
}
if (dic.ContainsKey(control)) dic[control] = val.Trim();
else
{
dic.Add(control, val.Trim());
control.MouseEnter += Control_Enter;
control.MouseLeave += Control_Leave;
control.Leave -= Control_Leave;
}
}
readonly List dic_in = new List();
private void Control_Leave(object? sender, EventArgs e)
{
if (sender != null && sender is Control obj)
lock (dic_in) dic_in.Remove(obj);
}
private void Control_Enter(object? sender, EventArgs e)
{
if (sender != null && sender is Control obj)
{
lock (dic_in) dic_in.Add(obj);
ITask.Run(() =>
{
Thread.Sleep(500);
if (dic_in.Contains(obj))
{
obj.BeginInvoke(new Action(() =>
{
new TooltipForm(obj, dic[obj], this).Show();
}));
}
});
}
}
}
#region 核心渲染
internal static class ITooltipLib
{
#region 渲染
public static Size RenderMeasure(this ITooltip core, Graphics g, out bool multiline)
{
multiline = core.Text.Contains("\n");
int padding = (int)Math.Ceiling(20 * Config.Dpi);
var font_size = g.MeasureString(core.Text, core.Font).Size();
if (core.CustomWidth.HasValue)
{
int width = (int)Math.Ceiling(core.CustomWidth.Value * Config.Dpi);
if (font_size.Width > width)
{
font_size = g.MeasureString(core.Text, core.Font, width).Size();
multiline = true;
}
}
if (core.ArrowAlign == TAlign.None) return new Size(font_size.Width + padding, font_size.Height + padding);
if (core.ArrowAlign == TAlign.Bottom || core.ArrowAlign == TAlign.BL || core.ArrowAlign == TAlign.BR || core.ArrowAlign == TAlign.Top || core.ArrowAlign == TAlign.TL || core.ArrowAlign == TAlign.TR)
return new Size(font_size.Width + padding, font_size.Height + padding + core.ArrowSize);
else return new Size(font_size.Width + padding + core.ArrowSize, font_size.Height + padding);
}
public static void Render(this ITooltip core, Graphics g, Rectangle rect, bool multiline, StringFormat s_c, StringFormat s_l)
{
int gap = (int)Math.Ceiling(5 * Config.Dpi), padding = gap * 2, padding2 = padding * 2;
using (var brush = new SolidBrush(Config.Mode == TMode.Dark ? Color.FromArgb(66, 66, 66) : Color.FromArgb(38, 38, 38)))
{
if (core.ArrowAlign == TAlign.None)
{
var rect_read = new Rectangle(rect.X + 5, rect.Y + 5, rect.Width - 10, rect.Height - 10);
using (var path = rect_read.RoundPath(core.Radius))
{
DrawShadow(core, g, rect, rect_read, 3, path);
g.FillPath(brush, path);
}
RenderText(core, g, rect_read, multiline, padding, padding2, s_c, s_l);
}
else
{
Rectangle rect_text;
switch (core.ArrowAlign.AlignMini())
{
case TAlignMini.Top:
rect_text = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height - core.ArrowSize);
break;
case TAlignMini.Bottom:
rect_text = new Rectangle(rect.X, rect.Y + core.ArrowSize, rect.Width, rect.Height - core.ArrowSize);
break;
case TAlignMini.Left:
//左
rect_text = new Rectangle(rect.X, rect.Y, rect.Width - core.ArrowSize, rect.Height);
break;
default:
//右
rect_text = new Rectangle(rect.X + core.ArrowSize, rect.Y, rect.Width - core.ArrowSize, rect.Height);
break;
}
var rect_read = new Rectangle(rect_text.X + gap, rect_text.Y + gap, rect_text.Width - padding, rect_text.Height - padding);
using (var path = rect_read.RoundPath(core.Radius))
{
DrawShadow(core, g, rect, rect_read, 3, path);
g.FillPath(brush, path);
}
g.FillPolygon(brush, core.ArrowAlign.AlignLines(core.ArrowSize, rect, rect_read));
RenderText(core, g, rect_text, multiline, padding, padding2, s_c, s_l);
}
}
}
static void RenderText(ITooltip core, Graphics g, Rectangle rect, bool multiline, int padding, int padding2, StringFormat s_c, StringFormat s_l)
{
if (multiline) g.DrawStr(core.Text, core.Font, Brushes.White, new Rectangle(rect.X + padding, rect.Y + padding, rect.Width - padding2, rect.Height - padding2), s_l);
else g.DrawStr(core.Text, core.Font, Brushes.White, rect, s_c);
}
static void DrawShadow(this ITooltip core, Graphics _g, Rectangle brect, Rectangle rect, int size, GraphicsPath path2)
{
using (var bmp = new Bitmap(brect.Width, brect.Height))
{
using (var g = Graphics.FromImage(bmp))
{
int size2 = size * 2;
using (var path = new Rectangle(rect.X - size, rect.Y - size + 2, rect.Width + size2, rect.Height + size2).RoundPath(core.Radius))
{
path.AddPath(path2, false);
using (var brush = new PathGradientBrush(path))
{
brush.CenterColor = Color.Black;
brush.SurroundColors = new Color[] { Color.Transparent };
g.FillPath(brush, path);
}
}
}
_g.DrawImage(bmp, brect);
}
}
#endregion
}
public class TooltipConfig : ITooltipConfig
{
public Font? Font { get; set; }
public int Radius { get; set; } = 6;
public int ArrowSize { get; set; } = 8;
public TAlign ArrowAlign { get; set; } = TAlign.Top;
public int? CustomWidth { get; set; }
}
internal interface ITooltipConfig
{
///
/// 字体
///
Font? Font { get; set; }
///
/// 圆角
///
int Radius { get; set; }
///
/// 箭头大小
///
int ArrowSize { get; set; }
///
/// 箭头方向
///
TAlign ArrowAlign { get; set; }
///
/// 设定宽度
///
int? CustomWidth { get; set; }
}
internal interface ITooltip
{
///
/// 文本
///
string Text { get; set; }
///
/// 字体
///
Font Font { get; set; }
///
/// 圆角
///
int Radius { get; set; }
///
/// 箭头大小
///
int ArrowSize { get; set; }
///
/// 箭头方向
///
TAlign ArrowAlign { get; set; }
///
/// 设定宽度
///
int? CustomWidth { get; set; }
}
#endregion
}