如何使用 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
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();
}
}
以及他们的一个应用示例,其中显示了一个示例实现:
<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 位置向他们的支持团队提问:
如何将 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:
中找到的 classusing 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();
}
}
以及他们的一个应用示例,其中显示了一个示例实现:
<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 位置向他们的支持团队提问: