效果图
功能
-
添加任务栏图标 ( 托盘图标 )
-
添加右键菜单 (打开浏览器 / 控制台 /退出)
-
双击打开控制台,
-
-
启动一个web服务器 ( MVC , Razor , Blazor 等等 )
-
web服务器启动成功,自动打开浏览器页面
-
持续后台运行
由于使用 Winform 实现, 所以只支持windows
开发环境
- VS 2022
- .NET 6
代码
.csproj (项目的配置文件)
! 非常重要 !
// 移除
// <TargetFramework>net6.0</TargetFramework>
// 改为 ↓
<TargetFramework>net6.0-windows</TargetFramework> // 修改
<UseWindowsForms>true</UseWindowsForms> // 添加
有这两个才能在web项目中使用WinForm类库
,
否则会找不到 System.Windows.Forms
类库
完整代码如下:
ImplicitUsings
可以在编译时引入框架约定的通用的命名空间,
当然其中也包括了 System.Windows.Forms
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Update="appicon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Program.cs
public static class Program
{
//[STAThread]
static void Main(string[] args)
{
#if DEBUG
WebProgram.RunWebHost(args);
#else
Console.Title = "我的程序名";
// 初始化 托盘图标 和 隐藏控制台
NotifyIconUtils.Init();
// 新开一个线程运行 web服务器
Task.Run(() => WebProgram.RunWebHost(args));
// 消息循环, 处理托盘图标的点击操作, 并保持程序的持续运行,
// 使用 Application.Exit() 退出
Application.Run();
#endif
}
}
WebProgram.cs
非常普通的一个 .NET 6 WebHost,
直接把原来的代码复制过来就可以,
最后直接调用RunAndStartedOpenBrowser
或者 自己去注册ApplicationStarted
的事件
当前代码例子使用的是 Blazor
当然也可以用 MVC , RazorPage 等等
与任何框架都无关,
甚至你可以通过C#启动一个React, Vue 项目
也可以添加很多的右键菜单项,去做更多的功能
using MyNotifyIconBlazorApp.Data;
public static class WebProgram
{
/// <summary>
/// 非常普通的一个 .NET 6 WebHost
/// 只添加了一个 ApplicationStarted 启动浏览器的操作
/// </summary>
public static void RunWebHost(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<WeatherForecastService>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
#if DEBUG
app.Run();
#else
// 启动 web host 并 打开浏览器
app.RunAndStartedOpenBrowser();
#endif
}
}
NotifyIconUtils.cs
使用到了
appicon.ico
作为 托盘的图标 (必须有图标
)
可以改为自己的 ico, 记住要改复制到输出目录 -> 始终复制
否则发布后,找不到文件
using System.Runtime.InteropServices;
using System.Diagnostics;
public static class NotifyIconUtils
{
#region 句柄实用工具
[DllImport("User32.dll", EntryPoint = "FindWindow")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "GetSystemMenu")]
static extern IntPtr GetSystemMenu(IntPtr hWnd, IntPtr bRevert);
[DllImport("user32.dll", EntryPoint = "RemoveMenu")]
static extern IntPtr RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// 控制台窗口句柄
/// </summary>
public static IntPtr ConsoleWindowHandle { get; set; }
/// <summary>
/// 自动寻找当前控制台句柄
/// </summary>
/// <returns></returns>
public static IntPtr GetConsoleWindowHandle()
{
if (ConsoleWindowHandle == default)
{
ConsoleWindowHandle = FindWindow(null, Console.Title);
}
return ConsoleWindowHandle;
}
/// <summary>
/// 禁用控制台窗口的关闭按钮
/// </summary>
public static void DisableCloseButton()
{
IntPtr closeMenu = GetSystemMenu(GetConsoleWindowHandle(), IntPtr.Zero);
uint SC_CLOSE = 0xF060;
RemoveMenu(closeMenu, SC_CLOSE, 0x0);
}
static void HiddenConsoleWindow()
{
uint SW_HIDE = 0;
ShowWindow(GetConsoleWindowHandle(), SW_HIDE);
}
static void ShowConsoleWindow()
{
uint SW_SHOW = 5;
ShowWindow(GetConsoleWindowHandle(), SW_SHOW);
SetForegroundWindow(GetConsoleWindowHandle());
}
static bool _visable = true;
/// <summary>
/// 通过属性控制 console 的显示与隐藏
/// </summary>
public static bool ConsoleVisable
{
get { return _visable; }
set
{
_visable = value;
if (_visable)
ShowConsoleWindow();
else
HiddenConsoleWindow();
}
}
/// <summary>
/// 关闭某一个窗口
/// </summary>
public static void CloseWindow(IntPtr hwnd)
{
UInt32 WM_CLOSE = 0x0010;
SendMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
#endregion
#region 托盘图标
private static NotifyIcon _notifyIcon = new();
/// <summary>
/// 初始化
/// 在程序的开头执行
/// </summary>
public static void Init()
{
NotifyIconInitialization();
ShowNotifyIcon();
DisableCloseButton();
ConsoleVisable = false;
}
public static void NotifyIconInitialization()
{
_notifyIcon.Icon = new Icon("appicon.ico");
_notifyIcon.Visible = false;
_notifyIcon.Text = Console.Title;
var menu = new ContextMenuStrip()
{
RenderMode = ToolStripRenderMode.System,
};
menu.Items.Add(new ToolStripMenuItem()
{
Name = "OpenUrl",
Text = "打开浏览器"
});
menu.Items.Add(new ToolStripMenuItem()
{
Name = "Console",
Text = "打开/关闭控制台"
});
menu.Items.Add(new ToolStripMenuItem()
{
Name = "Exit",
Text = "退出"
});
// 设置右键菜单
_notifyIcon.ContextMenuStrip = menu;
menu.ItemClicked += (s, e) =>
{
Console.WriteLine("托盘菜单被点击");
Console.WriteLine("被点击的菜单是:{0}", e.ClickedItem.Text);
switch (e.ClickedItem.Name)
{
case "Exit":
// 结束程序
Application.Exit();
break;
case "OpenUrl":
// 打开浏览器
OpenBrowser(Url);
break;
case "Console":
// 控制台窗口的显示/隐藏
ConsoleVisable = !ConsoleVisable;
break;
}
};
// 双击
_notifyIcon.MouseDoubleClick += (s, e) =>
{
Console.WriteLine("托盘被双击.");
ConsoleVisable = !ConsoleVisable;
};
}
public static void ShowNotifyIcon()
{
_notifyIcon.Visible = true;
// 弹出系统提示
//_notifyIcon.ShowBalloonTip(3000, "", "我是托盘图标,右键单击显示菜单,左键双击 显示/隐藏 控制台窗口。", ToolTipIcon.None);
}
public static void HideNotifyIcon()
{
_notifyIcon.Visible = false;
}
#endregion
public static string? Url { get; set; }
/// <summary>
/// 打开浏览器
/// </summary>
public static void OpenBrowser(string url)
{
Console.WriteLine("打开浏览器:" + url);
var browser =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new ProcessStartInfo("cmd", $"/c start {url}") :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? new ProcessStartInfo("open", url) :
new ProcessStartInfo("xdg-open", url); //linux, unix-like
Process.Start(browser);
}
/// <summary>
/// 启动 web host 并 打开浏览器
/// </summary>
/// <param name="app"></param>
/// <param name="url"></param>
public static void RunAndStartedOpenBrowser(this WebApplication app, string? url = null)
{
url ??= app.Services.GetService<IConfiguration>()?["urls"]?.Split(";")?[^1] ?? "http://localhost:5000";
Url = url;
// 从容器中拿到 生命周期对象
var lifetime = app.Services.GetService<IHostApplicationLifetime>();
// 注册 程序启动成功后,要执行的方法
lifetime?.ApplicationStarted.Register(() => { OpenBrowser(Url); });
app.Run(url);
}
}
目录结构
高亮的文件
Github
完整代码与例子, 可直接启动查看效果