Files

444 lines
16 KiB
C#
Raw Permalink Normal View History

2026-07-14 13:55:17 +08:00
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace JY.Inspection
{
public enum Logstype//枚举类型
{
Message,
Warning,
Error
}
/// <summary>
/// 轻快型消息提示类
/// </summary>
public static class MessageTip
{
static readonly Image _iconOk;
static readonly Image _iconWarning;
static readonly Image _iconError;
/// <summary>
/// 全局停留时长(毫秒),影响后续弹出的tip。默认500
/// </summary>
public static int DefaultDelay { get; set; }
/// <summary>
/// 是否允许上浮动画。默认true
/// </summary>
public static bool AllowFloating { get; set; }
static MessageTip()
{
DefaultDelay = 500;
AllowFloating = true;
Bitmap spriteImage;
using (var ms = new MemoryStream(Convert.FromBase64String(DefaultIconData)))
{
//不能直接用Img.FromMs得到的对象,怀疑因该方法得到的对象与源ms有瓜葛
//ms释放后会导致莫名问题,比如下面的Clone会引发内存不足异常
//而new Bitmap(Image)相当于基于Image重造了一个全新的bmp
spriteImage = new Bitmap(Image.FromStream(ms));
}
_iconOk = spriteImage.Clone(new Rectangle(0, 0, 32, 32), spriteImage.PixelFormat);
_iconWarning = spriteImage.Clone(new RectangleF(32, 0, 32, 32), spriteImage.PixelFormat);
_iconError = spriteImage.Clone(new RectangleF(64, 0, 32, 32), spriteImage.PixelFormat);
}
/// <summary>
/// 显示良好消息,图标为绿勾 √
/// </summary>
/// <param name="text">消息文本</param>
/// <param name="delay">消息停留时长(毫秒)。指定负数则使用 DefaultDelay</param>
public static void ShowOk(string text = null, int delay = -1)
{
Show(text, _iconOk, Color.SeaGreen, Color.White, delay);
}
/// <summary>
/// 显示警告消息,图标为黄色感叹号 !
/// </summary>
/// <param name="text">消息文本</param>
/// <param name="delay">消息停留时长(毫秒)。指定负数则使用 DefaultDelay</param>
public static void ShowWarning(string text = null, int delay = -1)
{
Show(text, _iconWarning, Color.DarkOrange, Color.Black, delay);
}
/// <summary>
/// 显示出错消息,图标为红叉 X
/// </summary>
/// <param name="text">消息文本</param>
/// <param name="delay">消息停留时长(毫秒)。指定负数则使用 DefaultDelay</param>
public static void ShowError(string text = null, int delay = -1)
{
Show(text, _iconError, Color.Red, Color.White, delay);
}
/// <summary>
/// 显示消息
/// </summary>
/// <param name="text">消息文本</param>
/// <param name="icon">图标。不会进行缩放</param>
/// <param name="delay">消息停留时长(毫秒)。指定负数则使用 DefaultDelay</param>
public static void Show(string text, Image icon, Color bkColor, Color textColor, int delay = -1)
{
ThreadPool.QueueUserWorkItem(obj => new TipForm
{
TipText = text,
TipIcon = icon,
Delay = delay < 0 ? DefaultDelay : delay,
Floating = AllowFloating,
BkColor = bkColor,
TextColor= textColor,
BasePoint = Control.MousePosition //在鼠标点击的附近弹出
}.ShowDialog()); ;//要让创建浮动窗体的线程具有消息循环,所以要用ShowDialog
}
/// <summary>
/// 内置图标数据:√ ! X
/// </summary>
const string DefaultIconData = @"R0lGODlhYAAgANUAAOrcJ9LORebm5tJKShPLJLczM/z3s/XrkNfSOhS2JKaYMezeaMoREfhwcNS3
t7IREVSkWpWQZjCpPfz8+zS4RN3PZdTU1EjLWG9uaO/w7/Dke1HVYfHsx5UzM8Q8PLjYuqmmn8bl
yeO3txsbD+fck9DAUeDaVV1cHuHXMDGTOO3iSeHy4+BYWDvGTPn25O/hN5PIl8K1Pbfhu/z78nW/
fPb39lbeZ5/WpIAzM2HpcqITExGiIjV/N8S9luLbqf///yH5BAAAAAAALAAAAABgACAAAAb/wJ9w
SCwaj8ikcslsOp/QKFNUEEGpVqm2OQtUZlJqo+oUk7fHFUWmJWk0pKi4Mc4qxaw6uli7bNZRMxUG
BhUuT3gseWdIc4p6e0I0Fzk5EmxPboQHcU2Jiot2RZ+PjFs3LTk2G5aYTC6DBweFh0wFDaC5kERi
Ayy+v4oFeyEUNqsbrJdNmm8GcJ63A9PUA7s/VL/V0yymUWobNhfj4xsSTByxb7MVHNEN29xn2fG+
3lB9F+LkfstLFbMWaBAoq0KZWx4SJhzgoZuIbB4YLox4DwqNFuJaaGxxgQIMdIM0RMCAIYKGQu6a
OCjAQqHLAQUKSHSZsOKTG8YubNRIgUYT/4AHFpAkqaHghIMtPRRQuhAm06VQHSiRAQhJCAl/JCSQ
wJUChBogDQgcimHBggPtnqyEGbOt27c2hciQYMNfkRUSOu7Yu1fCVyYTAA5cMJKk2aJfEHVgC9et
UqlJqNowZlfIBAgULvDtK2FFEx+DzArFcCKCaLQ+rixVCpd1B1FG5k6mQNnVDxoUNmzewTUElxIH
BhMeMeJEiQBeFpQAo7YDa9YPdEiP+0M27evmMN2QQCNBgs0SbjgBHXSBCQQniJ9AjnxBhU4H2Uqf
L/21ErrXrydoIeHD1Q3e7eDdVh814QJwGpjAngLEKcBeeyWA5cli9FVYH2xEzPUHBS0MuP+fBClc
4KGA3B3FTAUaBICAigEocMIJCqy4ohcBwHeHczpEZyF99iGhIQUeetfCH0H6lYETByaIwJJLxvBi
DEwyaUIJtSThQAcFPBCdljl2uWV0PR7xQV4EDFhmAmWe6R0EnjlRQQULRLmkCW+cJ6eCBiUhApZa
9unnn312AJmYErRAwKFopplmAin45gQHJWgg56SUTpmSEVcyAKiWHXSwKaeDGjGmooeWemgKHzwR
mAkmoODqqygwOIICsL66Yp6j4KDpA7vuKuiVWu7KK6hJfJCCqcimUOCjJSxQ66vEEffsqyVcKoQD
umqqrbY4QJYprwxs+4CgxR6LLAEp+KT/agkmqAArANBG+yy8KARQgok/YBvuvvx2S4S+4PLLgL9I
GIvsDhDg+1kM7gLgMLwPR0BcBA8/jMLDMaQmRLYCh0vwvxx3jIMSBiMKwZGqxmDCCxW3rIIsKrTc
MgIxmKivyKGC3PHAOf8wQQ0ZZCAACObyYIEAQWdQw9ITNH2EDwzLLPXUFWc8xM37fnwE1h73/APQ
AlggNgg88NCD2EcLgLTSThtB6wsqvCD33HIDwDLLVDsc678dZO11EZl2jcTPQattuNpJK11D020T
MUEPCkQu+eSUV255DwrrS65Kum4eyRJNAy304aSXXrriTk8wOgg4gIB22okbjjbrICC9E/jnuEcR
+uiHB73076KT7rvTQQAAOw==";
/// <summary>
/// 浮动消息层
/// </summary>
private class TipForm : Form
{
/// <summary>
/// 图标和文本之间的间距(像素)
/// </summary>
const int IconTextSpacing = 3;
/// <summary>
/// 基准点。用于指导本窗体显示位置
/// </summary>
public Point BasePoint { get; set; }
/// <summary>
/// 显示文字
/// </summary>
string _tipText;
/// <summary>
/// 背景色
/// </summary>
Color _bkColor = Color.SeaGreen;
/// <summary>
/// 文字颜色
/// </summary>
Color _textColor=Color.Black;
/// <summary>
/// 提示图标
/// </summary>
public Image TipIcon { get; set; }
/// <summary>
/// 提示文本
/// </summary>
public string TipText
{
get { return _tipText ?? string.Empty; }
set { _tipText = value; }
}
/// <summary>
/// 文字显示颜色
/// </summary>
//[DefaultValue(500)]
public Color TextColor
{
get { return _textColor; }
set
{
_textColor = value;
this.ForeColor = _textColor;
}
}
/// <summary>
/// 停留时长(毫秒)
/// </summary>
[DefaultValue(500)]
public int Delay { get; set; }
/// <summary>
/// 停留时长(毫秒)
/// </summary>
//[DefaultValue(Color.White)]
public Color BkColor
{
get { return _bkColor; }
set
{
_bkColor = value;
this.BackColor = _bkColor;
}
}
/// <summary>
/// 是否允许浮动
/// </summary>
[DefaultValue(true)]
public bool Floating { get; set; }
//显示后不激活,即不抢焦点
protected override bool ShowWithoutActivation
{
get { return true; }
}
public TipForm()
{
//双缓冲。有必要
SetStyle(ControlStyles.UserPaint, true);
DoubleBuffered = true;
InitializeComponent();
Delay = 500;
Floating = true;
BkColor = Color.White;
this._timer.Tick += timer_Tick;
this.Load += TipForm_Load;
this.Shown += TipForm_Shown;
this.FormClosing += TipForm_FormClosing;
}
/// <summary>
/// 根据图标和文字处理窗体尺寸
/// </summary>
private void ProcessClientSize()
{
Size size = Size.Empty;
if (TipIcon != null)
{
size += TipIcon.Size;
}
if (TipText.Length != 0)
{
if (TipIcon != null)
{
size.Width += IconTextSpacing;//图标与文字的间距
}
var textSize = TextRenderer.MeasureText(TipText, this.Font);
size.Width += textSize.Width;
if (size.Height < textSize.Height) { size.Height = textSize.Height; }
}
this.ClientSize = size + Padding.Size;
}
private int x, y; //显示的坐标变量
/// <summary>
/// 根据基准点处理窗体显示位置
/// </summary>
private void ProcessLocation()
{
#region 弹窗显示在屏幕中间
this.y = (Screen.PrimaryScreen.Bounds.Height - 30 - this.Height) / 2;
this.x = (Screen.PrimaryScreen.Bounds.Width - 10 - this.Width) / 2;
this.Location = new Point(this.x, this.y);
#endregion
#region 弹窗显示在屏幕右下角
//this.x = Screen.PrimaryScreen.WorkingArea.Width - this.Width + 15;
//this.y = Screen.PrimaryScreen.WorkingArea.Height - this.Height * i - 5 * i;
//this.Location = new Point(this.x, this.y);
#endregion
#region 弹窗跟随鼠标位置
//var p = BasePoint;
//p.X -= Screen.PrimaryScreen.WorkingArea.Width - this.Width - 1000;//Screen.PrimaryScreen.WorkingArea.Width-this.Width / 2;
////横向处理。距离屏幕左右两边太近时的处理
//int screenWidth;
//if (p.X < 10)
//{
// p.X = 10;
//}
//else if (p.X + this.Width > (screenWidth = Screen.PrimaryScreen.Bounds.Width) - 10)
//{
// p.X = screenWidth - 10 - this.Width;
//}
////纵向处理。在鼠标上方显示
//p.Y -= this.Height + 20;
//this.Location = p;
#endregion
}
void TipForm_Load(object sender, EventArgs e)
{
ProcessClientSize();
ProcessLocation();
//上浮窗体动画。采用异步,以不阻塞透明渐变动画的进行
if (Floating)
{
ThreadPool.QueueUserWorkItem(obj =>
{
while (this.IsHandleCreated)
{
this.BeginInvoke(new Action<object>(arg =>
{
this.Top--;
Application.DoEvents();
}), (object)null);
Thread.Sleep(30);
}
});
}
//透明渐入动画。之所以不用异步是为了在完全显示后再开始Delay的计时
//不然如果Delay设置过低,还没等看清就渐隐了
this.Opacity = 0;
while (this.Opacity < 1)
{
this.Opacity += 0.1;
Application.DoEvents();
Thread.Sleep(10);
}
}
void TipForm_Shown(object sender, EventArgs e)
{
//因为timer.Interval不能为0
if (Delay > 0)
{
_timer.Interval = Delay;
_timer.Start();
}
else
{
this.Close();
}
}
void timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
this.Close();
}
void TipForm_FormClosing(object sender, FormClosingEventArgs e)
{
//透明渐隐动画
while (this.Opacity > 0)
{
this.Opacity -= 0.1;
Application.DoEvents();
Thread.Sleep(20);
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var clip = GetPaddedRectangle();//得到作图区域
var g = e.Graphics;
//g.DrawRectangle(Pens.Red, clip);//debug
//画图标
if (TipIcon != null)
{
g.DrawImageUnscaled(TipIcon, clip.Location);
}
//画文本
if (TipText.Length != 0)
{
if (TipIcon != null)
{
clip.X += TipIcon.Width + IconTextSpacing;
}
TextRenderer.DrawText(g, TipText, this.Font, clip, this.ForeColor, TextFormatFlags.VerticalCenter);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
//画边框
ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, SystemColors.ControlDark, ButtonBorderStyle.Solid);
}
/// <summary>
/// 获取刨去Padding的内容区
/// </summary>
private Rectangle GetPaddedRectangle()
{
Rectangle r = this.ClientRectangle;
r.X += this.Padding.Left;
r.Y += this.Padding.Top;
r.Width -= this.Padding.Horizontal;
r.Height -= this.Padding.Vertical;
return r;
}
#region 设计器内容
protected override void Dispose(bool disposing)
{
if (disposing)
{
_timer.Dispose();//这货必须显示释放
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this._timer = new System.Windows.Forms.Timer();
this.SuspendLayout();
this.AutoScaleMode = AutoScaleMode.None;
//this.ClientSize = new System.Drawing.Size(100, 100);
this.BackColor = Color.White;
this.Font = new Font(SystemFonts.MessageBoxFont.FontFamily, 12);
this.FormBorderStyle = FormBorderStyle.None;
this.Padding = new Padding(20, 10, 20, 10);
this.Name = "TipForm";
this.ShowInTaskbar = false;
this.ResumeLayout(false);
}
private System.Windows.Forms.Timer _timer;
#endregion
}
}
}