ServicesResourcesConferences Our TeamWeblogsAboutContact
     
  

Developer Resources

  Architecture Briefings
  .NET Remoting FAQ
  Conversations
  Tools and Samples
  Books

[Printable]

"Sealed" is Good!

A quick look into the classes contained in System.Windows.Forms reveales that some of the interesting controls are marked as "sealed" so that you can't derive from them. Sounds like a major restriction, doesn't it? What if you'd like to implement your own ImageList control for example? Impossible!

What looks like a arbitrary restriction is in fact the biggest performance winner in the .NET Framework. It might be one of the reasons why System.Windows.Forms outperforms the other VM's standard UI-technologies Swing and AWT. The reason for this lies not directly in the use of "sealed" (i.e. you won't gain massive performance advantages when using it) but instead in the fact that System.Windows.Forms uses some nice optimizations which would not be available if we would be allowed to derive from ImageList and friends.

So let's look at how the combination of, say, a ListView and an ImageList works in .NET. One might imagine that after setting the ListView's SmallImageList property, the ListView would simply use the ImageList's Images collection whenever it needs to display an image. Sounds reasonable? Yes, perfectly - apart from the small fact that Microsoft actually wanted to re-use the existing Win32 ListView without reimplementing it in purely managed code.

So let's have a sneak peek into ListView's ImageList property's implementation. It might look strikingly similar to the following:

public ImageList SmallImageList
{
   set
   {
       // ... some lines of Top Secret source code removed ...
       base.SendMessage(4099, ((IntPtr) 1), 
              ((value == null) ? IntPtr.Zero : value.Handle)); 
   }
}

Rings a bell? Ok, let's translate this to something more reasonable:

public ImageList SmallImageList
{
   set
   {
       // ... some lines of Top Secret source code removed ...
       IntPtr hwndImgList = (value == null) ? IntPtr.Zero : value.Handle;
       IntPtr lType = (IntPtr) 1;
       int LVM_SETIMAGELIST = 0x1003;
       base.SendMessage(LVM_SETIMAGELIST, lType, hwndImgList); 
   }
}

This proves nicely that there is in fact no .NET ListView. There is just a Win32 ListView, wrapped by a .NET class. The problem in this case is that the Win32 ListView doesn't actually know about the .NET ImageList and the only way around here is - you guessed - to just use the Win32 ImageList as well and just wrap it in .NET.

So let's look at the run-time interactions between a System.Windows.Forms ListView and ImageList in more detail. In the following example, I created a Windows Forms application, containing a Form with an ImageList and a ListView. The ImageList contains three images from the popular, stylish and extensive icon library shipped with Visual Studio 2003. On the click of a button, three items are added to the ListView as shown in Figure 01.

Figure 01 - Three Items in a ListView. Pure magic.

To further solidify my theory, I wanted to see the real run-time interactions between the ListView and the ImageList. I used a technique which is part of .NET Remoting: the generation of a custom RealProxy implementation which can be wrapped around the ImageList. I can then set the ListView's SmallImageList property to the proxy instead of the real ImageList, giving me the ability to intercept all calls which are targeted at the ImageList.

This proxy is shown here:

using System;
using System.Windows.Forms;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

namespace SealedIsGood
{
  public class GenericProxy: RealProxy 
  {
    private MarshalByRefObject _target;
    private ListBox _output;

    public GenericProxy(MarshalByRefObject target, ListBox output): 
      base(target.GetType())
    {
      _target = target;
      _output = output;
    }

    public override IMessage Invoke(IMessage msg)
    {
      IMethodCallMessage mcm = (IMethodCallMessage) msg;
      String log = String.Format("ImageList - {0}",mcm.MethodName);
      Console.WriteLine(log);
      _output.Items.Add(log);
      IMethodReturnMessage ret = RemotingServices.ExecuteMessage(_target, mcm);
      return ret;
    }

    public static Object GetProxyForControl(ImageList lst, ListBox output)
    {
      GenericProxy prx = new GenericProxy(lst,output);
      return prx.GetTransparentProxy();
    }
  }
}

I can then set the ListView's SmallImageList to a proxy to the actual ImageList before running the test. This generates the output shown in Figure 02.

private void btnRunTestProxy_Click(object sender, System.EventArgs e)
{
  ImageList prxImgLst = 
     (ImageList) GenericProxy.GetProxyForControl(imgMain, lstOperations);
  lvwMain.Items.Clear();
  lvwMain.SmallImageList = prxImgLst;
  lvwMain.Items.Add("Test 1",0);
  lvwMain.Items.Add("Test 2",1);
  lvwMain.Items.Add("Test 3",2);
}

Figure 02 - Not so much going on here.

As you can see in Figure 02, the ListView doesn't use the .NET ImageList's Images collection at all. Instead, it just uses the ImageList's Handle property (third line, "get_Handle") and your good old friend SendMessage() to interact with the ImageList. This however instantly means that creating a class which inherits from ImageList and which simply overrides get_Images wouldn't yield the expected result because the other System.Windows.Forms controls will still simply use the handle to the underlying Win32 ImageList. And that's the very reason why wrapper classes like this absolutely have to be marked as sealed.

Let me rephrase: Whenever the ListView needs an image from the associated ImageList, it doesn't use .NET - it especially doesn't use the .Images collection of the .NET ImageList - but instead just uses native SendMessage calls. The only way to implement your own, custom ImageList would therefore be a complete reimplementation of the complete message handling logic of the Win32 ImageList. Not exactly a five minute task, if you ask me.

Conclusion

Don't cry foul whenever you encounter sealed in some framework. The designers might have had their reasons for using it, and the implications of NOT using sealed might be bigger than you can imagine at first glance. In the case of the ImageList it would have meant to rewrite ALL windows forms controls in purely managed code instead of just wrapping the underlying Win32 controls.

Download and Feedback

You can download the sample application for this article here. Please note that this is a Visual Studio 2003 solution file!

If you'd like to leave feedback, or comment on this article, please don't hesitate to do so at this weblog post.

About the author

Ingo Rammer is an independent consultant, developer, author, and trainer based in central Europe. His books Advanced .NET Remoting and Advanced .NET Remoting in VB.NET were published by Apress in 2002. In his day-to-day work as a consultant, he focuses mainly on the architecture of .NET applications, and has worked with various companies in the telecommunication and software industry. You can reach him at http://www.ingorammer.com.






 

© 2002-2006 by Thinktecture, Ingo Rammer and Christian Weyer. All rights reserved. | Contact | Impressum