.NET 5 托盘图标在 C# Windows 服务中的使用
.NET 5 Tray Icon Usage in C# Windows Service
我搜索了在 .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' 上下文菜单 windows 系统托盘图标 and/or 给我一些关于最先进的尝试图标用法的文档?
IHostedService
的实施是否是最好的考虑?我如何引用 windows API?我需要一个 net5.0-windows
项目吗?
提前致谢!
我相信你现在已经找到了答案,但是没有“解决方案”,而且有很多观点,所以让我给出一个如何解决这个问题的选项。
这实际上是一个很常见的架构,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 = 真;
用户可以与托盘菜单上的图标进行交互
这里是 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 的 OS 上,并通过 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",
});
}
我搜索了在 .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' 上下文菜单 windows 系统托盘图标 and/or 给我一些关于最先进的尝试图标用法的文档?
IHostedService
的实施是否是最好的考虑?我如何引用 windows API?我需要一个 net5.0-windows
项目吗?
提前致谢!
我相信你现在已经找到了答案,但是没有“解决方案”,而且有很多观点,所以让我给出一个如何解决这个问题的选项。
这实际上是一个很常见的架构,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 = 真; 用户可以与托盘菜单上的图标进行交互 这里是 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 的 OS 上,并通过 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",
});
}