Search code examples
c#.netwindowscompositionsystem.componentmodel

Click event delayed in ContextMenu attached to NotifyIcon


I am working on a plugin (using System.ComponentModel.Composition) for an application to place an icon in the notification area of the Windows UI.

trayMenu.MenuItems.Clear();

// Create context menu items
foreach( IJob job in jobs ) {
  MenuItem menuItem = new MenuItem( job.Name ) {Tag = job};
  menuItem.Click += MenuItemClick;
  trayMenu.MenuItems.Add( menuItem );
}

private void MenuItemClick( object sender, EventArgs e ) {
  // ...
}

Now when I click on an item in the context menu of that icon, the Click handler is not being invoked.
Interestingly though, when I right-click the icon again (after having clicked a menu item) the Click handler for the previously clicked MenuItem is invoked. Left-clicking or hovering over the icon does not trigger this step.

What is going on?

Update: I have a strong feeling my problem is related to this question. But I'm still trying to figure out how I could apply that to my plugin/application.


Solution

  • To my understanding the problem is that no window messages are being processed for the NotifyIcon (or at least not as many messages as I liked/needed).

    I solved the issue by inheriting from Form and running another message pump for my plugin.

    using System;
    using ...
    
    namespace JobTracker.Tray {
      [Export( typeof( IJobTrackerPlugin ) )]
      public class TrayPlugin : Form, IJobTrackerPlugin {
    
        #region Plugin Interface
        [Import( typeof( IJobTracker ) )]
    #pragma warning disable 649
          private IJobTracker _host;
    #pragma warning restore 649
        private IJobTracker Host {
          get { return _host; }
        }
    
        public void Initialize() {
          trayMenu = new ContextMenu();
          trayMenu.MenuItems.Add( "Exit", OnExit );
    
          trayIcon = new NotifyIcon();
          trayIcon.Icon = new Icon( SystemIcons.Application, 32, 32 );
    
          trayIcon.ContextMenu = trayMenu;
    
          // Show the proxy form to pump messages
          Load += TrayPluginLoad;
          Thread t = new Thread(
            () => {
              ShowInTaskbar    = false;
              FormBorderStyle  = FormBorderStyle.None;
              trayIcon.Visible = true;
              ShowDialog();
            } );
          t.Start();
        }
    
        private void TrayPluginLoad( object sender, EventArgs e ) {
          // Hide the form
          Size = new Size( 0, 0 );
        }
        #endregion
    
        private NotifyIcon trayIcon;    
        private ContextMenu trayMenu;
    
        private void OnExit( object sender, EventArgs e ) {
          Application.Exit();
        }
    
        #region Implementation of IDisposable
    
        // ...
    
        private void DisposeObject( bool disposing ) {
          if( _disposed ) {
            return;
          }
          if( disposing ) {
            // Dispose managed resources.
            if( InvokeRequired ) {
              EndInvoke( BeginInvoke( new MethodInvoker( Close ) ) );
            } else {
              Close();
            }
            trayIcon.Dispose();
            trayMenu.Dispose();
          }
          // Dispose unmanaged resources.
          _disposed = true;
        }
        #endregion
      }
    }
    

    Seems to work great.