C# Windows服务中使用.NET 5托盘图标

3

我搜索了有关在.NET环境中处理Windows系统/托盘图标的最佳实践,但没有找到最新信息。

考虑一个通常的.NET 5项目配置:

<PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

使用以下代码(Program.cs):
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<TrayIconService>();
    })
    .Build()
    .Run();

class TrayIconService : IHostedService, IAsyncDisposable
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // what is the recommended way to create a windows tray icon in .NET 5?
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await this.DisposeAsync();
    }

    public async ValueTask DisposeAsync()
    {
        // and how do I close the tray icon and dispose related resources afterwards?
    }
}

你能帮我用C#实现一个简单的'Hello World'右键菜单系统托盘图标吗?或者给我一些关于最新系统托盘图标使用的文档资料?

是否使用IHostedService接口是最好的考虑?我该如何引用Windows API?我需要一个net5.0-windows项目吗?

先感谢您的帮助!


2
托盘属于外壳,它在每个用户的会话中运行。托盘图标属于在同一会话中运行的应用程序。服务在0号会话中运行,必须与表示该空间中服务的任何应用程序实例进行通信。 - undefined
3
https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.notifyicon?view=net-5.0 似乎你仍需使用旧的Forms API。还有这个链接:https://github.com/HavenDV/H.NotifyIcon.WPF,但根据我搜索的结果,没有找到原生的WPF .NET 5方式。 - undefined
@sommmen жүҖд»ҘдҪ е»әи®®жҲ‘еңЁжҲ‘зҡ„WindowsжңҚеҠЎдёӯдҪҝз”ЁHavenDV/H.NotifyIcon.WPF NuGetеҢ…дҪңдёәи§ЈеҶіж–№жЎҲпјҹ - undefined
这里需要进行一些澄清:所谓的“托管服务”是一个更通用的概念,可能与Windows所称的“服务”没有任何关系。您可以将托管服务作为Windows服务运行,但是您不能拥有任何用户界面(除非通过IPC与另一个程序完成),或者您可以拥有一个带有托盘图标的常规应用程序,但那样就无法作为Windows服务运行。 - undefined
@JeroenMostert 我明白你的意思。那么,你如何在.NET 5中托管我的托盘图标,并将其配置为Windows服务?我已经编辑了我的问题。 - undefined
4
重点是:你不能。Windows服务无法显示托盘图标。(这不仅限于.NET。)您只能运行一个常规的用户应用程序来显示托盘图标(并使其在用户配置文件中启动),例如。如果您还需要作为服务运行的代码(始终处于运行状态,即使没有用户登录),并且您需要一个用户界面来配置它,则需要两个进程:Windows服务和显示图标的应用程序,需要某种形式的进程间通信来使应用程序与服务进行通信。你真的需要一个服务吗? - undefined
1个回答

4

我相信你现在已经找到答案了,但是并没有“解决方案”,而且有很多观点和看法,所以让我给你提供一个解决方法的选项。

实际上,这是一种常见的架构,Windows 希望你使用新的 Windows 11 MAUI API 的其他技术,但是用户并不喜欢“平铺式”设计,时间线充满了点击诱饵,不是与用户“交流”的可靠方式。

你可以通过在服务代码中启动托盘图标,使其成为仅限 Windows 的服务的几种方式之一。

基本上,你需要将服务与 System.Windows.Forms.NotifyIcon 和 TrayIcon.Visible 属性耦合起来。

通过 NotifyIcon,你可以做到以下操作:

class MyTray:IDisposable
{
   NotifyIcon ni;//needs disposed
    public ()
    {
       ni= new NotifyIcon()
       //use a helper class to generate a context menu for the tray icon if there is a menu like start, stop ...
       ni.ContextMenuStrip = new MyContextMenus(menuDataContext).Create();
     }

}

然后当你调用它时,你执行:
ni.Icon = Resources.Icon_red;
ni.Text = "Some ballon Text";

//让托盘图标可见 ni.Visible = true; 用户可以在托盘菜单上与图标交互 这是MyContextMenu(backingField)创建的示例:

public ContextMenuStrip Create()
{
    // Add the default menu options.
    menu = new ContextMenuStrip();
    ToolStripMenuItem item;
    ToolStripSeparator sep;

    item = new ToolStripMenuItem
    {
        Text = "License",
        Image = Resources.contract
    };
    item.Click += new EventHandler(License_Click);
    menu.Items.Add(item);


    // About.
    item = new ToolStripMenuItem
    {
        Text = "Service Status",
        Image = Resources.data_green1
    };
    item.Click += new EventHandler(Status_Click);

    menu.Items.Add(item);

    // Separator.
    sep = new ToolStripSeparator();
    menu.Items.Add(sep);

    //rule engine editor
    item = new ToolStripMenuItem
    {
        Text = "Rule Engine Editor",
        Image = Resources.data_edit1
    };

    item.Click += new System.EventHandler(Editor_Click);

    menu.Items.Add(item);

    // Separator.
    sep = new ToolStripSeparator();
    menu.Items.Add(sep);
    // Exit.
    item = new ToolStripMenuItem
    {
        Name = "mnuClose",
        Text = "Close",
        Image = Resources.data_down
    };
    item.Click += new EventHandler(Exit_Click);

    menu.Items.Add(item);
    return menu;
}

或者将其解耦,就像在这个示例中,服务可以在支持.net并通过像ProtoBuf、Sockets WCF或命名管道这样的协议进行通信的任何操作系统上运行。

也许有一种“更好”的方法来实现这个

看一下这篇文章

此示例使用命名管道(一种网络连接)与应用程序交流,使用NuGet包和WPF作为演示平台。

服务器像这样与任何监听Pipe的人交谈:

using H.Pipes;
using H.Pipes.Args;
using NamedPipesSample.Common;

namespace NamedPipesSample.WindowsService
{
    public class NamedPipesServer : IDisposable
    {
        const string PIPE_NAME = "samplepipe";

        private PipeServer<PipeMessage> server;

        public async Task InitializeAsync()
        {
            server = new PipeServer<PipeMessage>(PIPE_NAME);

            server.ClientConnected += async (o, args) => await OnClientConnectedAsync(args);
            server.ClientDisconnected += (o, args) => OnClientDisconnected(args);
            server.MessageReceived += (sender, args) => OnMessageReceived(args.Message);
            server.ExceptionOccurred += (o, args) => OnExceptionOccurred(args.Exception);

            await server.StartAsync();
        }

        private void OnClientConnected(ConnectionEventArgs<PipeMessage> args)
        {
            Console.WriteLine($"Client {args.Connection.Id} is now connected!");

            await args.Connection.WriteAsync(new PipeMessage
            {
                Action = ActionType.SendText,
                Text = "Hi from server"
            });
        }

        private void OnClientDisconnected(ConnectionEventArgs<PipeMessage> args)
        {
            Console.WriteLine($"Client {args.Connection.Id} disconnected");
        }

        //...
    }
}

如果您按照示例进行操作,作为托盘图标的WPF应用程序将被“修剪”为以下样式:
public async Task InitializeAsync()
{
    if (client != null && client.IsConnected)
        return;

    client = new PipeClient<PipeMessage>(pipeName);
    client.MessageReceived += (sender, args) => OnMessageReceived(args.Message);
    client.Disconnected += (o, args) => MessageBox.Show("Disconnected from server");
    client.Connected += (o, args) => MessageBox.Show("Connected to server");
    client.ExceptionOccurred += (o, args) => OnExceptionOccurred(args.Exception);

    await client.ConnectAsync();

    await client.WriteAsync(new PipeMessage
    {
        Action = ActionType.SendText,
        Text = "Hello from client",
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接