1

I have implemented system tray icon in Windows using examples provided here.

However this example is missing implementation of context menu on mouse right click. Is there some way to add features mentioned above to existing example?

Here is system icon class:

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;

        /// <summary>
        /// Receives messages from the taskbar icon.
        /// </summary>
        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();

            // init icon data structure
            iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle, iconFile);

            //IntPtr hIcon = PInvoke.User32.LoadImage(IntPtr.Zero, "dotnetbot.ico",
            //    PInvoke.User32.ImageType.IMAGE_ICON, 16, 16, PInvoke.User32.LoadImageFlags.LR_LOADFROMFILE);

            //PInvoke.User32.SendMessage(iconData.WindowHandle, PInvoke.User32.WindowMessage.WM_SETICON, (IntPtr)0, hIcon);

            // create the taskbar icon
            CreateTaskbarIcon();

            // register event listeners
            messageSink.MouseEventReceived += MessageSink_MouseEventReceived;
            messageSink.TaskbarCreated += MessageSink_TaskbarCreated;
            //messageSink.ChangeToolTipStateRequest += OnToolTipChange;
        }

        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;

                //write initial configuration
                var status = WriteIconData(ref iconData, NotifyCommand.Add, members);
                if (!status)
                {
                    // couldn't create the icon - we can assume this is because explorer is not running (yet!)
                    // -> try a bit later again rather than throwing an exception. Typically, if the windows
                    // shell is being loaded later, this method is being re-invoked from OnTaskbarCreated
                    // (we could also retry after a delay, but that's currently YAGNI)
                    return;
                }

                //set to most recent version
                SetVersion();
                //messageSink.Version = (NotifyIconVersion)iconData.VersionOrTimeout;

                IsTaskbarIconCreated = true;
            }
        }

        private void RemoveTaskbarIcon()
        {
            lock (lockObject)
            {
                // make sure we didn't schedule a creation

                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();


        /// <summary>
        /// Updates the taskbar icons with data provided by a given
        /// <see cref="NotifyIconData"/> instance.
        /// </summary>
        /// <param name="data">Configuration settings for the NotifyIcon.</param>
        /// <param name="command">Operation on the icon (e.g. delete the icon).</param>
        /// <returns>True if the data was successfully written.</returns>
        /// <remarks>See Shell_NotifyIcon documentation on MSDN for details.</remarks>
        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command)
        {
            return WriteIconData(ref data, command, data.ValidMembers);
        }


        /// <summary>
        /// Updates the taskbar icons with data provided by a given
        /// <see cref="NotifyIconData"/> instance.
        /// </summary>
        /// <param name="data">Configuration settings for the NotifyIcon.</param>
        /// <param name="command">Operation on the icon (e.g. delete the icon).</param>
        /// <param name="flags">Defines which members of the <paramref name="data"/>
        /// structure are set.</param>
        /// <returns>True if the data was successfully written.</returns>
        /// <remarks>See Shell_NotifyIcon documentation on MSDN for details.</remarks>
        public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags)
        {

            data.ValidMembers = flags;
            lock (SyncRoot)
            {
                return WinApi.Shell_NotifyIcon(command, ref data);
            }
        }
    }
}

I think I need to add to above:

public ContextMenu ContextMenu { get; set; }

But after that I am not having any success so far. On how to register it and add to tray icon (ContextMenu does not have definition for MenuItems):

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();
    };
  }
}
10101
  • 2,232
  • 3
  • 26
  • 66
  • Why is this tagged MAUI? – Jason May 22 '23 at 23:52
  • @Jason I am trying to get it working in net maui windows application. Their WeatherTwentyOne example is basically the same, that is linked in links provided. So I am using net maui, not wpf as this one classes namespace may give you such a picture. – 10101 May 23 '23 at 00:17
  • They key part is in the WindowMessageSink not the WindowsTrayIcon. WindowsTrayIcon sets up the notification icon, and the WindowsMessageSink handles its notifications. The notification area will send a WM_CONTEXTMENU message to the message sink to handle the right click. WeatherTwentyOne's WindowMessageSink stubs out the message, and you'll need to replace that with code to show your context menu. See https://learn.microsoft.com/en-us/windows/win32/shell/notification-area for how the overall mechanism works – Rob Caplan - MSFT May 26 '23 at 01:33
  • Did you find a solution to this? – Angelru Jul 26 '23 at 05:45
  • @angelru no I have not, but you can check this https://github.com/HavenDV/H.NotifyIcon/tree/master/src%2Fapps%2FH.NotifyIcon.Apps.Maui – 10101 Jul 26 '23 at 22:35

0 Answers0