如何使用 Avalonia 将应用程序加载到系统托盘

How to load an application to systemtray using Avalonia

如何将 Avalonia 应用程序加载到系统托盘并设置菜单项?

Avalonia 似乎是 UI/WPF library/resource,所以我认为这不会影响您的应用程序的运行方式。这将与 WPF 应用程序开发有关。

稍微阅读了一下,看来您可能需要使用 System.Windows.Forms.NotifyIcon

您可能希望在主应用程序的上下文中实例化该图标。

我使用 .NET Framework 创建了一个示例 WPF 应用程序(以便我能够引用 System.Windows.Forms)并且能够为我的应用程序显示一个系统托盘图标。

下面是一些示例代码:

    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : System.Windows.Application
    {
        NotifyIcon TrayIcon;
        public App()
        {
            // we initialize the tray icon in the application constructor
            // and have that reference for the lifetime of the application
            TrayIcon = new NotifyIcon()
            {
                Icon = SystemIcons.Information,
                ContextMenu = new ContextMenu(new MenuItem[] { new MenuItem("Show/Hide MyApp", ShowHide), new MenuItem("Exit", OnExit) }),
                Visible = true
            };
        }

        private void OnExit(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        private void ShowHide(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
    }

看来 Avalonia 确实提供了他们自己的 TrayIcon 版本。这是我在他们的 Source Code:

中找到的 class

TrayIcon.cs

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.Utilities;

namespace Avalonia.Controls
{
    public sealed class TrayIcons : AvaloniaList<TrayIcon>
    {
    }

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

            var app = Application.Current ?? throw new InvalidOperationException("Application not yet initialized.");

            if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
            {
                lifetime.Exit += Lifetime_Exit;
            }
        }

        /// <summary>
        /// Raised when the TrayIcon is clicked.
        /// Note, this is only supported on Win32 and some Linux DEs,
        /// on OS X this event is not raised.
        /// </summary>
        public event EventHandler? Clicked;

        /// <summary>
        /// Defines the <see cref="Command"/> property.
        /// </summary>
        public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty =
            Button.CommandProperty.AddOwner<TrayIcon>(
                trayIcon => trayIcon.Command,
                (trayIcon, command) => trayIcon.Command = command,
                enableDataValidation: true);

        /// <summary>
        /// Defines the <see cref="CommandParameter"/> property.
        /// </summary>
        public static readonly StyledProperty<object?> CommandParameterProperty =
            Button.CommandParameterProperty.AddOwner<MenuItem>();

        /// <summary>
        /// Defines the <see cref="TrayIcons"/> attached property.
        /// </summary>
        public static readonly AttachedProperty<TrayIcons> IconsProperty
            = AvaloniaProperty.RegisterAttached<TrayIcon, Application, TrayIcons>("Icons");

        /// <summary>
        /// Defines the <see cref="Menu"/> property.
        /// </summary>
        public static readonly StyledProperty<NativeMenu?> MenuProperty
            = AvaloniaProperty.Register<TrayIcon, NativeMenu?>(nameof(Menu));

        /// <summary>
        /// Defines the <see cref="Icon"/> property.
        /// </summary>
        public static readonly StyledProperty<WindowIcon?> IconProperty =
            Window.IconProperty.AddOwner<TrayIcon>();

        /// <summary>
        /// Defines the <see cref="ToolTipText"/> property.
        /// </summary>
        public static readonly StyledProperty<string?> ToolTipTextProperty =
            AvaloniaProperty.Register<TrayIcon, string?>(nameof(ToolTipText));

        /// <summary>
        /// Defines the <see cref="IsVisible"/> property.
        /// </summary>
        public static readonly StyledProperty<bool> IsVisibleProperty =
            Visual.IsVisibleProperty.AddOwner<TrayIcon>();

        public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons);

        public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty);

        /// <summary>
        /// Gets or sets the <see cref="Command"/> property of a TrayIcon.
        /// </summary>
        public ICommand? Command
        {
            get => _command;
            set => SetAndRaise(CommandProperty, ref _command, value);
        }

        /// <summary>
        /// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
        /// <see cref="TrayIcon"/>.
        /// </summary>
        public object? CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// Gets or sets the Menu of the TrayIcon.
        /// </summary>
        public NativeMenu? Menu
        {
            get => GetValue(MenuProperty);
            set => SetValue(MenuProperty, value);
        }

        /// <summary>
        /// Gets or sets the icon of the TrayIcon.
        /// </summary>
        public WindowIcon? Icon
        {
            get => GetValue(IconProperty);
            set => SetValue(IconProperty, value);
        }

        /// <summary>
        /// Gets or sets the tooltip text of the TrayIcon.
        /// </summary>
        public string? ToolTipText
        {
            get => GetValue(ToolTipTextProperty);
            set => SetValue(ToolTipTextProperty, value);
        }

        /// <summary>
        /// Gets or sets the visibility of the TrayIcon.
        /// </summary>
        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);

            RemoveIcons(trayIcons);
        }

        private static void Icons_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems is not null)
                RemoveIcons(e.OldItems.Cast<TrayIcon>());
        }

        private static void RemoveIcons(IEnumerable<TrayIcon> 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<bool>());
            }
            else if (change.Property == ToolTipTextProperty)
            {
                _impl?.SetToolTipText(change.GetNewValue<string?>());
            }
            else if (change.Property == MenuProperty)
            {
                _impl?.MenuExporter?.SetNativeMenu(change.GetNewValue<NativeMenu?>());
            }
        }

        /// <summary>
        /// Disposes the tray icon (removing it from the tray area).
        /// </summary>
        public void Dispose() => _impl?.Dispose();
    }
}

以及他们的一个应用示例,其中显示了一个示例实现:

App.xaml

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:ControlCatalog.ViewModels"
             x:DataType="vm:ApplicationViewModel"
             x:CompileBindings="True"
             Name="Avalonia ControlCatalog"
             x:Class="ControlCatalog.App">
  <Application.Styles>
    <Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3">
      <Setter Property="TextWrapping" Value="Wrap" />
    </Style>
    <Style Selector="TextBlock.h1">
      <Setter Property="FontSize" Value="16" />
      <Setter Property="FontWeight" Value="Medium" />
    </Style>
    <Style Selector="TextBlock.h2">
      <Setter Property="FontSize" Value="14" />
    </Style>
    <Style Selector="TextBlock.h3">
      <Setter Property="FontSize" Value="12" />
    </Style>
    <Style Selector="Label.h1">
      <Setter Property="FontSize" Value="16" />
      <Setter Property="FontWeight" Value="Medium" />
    </Style>
    <Style Selector="Label.h2">
      <Setter Property="FontSize" Value="14" />
    </Style>
    <Style Selector="Label.h3">
      <Setter Property="FontSize" Value="12" />
    </Style>
    <StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
  </Application.Styles>
  <TrayIcon.Icons>
    <TrayIcons>
      <TrayIcon Icon="/Assets/test_icon.ico" ToolTipText="Avalonia Tray Icon ToolTip">
        <TrayIcon.Menu>
          <NativeMenu>
            <NativeMenuItem Header="Settings">
              <NativeMenu>
                <NativeMenuItem Header="Option 1" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
                <NativeMenuItem Header="Option 2" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
                <NativeMenuItemSeparator />
                <NativeMenuItem Header="Option 3" ToggleType="CheckBox" IsChecked="True" Command="{Binding ToggleCommand}" />
                <NativeMenuItem Icon="/Assets/test_icon.ico" Header="Restore Defaults" Command="{Binding ToggleCommand}" />
              </NativeMenu>
            </NativeMenuItem>
            <NativeMenuItem Header="Exit" Command="{Binding ExitCommand}" />
          </NativeMenu>
        </TrayIcon.Menu>
      </TrayIcon>
    </TrayIcons>
  </TrayIcon.Icons>
</Application>

他们确实有一些文档 here

不幸的是,他们的文档网站很难导航,而且在使用他们的搜索功能时似乎没有提及任何关于“TrayIcon”的内容。

幸运的是,他们似乎也有一个 Customer Support 团队,您可以随时 post 直接在他们的 GitHub 位置向他们的支持团队提问:

https://github.com/AvaloniaUI/Avalonia/discussions