More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  BadCorporateLogoPhotosProfileFriendsMore Tools Explore the Spaces community

BadCorporateLogo

Good Workmen Never Quarrel with their Tools
October 05

Making a Designer of Your Own - An Imperfect Example

Months ago, I started a little project that was supposed to be a diversion from my day job. My day job is building XNA Game Studio... And my project was an experiment to make programming with XNA Game Studio easier. I admit that I'm not very good at creating diversions for myself.

Anyway, after a couple nights of playing with samples from the XNA Creators Club Online web site, I had integrated the MenuScreen class into Visual Studio's designer framework. The result was being able to double-click a code file in a game project, and have a game menu open up in a document window. I added support for dragging-and-dropping menu items from the Toolbox, and editing properties in the Properties Window. It demonstrated some exciting possibilities, but at that point was only barely functional.

After posting some initial screenshots to my blog, I discovered that, although I'm terrible at distracting myself from work, I am pretty good at finding interesting things to work on. My blog traffic exploded by two orders of magnitude, and I received a number of requests for source code.

I'm pretty certain that my source code isn't self-explanatory. The designer I built is a customization for an existing framework, and the vast majority of the functionality isn't in my source. Anyone who understands enough of the framework to see how my source code works doesn't need the source code in the first place. So instead of answering requests with code, I started to explain the framework I was integrating into.

I'm finally at the point where further explanation will be easier to follow with a working example. For that reason, I've put my prototype project on CodePlex. The project doesn't build a ready-to-use designer. The design-time experience is runnable, but not usable. It's got tons of potential, and I'm going to continue working on it, but at this point, it's far from perfect.

My CodePlex project is called ferpect. If you are interested in building designer support for your own libraries, then I encourage you to take a look at it, and join the discussion if you'd like to request specific features be added for your reference.

Ferpect Game Component Model

I'm really looking forward to continuing my work on this project. Now that the fundamentals are out of the way, I think being able to review changesets and associated check-in comments will cut down on the amount of explanation required for new features and concepts.

That's all for now!

September 28

Making a Designer of Your Own - Mixing and Folding

For those of you who are waiting for my next article on creating a designer for XNA Framework game components, here it is! I've been working on creating a CodePlex project for the sample source code, but this turned out to be more work than I first imagined. I took a few shortcuts when I initially created my little designer, and for the code to work on other people's machines, I had to implement a bunch of additional features (no more hard-coded paths or pre-compiled assets!).

Anyway, as I continue to get that all prepared, I was forced to fix a few bugs in my designer serializer. I'd forgotten how some of my own code worked, so it took a bit of debugging before I figured out what was going on and how to fix it. Certainly there will be more bugs, so I decided to write about how serialization works -- both for my own benefit, and for others.

What The Heck is Serialization?

In the managed designer infrastructure, serialization is the process of converting objects into source code, and deserialization is the process of converting source code into objects. In general, serialization is easier than deserialization, but both are quite complicated. Fortunately, the managed designer infrastructure provides an extensible serialization implementation based on CodeDom. The key word there is "extensible," because it means we can rely on the standard behavior most of the time, and then customize things when we need to.

For convenience in the following description, let me introduce the term serialization manager as the part of the designer infrastructure that coordinates serialization and deserialization.

To deserialize an object from source code, the serialization manager invokes a parser to convert the source code from a file into its CodeDom representation. The CodeDom represents a type declaration and its initialization method. The serialization manager reflects on the base type to retrieve its DesignerSerializer attributes, which identify the specific CodeDom serializers to use. Then the serialization manager will instantiate the appropriate serializer and invoke its Deserialize method.

There are two kinds of serializers: TypeCodeDomSerializer and CodeDomSerializer. The first is for serializing/deserializing a type declaration. The second is for serializing/deserializing statements. To deserialize an object from code, you need both kinds of serializers.

This is a good time for an example.

Let's say that I have the following code in a file called MenuScreen1.cs:

   1:  public class MenuScreen1 : MenuScreen
   2:  {
   3:      private ToggleMenuItem toggleMenuItem1;
   4:   
   5:      public MenuScreen1()
   6:      {
   7:          this.InitializeComponent();
   8:      }
   9:   
  10:      private void InitializeComponent()
  11:      {
  12:          this.toggleMenuItem1 = new Ferpect.GameState.MenuItems.ToggleMenuItem();
  13:          // 
  14:          // toggleMenuItem1
  15:          // 
  16:          this.toggleMenuItem1.Label = "Sound";
  17:          this.toggleMenuItem1.ToggleValue = true;
  18:          // 
  19:          // MainMenuScreen
  20:          // 
  21:          this.MenuItems.Add(this.toggleMenuItem1);
  22:          this.Text = "Options";
  23:      }
  24:  }

When the parser reads this file and converts it to its CodeDom representation, it ignores all properties and methods in the class except the InitializeComponent method. The resulting CodeDom is a CodeTypeDeclaration with fields and one method containing a statement collection.

After parsing, the serialization manager needs to get the serializers for the base type of the class being deserialized. By inspecting the CodeTypeDeclaration, the serialization manager will determine the base class is MenuScreen (as seen on line 1). It then uses ITypeResolutionService to get a Type instance for MenuScreen, and then uses TypeDescriptor to get its DesignerSerializer attributes. If the base type doesn't have any DesignerSerializer attributes, the serialization manager continues searching up its inheritance chain until it finds a base type that identifies a TypeCodeDomSerializer.

After finding the type serializer for MenuScreen, the serialization manager will instantiate the serializer and invoke its Deserialize method, passing it the CodeTypeDeclaration for MenuScreen1. The standard TypeCodeDomSerializer will look at the base type in the CodeTypeDeclaration, MenuScreen, and instantiate it.

This very first object is returned to the serialization manager and placed on the design surface. Since it's the first component on the design surface, it becomes the root component, and its name is the name of the type being deserialized, MenuScreen1. The serialization manager then adds MenuScreen1 to its name table.

At this point, MenuScreen1 is only partly deserialized. After adding the root component to its name table, the serialization manager begins to interpret the code statements in the InitializeComponent method. The InitializeComponent method contains statements that are sorted into groups according to the object reference on the left-hand side.

That is, all the statements referencing "toggleMenuItem1" are grouped together, and so are the statements referencing "MenuScreen1". Each member variable is added to the name table, then for each one, the serialization manager retrieves the CodeDomSerializer for the member variable's type, and uses it to deserialize that member's assignment statements. The last step is to retrieve the CodeDomSerializer for MenuScreen, and use it to deserialize the remaining assignment statements for MenuScreen1.

If there is an assignment statement that references another member variable on the right-hand-side, the serialization manager is used to resolve the name. If the object hasn't been added to the name table yet, then the serialization manager will immediately deserialize the named component's statements so that it can return the instance.

In the example above, toggleMenuItem1 will be deserialized into a ToggleMenuItem instance, and that instance's Label and ToggleValue properties will be initialized to "Sound" and true, respectively.

When MenuScreen1's statements are deserialized, you can see there is a reference to toggleMenuItem1 on line 21. The serializer will resolve this reference by asking the serialization manager for an object with that name. Since toggleMenuItem1 is already deserialized at that point, it is returned, and then can be used to be added to the MenuItems collection.

When the deserialization is complete, the design surface shows a MenuScreen instance that has been initialized exactly like the code in the MenuScreen1.cs file.

Serialization is similar, but works in the opposite direction. The root component is serialized into a type declaration, then each of the other components on the design surface becomes a member variable plus some initialization statements, and finally the root component's initialization statements are generated.

The statements usually are generated into the InitializeComponent method, because that's the only method that will be parsed during deserialization. A designer isn't going to be much use if it can't round-trip from objects to code and back again.

The code file, MenuScreen1.cs may contain other code written by the user. For example, the user may add event handlers. After serializing the design surface to CodeDom, the CodeDom is turned into source code by a code generator. That source code is then strategically inserted into the source file to completely replace existing methods with the same names.

Skillful Substitution Explained

In my last post on designers, I said the following:

Every experienced cook knows how to substitute certain ingredients for others. However, it takes a certain amount of skill and know-how to still end up with a palatable result. (I'm really determined to keep this cooking analogy going...)

The reason I started to write this whole series in the first place was to explain how to re-use the existing Windows Forms designer for XNA Framework game components. The problem with trying to use the Windows Forms designer is that it only works with Windows Forms Control classes, and our XNA Framework game components are not Controls.

As I discussed in previoius posts, it is possible to implement a Windows Forms Control that will create an XNA Framework graphics device and draw an XNA Framework game component. Following my example above, let's imagine that MenuScreen is an XNA Framework game component. To be able to design this component in the Windows Forms designer, I need to deserialize MenuScreen into a Control that can draw a MenuScreen component (let's call that a MenuScreenControl).

I need several things for this. First, I need to put an attribute on MenuScreen to point to a custom type serializer. The type serializer is responsible for deserializing the CodeTypeDeclaration into an object instance. With a very small amount of code, it's possible to create a custom TypeCodeDomSerializer that simply modifies the CodeTypeDeclaration it is given so it looks like MenuScreen1 derives from MenuScreenControl, and then passes it along to the TypeCodeDomSerializer for MenuScreenControl.

The MenuScreenControl instance will use ICustomTypeDescriptor to make it look like a MenuScreen instance when viewed through reflection (design-time reflection is always done with TypeDescriptor). Since serialization uses TypeDescriptor, we don't need to modify the TypeCodeDomSerializer's serialization step -- because the ICustomTypeDescriptor implementation fools the serializer into thinking it's serializing a MenuScreen instance.

The MenuScreenControl implementation is pretty straightforward. It is just a GraphicsDeviceControl with ICustomTypeDescriptor. Since it needs to draw a MenuScreen instance, it makes sense to instantiate one of those and store it in a member variable. Then the ICustomTypeDescriptor implementation just needs to invoke TypeDescriptor on the MenuScreen instance.

Note: There is a bit of futzing required to make the properties work properly in the Properties Window, but I'll explain that another time.

The last thing required is a custom CodeDomSerializer, also identified by an attribute on MenuScreen. This one is required to acquire the CodeDomSerializer of a standard Component, and then just delegate its Serialize method to that instance. The reason for this is because the initialization statements are generated by the CodeDomSerializer, and the standard serializer for a Control will generate additional statements for Control layout that won't compile when applied to a game component.

Wrapping Up the Leftovers

At this point, you're probably thinking, "How many times can this guy write 'serializer' in one post?" I don't blame you. But if you stuck through all this, then you're probably starting to see how extensible the designer framework really is, and how finding the right extensibility points is the key to re-using code and saving yourself a truck load of work.

September 17

Debugging Zune Games in XGS 3.0

If you've been using XNA Game Studio to develop games for Zune, you're probably familiar with the following debug output:

The thread 0x1234 has exited with code 0 (0x0).

This message gets spit into the Output Window while debugging, about once per second, if not more. It shows up so frequently, in fact, that it makes it very hard to see any other output at all!

Since it's pretty rare that you're actually interested in these thread exit messages, seeing them can get quite aggravating. That's why I'm posting this to explain how to suppress these messages from the debug output.

While debugging, right-click on the Output Window, and uncheck "Thread Exit Messages". Done! Now you can use the Output Window again!

image

Contact Info

I know I said in my last post (ages ago!) that I would provide designer source code in my next post (this one)... But I was wrong, because here is the next post and there's no source code anywhere to be found!

This post is specifically to address a couple people who have tried to contact me recently. I can't reply to messages unless you provide an e-mail address, or you set your account permissions to allow anyone to send you messages. If you were expecting a reply and didn't get one, please verify that you gave me a way to answer, and ping me again.

I'm happy to answer a few questions, but I do need a way to respond.

Thanks!

P.S. For the rest of you, I've started setting up a project on CodePlex... But in doing so I found a bunch of features I haven't implemented yet. For example, rendering the design surface when an asset can't be loaded (what should the menu designer show when there are no fonts in your project?).

July 27

Making a Designer of Your Own - Soup Stock

In my last couple posts, I've been explaining concepts needed to customize the managed designer infrastructure in Visual Studio. I've been forced to put this project aside for quite a while on account of being too busy with other things... But this weekend I really need to think about something other than work for a while. Despite Shawn thinking I'm nuts for having the same hobby as my work, the important thing for me is that I work on different problems. Thinking about my own programming problems doesn't involve quite the stress as thinking about programming problems for work.

Anyway, I've been using the analogy of learning to cook as the theme of my blog posts. In this post, I'm going to describe a set of base classes that we can re-use for nearly any type of designable component. In cooking terms, it'll be like creating a soup stock. It forms the basis of many dishes, and each one can be uniquely characterized by adding different elements!

The Concept

I want to create a visual designer for an XNA Framework drawable component, so the first thing I need is a way to render it in a Visual Studio editor. When I first thought about this, two things came to mind. First, there is already a designer for Windows Forms Controls. Second, the Creators Club Online web site has a sample showing how to render XNA Framework graphics classes in a Windows Form Control.

At this point, it almost seems like there's nothing left to do! On the other hand, I don't really want a Windows Forms Control in my game because Windows Forms is not supported on either Xbox 360 or Zune. I would prefer to keep the code in my game libraries platform-neutral, so I can use the same components no matter what platform I'm targeting. Hmm...

I really want a designable XNA Framework game component that doesn't derive from Control, but creates a Control in the design surface when opened in design view. Furthermore, I want to edit the component's properties instead of the Control's properties. Sounds difficult -- and it is -- but applying the right ingredients to a powerful stock will result in exactly the secret sauce I'm after.

For now, let's focus on building the right stock. Later, I'll explain the switcheroo.

The Prep

The sample (here) provides source code for GraphicsDeviceControl, a Windows Forms Control that renders XNA Framework content. It's trivial to derive from this existing class and override the Draw method to draw whatever XNA Framework content that I want. That means it's trivial to make a designable Control that uses the XNA Framework to render itself.

To get started, we need to prepare a couple things. First, I'd like a base class for my designable components. The XNA Framework's DrawableGameComponent isn't suitable as a base for my designable components because it requires a Game class instance to be provided to its constructor. At design-time, we're not going to have a Game instance, so we wouldn't be able to instantiate and use it. We also would like the base class to derive from Component so that Visual Studio recognizes it as designable (at some point, we're going to need this, even if I haven't explained how it will work yet).

To start, we need two classes: the first will derive from GraphicsDeviceControl, and the second will be a modified version of DrawableGameComponent.

Class Description
ViewControl<TComponent> Derives from GraphicsDeviceControl, the control from the sample I mentioned earlier. It instantiates TComponent, which must implement IComponent, IDrawable, and IGameComponent. This control's Draw method will clear the device and invoke Draw on the TComponent instance. ViewControl<> is intended to provide the design-time view for TComponent, so it will also include logic to draw at a fixed timestep so that it doesn't monopolize the IDE's CPU usage.
ViewComponent A base implementation of IComponent, IDrawable, and IGameComponent. It is very similar to DrawableGameComponent from the XNA Framework, but differs in that it doesn't depend on a Game class being passed to its constructor.

To create a specific designable component, I'll need to introduce more classes. However, each specific designable component can re-use these same base classes. We'll look at examples another time.

The Code

I put the code files below on my skydrive, with links below.

Some of the code files are copied or derived from samples that were made available under the Microsoft Permissive License (Ms-PL). In accordance with the license, my derivative source can only be made available under the same license. Be sure to read it before looking at any of the code files!

For your convenience, I'll note the source of the original code file and a summary of the changes I've made (at least those I remember).

File Description
GraphicsDeviceService.cs
  • Originally from the WinForms sample.
  • There were two Interlocked references in this class that worried me. Use of Interlocked implies it should be thread-safe - but if that's the case, the sample code contains several bugs! I fixed the race conditions in AddRef/Release just in case, but there are more I didn't fix. [Edit: Oops! It turns out there wasn't a race condition there at all! The class is not intended to be thread-safe in general, except for the particular case of handling a finalizer thread, which it was doing correctly.]
GraphicsDeviceControl.cs
  • Originally from the WinForms sample.
  • Removed the "if (!DesignMode)" condition from OnCreateControl. The whole purpose of this control is to render our graphics at design-time! :-)
  • Switched to using the .NET Framework's ServiceContainer rather than the one defined in the sample (which does the same thing).
ViewComponent.cs This is the base class I want to use for my designable objects. It derives from Component to tie into the designer infrastructure. It also implements IDrawable, IGameComponent, and IUpdateable. These interfaces are implemented so that the specific components you create from this base class can be plugged into any XNA Framework game in a standard way.

There is an obvious problem with IGameComponent in that its Initialize method doesn't take any arguments. It's nearly useless as an interface without providing an IServiceProvider instance, because for every object implementing it, you need to define some custom mechanism outside of the interface to connect the object to its environment. To work around this problem, ViewComponent uses a ContentManager property named Content. This property must be set before Initialize is called so that the object can query its environment through the ContentManager's ServiceProvider property.

The rest of the events, properties, and methods are implemented as simply as they can be. Attributes are used to provide standard design-time metadata.
ViewControl.cs This is the control that will draw our components at design-time. It has to serve about the same purpose as the Game class in a game program, which is to provide a graphics device and other services, and to invoke the XNA Framework interface methods at the appropriate time.

My view control is quite simple at the moment (it will evolve in the next post). It uses a Stopwatch instance to invalidate the control every 16 milliseconds (max). The Draw method is then overridden to clear the graphics device and draw the ViewComponent specified as the generic type argument.

Although I said that the ViewControl is supposed to replace the Game, you might have noticed that this class doesn't actually invoke Initialize or Update on the ViewComponent. That's partly because the decision to invoke Update will depend on the specific ViewComponent instance, so it should be implemented in the specific class that derives from ViewControl<T>. Another reason is because we haven't looked at how to create a ContentManager that can be used at design-time. Invoking Initialize without a ContentManager will not succeed if the ViewComponent needs to load any content.

Next Steps

We've gathered up the primary ingredients, but it's pretty clear we can't just throw them in a bowl and set the mixer on high. We have a ViewControl class that is supposed to draw the ViewComponent, but we don't have a way to get the user experience we need. What we want is to be able to add types in a project that derive from a specific ViewComponent, and have that show up in the designer when you double-click the file.

So, for example, let's say we have this specific ViewComponent:

public class MenuScreen : ViewComponent

{

}

We want to be able to define new classes in our projects like this:

public class MenuScreen1 : MenuScreen

{

}

...And then have MenuScreen appear in the designer when the code file is opened in design view.

As I described in a previous post, the designer infrastructure will instantiate the base class of the designable component. We already know that MenuScreen is a ViewComponent, so it relies on its host to provide a graphics device and to tell it when to draw.

To be able to draw the MenuScreen, we need another class:

public class MenuScreenControl : ViewControl<MenuScreen>

{

}

We don't want this control to show up anywhere in the project where MenuScreen1 is defined, but we do want it to be instantiated in the designer in place of MenuScreen. The magic trick to this is customizing MenuScreen's designer serializer.

For detailed information on designer serialization, refer to Designer Serialization Overview.

When you try to open a code file in design view, the designer infrastructure looks at the base type of the class to see if it inherits from Component. If it does, it then looks to see if the class has a designer serializer. If the type doesn't specify one, the serializer is inherited from its base class. By default, every designable component will use the Component's designer serializer. The job of the serializer is to instantiate the component from its serialized state (or to deserialize it).

The designer serialization is based on CodeDom, so the serializer is passed a CodeDom definition of the base class from the code file, and is asked to deserialize it. Naturally, the default serializer simply instantiates the type specified in the CodeTypeDeclaration passed to it.

By providing a custom designer serializer, we can customize the deserialization step. You might have already guessed that by doing this, we can instantiate a MenuScreenControl in place of the MenuScreen component specified in the CodeTypeDeclaration!

Skillful Substitution

Every experienced cook knows how to substitute certain ingredients for others. However, it takes a certain amount of skill and know-how to still end up with a palatable result. (I'm really determined to keep this cooking analogy going...)

In our designer, it's not enough to just instantiate a MenuScreenControl in place of a MenuScreen. When looking at the design surface, it'll be exactly what a user might expect, but as soon as the user tries working with it, the illusion will fall apart! The Properties Window is going to reveal that the class is a MenuScreenControl instance, and it won't have the MenuScreen's properties. It's like we're trying to pass off tofurkey for the real thing at Thanksgiving dinner! Nobody who's ever had real turkey before is going to be fooled!

More design-time tricks are needed, and this time, ICustomTypeDescriptor is our secret ingredient. By implementing ICustomTypeDescriptor on our MenuScreenControl, we can make it appear to be an instance of MenuScreen. We can give it the same type name, the same properties, the same attributes. The best part is that if we do it right, we'll even fool the MenuScreenControl's serializer into generating code for a MenuScreen. That means no more serializer customization!

To pull off the ICustomTypeDescriptor implementation, a few helper classes are needed. They aren't very complicated, but it'll be easier to just show them off with a sample.

Next time, I'll provide a working sample and explain how to set it up properly. Proper set up is essential to avoid common pitfalls in designer debugging. After that, I'll start to introduce some neat feature ideas specifically for XNA Framework projects.

July 12

Managed Designer Resources: The Restaurant Supply Store

Recently, I've been pretty swamped by work. Nights, weekends, holidays... I've been in the office so long, my wife has forgotten my name! This year, crunch time is like the playoffs - I'm not shaving and my beard is growing out. It's my first time with a beard, really. Honestly, it drives me nuts. I hate having food or drinks touch my my moustache and leave a lasting flavor. So besides working all the time, I'm also constantly wiping my mouth. Bleah!

Anyway, I've gotten a few messages from people asking for more about designers. Well, I still don't have time to put into writing. I did a couple weeks ago, but a hardware failure destroyed several hours of work and I didn't feel like starting again right away. Then I got too busy.

So instead of writing my own article on designers, I'm going to post a number of links where you can get information from the source: MSDN.

If I'd found the second link earlier, I probably wouldn't have written one of my posts since it covers the same stuff. I think I remember reading it several years ago, but I didn't find it when I looked again.

Here's a topic on developing components:

http://msdn.microsoft.com/en-us/library/51sc2s5c(VS.71).aspx

This specific sub-topic discusses design-time attributes:

http://msdn.microsoft.com/en-us/library/tk67c2t8(VS.71).aspx

That page links to the docs on customizing the design-time, which is what I'm writing about:

http://msdn.microsoft.com/en-us/library/37899azc(VS.71).aspx

The set of topics above actually provides a great deal of information and several samples. I think that's the best set of documentation for this kind of stuff.

After having posted these links, I'll try to focus on XNA Framework-specific customizations from now on. It'll probably be another week before my schedule is back to normal. After that, I'll be back to writing.

June 24

More on Designers... Still to Come

I've started the next three posts for my series on customizing the designer. Unfortunately, I haven't had time to finish any of them. I've received a couple messages from people saying thanks for the info... But when is the next part coming? :-)

I really appreciate that people are interested, and I do intend to continue writing. Sorry that it's taking me so long. Life has caught up with me.

When I built my menu designer in the first place, it was because I was looking for a diversion after writing a big design document. Well, implementation of that design is underway, and I'm having to put in longer hours to try to keep the schedule I set for myself (I ran into a couple unexpected setbacks -- which, ironically, is generally expected). That leaves less time at the end of the day for other interests.

Add to that the fact that it finally stopped raining (it rains all winter here), and suddenly I don't have an excuse anymore to sit inside instead of going out to work on my house and property. The remodel I started in January isn't finished yet, and I've got family coming to visit later this summer. So my evening hours are spent working with power tools instead of software tools.

My wife doesn't care who's waiting for my next blog post - she wants the house put back together and the furniture moved back in!

Thanks for your patience... I'll be posting the next article as soon as I can!

View more entries
 

Xbox Live GamerCard

Righteous Tool
Xbox Live GamerCard
Rep:
Reputation:Reputation:Reputation:Reputation:Reputation:
Score:
9163
Zone:
Recreation
N+Mass EffectTiQalJewel QuestCastle Crashers
Updated 2/25/2008
Updated 9/18/2008