You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
7.9 KiB
228 lines
7.9 KiB
// 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)
|
|
{
|
|
if (item is MenuItem menuItem)
|
|
{
|
|
// 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);
|
|
}
|
|
else if (item is Separator)
|
|
WindowApi.AppendMenu(popupMenu, MenuFlags.MfSeparator, i, null);
|
|
|
|
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;
|
|
}
|
|
} |