如何制作一个只在系统托盘中运行的 .NET Windows Forms 应用程序?

267

我需要做什么才能让一个Windows窗体应用程序在系统托盘中运行?

这不是一个可以最小化到托盘的应用程序,而是一个仅存在于托盘中的应用程序,仅包含:

  • 图标
  • 工具提示,和
  • 一个“右键单击”菜单。

8
好的,我会尽力在不改变原意的前提下对这篇名为《创建一个任务栏应用程序》的文章进行翻译。Creating a Tasktray Application创建一个任务栏应用程序This article provides an introduction to creating a Windows application that resides in the taskbar. The application also has a context menu.本文介绍如何创建一个可以停留在任务栏中的 Windows 应用程序,并且还具有上下文菜单。Introduction介绍The Windows taskbar is perhaps one of the most underrated yet useful features of the Windows operating system. The system tray part of the taskbar is where the clock usually resides, but many other applications use it as well. You can put your own application there too.Windows 任务栏可能是 Windows 操作系统中被低估但又非常实用的功能之一。任务栏的系统托盘部分通常用于显示时钟,但许多其他应用程序也使用它。你也可以将你自己的应用程序放到这里。Using the Code使用代码This project is a "hello world" example of how to create an application that resides in the taskbar. It was created using Microsoft Visual C++ 2005 Express Edition.这个项目是一个基本的示例,展示了如何创建一个可以停留在任务栏中的应用程序。它是使用 Microsoft Visual C++ 2005 Express Edition 创建的。Background背景When a window is minimized, it disappears from the desktop and goes to the taskbar. An icon representing the window appears on the taskbar. The icon is used to restore the window when the user clicks on it.当一个窗口被最小化,它会从桌面上消失并进入任务栏。代表该窗口的图标会出现在任务栏上。当用户点击它时,这个图标用于恢复窗口。Using the Code使用代码The project uses the Win32 API to create a window, and then makes the window hidden.这个项目使用 Win32 API 创建一个窗口,然后将窗口隐藏起来。To make the application appear in the taskbar, an icon is added to the system tray. This is done using Shell_NotifyIcon().为了让应用程序出现在任务栏中,需要将一个图标添加到系统托盘中。可以使用 Shell_NotifyIcon() 函数来完成这个操作。When the user right-clicks on the icon, a context menu appears. To handle this, the program responds to a WM_CONTEXTMENU message.当用户右键单击图标时,会出现一个上下文菜单。为了处理这个操作,程序会响应 WM_CONTEXTMENU 消息。Points of Interest注意事项
  • The Shell_NotifyIcon() function is key to getting your application to reside in the system tray.
  • Responding to a WM_CONTEXTMENU message is necessary to provide a context menu for the user.
  • Be sure to handle the messages that are sent to your window, even if you are not interested in them. Not handling them will cause your application to misbehave.
  • Shell_NotifyIcon() 函数是让你的应用程序停留在系统托盘中的关键。
  • 响应 WM_CONTEXTMENU 消息是提供上下文菜单给用户的必要步骤。
  • 确保处理发送给你的窗口的消息,即使你对它们不感兴趣。不处理它们会导致你的应用程序出现异常行为。
History历史记录
  • 22nd March, 2007: Initial post
  • 2007 年 3 月 22 日:初次发布
- Fawzan Izy
20
在大多数答案中都有一些遗漏的东西 - 不要忘记在退出应用程序时设置icon.Visible = false,然后Dispose()该图标。否则,您将在程序退出后仍然看到该图标。测试几次后,您将不再知道哪个图标是真实的。 - Tomasz Gandor
4
如果您想要一种更现代的WPF方法 - 您可以尝试这个链接:http://www.codeproject.com/Articles/36788/WPF-XAML-NotifyIcon-and-Taskbar-System-Tray-Popup - Murray Foxcroft
3
仅供记录,这是有关托盘应用程序的非常全面的文章链接(来自已删除的答案):http://www.simple-talk.com/dotnet/.net-framework/creating-tray-applications-in-.net-a-practical-guide/。 - xyz
13个回答

147

这篇来自Code Project的文章"Creating a Tasktray Application"提供了一个非常简单的解释和示例,展示了如何创建一个仅存在于系统托盘中的应用程序。

基本思路是将Program.cs中的Application.Run(new Form1());替换为启动从ApplicationContext继承的类,并且该类的构造函数初始化了一个NotifyIcon

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Application.Run(new MyCustomApplicationContext());
    }
}


public class MyCustomApplicationContext : ApplicationContext
{
    private NotifyIcon trayIcon;

    public MyCustomApplicationContext ()
    {
        // Initialize Tray Icon
        trayIcon = new NotifyIcon()
        {
            Icon = Resources.AppIcon,
            ContextMenu = new ContextMenu(new MenuItem[] {
                new MenuItem("Exit", Exit)
            }),
            Visible = true
        };
    }

    void Exit(object sender, EventArgs e)
    {
        // Hide tray icon, otherwise it will remain shown until user mouses over it
        trayIcon.Visible = false;

        Application.Exit();
    }
}

25
这是一个很好的起点。请注意,“AppIcon”必须是您使用“项目 -> 属性 -> 资源 -> 添加资源”添加的资源名称,并在重新编译项目之前执行此操作。 - Lavamantis
1
我已经完成了属性、资源、添加资源和图标的相关设置,并重新编译,但仍然出现“Error CS0103:当前上下文中不存在‘Resources’名称”的错误。 - barlop
1
@barlop 我不得不这样写:Properites.Resources.AppIcon - Simon Perepelitsa
@SimonPerepelitsa 谢谢,我不确定在我的实验中做了什么,我本意是要注释哪些是可行的,但可能忘记了其中一些可行的步骤。但最终我使用的方法是从可以拖动的项目调色板中拖动通知图标。然后在表单上单击它,点击一个小箭头,这将弹出一个菜单来选择它的图标! - barlop
2
注意:如果有人想在.NET Core中使用此代码,它仍然可以工作,但是您必须替换已弃用的类,像这样 - MarredCheese

19

就像mat1t所说的那样 - 你需要向应用程序添加一个NotifyIcon,然后使用以下代码设置工具提示和上下文菜单:

this.notifyIcon.Text = "This is the tooltip";
this.notifyIcon.ContextMenu = new ContextMenu();
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Option 1", new EventHandler(handler_method)));

这段代码仅在系统托盘中显示图标:

this.notifyIcon.Visible = true;  // Shows the notify icon in the system tray
如果您有一个表单(无论出于何种原因),则需要以下内容:
this.ShowInTaskbar = false;  // Removes the application from the taskbar
Hide();

右键获取上下文菜单是自动处理的,但如果想在左键单击时执行某些操作,则需要添加Click处理程序:

    private void notifyIcon_Click(object sender, EventArgs e)
    {
        var eventArgs = e as MouseEventArgs;
        switch (eventArgs.Button)
        {
            // Left click to reactivate
            case MouseButtons.Left:
                // Do your stuff
                break;
        }
    }

16

.NET Core

我将被接受的答案改编为.NET Core,并使用推荐的替代方案来替换已弃用的类:

  • ContextMenu -> ContextMenuStrip
  • MenuItem -> ToolStripMenuItem

Program.cs

namespace TrayOnlyWinFormsDemo
{
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            ApplicationConfiguration.Initialize();
            Application.Run(new MyCustomApplicationContext());
        }
    }
}

MyCustomApplicationContext.cs

using TrayOnlyWinFormsDemo.Properties;  // Needed for Resources.AppIcon

namespace TrayOnlyWinFormsDemo
{
    public class MyCustomApplicationContext : ApplicationContext
    {
        private NotifyIcon trayIcon;

        public MyCustomApplicationContext()
        {
            trayIcon = new NotifyIcon()
            {
                Icon = Resources.AppIcon,
                ContextMenuStrip = new ContextMenuStrip()
                {
                    Items = { new ToolStripMenuItem("Exit", null, Exit) }
                },
                Visible = true
            };
        }

        void Exit(object? sender, EventArgs e)
        {
            trayIcon.Visible = false;
            Application.Exit();
        }
    }
}

我认为你的回答刚刚帮我节省了几个小时的痛苦 :-))) 谢谢!! - Xan-Kun Clark-Davis
绝对不是愚人节玩笑;-) - Xan-Kun Clark-Davis

16

我使用.NET 1.1编写了一个托盘应用,没有必要使用窗体。
首先,将项目的启动对象设置为在模块中定义的Sub Main
然后,通过编程方式创建组件:NotifyIconContextMenu
确保包含一个名为“Quit”或类似的MenuItem
ContextMenu绑定到NotifyIcon
调用Application.Run()
在退出MenuItem的事件处理程序中,确保调用NotifyIcon.Visible = False,然后调用Application.Exit()。 根据需要添加内容到ContextMenu并进行适当处理 :)


14
  1. 使用向导创建一个新的Windows应用程序。
  2. 从代码中删除Form1
  3. 删除Program.cs中启动Form1的代码。
  4. 使用NotifyIcon类创建系统托盘图标(为其分配一个图标)。
  5. 为其添加上下文菜单。
  6. 或者响应NotifyIcon的鼠标单击事件,并区分右键和左键单击,为按下的任何按钮(右键/左键)设置上下文菜单并显示它。
  7. 使用Application.Run()保持应用程序运行,并使用Application.Exit()退出。或者使用bool bRunning = true; while(bRunning){Application.DoEvents(); Thread.Sleep(10);},然后设置bRunning = false;以退出应用程序。

当你到达 Main 的结尾并且没有 UI 线程时,程序将停止执行。 如果您的解决方案已经处理了这些问题,那么您会得到我的支持 :) - Matthew Steeples
你得到了我的投票。也许只需要提一下你仍然需要调用没有任何参数的Application.Run? - user93202
更新了一个替代App.Run的方案。 - Wolf5
thread.sleep 是过度使用了,但如果你有比 Application.Run 更好的“睡眠”循环替代方案,请发帖分享 :) - Wolf5
1
Thread.Sleep不是一个好主意:你最终会使用比你应该做的Application.Run更多的CPU和电池。 - Sneftel

13

"系统托盘"应用程序只是普通的Windows窗体应用程序,唯一的区别是它在Windows系统托盘区域创建了一个图标。要创建系统托盘图标,请使用NotifyIcon组件,在工具箱(常用控件)中可以找到它,并修改其属性:图标、工具提示。此外,它还使您能够处理鼠标单击和双击消息。

还有一件事,在主窗体显示事件中添加以下行以实现外观和标准托盘应用程序:

"
private void MainForm_Shown(object sender, EventArgs e)
{
    WindowState = FormWindowState.Minimized;
    Hide();
} 

8
据我所知,您仍然需要使用表单编写应用程序,但是在表单上没有任何控件,并且从未设置它可见。使用NotifyIcon(可以在此处找到MSDN示例)编写您的应用程序。

1
不太对。您的表单可以包含控件,但默认情况下应该隐藏。 - Rune Grimstad
5
无需任何窗体。在创建新的 Windows 应用程序向导后,只需删除 Form1 并删除打开它的代码。您可以在 Program.cs 中使用 NotifyIcon 和 ContextMenu 编写所有内容。无需其他要求。 - Wolf5
1
我知道它可以包含控件,但OP不想要它。 - Matthew Steeples

4

这是一个非常友好的通知区域应用框架... 只需在基础表单中添加NotificationIcon并将自动生成的代码更改为以下代码即可:

public partial class Form1 : Form
{
    private bool hidden = false;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        this.ShowInTaskbar = false;
        //this.WindowState = FormWindowState.Minimized;
        this.Hide();
        hidden = true;
    }

    private void notifyIcon1_Click(object sender, EventArgs e)
    {
        if (hidden) // this.WindowState == FormWindowState.Minimized)
        {
            // this.WindowState = FormWindowState.Normal;
            this.Show();
            hidden = false;
        }
        else
        {
            // this.WindowState = FormWindowState.Minimized;
            this.Hide();
            hidden = true;
        }
    }
}

4

以下是我如何使用 Visual Studio 2010 和 .NET 4 实现的

  1. 创建一个 Windows Forms 应用程序,在属性中设置“使应用程序成为单一实例”
  2. 添加一个 ContextMenuStrip
  3. 向上下文菜单条添加一些条目,双击它们以获取处理程序,例如,“退出”(双击) -> 处理程序 -> me.Close()
  4. 添加一个 NotifyIcon,在设计器中将 contextMenuStrip 设置为您刚创建的菜单条,选择一个图标(您可以在 VisualStudio 文件夹下的“common7...”中找到一些图标)
  5. 在设计器中为窗体设置属性:FormBorderStyle:none, ShowIcon:false, ShowInTaskbar:false, Opacity:0%, WindowState:Minimized
  6. 在 Form1_Load 的末尾添加 Me.Visible=false,这将在使用 Ctrl + Tab 时隐藏图标
  7. 运行并根据需要进行调整。

3
在 .Net 6 中,我需要像这样处理我的类的内部细节:
private NotifyIcon trayIcon;
private ContextMenuStrip contextMenu1;
private ToolStripMenuItem menuItem1;

public MyCustomApplicationContext()
{
    contextMenu1 = new System.Windows.Forms.ContextMenuStrip();
    menuItem1 = new System.Windows.Forms.ToolStripMenuItem();
    this.menuItem1.Text = "E&xit";
    this.menuItem1.Click += new System.EventHandler(Exit);
    this.contextMenu1.Items.AddRange(
            new System.Windows.Forms.ToolStripMenuItem[] {this.menuItem1 });
    trayIcon = new NotifyIcon(){Icon = Resources.AppIcon, ContextMenuStrip = this.contextMenu1, Visible = true };            

}

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