|
[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.
|