You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MAUI Windows托盘图标中添加右键上下文菜单?

在MAUI Windows托盘图标中添加右键上下文菜单

问题背景

基于MAUI的WeatherTwentyOne示例实现Windows系统托盘图标后,需要为其添加右键上下文菜单功能。尝试在WindowsTrayIcon类中添加ContextMenu属性后,无法将菜单关联到托盘图标,同时遇到ContextMenu无MenuItem定义的问题。

原WindowsTrayIcon核心代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hardcodet.Wpf.TaskbarNotification.Interop
{
    public class WindowsTrayIcon
    {
        private readonly object lockObject = new object();
        private NotifyIconData iconData;
        private readonly WindowMessageSink messageSink;

        public Action LeftClick { get; set; }
        public Action RightClick { get; set; }
        public bool IsTaskbarIconCreated { get; private set; }

        public WindowsTrayIcon(string iconFile)
        {
            messageSink = new WindowMessageSink();
            iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle, iconFile);
            CreateTaskbarIcon();
            messageSink.MouseEventReceived += MessageSink_MouseEventReceived;
            messageSink.TaskbarCreated += MessageSink_TaskbarCreated;
        }

        private void MessageSink_TaskbarCreated()
        {
            RemoveTaskbarIcon();
            CreateTaskbarIcon();
        }

        private void MessageSink_MouseEventReceived(MouseEvent obj)
        {
            if (obj == MouseEvent.IconLeftMouseUp)
            {
                LeftClick?.Invoke();
            } else if (obj == MouseEvent.IconRightMouseUp)
            {
                RightClick?.Invoke();
            }
        }

        private void CreateTaskbarIcon()
        {
            lock (lockObject)
            {
                if (IsTaskbarIconCreated) return;

                const IconDataMembers members = IconDataMembers.Message
                                                | IconDataMembers.Icon
                                                | IconDataMembers.Tip;

                var status = WriteIconData(ref iconData, NotifyCommand.Add, members);
                if (!status) return;

                SetVersion();
                IsTaskbarIconCreated = true;
            }
        }

        private void RemoveTaskbarIcon()
        {
            lock (lockObject)
            {
                if (!IsTaskbarIconCreated) return;

                WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
                IsTaskbarIconCreated = false;
            }
        }

        private void SetVersion()
        {
            iconData.VersionOrTimeout = (uint)0x4;
            bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData);
            if (!status) Debug.Fail("Could not set version");
        }

        public static readonly object SyncRoot = new object();

        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command)
        {
            return WriteIconData(ref data, command, data.ValidMembers);
        }

        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags)
        {
            data.ValidMembers = flags;
            lock (SyncRoot)
            {
                return WinApi.Shell_NotifyIcon(command, ref data);
            }
        }
    }
}

原TrayService代码:

public class TrayService : ITrayService
{
  WindowsTrayIcon tray;

  public Action ClickHandler { get; set; }

  public void Initialize()
  {
    tray = new WindowsTrayIcon("Platforms/Windows/trayicon.ico");

    var contextMenu = new ContextMenu();
    contextMenu.MenuItems.Add(new MenuItem());
    contextMenu.MenuItems.Add(new MenuItem());
    tray.ContextMenu = contextMenu;

    tray.LeftClick = () =>
    {
      WindowExtensions.BringToFront();
      ClickHandler?.Invoke();
    };
  }
}

解决方案

1. 修改WindowsTrayIcon类,添加上下文菜单支持

首先给项目安装System.Windows.Forms NuGet包,然后修改类代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; // 新增引用

namespace Hardcodet.Wpf.TaskbarNotification.Interop
{
    public class WindowsTrayIcon
    {
        private readonly object lockObject = new object();
        private NotifyIconData iconData;
        private readonly WindowMessageSink messageSink;

        public Action LeftClick { get; set; }
        public Action RightClick { get; set; }
        public bool IsTaskbarIconCreated { get; private set; }
        // 新增上下文菜单属性,使用ContextMenuStrip替代旧的ContextMenu
        public ContextMenuStrip ContextMenuStrip { get; set; }

        public WindowsTrayIcon(string iconFile)
        {
            messageSink = new WindowMessageSink();
            iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle, iconFile);
            CreateTaskbarIcon();
            messageSink.MouseEventReceived += MessageSink_MouseEventReceived;
            messageSink.TaskbarCreated += MessageSink_TaskbarCreated;
        }

        private void MessageSink_TaskbarCreated()
        {
            RemoveTaskbarIcon();
            CreateTaskbarIcon();
        }

        private void MessageSink_MouseEventReceived(MouseEvent obj)
        {
            if (obj == MouseEvent.IconLeftMouseUp)
            {
                LeftClick?.Invoke();
            }
            else if (obj == MouseEvent.IconRightMouseUp)
            {
                // 右键点击时显示上下文菜单
                ContextMenuStrip?.Show(Cursor.Position);
                RightClick?.Invoke();
            }
        }

        // 其余原有方法保持不变
        private void CreateTaskbarIcon()
        {
            lock (lockObject)
            {
                if (IsTaskbarIconCreated) return;

                const IconDataMembers members = IconDataMembers.Message
                                                | IconDataMembers.Icon
                                                | IconDataMembers.Tip;

                var status = WriteIconData(ref iconData, NotifyCommand.Add, members);
                if (!status) return;

                SetVersion();
                IsTaskbarIconCreated = true;
            }
        }

        private void RemoveTaskbarIcon()
        {
            lock (lockObject)
            {
                if (!IsTaskbarIconCreated) return;

                WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
                IsTaskbarIconCreated = false;
            }
        }

        private void SetVersion()
        {
            iconData.VersionOrTimeout = (uint)0x4;
            bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData);
            if (!status) Debug.Fail("Could not set version");
        }

        public static readonly object SyncRoot = new object();

        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command)
        {
            return WriteIconData(ref data, command, data.ValidMembers);
        }

        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags)
        {
            data.ValidMembers = flags;
            lock (SyncRoot)
            {
                return WinApi.Shell_NotifyIcon(command, ref data);
            }
        }
    }
}

2. 修改TrayService,创建并绑定上下文菜单

使用ToolStripMenuItem替代旧的MenuItem,并为菜单项添加点击事件:

using System.Windows.Forms; // 新增引用

public class TrayService : ITrayService
{
    WindowsTrayIcon tray;

    public Action ClickHandler { get; set; }

    public void Initialize()
    {
        tray = new WindowsTrayIcon("Platforms/Windows/trayicon.ico");

        // 创建上下文菜单
        var contextMenu = new ContextMenuStrip();
        
        // 添加"显示主窗口"菜单项
        var showWindowItem = new ToolStripMenuItem("显示主窗口");
        showWindowItem.Click += (sender, e) =>
        {
            WindowExtensions.BringToFront();
            ClickHandler?.Invoke();
        };
        
        // 添加"退出"菜单项
        var exitItem = new ToolStripMenuItem("退出");
        exitItem.Click += (sender, e) =>
        {
            Microsoft.Maui.Controls.Application.Current.Quit();
        };

        contextMenu.Items.Add(showWindowItem);
        contextMenu.Items.Add(exitItem);

        // 绑定到托盘图标
        tray.ContextMenuStrip = contextMenu;

        tray.LeftClick = () =>
        {
            WindowExtensions.BringToFront();
            ClickHandler?.Invoke();
        };
    }
}

关键说明

  • 选择ContextMenuStrip而非旧的ContextMenu,因为前者是Windows Forms推荐的上下文菜单控件,提供更丰富的功能和更好的兼容性。
  • 需要确保System.Windows.Forms NuGet包已安装,否则无法使用相关控件。
  • 菜单显示位置使用Cursor.Position,确保菜单在鼠标右键点击的位置弹出。

内容的提问来源于stack exchange,提问作者10101

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 08:52:03