Introduction to the Cider Designer's Architecture

Brian in Cider | 9 Comments October 23, 2005

This is the first of a series of posts about the architecture of the Cider designer. Cider isn’t going to be shipping for a long time, so this isn’t information you can immediately put to use. Actually, I’m hoping that by publishing our plans very early like this I can get feedback about what you like and what you don’t like. That way I can get the stuff you don’t like out of the product before you see it. I’m going to roll this out by first describing to you how everything works, and then I’ll get into what the “everything” part is.

The first big shocker is that Cider does not use the IComponent / IDesigner architecture that Windows Forms and ASP.NET use. The most obvious reason for this is that nothing in WPF implements IComponent, but we had a lot of good reasons not to use this existing architecture. Here are just a few of them:

  • IComponent requires too much planning ahead. If you wanted something to be designable, you had to implement IComponent. But, as the types of designers in the world grow beyond form and UI designers, all of a sudden everything looks like it could be designable. Cider only requires that you derive from System.Object, which should be pretty easy for most folks.
  • IContainer, which is the counterpart to IComponent that makes all the designers work, assumes that all components being designed can be expressed as a simple linear collection of objects. While it is possible to flatten most any graph to a linear list, in WPF XAML trees can be quite huge, surpassing tens of thousands of elements. It takes some specialized data structures to handle object graphics of that size, and exposing this through a collection interface would impact the performance of everyone who accessed the interface.
  • In a word, services. IServiceProvider, which is the key to providing design time functionality in the IComponent world, is a big black box. In fact, it’s infinitely big: it is impossible to shine a light through GetService to see what’s inside. This makes documentation a key requirement to doing anything worthwhile in the designer. Also, because a service is never guaranteed to exist nor is it guaranteed to continue existing once you find it, programming to GetService in a robust fashion is very hard. Cider still is built on top of services, but only some things in the designer need to be aware of it. Also, Cider has a richer interface that builds on top of IServiceProvider to shine some light into the box.

One of the key requirements for Cider, however, is interop with other technologies like Windows Forms. We will teach Cider enough about IComponents and IDesigners in order for Cider to create designers for IComponent-based classes, so exiting components will work just fine in Cider. Now, let’s dig into the foundation of Cider’s architecture.

The Editing Context

All Cider designers start with a class called an editing context. If you’re familiar with the IComponent world, Cider’s editing context is somewhat related to IDesignerHost. Compared to IDesignerHost, however, Cider’s editing context has been put on an extreme diet. The EditingContext class keeps track of just two pieces of information: a dictionary of services and a dictionary of context items.

EditingContext is a good example of the kinds of design patterns you’ll find in Cider. Cider has very little coupling between its various systems. You can almost think of it as a grab bag of utility classes that, when combined in the right way, produce a working designer. This helps keep Cider nimble over time. Don’t be fooled by the simplicity of this class, however. It offers some very useful functionality. Let’s start by looking at how it exposes services.


Services in Cider are similar to services in the IComponent world. To Cider, a “service” is an instance of a class or interface that is identified by its type. Generally the code who implements the service and the code who consumes it know nothing about each other. Cider accesses all services through the editing context, and there is only one editing context for each designer, so everyone has access to the same set of services (no more groveling for the right IServiceProvider instance). The editing context offers services through an instance of a class called ServiceManager. ServiceManager has some features not found on IServiceProvider, and a few behavioral differences too. Notably:

  • You can “subscribe” to a service. This allows you to be notified when a service becomes available. This feature becomes very interesting when coupled with a feature I’ll talk about in a later post which allows you to declaratively state which services a piece of code needs, allowing you to delay activate that code until all the services it needs to function are present.
  • You can enumerate services. Yes, no more black box. The service manager implements IEnumerable<Type> so you can easily peer into it to see what’s happening.
  • Services are forever. Once added, a service cannot be replaced or removed without disposing the entire editing context (which closes the designer). While this is less flexible than IServiceContainer, it is far more predictable and I think it will help Cider be more reliable.
  • The service manager does not walk upwards looking for services if they can’t be resolved locally. This is a large departure from IServiceProvider, where it was common to check your local stash of services and if you couldn’t find a match you’d ask your parent. Cider doesn’t do that for the same reason it doesn’t allow you to remove services: simplicity and predictability are more important to us than being overly flexible.

Services make up a very important half of the story. Context items make up the other half. Let’s talk about those next.

Context Items

Context items are a new concept in Cider. When I started working on Cider I did two things. First, I interviewed a lot of people who were using the IComponent based designer model to find what they liked and what the didn’t like. Second, I looked at all the classes in the Windows Forms designer to see if there were common patterns we could roll into the Cider infrastructure so they didn’t have to be implemented again and again.

One of the common patterns that emerged from the Windows Forms designer was this idea of a service whose sole goal was to maintain a bit of state and provide an event to let other objects know when that state changed. The best example of this is ISelectionService. This service gives you access to an array of selected objects, and offers an event to let you know when the array has changed. A similar pattern is duplicated all over the Windows Form designer (IHelpService, IDesignerHost’s transaction API, etc).

Context items provide a standardized mechanism for this type of pattern. A “context item” is an instance of a class that:

  1. Derives from ContextItem.
  2. Is Immutable.
  3. Overrides ContextItem’s ContextItemType property to provide the type that should be used when locating context items.

Let’s look at how the concept of selection works in Cider to see how context items are used. Selection in Cider is based on the value of a Selection object, which derives from ContextItem:

public class Selection : ContextItem {
    public Selection(params object[] selectedObjects);
    public sealed override Type ContextItemType
        get { return typeof(Selection); }
    public IEnumerable SelectedObjects { get; }
    public object PrimarySelection { get; }

As you can see, there’s not much too it. It’s also pretty easy to use. If I wanted the selection to be “button1” I’d just do this:

Selection s = new Selection(button1);

If I wanted to know when selection changed I could subscribe to a change event on the editing context:

editingContext.Items.Subscribe<Selection>(delegate (Selection newSelection) {
    // selection changed, do something here.

Above I used a generic-based API to subscribe to the event, but non-generic events are supported too.

The true value of this model is that it can be extended. What if you had a designer that allowed the user to select text in a rich editor? You’d want a way for the selected text to be part of the selection. The only problem is that the Selection class I described above doesn’t have anywhere to store the text. No problem – just derive from the Selection class:

public class TextSelection : Selection {
    public TextSelection(string text, params object[] selectedObjects);
    public string SelectedText { get; }

Now if you want to set a text selection, pass a TextSelection object to the editing context. It will be seen as a selection (because it is), and anyone who is aware of text selection can check to see if the current selection contains text.

Well, that’s it for editing context. In my next installment I’ll talk about a layer that builds on top of the EditingContext class and sets the foundation for Cider’s extensibility model.

Comments (9) -

Frank Hileman, Tuesday, October 25, 2005 at 8:09 AM

Hello Brian,
I have many comments and opinions on the design-time architecture, but I will withold them until I read your second article. If I have enough time I will try to write an article contrasting your approach, with the approach we took for the MicroDesigner SDK, a toolkit for building custom graphical editors. We were able to reduce the amount and complexity of code by using a custom design-time architecture.
- Frank Hileman

Brian, Tuesday, October 25, 2005 at 3:41 PM

Frank -- Great!  I'd love to get your feedback.  We're trying to design this new architecture so it provides value for anyone writing a designer whose presentation is built on WPF (but the underlying thing your're designing can be anything).

SeanH, Wednesday, October 26, 2005 at 6:09 AM

"You can “subscribe” to a service. This allows you to be notified when a service becomes available. This feature becomes very interesting when coupled with a feature I’ll talk about in a later post which allows you to declaratively state which services a piece of code needs, allowing you to delay activate that code until all the services it needs to function are present. "
Do you do this via a delegate or do you actually queue the service instance for initialization until the depended services are availalbe?
Further, what about dependencies that are not that critical to a service (weak dependency)? Do you go ahead with initializing the weakly dependent one? Also, what about dependencies on services that are instantiated on first use?

Brian, Thursday, October 27, 2005 at 10:46 AM

Sean -- yes, subscribing is simply a delegate callback that tells you when the service is available. For weak dependencies, you do not declare a requirement for them. Instead, you use GetService and handle cases where the weak dependencies are null.

SeanH, Thursday, October 27, 2005 at 8:07 PM

For weak dependencies, so the current programming model is: subscribers would poll for the service instance until it is non-null?

foxybrown, Friday, October 28, 2005 at 5:08 AM

Just want to say that your site isn't properly showed in Firefox

jtasler, Sunday, November 6, 2005 at 3:39 AM

I'm not entirely sure what the ContextItem.ContextItemType property is for. From what you descibe with the example Selection class, it seems that the editing context can contain no more that ONE instance of a particular "type" of ContextItem. This is my observation from how you callled editingContext.Items.Change(s);
Is this correct?
If so, then it seems that the ContextItem.ContextItemType property is being used by the editingContext.Items.Change(s) call to know which ContextItem to replace (if any). This, with the fact that you sealed the ContextItemType override, would be how the TextSelection class could extend the base, while still being used by others needing the Selection context item.
I think I may have answered my own question. Please advise if I'm misunderstanding this. Thank you for making this open so early into your designs!

Brian, Sunday, November 6, 2005 at 10:23 PM

jtasler -- I think you've got a good handle on it.  Each context item offers a single ItemType which is the key to a dictionary.  There can only be one instance of each type at a time, so there can only be one Selection object, even if there are other classes that derive from it.  We're still working out some of the nuances here, like what happens if you cask for a TextSelection.  Current that throws, and tells you that you shoudl be asking for a Selection, because that's the base item type, but we're thinking about trying to do something more intelligent.
Sean -- yes, if you had weak dependencies you would need to poll for the service.  The only improvement here over what we had in System.ComponentModel is it is now illegal for anyone to remove a service on you, so you can freely cache the instance to your heart's content.

Brian, Sunday, November 6, 2005 at 10:26 PM

foxybrown -- yes, I am aware of that.  That speaks more about my inability to code CSS that works in mutliple browsers than it does any individual opinion of Firefox.  I have Firefox installed on my system and I use it whenever I update the site to make sure things are reasonably consistent, but I haven't found a good way yet to code the CSS so it works both on Firefox and on IE without resorting to tables and hacks.
Comments are closed