SunnyUI/SunnyUI/Forms/UINotifier.cs
2024-09-13 23:43:27 +08:00

775 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
* CopyRight (C) 2012-2024 ShenYongHua(沈永华).
* QQ群56829229 QQ17612584 EMailSunnyUI@QQ.Com
*
* Blog: https://www.cnblogs.com/yhuse
* Gitee: https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI can be used for free under the GPL-3.0 license.
* If you use this code, please keep this note.
* 如果您使用此代码,请保留此说明。
******************************************************************************
* 文件名称: UINotifier.cs
* 文件说明: 信息提示框
* 文件作者: boschi84
* 开源协议: CPOL
* 引用地址: https://www.codeproject.com/Articles/1118187/Smart-UINotifier-for-Executables-Toast-for-NET
******************************************************************************/
//
// UINotifier.cs
//
// This project was born in the 2009 for a University application.
// Then it was resurrected in the 2015 to be part of an old system that cannot handle the
// 3.5 framework and grows up to include more features. This is not a professional work, so
// the code quality it's something like "let's do something quickly".
// If you are looking for something professional, you can do it by yourself and of course share it!
//
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace Sunny.UI
{
public sealed partial class UINotifier : Form
{
public void SetDPIScale()
{
Font = UIDPIScale.DPIScaleFont(Font, Font.Size);
noteTitle.Font = noteTitle.Font.DPIScaleFont(noteTitle.Font.Size);
noteContent.Font = noteContent.Font.DPIScaleFont(noteContent.Font.Size);
noteDate.Font = noteDate.Font.DPIScaleFont(noteDate.Font.Size);
buttonMenu.Font = noteDate.Font.DPIScaleFont(buttonMenu.Font.Size);
buttonClose.Font = noteDate.Font.DPIScaleFont(buttonClose.Font.Size);
}
#region GLOBALS
private class NoteLocation // Helper class to handle Note position
{
internal int X;
internal int Y;
internal Point initialLocation; // Mouse bar drag helpers
internal bool mouseIsDown;
public NoteLocation(int x, int y)
{
X = x;
Y = y;
}
}
private static readonly ConcurrentDictionary<short, UINotifier> Notes = new ConcurrentDictionary<short, UINotifier>(); // Keep a list of the opened Notifiers
private NoteLocation noteLocation; // Note position
private short ID; // Note ID
private string Description; // Note default Description
private string Title; // Note default Title
private UINotifierType _uiNotifierType; // Note default Type
private readonly bool IsDialog; // Note is Dialog
private BackDialogStyle backDialogStyle = BackDialogStyle.None; // DialogNote default background
private Color HoverColor = Color.FromArgb(0, 0, 0, 0); // Default Color for hover
private Color LeaveColor = Color.FromArgb(0, 0, 0, 0); // Default Color for leave
private readonly int Timeout; // Temporary note: timeout
private AutoResetEvent timerResetEvent; // Temporary note: reset event
private readonly Form InApplication; // In App UINotifier: the note is bind to the specified container
#endregion GLOBALS
#region CONSTRUCTOR & DISPLAY
//-------------------------------------------------------------------------------------------------------------------------------
// Default constructor
//-------------------------------------------------------------------------------------------------------------------------------
private UINotifier(string dsc, UINotifierType type, string title, bool isDialog = false, int timeout_ms = 0, Form insideMe = null)
{
IsDialog = isDialog;
Description = dsc;
_uiNotifierType = type;
Title = title;
Timeout = timeout_ms;
InApplication = insideMe;
InitializeComponent();
if (Notes.IsEmpty)
ID = 1;
else
ID = (short)(Notes.Keys.Max() + 1); // Set the Note ID
if (insideMe != null && !inAppNoteExists()) // Register the drag and resize events
{
insideMe.LocationChanged += inApp_LocationChanged;
insideMe.SizeChanged += inApp_LocationChanged;
}
foreach (Control c in Controls) // Make all the note area draggable
{
if (c is UISymbolLabel || c.Name == "icon")
{
c.MouseDown += OnMouseDown;
c.MouseUp += OnMouseUp;
c.MouseMove += OnMouseMove;
}
}
}
//-------------------------------------------------------------------------------------------------------------------------------
// Handle the drag drop and resize location of the notes
//-------------------------------------------------------------------------------------------------------------------------------
private void inApp_LocationChanged(object sender, EventArgs e)
{
foreach (var note in Notes.Values)
{
if (note.InApplication != null)
{
NoteLocation ln = adjustLocation(note);
note.Left = ln.X;
note.Top = ln.Y;
}
}
}
//-------------------------------------------------------------------------------------------------------------------------------
// On load form operations
//-------------------------------------------------------------------------------------------------------------------------------
private void OnLoad(object sender, EventArgs e)
{
BackColor = Color.Blue; // Initial default graphics
TransparencyKey = Color.FromArgb(128, 128, 128); // Initial default graphics
FormBorderStyle = FormBorderStyle.None; // Initial default graphics
Tag = "__Notifier|" + ID.ToString("X4"); // Save the note identification in the Tag field
setNotifier(Description, _uiNotifierType, Title);
}
//-------------------------------------------------------------------------------------------------------------------------------
// Create the Note and handle its location
//-------------------------------------------------------------------------------------------------------------------------------
private void setNotifier(string description, UINotifierType noteType, string title, bool isUpdate = false)
{
Title = title;
Description = description;
_uiNotifierType = noteType;
noteTitle.Text = title; // Fill the UINotifier data title
noteContent.Text = description; // Fill the UINotifier data description
noteDate.Text = DateTime.Now + ""; // Fill the UINotifier data Timestamp
#region ADJUST COLORS
switch (noteType)
{
case UINotifierType.ERROR:
icon.Symbol = 361527;
icon.SymbolColor = UIStyles.Red.ButtonFillColor;
LeaveColor = UIStyles.Red.ButtonFillColor;
HoverColor = UIStyles.Red.ButtonFillHoverColor;
buttonMenu.Style = buttonClose.Style = noteTitle.Style = UIStyle.Red;
break;
case UINotifierType.INFO:
icon.Symbol = 361530;
icon.SymbolColor = UIStyles.Blue.ButtonFillColor;
LeaveColor = UIStyles.Blue.ButtonFillColor;
HoverColor = UIStyles.Blue.ButtonFillHoverColor;
buttonMenu.Style = buttonClose.Style = noteTitle.Style = UIStyle.Blue;
break;
case UINotifierType.WARNING:
icon.Symbol = 361553;
icon.SymbolColor = UIStyles.Orange.ButtonFillColor;
LeaveColor = UIStyles.Orange.ButtonFillColor;
HoverColor = UIStyles.Orange.ButtonFillHoverColor;
buttonMenu.Style = buttonClose.Style = noteTitle.Style = UIStyle.Orange;
break;
case UINotifierType.OK:
icon.Symbol = 361528;
icon.SymbolColor = UIStyles.Green.ButtonFillColor;
LeaveColor = UIStyles.Green.ButtonFillColor;
HoverColor = UIStyles.Green.ButtonFillHoverColor;
buttonMenu.Style = buttonClose.Style = noteTitle.Style = UIStyle.Green;
break;
}
noteTitle.FillColor = noteTitle.RectColor;
noteTitle.ForeColor = Color.White;
#endregion ADJUST COLORS
#region DIALOG NOTE
if (IsDialog)
{
Button ok_button = new Button(); // Dialog note comes with a simple Ok button
ok_button.Font = noteTitle.Font;
ok_button.FlatStyle = FlatStyle.Flat;
ok_button.BackColor = LeaveColor;
ok_button.ForeColor = Color.White;
Size = new Size(Size.Width, // Resize the note to contain the button
Size.Height + 50);
ok_button.Size = new Size(120, 40);
ok_button.Location = new Point(noteContent.Right - ok_button.Width, Size.Height - 55);
ok_button.Text = UIStyles.CurrentResources.OK;
ok_button.Click += onOkButtonClick;
Controls.Add(ok_button);
noteDate.Location = new Point(noteDate.Location.X, // Shift down the date location
noteDate.Location.Y + 44);
noteLocation = new NoteLocation(Left, Top); // Default Center Location
}
#endregion DIALOG NOTE
#region NOTE LOCATION
if (!IsDialog && !isUpdate)
{
NoteLocation location = adjustLocation(this); // Set the note location
Left = location.X; // UINotifier position X
Top = location.Y; // UINotifier position Y
}
#endregion NOTE LOCATION
}
//-------------------------------------------------------------------------------------------------------------------------------
// Find a valid position for the note into the note area:
// 1. Inside the Screen (support multiple screens)
// 2. Inside the father application (if specified)
//-------------------------------------------------------------------------------------------------------------------------------
private NoteLocation adjustLocation(UINotifier note)
{
Rectangle notesArea;
int nColumn = 0, xShift = 40; // Custom note overlay
// x_Shift = Width + 5; // Full visible note (no overlay)
bool add = false;
if (InApplication != null && InApplication.WindowState == FormWindowState.Normal) // Get the available notes area, based on the type of note location
{
notesArea = InApplication.Bounds;
}
else
{
notesArea = new Rectangle(Screen.GetWorkingArea(note).Left,
Screen.GetWorkingArea(note).Top,
Screen.GetWorkingArea(note).Width,
Screen.GetWorkingArea(note).Height);
}
int nMaxRows = notesArea.Height / Height; // Max number of rows in the available space
int nMaxColumns = notesArea.Width / xShift; // Max number of columns in the available space
noteLocation = new NoteLocation(notesArea.Width + // Initial Position X
notesArea.Left -
Width,
notesArea.Height + // Initial Position Y
notesArea.Top -
Height);
while (nMaxRows > 0 && !add) // Check the latest available position (no overlap)
{
for (int nRow = 1; nRow <= nMaxRows; nRow++)
{
noteLocation.Y = notesArea.Height +
notesArea.Top -
Height * nRow;
if (!isLocationAlreadyUsed(noteLocation, note))
{
add = true; break;
}
if (nRow == nMaxRows) // X shift if no more column space
{
nColumn++;
nRow = 0;
noteLocation.X = notesArea.Width +
notesArea.Left -
Width - xShift * nColumn;
}
if (nColumn >= nMaxColumns) // Last exit condition: the screen is full of note
{
add = true; break;
}
}
}
noteLocation.initialLocation = new Point(noteLocation.X, // Init the initial Location, for drag & drop
noteLocation.Y);
return noteLocation;
}
#endregion CONSTRUCTOR & DISPLAY
#region EVENTS
//-------------------------------------------------------------------------------------------------------------------------------
// Close event for the note
//-------------------------------------------------------------------------------------------------------------------------------
private void onCloseClick(object sender, EventArgs e)
{
if (e == null || ((MouseEventArgs)e).Button != MouseButtons.Right)
{
closeMe();
}
}
//-------------------------------------------------------------------------------------------------------------------------------
// Show the menu (for the menu button) event
//-------------------------------------------------------------------------------------------------------------------------------
private void onMenuClick(object sender, EventArgs e)
{
closeAllToolStripMenuItem.Font = menu.Font.DPIScaleFont(menu.Font.Size);
menu.Show(buttonMenu, new Point(0, buttonMenu.Height));
}
//-------------------------------------------------------------------------------------------------------------------------------
// Close all the notes event
//-------------------------------------------------------------------------------------------------------------------------------
private void onMenuCloseAllClick(object sender, EventArgs e)
{
CloseAll();
}
//-------------------------------------------------------------------------------------------------------------------------------
// Dialog note Only (Ok button click event)
//-------------------------------------------------------------------------------------------------------------------------------
private void onOkButtonClick(object sender, EventArgs e)
{
onCloseClick(null, null); // It is the same as the close operation event
}
#endregion EVENTS
#region HELPERS
//-------------------------------------------------------------------------------------------------------------------------------
// Close the note event
//-------------------------------------------------------------------------------------------------------------------------------
private void closeMe()
{
Notes.TryRemove(this.ID, out _);
Close();
if (Notes.Count == 0)
ID = 0; // Reset the ID counter if no notes is displayed
}
//-------------------------------------------------------------------------------------------------------------------------------
// Check if a note with an inApp capabilities is set
//-------------------------------------------------------------------------------------------------------------------------------
private bool inAppNoteExists()
{
foreach (var note in Notes.Values)
{
if (note.InApplication != null)
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------------------------
// check if the specified location (X, Y) is already used by another note
//-------------------------------------------------------------------------------------------------------------------------------
private bool isLocationAlreadyUsed(NoteLocation location, UINotifier note)
{
foreach (var p in Notes.Values)
if (p.Left == location.X && p.Top == location.Y)
{
if (note.InApplication != null && p.ID == note.ID)
return false;
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Close all the notes
//-------------------------------------------------------------------------------------------------------------------------------
public static void CloseAll()
{
foreach (var note in Notes.Values)
{
note.closeMe();
}
Notes.Clear();
}
//-------------------------------------------------------------------------------------------------------------------------------
// Event used to draw a right side close icon
//-------------------------------------------------------------------------------------------------------------------------------
private void OnPaint(object sender, PaintEventArgs e)
{
var image = Properties.Resources.close;
if (image != null)
{
var g = e.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(image,
buttonClose.Width - image.Width,
buttonClose.Height - image.Height - 2,
image.Width,
image.Height);
}
}
#endregion HELPERS
#region NOTE CREATION AND MODIFY
//-------------------------------------------------------------------------------------------------------------------------------
// Show the note: it is the startup of the creation process of the note
//-------------------------------------------------------------------------------------------------------------------------------
public static short Show(string desc, UINotifierType type = UINotifierType.INFO, string title = "Notifier",
bool isDialog = false, int timeout = 0, Form inApp = null, EventHandler clickevent = null)
{
if (NotifierAlreadyPresent(desc, type, title, isDialog, out var updated_note_id, out var updated_note_occurence))
{
Update(updated_note_id, desc, type, "[" + ++updated_note_occurence + "] " + title);
}
else
{
UINotifier not = new UINotifier(desc, // Instantiate the Note
type,
title,
isDialog,
timeout,
inApp);
if (clickevent != null)
not.ItemClick = clickevent;
not.SetDPIScale();
not.Show(); // Show the note
if (not.Timeout >= 500) // Start auto close timer (if any)
{
not.timerResetEvent = new AutoResetEvent(false);
BackgroundWorker timer = new BackgroundWorker();
timer.DoWork += timer_DoWork;
timer.RunWorkerCompleted += timer_RunWorkerCompleted;
timer.RunWorkerAsync(not); // Timer (temporary notes)
}
Notes.TryAdd(not.ID, not); // Add to our collection of Notifiers
updated_note_id = not.ID;
}
return updated_note_id; // Return the current ID of the created/updated Note
}
//-------------------------------------------------------------------------------------------------------------------------------
// Check if the note is already present
// Point out the ID and the occurence of the already present note
//-------------------------------------------------------------------------------------------------------------------------------
private static bool NotifierAlreadyPresent(string desc, UINotifierType type, string title, bool isDialog,
out short updated_note_id, out short updated_note_occurence)
{
updated_note_id = 0;
updated_note_occurence = 0;
foreach (var note in Notes.Values)
{
short occurence = 0;
string filteredTitle = note.Title;
int index = filteredTitle.IndexOf(']');
if (index > 0)
{
string numberOccurence = filteredTitle.Substring(0, index); // Get occurrence from title
numberOccurence = numberOccurence.Trim(' ', ']', '[');
Int16.TryParse(numberOccurence, out occurence);
if (occurence > 1) // This will fix the note counter due to the
--occurence; // displayed note number that starts from "[2]"
filteredTitle = filteredTitle.Substring(index + 1).Trim();
}
if (note.Tag != null && // Get the node
note.Description == desc &&
note.IsDialog == isDialog &&
filteredTitle == title &&
note._uiNotifierType == type)
{
string hex_id = note.Tag.ToString().Split('|')[1]; // Get UINotifier ID
short id = Convert.ToInt16(hex_id, 16);
updated_note_id = id;
updated_note_occurence = ++occurence;
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Update the note with the new content. Reset the timeout if any
//-------------------------------------------------------------------------------------------------------------------------------
public static void Update(short ID, string desc, UINotifierType noteType, string title)
{
foreach (var note in Notes.Values)
{
if (note.Tag != null && // Get the node
note.Tag.Equals("__Notifier|" + ID.ToString("X4")))
{
note.timerResetEvent?.Set();
UINotifier myNote = note;
myNote.setNotifier(desc, noteType, title, true); // Set the new note content
}
}
}
#endregion NOTE CREATION AND MODIFY
#region TIMER
//-------------------------------------------------------------------------------------------------------------------------------
// Background Worker to handle the timeout of the note
//-------------------------------------------------------------------------------------------------------------------------------
private static void timer_DoWork(object sender, DoWorkEventArgs e)
{
UINotifier not = (UINotifier)e.Argument;
bool timedOut = false;
while (!timedOut)
{
if (!not.timerResetEvent.WaitOne(not.Timeout))
timedOut = true; // Time is out
}
e.Result = e.Argument;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Background Worker to handle the timeout event
//-------------------------------------------------------------------------------------------------------------------------------
private static void timer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UINotifier not = (UINotifier)e.Result;
not.closeMe(); // Close the note
}
#endregion TIMER
#region DIALOG NOTE CREATION
//-------------------------------------------------------------------------------------------------------------------------------
// Show a Dialog note: with faded background if specified
//-------------------------------------------------------------------------------------------------------------------------------
public static DialogResult ShowDialog(string content, UINotifierType type = UINotifierType.INFO, string title = "Notifier",
BackDialogStyle backDialogStyle = BackDialogStyle.FadedScreen, Form application = null)
{
Form back = null;
int backBorder = 200;
bool orgTopMostSettings = false;
if (backDialogStyle == BackDialogStyle.FadedApplication && application == null)
backDialogStyle = BackDialogStyle.FadedScreen;
if (backDialogStyle != BackDialogStyle.None)
{
back = new Form(); // Create the fade background
back.FormBorderStyle = FormBorderStyle.None;
back.BackColor = Color.FromArgb(0, 0, 0);
back.Opacity = 0.6;
back.ShowInTaskbar = false;
}
UINotifier note = new UINotifier(content, type, title, true); // Instantiate the UINotifier form
note.SetDPIScale();
note.backDialogStyle = backDialogStyle;
switch (note.backDialogStyle)
{
case BackDialogStyle.None:
if (application != null) // Set the startup position
{
note.Owner = application;
note.StartPosition = FormStartPosition.CenterParent;
}
else
{
note.StartPosition = FormStartPosition.CenterScreen;
}
break;
case BackDialogStyle.FadedScreen:
if (back != null && application != null)
{
back.Location = new Point(-backBorder, -backBorder);
back.Size = new Size(Screen.PrimaryScreen.WorkingArea.Width + backBorder,
Screen.PrimaryScreen.WorkingArea.Height + backBorder);
back.Show(application);
back.TopMost = true;
note.StartPosition = FormStartPosition.CenterScreen; // Set the startup position
}
break;
case BackDialogStyle.FadedApplication:
if (back != null && application != null)
{
orgTopMostSettings = application.TopMost;
application.TopMost = true;
back.StartPosition = FormStartPosition.Manual;
back.Size = application.Size;
back.Location = application.Location;
back.Show(application);
back.TopMost = true;
note.StartPosition = FormStartPosition.CenterParent; // Set the startup position
}
break;
}
Notes.TryAdd(note.ID, note); // Add to our collection of Notifiers
note.ShowInTaskbar = false;
note.ShowDialog();
back?.Close();
if (application != null) // restore app window top most property
application.TopMost = orgTopMostSettings;
return DialogResult.OK;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Show a Dialog note: fast creation
//-------------------------------------------------------------------------------------------------------------------------------
public static void ShowDialog(string content, string title = "Notifier", UINotifierType type = UINotifierType.INFO)
{
ShowDialog(content, type, title);
}
#endregion DIALOG NOTE CREATION
#region DRAG NOTE
//-------------------------------------------------------------------------------------------------------------------------------
// Handle the dragging event: change the position of the note
//-------------------------------------------------------------------------------------------------------------------------------
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (noteLocation.mouseIsDown)
{
int xDiff = noteLocation.initialLocation.X - e.Location.X; // Get the difference between the two points
int yDiff = noteLocation.initialLocation.Y - e.Location.Y;
int x = Location.X - xDiff; // Set the new point
int y = Location.Y - yDiff;
noteLocation.X = x; // Update the location
noteLocation.Y = y;
Location = new Point(x, y);
}
}
//-------------------------------------------------------------------------------------------------------------------------------
// Handle the mouse down event
//-------------------------------------------------------------------------------------------------------------------------------
private void OnMouseDown(object sender, MouseEventArgs e)
{
noteLocation.initialLocation = e.Location;
noteLocation.mouseIsDown = true;
}
//-------------------------------------------------------------------------------------------------------------------------------
// Handle the mouse up event
//-------------------------------------------------------------------------------------------------------------------------------
private void OnMouseUp(object sender, MouseEventArgs e)
{
noteLocation.mouseIsDown = false;
}
#endregion DRAG NOTE
private void UINotifier_Shown(object sender, EventArgs e)
{
closeAllToolStripMenuItem.Text = UIStyles.CurrentResources.CloseAll;
}
private void noteContent_Click(object sender, EventArgs e)
{
if (ItemClick != null)
{
ItemClick.Invoke(this, e);
closeMe();
}
}
public event EventHandler ItemClick;
} // Close Class
/// <summary>
/// 窗体背景风格
/// </summary>
public enum BackDialogStyle
{
/// <summary>
/// 无
/// </summary>
None,
/// <summary>
/// 全屏
/// </summary>
FadedScreen,
/// <summary>
/// 当前窗体
/// </summary>
FadedApplication
}
/// <summary>
/// 类型
/// </summary>
public enum UINotifierType
{
/// <summary>
/// 信息
/// </summary>
INFO,
/// <summary>
/// 警告
/// </summary>
WARNING,
/// <summary>
/// 错误
/// </summary>
ERROR,
/// <summary>
/// 正确
/// </summary>
OK,
/// <summary>
/// 询问
/// </summary>
Ask
}
}