using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Platform; using Avalonia.Reactive; namespace Avalonia.Controls { public sealed class TrayIcons : AvaloniaList { } public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable { private readonly ITrayIconImpl? _impl; private ICommand? _command; private TrayIcon(ITrayIconImpl? impl) { if (impl != null) { _impl = impl; _impl.SetIsVisible(IsVisible); _impl.OnClicked = () => { Clicked?.Invoke(this, EventArgs.Empty); if (Command?.CanExecute(CommandParameter) == true) { Command.Execute(CommandParameter); } }; } } public TrayIcon() : this(PlatformManager.CreateTrayIcon()) { } static TrayIcon() { IconsProperty.Changed.Subscribe(args => { if (args.Sender is Application) { if (args.OldValue.Value != null) { RemoveIcons(args.OldValue.Value); } if (args.NewValue.Value != null) { args.NewValue.Value.CollectionChanged += Icons_CollectionChanged; } } else { throw new InvalidOperationException("TrayIcon.Icons must be set on the Application."); } }); var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized."); if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) { lifetime.Exit += Lifetime_Exit; } } /// /// Raised when the TrayIcon is clicked. /// Note, this is only supported on Win32 and some Linux DEs, /// on OSX this event is not raised. /// public event EventHandler? Clicked; /// /// Defines the property. /// public static readonly DirectProperty CommandProperty = Button.CommandProperty.AddOwner( trayIcon => trayIcon.Command, (trayIcon, command) => trayIcon.Command = command, enableDataValidation: true); /// /// Defines the property. /// public static readonly StyledProperty CommandParameterProperty = Button.CommandParameterProperty.AddOwner(); /// /// Defines the attached property. /// public static readonly AttachedProperty IconsProperty = AvaloniaProperty.RegisterAttached("Icons"); /// /// Defines the property. /// public static readonly StyledProperty MenuProperty = AvaloniaProperty.Register(nameof(Menu)); /// /// Defines the property. /// public static readonly StyledProperty IconProperty = Window.IconProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty ToolTipTextProperty = AvaloniaProperty.Register(nameof(ToolTipText)); /// /// Defines the property. /// public static readonly StyledProperty IsVisibleProperty = Visual.IsVisibleProperty.AddOwner(); public static void SetIcons(Application o, TrayIcons? trayIcons) => o.SetValue(IconsProperty, trayIcons); public static TrayIcons? GetIcons(Application o) => o.GetValue(IconsProperty); /// /// Gets or sets the property of a TrayIcon. /// public ICommand? Command { get => _command; set => SetAndRaise(CommandProperty, ref _command, value); } /// /// Gets or sets the parameter to pass to the property of a /// . /// public object? CommandParameter { get { return GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } /// /// Gets or sets the Menu of the TrayIcon. /// public NativeMenu? Menu { get => GetValue(MenuProperty); set => SetValue(MenuProperty, value); } /// /// Gets or sets the icon of the TrayIcon. /// public WindowIcon? Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); } /// /// Gets or sets the tooltip text of the TrayIcon. /// public string? ToolTipText { get => GetValue(ToolTipTextProperty); set => SetValue(ToolTipTextProperty, value); } /// /// Gets or sets the visibility of the TrayIcon. /// public bool IsVisible { get => GetValue(IsVisibleProperty); set => SetValue(IsVisibleProperty, value); } public INativeMenuExporter? NativeMenuExporter => _impl?.MenuExporter; private static void Lifetime_Exit(object? sender, ControlledApplicationLifetimeExitEventArgs e) { var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized."); var trayIcons = GetIcons(app); if (trayIcons != null) { RemoveIcons(trayIcons); } } private static void Icons_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems is not null) RemoveIcons(e.OldItems.Cast()); } private static void RemoveIcons(IEnumerable icons) { foreach (var icon in icons) { icon.Dispose(); } } /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == IconProperty) { _impl?.SetIcon(Icon?.PlatformImpl); } else if (change.Property == IsVisibleProperty) { _impl?.SetIsVisible(change.GetNewValue()); } else if (change.Property == ToolTipTextProperty) { _impl?.SetToolTipText(change.GetNewValue()); } else if (change.Property == MenuProperty) { _impl?.MenuExporter?.SetNativeMenu(change.GetNewValue()); } } /// /// Disposes the tray icon (removing it from the tray area). /// public void Dispose() => _impl?.Dispose(); } }