using System; using System.ComponentModel; using System.Drawing; using System.Reflection; using System.Windows.Forms; using JD.Kernel.Classes; namespace JD.MessageService { /// /// Used to show messages in the tray icon. /// /// This is a gui object and should be created in the Gui thread. public class TrayBalloon : IDisposable { private const int MaxTitleLength = 63; #region Variables private readonly object m_lock = new object(); private readonly Container m_components = new Container(); // Must be initialized on Main thread. private readonly NotifyIcon m_icon; // Use a custom timer instead of default timeout to hide the icon and to bypass the minimum timeout of NotifyIcon private readonly System.Timers.Timer m_timer; private Request m_currentRequest; #endregion #region Constructor public TrayBalloon() { m_icon = new NotifyIcon(m_components); m_icon.Visible = true; m_icon.Visible = false; // Necessary or the icon is erroneous if shown the first time from another thread than the main thread. m_icon.BalloonTipClicked += m_icon_Click; m_icon.MouseUp += m_icon_MouseUp; m_icon.Icon = ExtractApplicationIcon(); m_timer = new System.Timers.Timer(); m_timer.Elapsed += m_timer_Tick; } public void Dispose() { DisposableHelper.SafeDispose(m_timer); DisposableHelper.SafeDispose(m_components); DisposableHelper.SafeDispose(m_icon); } #endregion #region Methods /// /// Shows a balloon for the requested duration. If there was another balloon, the new call close the old one. /// public IBalloon Show(TimeSpan timeout, string title, string text, ToolTipIcon icon, Action clickHandler) { if (m_icon != null) { lock (m_lock) { // Make sure the balloon does not flicker to infinite when prompting the same message a lot of times. if (IsOnSameRequest(title, text)) { return m_currentRequest; } m_currentRequest = new Request(this); // No initialization on single line ... m_currentRequest.Title = title; m_currentRequest.Text = text; m_currentRequest.ClickHandler = clickHandler; m_icon.Visible = true; m_timer.Stop(); m_timer.Interval = (int)timeout.TotalMilliseconds; m_timer.Start(); } m_icon.ShowBalloonTip((int)timeout.TotalMilliseconds, Truncate(title, MaxTitleLength), Truncate(text, 255), icon); } return m_currentRequest; } private bool IsOnSameRequest(string title, string text) { var request = m_currentRequest; return request != null && request.Title == title && request.Text == text; } private static string Truncate(string value, int length) { if (value == null) { return string.Empty; } return value.Substring(0, Math.Min(value.Length, length)); } private static Icon ExtractApplicationIcon() { try { return Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location); } catch { return null; } } public void Hide() { lock (m_lock) { m_timer.Stop(); m_currentRequest = null; m_icon.Visible = false; } } #endregion #region Handlers void m_timer_Tick(object sender, EventArgs e) { Hide(); } void m_icon_Click(object sender, EventArgs e) { var currentRequest = m_currentRequest; if (currentRequest != null) { currentRequest.OnClick(); } } void m_icon_MouseUp(object sender, MouseEventArgs e) { var currentRequest = m_currentRequest; if (currentRequest != null) { currentRequest.OnIconMouseUp(); } } #endregion #region Nested private class Request : IBalloon { private readonly TrayBalloon m_balloon; private string m_title; public Request(TrayBalloon balloon) { m_balloon = balloon; } public void Hide() { if (IsActiveRequest) { m_balloon.Hide(); } } private bool IsActiveRequest { get { return m_balloon.m_currentRequest == this; } } public string Title { get { return m_title; } set { m_title = value; if (IsActiveRequest) { m_balloon.m_icon.Text = Truncate(m_title, MaxTitleLength); } } } public string Text { get; set; } public Action IconMouseUpHandler { get; set; } public void OnIconMouseUp() { var handler = IconMouseUpHandler; if (handler != null) { IconMouseUpHandler = null; handler(); } } public Action ClickHandler { get; set; } public void OnClick() { var handler = ClickHandler; if (handler != null) { ClickHandler = null; handler(); } } } #endregion } }