Files
Yaulw/WinForms/SysTray.cs
2016-02-15 12:32:26 -05:00

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
}
}