TrayIcon.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Windows.Input;
  5. using Avalonia.Collections;
  6. using Avalonia.Controls.ApplicationLifetimes;
  7. using Avalonia.Controls.Platform;
  8. using Avalonia.Platform;
  9. using Avalonia.Reactive;
  10. namespace Avalonia.Controls
  11. {
  12. public sealed class TrayIcons : AvaloniaList<TrayIcon>
  13. {
  14. }
  15. public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
  16. {
  17. private readonly ITrayIconImpl? _impl;
  18. private ICommand? _command;
  19. private TrayIcon(ITrayIconImpl? impl)
  20. {
  21. if (impl != null)
  22. {
  23. _impl = impl;
  24. _impl.SetIsVisible(IsVisible);
  25. _impl.OnClicked = () =>
  26. {
  27. Clicked?.Invoke(this, EventArgs.Empty);
  28. if (Command?.CanExecute(CommandParameter) == true)
  29. {
  30. Command.Execute(CommandParameter);
  31. }
  32. };
  33. }
  34. }
  35. public TrayIcon() : this(PlatformManager.CreateTrayIcon())
  36. {
  37. }
  38. static TrayIcon()
  39. {
  40. IconsProperty.Changed.Subscribe(args =>
  41. {
  42. if (args.Sender is Application)
  43. {
  44. if (args.OldValue.Value != null)
  45. {
  46. RemoveIcons(args.OldValue.Value);
  47. }
  48. if (args.NewValue.Value != null)
  49. {
  50. args.NewValue.Value.CollectionChanged += Icons_CollectionChanged;
  51. }
  52. }
  53. else
  54. {
  55. throw new InvalidOperationException("TrayIcon.Icons must be set on the Application.");
  56. }
  57. });
  58. var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized.");
  59. if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
  60. {
  61. lifetime.Exit += Lifetime_Exit;
  62. }
  63. }
  64. /// <summary>
  65. /// Raised when the TrayIcon is clicked.
  66. /// Note, this is only supported on Win32 and some Linux DEs,
  67. /// on OSX this event is not raised.
  68. /// </summary>
  69. public event EventHandler? Clicked;
  70. /// <summary>
  71. /// Defines the <see cref="Command"/> property.
  72. /// </summary>
  73. public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty =
  74. Button.CommandProperty.AddOwner<TrayIcon>(
  75. trayIcon => trayIcon.Command,
  76. (trayIcon, command) => trayIcon.Command = command,
  77. enableDataValidation: true);
  78. /// <summary>
  79. /// Defines the <see cref="CommandParameter"/> property.
  80. /// </summary>
  81. public static readonly StyledProperty<object?> CommandParameterProperty =
  82. Button.CommandParameterProperty.AddOwner<TrayIcon>();
  83. /// <summary>
  84. /// Defines the <see cref="TrayIcons"/> attached property.
  85. /// </summary>
  86. public static readonly AttachedProperty<TrayIcons?> IconsProperty
  87. = AvaloniaProperty.RegisterAttached<TrayIcon, Application, TrayIcons?>("Icons");
  88. /// <summary>
  89. /// Defines the <see cref="Menu"/> property.
  90. /// </summary>
  91. public static readonly StyledProperty<NativeMenu?> MenuProperty
  92. = AvaloniaProperty.Register<TrayIcon, NativeMenu?>(nameof(Menu));
  93. /// <summary>
  94. /// Defines the <see cref="Icon"/> property.
  95. /// </summary>
  96. public static readonly StyledProperty<WindowIcon?> IconProperty =
  97. Window.IconProperty.AddOwner<TrayIcon>();
  98. /// <summary>
  99. /// Defines the <see cref="ToolTipText"/> property.
  100. /// </summary>
  101. public static readonly StyledProperty<string?> ToolTipTextProperty =
  102. AvaloniaProperty.Register<TrayIcon, string?>(nameof(ToolTipText));
  103. /// <summary>
  104. /// Defines the <see cref="IsVisible"/> property.
  105. /// </summary>
  106. public static readonly StyledProperty<bool> IsVisibleProperty =
  107. Visual.IsVisibleProperty.AddOwner<TrayIcon>();
  108. public static void SetIcons(Application o, TrayIcons? trayIcons) => o.SetValue(IconsProperty, trayIcons);
  109. public static TrayIcons? GetIcons(Application o) => o.GetValue(IconsProperty);
  110. /// <summary>
  111. /// Gets or sets the <see cref="Command"/> property of a TrayIcon.
  112. /// </summary>
  113. public ICommand? Command
  114. {
  115. get => _command;
  116. set => SetAndRaise(CommandProperty, ref _command, value);
  117. }
  118. /// <summary>
  119. /// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
  120. /// <see cref="TrayIcon"/>.
  121. /// </summary>
  122. public object? CommandParameter
  123. {
  124. get { return GetValue(CommandParameterProperty); }
  125. set { SetValue(CommandParameterProperty, value); }
  126. }
  127. /// <summary>
  128. /// Gets or sets the Menu of the TrayIcon.
  129. /// </summary>
  130. public NativeMenu? Menu
  131. {
  132. get => GetValue(MenuProperty);
  133. set => SetValue(MenuProperty, value);
  134. }
  135. /// <summary>
  136. /// Gets or sets the icon of the TrayIcon.
  137. /// </summary>
  138. public WindowIcon? Icon
  139. {
  140. get => GetValue(IconProperty);
  141. set => SetValue(IconProperty, value);
  142. }
  143. /// <summary>
  144. /// Gets or sets the tooltip text of the TrayIcon.
  145. /// </summary>
  146. public string? ToolTipText
  147. {
  148. get => GetValue(ToolTipTextProperty);
  149. set => SetValue(ToolTipTextProperty, value);
  150. }
  151. /// <summary>
  152. /// Gets or sets the visibility of the TrayIcon.
  153. /// </summary>
  154. public bool IsVisible
  155. {
  156. get => GetValue(IsVisibleProperty);
  157. set => SetValue(IsVisibleProperty, value);
  158. }
  159. public INativeMenuExporter? NativeMenuExporter => _impl?.MenuExporter;
  160. private static void Lifetime_Exit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
  161. {
  162. var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized.");
  163. var trayIcons = GetIcons(app);
  164. if (trayIcons != null)
  165. {
  166. RemoveIcons(trayIcons);
  167. }
  168. }
  169. private static void Icons_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  170. {
  171. if (e.OldItems is not null)
  172. RemoveIcons(e.OldItems.Cast<TrayIcon>());
  173. }
  174. private static void RemoveIcons(IEnumerable<TrayIcon> icons)
  175. {
  176. foreach (var icon in icons)
  177. {
  178. icon.Dispose();
  179. }
  180. }
  181. /// <inheritdoc />
  182. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  183. {
  184. base.OnPropertyChanged(change);
  185. if (change.Property == IconProperty)
  186. {
  187. _impl?.SetIcon(Icon?.PlatformImpl);
  188. }
  189. else if (change.Property == IsVisibleProperty)
  190. {
  191. _impl?.SetIsVisible(change.GetNewValue<bool>());
  192. }
  193. else if (change.Property == ToolTipTextProperty)
  194. {
  195. _impl?.SetToolTipText(change.GetNewValue<string?>());
  196. }
  197. else if (change.Property == MenuProperty)
  198. {
  199. _impl?.MenuExporter?.SetNativeMenu(change.GetNewValue<NativeMenu?>());
  200. }
  201. }
  202. /// <summary>
  203. /// Disposes the tray icon (removing it from the tray area).
  204. /// </summary>
  205. public void Dispose() => _impl?.Dispose();
  206. }
  207. }