- add lib.notifyIcon

master
Simon G 5 years ago
parent 6d66c7ae5c
commit f57cf2978d
  1. 52
      Lib.NotifyIcon/INotifyIcon.cs
  2. 17
      Lib.NotifyIcon/Lib.NotifyIcon.csproj
  3. 85
      Lib.NotifyIcon/Windows/Native/NativeWindow.cs
  4. 15
      Lib.NotifyIcon/Windows/Native/Types/CustomWindowsMessage.cs
  5. 17
      Lib.NotifyIcon/Windows/Native/Types/MenuFlags.cs
  6. 22
      Lib.NotifyIcon/Windows/Native/Types/NIF.cs
  7. 23
      Lib.NotifyIcon/Windows/Native/Types/NIIF.cs
  8. 16
      Lib.NotifyIcon/Windows/Native/Types/NIM.cs
  9. 36
      Lib.NotifyIcon/Windows/Native/Types/NotifyIconData.cs
  10. 12
      Lib.NotifyIcon/Windows/Native/Types/Point.cs
  11. 23
      Lib.NotifyIcon/Windows/Native/Types/UFlags.cs
  12. 240
      Lib.NotifyIcon/Windows/Native/Types/WindowsMessage.cs
  13. 26
      Lib.NotifyIcon/Windows/Native/Types/WndClassEx.cs
  14. 68
      Lib.NotifyIcon/Windows/Native/WindowApi.cs
  15. 224
      Lib.NotifyIcon/Windows/NotifyIcon.cs
  16. 40
      Lib.NotifyIcon/Windows/NotifyIconHelperWindow.cs
  17. 6
      Mystify.sln

@ -0,0 +1,52 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using Avalonia.Controls;
namespace Lib.NotifyIcon
{
public interface INotifyIcon
{
/// <summary>
/// Gets or sets the path for the notify icon
/// </summary>
public string IconPath { get; set; }
/// <summary>
/// Gets or sets the tooltip text for the notify icon
/// </summary>
public string ToolTipText { get; set; }
/// <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; set; }
/// <summary>
/// Removes the notify icon from the taskbar notification area
/// </summary>
public void Remove();
/// <summary>
/// This event is raised when a user clicks on the notification icon
/// </summary>
public event EventHandler<EventArgs> Click;
/// <summary>
/// This event is raised when a user double clicks on the notification icon
/// </summary>
public event EventHandler<EventArgs> DoubleClick;
/// <summary>
/// This event is raised when a user right clicks on the notification icon
/// </summary>
public event EventHandler<EventArgs> RightClick;
}
}

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="Linux" />
<Folder Include="OSX" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.0" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
</ItemGroup>
</Project>

@ -0,0 +1,85 @@
// Author: Gockner, Simon
// Created: 2021-04-07
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Lib.NotifyIcon.Windows.Native.Types;
namespace Lib.NotifyIcon.Windows.Native
{
internal abstract class NativeWindow
{
private const uint WS_POPUP = 0x80000000;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly WindowApi.WndProc _wndProc; //We need to store the window proc as a field so that it doesn't get garbage collected away
private readonly string _className = "NativeHelperWindow" + Guid.NewGuid();
/// <summary>
/// The window handle of the underlying native window
/// </summary>
public IntPtr Handle { get; }
/// <summary>
/// Creates a new native (Win32) helper window for receiving window messages
/// </summary>
protected NativeWindow()
{
_wndProc = WndProc;
WndClassEx wndClassEx = new()
{
cbSize = Marshal.SizeOf<WndClassEx>(),
lpfnWndProc = _wndProc,
hInstance = WindowApi.GetModuleHandle(null),
lpszClassName = _className
};
ushort atom = WindowApi.RegisterClassEx(ref wndClassEx);
if (atom == 0)
throw new Win32Exception();
Handle = WindowApi.CreateWindowEx(0, atom, null, WS_POPUP, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (Handle == IntPtr.Zero)
throw new Win32Exception();
}
/// <summary>
/// Destructs the object and destroys the native window
/// </summary>
~NativeWindow()
{
if (Handle != IntPtr.Zero)
WindowApi.PostMessage(Handle, (uint) WindowsMessage.WmDestroy, IntPtr.Zero, IntPtr.Zero);
}
/// <summary>
/// This function will receive all the system window messages relevant to our window
/// </summary>
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case (uint) WindowsMessage.WmClose:
{
WindowApi.DestroyWindow(hWnd);
break;
}
case (uint) WindowsMessage.WmDestroy:
{
WindowApi.PostQuitMessage(0);
break;
}
default:
return WindowApi.DefWindowProc(hWnd, msg, wParam, lParam);
}
return IntPtr.Zero;
}
}
}

@ -0,0 +1,15 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
namespace Lib.NotifyIcon.Windows.Native.Types
{
/// <summary>
/// Custom Win32 window messages for the NotifyIcon
/// </summary>
internal enum CustomWindowsMessage : uint
{
WmTrayIcon = WindowsMessage.WmApp + 1024,
WmTrayMouse = WindowsMessage.WmUser + 1024
}
}

@ -0,0 +1,17 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
namespace Lib.NotifyIcon.Windows.Native.Types
{
[Flags]
internal enum MenuFlags : uint
{
MfString = 0,
MfByPosition = 0x400,
MfSeparator = 0x800,
MfRemove = 0x1000
}
}

@ -0,0 +1,22 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
namespace Lib.NotifyIcon.Windows.Native.Types
{
// ReSharper disable once InconsistentNaming
[Flags]
internal enum NIF : uint
{
Message = 0x00000001,
Icon = 0x00000002,
Tip = 0x00000004,
State = 0x00000008,
Info = 0x00000010,
Guid = 0x00000020,
Realtime = 0x00000040,
ShowTip = 0x00000080
}
}

@ -0,0 +1,23 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
namespace Lib.NotifyIcon.Windows.Native.Types
{
// ReSharper disable once InconsistentNaming
[Flags]
internal enum NIIF : uint
{
None = 0x00000000,
Info = 0x00000001,
Warning = 0x00000002,
Error = 0x00000003,
User = 0x00000004,
IconMask = 0x0000000F,
NoSound = 0x00000010,
LargeIcon = 0x00000020,
RespectQuietTime = 0x00000080
}
}

@ -0,0 +1,16 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
namespace Lib.NotifyIcon.Windows.Native.Types
{
// ReSharper disable once InconsistentNaming
internal enum NIM : uint
{
Add = 0x00000000,
Modify = 0x00000001,
Delete = 0x00000002,
SetFocus = 0x00000003,
SetVersion = 0x00000004
}
}

@ -0,0 +1,36 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using System.Runtime.InteropServices;
namespace Lib.NotifyIcon.Windows.Native.Types
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal class NotifyIconData
{
public int cbSize = Marshal.SizeOf<NotifyIconData>();
public IntPtr hWnd;
public int uID;
public NIF uFlags;
public int uCallbackMessage;
public IntPtr hIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szTip;
public int dwState = 0;
public int dwStateMask = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szInfo;
public int uTimeoutOrVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string szInfoTitle;
public NIIF dwInfoFlags;
}
}

@ -0,0 +1,12 @@
// Author: Gockner, Simon
// Created: 2021-04-07
// Copyright(c) 2021 SimonG. All Rights Reserved.
namespace Lib.NotifyIcon.Windows.Native.Types
{
internal struct Point
{
public int X;
public int Y;
}
}

@ -0,0 +1,23 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
namespace Lib.NotifyIcon.Windows.Native.Types
{
[Flags]
internal enum UFlags : uint
{
TpmLeftAlign = 0x0000,
TpmCenterAlign = 0x0004,
TpmRightAlign = 0x0008,
TpmTopAlign = 0x0000,
TpmVcenterAlign = 0x0010,
TpmBottomAlign = 0x0020,
TpmHorizontal = 0x0000,
TpmVertical = 0x0040,
TpmNoNotify = 0x0080,
TpmReturnCmd = 0x0100
}
}

@ -0,0 +1,240 @@
// Author: Gockner, Simon
// Created: 2021-04-07
// Copyright(c) 2021 SimonG. All Rights Reserved.
namespace Lib.NotifyIcon.Windows.Native.Types
{
internal enum WindowsMessage : uint
{
WmNull = 0x0000,
WmCreate = 0x0001,
WmDestroy = 0x0002,
WmMove = 0x0003,
WmSize = 0x0005,
WmActivate = 0x0006,
WmSetFocus = 0x0007,
WmKillFocus = 0x0008,
WmEnable = 0x000A,
WmSetRedraw = 0x000B,
WmSetText = 0x000C,
WmGetText = 0x000D,
WmGetTextLength = 0x000E,
WmPaint = 0x000F,
WmClose = 0x0010,
WmQueryEndSession = 0x0011,
WmQueryOpen = 0x0013,
WmEndSession = 0x0016,
WmQuit = 0x0012,
WmEraseBkgnd = 0x0014,
WmSysColorChange = 0x0015,
WmShowWindow = 0x0018,
WmWinIniChange = 0x001A,
WmSettingChange = WmWinIniChange,
WmDevModeChange = 0x001B,
WmActivateApp = 0x001C,
WmFontChange = 0x001D,
WmTimeChange = 0x001E,
WmCancelMode = 0x001F,
WmSetCursor = 0x0020,
WmMouseActivate = 0x0021,
WmChildActivate = 0x0022,
WmQueueSync = 0x0023,
WmGetMinMaxInfo = 0x0024,
WmPaintIcon = 0x0026,
WmIconEraseBkgnd = 0x0027,
WmNextDlgCtl = 0x0028,
WmSpoolerStatus = 0x002A,
WmDrawItem = 0x002B,
WmMeasureItem = 0x002C,
WmDeleteItem = 0x002D,
WmVKeyToItem = 0x002E,
WmCharToItem = 0x002F,
WmSetFont = 0x0030,
WmGetFont = 0x0031,
WmSetHotKey = 0x0032,
WmGetHotKey = 0x0033,
WmQueryDragIcon = 0x0037,
WmCompareItem = 0x0039,
WmGetObject = 0x003D,
WmCompacting = 0x0041,
WmWindowPosChanging = 0x0046,
WmWindowPosChanged = 0x0047,
WmCopyData = 0x004A,
WmCancelJournal = 0x004B,
WmNotify = 0x004E,
WmInputLangChangeRequest = 0x0050,
WmInputLangChange = 0x0051,
WmTCard = 0x0052,
WmHelp = 0x0053,
WmUserChanged = 0x0054,
WmNotifyFormat = 0x0055,
WmContextmenu = 0x007B,
WmStyleChanging = 0x007C,
WmStyleChanged = 0x007D,
WmDisplayChange = 0x007E,
WmGetIcon = 0x007F,
WmSetIcon = 0x0080,
WmNcCreate = 0x0081,
WmNcDestroy = 0x0082,
WmNcCalcSize = 0x0083,
WmNcHitTest = 0x0084,
WmNcPaint = 0x0085,
WmNcActivate = 0x0086,
WmGetDlgCode = 0x0087,
WmSyncPaint = 0x0088,
WmNcMousemove = 0x00A0,
WmNclButtonDown = 0x00A1,
WmNclButtonUp = 0x00A2,
WmNclButtonDblClk = 0x00A3,
WmNcrButtonDown = 0x00A4,
WmNcrButtonUp = 0x00A5,
WmNcrButtonDblClk = 0x00A6,
WmNcmButtonDown = 0x00A7,
WmNcmButtonUp = 0x00A8,
WmNcmButtonDblClk = 0x00A9,
WmNcxButtonDown = 0x00AB,
WmNcxButtonUp = 0x00AC,
WmNcxButtonDblClk = 0x00AD,
WmInputDeviceChange = 0x00FE,
WmInput = 0x00FF,
WmKeyFirst = 0x0100,
WmKeydown = 0x0100,
WmKeyup = 0x0101,
WmChar = 0x0102,
WmDeadChar = 0x0103,
WmSysKeyDown = 0x0104,
WmSysKeyUp = 0x0105,
WmSysChar = 0x0106,
WmSysDeadChar = 0x0107,
WmUniChar = 0x0109,
WmKeyLast = 0x0109,
WmImeStartComposition = 0x010D,
WmImeEndComposition = 0x010E,
WmImeComposition = 0x010F,
WmImeKeyLast = 0x010F,
WmInitDialog = 0x0110,
WmCommand = 0x0111,
WmSysCommand = 0x0112,
WmTimer = 0x0113,
WmHScroll = 0x0114,
WmVScroll = 0x0115,
WmInitMenu = 0x0116,
WmInitMenuPopup = 0x0117,
WmMenuselect = 0x011F,
WmMenuChar = 0x0120,
WmEnterIdle = 0x0121,
WmMenuRButtonUp = 0x0122,
WmMenuDrag = 0x0123,
WmMenuGetObject = 0x0124,
WmUnInitMenuPopup = 0x0125,
WmMenuCommand = 0x0126,
WmChangeUiState = 0x0127,
WmUpdateUiState = 0x0128,
WmQueryUiState = 0x0129,
WmCtlColorMsgBox = 0x0132,
WmCtlColorEdit = 0x0133,
WmCtlColorListBox = 0x0134,
WmCtlColorBtn = 0x0135,
WmCtlColorDlg = 0x0136,
WmCtlColorScrollBar = 0x0137,
WmCtlColorStatic = 0x0138,
WmMouseFirst = 0x0200,
WmMousemove = 0x0200,
WmLButtonDown = 0x0201,
WmLButtonUp = 0x0202,
WmLButtonDblClk = 0x0203,
WmRButtonDown = 0x0204,
WmRButtonUp = 0x0205,
WmRButtonDblClk = 0x0206,
WmMButtonDown = 0x0207,
WmMButtonUp = 0x0208,
WmMButtonDblClk = 0x0209,
WmMousewheel = 0x020A,
WmXButtonDown = 0x020B,
WmXButtonUp = 0x020C,
WmXButtonDblClk = 0x020D,
WmMouseHWheel = 0x020E,
WmMouseLast = 0x020E,
WmParentNotify = 0x0210,
WmEnterMenuLoop = 0x0211,
WmExitMenuLoop = 0x0212,
WmNextMenu = 0x0213,
WmSizing = 0x0214,
WmCaptureChanged = 0x0215,
WmMoving = 0x0216,
WmPowerBroadcast = 0x0218,
WmDevicechange = 0x0219,
WmMdiCreate = 0x0220,
WmMdiDestroy = 0x0221,
WmMdiActivate = 0x0222,
WmMdiRestore = 0x0223,
WmMdiNext = 0x0224,
WmMdiMaximize = 0x0225,
WmMdiTile = 0x0226,
WmMdiCascade = 0x0227,
WmMdiIconArrange = 0x0228,
WmMdiGetActive = 0x0229,
WmMdiSetMenu = 0x0230,
WmEnterSizeMove = 0x0231,
WmExitSizeMove = 0x0232,
WmDropFiles = 0x0233,
WmMdiRefreshMenu = 0x0234,
WmImeSetContext = 0x0281,
WmImeNotify = 0x0282,
WmImeControl = 0x0283,
WmImeCompositionFull = 0x0284,
WmImeSelect = 0x0285,
WmImeChar = 0x0286,
WmImeRequest = 0x0288,
WmImeKeydown = 0x0290,
WmImeKeyup = 0x0291,
WmMouseHover = 0x02A1,
WmMouseleave = 0x02A3,
WmNcMouseHover = 0x02A0,
WmNcMouseLeave = 0x02A2,
WmWtsSessionChange = 0x02B1,
WmTabletFirst = 0x02c0,
WmTabletLast = 0x02df,
WmDpiChanged = 0x02E0,
WmCut = 0x0300,
WmCopy = 0x0301,
WmPaste = 0x0302,
WmClear = 0x0303,
WmUndo = 0x0304,
WmRenderFormat = 0x0305,
WmRenderAllFormats = 0x0306,
WmDestroyClipboard = 0x0307,
WmDrawClipboard = 0x0308,
WmPaintClipboard = 0x0309,
WmVScrollClipboard = 0x030A,
WmSizeClipboard = 0x030B,
WmAskCbForMatName = 0x030C,
WmChangeCbChain = 0x030D,
WmHScrollClipboard = 0x030E,
WmQueryNewPalette = 0x030F,
WmPaletteIsChanging = 0x0310,
WmPaletteChanged = 0x0311,
WmHotkey = 0x0312,
WmPrint = 0x0317,
WmPrintClient = 0x0318,
WmAppCommand = 0x0319,
WmThemeChanged = 0x031A,
WmClipboardUpdate = 0x031D,
WmDwmCompositionChanged = 0x031E,
WmDwmNcRenderingChanged = 0x031F,
WmDwmColorizationColorChanged = 0x0320,
WmDwmWindowMaximizedChange = 0x0321,
WmGetTitleBarInfoEx = 0x033F,
WmHandheldFirst = 0x0358,
WmHandheldLast = 0x035F,
WmAfxFirst = 0x0360,
WmAfxLast = 0x037F,
WmPenWinFirst = 0x0380,
WmPenWinLast = 0x038F,
WmTouch = 0x0240,
WmApp = 0x8000,
WmUser = 0x0400,
WmDispatchWorkItem = WmUser
}
}

@ -0,0 +1,26 @@
// Author: Gockner, Simon
// Created: 2021-04-07
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using System.Runtime.InteropServices;
namespace Lib.NotifyIcon.Windows.Native.Types
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WndClassEx
{
public int cbSize;
public int style;
public WindowApi.WndProc lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
public IntPtr hIconSm;
}
}

@ -0,0 +1,68 @@
// Author: Gockner, Simon
// Created: 2021-04-06
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using Lib.NotifyIcon.Windows.Native.Types;
using Point = Lib.NotifyIcon.Windows.Native.Types.Point;
namespace Lib.NotifyIcon.Windows.Native
{
internal static class WindowApi
{
public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("shell32", CharSet = CharSet.Auto)]
public static extern int Shell_NotifyIcon(NIM dwMessage, NotifyIconData lpData);
[DllImport("user32.dll")]
public static extern void PostQuitMessage(int nExitCode);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr CreatePopupMenu();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool AppendMenu(IntPtr hMenu, MenuFlags uFlags, uint uIdNewItem, string lpNewItem);
[DllImport("user32.dll")]
public static extern uint TrackPopupMenuEx(IntPtr hMenu, UFlags uFlags, int x, int y, IntPtr hWnd, IntPtr lpTpm);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
uint lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")]
public static extern ushort RegisterClassEx(ref WndClassEx lpWcx);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
public static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
}

@ -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;
}
}

@ -0,0 +1,40 @@
// Author: Gockner, Simon
// Created: 2021-04-07
// Copyright(c) 2021 SimonG. All Rights Reserved.
using System;
using Lib.NotifyIcon.Windows.Native;
using Lib.NotifyIcon.Windows.Native.Types;
namespace Lib.NotifyIcon.Windows
{
/// <summary>
/// A native Win32 helper window encapsulation for dealing with the window messages sent by the notification icon
/// </summary>
internal class NotifyIconHelperWindow : NativeWindow
{
private readonly NotifyIcon _notifyIcon;
public NotifyIconHelperWindow(NotifyIcon notifyIcon) => _notifyIcon = notifyIcon;
/// <summary>
/// This function will receive all the system window messages relevant to our window.
/// </summary>
protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case (uint) CustomWindowsMessage.WmTrayMouse:
{
// Forward WM_TRAYMOUSE messages to the tray icon's window procedure
_notifyIcon.WndProc(hWnd, msg, wParam, lParam);
break;
}
default:
return base.WndProc(hWnd, msg, wParam, lParam);
}
return IntPtr.Zero;
}
}
}

@ -2,6 +2,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mystify", "Mystify\Mystify.csproj", "{87373A7B-B012-4E47-A875-0F189DE731E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.NotifyIcon", "Lib.NotifyIcon\Lib.NotifyIcon.csproj", "{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -12,5 +14,9 @@ Global
{87373A7B-B012-4E47-A875-0F189DE731E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87373A7B-B012-4E47-A875-0F189DE731E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87373A7B-B012-4E47-A875-0F189DE731E7}.Release|Any CPU.Build.0 = Release|Any CPU
{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Loading…
Cancel
Save