C# .NET 给Web项目(MVC,RazorPage,Blazor等)添加一个系统托盘图标,替代Winform,WPF,Electron,QT,WebView2,Tauri 等桌面程序

效果图在这里插入图片描述

功能

  1. 添加任务栏图标 ( 托盘图标 )

    • 添加右键菜单 (打开浏览器 / 控制台 /退出)

    • 双击打开控制台,

  2. 启动一个web服务器 ( MVC , Razor , Blazor 等等 )

  3. web服务器启动成功,自动打开浏览器页面

  4. 持续后台运行

由于使用 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

完整代码与例子, 可直接启动查看效果

https://github.com/xxxxue/MyNotifyIconApp

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值