.NET Framework Designtime – I: An Introduction...

Hi Folks...

For a while now, I have been studying the .NET Framework Designtime Infrastructure. Not only because I now own the VSD WinForms and Data designers. But also because the design & architecture of this piece is quiet fascinating… What is the .NET Framework Designtime Infrastructure.? It is the guts of the Visual Studio designer that lets you rapidly design and build rich Windows Forms UI.

In this series, I will dump my understanding of the .NET Framework Designtime. We will also progressively build an application that can design Windows Installer UI. Our application will also persist the UI in a Windows Installer XML Source file (.wxs) that you can readily use in your WiX projects. The first part of the series will be an introduction to the .NET Framework Designtime. Nothing much explained but a lot outlined. So here goes…

First off...

The term designer used here is not the same as VS WinForms Designer that you see when you create a new windows application. What you see is really the design surface provided by VS acting as the designer host.

Earlier, in the MFC/ActiveX worlds, the logic for designtime behavior of a component was hardcoded into the tool (a.k.a. IDE) that provided the design surface – meaning, it was the IDE and not the MFC/ActiveX component that was responsible for providing its (the component’s) designtime behavior. This also meant that different IDEs offered their own designtime environments for components, and with different user interfaces and capabilities. And since an IDEs fixed feature set cannot account for the needs of all components, ActiveX/MFC components had only a limited level of custom designtime support.

In .NET Framework world though, the framework provides every component with its own runtime (obviously!) and designtime behaviors. And just like how the runtime behavior of a component can be customized/extended (e.g. subclassing), so can the designtime behavior of the component be customized & extended by IDEs. This means that every .NET Framework component contains logic for how it behaves and runtime and at designtime. The IDE simply extends, configures and exposes this designtime behavior to the developer. There are of course some services and behaviors that are very strongly tied to the IDE that hosts the design surface (e.g. exposing the design surface, context menus, toolbox interaction etc.). These services are implemented by the host IDE and the component uses these services at designtime after obtaining them from the host IDE through well defined interfaces.

Comparing MFC/ActiveX & .NET Framework Designtime: Click to enlarge

Designtime Runtime code seperation...

Of course when I say that every component provides both its runtime and designtime behavior, I don’t mean that every component needs to contain the code for its designtime behavior and the code for its runtime behavior in the same class or even the same assembly. Although this is possible, in most cases it is not practical. Doing so would simply bloat the runtime code which necessarily needs to be as slim and trim as possible. Note that every byte loaded into memory has a cost associated with it (working set, time to JIT etc.). As an example, consider System.Windows.Forms.Control. Its runtime code is in System.Windows.Forms.Control, System.Windows.Forms.dll and its designtime code in System.Windows.Forms.Design.ControlDesigner, System.Design.dll. Use Reflector to peek into the implementations of both and you will get an idea of the bloat. Just going by lines of code Control.cs is 19217 lines and ControlDesigner.cs is 3842 lines which is ~20% code bloat.

So more often than not, designtime and runtime code separation is employed. This basically means that the designtime code is located on a different class/assembly than the runtime code, like in the System.Windows.Forms.Control example. And the runtime code provides pointers to the designtime code using one or more attributes (e.g. DesignerAttribute). At designtime the .NET Designtime Infrastructure then just looks up the designtime attributes on the component, locates and executes the designtime code to allow us to design the component.

Things get a little bit trickier when you are designing .NET CF components. The .NET CF usually executes on memory constrained devices so it needs to be highly trimmed. Since attributes contribute to size of metadata and therefore overall size of the assembly, the usage of attributes in .NET CF code cannot be as lavish as that on the desktop. Because of this the .NET CF doesn’t support any of the designtime attributes. To work around this the VSD WinForms designers uses the XMTA and AsmMeta files. I will elaborate on the XMTA/AsmMeta files in a future blog but for now, in short, the pointers to the designtime code reside in the AsmMeta assemblies. So at designtime for a .NET CF component, the VSD WinForms designer locates the AsmMeta assembly for the component, reads the pointer to the designtime code from it, loads and executed the designtime code to allow us to design the .NET CF component.

An overview of the designtime infrastructure...

How do you typically use the IDE to design a windows forms application? For starters, you create a class that derivers from Form (or UserControl or Component). Then you open that class in design view and drag-n-drop other controls and/or components on it, edit the various properties and hook up handlers for the various events. While you are designing the application the IDE may look like the following:

Design surface items: Click to enlarge

As usual, the details hidden from the user are a lot more complicated. The following diagram will give you an idea of what is actually going on under the hood. A sound understanding of the pieces in the diagram is the key to understanding the .NET Framework Designtime Infrastructure. In this series I will be explaining each of these pieces.

Designtime Environment: Click to enlarge

How and where does the VSD WinForms designer fit in?

In the case of the desktop .NET Framework both designtime and runtime code is executed by the desktop CLR. But for .NET CF, the designtime code is executed by the desktop CLR (hosted inside Visual Studio) while the runtime code is executed by the device CLR. This implies that for .NET CF components the designtime code targets the desktop .NET Framework while the runtime code targets the .NET CF.

Also the architecture of .NET Designtime is such that during designtime the .NET Framework components (well controls really!) draw themselves! Meaning at designtime, in addition to the component's designtime code, its runtime code is also executing! Meaning during designtime, since Visual Studio has loaded only the desktop CLR, device controls that require the device CLR cannot run! All device components belong to the following two categories:

  1. Retargetable: On the desktop when you try and load a retargettable assembly (e.g. System.WinForms.dll), the desktop fusion will load the corresponding desktop assembly automatically
  2. Desktop Incompatible: Assemblies referring directly or indirectly to platform specific drawing/rendering code (e.g. through P/Invoke) For both above cases, you are forced to use the desktop components during design time!

The above is the basis for the .NET CF Designtime.

Moreover the set of Properties/Attributes/Events exposed by a .NET CF component is usually, but not always, a subset of the set exposed by the corresponding desktop .NET component. This needs to be reflected during the designtime – we cannot show a desktop only P/A/E while designing a .NET CF component. To achieve this VSD WinForms designer uses the XMTA/AsmMeta trick that is based on the P/A/E filtering natively supported by the .NET 2.0 designtime.

Central to the P/A/E filtering architecture is the TypeDescriptor. The v2.0 .NET designtime doesn’t use System.Reflection to obtain a component’s metadata. Instead the TypeDescriptor is used. Using it, you can remove all of the components native metadata (ones that you get by System.Reflection) and replace them with your choice of metadata! More on this in a subsequent post…

Thats all this post. Next post will dig deeper into the Designtime Environment diagram and start constructing our WiX UI designer.

See you later...