转自:http://www.microsoft.com/china/msdn/archives/library/techart/pdc_vsdescmp.ASP#pdc_vsdescmp_topic8
Shawn Burke
Microsoft Corporation
2000年7月
摘要:Microsoft .NET 组件是以管理代码编写并在通用语言运行时上构建的。本文论述了它是如何向开发人员提供了一个全新的混合开发环境,即象 Microsoft Visual Basic 一样容易,而同时又提供了强大的低级编程能力,与 ATL 或 MFC 更加相关。
目录
简介
微软即将发布的 Visual Studio .NET 将使程序开发人员获得一个集成开发环境,它不但为开发传统的 C/C++ 应用程序,而且也为令人振奋的 Microsoft .NET 组件提供了丰富的工具。这些以管理代码编写、在通用语言运行时构建的组件向开发人员提供了一个全新的混合开发环境,即象 Microsoft Visual Basic 一样容易,而同时又提供了强大的低级编程能力,与 ATL 或 MFC 更加相关。随着以生产效率为中心的管理环境的到来,它可与传统 COM 组件很好地协同工作。开发人员可以将更多时间花在构建大型组件上,而不用再为内存泄漏、安全和头文件担心。
除了提供 Microsoft .NET Framework 组件的开发外,Visual Studio .NET (VS .NET) 还拥有很多工具,可以让组件利用 VS .NET 中设计器架构的优势来设计出在外观和性能上与 VS .NET 所附带组件相近的产品。在开发管理组件时,在 VS.NET 设计器中获得的所有特性都使用组件本身的 .NET Framework,从而获得设计时与运行时组件之间的紧密集成。
组件是什么
很显然,Microsoft .NET Framework 组件很容易编写。让它们与 Visual Studio .NET 设计器一同工作的唯一要求是它们实现 System.ComponentModel.IComponent,即通常表明继承于 IComponent 的默认应用。IComponent 使组件可跟踪设计时的信息(如它的容器组件或名称)或访问设计器提供的服务。
让我们编写一个简单的 .NET 组件,它的形式如下:
using System;
using System.ComponentModel;
public class BoolTracker : Component {
private bool state;
private EventHandler handler;
private static object EventValueChanged = new object();
public BoolTracker() {
}
public bool Value {
get {
return state;
}
set {
if (this.state != value) {
this.state = value;
OnValueChanged(new EventArgs());
}
}
}
public void AddOnValueChanged(EventHandler h) {
handler = (EventHandler)Delegate.Combine(handler, h);
}
protected virtual void OnValueChanged(EventArgs e) {
if (handler != null) {
handler(this, e);
}
}
public void RemoveOnValueChanged(EventHandler h) {
handler = (EventHandler)Delegate.Remove(handler, h);
}
}
显然,这个组件不完成什么功能, 但会将它置入 Visual Studio .NET Win 窗体设计器或组件设计器中,即可从属性浏览器中看见它有名称,也有一个称为 “Value” 的属性,使用下拉箭头可以将值设置为 True 或 False,当值在 True 和 False 之间切换时,可以触发事件 OnValueChanged。
对于设计器来说,组件只是我们要说明的一半,最重要的部分是属性,它组成了元数据,元数据是关于类、属性、事件等的信息。让我们以 Value 属性为例。仅作为属性,就已经有相关的元数据了,例如类型(布尔)、行为(读/写)或名称(“Value”)。使用“反射”对基本元数据进行检索,即通用语言运行时允许用户在运行时检查对象的类型、基本类型、属性、方法、构造器、字段和访问级别。所有这些信息都被认为是元数据。
定制元数据
定制元数据包括可添加到类或类成员的任意信息段(字段、属性或方法),实际上是类型本身被特定客户所识别。对于Visual Studio .NET 设计器来说,定制元数据构成了所有可扩展性的基础。VS .NET 设计器理解的所有元数据属性都基于一个名为 System.ComponentModel.MemberAttribute 的类。它提供一个基本类,因此开发器所关心的属性可以通过它们的类型快速标识。
通过一个典型实例可以更容易理解这一概念。比如我们不希望 Value 属性在属性浏览器中显示。我们可以添加一个元数据属性 System.ComponentModel.BrowsableAttribute 来控制一个属性是否可被浏览。
[Browsable(false)]
public bool Value {
get {
return state;
}
set {
if (this.state != value) {
this.state = value;
OnValueChanged(new EventArgs());
}
}
}
在指定属性时,可以将“BrowsableAttribute”缩略为“Browsable”。由 C# 编译器为我们添加“Attribute”一词。唯一的限制是如果指定了属性值,它必须与构造器的属性类型相符,且该数值必须是常量。在本例中,BrowsableAttribute 有一个单一的布尔型参数“Browsable”的构造器,编译器把这个元数据属性绑定到该构造器并创建一个属性类的实例。如果属性类浏览器获得了这个对象,它将枚举出该对象的属性并忽略“browsable”属性,因为它以此属性为标签。因此看起来该对象没有属性。BrowsableAttribute 也可应用于事件。
Microsoft .NET Framework 拥有丰富的属性集来控制设计器如何使用组件。这里是其中一些有用属性的列表,使您在以后的阅读中更能理解其含义:
属性名 |
说明 |
BrowsableAttribute |
控制属性或事件是否显示在属性浏览器中。 |
BindableAttribute |
确定属性是否适合数据绑定器进行绑定。 |
CategoryAttribute |
指定属性在属性浏览器中应分组的类别(“Appearance”, “Layout”, “Behavior”,“ Misc”等等)。 |
DefaultEventAttribute/ DefaultPropertyAttribute |
指定对象的默认事件或属性。 |
HelpAttribute |
指定属性或事件的帮助文件和主题。 |
LicenseProviderAttribute |
指向为组件提供许可证信息的 LicenseProvider。 |
MergablePropertyAttribute |
在属性浏览器中当多个组件被浏览和选中时,允许或阻止包含某属性。 |
PersistableAttribute |
确定在 Win Forms Designer 或 Component Designer 等可视设计器中生成代码时,属性值是否应与代码保持一致。 |
PersistContentsAttribute |
确定代码生成是否应回归到对象的非数值类型属性以及是否保持代码与属性值一致。ICollection 属性类型是这一应用的典型示例。 |
ShowInToolboxAttribute |
确定是否允许在工具框中使用这一组件。 |
ToolBoxItemAttriubte |
指定从工具框中创建类时应使用的 ToolboxItem 类型。 |
属性浏览器接口
你或许注意到属性浏览器已在上一部分中多次提到。这是因为在设计器中组件参与的大量活动发生在属性浏览器中。设计器组织并显示组件,但是主要是由对象浏览器决定允许可以修改它们。在以前的版本中,浏览器显示自带的 COM 对象,这些对象拥有要显示的数据类型的已定义子集:数字、字符串、字体、颜色、图像、布尔值和枚举值。除此之外,由属性页或对话框来完成其它的属性操作。但是,VS .NET 设计器允许组件设计器来定义属性浏览器如何处理对象的任何属性类型,以及用户如何编辑这些类型。
.NET Framework 使用这些编辑器编辑许多内置类型,例如,System.Drawing.Color、System.Drawing.Font 或 Win Forms 控件的Dock和Anchor属性。
图 1. 属性编辑器
此属性浏览器可扩展架构来提供四种基本功能类型:
System.ComponentModel.TypeConverter 处理前三个功能类型,源自System.WinForms.Design.UITypeEditor 的编辑器处理最后一个。
所有在 .NET Framework 中遇到的属性都有内置的 TypeConverters。让我们先看一下 TypeConverter 类及其功能,然后再讨论特定的示例。下面的代码是 TypeConverter 的缩减版。它具有许多已列出方法的特色,但是为了简单起见,将它们忽略,只有那些核心方法被保留下来。所有此处列出的方法都是虚拟的,因此可在需要的时候忽略它们。但是,TypeConverter 是作为一个基类而不是接口来实现的,大多数重要功能已经内置,用户可以忽略与应用程序相关的部分。
public class TypeConverter {
//
// 值转换方法。
//
// 确定 TypeConverter 可以从特定类型转换为目标类型。
public virtual bool CanConvertFrom(
ITypeDescriptorContext context,
Type sourceType);
// 确定 TypeConverter 是否可以从目标类型转换为特定类型。
public virtual bool CanConvertTo(
ITypeDescriptorContext context,
Type destinationType);
// 为 TypeConverter 转换目标类型值中的值。
public virtual object ConvertFrom(
ITypeDescriptorContext context,
object value,
object[] arguments);
// 将值从 TypeConverters 目标类型转换为目的类型。
public virtual object ConvertTo(
ITypeDescriptorContext context,
object value,
Type destinationType,
object[] arguments);
//
// 实例创建方法。
//
// 创建目标类型的对象。
public virtual object CreateInstance(
ITypeDescriptorContext context,
PersistInfo persistInfo);
// 创建目标类型的对象并从给定的 IDictionary 置入它的值。
public virtual object CreateInstance(
ITypeDescriptorContext context,
IDictionary propertyValues);
// 指定 TypeConverter 是否知道如何创建目标类型的对象实例。
public virtual bool GetCreateInstanceSupported(
ITypeDescriptorContext context);
// 获得值的 PersistInfo,
// PersistInfo 是说明给定值保留状态的对象。
public virtual PersistInfo GetPersistInfo(
ITypeDescriptorContext context,
object value);
//
// 子属性方法。
//
// 检索确实要在属性浏览器中为此类型显示的任何子属性。
public virtual PropertyDescriptorCollection GetProperties(
ITypeDescriptorContext context,
object value,
MemberAttribute[] attributes);
// 指定此类型是否应该在属性浏览器中与 '+' 一同显示,且是否显示子属性。
public virtual bool GetPropertiesSupported(
ITypeDescriptorContext context);
//
// 预定义的/标准值方法。
//
// 为此类型检索一个预定义的“标准”值集合。
public virtual StandardValuesCollection GetStandardValues(
ITypeDescriptorContext context);
// 指定 GetStandardValues 的值集合是一个可能或有效值的完整列表。
public virtual bool GetStandardValuesExclusive(
ITypeDescriptorContext context);
// 确定 TypeConverter 是否可以返回一个标准值列表。
public virtual bool GetStandardValuesSupported(
ITypeDescriptorContext context);
// 检查值对于此类型是否是有效值。
public virtual bool IsValid(
ITypeDescriptorContext context, object value);
//
// 实用方法。
//
// 按名称数组指定的属性顺序排列属性集合。
protected PropertyDescriptorCollection SortProperties(
PropertyDescriptorCollection props,
string[] names);
}
public interface ITypeDescriptorContext : IServiceObjectProvider {
// 返回符合此上下文环境的 IContainer。
IContainer Container { get; }
// 返回正在检查的实例。
// 如果有值传递给 TypeConverter,
// ITypeDescriptorContext::Instance 将返回提供值的对象。
object Instance { get; }
// 在对从实例返回的对象更改前调用。如果返回 False, 则不能做出更改并应中止。
bool OnComponentChanging();
// 在更改后,或已经对从实例返回的对象更改后调用。
void OnComponentChanged();
}
正如您看到的那样,TypeConverter 包含很多内容。还包括 ITypeDescriptorContext 的一个简要说明。因为它在大多数 TypeConverter 方法中以一个参数形式出现。 ITypeDescriptorContext 允许对服务和组件对象进行访问,这些组件对象提供传递给 TypeConverter 的所有值。
但是,仔细研究 TypeConverter 会发现它并不象表现的那样复杂。其功能可分为四组:
- 值转换:诸如属性浏览器的客户机需要经常将一种字符串表示方式方便地转换为另一种。TypeConverter 为此提供了一种标准方法,以及一种判定给定转换是否可行的方法。因为字符串之间的转换最为常见,所以 TypeConvert 为 ConvertFromString 和 ConvertToString(未显示)提供了更有益的方式。
- 实例的创建与持续性:TypeConverter 负责创建给定类型的实例。TypeConverter 可使类型的创建者来控制这一过程,而不是强制客户机来检查构造器或获知默认值。按此方式,TypeConverter 也扮演在设计器中通过代码保持组件的角色。它可以创建与使用 PersistInfo 对象,该对象说明对象状态的哪一部分与持续性相关(经常取自代码)以及如何保持它们。
- 子属性方法:TypeConverter 允许对象对在属性浏览器中显示的子属性进行控制。System.Drawing.Font 就是一例,实例展开时显示的属性并不完全匹配 Font 对象本身的实际属性。它们为明确性和可用性而被更改并重新排序。
- 标准值:许多类型(如枚举)都具有定义的值子集。其它类型可以有预定义的全集,但也可以接受其它数值。TypeConverter 允许客户机检查这些列表并检查未在列表中的值是否可以接受。
TypeConverter 可以通过两种方式之一与属性相关联。通过在类声明中有 TypeConverterAttribute,类型可以指定它们自己的 TypeConverter。通常这些类让它们的转换器成为嵌套类,因此类型和设计时的信息是一个整体。换句话说,属性也可以通过属性声明本身指定 TypeConverter 来忽略与属性类型相关的 TypeConverter。当然在那里指定的 TypeConverter 必须要知晓特定类型。不能任意指定 TypeConverter,但是这种方法有助于了解给定属性的显示方式,添加或删除子属性或为不同字符串转换添加支持。您可以从默认的 TypeConveter 中导出一种类型,并将导出的类型指定为给定属性的 TypeConverter 来了解这些知识。与属性浏览器接口的另一半涉及属性编辑器。属性编辑器的结构远比 TypeConverter 结构简单。为了给不依赖于 Win Forms 或不与其连接的编辑器保持开放,并没有基本编辑器类型。因为编辑器通常采用用户界面(UI)驱动,.NET Framework 中的所有标准编辑器都源自 System.Drawing.Design.UITypeEditor。
public class UITypeEditor {
// 在编辑值时调用。
public virtual object EditValue(
ITypeDescriptorContext context, IServiceObjectProvider provider,
object value);
// 指定此编辑器是否可以显示值。
public virtual bool GetPaintValueSupported(
ITypeDescriptorContext context);
// 如果有,则返回此编辑器的用户界面样式。
public virtual UITypeEditorEditStyle GetEditStyle(
ITypeDescriptorContext context);
// 当客户机需要在 UI 上显示值时调用。
public virtual void PaintValue(
ITypeDescriptorContext context,
object value,
Graphics canvas,
Rectangle rectangle);
}
public enum UITypeEditorEditStyle {
// 没有交互的 UI 组件。
None = 1,
// 模态 UI 属性将显示一个 [...]
// 来启动一个对话。
Modal = 2,
// 下拉 UI 属性将显示向下箭头的按钮,并且
// UI 将处于类似组合框的下拉菜单中。
DropDown = 3
}
// 接口可通过调用 UITypeEditor::EditValue 中的
// provider.GetServiceObject(typeof(IwinFormsEditorService))
// 进行检索。
public interface IWinFormsEditorService {
// 关闭当前显示的下拉菜单。
void CloseDropDown();
// 为值的 UI 编辑将给定控件
// 置于下拉菜单中。
void DropDownControl(Control control);
// 启动模态对话框来编辑属性值
DialogResult ShowDialog(Form dialog);
}
EditorAttribute 与 TypeConverterAttribute 类似, 可在它们应用的类型或某一特定类型的属性上予以指定,该特定类型将取代在 Type 本身中指定的任何值。
用户单击属性浏览器上的下拉或椭圆按钮将调用 UITypeEditor.EditValue。这里是在下拉编辑器中 EditValue 的一个典型实现:
public override object EditValue(
ITypeDescriptorContext context, IServiceObjectProvider provider,
object value) {
object returnValue = value;
if (provider != null) {
IWinFormsEditorService edSvc = (IWinFormsEditorService)
provider.GetServiceObject(
typeof(IWinFormsEditorService));
if (edSvc != null) {
MyUIEditorControl uiEditor =
new MyUIEditorControl(edSvc);
edSvc.DropDownControl(uiEditor);
value = uiEditor.NewValue;
}
}
return value;
}
PaintValue 允许编辑器显示可见的特定值的表示。例如,WinForms 对象使用该编辑器编辑图像、颜色和字体。
图 2. 编辑器显示
这是一个基于代码的示例:
public override void PaintValue(
ITypeDescriptorContext context,
object value,
Graphics canvas,
Rectangle rectangle) {
if (value is Color) {
Color color = (Color)value;
SolidBrush b = new SolidBrush(color);
canvas.FillRectangle(b, rectangle);
b.Dispose();
}
}
ColorEditor's PaintValue code.
从外部引入:代码持续性
与一些过去的设计器不同,.NET Framework 组件的 Win Forms 和其它 VS .NET 设计器只依赖于代码持续性来形成状态。没有不可思议的格式,没有隐藏的数据,只有简明的代码。当然,类似位图和本地化字符串之类的内容会以二进制的形式与代码打包,但是组件的状态及其组成物在代码中保持连续。在设计器中做改动时会生成代码。如果您处理代码,它会被重新分析,更改也会反映到设计器中。
为了充分利用这一功能,.NET Framework 中的设计器提供了所有的渠道。对于任何类型,所有设计器都要了解以下内容:
- 对于持续性,对象状态的什么信息有意义?
- 信息怎样返回给活动对象?
这些已经在前面章节中进行了简要的论述。我们再次强调 TypeConverter 是过程的核心。代码生成和代码分析设计器涉及一种称为 CreationBundle 的特别类型的 PersistInfo。例如:
[TypeConverter(typeof(IntBoolString.IntBoolStringConverter))]
public class
IntBoolString {
private int intVal;
private string stringVal;
private bool boolVal;
public IntBoolString(string s, int I, bool b) {
This.intVal = I;
This.stringVal =s ;
This.boolVal = b;
}
public bool Bool{
get {return boolVal;}
set {boolVal = value;}
}
public int Int {
get {return intVal;}
set {intVal = value;}
}
public string String {
get {return stringVal;}
set {stringVal = value;}
}
public override string ToString() {
return intVal + "," + boolVal + "," + stringVal;
}
public class IntBoolStringConverter : TypeConverter {
public override bool CanConvertFrom(
ITypeDescriptorContext context,
Type sourceType) {
return (sourcetType == typeof(string));
}
public virtual object ConvertFrom(
ITypeDescriptorContext context,
object value,
object[] arguments) {
if (value is string) {
string stringValue = (string)value;
int intValue;
bool boolValue;
int commaIndex =
stringValue.IndexOf(',');
if (commaIndex != -1) {
intValue = Int32.
Parse(stringValue.
Substring(0, commaIndex));
commaIndex = stringValue.
IndexOf(',',
commaIndex + 1);
if (commaIndex != -1) {
int nextComma = stringValue.IndexOf(',', commaIndex + 1);
if (nextComma != -1) {
boolValue = Boolean.Parse(stringValue.Substring
(commaIndex+1,
nextComma - commaIndex));
stringValue = stringValue.Substring(nextComma+1);
return new IntBoolString(intVal, boolVal, stringValue);
}
}
}
throw new FormatException("Can't convert"' + stringValue +
"' to IntBoolString Object");
}
}
public override PersistInfo GetPersistInfo(ITypeDescriptorContext
context,
object value) {
if (value is IntBoolString) {
IntBoolString ibs = (IntBoolString)value;
return new CreationBundle(typeof(IntBoolString), null,
new CreationArgument[] {
new CreationArgument(ibs.Int, typeof(Int32)),
new CreationArgument(ibs.Bool, typeof(bool)),
new CreationArgument(ibs.String, typeof(string))});
}
return base.GetPersistInfo(context, value);
}
}
public override object CreateInstance(ITypeDescriptorContext
context, IDictionary propertyValues) {
return new IntBoolString((int)propertyValues["Int"],
(bool)propertyValues["Bool"],
(string)propertyValue["String"]);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext
context) {
return true;
}
}
使用 CreationBundle 对象的好处是,如果构建器与传递来的 CreationArguments 的每一类型都匹配,那么该对象知道如何创建存储信息的对象。当调用 TypeConverter::CreateInstance 并试图以这种方式创建和初始化对象时,TypeConverter 的默认实现调用 CreationBundle::Invoke。如果没有构建器,CreateInstance 调用会使用 IDictionary 定制更为灵活的对象创建。传入的 IDictionary 对象包含每一个属性名的非持续性值。
组件经常包含组成多个对象的属性值。其它框架经常为此目的使用属性数组。但使用数组也有一些缺点。例如,调入和调出数组时都需要复制数组,这会带来性能问题。数组也不能对添加、修改、或删除值等操作提供智能通知。实际上,如果属性传回数组,则还要进行大量的添加和删除项目的工作。数组还是一个快照值,并且在基本对象改变时也不会更新。
与此不同,.NET Framework 为此目的使用集合,集合是实现 ICollection 的对象。对象可以创建集合并将其传给任一对象,基本对象的任何改动都会使引用更新。如果集合被其它对象更改,也会通知该对象。为了使 .NET Framework 设计器使用集合,它们还需要支持 ALL 属性,该属性有 “get” 和 “set”,它的类型是一个集合持有的对象数组。例如:
public class IntCollection : ICollection {
private int[] values;
public IntCollection(int[] intValues) {
this.values = (int[])intValues.Clone();
}
public int[] All {
get {
return (int[])values.Clone();
}
set {
values = (int[])value.Clone();
}
}
public int Count {
get {
if (values == null) {
return 0;
}
return values.Length;
}
}
[Browsable(false)]
public object SyncRoot {
get {
return this;
}
}
[Browsable(false)]
public bool IsReadOnly {
get {
return false;
}
}
[Browsable(false)]
public bool IsSynchronized {
get {
return true;
}
}
}
.NET Framework 持续性机制可以使集合保持或解除持续性。如果集合由更高级的类型组成,例如上面的 BoolInString 示例类型,那么只需让与该类型关联的 TypeConverter 为集合中的每一项创建一个有效的 PersistInfo (特别是为 VS .NET 设计器创建 CreationBundle)。
组件设计器
如前所述,.NET Framework 内置的设计器可以满足大多数主要组件的要求。尽管这样,.NET Framework 为组件设计者提供了一个可充分扩展的体系结构。所有设计器都基于下面的 System.ComponentModel.Design.IDesigner 接口:
public interface IDesigner {
// 与此设计器相关联的组件。
IComponent Component {get;}
// 与此组件相关联的设计时动词,
// 例如用于 TabControl 的“添加标签”。
DesignerVerb[] Verbs {get;}
// 分配设计器使用的资源,
// 设计器在此调用后不再可用。
void Dispose();
// 调用使设计器执行“默认操作”,
// 通常为响应运行时对组件的双击而调用。
void DoDefaultAction();
// 用给定组件初始化设计器。
void Initialize(IComponent component);
}
正如您所看到的那样,IDesigner 是直接的。设计者通过 DesignerAttribute 与组件关联:
[Designer("MyNameSpace.Design.MyComponentDesigner, MyNameSpace.DLL")]
Public class MyComponent : Component {
}
如果 DesignerAttribute 不出现在类中,则类分层结构将被遍历直至找到开发环境为止。在前面的示例中,将默认的 ComponentDesigner 定位在 Component 基类上,然后会使用它。一些设计器使用 UI。而另一些则不然。对于 ComponentDesigner,您会看到一个表示对象的图标,因为组件通常没有 UI。另一方面,Win Forms 控件具有在设计时显示实际控件的设计器。
图 3. 设计时的 Win Form 控件
请注意不显示 UI 的控件就是设计器下面的图标,拥有 UI 的 Win Forms 控件显示在窗体设计器中,这与运行时一样。所有这些都构建在 IDesigner 基础上。通常,控件的设计器会控制其正在设计的控件的 WindowProc(取自 System.WinForms.Design.ControlDesigner 的设计器可以通过简单地覆盖 WindowProc 方法来完成此项任务)来执行诸如命中测试这类的复杂任务。然而,对于大多数组件来说,内置的默认设计器应该就足够了。
访问设计器服务和基础结构
VS .NET 中的 .NET Framework 设计器开发环境的许多服务和基础结构组件简化了复杂的操作,使设计器各部分之间可以互通状态信息。这些服务总是通过使用 GetServiceObject 方法的 IServiceObjectProvider 来进行访问。这是一些有趣的设计器服务列表:
类型
|
说明 |
IDesignerHost |
与任何顶级设计器相关联的主类。提供方法以添加服务,创建和安放组件,删除组件,以及将操作批处理化。 |
IComponentChangeService |
在添加、删除、重命名、或修改组件时提供通知。 |
ISelectionService |
设置或获取当前在设计器中选择的项目。 |
IToolboxService |
允许检查或修改工具框上的项目以及它们的选择状态等等。 |
IUndoService |
提供创建操作的撤销/重做单元的工具,并管理撤销/重做栈。 |
IHelpService |
允许设置帮助主题或调用帮助项目 |
IMenuCommandService |
允许处理设计器菜单命令和动词。 |
IReferenceService |
将设计器引用映射到对象。例如,命名组件“button 1”。 |
IDesignerHost 是 VS .NET 中所有设计器的基础。IDesignerHost 也是一个 IServiceObjectProvider,它可以动态地添加或删除服务。它还提供方法来创建组件以确保正确安放组件。这里是一个使用 IDesignerHost 创建组件的示例:
Public class MyDesigner : IDesigner {
// .
Private void OnSurfaceDoubleClick(object s, EventArgs e) {
IDesignerHost host =
(IDesignerHost)this.Component.Site.GetServiceObject(typeof(IDesignerHost));
If (host != null) {
Object newComponent = host.CreateComponent(typeof(MyComponent));
DoSomethingInterestingWithComponent(newComponent);
}
}
// ...
为组件授予许可证
在 .NET Framework 的可扩展模型中,授予许可证的体系结构也可以扩展。为了简便使用,框架定义了一个内置的、标准许可证授予方法,以控制组件是否许可在设计时使用,但开发人员可以用任何他们认为恰当的方式替换此方案。
// 向控件添加 LicenseProviderAttribute。
LicenseProvider(typeof(LicFileLicenseProvider))]
public class MyControl : RichControl {
// 创建新的空许可证。
private License license = null;
public MyControl () {
// 向控件的构建器添加验证。
license = LicenseManager.Validate(typeof(MyControl), this);
// 执行其它实例化任务... }
public override void Dispose() {
if (license != null) {
license.Dispose();
license = null;
}
}
protected override void Finalize() {
Dispose();
base.Finalize();
}
}
此示例使用内置的许可支持来授予许可证。LicFieLicenseProvider 只是在与类组合体相同的目录中,寻找一个名为 <classname>.lic 的文件,其中 classname 是类型全名。String 类型的名称是 System.String.lic。该文件包含字符串“System.String 是一个许可的组件”。如果找到该文件,LicenseManager.Validate 会返回一个 License 对象,它将与类实例一同分配。
实现您自己的许可证方案也很容易。只需创建您自己的源于 LicenseProvider 的类并实现自己的 GetLicense 方法。您可能要实现一个基于注册表的许可方案,其中许可的设计时组件都在注册表中有一个条目:
public class RegistryLicenseProvider: LicenseProvider {
public override License GetLicense(
LicenseContext context,
Type type,
object instance,
bool allowExceptions) {
RegistryKey licenseKey = Registry.LocalMachine.
OpenSubKey("Software\\MyCompany\\ComponentLicenses");
if (context.UsageMode == LicenseUsageMode.Designtime) {
if (licenseKey != null && licenseKey.GetValue(type.FullName) != null) {
return new RegLicense(this, type);
}
if (allowExceptions) {
throw new LicenseException(type, instance,
"Couldn''t get design-time license for ''"" +
type.FullName + "''");
}
return null;
}
else {
return new RuntimeRegLicense(this, type);
}
}
private class RuntimeRegLicense : License {
public string LicenseKey {
get {
return type.FullName;
}
}
public override void Dispose() {
}
}
private class RegLicense : License {
private RegistryLicenseProvider owner;
private Type type;
public RegLicense(RegistryLicenseProvider owner, Type type) {
this.owner = owner;
this.type = type;
}
public string LicenseKey {
get {
return type.FullName;
}
}
public override void Dispose() {
}
}
[LicenseProvider(typeof(RegistryLicenseProvider))]
public class MyControl : Control {
}
使用该许可证的组件将在注册表的以下位置中有一个条目:
HKEY_LOCAL_MACHINE\Software\MyCompany\ComponentLicenses
<Type full name>="true"
结论
以管理代码编写控件远远优于传统的 C++/COM 方法。Mircosoft 对从琐碎的编程到 C,从通用语言运行时到重组的 Visual Basic 语言进行根本性的工程改进,使可能遇到的故障大大减少。Mircosoft .NET Framework 是用这些技术与原理来开发的第一个代码集,对集成设计器的支持是这一方法的关键。