1

我搜索了当前在 .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项目吗?

提前致谢!

4

1 回答 1

1

我相信你现在已经找到了答案,但是没有“解决方案”并且有很多观点,所以让我给出一个关于如何解决这个问题的选项。

这实际上是一个非常常见的架构,像您这样的 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 作为演示平台的应用程序对话。

服务器与任何收听管道的人对话,如下所示:

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",
    });
}
于 2022-01-12T13:32:48.770 回答