|
|
|
|
@ -0,0 +1,224 @@ |
|
|
|
|
// Author: Gockner, Simon |
|
|
|
|
// Created: 2021-04-06 |
|
|
|
|
// Copyright(c) 2021 SimonG. All Rights Reserved. |
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
using System.Drawing; |
|
|
|
|
using Avalonia; |
|
|
|
|
using Avalonia.Controls; |
|
|
|
|
using Avalonia.Platform; |
|
|
|
|
using Lib.NotifyIcon.Windows.Native; |
|
|
|
|
using Lib.NotifyIcon.Windows.Native.Types; |
|
|
|
|
using Point = Lib.NotifyIcon.Windows.Native.Types.Point; |
|
|
|
|
|
|
|
|
|
namespace Lib.NotifyIcon.Windows |
|
|
|
|
{ |
|
|
|
|
public class NotifyIcon : INotifyIcon |
|
|
|
|
{ |
|
|
|
|
private static int _nextUId; |
|
|
|
|
|
|
|
|
|
private readonly NotifyIconHelperWindow _helperWindow; |
|
|
|
|
private readonly int _uId; |
|
|
|
|
|
|
|
|
|
private bool _doubleClick; |
|
|
|
|
private bool _iconAdded; |
|
|
|
|
private Icon _icon; |
|
|
|
|
|
|
|
|
|
private string _iconPath = ""; |
|
|
|
|
private string _toolTipText = ""; |
|
|
|
|
private bool _visible; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new <see cref="NotifyIcon"/> instance and sets up some required resources |
|
|
|
|
/// </summary> |
|
|
|
|
public NotifyIcon() |
|
|
|
|
{ |
|
|
|
|
_uId = ++_nextUId; |
|
|
|
|
_helperWindow = new NotifyIconHelperWindow(this); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
~NotifyIcon() => UpdateIcon(remove: true); |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets or sets the path for the notify icon |
|
|
|
|
/// </summary> |
|
|
|
|
public string IconPath |
|
|
|
|
{ |
|
|
|
|
get => _iconPath; |
|
|
|
|
set |
|
|
|
|
{ |
|
|
|
|
_iconPath = value; |
|
|
|
|
|
|
|
|
|
IAssetLoader assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
|
|
|
|
_icon = new Icon(assetLoader.Open(new Uri(_iconPath))); |
|
|
|
|
|
|
|
|
|
UpdateIcon(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets or sets the tooltip text for the notify icon |
|
|
|
|
/// </summary> |
|
|
|
|
public string ToolTipText |
|
|
|
|
{ |
|
|
|
|
get => _toolTipText; |
|
|
|
|
set |
|
|
|
|
{ |
|
|
|
|
_toolTipText = value; |
|
|
|
|
UpdateIcon(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets or sets the context menu for the notify icon |
|
|
|
|
/// </summary> |
|
|
|
|
public ContextMenu ContextMenu { get; set; } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets or sets if the notify icon is visible in the taskbar notification area or not |
|
|
|
|
/// </summary> |
|
|
|
|
public bool Visible |
|
|
|
|
{ |
|
|
|
|
get => _visible; |
|
|
|
|
set |
|
|
|
|
{ |
|
|
|
|
_visible = value; |
|
|
|
|
UpdateIcon(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Removes the notify icon from the taskbar notification area |
|
|
|
|
/// </summary> |
|
|
|
|
public void Remove() => UpdateIcon(true); |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Handles the NotifyIcon-specific window messages sent by the notification icon |
|
|
|
|
/// </summary> |
|
|
|
|
public void WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) |
|
|
|
|
{ |
|
|
|
|
//We only care about tray icon messages |
|
|
|
|
if (msg != (uint) CustomWindowsMessage.WmTrayMouse) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
//Determine the type of message and call the matching event handlers |
|
|
|
|
switch (lParam.ToInt32()) |
|
|
|
|
{ |
|
|
|
|
case (int) WindowsMessage.WmLButtonUp: |
|
|
|
|
{ |
|
|
|
|
if (!_doubleClick) |
|
|
|
|
Click?.Invoke(this, new EventArgs()); |
|
|
|
|
|
|
|
|
|
_doubleClick = false; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case (int) WindowsMessage.WmLButtonDblClk: |
|
|
|
|
{ |
|
|
|
|
DoubleClick?.Invoke(this, new EventArgs()); |
|
|
|
|
_doubleClick = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case (int) WindowsMessage.WmRButtonUp: |
|
|
|
|
{ |
|
|
|
|
EventArgs e = new EventArgs(); |
|
|
|
|
RightClick?.Invoke(this, e); |
|
|
|
|
ShowContextMenu(); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Shows, hides or removes the notify icon based on the set properties and parameters |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="remove">If set to true, the notify icon will be removed</param> |
|
|
|
|
private void UpdateIcon(bool remove = false) |
|
|
|
|
{ |
|
|
|
|
NotifyIconData iconData = new() |
|
|
|
|
{ |
|
|
|
|
hWnd = _helperWindow.Handle, |
|
|
|
|
uID = _uId, |
|
|
|
|
uFlags = NIF.Tip | NIF.Message, |
|
|
|
|
uCallbackMessage = (int) CustomWindowsMessage.WmTrayMouse, |
|
|
|
|
hIcon = IntPtr.Zero, |
|
|
|
|
szTip = ToolTipText |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (!remove && _icon != null && Visible) |
|
|
|
|
{ |
|
|
|
|
iconData.uFlags |= NIF.Icon; |
|
|
|
|
iconData.hIcon = _icon.Handle; |
|
|
|
|
|
|
|
|
|
if (!_iconAdded) |
|
|
|
|
{ |
|
|
|
|
WindowApi.Shell_NotifyIcon(NIM.Add, iconData); |
|
|
|
|
_iconAdded = true; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
WindowApi.Shell_NotifyIcon(NIM.Modify, iconData); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
WindowApi.Shell_NotifyIcon(NIM.Delete, iconData); |
|
|
|
|
_iconAdded = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// If available, displays the notification icon's context menu |
|
|
|
|
/// </summary> |
|
|
|
|
private void ShowContextMenu() |
|
|
|
|
{ |
|
|
|
|
if (ContextMenu == null) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
// Since we can't use the Avalonia ContextMenu directly due to shortcomings |
|
|
|
|
// regrading its positioning, we'll create a native context menu instead. |
|
|
|
|
// This dictionary will map the menu item IDs which we'll need for the native |
|
|
|
|
// menu to the MenuItems of the provided Avalonia ContextMenu. |
|
|
|
|
Dictionary<uint, MenuItem> contextItemLookup = new(); |
|
|
|
|
|
|
|
|
|
// Create a native (Win32) popup menu as the notify icon's context menu. |
|
|
|
|
IntPtr popupMenu = WindowApi.CreatePopupMenu(); |
|
|
|
|
|
|
|
|
|
uint i = 1; |
|
|
|
|
foreach (var item in ContextMenu.Items) |
|
|
|
|
{ |
|
|
|
|
MenuItem menuItem = (MenuItem)item; |
|
|
|
|
|
|
|
|
|
// Add items to the native context menu by simply reusing |
|
|
|
|
// the information provided within the Avalonia ContextMenu. |
|
|
|
|
WindowApi.AppendMenu(popupMenu, MenuFlags.MfString, i, (string) menuItem.Header); |
|
|
|
|
|
|
|
|
|
// Add the mapping so that we can find the selected item later |
|
|
|
|
contextItemLookup.Add(i, menuItem); |
|
|
|
|
i++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// To display a context menu for a notification icon, the current window |
|
|
|
|
// must be the foreground window before the application calls TrackPopupMenu |
|
|
|
|
// or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user |
|
|
|
|
// clicks outside of the menu or the window that created the menu (if it is |
|
|
|
|
// visible). If the current window is a child window, you must set the |
|
|
|
|
// (top-level) parent window as the foreground window. |
|
|
|
|
WindowApi.SetForegroundWindow(_helperWindow.Handle); |
|
|
|
|
|
|
|
|
|
// Get the mouse cursor position |
|
|
|
|
WindowApi.GetCursorPos(out Point pt); |
|
|
|
|
|
|
|
|
|
// Now display the context menu and block until we get a result |
|
|
|
|
uint commandId = WindowApi.TrackPopupMenuEx(popupMenu, |
|
|
|
|
UFlags.TpmBottomAlign | UFlags.TpmRightAlign | UFlags.TpmNoNotify | UFlags.TpmReturnCmd, |
|
|
|
|
pt.X, pt.Y, _helperWindow.Handle, IntPtr.Zero); |
|
|
|
|
|
|
|
|
|
// If we have a result, execute the corresponding command |
|
|
|
|
if (commandId != 0) |
|
|
|
|
contextItemLookup[commandId].Command?.Execute(null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public event EventHandler<EventArgs> Click; |
|
|
|
|
public event EventHandler<EventArgs> DoubleClick; |
|
|
|
|
public event EventHandler<EventArgs> RightClick; |
|
|
|
|
} |
|
|
|
|
} |