Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 73 additions & 14 deletions docs/concepts/TrayIcon.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,24 @@ var wm = WindowManager.Get(MyWindow);
wm.IsVisibleInTray = true;
```

By default, if the user double-clicks this icon, the window is brought to the front, and if minimized, restored.
By default, if the user clicks/selects this icon, the window is brought to the front, and if minimized, restored.
The icon used is obtained from the Window's task bar icon. You can set the icon using the `Window.AppWindow.SetTaskbarIcon` method.

### Context menu and custom click actions
You can add additional actions to user-inactions by subscribing to the [`TrayIconInvoked`](https://dotmorten.github.io/WinUIEx/api/WinUIEx.WindowManager.TrayIconInvoked.html) event.
This event gives you the ability override the default double-click action by marking the event handled,
or adding any kind of flyout by setting the [`Flyout`](https://dotmorten.github.io/WinUIEx/api/WinUIEx.TrayIconInvokedEventArgs.Flyout.html#WinUIEx_TrayIconInvokedEventArgs_Flyout) property on the event argument.
You can add additional actions to user-inactions by subscribing to the [`TrayIconContextMenu`](https://dotmorten.github.io/WinUIEx/api/WinUIEx.WindowManager.TrayIconContextMenu.html) event.
This event gives you the ability override the display any kind of flyout by setting the
[`Flyout`](https://dotmorten.github.io/WinUIEx/api/WinUIEx.TrayIconEventArgs.Flyout.html#TrayIconEventArgs) property on the event argument.

For example:
```cs
wm.TrayIconInvoked += (w, e) =>
wm.TrayIconContextMenu += (w, e) =>
{
if (e.Type == TrayIconInvokeType.RightMouseUp)
{
var flyout = new MenuFlyout();
flyout.Items.Add(new MenuFlyoutItem() { Text = "Open" });
flyout.Items.Add(new MenuFlyoutItem() { Text = "Quit App" });
((MenuFlyoutItem)flyout.Items[0]).Click += (s, e) => MyWindow.Activate();
((MenuFlyoutItem)flyout.Items[1]).Click += (s, e) => MyWindow.Close();
e.Flyout = flyout;
}
var flyout = new MenuFlyout();
flyout.Items.Add(new MenuFlyoutItem() { Text = "Open" });
flyout.Items.Add(new MenuFlyoutItem() { Text = "Quit App" });
((MenuFlyoutItem)flyout.Items[0]).Click += (s, e) => MyWindow.Activate();
((MenuFlyoutItem)flyout.Items[1]).Click += (s, e) => MyWindow.Close();
e.Flyout = flyout;
};
```

Expand All @@ -51,4 +48,66 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
else
_window.Activate();
}
```

## Using the TrayIcon class

For more fine-grained control over the tray icon, you can use the [`TrayIcon`](https://dotmorten.github.io/WinUIEx/api/WinUIEx.TrayIcon.html) class directly.
This class allows you to create a tray icon without associating it with a window, and gives you more control over its behavior,
updating its icon and tooltip, or even have multiple icons for a single process.

Note: Make sure once you close your application, that all TrayIcon instances are disposed, otherwise the icon will remain in the tray and the process will not exit.
This behavior however also allows you to create a window-less application, or (if needed) creating a Window on demand based on TrayIcon interactions. For example:

```cs
public partial class App : Application
{
private TrayIcon icon;
private Window? _window;

public App()
{
InitializeComponent();
}

private Window GetMainWindow()
{
if (_window is not null)
return _window;
_window = new MainWindow();
_window.AppWindow.Closing += (s, e) =>
{
// Prevent closing so it can be re-activated later. We'll just hide it for now
// As an alternative don't cache the Window, but close and recreate a new Window every time.
e.Cancel = true;
s.Hide();
};
var wm = WindowManager.Get(_window);
wm.WindowStateChanged += (s, state) => wm.AppWindow.IsShownInSwitchers = state != WindowState.Minimized;
return _window;
}

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
// In OnLaunched we don't create a window but just the tray icon. We'll create a window later if we need to.
// Note: This icon will keep the application process alive as well.
icon = new TrayIcon(1, "Images/StatusOK.ico", "Test");
icon.IsVisible = true;
icon.Selected += (s, e) => GetMainWindow().Activate();
icon.ContextMenu += (w, e) =>
{
var flyout = new MenuFlyout();
flyout.Items.Add(new MenuFlyoutItem() { Text = "Open" });
((MenuFlyoutItem)flyout.Items[0]).Click += (s, e) => GetMainWindow().Activate();
flyout.Items.Add(new MenuFlyoutItem() { Text = "Quit App" });
((MenuFlyoutItem)flyout.Items[1]).Click += (s, e) =>
{
// Make sure we close both the main window, and the icon for the process to exit
_window?.Close();
icon.Dispose();
};
e.Flyout = flyout;
};
}
}
```
1 change: 1 addition & 0 deletions src/WinUIEx/HwndExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public static void SetWindowSize(IntPtr hwnd, double width, double height)
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="icon">Icon</param>
[Obsolete("Use AppWindow.SetTaskbarIcon")]
public static void SetTaskBarIcon(IntPtr hWnd, Icon? icon)
{
PInvoke.SendMessage(new HWND(hWnd), (uint)Messaging.WindowsMessages.WM_SETICON, new WPARAM(1), new LPARAM(icon?.Handle.Value ?? IntPtr.Zero));
Expand Down
1 change: 1 addition & 0 deletions src/WinUIEx/Icon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace WinUIEx
/// Manages a native Windows Icon instance
/// </summary>
[CreateFromString(MethodName = "WinUIEx.Icon.FromFile")]
[Obsolete]
public unsafe class Icon : IDisposable
{
private readonly HICON handle;
Expand Down
105 changes: 48 additions & 57 deletions src/WinUIEx/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ internal static unsafe bool Shell_NotifyIcon(uint dwMessage, in NOTIFYICONDATAW6
[DllImport("Shell32", ExactSpelling = true, EntryPoint = "Shell_NotifyIconW")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static extern unsafe bool Shell_NotifyIcon(uint dwMessage, NOTIFYICONDATAW64* lpData);

[DllImport("shell32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static extern int Shell_NotifyIconGetRect([In] ref NOTIFYICONIDENTIFIER identifier, [Out] out Windows.Graphics.RectInt32 iconLocation);
}

/// <summary>Contains information about a system appbar message.</summary>
Expand Down Expand Up @@ -306,7 +310,7 @@ internal partial struct NOTIFYICONDATAW32
/// <para><see href="https://docs.microsoft.com/windows/win32/api//shellapi/ns-shellapi-notifyicondataw#members">Read more on docs.microsoft.com</see>.</para>
/// </summary>
internal __ushort_256 szInfo;
internal _Anonymous_e__Union Anonymous;
internal uint VersionOrTimeout;
/// <summary>
/// <para>Type: <b>TCHAR[64]</b></para>
/// <para><b>Windows 2000 and later</b>. A null-terminated string that specifies a title for a balloon notification. This title appears in a larger font immediately above the text. It can have a maximum of 64 characters, including the terminating null character, but should be restricted to 48 characters in English to accommodate localization.</para>
Expand All @@ -333,36 +337,26 @@ internal partial struct NOTIFYICONDATAW32
/// </summary>
internal HICON hBalloonIcon;


internal struct __ushort_256
{
internal ushort _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, _71, _72, _73, _74, _75, _76, _77, _78, _79, _80, _81, _82, _83, _84, _85, _86, _87, _88, _89, _90, _91, _92, _93, _94, _95, _96, _97, _98, _99, _100, _101, _102, _103, _104, _105, _106, _107, _108, _109, _110, _111, _112, _113, _114, _115, _116, _117, _118, _119, _120, _121, _122, _123, _124, _125, _126, _127, _128, _129, _130, _131, _132, _133, _134, _135, _136, _137, _138, _139, _140, _141, _142, _143, _144, _145, _146, _147, _148, _149, _150, _151, _152, _153, _154, _155, _156, _157, _158, _159, _160, _161, _162, _163, _164, _165, _166, _167, _168, _169, _170, _171, _172, _173, _174, _175, _176, _177, _178, _179, _180, _181, _182, _183, _184, _185, _186, _187, _188, _189, _190, _191, _192, _193, _194, _195, _196, _197, _198, _199, _200, _201, _202, _203, _204, _205, _206, _207, _208, _209, _210, _211, _212, _213, _214, _215, _216, _217, _218, _219, _220, _221, _222, _223, _224, _225, _226, _227, _228, _229, _230, _231, _232, _233, _234, _235, _236, _237, _238, _239, _240, _241, _242, _243, _244, _245, _246, _247, _248, _249, _250, _251, _252, _253, _254, _255;
/// <summary>Always <c>256</c>.</summary>
internal int Length => 256;
/// <summary>
/// Gets a ref to an individual element of the inline array.
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned reference outlive the stack frame that defines it.
/// </summary>
internal ref ushort this[int index] => ref AsSpan()[index];
/// <summary>
/// Gets this inline array as a span.
/// </summary>
/// <remarks>
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned span outlive the stack frame that defines it.
/// </remarks>
internal Span<ushort> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 256);
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
internal partial struct _Anonymous_e__Union
{
[FieldOffset(0)]
internal uint uTimeout;
[FieldOffset(0)]
internal uint uVersion;
}
}

internal struct __ushort_256
{
internal ushort _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, _71, _72, _73, _74, _75, _76, _77, _78, _79, _80, _81, _82, _83, _84, _85, _86, _87, _88, _89, _90, _91, _92, _93, _94, _95, _96, _97, _98, _99, _100, _101, _102, _103, _104, _105, _106, _107, _108, _109, _110, _111, _112, _113, _114, _115, _116, _117, _118, _119, _120, _121, _122, _123, _124, _125, _126, _127, _128, _129, _130, _131, _132, _133, _134, _135, _136, _137, _138, _139, _140, _141, _142, _143, _144, _145, _146, _147, _148, _149, _150, _151, _152, _153, _154, _155, _156, _157, _158, _159, _160, _161, _162, _163, _164, _165, _166, _167, _168, _169, _170, _171, _172, _173, _174, _175, _176, _177, _178, _179, _180, _181, _182, _183, _184, _185, _186, _187, _188, _189, _190, _191, _192, _193, _194, _195, _196, _197, _198, _199, _200, _201, _202, _203, _204, _205, _206, _207, _208, _209, _210, _211, _212, _213, _214, _215, _216, _217, _218, _219, _220, _221, _222, _223, _224, _225, _226, _227, _228, _229, _230, _231, _232, _233, _234, _235, _236, _237, _238, _239, _240, _241, _242, _243, _244, _245, _246, _247, _248, _249, _250, _251, _252, _253, _254, _255;
/// <summary>Always <c>256</c>.</summary>
internal int Length => 256;
/// <summary>
/// Gets a ref to an individual element of the inline array.
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned reference outlive the stack frame that defines it.
/// </summary>
internal ref ushort this[int index] => ref AsSpan()[index];
/// <summary>
/// Gets this inline array as a span.
/// </summary>
/// <remarks>
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned span outlive the stack frame that defines it.
/// </remarks>
internal Span<ushort> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 256);
}

internal struct __ushort_64
{
Expand Down Expand Up @@ -454,7 +448,7 @@ internal partial struct NOTIFYICONDATAW64
/// <para><see href="https://docs.microsoft.com/windows/win32/api//shellapi/ns-shellapi-notifyicondataw#members">Read more on docs.microsoft.com</see>.</para>
/// </summary>
internal __ushort_256 szInfo;
internal _Anonymous_e__Union Anonymous;
internal uint VersionOrTimeout;
/// <summary>
/// <para>Type: <b>TCHAR[64]</b></para>
/// <para><b>Windows 2000 and later</b>. A null-terminated string that specifies a title for a balloon notification. This title appears in a larger font immediately above the text. It can have a maximum of 64 characters, including the terminating null character, but should be restricted to 48 characters in English to accommodate localization.</para>
Expand All @@ -480,33 +474,30 @@ internal partial struct NOTIFYICONDATAW64
/// <para><see href="https://docs.microsoft.com/windows/win32/api//shellapi/ns-shellapi-notifyicondataw#members">Read more on docs.microsoft.com</see>.</para>
/// </summary>
internal HICON hBalloonIcon;

internal struct __ushort_256
{
internal ushort _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, _71, _72, _73, _74, _75, _76, _77, _78, _79, _80, _81, _82, _83, _84, _85, _86, _87, _88, _89, _90, _91, _92, _93, _94, _95, _96, _97, _98, _99, _100, _101, _102, _103, _104, _105, _106, _107, _108, _109, _110, _111, _112, _113, _114, _115, _116, _117, _118, _119, _120, _121, _122, _123, _124, _125, _126, _127, _128, _129, _130, _131, _132, _133, _134, _135, _136, _137, _138, _139, _140, _141, _142, _143, _144, _145, _146, _147, _148, _149, _150, _151, _152, _153, _154, _155, _156, _157, _158, _159, _160, _161, _162, _163, _164, _165, _166, _167, _168, _169, _170, _171, _172, _173, _174, _175, _176, _177, _178, _179, _180, _181, _182, _183, _184, _185, _186, _187, _188, _189, _190, _191, _192, _193, _194, _195, _196, _197, _198, _199, _200, _201, _202, _203, _204, _205, _206, _207, _208, _209, _210, _211, _212, _213, _214, _215, _216, _217, _218, _219, _220, _221, _222, _223, _224, _225, _226, _227, _228, _229, _230, _231, _232, _233, _234, _235, _236, _237, _238, _239, _240, _241, _242, _243, _244, _245, _246, _247, _248, _249, _250, _251, _252, _253, _254, _255;
/// <summary>Always <c>256</c>.</summary>
internal int Length => 256;
/// <summary>
/// Gets a ref to an individual element of the inline array.
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned reference outlive the stack frame that defines it.
/// </summary>
internal ref ushort this[int index] => ref AsSpan()[index];
/// <summary>
/// Gets this inline array as a span.
/// </summary>
/// <remarks>
/// ⚠ Important ⚠: When this struct is on the stack, do not let the returned span outlive the stack frame that defines it.
/// </remarks>
internal Span<ushort> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 256);
}
}

[StructLayout(LayoutKind.Explicit)]
internal partial struct _Anonymous_e__Union
{
[FieldOffset(0)]
internal uint uTimeout;
[FieldOffset(0)]
internal uint uVersion;
}
[StructLayout(LayoutKind.Sequential)]
internal struct NOTIFYICONIDENTIFIER
{
public uint cbSize;
public nint hWnd;
public uint uID;
public Guid guidItem;
}

internal struct MINMAXINFO
{
#pragma warning disable CS0649
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
#pragma warning restore CS0649
}
internal struct POINT
{
public int X;
public int Y;
}
}
2 changes: 1 addition & 1 deletion src/WinUIEx/Messaging/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public override string ToString()
default:
break;
}
return $"{(WindowsMessages)MessageId}: LParam={LParam} WParam={WParam}";
return $"{(WindowsMessages)MessageId}: LParam=0x{LParam.ToString("x")} WParam=0x{WParam.ToString("x")}";
}
}
}
Loading
Loading