Posts tagged with winforms

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

WinForms tricks & tips

TreeView right-mouse button select the node

I’m not sure why it doesn’t do this as standard but a simple event handler should do the trick:

private void treeView_MouseDown(object sender, MouseEventArgs e) {
    if (e.Button == MouseButtons.Right) {
    TreeNode node = treeView.GetNodeAt(e.X, e.Y);
    if (node != null)
        treeView.SelectedNode = node;
    }
}

Adding text to a text box

People have asked (in the IRC #CSharp) why adding text to a TextBox is so slow and flickery. Normally they are trying:

textBox1.Text += myNewText;

The problem with this is that it copies all the Text from the text box then adds myNewText to it and copies the whole result back. This is because strings in .NET are immutable, i.e. can’t be changed, and so adding one string to another always results in this overhead (and hence the existence of the StringBuilder class).

The solution is to abandon the slow, inefficient string concatenation and use the method AppendText thusly:

textBox1.AppendText(myNewText);

Which is fast and efficient whilst also being available to TextBox, RichTextBox and MaskedTextBox (by virtue of being a method of BaseTextBox).

They usually also ask how to make the text box scroll to the end. Just use the following line:

textBox1.ScrollToCaret();

Don’t forget keyboard input

Check those tab orders and accelerator keys!

Creating dynamic controls

If you’re ever unsure how to work with a dynamic control just create it in Visual Studio’s Designer and then head into the .designer.cs file and examine the code it generates.

Windows Forms FAQ

There are many other hints, tips and solutions in the Windows Forms FAQ.

[)amien