using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using CPF.Drawing;
using System.Globalization;
using CPF.Controls;
using CPF.Input;
using CPF.Platform;
namespace CPF.Windows
{
public sealed class NotifyIcon : CpfObject, INotifyIconImpl
{
private const int WM_TRAYMOUSEMESSAGE = (int)UnmanagedMethods.WindowsMessage.WM_USER + 1024;
private static int WM_TASKBARCREATED = UnmanagedMethods.RegisterWindowMessage("TaskbarCreated");
private object syncObj = new object();
IntPtr iconHandle;
//private Image icon = null;
private string text = "";
private int id = 0;
private bool added = false;
private NotifyIconNativeWindow window = null;
private static int nextId = 0;
//private object userData;
private bool doubleClick = false; // checks if doubleclick is fired
// Visible defaults to false, but the NotifyIconDesigner makes it seem like the default is
// true. We do this because while visible is the more common case, if it was a true default,
// there would be no way to create a hidden NotifyIcon without being visible for a moment.
private bool visible = false;
///
///
///
/// Initializes a new instance of the class.
///
///
public NotifyIcon()
{
id = ++nextId;
window = new NotifyIconNativeWindow(this);
UpdateIcon(visible);
}
///
///
/// [This event is raised on the NIN_BALLOONUSERCLICK message.]
///
public event EventHandler BalloonTipClicked
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
/// [This event is raised on the NIN_BALLOONTIMEOUT message.]
///
public event EventHandler BalloonTipClosed
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
/// [This event is raised on the NIN_BALLOONSHOW or NIN_BALLOONHIDE message.]
///
public event EventHandler BalloonTipShown
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
[PropertyMetadata("")]
public string BalloonTipText
{
get
{
return GetValue();
}
set
{
SetValue(value);
}
}
public static bool IsEnumValid(Enum enumValue, int value, int minValue, int maxValue)
{
bool valid = (value >= minValue) && (value <= maxValue);
return valid;
}
///
///
///
/// Gets or sets the BalloonTip icon displayed when
/// the mouse hovers over a system tray icon.
///
///
public ToolTipIcon BalloonTipIcon
{
get
{
return GetValue();
}
set
{
//valid values are 0x0 to 0x3
if (!IsEnumValid(value, (int)value, (int)ToolTipIcon.None, (int)ToolTipIcon.Error))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(ToolTipIcon));
}
SetValue(value);
}
}
///
///
///
/// Gets or sets the BalloonTip title displayed when
/// the mouse hovers over a system tray icon.
///
///
[PropertyMetadata("")]
public string BalloonTipTitle
{
get
{
return GetValue();
}
set
{
SetValue(value);
}
}
///
///
///
/// Gets or sets context menu
/// for the tray icon.
///
///
public ContextMenu ContextMenu
{
get
{
return GetValue();
}
set
{
SetValue(value);
}
}
///
///
///
/// Gets or sets the current
/// icon.
///
///
public Image Icon
{
get
{
return GetValue();
}
set
{
SetValue(value);
}
}
[PropertyChanged(nameof(Icon))]
void OnIcon(object newValue, object oldValue, CPF.PropertyMetadataAttribute attribute)
{
UpdateIcon(visible);
}
///
///
///
/// Gets or sets the ToolTip text displayed when
/// the mouse hovers over a system tray icon.
///
///
public string Text
{
get
{
return text;
}
set
{
if (value == null) value = "";
if (value != null && !value.Equals(this.text))
{
if (value != null && value.Length > 63)
{
throw new ArgumentOutOfRangeException("Text", value, "文字太长,不能超过63个字符");
}
this.text = value;
if (added)
{
UpdateIcon(true);
}
}
}
}
///
///
///
/// Gets or sets a value indicating whether the icon is visible in the Windows System Tray.
///
///
public bool Visible
{
get
{
return visible;
}
set
{
if (visible != value)
{
UpdateIcon(value);
visible = value;
}
}
}
///
///
/// Occurs when the user clicks the icon in the system tray.
///
public event EventHandler Click
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
/// Occurs when the user double-clicks the icon in the system tray.
///
public event EventHandler DoubleClick
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
public event EventHandler MouseClick
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
public event EventHandler MouseDoubleClick
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
///
/// Occurs when the
/// user presses a mouse button while the pointer is over the icon in the system tray.
///
///
public event EventHandler MouseDown
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
///
/// Occurs
/// when the user moves the mouse pointer over the icon in the system tray.
///
///
public event EventHandler MouseMove
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
///
/// Occurs when the
/// user releases the mouse button while the pointer
/// is over the icon in the system tray.
///
///
public event EventHandler MouseUp
{
add
{
AddHandler(value);
}
remove
{
RemoveHandler(value);
}
}
///
///
///
/// Disposes of the resources (other than memory) used by the
/// .
///
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (window != null)
{
this.Icon = null;
this.Text = String.Empty;
UpdateIcon(false);
window.DestroyHandle();
window = null;
ContextMenu = null;
if (iconHandle != IntPtr.Zero)
{
UnmanagedMethods.DestroyIcon(iconHandle);
}
iconHandle = IntPtr.Zero;
}
}
//else
//{
// // This same post is done in ControlNativeWindow's finalize method, so if you change
// // it, change it there too.
// //
// if (window != null && window.Handle != IntPtr.Zero)
// {
// UnmanagedMethods.PostMessage(new HandleRef(window, window.Handle), UnmanagedMethods.WindowsMessage.WM_CLOSE, 0, 0);
// window.ReleaseHandle();
// }
//}
base.Dispose(disposing);
}
private void OnBalloonTipClicked()
{
this.RaiseEvent(EventArgs.Empty, nameof(BalloonTipClicked));
}
///
///
///
/// This method raised the BalloonTipClosed event.
///
///
private void OnBalloonTipClosed()
{
this.RaiseEvent(EventArgs.Empty, nameof(BalloonTipClosed));
}
///
///
///
/// This method raised the BalloonTipShown event.
///
///
private void OnBalloonTipShown()
{
this.RaiseEvent(EventArgs.Empty, nameof(BalloonTipShown));
}
///
///
///
/// This method actually raises the Click event. Inheriting classes should
/// override this if they wish to be notified of a Click event. (This is far
/// preferable to actually adding an event handler.) They should not,
/// however, forget to call base.onClick(e); before exiting, to ensure that
/// other recipients do actually get the event.
///
///
private void OnClick(EventArgs e)
{
RaiseEvent(e, nameof(Click));
}
///
///
/// Inheriting classes should override this method to handle this event.
/// Call base.onDoubleClick to send this event to any registered event listeners.
///
private void OnDoubleClick(EventArgs e)
{
RaiseEvent(e, nameof(DoubleClick));
}
///
///
/// Inheriting classes should override this method to handle this event.
/// Call base.OnMouseClick to send this event to any registered event listeners.
///
private void OnMouseClick(NotifyIconMouseEventArgs mea)
{
RaiseEvent(mea, nameof(MouseClick));
}
///
///
/// Inheriting classes should override this method to handle this event.
/// Call base.OnMouseDoubleClick to send this event to any registered event listeners.
///
private void OnMouseDoubleClick(NotifyIconMouseEventArgs mea)
{
RaiseEvent(mea, nameof(MouseDoubleClick));
}
///
///
///
/// Raises the event.
/// Inheriting classes should override this method to handle this event.
/// Call base.onMouseDown to send this event to any registered event listeners.
///
///
///
private void OnMouseDown(NotifyIconMouseEventArgs e)
{
RaiseEvent(new Input.NotifyIconMouseEventArgs(e.Button), nameof(MouseDown));
}
///
///
///
/// Inheriting classes should override this method to handle this event.
/// Call base.onMouseMove to send this event to any registered event listeners.
///
///
///
private void OnMouseMove(NotifyIconMouseEventArgs e)
{
RaiseEvent(e, nameof(MouseMove));
}
///
///
///
/// Inheriting classes should override this method to handle this event.
/// Call base.onMouseUp to send this event to any registered event listeners.
///
///
private void OnMouseUp(NotifyIconMouseEventArgs e)
{
RaiseEvent(new Input.NotifyIconMouseEventArgs(e.Button), nameof(MouseUp));
}
///
///
///
/// Displays a balloon tooltip in the taskbar.
///
/// The system enforces minimum and maximum timeout values. Timeout
/// values that are too large are set to the maximum value and values
/// that are too small default to the minimum value. The operating system's
/// default minimum and maximum timeout values are 10 seconds and 30 seconds,
/// respectively.
///
/// No more than one balloon ToolTip at at time is displayed for the taskbar.
/// If an application attempts to display a ToolTip when one is already being displayed,
/// the ToolTip will not appear until the existing balloon ToolTip has been visible for at
/// least the system minimum timeout value. For example, a balloon ToolTip with timeout
/// set to 30 seconds has been visible for seven seconds when another application attempts
/// to display a balloon ToolTip. If the system minimum timeout is ten seconds, the first
/// ToolTip displays for an additional three seconds before being replaced by the second ToolTip.
///
///
public void ShowBalloonTip(int timeout)
{
ShowBalloonTip(timeout, this.BalloonTipTitle, this.BalloonTipText, this.BalloonTipIcon);
}
///
///
///
/// Displays a balloon tooltip in the taskbar with the specified title,
/// text, and icon for a duration of the specified timeout value.
///
/// The system enforces minimum and maximum timeout values. Timeout
/// values that are too large are set to the maximum value and values
/// that are too small default to the minimum value. The operating system's
/// default minimum and maximum timeout values are 10 seconds and 30 seconds,
/// respectively.
///
/// No more than one balloon ToolTip at at time is displayed for the taskbar.
/// If an application attempts to display a ToolTip when one is already being displayed,
/// the ToolTip will not appear until the existing balloon ToolTip has been visible for at
/// least the system minimum timeout value. For example, a balloon ToolTip with timeout
/// set to 30 seconds has been visible for seven seconds when another application attempts
/// to display a balloon ToolTip. If the system minimum timeout is ten seconds, the first
/// ToolTip displays for an additional three seconds before being replaced by the second ToolTip.
///
///
public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon)
{
if (timeout < 0)
{
throw new ArgumentOutOfRangeException("timeout", "ShowBalloonTip超时" + timeout);
}
if (string.IsNullOrEmpty(tipText))
{
throw new ArgumentException("tipText不能为空");
}
//valid values are 0x0 to 0x3
if (!IsEnumValid(tipIcon, (int)tipIcon, (int)ToolTipIcon.None, (int)ToolTipIcon.Error))
{
throw new InvalidEnumArgumentException("tipIcon", (int)tipIcon, typeof(ToolTipIcon));
}
if (added)
{
// Bail if in design mode...
//if (DesignMode)
//{
// return;
//}
//IntSecurity.UnrestrictedWindows.Demand();
UnmanagedMethods.NOTIFYICONDATA data = new UnmanagedMethods.NOTIFYICONDATA();
if (window.Handle == IntPtr.Zero)
{
window.CreateHandle(new CreateParams());
}
data.hWnd = window.Handle;
data.uID = id;
data.uFlags = UnmanagedMethods.NIF_INFO;
data.uTimeoutOrVersion = timeout;
data.szInfoTitle = tipTitle;
data.szInfo = tipText;
switch (tipIcon)
{
case ToolTipIcon.Info: data.dwInfoFlags = UnmanagedMethods.NIIF_INFO; break;
case ToolTipIcon.Warning: data.dwInfoFlags = UnmanagedMethods.NIIF_WARNING; break;
case ToolTipIcon.Error: data.dwInfoFlags = UnmanagedMethods.NIIF_ERROR; break;
case ToolTipIcon.None: data.dwInfoFlags = UnmanagedMethods.NIIF_NONE; break;
}
UnmanagedMethods.Shell_NotifyIcon(UnmanagedMethods.NIM_MODIFY, data);
}
}
///
///
/// Shows the context menu for the tray icon.
///
///
private void ShowContextMenu()
{
UnmanagedMethods.SetForegroundWindow(window.Handle);
var contextMenu = ContextMenu;
if (contextMenu != null)
{
//UnmanagedMethods.POINT pt;
//UnmanagedMethods.GetCursorPos(out pt);
// VS7 #38994
// The solution to this problem was found in MSDN Article ID: Q135788.
// Summary: the current window must be made the foreground window
// before calling TrackPopupMenuEx, and a task switch must be
// forced after the call.
if (contextMenu != null)
{
contextMenu.Placement = PlacementMode.Mouse;
contextMenu.PopupMarginBottm = 1;
contextMenu.PopupMarginTop = "auto";
contextMenu.IsOpen = true;
//contextMenu.OnPopup(EventArgs.Empty);
//SafeNativeMethods.TrackPopupMenuEx(new HandleRef(contextMenu, contextMenu.Handle),
// UnmanagedMethods.TPM_VERTICAL | UnmanagedMethods.TPM_RIGHTALIGN,
// pt.x,
// pt.y,
// new HandleRef(window, window.Handle),
// null);
//// Force task switch (see above)
//UnmanagedMethods.PostMessage(new HandleRef(window, window.Handle), UnmanagedMethods.WindowsMessage.WM_NULL, IntPtr.Zero, IntPtr.Zero);
}
}
}
///
///
/// Updates the icon in the system tray.
///
///
private void UpdateIcon(bool showIconInTray)
{
lock (syncObj)
{
// Bail if in design mode...
//
//if (DesignMode)
//{
// return;
//}
//IntSecurity.UnrestrictedWindows.Demand();
window.LockReference(showIconInTray);
UnmanagedMethods.NOTIFYICONDATA data = new UnmanagedMethods.NOTIFYICONDATA();
data.uCallbackMessage = WM_TRAYMOUSEMESSAGE;
data.uFlags = UnmanagedMethods.NIF_MESSAGE;
if (showIconInTray)
{
if (window.Handle == IntPtr.Zero)
{
window.CreateHandle(new CreateParams());
}
}
data.hWnd = window.Handle;
data.uID = id;
data.hIcon = IntPtr.Zero;
data.szTip = null;
if (iconHandle != IntPtr.Zero)
{
UnmanagedMethods.DestroyIcon(iconHandle);
}
iconHandle = IntPtr.Zero;
if (Icon != null)
{
data.uFlags |= UnmanagedMethods.NIF_ICON;
var stream = Icon.SaveToStream(ImageFormat.Png);
var states = UnmanagedMethods.GdipCreateBitmapFromStream(new GPStream(stream), out IntPtr bitmap);
stream.Dispose();
UnmanagedMethods.GdipCreateHICONFromBitmap(bitmap, out IntPtr hIcon);
UnmanagedMethods.GdipDisposeImage(bitmap);
iconHandle = hIcon;
data.hIcon = iconHandle;
}
data.uFlags |= UnmanagedMethods.NIF_TIP;
data.szTip = text;
if (showIconInTray && Icon != null)
{
if (!added)
{
UnmanagedMethods.Shell_NotifyIcon(UnmanagedMethods.NIM_ADD, data);
added = true;
}
else
{
UnmanagedMethods.Shell_NotifyIcon(UnmanagedMethods.NIM_MODIFY, data);
}
}
else if (added)
{
UnmanagedMethods.Shell_NotifyIcon(UnmanagedMethods.NIM_DELETE, data);
added = false;
}
}
}
///
///
/// Handles the mouse-down event
///
///
private void WmMouseDown(ref Message m, MouseButton button, int clicks)
{
if (clicks == 2)
{
OnDoubleClick(new NotifyIconMouseEventArgs(button, 2));
OnMouseDoubleClick(new NotifyIconMouseEventArgs(button, 2));
doubleClick = true;
}
OnMouseDown(new NotifyIconMouseEventArgs(button, clicks));
}
///
///
/// Handles the mouse-move event
///
///
private void WmMouseMove(ref Message m)
{
OnMouseMove(new NotifyIconMouseEventArgs(MouseButton.None, 0));
}
///
///
/// Handles the mouse-up event
///
///
private void WmMouseUp(ref Message m, MouseButton button)
{
OnMouseUp(new NotifyIconMouseEventArgs(button, 0));
//subhag
if (!doubleClick)
{
OnClick(new NotifyIconMouseEventArgs(button, 0));
OnMouseClick(new NotifyIconMouseEventArgs(button, 0));
}
doubleClick = false;
}
private void WmTaskbarCreated(ref Message m)
{
added = false;
UpdateIcon(visible);
}
private void WndProc(ref Message msg)
{
switch ((UnmanagedMethods.WindowsMessage)msg.Msg)
{
case (UnmanagedMethods.WindowsMessage)WM_TRAYMOUSEMESSAGE:
switch ((UnmanagedMethods.WindowsMessage)msg.LParam)
{
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDBLCLK:
WmMouseDown(ref msg, MouseButton.Left, 2);
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
WmMouseDown(ref msg, MouseButton.Left, 1);
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP:
WmMouseUp(ref msg, MouseButton.Left);
break;
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDBLCLK:
WmMouseDown(ref msg, MouseButton.Middle, 2);
break;
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN:
WmMouseDown(ref msg, MouseButton.Middle, 1);
break;
case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP:
WmMouseUp(ref msg, MouseButton.Middle);
break;
case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE:
WmMouseMove(ref msg);
break;
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDBLCLK:
WmMouseDown(ref msg, MouseButton.Right, 2);
break;
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN:
WmMouseDown(ref msg, MouseButton.Right, 1);
break;
case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP:
//if (ContextMenu != null)
{
ShowContextMenu();
}
WmMouseUp(ref msg, MouseButton.Right);
break;
case (UnmanagedMethods.WindowsMessage)UnmanagedMethods.NIN_BALLOONSHOW:
OnBalloonTipShown();
break;
case (UnmanagedMethods.WindowsMessage)UnmanagedMethods.NIN_BALLOONHIDE:
OnBalloonTipClosed();
break;
case (UnmanagedMethods.WindowsMessage)UnmanagedMethods.NIN_BALLOONTIMEOUT:
OnBalloonTipClosed();
break;
case (UnmanagedMethods.WindowsMessage)UnmanagedMethods.NIN_BALLOONUSERCLICK:
OnBalloonTipClicked();
break;
}
break;
case UnmanagedMethods.WindowsMessage.WM_COMMAND:
if (IntPtr.Zero == msg.LParam)
{
//if (Command.DispatchID((int)msg.WParam & 0xFFFF)) return;
}
else
{
window.DefWndProc(ref msg);
}
break;
case UnmanagedMethods.WindowsMessage.WM_DRAWITEM:
// If the wparam is zero, then the message was sent by a menu.
// See WM_DRAWITEM in MSDN.
if (msg.WParam == IntPtr.Zero)
{
//WmDrawItemMenuItem(ref msg);
}
break;
case UnmanagedMethods.WindowsMessage.WM_MEASUREITEM:
// If the wparam is zero, then the message was sent by a menu.
if (msg.WParam == IntPtr.Zero)
{
//WmMeasureMenuItem(ref msg);
}
break;
case UnmanagedMethods.WindowsMessage.WM_INITMENUPOPUP:
WmInitMenuPopup(ref msg);
break;
case UnmanagedMethods.WindowsMessage.WM_DESTROY:
// Remove the icon from the taskbar
UpdateIcon(false);
break;
default:
if (msg.Msg == WM_TASKBARCREATED)
{
WmTaskbarCreated(ref msg);
}
window.DefWndProc(ref msg);
break;
}
}
private void WmInitMenuPopup(ref Message m)
{
//if (contextMenu != null)
//{
// if (contextMenu.ProcessInitMenuPopup(m.WParam))
// {
// return;
// }
//}
window.DefWndProc(ref m);
}
//private void WmMeasureMenuItem(ref Message m)
//{
// // Obtain the menu item object
// UnmanagedMethods.MEASUREITEMSTRUCT mis = (UnmanagedMethods.MEASUREITEMSTRUCT)m.GetLParam(typeof(UnmanagedMethods.MEASUREITEMSTRUCT));
// Debug.Assert(m.LParam != IntPtr.Zero, "m.lparam is null");
// // A pointer to the correct MenuItem is stored in the measure item
// // information sent with the message.
// // (See MenuItem.CreateMenuItemInfo)
// MenuItem menuItem = MenuItem.GetMenuItemFromItemData(mis.itemData);
// Debug.Assert(menuItem != null, "UniqueID is not associated with a menu item");
// // Delegate this message to the menu item
// if (menuItem != null)
// {
// menuItem.WmMeasureItem(ref m);
// }
//}
//private void WmDrawItemMenuItem(ref Message m)
//{
// // Obtain the menu item object
// UnmanagedMethods.DRAWITEMSTRUCT dis = (UnmanagedMethods.DRAWITEMSTRUCT)m.GetLParam(typeof(UnmanagedMethods.DRAWITEMSTRUCT));
// // A pointer to the correct MenuItem is stored in the draw item
// // information sent with the message.
// // (See MenuItem.CreateMenuItemInfo)
// MenuItem menuItem = MenuItem.GetMenuItemFromItemData(dis.itemData);
// // Delegate this message to the menu item
// if (menuItem != null)
// {
// menuItem.WmDrawItem(ref m);
// }
//}
///
///
/// Defines a placeholder window that the NotifyIcon is attached to.
///
///
private class NotifyIconNativeWindow : NativeWindow
{
internal NotifyIcon reference;
private GCHandle rootRef; // We will root the control when we do not want to be elligible for garbage collection.
///
///
/// Create a new NotifyIcon, and bind the window to the NotifyIcon component.
///
///
internal NotifyIconNativeWindow(NotifyIcon component)
{
reference = component;
}
~NotifyIconNativeWindow()
{
// This same post is done in Control's Dispose method, so if you change
// it, change it there too.
//
if (Handle != IntPtr.Zero)
{
UnmanagedMethods.PostMessage(Handle, (uint)UnmanagedMethods.WindowsMessage.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
// This releases the handle from our window proc, re-routing it back to
// the system.
}
public void LockReference(bool locked)
{
if (locked)
{
if (!rootRef.IsAllocated)
{
rootRef = GCHandle.Alloc(reference, GCHandleType.Normal);
}
}
else
{
if (rootRef.IsAllocated)
{
rootRef.Free();
}
}
}
///
///
/// Pass messages on to the NotifyIcon object's wndproc handler.
///
///
protected override void WndProc(ref Message m)
{
Debug.Assert(reference != null, "NotifyIcon was garbage collected while it was still visible. How did we let that happen?");
reference.WndProc(ref m);
}
}
}
///
public enum ToolTipIcon : byte
{
///
///
///
/// No Icon.
///
///
None = 0,
///
///
///
/// A Information Icon.
///
///
Info = 1,
///
///
///
/// A Warning Icon.
///
///
Warning = 2,
///
///
///
/// A Error Icon.
///
///
Error = 3
}
public class NotifyIconMouseEventArgs : EventArgs
{
public NotifyIconMouseEventArgs(MouseButton button, int clicks)
{
Button = button;
Clicks = clicks;
}
public MouseButton Button { get; set; }
public int Clicks { get; set; }
}
}