| Profilo di StephenBadCorporateLogoFotoBlogElenchi | Guida |
|
02 giugno Making a Designer of Your Own - A Starter CourseA couple people have e-mailed me recently about my "teaser" screen shots, because they want to know how I did it. As much as I'd like to explain the menu designer I'm working on, it's complicated. Writing long blog entries just isn't as interesting as writing code (for me, at least), so in all honesty, I'd rather work on the designer than try to explain what I'm doing. The easiest way to explain it is, I'm using the Windows Forms designer framework. It's reasonably well-documented, but not all in one place. You can't read it like a book. Someone For that reason, I'm not going to share my recipe -- not yet, at least. Instead, I'll try to focus on explaining how to cook. Let's start with an appetizer, and work our way up to the entree. In Visual Studio, there is already an extensible designer infrastructure for components (types that implement System.ComponentModel.IComponent). This infrastructure has further specialization to support Components (types that derive from System.ComponentModel.Component) and Controls (types that derive from System.Windows.Forms.Control). Note that the general infrastructure is for the IComponent interface, while there is extra stuff for the Component class, which is a default implementation of the IComponent interface. There is even more extra stuff for Controls and various specialized subclasses, like Form. The extra stuff is the interesting part. Windows Forms has an editor registered in Visual Studio that will handle any code file in a code project that contains a class declaration for a class that derives from Component. The IDE determines this by asking the language service (the thing that gives you Intellisense) for the first class declaration in a code file. Then it uses another service (probably ITypeResolutionService, but I forget, and it doesn't matter enough to check), to determine whether the base class of the type declaration derives from Component. If so, then the file is treated as a "component" file. Let's take a look at an example. Figure 1 shows a regular Class Library project in VC# Express. Look what happens when I edit the declaration for Class1 to derive from Component. Figure 2 shows the subtle change. In the Solution Explorer, the icon next to the Class1.cs file is the "component file" icon. Even though the file is already open in the code editor, now when you double-click on the file in Solution Explorer, it will open the Component Designer. Figure 3 shows the Class1 class in the the Component Designer. The Component Designer provides a basic, visual code editing experience. You can drag and drop components onto the Class1 design view from the Toolbox, and edit those objects in the Properties Window. The changes you make will be serialized to code in a method called InitializeComponent in the Class1 class declaration. After some experimentation, an observant person will note several things. First, the generated code appears in InitializeComponent, but that function is never called. This is because the designer generally ignores everything else in the class except that function. The standard practice is to define a default constructor that calls InitializeComponent. Since Class1 was not created from a Component template, I'll need to add that code myself. Second, the name of Class1 in the Properties Window refers to the name of the type Class1. Every other component dropped onto the design surface has a name that refers to the name of the member variable that the component is assigned to. Third, the properties in the Properties Window aren't the properties of Class1. If you add a new property to the class definition in Class1.cs, it won't appear in the Properties Window at all. For example, let's add a string property to Class1. public class Class1 : Component { private string myProperty; public string MyProperty { get { return myProperty; } set { myProperty = value; } } } Going back to the design view, MyProperty is nowhere to be seen. If you look closely at the Properties Window, you'll see the reason why. It shows Class1 to be an instance of System.ComponentModel.Component. This is important -- the designer creates an instance of the component's base class at design time. The same goes for editing Forms or Controls. This only applies to the root component, however -- the type being designed -- and not for components that are dragged onto the design surface. Part of the reason for this is because you are changing the source code for the type as you edit it in the designer -- making it difficult to keep a compiled instance of the type-being-edited up to date with the latest changes. Now that you know the designer creates an instance of the base type instead of the actual type, you might already have guessed what I will do next. I'm going to add another class to the project in its own file, Class2.cs, and declare a new class Class2 that derives from Class1. Now, as long as I've compiled the project first, I can double-click on Class2.cs and have it open up in the designer. This happens because Class2 derives from Component. Except this time, the instance on the design surface is an instance of Class1! See how the Properties Window shows MyProperty? Figure 4 shows that you can design your own types in the Component Designer. Getting your own class into the Component Designer is the first step. Now it's time to customize! There are a lot of ways to customize the design-time experience for particular components. Most of it is driven through metadata, or custom attributes. For example, using attributes, I can change the way MyProperty appears in the Properties Window. public class Class1 : Component { private string myProperty = "elephant"; [DefaultValue("elephant")] [Category("Animals")] [DisplayName("My Property")] [Description("Set this property to the first thing that comes to mind.")] public string MyProperty { get { return myProperty; } set { myProperty = value; } } } After rebuilding the project, the Properties Window for Class2 reflects the changes. Figure 5 provides an example of how you can customize the design-time behavior of your components. The MyProperty property is now displayed as My Property (note the space), it is in the Animals category instead of Misc, and it has a description at the bottom of the Properties Window. The value of the property is "elephant", which is the default value. This is important to the designer because when a property has its default value, it is not serialized to code. Imagine having a component with two hundred properties, and all but one is set to its default value (the value it has when constructed from a default constructor). You would want the code generated for that component to only set the one property that was different from its default, not all 200 values. Otherwise, such a class would initialize 199 of its properties twice! It's important to note here that the DefaultValueAttribute custom attribute is used as a hint to the designer, and doesn't actually set the property to anything. It's purpose is to tell the designer what the default value is, so the designer can compare the property's actual value against it when it's time to generate code. The Properties Window also uses it to visually indicate which properties need to be serialized -- that is, if a property's value is anything but the default, then that value will appear in bold in the Properties Window. If you specify the wrong value in the attribute, the designer will behave incorrectly -- so be careful! There are a bunch of attributes in System.ComponentModel that are interesting. A few noteworthy attributes in terms of editing properties are:
In my menu designer, I've been getting a lot of mileage out of creating custom type converters. I created one to enumerate all types that derive from GameScreen to assign to a System.Type property, I created one to enumerate the names of all SpriteFonts in the nested Content project to assign to a string property, and I created another to enumerate the names of all Texture2D content to assign to another string property. Okay, that's as far as I'm going to get in one sitting. Our starter course is looking pretty modest -- more like cheese and crackers than shrimp cocktail -- but at least there's something on the plate! RiferimentiL'URL di riferimento per questo intervento è: http://badcorporatelogo.spaces.live.com/blog/cns!43EB71B104A2D711!283.trak Blog che fanno riferimento a questo intervento
|
|
|