409 lines
15 KiB
C#
409 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using Yaulw.Thread;
|
|
using System.Timers;
|
|
|
|
namespace Yaulw.WinForms
|
|
{
|
|
/// <remarks>
|
|
/// Wrapper Class around .Net NotifyIcon, to make it easier to work with an System Tray Icon.
|
|
/// Use the InitializeContextMenu Callback to create the ContextMenu.
|
|
/// Subscripe to the MouseLeftClick, and MouseLeftDoubleClick event to get accurate events to handle,
|
|
/// Call Show()/Hide() to show/hide the System Tray Icon, respectively.
|
|
/// </remarks>
|
|
public class SysTray : IDisposable
|
|
{
|
|
#region Public States
|
|
|
|
/// <summary>
|
|
/// Allow caller to specify multipe states to show for the tray icon
|
|
/// </summary>
|
|
public struct TrayState
|
|
{
|
|
string nameOfState;
|
|
string toolTip;
|
|
Icon icon;
|
|
}
|
|
|
|
// keep track of added States
|
|
private List<TrayState> _states = new List<TrayState>();
|
|
|
|
/// <summary>
|
|
/// Add a possible state to the collection of tray icon states
|
|
/// </summary>
|
|
/// <param name="state"></param>
|
|
public void AddState(TrayState state)
|
|
{
|
|
_states.Add(state);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Get/Set the Icon to show on the System Tray Notification
|
|
/// </summary>
|
|
public Icon Icon { get { return trayNotify.Icon; } set { trayNotify.Icon = value; } }
|
|
|
|
/// <summary>
|
|
/// Get/Set ToolTip to show over System Tray Notification
|
|
/// </summary>
|
|
public string toolTip { get { return trayNotify.Text; } set { trayNotify.Text = value; } }
|
|
|
|
/// <summary>
|
|
/// Is System Tray Notification Visible?
|
|
/// </summary>
|
|
public bool IsVisible { get { return trayNotify.Visible; } }
|
|
|
|
#endregion
|
|
|
|
#region Private Properties
|
|
|
|
private NotifyIcon trayNotify { get; set; }
|
|
private ContextMenu trayMenu { get { return trayNotify.ContextMenu; } set { trayNotify.ContextMenu = value; } }
|
|
private ContextMenuStrip trayMenuStrip { get { return trayNotify.ContextMenuStrip; } set { trayNotify.ContextMenuStrip = value; } }
|
|
|
|
#endregion
|
|
|
|
#region Public Events
|
|
|
|
/// <summary>
|
|
/// System Tray Left Mouse Click Delegate
|
|
/// </summary>
|
|
/// <param name="e">MouseEventArgs</param>
|
|
public delegate void SingleLeftMouseClick(MouseEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Subscribe to get the Left Mouse Single Click Event
|
|
/// </summary>
|
|
public event SingleLeftMouseClick LeftMouseClick;
|
|
|
|
/// <summary>
|
|
/// System Tray Left Mouse Double Click Delegate
|
|
/// </summary>
|
|
/// <param name="e">MouseEventArgs</param>
|
|
public delegate void DoubleLeftMouseClick(MouseEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Subscribe to get the Left Mouse Double Click Event
|
|
/// </summary>
|
|
public event DoubleLeftMouseClick LeftMouseDoubleClick;
|
|
|
|
/// <summary>
|
|
/// Initialize Context Menu Dynamically Call-back * i.e. On every Right Click *
|
|
/// Allows the Context Menu to change depending on application state
|
|
/// </summary>
|
|
/// <param name="trayMenuStrip">Expected a new ContextMenuStrip to Display</param>
|
|
/// <param name="MenuClickEventHandler">Event Handle to handle Menu Click Events</param>
|
|
public delegate void InitializeContextMenu(out ContextMenuStrip trayMenuStrip, out EventHandler MenuClickEventHandler);
|
|
|
|
#endregion
|
|
|
|
#region Private Members
|
|
|
|
private InitializeContextMenu _ContextMenuInitializer = null;
|
|
private TTimerDisp SingleClickDetectTimer = null;
|
|
private TimeSpan _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks);
|
|
private const int _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT = 350;
|
|
private const int _N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT = 2; // to avoid tripple clicks, etc... (only sends one double click)
|
|
private bool _disposed = false;
|
|
|
|
/// <summary>
|
|
/// Returns true if enough time since _LastFiredEvent has passed
|
|
/// </summary>
|
|
private bool EnoughTimeSinceLastEventHasElapsed
|
|
{
|
|
get
|
|
{
|
|
return (DateTime.Now.Ticks - _LastFiredEvent.Ticks) >= (TimeSpan.FromSeconds(_N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT).Ticks);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Construction
|
|
|
|
/// <summary>
|
|
/// Construct a System Tray Icon (use public properties like ContextMenu,Icon,toolTip to customize further)
|
|
/// </summary>
|
|
/// <param name="ContextMenuInitializer">Callback to initialize the Context Menu (can't be null)</param>
|
|
/// <param name="toolTip">pass in an initial ToolTip to display, defaults to ""</param>
|
|
/// <param name="icon">if null, defaults to systemIcons.Application</param>
|
|
public SysTray(InitializeContextMenu ContextMenuInitializer, string toolTip = "", Icon icon = null)
|
|
{
|
|
if (ContextMenuInitializer == null)
|
|
throw new ArgumentNullException("ContextMenuInitializer can not be Null");
|
|
|
|
// Imp, in order to initialize the Context Menu Everytime it is needed
|
|
_ContextMenuInitializer = ContextMenuInitializer;
|
|
|
|
// Create internal objects
|
|
this.trayNotify = new NotifyIcon();
|
|
this.SingleClickDetectTimer = new TTimerDisp(new ElapsedEventHandler(RealSingleClickDetectTimer_ElapsedEventHandler), _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT);
|
|
|
|
// Add Single / Double-Click Event Handlers
|
|
trayNotify.Click += new EventHandler(trayNotify_Click);
|
|
trayNotify.DoubleClick += new EventHandler(trayNotify_DoubleClick);
|
|
trayNotify.MouseDown += new MouseEventHandler(trayNotify_MouseDown);
|
|
|
|
// Set ToolTip
|
|
if (!String.IsNullOrEmpty(toolTip))
|
|
this.toolTip = toolTip;
|
|
|
|
// Set Icon
|
|
if (icon == null)
|
|
this.Icon = new Icon(SystemIcons.Application, 40, 40);
|
|
else
|
|
this.Icon = icon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizer
|
|
/// </summary>
|
|
~SysTray()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Click Event Handlers
|
|
|
|
/// <summary>
|
|
/// Called by NotifyIcon DoubleClick Event, We filter for only the left mouse double-click,
|
|
/// event and fire event when neccessary
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void trayNotify_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
MouseEventArgs args = (MouseEventArgs)e;
|
|
if (args.Button == MouseButtons.Left)
|
|
{
|
|
SingleClickDetectTimer.Stop();
|
|
if (LeftMouseDoubleClick != null && EnoughTimeSinceLastEventHasElapsed)
|
|
{
|
|
_LastFiredEvent = new TimeSpan(DateTime.Now.Ticks);
|
|
LeftMouseDoubleClick(new MouseEventArgs(MouseButtons.Left, 2, 0, 0, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
// Called by NotifyIcon Click Event, We filter for only the left mouse click,
|
|
/// event and fire event when neccessary
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void trayNotify_Click(object sender, EventArgs e)
|
|
{
|
|
MouseEventArgs args = (MouseEventArgs) e;
|
|
if (args.Button == MouseButtons.Left && EnoughTimeSinceLastEventHasElapsed)
|
|
SingleClickDetectTimer.Start(); // Start Single Click Detect Timer
|
|
}
|
|
|
|
/// <summary>
|
|
/// In order to accurately re-do a context menu, we handle MouseDown for the
|
|
/// Right-Mouse click. Mouse Down comes in before the click event, which gives
|
|
/// the caller an opportunity to handle/recreate the context menu dynamically, if needed
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
void trayNotify_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
MouseEventArgs args = (MouseEventArgs)e;
|
|
if (args.Button == MouseButtons.Right)
|
|
{
|
|
// Dynamically re-create Menu on every Right-Click
|
|
InitializeContextMenuFromScratchUsingInitializer();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used to detect ONLY Single Clicks, since a single-click and then a double-click fires,
|
|
/// we want to ignore the first click,and first see if a double-click comes in, if so, ignore
|
|
/// the single click, otherwise send it. (this is done by trayNotify_Click & transNotify_DoubleClick)
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void RealSingleClickDetectTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e)
|
|
{
|
|
SingleClickDetectTimer.Stop();
|
|
if (LeftMouseClick != null)
|
|
{
|
|
_LastFiredEvent = new TimeSpan(DateTime.Now.Ticks);
|
|
LeftMouseClick(new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Helpers
|
|
|
|
/// <summary>
|
|
/// (Re)Initialize Context Menu from scratch, allows us to build different menu's
|
|
/// depending on the application state * External Caller responsible for passing in
|
|
/// the context Menu and Click Event Handler *
|
|
/// </summary>
|
|
private void InitializeContextMenuFromScratchUsingInitializer()
|
|
{
|
|
if (_ContextMenuInitializer != null)
|
|
{
|
|
ContextMenuStrip menuStrip = null;
|
|
EventHandler clickHandler = null;
|
|
|
|
// Call Outside Caller's Initialize Context menu Function
|
|
_ContextMenuInitializer(out menuStrip, out clickHandler);
|
|
|
|
// Set up the Click Event
|
|
if (menuStrip != null && clickHandler != null)
|
|
AssignClickEventHandlerToAllMenuItems(menuStrip, clickHandler);
|
|
|
|
// Assign New Context Menu
|
|
trayMenuStrip = menuStrip;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls AssignClickEventHandlerToMenuItem() for each Root Menu Item
|
|
/// </summary>
|
|
/// <param name="menuStrip"></param>
|
|
/// <param name="clickHandler"></param>
|
|
private void AssignClickEventHandlerToAllMenuItems(ContextMenuStrip menuStrip, EventHandler clickHandler)
|
|
{
|
|
if (menuStrip != null)
|
|
{
|
|
foreach (ToolStripMenuItem item in menuStrip.Items.OfType<ToolStripMenuItem>())
|
|
AssignClickEventHandlerToMenuItem(item, clickHandler);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursive Function, in order to assign all MenuItems as well as
|
|
/// all subsequent MenuItems, in the DropDownList, the ToolStripMI_Click
|
|
/// event Handler
|
|
/// </summary>
|
|
/// <param name="item">pass in a ToolStripMenuItem to asign the click handler to</param>
|
|
/// <param name="clickHandler"></param>
|
|
private void AssignClickEventHandlerToMenuItem(ToolStripMenuItem item, EventHandler clickHandler)
|
|
{
|
|
if (item.DropDownItems == null || item.DropDownItems.Count == 0)
|
|
{
|
|
if(clickHandler != null)
|
|
item.Click += clickHandler;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
foreach (ToolStripMenuItem _item in item.DropDownItems.OfType<ToolStripMenuItem>())
|
|
AssignClickEventHandlerToMenuItem(_item, clickHandler);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Show N' Hide
|
|
|
|
/// <summary>
|
|
/// Show the System Tray Icon
|
|
/// </summary>
|
|
public void Show()
|
|
{
|
|
// Create Context Menu
|
|
InitializeContextMenuFromScratchUsingInitializer();
|
|
trayNotify.Visible = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hide the System Tray Icon
|
|
/// </summary>
|
|
public void Hide()
|
|
{
|
|
trayNotify.Visible = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public ShowBallon
|
|
|
|
/// <summary>
|
|
/// Type of Icon to show over Ballon
|
|
/// </summary>
|
|
public enum BallonIcon
|
|
{
|
|
None,
|
|
Error,
|
|
Warning,
|
|
Info
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pops up a Ballon over the System Tray Icon
|
|
/// </summary>
|
|
/// <param name="BallonTipTitle">Title to show on the Ballon Tip</param>
|
|
/// <param name="BallonTipText">Text to show on the Ballon Tip</param>
|
|
/// <param name="tipIcon">Icon to show on the Ballon tip</param>
|
|
/// <param name="nTimeoutInSeconds">Specify the Timeout in Seconds (System mininimum is 10 seconds)</param>
|
|
public void ShowBallon(string BallonTipTitle, string BallonTipText, BallonIcon tipIcon = BallonIcon.None, int nTimeoutInSeconds = 10)
|
|
{
|
|
ToolTipIcon _tipIcon = ToolTipIcon.None;
|
|
switch (tipIcon)
|
|
{
|
|
case BallonIcon.Error:
|
|
_tipIcon = ToolTipIcon.Error;
|
|
break;
|
|
case BallonIcon.Info:
|
|
_tipIcon = ToolTipIcon.Info;
|
|
break;
|
|
case BallonIcon.Warning:
|
|
_tipIcon = ToolTipIcon.Warning;
|
|
break;
|
|
}
|
|
trayNotify.ShowBalloonTip((int)TimeSpan.FromSeconds(nTimeoutInSeconds).TotalMilliseconds, BallonTipTitle, BallonTipText, _tipIcon);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable Members
|
|
|
|
/// <summary>
|
|
/// Dispose the Registry Handle
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
|
|
// Use SupressFinalize in case a subclass
|
|
// of this type implements a finalizer
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose the Registry Handle
|
|
/// </summary>
|
|
/// <param name="disposing">true, if called from within</param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (trayNotify != null)
|
|
trayNotify.Dispose();
|
|
}
|
|
|
|
// Indicate that the instance has been disposed.
|
|
trayNotify = null;
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|