使用Visual Studio 2005创建以及移植智能设备自定义控件

来源:互联网 发布:用手机怎样申请淘宝号 编辑:程序博客网 时间:2024/05/16 06:54
本文适用于
   Microsoft .NET Compact Framework version 1.0
   Microsoft .NET Compact Framework version 2.0
   Microsoft Visual Studio .NET 2003
   Microsoft Visual Studio 2005
   Windows Mobile 2003 software

摘要:学习如何使用Visual Studio 2003和Visual Studio 2005来创建自定义控件,学习如何升级那些现存的针对智能设备和 .NET Compact Framework的自定义控件。本文的重点在于一步一步的教会你如何创建一个自定义控件以及为其添加设计时特性,缺乏友好的设计时体验的自定义控件最终用户是很难接受的。

内容提要
简介
待开发控件的背景介绍
使用Visual Studio 2003开发该自定义控件
使用Visual Studio 2005开发该自定义控件
如何将Visual Studio 2003开发的自定义控件移植到Visual Studio 2005
结论

简介
到目前为止,所有的软件开发者,都应该能够明显的感受到现代的快速应用开发环境给我们的生产效率带来的显著提升。开发者们基于自己的不同目的,使用着不同的开发环境来开发应用。一些是基于Internet的商业应用,一些仅仅是为了牟利,还有一些则是开发者完全免费提供给社区的。但不论开发动机如何,所有这些应用的用户界面的基本组成都是一个个被称为控件的元素。

通过使用在Microsoft Visual Studio称为Windows Forms Designer的窗体设计器,控件可以被快速的对齐、移动、调整大小等等,总而言之,就是开发者能够在设计时即所见即所得的完成他们的开发。应用的用户界面设计完全成了一件艺术,而再不需要过多的技术。窗体设计器提供了一块画布,鼠标就像一把刷子,而控件则成了一个像点、线这样的基本绘图元素。

When a developer uses Microsoft Visual Studio .NET 2003 to create custom controls for smart device applications the process is tedious and error-prone. Creating a custom control that functions solely at run time almost seems trivial. Unfortunately, getting that custom control to properly integrate with the designer can be frustrating for Visual C# developers, and it is not possible for Visual Basic developers. But that was then; now, Visual Studio 2005 has changed the way that developers build design-time support for custom controls.
当一个开发者使用Microsoft Visual Studio .NET 2003为智能设备程序创建自定义控件时,创建过程非常的繁琐且容易出错。不幸的是,要让自定义控件与设计器很好的集成对于C#开发者来说更是一件让人痛苦的事情,而对于VB开发者来说则干脆就是件不可能的事情。不过这都是过去的事了,现在Visual Studio 2005已经改变了为自定义控件开发设计时支持的方式。

待开发控件的背景介绍

Throughout this article, you're going to develop a custom control for Contoso, Ltd. that targets the Pocket PC platform. The general purpose of the control is to display text to the end user and to provide a strong design-time experience for the end developer. The result will be two versions of your control—one compiled against the Microsoft .NET Compact Framework version 1.0 and another compiled against version 2.0. However, because you want the maximum number of Pocket PC developers to be able to use this control, you'll also need to ensure that you have the expected design-time experience in both Visual Studio .NET 2003 and Visual Studio 2005.

This is one of the more complicated scenarios that a custom control developer may encounter because Visual Studio 2005 enables developers to target Windows Mobile 2003–based Pocket PCs with .NET Compact Framework 1.0 and 2.0 project types. What this means to you is that the control needs to support both the old and the new design-time paradigms because Visual Studio 2005 offers the same design-time support to .NET Compact Framework 1.0 project types as it does to the 2.0 project types. The challenge is to provide seamless support to end developers for the versions of the .NET Compact Framework and Visual Studio that are currently on the market.

When developing a custom control or class library, following the Design Guidelines for Class Library Developers is important for the discoverability of types and members in addition to the consistency of fitting in with the .NET Compact Framework.

You should name the control BasicLabel; you should define an AutoSize property that automatically sizes the control to the displayed text at both design time and run time; and you should also define a BorderStyle property that allows the border of the control to be modified at both design time and run time. Following the design guidelines that state that a namespace should be constructed from the company name and the technology name, you should use "Contoso.Windows.Forms" as the primary namespace in which the control is defined and use "Contoso.Windows.Forms.Design" as the associated design namespace when you should define any design-time–specific types. With this information, the fully qualified name of the control should be Contoso.Windows.Forms.BasicLabel. This name implies that the company (Contoso) is building upon a technology (Windows Forms) by providing a class named BasicLabel that defines a custom control. The basic code definition for your BasicLabel class looks like the following.

Copy Code
namespace Contoso.Windows.Forms
{
public class BasicLabel : System.Windows.Forms.Control
{
...
}
}

In this scenario, because you're inheriting from the Control class, which does not provide any foreground painting, you've elected to perform the painting yourself. In contrast, if the goal was merely to add functionality to an existing Windows Forms control, you could inherit from that control and add custom members and still allow the base control to continue to handle the painting. In this situation, the custom painting is straightforward. All painting is happening in the overridden OnPaint method, which is called in response to a WM_PAINT message indicating that all, or only part, of the control should be redrawn. The background and foreground are drawn together in the same method, which can reduce noticeable flicker that may be caused when the background and foreground are painted in response to separate Windows messages.

To accomplish this task, you simply override the OnPaintBackground method and purposely avoid calling the base class implementation, which would otherwise paint the background. In the OnPaint method, the background is drawn, followed by the foreground, that uses padding to ensure that the text is drawn slightly away from the border. Then, you dispose of the brushes used, so any resources that these brushes consume can be released as soon as possible. The following code shows the activity in the OnPaint method.

Copy Code
protected override void OnPaint(PaintEventArgs e)
{
// Draw the background.
SolidBrush backColorBrush = new SolidBrush(this.BackColor);
e.Graphics.FillRectangle(backColorBrush, this.ClientRectangle);

// Draw the foreground.
Color foregroundColor = ((this.Enabled) ? this.ForeColor :
SystemColors.GrayText);
SolidBrush foreColorBrush = new SolidBrush(foregroundColor);
Rectangle textRectangle = this.ClientRectangle;
textRectangle.Inflate(-2, -2);
e.Graphics.DrawString(this.Text, this.Font, foreColorBrush,
textRectangle);

// Clean up.
backColorBrush.Dispose();
foreColorBrush.Dispose();

// Call the base class implementation to ensure that the
// Paint event will be raised.
base.OnPaint(e);
}

protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing.
}

The properties are implemented in a standard way. A private field stores the actual value. The property provides abstraction that, in turn, gives you the flexibility to apply visual or behavioral changes based on the supplied value. As an example, look at the following BorderStyle property.

Copy Code
private System.Windows.Forms.BorderStyle borderStyleValue;

public System.Windows.Forms.BorderStyle BorderStyle
{
get
{
return borderStyleValue;
}
set
{
...
if (borderStyleValue != value)
{
borderStyleValue = value;
...
// Code to call Win32 API functions so that the window
// style of the control can be modified to indicate that
// the system should handle drawing the border as non-client
// area, if applicable.
...
}
...
}
}

From within the set accessor of the BorderStyle property, Microsoft Win32 API functions are called to modify the border of the control. Allowing the system to draw the border means that you're not responsible for painting the border yourself. There are some obvious, and maybe not-so-obvious, benefits to this approach. If you were responsible for writing this code, there is a possibility that you could introduce a subtle bug. Tracking down bugs costs time. Another benefit is size. The more code you have, the larger your run-time assembly will become. Under the not-so-obvious category, having the system draw the border also means that any properties related to the client area, such as ClientRectangle and ClientSize, will return the correct values because the border will now be correctly classified as non-client area.

The Win32 API functions GetCapture, GetWindowLong, SetWindowLong, and SetWindowPos are used to update the control's window style. However, these functions are wrapped inside a utility class to hide details of the underlying operating system from the control. The control calls the exposed method that has the same name as the desired Win32 API function, and the method internally makes the appropriate platform invoke call. Because the dynamic link library that contains these function calls has a different name on the desktop operating system (User32.dll on Windows 2000, Windows XP, and related operating systems) than it does on the device or emulator operating system (Coredll.dll on Windows CE), having the Win32 API functions wrapped allows the same class to be used on both platforms. This level of abstraction is important because your control will be used through Visual Studio at design time. Now, the BorderStyle property will function through the form designer in the same way that it would on the target device or emulator.

A custom control developer should always remember the rapid application development environment and the need that end developers have to visualize their applications without having to deploy and run the applications after each change just to see the user interface. One, possibly obscure, fact of the .NET Compact Framework is that it's retargetable to the full Microsoft .NET Framework. This term means that a developer, or even an end user, may attempt to use the run-time version of your control by running his or her application on the desktop computer. Without wrapping any Win32 API functions as you have done, the control will not exhibit the expected behavior or may cause the application to encounter an exception when the specified dynamic link library is unable to be located. A scaled-down version of the Win32 API wrapper class is as follows.

Copy Code
public class API
{
private static readonly bool UsingWinCE;

// The GetCapture function in Windows is located in User32.dll.
[DllImport("User32", EntryPoint="GetCapture")]
private static extern IntPtr GetCaptureWin();

// The GetCapture function in Windows CE is located in Coredll.dll.
[DllImport("Coredll", EntryPoint="GetCapture")]
private static extern IntPtr GetCaptureWinCE();

...

public static IntPtr GetCapture()
{
// Depending on the underlying operating system, call the
// appropriate Win32 API function.
if (UsingWinCE)
{
return GetCaptureWinCE();
}
else
{
return GetCaptureWin();
}
}

...

// Check the underlying operating system and store a flag that
// will be used by any wrapper method to determine whether it
// should call the Windows or Windows CE version of the
// corresponding Win32 API function.
static API()
{
UsingWinCE = (Environment.OSVersion.Platform ==
PlatformID.WinCE);
}
}

Now that you've briefly examined some code that will be used at both design time and run time, you can learn how to build this control by using Visual Studio .NET 2003, and then how to create the same control using Visual Studio 2005 and still provide strong design-time support.

使用Visual Studio 2003开发该自定义控件

As review, and for those of you who had the pleasure of not experiencing what it takes to build a custom control using Visual Studio .NET 2003, you're going to create your custom control by using Visual Studio .NET 2003.

Note   You can provide design-time support in Visual Studio .NET 2003 only if you create your custom control by using the Visual C# development language. So, if you're developing in Visual Basic, you have to port your code to Visual C# to provide design-time support; otherwise, you can provide only run-time support for your controls. The inability to provide design-time support when using Visual Basic is due to the Visual Basic compiler that treats ambiguities of referenced types as errors, whereas the Visual C# compiler treats this same situations as warnings. The ambiguity is a complication that is met during the compilation process of the design-time assembly.

When creating a custom control in Visual Studio .NET 2003, you need to create a run-time version of the control, compiled against the .NET Compact Framework version 1.0. You also need to create a design-time version of the control, compiled against the full .NET Framework.

Creating the Run-Time Assembly

The first step in creating the run-time version of a custom control is to start Visual Studio .NET 2003 and to create a new Visual C# project by using the Smart Device Application template. In the scenario for this article, name your project Controls. Figure 1 shows the New Project dialog box.

Figure 1. New Project dialog box

After you click OK in the New Project dialog box, the Smart Device Application Wizard starts and contains options to choose the target platform and project type. Because you want to build a custom control targeted at the Pocket PC platform, select the Pocket PC platform as the target and Class Library as the project type, and then click OK to create the project.

The next step is to open Solution Explorer (by clicking View on the main menu, and then clicking Solution Explorer). Begin working top-down by setting project properties, adding the appropriate references, and updating the default class file name. It's important to work from the outside in, before you start writing code, so that all properties and references are set appropriately. This technique will alleviate possible problems if, when you write code, Microsoft IntelliSense is not showing a particular type because you forgot to add the reference. Plus, it's a good idea to take care of all the subtle details before you start handling large amounts of code.

Now that you've created the project, you need to set some project properties. You can display the property pages for the custom control by right-clicking the project (in this case, Controls in Solution Explorer), and then clicking Properties on the shortcut menu. Figure 2 shows the property pages dialog box for your project.

 

Click here for larger image

Figure 2. Project property pages. Click the thumbnail for a larger image.

On the General page of the Common Properties section, both Assembly Name and Default Namespace should be set to something meaningful; something that distinguishes your assembly and namespace from the others. In the current scenario, set the default namespace to "Contoso.Windows.Forms". You'll set the assembly name to the same name. Setting the default namespace and assembly to the same name is a recommended practice; however, you could set these names to anything that you want.

On the Build page of the Configuration Properties section, change the Configuration setting, at the upper left of the dialog box, to All Configurations. This action ensures that the modification you're about to make is still valid if, for example, you changed from the Debug to the Release configuration. Next, set XML Documentation File to the same name as the assembly name but with an .xml file name extension. In the current scenario, this action means that the XML documentation file property will be set to Contoso.Windows.Forms.xml. The XML documentation file will be built from the information contained within the special style of commenting that uses XML tags. For more information about the XML comment tags, refer to Recommended Tags for Documentation Comments.

An important concept to understand is that the XML documentation file needs to be named the same as the run-time assembly and located in the same directory so Visual Studio can load this file and extract the descriptive information about types and members (such as properties, methods, and events) that the run-time assembly contains. This descriptive information is displayed through ToolTips within the code editor to help the end developer understand the intention of a property or method, or even to present information about a particular parameter when the developer is typing a method call. Note that while it is strongly encouraged to have an XML documentation file to accompany your run-time assembly, it is not required.

Now that you've set your properties, you need to reference the required assemblies. References indicate the assemblies that contain the types you're going to use. Not only does Visual Studio use the information in these assemblies to populate the IntelliSense Help, the compiler uses information about what assemblies are being referenced to verify that the types and members that you use in your code are valid. The fact that the compiler does this work for you eliminates many potential run-time errors that would occur if you, for example, attempted to call a method that does not exist. This is one of the main reasons that you may hear the expression, "The compiler is your friend."

To add a reference, or multiple references, to a project from within Solution Explorer, right-click References under the appropriate project, and then click Add Reference on the shortcut menu. At this point, you'll see the Add Reference dialog box with two tabs: .NET and Projects. You can use the default tab displayed, the .NET tab, to select assemblies from a list, or you can click the Browse button and locate an assembly if it's not already in the list. Figure 3 shows the Add Reference dialog box.

Figure 3. Add Reference dialog box

To add an assembly (referred to as a component in the dialog box) to the list of selected assemblies that will be added as references, either double-click an assembly in the list or select the assembly and then click Select. After all of the desired assemblies have been added to the list of selected components, click OK to close the dialog box.

If you expand the References section in Solution Explorer, you'll notice that the assemblies that you added are now included as references along with the ones that were automatically added when the project was created. In the current scenario, and typically when creating a custom control, you'll need to add a reference to the System.Drawing.dll and System.Windows.Forms.dll assemblies. You can then use drawing elements, such as brushes and pens, for performing custom painting and also to inherit from the Control class, as required in the current scenario.

The last tasks to do, before writing the code that defines a custom control, are to modify some of the default code that has been generated and rename one of the generated files to something more appropriate. There are two source files in the project: AssemblyInfo.cs and Class1.cs. The AssemblyInfo.cs file contains attributes that you can use to, among other things, specify a version number and metadata to describe your custom control. If you open this file in the code editor by double-clicking the file name in Solution Explorer, you can set, or modify, the value of these attributes. In the current scenario, you're going to set the version number by changing the AssemblyVersionAttribute value from 1.0.* to 1.0.0.0, as shown in the following code.

Copy Code
[assembly: AssemblyVersion("1.0.0.0")]

You'll use the Class1.cs file to define your custom control. However, before you modify the generated code in this file, rename the file in Solution Explorer by right-clicking the Class1.cs file, clicking Rename on the shortcut menu, and then changing the name of the file to something that more properly describes the code that the file contains. In the current scenario, because this file will contain the definition for your BasicLabel custom control, use BasicLabel.cs as the new file name.

If the file is not already open, double-click the file name in Solution Explorer to open the file in the code editor. The last task to do before writing the custom control is to modify the generated code in the file that you just renamed. You'll start by adding the following directives to the top of the source file.

Copy Code
using System.Drawing;
using System.Windows.Forms;

Next, change the namespace to something more meaningful, which would probably be the default namespace that you set in the project properties. In the current scenario, change the namespace to "Contoso.Windows.Forms". Now, you'll change the class name to reflect the name of the control that you're going to build. In the scenario, name the class BasicLabel and also update the constructor (the initialization method with the same name as the class) to BasicLabel as well.

It's a good idea to explicitly specify a default constructor (which is parameterless) in code. If no constructor is present in a class definition, the Visual C# compiler adds a default constructor during compilation. However, if you specify a constructor that takes parameters, a default constructor is not automatically added. The absence of the default constructor can cause long-term problems if an updated version of your control explicitly specifies a constructor that requires parameters, because any existing code that depends on the default constructor will break—because the default constructor is not present. This is the primary reason why explicitly specifying a default constructor is encouraged. So rename the default constructor to match the class: BasicLabel. Because this is a custom control, you need to inherit from an existing control class. Inheriting from a control class indicates that your class is not just another class; rather, it is a control and can therefore be added to a container, such as a form, and display information to the end user. In the current scenario, you'll inherit your class from the System.Windows.Forms.Control class. After you make all of the changes to the generated code, the code should look similar to the following.

Copy Code
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace Contoso.Windows.Forms
{
/// <summary>
/// Summary description for Class1.
/// </summary>
public class BasicLabel : System.Windows.Forms.Control
{
public BasicLabel()
{
}
}
}

Something that you'll notice in the code is the default way that the class is commented. These are the XML comments that are used to build the XML documentation file that you specified in the project properties earlier in this article. In this situation, the BasicLabel class would display the description Summary description for Class1 in a ToolTip when the class is being used through the code editor. Of course, you'll want to change the description to something that has more meaning to a developer. In the current scenario, change the comment as follows.

Copy Code
/// <summary>
/// Represents a basic label used to display text.
/// </summary>

When you're above a type, or member, in the code editor, typing three forward slash marks (///) causes a <summary> tag to be generated automatically. If you're above a method, a <summary> tag, a <param> tag for each parameter that the method expects, and a <returns> tag if the method has a specified return type will be generated. Of course, if desired, you need to specify the descriptive information for each of these tags.

If you attempt to build the project at this point, you'll get a compiler warning stating that you're missing XML comments on your constructor. If the type, or member, is visible outside the assembly, or type, in which it's contained, the compiler generates a warning for each of these types, or members, that do not have some form of XML comment specified. The reason that the compiler generates this warning is that you've asked it to create an XML documentation file by specifying the file name in the project properties. If you did not specify an XML documentation file name, the compiler would not generate warnings about missing XML comments. So now, you need to specify a description for your constructor to eliminate this warning. Place the cursor above the constructor, type three forward slashes, and then specify an appropriate description between the <summary> tags that are generated.

At this point, the custom control should be defined with properties, method, events, and any other supporting types, such as enumerations or classes. The code that you specify is your choice and is based on the intention of the custom control. Some of the run-time code that will be used in the current scenario was discussed previously in this article, and the complete source code can be found in the download code sample.

Creating the Design-Time Assembly

Before examining the process of creating a design-time version of a custom control by using Visual Studio .NET 2003, you should understand why it's necessary to have two different assemblies: one for run time and one for design time.

A lot of the power of the design-time environment is realized through attributes, which provide metadata, and other special classes. To keep the .NET Compact Framework as small as possible, the decision was made to exclude these special classes because they are not necessary on a device or emulator. You need these classes only through the form designer that runs within Visual Studio, which, in turn, is on a computer that has the full .NET Framework installed. If you tried to write code that inherited from, or in any way used, these classes in your smart device project, the code would fail to compile. So at this point, you need to build a separate assembly, compiled against the full .NET Framework, to garner design-time support for a custom control. One of the tricks is to share the same source code between these two projects—the run-time project (.NET Compact Framework) and the design-time project (full .NET Framework) —so the code common to the control at run time and design time does not need to be written twice.

There are different ways to create a design-time version of a smart device custom control. Probably the most well-known process is to compile from the command line by using a batch file to store the required commands and arguments to the Visual C# compiler. However, there is a different way that doesn't require the command line or batch files.

Because the same source code is used to compile both the design time and run time versions, the design-time–specific code needs to be isolated. You isolate the design-time–specific code by using a conditional compilation constant. The two constants that you'll see most often are NETCFDESIGNTIME and DESIGN. This article will design-time version built through Visual Studio .NET 2003. Keep in mind that the name of the conditional compilation constant is entirely up to you. It can be any name that you find meaningful.

The common practice of using conditional compilation constants and the command line to create the design-time assembly can be problematic. When attempting to edit design-time–specific code from within the run-time project, a control developer sees that all of the design-time sections of code appear dimmed in the editor. These sections appear dimmed because the conditional compilation constant was not defined within the run-time project. In this case, the control developer makes changes to code within these dimmed sections, and then, from outside Visual Studio, runs the batch file to build the design-time version of the control. Because Visual Studio will not compile the dimmed code, you will know that the changes compile properly only by compiling on the command line. If errors occur, the control developer goes back to Visual Studio, finds the location of the reported errors, attempts to fix the problems, and then runs the batch file again. This process repeats until all of the errors are resolved.

The problems with this approach are obvious. Visual Studio has an incredibly powerful code editor, yet in the preceding situation, you essentially resort to using Microsoft Notepad and the command line to edit and build your design-time version. Not only are you sacrificing the syntax highlighting that you're used to, but even if you defined that conditional compilation constant within your smart device project, you're still not going to get the IntelliSense help that you want because Visual Studio is pulling that information from the referenced assemblies, which are .NET Compact Framework assemblies. These problems don't seem too serious if you're just putting attributes on properties. However, after you start writing custom design-time types (such as ControlDesigner, UITypeEditor, or TypeConverter), this problem can become tiresome.

At this point in time, you'll create the design-time version of the custom control in a way that is a little more accommodating to the average developer—by using Visual Studio. Because the design-time version of a custom control will be used from within the form designer, you'll create a full .NET Framework project to build your design-time version. The first step is to start Visual Studio .NET 2003 and create a new Visual C# project by using the Empty Project template. In the current scenario, you'll name your project Controls.Design.

Open Solution Explorer and begin working top-down, similar to how you set up the run-time project, by setting project properties and adding references. First, you'll focus on the project properties. Open the property pages for the new project and ensure that the General page of the Common Properties section is visible. Set the assembly name to something meaningful. In the current scenario, use "Contoso.Windows.Forms.Design". Also, set the default namespace to something more appropriate. In the current scenario, use "Contoso.Windows.Forms" as the default namespace. Change the Output Type property to Class Library. Click Apply.

On the Build page of the Configuration Properties section, change the Configuration setting to All Configurations so the changes you're about to make are valid for all configuration types. Set Conditional Compilation Constants to DESIGN_VS2003 to define this constant throughout the project. Then, set the Suppress Specific Warnings property to 1595. By indicating that you want to suppress warnings referenced by the number 1595, you're stating that warnings due to types defined in multiple places can be ignored. This setting is necessary due to the assemblies that you must reference for your design-time version to be added to the toolbox and behave as expected within a smart device project.

Figure 4 shows the property pages dialog box for your project.

 

Click here for larger image

Figure 4. Project property pages. Click the thumbnail for a larger image.

One very important step in this process is not only the inclusion of special assemblies but also the order in which the assemblies are referenced. Open the Add Reference dialog box. Click Browse, and then browse to the following directory, where Visual Studio .NET 2003 represents the installation directory for Visual Studio .NET 2003:

Visual Studio .NET 2003/CompactFrameworkSDK/v1.0.5000/Windows CE/Designer.

Select the System.CF.Design.dll, System.CF.Drawing.dll, and System.CF.Windows.Forms.dll assemblies by holding down the CTRL key while clicking them. Click Open to add the assemblies to the list of selected components. Next, add the System.dll and System.Windows.Forms.dll assemblies from the list on the .NET tab to the list of selected components. After you add all of the desired assemblies to the list of selected components, click OK to close the dialog box.

When the project is being compiled, the references will be resolved in the order in which they were added. The references are not set in the order that they appear in the References section of Solution Explorer because they are merely displayed in alphabetical order for convenience. This is critical to understand because the System.CF.* assemblies need to be referenced first.

As an example, the System.CF.Windows.Forms.dll and System.Windows.Forms.dll assemblies both contain a definition for the System.Windows.Forms.Control class. However, you need to ensure that your control inherits from the version that is defined in System.CF.Windows.Forms.dll because this definition is adorned with attributes that allow a component to be available in the Toolbox within a smart device project. This is the reason that you set up the project properties to suppress warnings related to referenced assemblies that define the same type. The compiler will use the type definition from the first assembly that it finds when there is a conflict.

Now that you've prepared the project to create an assembly that can be used as a design-time custom control, you need to link to the source files that are used to define the custom control so you can add design-time–specific code. In Solution Explorer, right-click the project (in the current scenario, Controls.Design), point to Add on the shortcut menu, and then click Add Existing Item. In the Add Existing Item dialog box, browse to the location of the run-time project and select all of the required source files. In the current scenario, the required source files are AssemblyInfo.cs and BasicLabel.cs. Instead of clicking Open, which would copy the source files into the design-time project directory, click the arrow on the right side of the Open button, and then click Link File. This action will not copy the source files into the design-time project directory; rather, it will link directly to the source files in the run-time project directory.

You now have two different projects, in two different solutions, which share the same source files. If a change is made in the run-time project, the change will be visible in the source of the design-time project. If you have both solutions open at the same time, in two different instances of Visual Studio .NET 2003, remember to save your work before moving from one to the other. If a saved change is detected and the file is open, Visual Studio will prompt you to reload the file, and the changes will become visible in the other solution.

You can now start adding design-time–specific code. Attributes that need to be placed on types or members that are shared between the run-time and design-time projects need to be encapsulated in design-time-specific code sections, as shown in the following code.

Copy Code
#if DESIGN_VS2003
[CategoryAttribute("Behavior")]
[DefaultValueAttribute(false)]
[DescriptionAttribute("Indicates whether the control is automatically
resized to display its entire contents.")]
#endif
public bool AutoSize
{
get
{
...
}
set
{
...
}
}

The preceding code shows three attributes, CategoryAttribute, DefaultValueAttribute, and DescriptionAttribute, encapsulated in a design-time–specific code section. When the run-time version of the custom control is compiled, these attributes are not processed with the rest of the code because the DESIGN_VS2003 conditional compilation constant is not defined in that project. However, when you compile the design-time version, these attributes are compiled into the assembly because the DESIGN_VS2003 conditional compilation constant is defined in the design-time project. Essentially, you're turning blocks of code on and off by using this constant. The attributes provide metadata, or descriptive information, that can be used through the form designer.

The CategoryAttribute, DefaultValueAttribute, and DescriptionAttribute attributes are extremely common to have on every property in a custom control. Each of these attributes provides the end developer with some sort of visual information in the Properties window. CategoryAttribute allows a property or event to be categorized within the Properties window. This attribute makes discovering properties and events easier by grouping them in categories instead of alphabetically. DefaultValueAttribute specifies the initial value that the control developer has assigned to the property. The control developer is still responsible for ensuring that the actual default value of the property is set to the same value as this attribute when an instance of the control is created. When a property value appears in bold in the Properties pane in Visual Studio, the value is different from the default. A bold property value also indicates that the value has been serialized into code and will be set into the property when the application first starts. The more properties that need to be set at startup, the longer the application will take to start. Setting default values that will satisfy the majority of end developers in conjunction with DefaultValueAttribute can actually minimize application startup time. Setting DefaultValueAttribute also enables the end developer to right-click the property and reset its value to the default. DescriptionAttribute specifies a description of the property or event that will appear in the Description pane, located at the bottom of the Properties pane, when the property or event is selected.

The CategoryAttribute, DefaultValueAttribute, and DescriptionAttribute attributes play an important role at design time. The Properties window provides an IntelliSense-like experience when in the visual design of an application. These attributes enable a control developer to further enhance that experience for an end developer.

If there are types that need to be defined to support the custom control in the form designer, such as custom ControlDesigner, UITypeEditor, or TypeConverter, these types should be defined in new code files and placed only in the design-time project. Any design-time–specific types will be associated with properties within the custom control, or associated with the custom control itself, through attributes in design-time–specific code sections.

A custom ControlDesigner type enables the control developer to specify how the appearance or behavior should be modified for a custom control when it's being designed through the form designer. In the current scenario, the custom control contains a property named AutoSize. This property will automatically resize the control, based on the displayed text, if it's set to true. If the end developer has set the AutoSize property to true, he or she has indicated that the control should update its own size without intervention. Thus, if this property is set to true, there is no need for resize handles to be shown on the control when the control is displayed within the form designer because the control should resize itself based on changes in the text, font, and so on. The code for this custom ControlDesigner type is as follows.

Copy Code
public class BasicLabelDesigner : 
System.Windows.Forms.Design.ControlDesigner
{
public override System.Windows.Forms.Design.SelectionRules
SelectionRules
{
get
{
// Get the default selection rules from the base class.
SelectionRules rules = base.SelectionRules;

// If the AutoSize property is set to true, modify the
// selection rules to prevent the control from being resized
// on the design surface. However, if the AutoSize property
// is set to false, modify the selection rules to allow the
// control to be resized on the design surface.
if (((BasicLabel)this.Control).AutoSize)
{
rules &= ~SelectionRules.AllSizeable;
}
else
{
rules |= SelectionRules.AllSizeable;
}

// Return the selection rules.
return rules;
}
}
}

When developing with Visual Studio .NET 2003, the base class for a custom designer type is System.Windows.Forms.Design.ControlDesigner. To create a custom designer type, you inherit from this class and override any properties and methods necessary to provide the appropriate design-time appearance or behavior. In the current scenario, you need to update the state of the resize handles displayed for your custom control based on the value of the AutoSize property. To accomplish this task, you need to override the SelectionRules property in the custom ControlDesigner type. This property returns a value that indicates how the control can be moved and resized through the form designer. First, you need to get the list of valid rules from the base implementation. Second, you need to ensure that the control can or cannot be resized on the design surface based on the value of the AutoSize property.

You can associate this custom ControlDesigner type with the control by using the DesignerAttribute attribute, as shown in the following code.

Copy Code
#if DESIGN_VS2003
[DesignerAttribute(typeof(Contoso.Windows.Forms.Design.
BasicLabelDesigner))]
#endif
public class BasicLabel : System.Windows.Forms.Control
{
...
}

There is one more attribute to discuss in relation to the design-time version of a custom control. This is an important attribute. The attribute is named RuntimeAssemblyAttribute and is in the System.CF.Design.dll assembly that you added as a reference. This is an assembly-level attribute. In other words, this attribute should be specified outside the scope of all types and namespaces. One good place for this attribute is in the AssemblyInfo.cs file with the other assembly-level attributes. This attribute can be used as follows.

Copy Code
[assembly: System.CF.Design.RuntimeAssemblyAttribute(
"Contoso.Windows.Forms,
Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=null")
]

The only parameter that RuntimeAssemblyAttribute expects is a string representing the full name of the run-time assembly. In the current scenario, this attribute points to the Contoso.Windows.Forms.dll assembly, which is the run-time assembly for your custom control that has been compiled against the .NET Compact Framework. The purpose of this attribute is to allow Visual Studio to locate and automatically add a reference, in the smart device project, to the associated run-time assembly when the control is dragged to the form from the Toolbox.

This article mentions only a few of the attributes available to control developers. The download code sample shows more useful attributes, including how to specify the event for which an event handler is created when the control is double-clicked in the form designer.

Testing the Control

The first step in testing a custom control is to start Visual Studio .NET 2003 and create a new Visual C# project by using the Smart Device Application template. In the current scenario, name your project Controls.Test. In the Smart Device Application Wizard, select the Pocket PC platform as the target and Windows Application as the project type, and then click OK to create the project.

When actually testing a custom control that has a design-time aspect, you need to ensure that the control can be properly added to the Toolbox. However, before you add the control to the Toolbox, you must move the run-time assembly, the XML documentation file, and the design-time assembly to special directories. The run-time assembly and the XML documentation file need to go to the following directory, where Visual Studio .NET 2003 represents the installation directory for Visual Studio .NET 2003:

Visual Studio .NET 2003/CompactFrameworkSDK/v1.0.5000/Windows CE

In the current scenario, the run-time assembly is named Contoso.Windows.Forms.dll; the XML documentation file is named Contoso.Windows.Forms.xml; and the design-time assembly is named Contoso.Windows.Forms.Design.dll. The design-time assembly needs to go to the following directory:

Visual Studio .NET 2003/CompactFrameworkSDK/v1.0.5000/Windows CE/Designer

To add the control to the Toolbox, use the form designer to open the main form for the project that you just created. Next, open the Toolbox by clicking View, and then clicking Toolbox. When the Toolbox appears, right-click in it, and then click Add/Remove Items on the shortcut menu. The Customize Toolbox dialog box appears. On the .NET Framework Components tab, click Browse. Browse to the following directory, which contains the design-time assembly:

Visual Studio .NET 2003/CompactFrameworkSDK/v1.0.5000/Windows CE/Designer

Next, select the design-time assembly in the list, and then click Open. The control should now appear in the Customize Toolbox dialog box, as shown in Figure 5.

Figure 5. Customize Toolbox dialog box

In the Customize Toolbox dialog box, click OK. The custom control should appear in the Toolbox. It's important to reinforce that when adding items to the Toolbox, the design-time assembly should be added and not the run-time assembly. Any valid controls found within the design-time assembly will be added to the Toolbox. When a custom control is dragged to a form from the Toolbox, the associated run-time assembly should automatically be added to the References section of the project that contains the form. Of course, the connection is made through RuntimeAssemblyAttribute, as discussed earlier.

Now, you can drag an instance of the custom control to the form and verify that the run-time assembly was added to the list of references for the project. After you verify that the association between your run-time assembly and design-time assembly is being made successfully, you should start going through a small checklist for each custom property. First, select a custom property (in the current scenario, the AutoSize or BorderStyle property) in the Properties window and verify that the correct description appears in the Description pane. If not, you have forgotten DescriptionAttribute. Next, change the property ordering from Alphabetic to Categorized and verify that the property appears in the correct category. If the property appears in the Misc category, you have forgotten CategoryAttribute, unless this is the intended category. The next step is to verify that the initial value displayed in the Properties window is not bold. If the value is displayed in bold, you may have either forgotten DefaultValueAttribute or forgotten to set the initial value of the property to be the same value as DefaultValueAttribute.

Next, you need to change the value of the property to something other than the default. The property value should now appear in bold. Switch to the code view and look at the code generated in the InitializeComponent method. This method is where the form designer serializes changes made at design time. You need to verify that the property you just changed is in fact being set to the proper value, on the proper instance of the control, within the InitializeComponent method. If the property is not there, you may need to specify DesignerSerializationVisibilityAttribute for the property. (For more information about this attribute, see the Microsoft MSDN Help documentation.) If the property is serialized as expected, go back to the form designer, right-click the property, and then click Reset on the shortcut menu. The property value should have been reset back to the default value and should not appear bold. Switch back to the code view to make sure that after you reset the property it is no longer being set in the InitializeComponent method.

You've now completed a fairly good checklist from the form designer perspective. You can repeat this process for each custom property and also complete similar checks for custom events.

Events, in a Visual C# project, can be accessed through the Properties pane. Just like properties, each event can have a description and a category. However, DefaultValueAttribute is not used with events; from a designer's perspective, it is in either one of two states—either the event is tied to an event handler or not. By double-clicking the event name in the Properties pane, you create an event handler. Right-clicking the event name and clicking Reset on the shortcut menu removes the association between the event handler and the event.

In addition, you'll want to ensure that any custom ControlDesigner, UITypeEditor, and TypeConverter types are working as expected.

You should now turn your attention to the code editor. For your control, you should verify that the appropriate members are being displayed through IntelliSense and that the descriptive information from the XML documentation file is being shown properly for each member.

Now that you know that there is strong design-time experience associated with your control, similar to the .NET Compact Framework controls, the last task in the test phase is to construct a user interface that you can use to test the custom properties and events at run time. This is a straightforward process that's unique to each custom control. The only real requirement is that each custom member is tested to ensure that the control appears and behaves on the device or emulator the same as it does through the form designer.

At this point, it may look like you've met the objective of creating a custom control that displays text to the end user and has a strong design-time experience for the end developer. However, you've covered only the requirement of version 1.0 of your custom control (compiled against the .NET Compact Framework version 1.0), which can be used at design time with Visual Studio .NET 2003. Next, you'll learn about how you can build the same control by using Visual Studio 2005 and the .NET Compact Framework version 2.0.

使用Visual Studio 2005开发该自定义控件

As you saw previously in this article, creating a custom control by using Visual Studio .NET 2003 is challenging. To enable the end developer to interact with a custom control at design time, the creator of the control is responsible for creating a special design-time build of the control in addition to the run-time build. This means that you have two assemblies that must be explicitly built—one for run time and one for design time. This effort is mandatory.

By using Visual Studio 2005, you can build custom controls in a much easier, and much cleaner, manner. First, if you aren't concerned with associating design-time metadata (such as category and description), you can simply build the control and add it to the Toolbox, and the control will just work. However, if you want to match the strong design-time experience that a developer expects, you still need to have at least two assemblies. Don't get scared. In the simplest form, you're now responsible for creating a custom control, which can be thought of as the run-time assembly, and for providing Visual Studio 2005 with the proper metadata information. This metadata information is stored in a special XML file by means of tags named the same as the common attributes that you used in the past. Visual Studio 2005 will automatically build an assembly by using this special XML file, which contains design-time information that the form designer uses.

As this article pointed out before it walked through the process of building a custom control using Visual Studio .NET 2003, it is possible to provide design-time support in Visual Studio .NET 2003 only if you create your custom control by using the Visual C# language. Well, that's changed too. Visual Studio 2005 enables developers to use the Visual Basic language to create custom controls that offer the same strong design-time experience that control developers provide by using Visual C#. The playing field has been leveled.

Another benefit of Visual Studio 2005 is support for the .NET Compact Framework versions 1.0 and 2.0. If you focus on Pocket PC, you'll notice that there are templates for both versions 1.0 and 2.0 project types. The good news is that even if you build a custom control that targets the .NET Compact Framework 1.0, you can still use the new design-time paradigm from within Visual Studio 2005. The challenge, as outlined in the current scenario, is to provide support for a custom control across Visual Studio .NET 2003 and Visual Studio 2005 because an end developer, as a customer, may be targeting the .NET Compact Framework 1.0 by using either version of Visual Studio. This article will address this challenge later when it discusses the topic of migration.

Next, this article describes the steps that are required to build a custom control by using Visual Studio 2005. You'll compile against the .NET Compact Framework 2.0. After creating the control, you'll instruct Visual Studio 2005 to build an assembly that contains rich metadata to describe your control to the form designer. You'll then expose any supporting types (ControlDesigner, UITypeEditor, and TypeConverter) that a control may require at design time.

Creating the Run-Time Assembly

Essentially, building a custom control, from a run-time perspective, hasn't changed between Visual Studio .NET 2003 and Visual Studio 2005. You still create a project, targeted at the desired version of the .NET Compact Framework, and define properties, methods, and events to yield a necessary appearance and behavior. However, there's a slight terminology shift. In Visual Studio 2005, what was previously referred to as the run-time assembly (because in Visual Studio .NET 2003, you have a run-time assembly and a design-time assembly), you can now simply call a custom control, just like a control developer targeting the full .NET Framework. This single .NET Compact Framework assembly now serves as both the run-time assembly, which is deployed to the device or emulator, and as the designer assembly, which is added to the Visual Studio 2005 Toolbox. With Visual Studio .NET 2003, the assembly that was used in the form designer was the version that you explicitly compiled as a separate design-time assembly. This is not the case in Visual Studio 2005. So you no longer need to make a distinction between a run-time assembly and a design-time assembly. So, in Visual Studio 2005, a typical control experience is still delivered in two pieces—the assembly that contains the control implementation and the assembly that contains rich design-time metadata.

The first step in creating a custom control is to start Visual Studio 2005. On the File menu in Visual Studio 2005, point to New, and then click Project to display the New Project dialog box. Because you're going to target the Pocket PC platform and the .NET Compact Framework 2.0 by using Visual C#, you need to move through the Project types tree until you find the Pocket PC 2003 project type under the Smart Device node for the proper language. Next, select the Class Library template and give the control project an appropriate name. In the current scenario, name your project Controls. Figure 6 shows the New Project dialog box.

 

Click here for larger image

Figure 6. New Project dialog box. Click the thumbnail for a larger image.

The reason that you're not going to use the Control Library template is that it's targeted toward developers that are building composite controls. In other words, this template is for developers who are building controls, inherited from the UserControl class, that contain other Windows Forms controls. The UserControl class is supported in the .NET Compact Framework 2.0. However, in the current scenario, because you do not want the custom control to contain other controls, you inherit from the Control class, and using the Class Library template (as you did by means of Visual Studio .NET 2003) happens to be an easier approach.

Now that you've created the project, you can begin working top-down, just as you did in Visual Studio .NET 2003, setting properties, adding references, and updating the generated class file. If Solution Explorer is not already open, open it, and then double-click the word Properties under the appropriate project. In the current scenario, this is the Controls project. Notice how the properties for the control open—not in the form of a dialog box, but as a tab in the target window—just like any opened file. Moreover, the project property layout has changed to a more updated vertical tab appearance. Of course, you could have opened the project properties by right-clicking the project name and then clicking Properties on the shortcut menu, just as you did in Visual Studio .NET 2003. However, double-clicking the Properties node provides a shortcut to the project properties.

On the Application tab of the project properties, the assembly name and default namespace should be set appropriately. In the current scenario, type Contoso.Windows.Forms as both the assembly name and the default namespace. Still on the Application tab, click Assembly Information to display a dialog box that allows values such as assembly title, company, and version to be specified. In the current scenario, you need to ensure that the values listed in the dialog box are set to reflect the purpose of the assembly. On the Build tab, change the Configuration setting to All Configurations. Next, check the XML documentation file option and specify the same name that was used for the assembly, with an .xml file name extension. In the current scenario, the XML documentation file name is set to Contoso.Windows.Forms.xml. You've now finished modifying the properties, so you can save the changes the same way that you'd save changes to a code file, and then close the project properties.

The next step is to add any required references that do not already exist in the project. In the current scenario, and in most situations when you're creating a custom control, you need to add a reference to the System.Drawing.dll and System.Windows.Forms.dll assemblies. To add references, in Solution Explorer, right-click the References section for the appropriate project, and then click Add Reference on the shortcut menu. In the Add Reference dialog box, on the .NET tab, select all of the required assemblies by holding down the CTRL key while clicking them. Click OK to close the dialog box and add the selected assemblies to the References section.

You now need to change the name of the default code file that is generated as part of the project. Rename the Class1.cs file to something that better reflects the custom control. In the current scenario, name the file BasicLabel.cs because it will contain the definition for the BasicLabel custom control.

Next, you'll modify the default code, for the file that you just renamed, in the same way that you did by using Visual Studio .NET 2003 previously in this article. You can start by adding some using directives (as shown in the following code), so you can use the types in the assemblies that you referenced earlier.

Copy Code
using System.Drawing;
using System.Windows.Forms;

At this point, you need to change the namespace and class name to names that are appropriate for your custom control. In the current scenario, change the namespace to "Contoso.Windows.Forms", which matches the default namespace that you set in the project properties. Set the class name to BasicLabel, because this is the required name of the custom control.

The generated code does not include a default constructor, so you need to add that to the class definition. You need to inherit from an existing control class, so you can visually display information. In the current scenario, because you've chosen to handle the appearance yourself, the System.Windows.Forms.Control class will provide the base class to your custom control. The code that you have so far is as follows.

Copy Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace Contoso.Windows.Forms
{
public class BasicLabel : System.Windows.Forms.Control
{
public BasicLabel()
{
}
}
}

Because you have requested the creation of an XML documentation file when the project is built, if you build the project at this point, you'll see two compiler warnings related to missing XML comments. The BasicLabel class and the default constructor both need to have descriptions within the XML comment tags that were discussed previously in this article. After you specify these comments, as you did when you created the custom control by using Visual Studio .NET 2003, the warnings will disappear.

As with the project that you created by using Visual Studio .NET 2003, the code from this point forward would be constructed by means of properties, method, events, and other supporting types to produce the desired appearance and behavior for the custom control. However, because the focus of this article is on the design-time experience, this article will forgo the complete explanation of the control definition. The download code sample contains the complete, and commented, source code if you would like to see more code.

With Visual Studio 2005, if you compile your custom control right now and add it to the Toolbox, you will then be able to successfully add the control to a form, just as you would add any other control. This is a big step forward in relation to where you were at this point when using Visual Studio .NET 2003. However, custom properties and events for your control will not display descriptions when selected in the Properties pane, and these custom members will also be placed in the default Misc category. This accomplishment definitely does not meet the requirement for the current scenario because you're lacking a strong design-time experience for the end developer. To correct this problem, you'll need to supply some metadata within your project.

Creating the Metadata Assembly

As mentioned previously, the design-time experience in Visual Studio 2005 works by using the combination of a custom control assembly and metadata assembly. The term metadata is used to imply descriptive information. So the purpose of the metadata assembly is to provide descriptive information at design time about the custom control in the form of the design-time attributes. This information specifies, among other things, descriptions and categories for custom properties and events.

In Visual Studio .NET 2003, descriptive information is compiled into a design-time assembly through the use of attributes placed in the code. In Visual Studio 2005, the descriptive information is compiled into a metadata assembly by means of a special XML file with an .xmta extension. This XML file can be thought of as a container to hold XML–based metadata attributes. In fact, the .xmta extension is derived from the X in XML–based, the M and second T in metadata, and the A in attributes. Rather than specifying the attributes in code, you're essentially stating what attributes should be used, and on what types and members, through this XML file. The information in this file will be processed during the build process to automatically create an assembly. This assembly is the metadata assembly. Remember that mobile devices have very limited storage capacity relative to desktop computers; therefore; the tools that create the runtime assemblies that target mobile devices need to keep the size of the assembly as small as possible so they don't unnecessarily consume the limited storage resources of the mobile devices. By placing the design-time attributes in a metadata assembly, Visual Studio 2005 is able to provide the user with a rich design-time experience without unnecessarily increasing the size of the run-time assembly with this design-time information.

There are two ways to get an .xmta file into a project. The first way is to add a new file item to the custom control project from a template. The second way is to use a new form designer in Visual Studio 2005 named Class Designer. This article will first explore the option of adding the file manually, and later it will discuss how to use Class Designer to add this file.

You can add a new item by right-clicking the project in Solution Explorer, pointing to Add on the shortcut menu, and then clicking New Item. From the list of available templates, you can select Design-Time Attribute File and give it an appropriate name. Click Add to add the new item to the project. The file contains the following text.

Copy Code
<?xml version="1.0" encoding="utf-16"?>
<Classes
xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/
SmartDevices/XMTA.xsd">
</Classes>
Note   When the .xmta file is added to the Visual Studio 2005 project, the Build Action property for the file is set to None. Although it may seem counterintuitive, None is the correct value for this property. The .xmta file does not require a specific build action because the metadata assembly is automatically generated from the contents of the .xmta file as part of the Visual Studio 2005 project build process.

The .xmta file is just an ordinary XML file. By default, only the Classes element is provided. Within this element, Class elements can be added to represent types, defined in the custom control assembly, to which attributes should be applied. In the current scenario, you may want to specify attributes for your custom control, so you need to add a Class element and set the Name attribute to the name of your control as follows.

Copy Code
<Classes xmlns="...">
<Class Name="Contoso.Windows.Forms.BasicLabel">
</Class>
</Classes>

The preceding code states that any allowable elements that exist within the Class element should be treated as attributes to be applied to the class or any method, property, or event within the class. Now, you can add an attribute that should be applied to the control, as follows.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
<DefaultEvent>Click</DefaultEvent>
</Class>

The preceding XML code indicates that the class Contoso.Windows.Forms.BasicLabel, which represents your custom control, should have an attribute named DefaultEvent, with the value Click, applied to it at design time. The DefaultEvent attribute specifies the event for which an event handler is automatically created when the end developer double-clicks the control on the design surface. In this situation, you're stating that an event handler for the Click event should be generated if the user double-clicks the control. The following code specifies an attribute for a property in your control.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
...
<Property Name="AutoSize">
<Category>Behavior</Category>
</Property>
...
</Class>

The preceding XML code indicates that the AutoSize property, which is a member of the Contoso.Windows.Forms.BasicLabel class, should have the Category attribute, with the value Behavior, applied to it at design time. The result is that the AutoSize property should appear under the Behavior category in the Properties window at design time. Every attribute that you would have placed above the appropriate property by using Visual Studio .NET 2003 should now be specified within a Property element by means of an .xmta file and Visual Studio 2005.

There are a couple of useful features of the XML editor when you're editing the .xmta file. For example, notice that you actually get IntelliSense technology when you begin typing an XML element. The list that appears shows the supported attributes, and, when inside an attribute, can help determine the required information when you want to specify the attribute value. For example, when you want to specify the DefaultValue attribute, the expected information are the Type and Value elements, as shown in the following code.

Copy Code
<Property Name="AutoSize">
...
<DefaultValue>
<Type>
System.Boolean, mscorlib, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</Type>
<Value>False</Value>
</DefaultValue>
...
</Property>

The Type element represents the assembly-qualified name of the type that is specified in the Value element. So, in the preceding XML, the Type element indicates that the type used for the default value is System.Boolean, or bool in Visual C#, located in version 2.0.0.0 of the mscorlib.dll assembly. The Value element, which becomes the default value, is False, which can be translated into a meaningful System.Boolean type.

When you create the .xmta file, the different colored wavy underlines that appear when you type something incorrectly, or when you try to specify an attribute that is not supported, represent invalid information that needs your attention. In other words, these marks provide validation.

Both IntelliSense technology and validation are accomplished by means of a schema. You can actually view this schema to understand how the .xmta file is being validated. You can also view the schema to see what attributes are supported and what information is required when these attributes are specified. The schema is located in the following file, where Visual Studio 2005 represents the installation directory for Visual Studio 2005:

Visual Studio 2005/Xml/Schemas/xmta.xsd

Note   Visual Studio 2005 uses the xmta.xsd schema file to provide the IntelliSense feature of the XML editor. In the currently available version of Visual Studio 2005, there is a bug in the interaction between IntelliSense and the xmta.xsd schema file that may cause IntelliSense to inaccurately display attribute information in some cases. This problem does not commonly occur, but should you encounter it, you can ignore IntelliSense and manually type the correct attributes. You can find the correct .xmta file element and attribute information by viewing the xmta.xsd file directly. The IntelliSense problem is expected to be resolved with the release of Visual Studio 2005 Service Pack 1.

Now that you've seen how you can add and modify the .xmta file directly through the XML editor, you'll learn how you can use the new Class Designer feature instead.

To manipulate types within the project, in addition to members within those types, in a more visual manner, you can display a class diagram for your project. In Solution Explorer, right-click the project that contains the custom control, and then click View Class Diagram in the shortcut menu. Notice that a new file has been added to the project. This file corresponds to the class diagram that you just generated. A visual representation of all the types in the project appears so that types and members can be added, modified, or removed. Figure 7 shows a simplistic version of the example custom control as an expanded class diagram item in Class Designer.

Figure 7. Class diagram item

To add an attribute to the custom control, or to a member of the control, first select the class, or member, on the class diagram item. Next, locate and select the Custom Attributes property in the Properties window. Click the ellipsis (...) button that is associated with the Custom Attributes property value. You can now add attributes by using the Custom Attributes for Name dialog box. If you add attributes, and then click OK, an .xmta file is created and added to the project. The attributes would normally be placed above the type or member in the code file; however, because this is a smart device project, these attributes are redirected to the .xmta file and translated into XML. Figure 8 shows the Custom Attributes dialog box for the AutoSize property.

Figure 8. Dialog box for adding custom attributes

Note   For the Custom Attributes editor to persist the entered attributes into the .xmta file, the attributes must have the proper syntax including all quotes, parenthesis, and so on. Should an attribute entered into the Custom Attributes editor not appear in the .xmta file, a syntax error is the most likely cause. There have been a few situations identified where the attribute may not persist to the .xmta file when entered correctly. This is not a frequently occurring situation but it has been observed to happen. If you experience this problem, open the .xmta file with the XML Editor and then enter the attribute directly into the .xmta file. The problem of properly entered attributes not persisting to the .xmta file is expected to be fixed in Visual Studio 2005 Service Pack 1.

Before you move on and learn about the way the .xmta file is formed into a metadata assembly, you should become familiar with a few of the new attributes that exist for developers of smart device custom controls. These attributes, just like the others, are placed in the .xmta file.

The first new, and incredibly noteworthy, attribute is DesktopCompatibleAttribute. This attribute can mean the difference between a custom control that's rendered properly in the form designer and a control that's rendered with a generic appearance, displaying the fully qualified name of the control. As the name implies, this attribute is used to mark a class as compatible with the desktop computer. In other words, this is a class-level attribute that, when applied to a custom control, indicates that the control is safe to render within the form designer.

So why does DesktopCompatibleAttribute exist? What makes a control unsafe to add into the design-time environment? As is the case with some custom controls, device-specific features may be referenced, such as the InputPanel component in the Microsoft.WindowsCE.Forms.dll assembly. In addition, the control may contain platform invoke declarations to Win32 API functions from Coredll.dll, which is an essential dynamic link library of the Windows CE operating system but does not exist on the desktop computer. In these types of situations, there is no guarantee that the custom control can be instantiated and displayed within the form designer without throwing exceptions that may degrade the design-time experience.

Despite its significance, DesktopCompatibleAttribute shouldn't just be used on every custom control that you create just so that your control can be rendered at design time. Rather, the idea is to remind the creator of a custom control to examine all areas of the control that may be accessed through the form designer to ensure that any device-specific functionality will not be triggered at design time.

Before you specify this attribute in the .xmta file, be aware that you have two options for ensuring that device-specific functionality will not negatively affect the design-time experience. The first option is to isolate device-specific code in a try…catch block, as shown in the following code.

Copy Code
[DllImport("Coredll")]
private static extern IntPtr GetCapture();
...
try
{
IntPtr hWnd = GetCapture();
}
catch (DllNotFoundException)
{
// Handle this situation at design time.
}

The preceding code states that if Coredll.dll cannot be found when an attempt is made to call the Win32 API function GetCapture, catch the exception that will be thrown at design time so that the end developer can't see it. Although this is an optional way to handle a failure at design time, the better way is to not have the exception thrown in the first place. To avoid the exception being thrown, check the static OSVersion property of the Environment class to determine the underlying operating system. This solution is a more robust way of handling situations in which you know that code would fail if executed on an operating system other than Windows CE. In these types of situations, you can take steps to provide an alternative, and equally applicable, action, as shown in the following code.

Copy Code
[DllImport("Coredll", EntryPoint="GetCapture")]
private static extern IntPtr GetCaptureWinCE();

[DllImport("User32", EntryPoint="GetCapture")]
private static extern IntPtr GetCaptureWin();

...

IntPtr hWnd;

if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
hWnd = GetCaptureWinCE();
}
else
{
hWnd = GetCaptureWin();
}
Note   The .NET Compact Framework 2.0 has support for Control.Site.DesignMode, which was not available in the .NET Compact Framework 1.0. Any code in your custom control that is intended for use only in design-mode should be contained in an if-block that checks the value of this.Site.DesignMode (use this because custom controls always inherit from the Control base class); a value of true indicates that the assembly is currently running within the designer. The use of Environment.OSVersion.Platform, as the previous code shows, is most appropriate for scenarios where an assembly is to be shared between a Windows Mobile device and a desktop computer at runtime.

With the preceding code, you're referencing the proper dynamic link library, depending on the underlying operating system, when using platform invoke to call the GetCapture Win32 API function. Earlier, this article discussed the Win32 API wrapper class that is part of the source code for the custom control in the current scenario. One of the primary purposes of this wrapper class is to allow Win32 API functions to be called at both run time and design time. The preceding code is how this type of logic is built.

When you're sure that you've made your custom control desktop compatible, you can add DesktopCompatibleAttribute to the .xmta file as follows.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
<DesktopCompatible>true</DesktopCompatible>
...
</Class>

The preceding XML states that the Contoso.Windows.Forms.BasicLabel custom control is compatible with the design-time environment. Without DesktopCompatibleAttribute, the custom control in your scenario would be rendered with a generic appearance, displaying only the fully qualified name, as shown in Figure 9.

Figure 9. Generic appearance

Another new attribute for developers of smart device custom controls is ApplyDeviceDefaultsAttribute. This class-level attribute indicates that the properties for a control, or one particular property if a single property is specified, should have the default value assigned to it when the control is instantiated through the form designer. As an example, look at the following xmta file excerpt.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
...
<ApplyDeviceDefaults>
<PropertyName>Size</PropertyName>
<ApplyDefaults>false</ApplyDefaults>
</ApplyDeviceDefaults>
...
</Class>

The preceding XML states that the Size property of the Contoso.Windows.Forms.BasicLabel custom control should not have its default value assigned to the control after it has been instantiated through the form designer. The default value is pulled from the value of DefaultValueAttribute for each property. This is a process that is separate from how DefaultValueAttribute is used in serializing and resetting a value through the Properties pane.

After the developer drags a control to a form from the Toolbox at design time, because ApplyDeviceDefaultsAttribute has been set to false for the Size property, anything that has been assigned to this property (for example, in the constructor of the control) will be reflected in the size of the control on the design surface. However, if the developer adjusted the .xmta file to specify a true value for the ApplyDeviceDefaultsAttribute, whatever is specified for the Size properties DefaultValueAttribute is set into the Size property after the control instance has been created—even if a different value has been assigned through the constructor of the control. This can catch you off guard if you assume because you assigned a value to a property in the constructor it will be the value shown in the Properties pane when a control instance is created. For anyone that has created a smart device custom control in Visual Studio 2005 and wondered why the control has a size of 200 x 200—even though you have specifically set the Size property to a different value through the controls constructor—the fact that ApplyDeviceDefaultsAttribute is, by default, treated as having a true value is the reason.

This ApplyDeviceDefaultsAttribute would ensure that the underlying values that are stored for the specified properties are aligned with the default values specified in the metadata assembly. However, because this action is performed only at design time through the form designer, you still need to ensure that you explicitly set the values either inline with the field declarations or into the properties through the constructor so the defaults are set when the a control instance is created on the actual device or emulator at run time.

The last new attribute of note for this article is SupportedAttribute. This attribute can be applied to all levels, including the class level and the member level. This attribute is used to mark a type, or member, as supported or not supported.

As an example, SupportedAttribute can be useful when you're building a common custom control library that contains controls that are targeted at both the Pocket PC and Smartphone platforms. If one of these controls will not be supported on the Smartphone platform, the metadata assembly for this platform can mark the control as not supported. This action will prevent the control from appearing in the Toolbox when the end developer is targeting the unsupported platform. Also, if this control is being used within a project for Pocket PC, and then the target platform is changed to Smartphone, the control will be rendered through the form designer in a way that clearly indicates that the control is not supported. When this control is selected, the properties and events in the Properties window will be disabled.

A similar example for SupportedAttribute, but at the member level, is if a property is supported on one platform but not the other. This property can be marked as not supported in the metadata assembly for the appropriate platform, and when the control is used in a project that is targeting that platform, the property will not appear in the Properties window. If the end developer attempts to use this unsupported property through the code editor, a warning will be generated when the code is compiled. An example of SupportedAttribute is as follows.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
...
<Property Name="TabStop">
<Supported>false</Supported>
</Property>
...
</Class>

The preceding XML states that the TabStop property of the Contoso.Windows.Forms.BasicLabel custom control is not supported. This is an example of how this attribute is used and is not reflected in the source code accompanying this article.

After you add all of the necessary attributes to the .xmta file by using either the XML editor or Class Designer, you need to compile the project. One of the steps performed during the build process is to determine whether any files with an .xmta extension exist within the project. If there's a file found with an .xmta extension, one of the executed tasks uses the BuildAsmmeta class within the Microsoft.CompactFramework.Build.Tasks assembly. The BuildAsmmeta class is responsible for preparing and passing appropriate arguments to the genasm tool. This tool is in the following directory, where Visual Studio 2005 represents the installation directory for Visual Studio 2005:

Visual Studio 2005/SDK/v2.0/Bin

The genasm tool will generate the metadata assembly by using the information from the .xmta file and the custom control assembly. After the build process is complete, there will be two assemblies in the output directory—the custom control assembly and the metadata assembly. In the current scenario, the custom control assembly is named Contoso.Windows.Forms.dll because this is the name that you specified in the project properties. The metadata assembly is named Contoso.Windows.Forms.PocketPC.asmmeta.dll. This name is constructed by combining the custom control assembly name (Contoso.Windows.Forms), the platform family name for the project (PocketPC), and the abbreviation "asmmeta" (which stands for "assembly metadata").

Now that you know the parts that make up the name of the metadata assembly, you can identify the purpose of the assembly simply by reading the name backward. In the current scenario, such an identification is as follows: "This is an assembly containing metadata that should be applied, through the form designer, when an end developer is targeting the Pocket PC platform by using controls from the Contoso.Windows.Forms assembly." If you were to look inside the metadata assembly, you'd see that it's merely a shell of the custom control assembly. That is, it's a replica of the custom control assembly without member implementation and other non-essential information. However, the attributes have been applied to the appropriate types and members, as specified in the .xmta file.

When a custom control is dragged to a form from the Toolbox, Visual Studio 2005 will attempt to locate an assembly that has the same name as the assembly that contains the control, but with a platform family name and the abbreviation "asmmeta" appended, as described previously. The platform family name represents the platform targeted by the smart device project that contains the form. This name will be PocketPC, Smartphone, or WindowsCE. If this assembly is found, the metadata is used to provide the expected descriptive information, in addition to indicating any types (ControlDesigner, UITypeEditor, and TypeConverter) that may need to be used, at design time. Later, this article will discuss where Visual Studio 2005 finds this metadata assembly.

At this point, you've built the custom control assembly and the metadata assembly. If you want to use custom ControlDesigner, UITypeEditor, and TypeConverter types with your custom control at design time, you'll need to build a third assembly.

Creating the Designer Support Assembly

Sometimes, when you're attempting to build a strong design-time experience for a custom control, you need to specify more than descriptions, categories, and default values. The Panel control is an example of where more design-time support is necessary. A custom designer type is used to draw a dashed border around the Panel control at design time. However, this border is not drawn at run time. The fact that a border is displayed at design time helps you to visualize where the Panel control sits within its container, because by default, the Panel control will appear in the same background color as its parent and does not include any foreground information such as text. A custom designer type is a powerful concept that allows the appearance and behavior of a control to be altered at design time.

There's another example of an important design-time type. When you select the Font property in the Properties window, an ellipsis (…) button appears. Clicking this button displays a dialog box that contains a list of font names, sizes, and styles. You can even see a preview of your selection before you close the dialog box. This ability brings a much better design-time experience than having the end developer enter that information solely as a comma-separated string value. The Font property is an example of where a custom UITypeEditor type can make specifying a property value easier and less error prone for the end developer.

One more type that is often used at design time is a custom TypeConverter type. With TypeConverter, you can change from one type to another. This type is especially important to the Properties window, where everything is essentially a string. In this situation, the job of a custom TypeConverter type is to convert to and from the string type and the actual type that the property expects.

With Visual Studio .NET 2003, you'd typically define custom ControlDesigner, UITypeEditor, and TypeConverter types within the design-time version of the custom control. The design-time–specific types were in the same assembly as the design-time version of the custom control grouping everything into one assembly. With Visual Studio 2005, design-time–specific types need to be defined within a separate assembly—separate from the control logic—through a project targeted at the full .NET Framework 2.0.

First, open the solution that contains your custom control project, targeted at the .NET Compact Framework 2.0, in Visual Studio 2005. The next step is to add a new project to the existing solution that represents your designer support assembly. The reason that you're adding the control project and the designer support project to the same solution is to keep all aspects of your custom control grouped together. This technique should make your custom control easier to maintain over time. To use this technique from Solution Explorer, right-click the solution name, point to Add on the shortcut menu, and then click New Project. In the Add New Project dialog box, select the Visual C# node in the Project types tree, select the Class Library template, and then give the project an appropriate name. In the current scenario, name your project Controls.Design. Figure 10 shows the Add New Project dialog box.

Figure 10. Add New Project dialog box

After the project is created and added to the existing solution, you need to set project properties, add references, and update the generated class file.

Open the project properties. On the Application tab, set the assembly name and default namespace to something appropriate. In the current scenario, set the assembly name to "Contoso.Windows.Forms.Design", indicating that this assembly contains design-time–specific types. Set the default namespace to "Contoso.Windows.Forms". Next, click Assembly Information and supply meaningful information about the assembly. Save the changes and close the project properties.

The next step is to add references. In the current scenario, add a reference to the System.Design.dll and System.Windows.Forms.dll assemblies. You also need to add a reference to a new assembly that you'll need when you're creating the custom designer type. This assembly is named Microsoft.CompactFramework.Design.dll and is in the Global Assembly Cache (GAC) in addition to the following directory, where Visual Studio 2005 represents the installation directory for Visual Studio 2005:

Visual Studio 2005/SmartDevices/SDK/VisualStudio/Designer

After you have referenced all of the necessary assemblies, you can move on to changing the default code file. First, rename the file from Class1.cs to something more appropriate. In the current scenario, rename the file BasicLabelDesigner.cs because it will contain the definition for the BasicLabelDesigner class, which defines a custom designer type for your control. Then, change the default code by setting any using directives, specifying a more accurate namespace and class name, and inheriting your class from a preexisting type.

As you did earlier in this article by using Visual Studio .NET 2003, you now need to define a custom designer type that governs the usability of the resize handles for the control when shown through the form designer. In the current scenario, this custom designer type will be set to react in relation to a change with the AutoSize property value. Use the following code in Visual Studio 2005 to define a custom designer type for your smart device custom control.

Copy Code
public class BasicLabelDesigner : 
Microsoft.CompactFramework.Design.DeviceControlDesigner
{
public override System.Windows.Forms.Design.SelectionRules
SelectionRules
{
get
{
...
}
}
}

Because this article discussed this code earlier in the context of Visual Studio .NET 2003, you can now focus on the important change in how you create a custom designer type for a smart device control using Visual Studio 2005. The change is that you're no longer inheriting your custom designer type from the System.Windows.Forms.Design.ControlDesigner class. Instead, you should inherit from the Microsoft.CompactFramework.Design.DeviceControlDesigner class. This class is defined in the Microsoft.CompactFramework.Design.dll assembly, which is why you added a reference to this assembly earlier. This new class actually inherits from the System.Windows.Forms.Design.ControlDesigner class, so you'll still have access to everything that you had previously in addition to some new members. One of these new members is a virtual method named PaintControlUI. You can override this method to custom paint the user interface for the control at design time, if desired.

After all of the required types have been defined in the designer support project, you need to ensure that the assembly has a strong name when it's built so that it can be installed into the GAC. When a type from the designer support assembly is referenced at design time through attributes in the metadata assembly, Visual Studio 2005 will need to be able to locate this assembly to access these types. By installing the designer support assembly into the GAC, you're ensuring that the assembly will be found.

Visual Studio 2005 provides a tab in the project properties where you can create a strong name key file and indicate that this key should be used to sign the assembly. Open the project properties for the designer support project. On the Signing tab, select Sign the assembly check box. In the Choose a strong name key file list, you can choose to browse to an existing key, or you can choose to create a new key. In the current scenario, click New to create a new key. The Create Strong Name Key dialog box appears. You can specify a name for the key file, and optionally protect this key file with a password. In the current scenario, opt to not protect the key file with a password and set the key file name to Contoso.Key. After you click OK, the key file is created and added to your project. Now, when you build the designer support project, the output assembly will be signed.

To install the designer support assembly into the GAC, you can use the gacutil tool. This tool is in the following directory, where Visual Studio 2005 represents the installation directory for Visual Studio 2005:

Visual Studio 2005/SDK/v2.0/Bin

Because the GAC is in a folder under the Windows directory, you'll need to have the necessary permissions to write to this folder to install to the GAC. If you have the necessary permissions, you can install an assembly to the GAC by using the following command-line syntax:

gacutil /i [assembly name]

In the current scenario, to install the designer support assembly into the GAC, you can use the following command:

gacutil /i Contoso.Windows.Forms.Design.dll

However, an even better way is to allow this assembly to be installed into the GAC automatically after a successful build through Visual Studio 2005. This technique ensures that the most up-to-date build of the assembly is in the GAC. Again, open the properties for the designer support project. On the Build Events tab, make sure that the Run the post-build event option is set to On successful build, and then add the following line to the Post-build event command line text box:

"$(DevEnvDir)../../SDK/v2.0/Bin/gacutil.exe" /i "$(TargetPath)"

The preceding command simply uses the gacutil tool to add the project output assembly into the GAC.

If you build the designer support project at this point, and the project builds successfully, you should be able to find the output assembly in the GAC. There are many ways to see the contents of the GAC. Possibly the easiest way is to open Windows Explorer and browse to the following directory, where Windows represents the location of the Windows directory, which is typically either C:/Windows or C:/WINNT:

Windows/assembly

You should see the output assembly in the list. In the current scenario, this assembly is Contoso.Windows.Forms.Design.dll. While still in this directory, right-click the assembly name of the designer support assembly, and then click Properties on the shortcut menu. You'll need some of the information that appears. Copy the name, version number, culture, and public key token.

The last step is to associate any types that are exposed through the designer support assembly with the custom control. In the current scenario, this is the step where you specify that the custom control should use your custom designer type at design time. Go back to the custom control project, which should be in the same solution as the designer support project, and open the .xmta file. Within the Class element that contains information about your custom control, specify the Designer element, which maps to DesignerAttribute. This element expects a Type element that should contain the assembly-qualified name of the custom designer type. In addition, the Designer element expects a BaseType element that should contain the assembly-qualified name of a base type for the custom designer type specified in the Type element. In the current scenario, add information to the .xmta file similar to the following text.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
<Designer>
<Type>
Contoso.Windows.Forms.Design.BasicLabelDesigner,
Contoso.Windows.Forms.Design,
Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=e094b629d475d255
</Type>
<BaseType>
System.ComponentModel.Design.IDesigner,
System,
Version=2.0.0.0,
Culture=neutral,
PublicKeyToken=b77a5c561934e089
</BaseType>
</Designer>
...
</Class>

As shown in the preceding XML, the Designer element is where you use the values that you copied from the GAC. What you're stating is that the Contoso.Windows.Forms.Design.BasicLabelDesigner type, which is located in version 1.0.0.0 of the Contoso.Windows.Forms.Design.dll assembly, should be used as the designer type for the Contoso.Windows.Forms.BasicLabel custom control.

Now that you've created a custom control assembly, a metadata assembly, and a designer support assembly, you can draw a comparison between these three assemblies and the run-time assembly and design-time assembly that you used with Visual Studio .NET 2003. Essentially, you can think of the custom control assembly as similar to the run-time assembly; the difference is that you're also using the custom control assembly at design time with Visual Studio 2005. The design-time assembly, from Visual Studio .NET 2003, is split apart such that the attributes are compiled into the metadata assembly and the supporting types are compiled into the designer support assembly.

After everything has been compiled successfully, and the designer support assembly has been installed into the GAC, you can proceed to test the control by using the same technique that you used when testing under Visual Studio .NET 2003.

Testing the Control

To test the control, you'll start by creating a test application. You'll add the test application directly to the solution that contains the custom control, just as you did for the designer support project. In Solution Explorer, right-click the solution name, point to Add on the shortcut menu, and then click New Project. Because you need to test a smart device custom control, you need to select a Device Application template, from the available project templates, that reflects the platform family and .NET Compact Framework version that you're targeting. In the current scenario, select the Pocket PC 2003 project type under the Smart Device node for the Visual C# development tool, and then select the Device Application template that targets the .NET Compact Framework 2.0. Then, name the project Controls.Test.

The first test that you perform is a simple check to ensure that the control can be added to the Toolbox. In Visual Studio 2005, only smart device custom controls that have been compiled against the same version of the .NET Compact Framework as the target for the open project will be visible in the Toolbox. In other words, if a form is being designed within a project that targets the .NET Compact Framework 1.0, only controls that have been compiled against the .NET Compact Framework 1.0 will appear in the Toolbox. The same is true about projects targeting the .NET Compact Framework 2.0. You therefore need to ensure that you're testing your custom control by using a smart device application that targets the same .NET Compact Framework version.

By adding the test application to the same solution as the custom control, you can right-click the control project name in Solution Explorer and then click Build on the shortcut menu, and the control will automatically appear in a special section near the top of the Toolbox when the form in the test application is open. This is the simplest way to add the custom control to the Toolbox for testing purposes because the control will appear in the Toolbox only when the solution containing the control project is open. The other benefit to having the test application in the same solution as the control project is that changes can be made to the control, and after the control is rebuilt, the test application form refreshes, and these changes are visible.

At some point, the custom control needs to be added to the Toolbox so that it can be used by an end developer or so that it can be tested outside its own solution. Assuming that the designer support assembly, if necessary, is installed in the GAC, there are two options for where the control and metadata assemblies can be located. The first option is to place the control assembly, the metadata assembly, and the XML documentation file in the same directory. When Visual Studio 2005 looks for the metadata assembly, it will look in the same directory as the control assembly. This is a simple way that allows everything to be grouped in a specific folder (for example, a folder related to the company or product). The only downside to this approach is the following: for Visual Studio 2005 to display the assemblies on the .NET tab of the Add Reference dialog box, which would make it easier for a developer to explicitly add a reference to an assembly because they would not need to browse, you need to add a key to the registry. This addition requires the control installer to have privilege to write to the registry. If you prefer this approach, you can add a new subkey to the following registry key, where Target is PocketPC, Smartphone, or WindowsCE.

Copy Code
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETCompactFramework/v2.0.0.0/
Target/AssemblyFoldersEx

You can name the new subkey any name, but you should name it after your company or product. In this scenario, you could create a new key named Contoso and add this as a subkey to AssemblyFoldersEx.

Copy Code
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETCompactFramework/v2.0.0.0/
PocketPC/AssemblyFoldersEx/Contoso

The only value that is necessary is to set the default value to a string that represents the path to the assemblies. As an example, it could be something like the following: C:/Program Files/Contoso/.

This path would direct Visual Studio 2005 to present the assemblies that it finds at C:/Program Files/Contoso/ to the developer on the .NET tab of the Add Reference dialog box when the developer is editing a Pocket PC project that targets the .NET Compact Framework version 2.0.

The second option, which is more like the process used with Visual Studio .NET 2003, is to move the assemblies and XML documentation file to specific directories.

If the control is built against the .NET Compact Framework 1.0, the control assembly and XML documentation file can be placed in the following directory, where Visual Studio 2005 represents the installation directory for Visual Studio 2005:

Visual Studio 2005/SmartDevices/SDK/CompactFramework/2.0/v1.0/WindowsCE.

A metadata assembly, created through a custom control project that was compiled against the .NET Compact Framework 1.0, can be placed in the following directory:

Visual Studio 2005/SmartDevices/SDK/CompactFramework/2.0/v1.0/WindowsCE/DesignerMetadata.

If the control is built against the .NET Compact Framework 2.0, the control assembly and XML documentation file can be placed in the following directory:

Visual Studio 2005/SmartDevices/SDK/CompactFramework/2.0/v2.0/WindowsCE.

A metadata assembly, created through a custom control project that was compiled against the .NET Compact Framework 2.0, can be placed in the following directory:

Visual Studio 2005/SmartDevices/SDK/CompactFramework/2.0/v2.0/WindowsCE/DesignerMetadata.

To add the custom control to the Toolbox, right-click in the Toolbox, and then click Choose Items on the shortcut menu. In the Choose Toolbox Items dialog box, on the .NET Framework Components tab, click Browse. Browse to the directory that contains the custom control assembly, select the control assembly in the list, and then click Open.

The process for verifying descriptions, categories, default values, and, in general, any other attributes, in addition to the process for verifying all designer support types, accurate IntelliSense technology, XML documentation, and run-time appearance and behavior, is the same as the process described earlier in the context of testing the custom control by using Visual Studio .NET 2003.

Just as you did with Visual Studio .NET 2003, you've used Visual Studio 2005 to create a custom control that displays text to the end user and that provides a strong design-time experience for the end developer. You've now seen how to build the same control by using both Visual Studio .NET 2003 and Visual Studio 2005. The next section discusses steps that you can take to migrate an existing control from Visual Studio .NET 2003 to Visual Studio 2005.

如何将Visual Studio 2003开发的自定义控件移植到Visual Studio 2005

For the developers who have an existing investment in smart device custom controls, migrating from Visual Studio .NET 2003 to Visual Studio 2005 is a relatively straightforward process. This article has described developing the same custom control by using both Visual Studio .NET 2003 and Visual Studio 2005. The real differences that you encountered were in regard to how you achieve a strong presence at design time. Therefore, the bulk of the work is in changing over from the old design-time paradigm to the new.

In the scenario that you've been following in this article, one of the requirements is to maximize the number of potential end developers who can use the custom control. Fortunately, Visual Studio 2005 provides the ability to easily convert preexisting Visual Studio .NET 2003 projects, in addition to allowing projects that target the .NET Compact Framework 1.0 to be upgraded to target the .NET Compact Framework 2.0.

You can begin by opening the custom control solution for the run-time version, built by means of Visual Studio .NET 2003, from within Visual Studio 2005. Start Visual Studio 2005. On the File menu, point to Open, and then click Project/Solution to display the Open Project dialog box. Next, locate the Visual Studio .NET 2003 solution that contains the smart device custom control project that you intend to migrate, and then click Open to open this solution. The Visual Studio Conversion Wizard walks you through the steps of converting your solution from the Visual Studio .NET 2003 format to the new Visual Studio 2005 format. Figure 11 shows the welcome page for the Visual Studio Conversion Wizard.

Figure 11. Visual Studio Conversion Wizard welcome page

As you progress through the wizard, you're asked to make a backup of the solution, which will also back up any project files that this solution contains. In the current scenario, you need to select the backup option so you can still have a working solution for Visual Studio .NET 2003. On the last page of the wizard, click Finish to begin the conversion. When the conversion is complete, Visual Studio 2005 will generate, and by default display, a conversion report that indicates any errors or warnings that may have been encountered with projects and files within the solution.

At this point, you have a solution containing your custom control project that targets the same platform as in Visual Studio .NET 2003, and that even targets the .NET Compact Framework 1.0 if Visual Studio 2005 supports the .NET Compact Framework 1.0 for the target platform and project type. One of the key benefits of converting the solution, and project, by using the Visual Studio Conversion Wizard is that this technique maintains the project settings. In other words, the converted project is still the same project type as it was previously, the references are still set appropriately, and the project properties have been maintained.

Now that the smart device custom control solution has been converted to Visual Studio 2005, the control assembly (called the run-time assembly in the context of Visual Studio .NET 2003) can be compiled and added to the Toolbox. However, the design-time experience will not be what's expected because you haven't specified the .xmta file that will be used to build the metadata assembly. This is where the majority of the time will be spent during migration. Unfortunately, the process involves manually copying attributes from the design-time–specific sections in your custom control source code to an XML format in the .xmta file. For example, you need to use the following code to represent the attributes within an .xmta file.

Copy Code
namespace Contoso.Windows.Forms
{
public class BasicLabel : System.Windows.Forms.Control
{
#if DESIGN_VS2003
[CategoryAttribute("Behavior")]
[DefaultValueAttribute(false)]
[DescriptionAttribute("Indicates whether the control is
automatically resized to display its entire contents.")]
#endif
public bool AutoSize
{
...
}

...

}
}

The result of translating the attributes in code form, as shown previously, to the XML form within an .xmta file is as follows.

Copy Code
<Class Name="Contoso.Windows.Forms.BasicLabel">
...
<Property Name="AutoSize">
<Category>Behavior</Category>
<DefaultValue>
<Type>
System.Boolean, mscorlib, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</Type>
<Value>False</Value>
</DefaultValue>
<Description>
Indicates whether the control is automatically resized to
display its entire contents.
</Description>
</Property>
...
</Class>

Each design-time–specific section needs to be translated in a similar way until all of the design-time attributes that were on types and members are now in the .xmta file. After you move all of the attributes and compile the project, the metadata assembly that describes the custom control will be built. However, keep in mind that if you reference device-specific features, or if you declare Win32 API functions to be invoked through platform invoke, and assuming that you have looked over the custom control to ensure compatibility with the design-time environment, you need to apply DesktopCompatibleAttribute.

The last step in migration is to isolate any designer support types that were included in the design-time assembly by means of Visual Studio .NET 2003, and place them in a separate, full .NET Framework 2.0 class library project. After you successfully compile this designer support assembly, you need to install it into the GAC so that Visual Studio 2005 can access types in this assembly at design time.

If, at some point, you want to upgrade your custom control to target the .NET Compact Framework 2.0, Visual Studio 2005 has a built-in feature to make the upgrade for you. However, because Visual Studio 2005 can only upgrade a project, and after the project is upgraded, Visual Studio 2005 cannot be used to automatically downgrade, you'll need to make a copy of the project before you begin. You accomplish this task outside Visual Studio 2005, as a file copy operation in Windows Explorer. After you make a copy of the project, in Solution Explorer, right-click the project that currently targets the .NET Compact Framework 1.0, and then click Upgrade Project on the shortcut menu. In the message that appears, click Yes to upgrade your project. When you build the custom control, it will now be compiled against the .NET Compact Framework 2.0.

To recap, you migrate an existing smart device custom control by opening the custom control solution (which represents the run-time version) within Visual Studio 2005. Then, you run through the Visual Studio Conversion Wizard to convert the necessary files to the new Visual Studio 2005 format. Then, you translate the attributes in the design-time–specific sections into an XML form that can be placed inside an .xmta file. You compile the converted project to produce a custom control assembly and a metadata assembly. Last, you move all of the design-time supporting types to a full .NET Framework 2.0 class library project, compile this project, and then install the resulting designer support assembly into the GAC. Now, if you want to upgrade to target the .NET Compact Framework 2.0, the task will be as simple as clicking a command on a menu.

The scenario that you've followed in this article included the Visual Studio Conversion Wizard, some work to move around attributes and designer support types, and the upgrade feature that enables a seamless upgrade to the .NET Compact Framework 2.0. Although the scenario required supporting both the .NET Compact Framework 1.0 and 2.0, in addition to supporting both Visual Studio .NET 2003 and Visual Studio 2005, the scenario wasn't as complicated as you might have initially thought. In fact, by using a combination of source file linking and conditional compilation constants, you can even share the same source code across all projects, even if those projects are targeting different versions of the .NET Compact Framework or are using either Visual Studio .NET 2003 or Visual Studio 2005. Furthermore, even though the design-time paradigms between Visual Studio .NET 2003 and Visual Studio 2005 are different, you're still able to provide support for a strong design-time experience to the end developer. Certainly, you've met the objective of the scenario in this article.

结论

This article focused on the architecture of building smart device controls by using Visual Studio. It started by outlining a scenario that you followed as a complement to the concepts that the article discussed. First, you built a custom control by using Visual Studio .NET 2003 to review how smart device controls are being built today. You then built the same control by using Visual Studio 2005. Seeing a complete walkthrough of how to build a smart device control by using Visual Studio .NET 2003 and Visual Studio 2005 gives perspective on the effort required and the differences that a developer may encounter. You finished by exploring how a custom control developer can migrate an existing control from Visual Studio .NET 2003 to Visual Studio 2005.

This article provided code examples when applicable. However, because each control is usually developed out of necessity, and therefore the code required is unique to the task, this article was written to convey an understanding of the steps required to create and test custom controls rather than provide a detailed technical discussion of the code itself. This article placed an emphasis on ensuring that a custom control can provide an appropriate view within a form designer.

Remember that the design time is to an end developer what the run time is to an end user. Developers need the Visual Studio environment to be consistent, productive, and empowering. With Visual Studio 2005, not only are you much more productive when building and testing custom controls, but you're also able to produce a design-time experience that enables end developers to be more productive.

The download code sample contains a complete implementation of a custom control that supports Visual Studio .NET 2003, Visual Studio 2005, and both the .NET Compact Framework 1.0 and 2.0.

 
原创粉丝点击