接下来的任务是与 XAML 交互,以便可通过编程更改标题文本,并启动上文所述的可变大变小的动画。最简单的交互方法是以声明的方式将处理程序分配给 XAML 中定义对象的事件。在本例中,用户单击球体会出现变大变小的动画——而运行哪个动画取决于球体的当前大小。
首先,我将处理程序添加到定义球体的 Ellipse 的 MouseLeftButtonDown 事件,然后在页面上放置用 JavaScript 编写的相应方法。按类似的方式处理 TextBlock 的 Loaded 事件,以将球体上显示的文本设置为自定义的字符串。添加到 XAML 的处理程序与下面的形式类似:
手动将 Silverlight 内容集成到 ASP.NET 页面的所有步骤就是这些。在浏览器中显示此页时,标题文本会显示“My Growing Sphere”。当用户第一次单击球体时,球体变大,第二次单击,球体变小。如您所见,将 Silverlight 内容集成到 ASP.NET 页面相对容易,而且易于操作,因为其整个对象模型都向页面上的脚本公开。
但是,对于诸如球体之类的简单元素,您可能希望将其打包成自定义控件,这样只需将其控件拖放到页面的设计图面,就可以集成到应用程序。这便是封装 Silverlight 发挥作用的地方。
封装 Silverlight 的任务从创建新自定义 ASP.NET 控件开始:
namespace
MsdnMagazine
{
public
class
SilverlightSphere : Control
{
}
}
在本例中,我从 System.Web.UI.Control 派生。我选择不从 WebControl 派生是因为该类定义的其他样式属性不适合此控件。
接着,需要呈现手动包含的 JavaScript 处理程序。在 ASP.NET 控件中集成 JavaScript 的最简单方法是,先定义一个 JavaScript 文件,然后将其作为资源嵌入编译好的程序集中。将两个事件处理程序放在单独的 JavaScript 文件(称为 SilverlightSphere.js)中可以实现这一点:
//
File: SilverlightSphere.js
function onSphereButtonDown(sender, args)
{
//
Same implementation as before
}
function onTextLoaded(sender, args)
{
//
Same implementation as before
}
接着,将新 SilverlightSphere.js 文件和原 Silverlight.js 文件作为资源嵌入控件项目。这使控件可以作为单个程序集部署,而没有任何依赖文件。您还可以将 XAML 文件作为资源嵌入,并使用 ASP.NET 中提取嵌入资源的 WebResource.axd 处理程序机制引用该文件和这两个 JavaScript 文件。
在 Visual Studio® 中针对这三个文件将“生成操作”设置为“嵌入的资源”(命令行编译器的 /res)后,再使用程序集级属性 System.Web.UI.WebResource 授予这些资源的权限,以便得到 WebResource.axd 的支持并关联用于响应的 MIME 类型。使用下面的声明可实现这一点(其中 SlSphere 是控件项目的名称):
[assembly: WebResource("SlSphere.SilverlightSphere.js",
"text/javascript")]
[assembly: WebResource("SlSphere.Silverlight.js",
"text/javascript")]
[assembly: WebResource("SlSphere.Sphere.xaml", "text/xml")]
现在,JavaScript 文件作为资源被编译成程序集。因此,现在可以使用 ClientScriptManager 类(通过 Page.ClientScriptManager 访问)的 RegisterClientScriptResource 方法使呈现的页包含这些文件的引用。在从 Control 基类继承的 OnInit 方法的重写方法中对每个文件调用此方法:
protected
override
void
OnInit(EventArgs e)
{
Page.ClientScript.RegisterClientScriptResource(
this
.GetType(),
"
SlSphere.Silverlight.js
"
);
Page.ClientScript.RegisterClientScriptResource(
this
.GetType(),
"
SlSphere.SilverlightSphere.js
"
);
base
.OnInit(e);
}
该控件最后且最重要的部分是实现虚拟 Render 方法。在此方法中必须完成两个任务。首先,需要呈现
标记以作为承载 Silverlight 插件的 HTML 元素。其次,必须呈现嵌入的脚本以创建 Silverlight 插件。
调用 ClientScriptManager 类的 GetWebResourceUrl 方法可生成对 XAML 文件的引用。这可确保使用作为资源嵌入到程序集中的 XAML 文件的内容初始化 Silverlight 控件。最后,需要为 Silverlight 插件本身指定唯一的标识符。在本例中,使用控件的 ID (this.ClientID) 并连接“_ctrl”作为其标识符(请参见图 5)。
现在即拥有一个服务器端控件(以及嵌入的 JavaScript 和 XAML 资源),该控件封装在 ASP.NET 页面上部署 Silverlight 所需的各部分。可以在页面上通过下列声明创建此控件的实例:
<%
@ Register Assembly="SlSphere" Namespace="MsdnMagazine"
TagPrefix="csc"
%>
<%
@ Register Assembly
=
"
SlSphere
"
Namespace
=
"
MsdnMagazine
"
TagPrefix
=
"
csc
"
%>
...
<
csc:SilverlightSphere
ID
="_silverlightSphere"
runat
="server"
/>
现在我能达成颇为引人注目的成果——任何 ASP.NET 应用程序都可以轻松集成 Silverlight 中承载的变大变小球体,而无需考虑承载 Silverlight 内容的详细信息。
当然,使用高级 XAML 呈现,可以生成更引人入胜的复杂控件。但是,当开始考虑可能要添加到控件的功能时,很快便会遇到如何使用服务器控件中的属性值处理 XAML 的问题。例如,假定要公开控件中的 Title 属性,并将其映射到 XAML 文件中 titleText 元素的 Text 属性。通过当前实现显然无法做到这一点。
当前实现的另一个缺点是 XAML 被固定为嵌入的资源。尽管这种实现方式便于部署,但 Silverlight 和 XAML 将行为与设计分离的优点却使 XAML 可被替换。可以在控件上定义一个 XamlUrl 属性来重写嵌入的 XAML 内容。此方法实现起来不难,但会在 XAML 中公开 JavaScript 处理程序的名称时带来问题,这就要求设计者使用最新版 XAML。
问题的症结是确定如何将服务器端属性引入客户端脚本。很幸运,ASP.NET AJAX 扩展提供了一种简洁的方法,即通过 System.Web.Extensions 程序集定义的 IScriptControl 接口将服务器端控件与客户端属性关联起来。此接口定义了两个方法:GetScriptReferences 和 GetScriptDescriptors。实现这些方法并将其与页面的 ScriptManager 控件关联后,它们会分别生成对 JavaScript 文件的引用和声明 JavaScript 类的实例:
public interface IScriptControl {
IEnumerable
GetScriptDescriptors();
IEnumerable
GetScriptReferences();
}
您可以将描述符视作服务器端控件的客户端代理,其中含有初始化为服务器控件定义的属性。这会将 Silverlight 和 JavaScript 处理程序可使用的服务器控件属性和客户端属性联系起来。描述符还提供了一个简洁的范围机制,用于隔离命名空间和类下所有控件的客户端功能(ASP.NET AJAX 客户端库至少在 JavaScript 中定义了命名空间和类)。
当生成承载 Silverlight 内容的 ASP.NET AJAX 控件时,首先创建客户端类定义,其中含有定义好的与 Silverlight 控件交互的方法和属性。可以将 Title 和 XamlUrl 属性都添加到此控件,因此开始时先定义带有这些属性的类,将类命名为 MsdnMagazine.SilverlightAjaxSphere:
// File: SilverlightAjaxSphere.js
Type.registerNamespace('MsdnMagazine');
MsdnMagazine.SilverlightAjaxSphere = function(element) {
MsdnMagazine.SilverlightAjaxSphere.initializeBase(
this, [element]);
this._title = null;
this._xamlUrl = null;
}
现在需要为类的原型填充方法,开始初始化(请参见图 6)。与将脚本嵌入
元素(就像在第一个控件实现中那样)不同,这次是在客户端类的初始化方法中创建 Silverlight 插件。请注意,只有在调用此方法之前,用服务器端值填充类中定义的 _xamlUrl 属性,才有可能创建。还可以访问
的标识符,它是由服务器端控件使用从 Sys.UI.Control 基类继承的 get_id 方法呈现的。
当在类中调用初始化方法时,即会创建 Silverlight 插件,但尚未对 XAML 求值。因此,我使用 Sys.Silverlight 的 createObject 方法中定义的 onLoad 事件处理程序指定加载 XAML 后调用的回调——这便可以绑定事件处理程序并初始化各 XAML 元素的属性。此处理程序(我已将其称为 _onXamlLoaded)以引用的方式传递给根 Silverlight 元素,可以使用此元素检索 XAML 中的命名元素。
当与命名 XAML 元素交互时,必须保持耐心,因为这要求必须检查这些元素是否存在,然后才能真正修改其内容或绑定事件处理程序。如果缺少元素且元素对控件的操作不重要(就像控件的 XAML 中的 titleText 元素对控件不重要一样),请跳过该元素的初始化步骤。通过这种方式,设计者可以选择删除某些元素,而控件仍正常工作。
类的其余实现是定义标题和 xamlUrl 的属性访问器,并实现 _onSphereButtonDown 函数以启动变大和变小动画,这与前面类似。但是,此时方法是在类中封装的。
客户端脚本的最后步骤是注册新类,指定基类为 Sys.UI.Control。
余下的任务是生成通过 IScriptControl 接口使用此类的服务器端控件(请参见图 7)。与客户端类相似,服务器控件公开两个属性:Title 和 XamlUrl。(ViewState 支持这些属性,因为 ViewState 总是支持所有服务器端控件属性。)我将 XamlUrl 属性初始化为 URL,使用上个控件实现中使用过的同一个 GetWebResourceUrl 方法引用嵌入的 XAML 文件。通过在 OnInit 的重写中设置此属性,确保该属性默认设为此 URL,但客户端指定的任何重写版本将始终优先,因为状态在生命周期的后期加载。
接下来的两个方法 GetScriptDescriptors 和 GetScriptReferences 都包含 IScriptControl 接口的实现。因为将在程序集中将 JavaScript 文件作为资源嵌入,所以使用 ScriptReference 的重载构造函数,该函数使用资源名称和程序集名称。这会生成对嵌入脚本资源的引用,其方式与在前面的控件实现中生成对 RegisterClientScriptResource 的实现大致相同。GetScriptDescriptors 是创建使用服务器端控件中属性初始化客户端 MsdnMagazine.SilverlightAjaxSphere 类的实例关键。
ScriptControlDescriptor 类采用客户端类的名称和其构造函数中关联客户端元素的 ID。然后,使用类的 AddProperty 方法指定新客户端类的每个属性的初始值。通过传递控件中的当前属性值,将确保使用控件中的当前属性值创建客户端类。
仍需要最后关注 OnPreRender 的重写,在此重写中,使用页面上的当前 ScriptManager(若无 ScriptManager 将引发异常)以及脚本描述符将控件注册为脚本控件。这会导致 ScriptManager 调用控件上 IScriptControl 的两个方法获取脚本引用并创建初始的客户端类。Render 方法与前面的控件实现相同,仅创建
元素充当 Silverlight 插件的宿主。
现在就得到了封装 Silverlight 的完整的服务器端控件。在此实现中,控件公开用于初始化 XAML 元素的属性(或如需要,甚至替换整个 XAML)。可以在页面上通过下列声明创建新控件的实例,且标题文本设置为自定义字符串:
<%
@ Register Assembly
=
"
SlAjaxSphere
"
Namespace
=
"
MsdnMagazine
"
TagPrefix
=
"
sas
"
%>
...
<
sas:SilverlightSphere
ID
="_silverlightSphere"
runat
="server"
Title
="My custom title!"
/>
2007 年 5 月发布的 ASP.NET Futures 版本中引入了两个新 ASP.NET AJAX 控件:asp:Xaml 和 asp:Media。(请注意,asp:Media 继承自 asp:XAML。)这两个控件都提供自己的 Silverlight 封装,而且这两个控件生成的方式与前文提供的示例大致相同。实际上,我生成控件的灵感就来自这两个控件。
asp:Xaml 控件可以将 XAML 文件与控件关联。它包含 Silverlight 插件创建的各种属性,这些属性处理创建插件和加载 XAML 文件的所有细节。例如,可以通过下列声明,使用 XAML 文件显示 Sphere.xaml 文件:
<
asp:Xaml
runat
="server"
ID
="_sphereXaml"
Windowless
="true"
Width
="300px"
Height
="300px"
XamlUrl
="~/Sphere.xaml"
/>
使用 asp:Media 控件可以轻松将媒体内容(视频或音频)嵌入 ASP.NET 页面,并在创建封装 Silverlight 的 ASP.NET 控件时对这些功能实实在在地加以展示。Media 控件其中一个吸引人的功能是它能提供八种不同的外观(XAML 文件),您可以选择这些外观来呈现媒体,或者可以设计自己的外观。图 8 显示了一个使用 Expression 外观播放视频的 Media 控件示例。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
使用 asp:Media 控件可以轻松将媒体内容(视频或音频)嵌入 ASP.NET 页面,并在创建封装 Silverlight 的 ASP.NET 控件时对这些功能实实在在地加以展示。Media 控件其中一个吸引人的功能是它能提供八种不同的外观(XAML 文件),您可以选择这些外观来呈现媒体,或者可以设计自己的外观。图 8 显示了一个使用 Expression 外观播放视频的 Media 控件示例。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
2007 年 5 月发布的 ASP.NET Futures 版本中引入了两个新 ASP.NET AJAX 控件:asp:Xaml 和 asp:Media。(请注意,asp:Media 继承自 asp:XAML。)这两个控件都提供自己的 Silverlight 封装,而且这两个控件生成的方式与前文提供的示例大致相同。实际上,我生成控件的灵感就来自这两个控件。
asp:Xaml 控件可以将 XAML 文件与控件关联。它包含 Silverlight 插件创建的各种属性,这些属性处理创建插件和加载 XAML 文件的所有细节。例如,可以通过下列声明,使用 XAML 文件显示 Sphere.xaml 文件:
<
asp:Xaml
runat
="server"
ID
="_sphereXaml"
Windowless
="true"
Width
="300px"
Height
="300px"
XamlUrl
="~/Sphere.xaml"
/>
使用 asp:Media 控件可以轻松将媒体内容(视频或音频)嵌入 ASP.NET 页面,并在创建封装 Silverlight 的 ASP.NET 控件时对这些功能实实在在地加以展示。Media 控件其中一个吸引人的功能是它能提供八种不同的外观(XAML 文件),您可以选择这些外观来呈现媒体,或者可以设计自己的外观。图 8 显示了一个使用 Expression 外观播放视频的 Media 控件示例。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
使用 asp:Media 控件可以轻松将媒体内容(视频或音频)嵌入 ASP.NET 页面,并在创建封装 Silverlight 的 ASP.NET 控件时对这些功能实实在在地加以展示。Media 控件其中一个吸引人的功能是它能提供八种不同的外观(XAML 文件),您可以选择这些外观来呈现媒体,或者可以设计自己的外观。图 8 显示了一个使用 Expression 外观播放视频的 Media 控件示例。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。
<
asp:Media
runat
="server"
ID
="_butterflyVideo"
MediaSkin
="Expression"
MediaUrl
="~/Butterfly.wmv"
另一种创建自己处理过的 Silverlight 服务器端控件的方法是直接从 asp:Xaml 控件派生,然后用属性和方法添加控件,方法大致与上文第二种控件实现方法类似。在本期专栏的代码下载中,我包含了另一个继承自 asp:Xaml 的 SilverlightSphere 控件版本。