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 { /// /// 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. /// public class SysTray : IDisposable { #region Public States /// /// Allow caller to specify multipe states to show for the tray icon /// public struct TrayState { string nameOfState; string toolTip; Icon icon; } // keep track of added States private List _states = new List(); /// /// Add a possible state to the collection of tray icon states /// /// public void AddState(TrayState state) { _states.Add(state); } #endregion #region Public Properties /// /// Get/Set the Icon to show on the System Tray Notification /// public Icon Icon { get { return trayNotify.Icon; } set { trayNotify.Icon = value; } } /// /// Get/Set ToolTip to show over System Tray Notification /// public string toolTip { get { return trayNotify.Text; } set { trayNotify.Text = value; } } /// /// Is System Tray Notification Visible? /// 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 /// /// System Tray Left Mouse Click Delegate /// /// MouseEventArgs public delegate void SingleLeftMouseClick(MouseEventArgs e); /// /// Subscribe to get the Left Mouse Single Click Event /// public event SingleLeftMouseClick LeftMouseClick; /// /// System Tray Left Mouse Double Click Delegate /// /// MouseEventArgs public delegate void DoubleLeftMouseClick(MouseEventArgs e); /// /// Subscribe to get the Left Mouse Double Click Event /// public event DoubleLeftMouseClick LeftMouseDoubleClick; /// /// Initialize Context Menu Dynamically Call-back * i.e. On every Right Click * /// Allows the Context Menu to change depending on application state /// /// Expected a new ContextMenuStrip to Display /// Event Handle to handle Menu Click Events 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; /// /// Returns true if enough time since _LastFiredEvent has passed /// private bool EnoughTimeSinceLastEventHasElapsed { get { return (DateTime.Now.Ticks - _LastFiredEvent.Ticks) >= (TimeSpan.FromSeconds(_N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT).Ticks); } } #endregion #region Construction /// /// Construct a System Tray Icon (use public properties like ContextMenu,Icon,toolTip to customize further) /// /// Callback to initialize the Context Menu (can't be null) /// pass in an initial ToolTip to display, defaults to "" /// if null, defaults to systemIcons.Application 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; } /// /// Finalizer /// ~SysTray() { Dispose(true); } #endregion #region Click Event Handlers /// /// Called by NotifyIcon DoubleClick Event, We filter for only the left mouse double-click, /// event and fire event when neccessary /// /// /// 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)); } } } /// // Called by NotifyIcon Click Event, We filter for only the left mouse click, /// event and fire event when neccessary /// /// /// 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 } /// /// 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 /// /// /// 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(); } } /// /// 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) /// /// /// 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 /// /// (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 * /// 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; } } /// /// Calls AssignClickEventHandlerToMenuItem() for each Root Menu Item /// /// /// private void AssignClickEventHandlerToAllMenuItems(ContextMenuStrip menuStrip, EventHandler clickHandler) { if (menuStrip != null) { foreach (ToolStripMenuItem item in menuStrip.Items.OfType()) AssignClickEventHandlerToMenuItem(item, clickHandler); } } /// /// Recursive Function, in order to assign all MenuItems as well as /// all subsequent MenuItems, in the DropDownList, the ToolStripMI_Click /// event Handler /// /// pass in a ToolStripMenuItem to asign the click handler to /// 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()) AssignClickEventHandlerToMenuItem(_item, clickHandler); } } #endregion #region Show N' Hide /// /// Show the System Tray Icon /// public void Show() { // Create Context Menu InitializeContextMenuFromScratchUsingInitializer(); trayNotify.Visible = true; } /// /// Hide the System Tray Icon /// public void Hide() { trayNotify.Visible = false; } #endregion #region Public ShowBallon /// /// Type of Icon to show over Ballon /// public enum BallonIcon { None, Error, Warning, Info } /// /// Pops up a Ballon over the System Tray Icon /// /// Title to show on the Ballon Tip /// Text to show on the Ballon Tip /// Icon to show on the Ballon tip /// Specify the Timeout in Seconds (System mininimum is 10 seconds) 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 /// /// Dispose the Registry Handle /// public void Dispose() { Dispose(true); // Use SupressFinalize in case a subclass // of this type implements a finalizer GC.SuppressFinalize(this); } /// /// Dispose the Registry Handle /// /// true, if called from within 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 } }