NotifyIcon context menus for both buttons in .NET (evolution of a hack)

Here’s the evolution of what should have been a clean reusable component in .NET and how it becomes a hack thanks to the limitation of the .NET framework.

Requirement

I’m working on an application where I want two contextual menus on the notify icon. The right one will display a number of options for settings and creating new items and the left to switch between the various items.

Before anyone else harps on about UI conventions this is exactly how the Windows Safely Remove Hardware notify icon works.

A brief glance at the NotifyIcon component in .NET shows that it supports one contextual menu displayed with the right button.

This is a shortcoming but not a problem right? This is a modern object oriented programming environment and extending functionality should be easy. It certainly was in Delphi back in the early 90’s with it’s .NET-like VCL framework.

Attempt 1. Sub-classing NotifyIcon (failed)

One of the founding principles of object oriented programming is reuse. The sealed keyword prevents reuse and forces use instead.

Keith Pleas wrote “If they extend it, they will break it. Use internal more. Seal first, ask questions later.” which is absolute rubbish.

In our case the sub-classed NotifyIcon would hook into the click mechanism and display a menu. Reflector shows nothing would break.

This is an unfortunate trend in the .NET Framework and with each release the framework grows towards an inflexible monolithic framework of simplistic classes. These classes force you to reinvent the wheel if they are not quite what you like because the developers believe they know exactly how you will use the framework.

Online forums provide substantial evidence to the contrary.

So creating a reusable component that just has two ContextMenuStrip properties – one for each button – is out thanks to short-sighted framework developers.

Attempt 2. Show ContextMenu on left-click (failed)

We’ll have to reproduce this code in each app we write and instead wire up to the NotifyIcon’s OnMouseClick event.

There we will need to test to ensure the left button is clicked and show the alternative ContextMenuStrip:

private void notifyIcon_MouseClick(object sender, MouseEventArgs e) {
  if (e.Button == MouseButtons.Left)
    taskMenuStrip.Show(e.Location);
}

Okay, this shows the menu now when the click happens but does not behave like the right-click menu. The positioning and click tracking is off and an odd blank task-bar item appears. A quick glance at the disassembly of NotifyIcon.ShowContextMenu shows code to handle the positioning and the the method we want to handle the tracking/task-bar is ContextMenuStrip.ShowInTaskbar.

And guess what? It’s internal.

Attempt 3. Hack the control (success)

Back at the drawing board we decide the only option left to us is the ugly hack. Replace the ContextMenuItem, call ShowContextMenu and then switch the ContextMenuItem back.

Of course even NotifyIcon.ShowContextMenu is unfathomably marked private so we’ll have to call that via reflection. The result is:

private void notifyIcon_MouseClick(object sender, MouseEventArgs e) {
  if (e.Button == MouseButtons.Left) {
    notifyIcon.ContextMenuStrip = taskMenuStrip;
    typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(notifyIcon, null);
    notifyIcon.ContextMenuStrip = contextMenuStrip;
  }
}

Conclusion

The framework developers were worried I might break NotifyIcon if I subclass it and took measures to prevent that possibility through a combination of the sealed, private and internal.

Instead I am forced to develop a solution that is much more likely to break as I now rely on:

  • ContextMenuStrip property setter not clearing or removing existing displayed menu
  • ShowContextMenu private property not changing name or visibility

[)amien

6 responses

  1. Avatar for steve

    I've long held the opinion that MS knows very little about writing well-structured software. My experience with all of their frameworks (although my exposure to .Net has been less so) has resulted in much hair-pulling. And most of the time looking at the MSDN articles on their recommended software structures made me wince - much of the time they appeared to be written by college graduates with no experience of long-term software development. The fact that .Net seems to want to 'morph' into a new form every 12 months with new whizzy developer gimmicks (like LINQ) rather than concentrating on a solid, incrementally evolving architectural base reinforces that opinion. Less discrete productising, more overarching, stable structure is the key to long-term design and they don't seem to 'get' that at all.

    Your principles on re-use agree with mine, and is one reason why our coding standard for Ogre say that you should never use the 'private' keyword (preventing access from subclasses). C++ doesn't have a 'sealed' keyword but it's equivalent to 'final' in Java, which again good coders are told never to use for anything except immutable constants. Recommending you make most classes 'final' in the first instance would have been laughed at by most serious Java developers.

    This post reinforces my opinion that MS and .Net are still terribly 'young' in their attitude to software engineering. Whizzy language gimmicks pull in developers but it's all for naught if you can't deliver a good long-term framework.

    steve 31 May 2007
  2. Avatar for Damien Guard

    I think it's unfair to blame the whole .NET team. The CLR and language guys seem to be doing really good work - it's just the attitude and inexperience of the framework guys that seems to be letting down the entire platform.

    Damien Guard 31 May 2007
  3. Avatar for steve

    Maybe so, but IMO it just points to a poor set of priorities. Loads of resources are going into upgrading low-level components of C# and the like, whichi s nice and makes for good tech articles but at the end of the day is it really helping to get projects done? Yeah, it's fun and exciting but real projects need large components that are mature, flexible, extensible and just work. Arguably as developers we don't need a constant stream of language updates and low-level things, we need good software building blocks that adapt well to our needs and have a good shelf-life. IMO they've spent way too much time on cool gadgets and stuff that low-level programmers like to play with and not enough time on what's actually useful for building large, real-world systems.

    steve 31 May 2007
  4. Avatar for Damien Guard

    This is true and I think Microsoft wanted to achieve just that with the Enterprise Library and Patterns & Practice blocks but for the most part missed the objective once again by delivering some incredibly abstract blocks.

    Damien Guard 1 June 2007
  5. Avatar for DonBeto97

    Here is the same code in VB.NET:

    Private Sub notifyIcon_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles notifyIcon.MouseClick
        If e.Button = MouseButtons.Left Then
            notifyIcon.ContextMenuStrip = taskMenuStrip
            notifyIcon.GetType.GetMethod("ShowContextMenu", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).Invoke(notifyIcon, Nothing)
            notifyIcon.ContextMenuStrip = contextMenuStrip
        End If
    End Sub
    
    DonBeto97 16 October 2009
  6. Avatar for Simon

    Superb just what i needed, must say the odd icon shown on the taskbar had me dumbfounded now works like a charm...

    Keep up the good work

    Simon 8 May 2010