Silverlight 控件 -可重用 XAML 之路






4.72/5 (28投票s)
一篇关于 Silverlight 控件 -
引言
Silverlight 就像狂野的西部。
每个人都随心所欲地编码。每一个新发现都像处女地,一切都散发着新车的味道。
目前大多数 Silverlight 示例都是基于过程的,而不是面向对象的。
我们都沉浸在制作炫酷样本中,以至于忘记了那些奇怪的、像外星人一样的概念:“不要重复自己”和“Blob”/“臃肿对象”/“上帝对象”。
不要重复自己 (DRY,也称为一次且仅一次) 是一种旨在减少重复(尤其是在计算中)的流程哲学。该哲学强调信息不应重复,因为重复会增加更改的难度,可能会降低清晰度,并导致不一致的机会。 [维基百科]
结构化编程的基本思想是将一个大问题分解成许多小问题(分而治之),并为每个小问题创建解决方案。基于上帝对象的代码不遵循这种方法。相反,程序的大部分整体功能被编码到一个单一的对象中。因为这个对象包含如此多的数据并拥有如此多的方法,所以它在程序中的角色变得上帝般(包罗万象)。 [维基百科]
作为开发人员,我们没有获得关于如何开发可重用 Silverlight 对象的适当工具和说明。
让我们看看现在是否可以开始。
我们将看到目前有四种方法试图实现可重用 Silverlight 对象。
之后我将提供第五种更面向对象的方法。
在未来的文章中,我将展示如何在数据驱动的 Silverlight 应用程序中使用此技术。
创建可重用 Silverlight 对象的第一种方法
在客户端基于某些 XAML 脚本使用“content.createFromXaml(Xaml)”JavaScript
让我们来看看Richard Z 著名的 Silverlight 图表示例。
这是一个用 Silverlight 构建的非常时尚的图表。
您可以看到,将鼠标指针悬停在上面会使列的颜色变为绿色。
让我们刷新页面(通过按 F5 或点击“新图表”按钮)。
这里有一些事情变得越来越清楚(除了 Richard 知道如何制作出色的图表这一事实之外)——这不是一个静态图像!
这实际上加载数据并重新构建 XAML 代码!
让我们看看它是如何完成的
for (i = 1; i < 21; ++i)
{
values[i] = Math.min(260, //10 + 280 * (group + 1) / colours.length,
Math.max(10, // + 280 * group / colours.length,
values[i-1] - 70 + Math.random()*141));
var x = i * 40;
// Draw the bar
var bar = control.content.createFromXaml(
'<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
+ ' Canvas.Top="' + (304 - values[i-1]) + '" Height="'
+ (values[i-1] + 5) + '"'
+ ' RadiusX="2" RadiusY="2">'
+ ' <Rectangle.Fill>'
+ ' <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
+ ' </Rectangle.Fill>'
+ ' <Rectangle.RenderTransform>'
+ ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
+ values[i-1] + '" ScaleY="0.0" />'
+ ' </Rectangle.RenderTransform>'
+ '</Rectangle>'
);
_sl.children.add(bar);
}
此代码获取硬编码的 XAML,根据 JavaScript 可用的数据更改多个属性(例如条形图的Height
和Canvas.Left
),并使用createFromXaml
根据此动态 XAML 标记代码创建 XAML 控件。
优点
- 它是动态的,意味着相关数据会设置各种显示相关的属性。
缺点
- XAML 是硬编码的。Expression Blend 2 无法直接获取此 XAML 代码并开始编辑它。
- 是时候提一下 JavaScript 不支持编译或可靠的单元测试支持了。这简直是陷入撇号维护地狱(这是 DLL 地狱之前我们遇到的地狱)的必经之路。
创建可重用 Silverlight 对象的第二种方法
在客户端基于某些 XAML 脚本使用“XmlReader.Read(Xaml)” .NET 代码
使用此技术的一个著名示例是 Silverlight 中用于数据绑定的流式模板处理 (代码)。它基本上与上一个示例相同,只是现在它必须是 Silverlight 1.1 才能运行。让我们看一些代码
string templateXml =
@"<Canvas
xmlns=""http://schemas.microsoft.com/client/2007""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
Width=""960"" Height=""150"" x:Name=""$(cnvItem)$"" Opacity=""1""
MouseLeftButtonDown=""DoClick"">
<TextBlock x:Name=""$(hdln)$"" Width=""576"" Height=""40""
Canvas.Left=""376"" Canvas.Top=""8""
FontFamily=""Tahoma"" FontSize=""24""
FontWeight=""Normal"" Foreground=""#FFFFFFFF""
Text=""$/title$"" TextWrapping=""Wrap""/>
<TextBlock x:Name=""$(detl)$"" Width=""576""
Height=""96"" Canvas.Left=""376""
Canvas.Top=""48"" FontFamily=""Tahoma""
FontSize=""14"" FontWeight=""Normal""
Foreground=""#FFFFFFFF""
Text=""$/description$""
TextWrapping=""Wrap""/>
</Canvas>";
XmlReader template = XmlReader.Create(new StringReader(templateXml));
string xamlResult = rssItem.ProcessTemplate(template, AllRssItemTemplates);
Visual v = (Visual)XamlReader.Load(xamlResult);
this.Children.Add(v);
有硬编码的 XAML,其中包含令牌 $token$,并且对于每个 RSS 帖子,它会处理模板一次,然后使用 XmlReader.Load(xaml)
加载最终的 XAML。XAML 中的令牌随后通过类似 xpath 的半机制进行处理,该机制将其内容替换为升序 ID 号和 RSS 帖子中的内容。由于它们都是这种方式,并且第一种方式是以模板客户端为中心的,因此它们的优缺点是相同的。
创建可重用 Silverlight 对象的第三种方法
将我们的 Silverlight 控件的源属性设置为呈现现有 XAML 文件的服务器端 .NET 页面/HttpHandler。
Rob Conery 的博客上可以找到这种技术的一个很好的例子:Silverlight 第 2 天:创建一个数据驱动的控件。
//contains calls to silverlight.js, example below loads Page.xaml
function createSilverlight(source, elementID)
{
Sys.Silverlight.createObjectEx({
source: source,
parentElement: document.getElementById(elementID),
id: "SilverlightControl"+elementID,
properties: {
width: "100%",
height: "100%",
version: "0.95",
enableHtmlAccess: true
},
events: {}
});
}
function loadAG(){
createSilverlight("Menu.xaml","Menu");
createSilverlight("XAMLWelcome.aspx","Welcome");
}
此代码有一个服务器端 ASPX 页面,其中包含像普通 ASP 脚本一样运行并替换值的硬编码 XAML。在这个例子中,我们实际上可以使用 ASP 3 脚本来呈现 XAML 代码。但在更复杂和实际的例子中,我们将需要 .NET 的强大功能。
优点
- 动态
- 可以完全访问 .NET Framework(或我们将使用的任何服务器端技术),这比 Silverlight 1.1 CLR 或 JavaScript 要好得多。
缺点
- 这实际上是将 XAML 代码硬编码到 ASP.NET 中。这不可能成为软件发展的未来。
- 无法将 XAML 代码加载到 Designer Blend 中。
创建可重用 Silverlight 对象的第四种方法
使用某种生成工具呈现 XAML 代码,该工具使用 XML 注释作为指令
这是一个有趣的。它使用嵌入在 XAML 中的 XML 注释来运行某些面向生成器的命令,这些命令有助于替换 XML 属性或复制现有的 XML 元素。以下是此类实现的一个很好的示例:Silverlight 控件的自动代码生成
这将进入一个自定义生成器应用程序,该应用程序生成最终的 XAML。在上面的示例中,我们可以看到三种类型的语句
<cc:Repeat>XXX<cc:RepeatEnd>
,它会复制其中的 XML 内容。<cc:Replace Attribute="XXX">NewValue</cc:Replace>
,它根据局部脚本变量更改前一个 XML 节点的 XML 属性。<cc:Evaluate>
和<cc:Declare>
,允许您声明一个局部脚本变量并更改其值。
优点
- 这实际上可以由 Expression Blend 加载。
- 我们试图实现的目标非常明确,这不仅仅是任何“在此处放置一些东西”的代码。它是“替换 XML 节点的属性”,这要好一些。
缺点
- 它是一个完整的编程语言,写在 XML 注释中。没有智能感知,没有编译,什么都没有。
总结
缺点
- 我们现在看到的大多数开发选项都不支持将 XAML 加载到 Expression Blend 中。
这对于这些选项来说几乎是死刑,因为没有一个头脑清醒的人会做这么多修复工作来回地将 XAML 提取到 Expression Blend 中。你必须疯两倍才能尝试编辑这种脆弱的 XAML-JavaScript/C#/ASP.NET 代码。正是这种类型的代码让我逃离 ASP 3 去了 ASP.NET 的乐土。从 learnasp.com 上的关于如何更新数据库的教程中复制。如果放置一个撇号错误,你将调试它一辈子。
-
我们仍然不能在两个不同的文件中使用相同的 XAML 代码。假设我们有一个标准的 XAML 按钮,我们不会在整个系统中重复它,我们必须使用一些 myButton.Xaml 文件,而这里不支持任何格式。
-
这些是在一种极度不可扩展的格式中获取某种可扩展标记的技巧。
-
回到过程式编程。我们又回到了创建大量代码块,这些代码块生成的标记只会变得越来越大、越来越混乱,直到无法维护。
优点
- 它确实有效。你不能对此嗤之以鼻,这些都是真正聪明的人发明的,他们必须做些什么,而且它确实有效。
创建可重用 Silverlight 对象的第五种方法
使用“一个 .NET/JavaScript 类 - 一个 XAML 文件”项目方法论
这正是我们今天在 ASP.NET 中所拥有的。我们有一个标记文件和一个代码隐藏文件。
我喜欢这种模式,我用这种模式做得最好,它可扩展、可维护、面向对象。
像任何面向对象编程一样,这种模式很容易被滥用。这是唯一能提供给我们“KISS”(保持简单直接)编码、提供“YAGNI”(你不需要它)、并期望“DRY”(不要重复自己)的模式。
让我们以 Jelly 条形图 示例为例并重写它。本文中编写的所有代码均可在此处下载 下载。
我看到的是——在示例中有一个重复的对象(在示例页面上使用“查看源”来亲自查看)。
这个对象就是我们看到的蓝色/绿色条形图及其所有 GUI 逻辑。
有很多东西要绘制。有蓝色图表,鼠标悬停时,我们需要改变颜色并显示工具提示;鼠标离开时,我们需要重新绘制蓝色条形图并隐藏工具提示。但是,每一组 Silverlight XAML 控件(矩形和文本块)和 XAML 动画(显示/隐藏工具提示,条形图着色)都只是一个“条形图对象”。
那么,假设我们已经重构了条形图对象,剩下什么呢?
如果我们去掉图表条,剩下的是主要的 Silverlight 画布。它有 1) 一个漂亮的标题,2) 一个刷新按钮,3) 中间有一个特殊的画布。中间的特殊画布用于将我们的条形图对象初始化到其中。
步骤 #1 - XAML:创建 XAML 文件
让我们将 Bar 对象重构到它自己的 XAML 文件中。所以我们首先应该做的是在 Expression Blend 中创建一个新的空 XAML 文件
这是我们需要转换为 XAML 的第一段 JavaScript
for (i = 1; i < 21; ++i)
{
values[i] = Math.min(260, //10 + 280 * (group + 1) / colours.length,
Math.max(10, // + 280 * group / colours.length,
values[i-1] - 70 + Math.random()*141));
var x = i * 40;
// Draw the bar
var bar = control.content.createFromXaml(
'<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35)
+ '" Width="30"'
+ ' Canvas.Top="' + (304 - values[i-1]) + '" Height="'
+ (values[i-1] + 5) + '"'
+ ' RadiusX="2" RadiusY="2">'
+ ' <Rectangle.Fill>'
+ ' <SolidColorBrush Name="' + id('brushB',i)
+ '" Color="#7F004296" />'
+ ' </Rectangle.Fill>'
+ ' <Rectangle.RenderTransform>'
+ ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
+ values[i-1] + '" ScaleY="0.0" />'
+ ' </Rectangle.RenderTransform>'
+ '</Rectangle>'
);
_sl.children.add(bar);
所以首先让我们理解 value[i]
和 x
的含义。
values[i]
是当前条形图的高度,最大高度为 300 像素x
是当前条形图距离画布左侧的距离(即Canvas.Left
)
现在,我们希望将所有 GUI 逻辑从 XAML 中移除到一个新类中。我们将这样做,并且可以在任何计算值的位置放置“0.0”。然而,我们确实希望获得一些 Expression Blend 支持,以便我们可以看到一些东西,所以我们假设我们的“模板”条形图大约有 150 像素。
让我们将这个 JavaScript 重构为普通的 XAML
// Draw the bar
var bar = control.content.createFromXaml(
'<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
+ ' Canvas.Top="' + (304 - values[i-1]) + '" Height="'
+ (values[i-1] + 5) + '"'
+ ' RadiusX="2" RadiusY="2">'
+ ' <Rectangle.Fill>'
+ ' <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
+ ' </Rectangle.Fill>'
+ ' <Rectangle.RenderTransform>'
+ ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
+ values[i-1] + '" ScaleY="0.0" />'
+ ' </Rectangle.RenderTransform>'
+ '</Rectangle>'
);
_sl.children.add(bar);
变成
<!-- Draw the bar -->
<Rectangle x:Name="barB" Canvas.Left="5" Width="30"
Canvas.Top="154" Height="150"
RadiusX="2" RadiusY="2">
<Rectangle.Fill>
<SolidColorBrush x:Name="brushB" Color="#7F004296" />
</Rectangle.Fill>
<Rectangle.RenderTransform>
<ScaleTransform x:Name="barscaleB" CenterY="150.0" ScaleY="0.0" />
</Rectangle.RenderTransform>
</Rectangle>
我们将逐行查看我们更改了什么。
'<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
+ ' Canvas.Top="' + (304 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
+ ' RadiusX="2" RadiusY="2">'
我们将删除所有 ID 构建函数,只在那里放置一个普通的 x:Name
。
我们说过我们会将所有的 x
值替换为 40,所以 Canvas.Left
是 5 (40 - 35 = 5)。
我们说过当前条形图的高度为 150 像素,所以 Canvas.Top
为 154 像素 (304 - 150 = 154)。Height
也将变为 155 像素 (150 + 5)。
+ ' <Rectangle.Fill>'
+ ' <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
+ ' </Rectangle.Fill>'
+ ' <Rectangle.RenderTransform>'
+ ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="'
+ values[i-1] + '" ScaleY="0.0" />'
+ ' </Rectangle.RenderTransform>'
+ '</Rectangle>'
变成
<Rectangle.Fill>
<SolidColorBrush x:Name="brushB" Color="#7F004296" />
</Rectangle.Fill>
<Rectangle.RenderTransform>
<ScaleTransform x:Name="barscaleB" CenterY="150.0" ScaleY="0.0" />
</Rectangle.RenderTransform>
</Rectangle>
这里也是一样,我们将所有 ID 更改为静态 X:Name
,并根据“x = 40, values[i-1] = 150”写入模拟值。
我们将以相同的方式进行所有剩余的转换。让我们再看一个
////////////////
// This is the text block that shows up inside the bubble
//
var yInt = parseInt(values[i-1]);
var textBlock = control.content.createFromXaml(
'<TextBlock Name="' + id('bubbleText',i) + '" FontSize="11" Text="' + yInt + '"'
+ ' Canvas.Left="' + (x - 32) + '" Canvas.Top="'
+ (273 - values[i-1] + 4) + '"'
+ ' Canvas.ZIndex="3" Opacity="0" Foreground="#FFFFFF">'
+ ' <TextBlock.RenderTransform>'
+ ' <ScaleTransform CenterX="12" CenterY="24" ScaleX="1.5" ScaleY="1.5" />'
+ ' </TextBlock.RenderTransform>'
+ '</TextBlock>'
);
_sl.children.add(textBlock);
变成
<!-- This is the text block that shows up inside the bubble -->
<TextBlock x:Name="bubbleText" FontSize="11" Text="150"
Canvas.Left="18" Canvas.Top="127"
Canvas.ZIndex="3" Opacity="0" Foreground="#FFFFFF">
<TextBlock.RenderTransform>
<ScaleTransform CenterX="12" CenterY="24" ScaleX="1.5" ScaleY="1.5" />
</TextBlock.RenderTransform>
</TextBlock>
id
变为静态x:Name
yint
被替换为 150Canvas.Left
是 8 (40 - 32 = 8)Canvas.Top
是 127 (273 - 150 - 4 = 127)
所以我们假设我们已经将我们整个 JavaScript Bar Dynamic XAML 转换为 Bar.Xaml 文件。
如果我们在 Blend Expression 中打开 XAML 文件,我们会看到这个
我们得到的只是一个小小的白色画布。
让我们看看 Expression 在屏幕左侧是否有任何有趣的信息要告诉我们……
嗯……这很有趣,页面上有四个元素(barB
、bar
、bubble
和bubbleText
),但我们一个也看不到!
让我们选择它们,看看 Expression Blend 能否向我们展示它们的位置。
好的,屏幕上的每条蓝线都是一个元素的位置。但我们仍然无法实际看到它,让我们尝试运行我们的一个时间线,看看会得到什么……
现在我们可以看到条形图了!它可能只在加载后才显示……但是我们的 TextBubble
在哪里呢?
根据我们启动的动画故事板,我们的 Bar 对象会显示不同的项目。
步骤 #2 - C#:为 Silverlight 1.1 设置 XAML 代码隐藏(Silverlight 1.0 紧随其后)
让我们创建一个新的 Silverlight 项目。

我们还将创建一个 Silverlight 类库。
这就是我们的解决方案资源管理器的样子
我们将在 Silverlight 1.1 项目中创建一个名为 Bar 的新 Silverlight 用户控件。
我们得到一个空的 XAML 文件和 C# 代码隐藏
我们将把我们的 Bar.Xaml 文件复制到这个新的空 XAML 文件中。
我们还将从我们的 Silverlight 项目添加对 Silverlight 控件库的引用。
让我们来看看 bar.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightOOControlsLibrary
{
public class Bar : Control
{
public Bar()
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
}
}
}
这很简单,只有两行代码连接了 XAML 文件和 CS 文件。我们必须更改 Bar.Xaml 文件中元素的内部 ID,以避免 ID 冲突。
public Bar()
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
this.InitializeFromXaml(originalXaml);
}
变成
public Bar() : this("Bar")
{
}
public Bar(string ID)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
this.InitializeFromXaml(originalXaml);
}
现在我们将根据我们刚刚获得的 ID 字符串更改所有 XAML 元素的内部 x:Name
属性。
public class Bar : Control
{
public Bar() : this("Bar")
{
}
public Bar(string ID)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("x:Name=\"",
string.Format("x:Name=\"{0}_", ID));
this.InitializeFromXaml(originalXaml);
}
}
ID 也可以由控件层次结构决定,就像 ASP.NET 所做的那样,但这超出了这篇已经很长的文章的范围。
public Bar(string ID)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("x:Name=\"",
string.Format("x:Name=\"{0}_", ID));
this.InitializeFromXaml(originalXaml);
this.ID = ID;
}
private string _ID;
public string ID
{
get { return _ID;}
private set { _ID = value; }
}
我们添加了一个 ID 属性来保留我们的控件 ID。初始化条形图时,我们还应该接收条形图的高度。这就是最终结果
public class Bar : Control
{
public Bar() : this("Bar", 150)
{
}
public Bar(string ID, int BarHeight)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("x:Name=\"",
string.Format("x:Name=\"{0}_", ID));
this.InitializeFromXaml(originalXaml);
this.ID = ID + "_";
this.BarHeight = BarHeight;
}
private int _BarHeight;
public int BarHeight
{
get { return _BarHeight; }
private set { _BarHeight = value; }
}
private string _ID;
public string ID
get { return _ID;}
private set { _ID = value; }
}
}
所以我们有一个 Silverlight 1.1 用户控件,它获取一些构造函数数据,并可以以特定的名称层次结构初始化 XAML 文件。
步骤 #2 JavaScript - 为 Silverlight 1.0 设置 XAML 代码隐藏
我们将在 Expression Blend 中创建一个新的 Silverlight 1.0 JavaScript 项目
我们将把新的 Bar.Xaml 文件复制到两个 JavaScript Silverlight 项目中。
到目前为止,您已经注意到我们正在以与 Silverlight 1.1 C# 用户控件并行的方式开发 Silverlight 1.0 JavaScript 控件。了解如何在 JavaScript 中轻松编写面向对象的代码,就像在 C# 中一样,这一点很重要。因此,第一步是创建一个名为 bar.xaml.js 的新 JavaScript 文件。
现在我们需要创建一个名为 Bar
的新类。
在继续之前,让我们回顾一下这个语法。
Bar = function()
{
}
前三行等同于 C# 中的类构造函数。
Bar.prototype =
{
}
在这三行中,我们将编写类的成员(属性、方法和事件)。我们已经知道 Bar
对象在其构造函数中接收两个参数。
Bar = function(ID, BarHeight)
{
}
Bar.prototype =
{
}
JavaScript 的问题在于它不是强类型语言,使用这个类的开发人员可能无法知道 ID 和 Parent 的类型。因此,我们将使用 VS2008 JavaScript 注释语法为这个类添加一些智能感知。
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" integer="true" />
}
Bar.prototype =
{
}
我们这里基本上是说,“任何使用这个构造函数的人请注意——ID
的类型是 String
,BarHeight
是一个 integer
”。以下是我们在 VS2008 中为此类构造函数获得的智能感知示例
现在让我们将类构造函数中获得的参数存储为类实例的私有变量。
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" intger="true" />
this._ID = ID;
this._barHeight = BarHeight;
}
Bar.prototype =
{
}
接下来,我们将确保内部变量具有一些 getter 属性。我们这样做有两个原因:首先,我们希望确保使用我们类的开发人员不会使用我们的“内部”变量(因为他们可以使用),其次更重要的是,我们目前在 VS2008 Beta2 中还没有“this._XXX”变量的智能感知。
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" intger="true" />
this._ID = ID;
this._barHeight = BarHeight;
}
Bar.prototype =
{
get_ID : function()
{
return this._ID;
},
get_barHeight : function()
{
return this._barHeight;
}
}
此外,我们还将 <returns />
XML JavaScript 注释添加到 getter 属性。
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" intger="true" />
this._ID = ID;
this._barHeight = BarHeight;
}
Bar.prototype =
{
get_ID : function()
{
/// <returns type="String" />
return this._ID;
},
get_barHeight : function()
{
/// <returns type="Number" intger="true" />
return this._barHeight;
}
}
我们添加了这些注释,以便使用我们类的用户(以及我们自己)知道这些函数的返回类型。
此时,我想为我们的项目添加 Silverlight JavaScript Intellisense。我将执行 Silverlight 1.0 完整 JavaScript Intellisense 文章中列出的五个步骤。
/// <reference path="intellisense.js" />
Bar = function(ID, BarHeight)
{
/// <param name="ID" type="String" />
/// <param name="BarHeight" type="Number" intger="true" />
this._ID = ID;
this._barHeight = BarHeight;
}
Bar.prototype =
{
get_ID : function()
{
/// <returns type="String" />
return this._ID;
},
get_barHeight : function()
{
/// <returns type="Number" intger="true" />
return this._barHeight;
}
}
我将节省一些时间,并预先告诉您,我们还需要在构造函数中获取两个额外的参数——Xlocation
,它将是条形图在 X 轴上的位置(Canvas.Left
),以及 Parent
,它是 Bar
的父 Canvas
。我们将添加这些,并附带 JavaScript XML 注释、内部变量和 getter 属性。
/// <reference path="intellisense.js" />
Bar = function(ID, Parent, BarHeight, XLocation)
{
/// <param name="ID" type="String" />
/// <param name="Parent" type="Canvas"/>
/// <param name="BarHeight" type="Number" integer="true" />
/// <param name="XLocation" type="Number">Canvas.Left</param>
this._ID = ID + "_";
this._parent = Convert.ToCanvas(Parent);
this._barHeight = BarHeight;
this._XLocation = XLocation;
}
Bar.prototype =
{
get_ID : function()
{
/// <returns type="String" />
return this._ID;
},
get_parent : function()
{
/// <returns type="Canvas" />
return this._parent;
},
get_barHeight : function()
{
/// <returns type="Number" integer="true" />
return this._barHeight;
},
get_XLocation: function()
{
/// <returns type="Number" />
return this._XLocation;
}
}
请注意,Parent
的类型为 Canvas
。Canvas
的定义是 Silverlight JavaScript Intellisense 的一部分。现在,如果我们需要,我们将获得 Canvas
的 Intellisense。(目前我们不需要,但很快就会需要)
我还将添加一个额外的内部变量,以便以后可以将其用作“快捷方式”。
Bar = function(ID, Parent, BarHeight, XLocation)
{
/// <param name="ID" type="String" />
/// <param name="Parent" type="Canvas"/>
/// <param name="BarHeight" type="Number" integer="true" />
/// <param name="XLocation" type="Number">Canvas.Left</param>
this._ID = ID + "_";
this._parent = Convert.ToCanvas(Parent);
this._barHeight = BarHeight;
this._XLocation = XLocation;
this._host = this._parent.element.getHost();
}
现在,在我们处理完构造函数、构造函数参数、构造函数参数注释、内部变量、内部变量的 getter 属性以及 getter 属性的 JavaScript XML 注释之后,我们能加载一些 XAML 吗?我们将使用 Silverlight 下载器对象,它只能在异步模式下工作。这意味着一个函数将开始 XAML 下载,另一个函数将不得不获取 XAML 下载的结果。
Bar = function(ID, Parent, BarHeight, XLocation)
{
...
this.StartXamlDownload();
}
Bar.prototype =
{
...
StartXamlDownload : function()
{
},
XamlDownloadCompleted : function(sender, eventArgs)
{
}
}
让我们创建一个新的 Downloader
对象。
我们将把之前初始化的内部
host
变量作为 Host
发送。
StartXamlDownload : function()
{
var xamlDownloader = Downloader.createFromXaml(this._host);
},
让我们启动一个下载请求来下载 Bar.Xaml。
现在我们还应该确保当下载器 Silverlight 对象完成下载时,它将调用
XamlDownloadCompleted
函数。
StartXamlDownload : function()
{
var xamlDownloader = Downloader.createFromXaml(this._host);
xamlDownloader.open("GET", "Bar.Xaml");
xamlDownloader.add_Completed(this.XamlDownloadCompleted);
},
虽然这是 C# 类的适当语法,但它在 JavaScript 中会有意想不到的结果。当 this.XamlDownloadCompleted
函数被调用时,它会初始化一个新的 Bar
对象并使用其 XamlDownloadCompleted
方法。这就是为什么我们应该使用以下语法来确保调用当前实例的 XamlDownloadCompleted
。
StartXamlDownload : function()
{
var xamlDownloader = Downloader.createFromXaml(this._host);
xamlDownloader.open("GET", "Bar.Xaml");
xamlDownloader.add_Completed(Silverlight.createDelegate
(this, this.XamlDownloadCompleted));
},
让我们调用 send 方法来确保我们的下载开始。
好的,XAML 下载过程已经开始,假设它已完成——让我们编写 XamlDonwloadCompleted
函数。
所以我们做的第一件事是获取下载器的特定类型和类型安全的实例。此外,从 VS2008 Beta2 开始,我们必须“重新输入”内部变量类型才能获得它们的智能感知。
XamlDownloadCompleted : function(sender, eventArgs)
{
var xamlDownloader = Convert.ToDownloader(sender);
var _parent = Convert.ToCanvas(this._parent);
}
让我们获取刚刚下载完成的 XAML 文本。
我们将确保更改 XAML
x:Name
控件 ID,以免与现有的 Bar
对象发生冲突。
XamlDownloadCompleted : function(sender, eventArgs)
{
var xamlDownloader = Convert.ToDownloader(sender);
var _parent = Convert.ToCanvas(this._parent);
var originalXaml = xamlDownloader.get_responseText();
originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
}
让我们从刚刚从服务器获取的 XAML 中初始化一个 Canvas
控件,并将其添加到父 XAML 控件中。
XamlDownloadCompleted : function(sender, eventArgs)
{
var xamlDownloader = Convert.ToDownloader(sender);
var _parent = Convert.ToCanvas(this._parent);
var originalXaml = xamlDownloader.get_responseText();
originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
}
XamlDownloadCompleted : function(sender, eventArgs)
{
var xamlDownloader = Convert.ToDownloader(sender);
var _parent = Convert.ToCanvas(this._parent);
var originalXaml = xamlDownloader.get_responseText();
originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
_parent.get_children().add(newElement);
}
因此,我们有一个 Silverlight 1.0 JavaScript 对象,它获取一些构造函数数据,并可以以特定的名称层次结构初始化 XAML 文件。
步骤 #3 C#:在 Silverlight 1.1 中设置特定的 XAML 控件引用
在我们的 Silverlight 1.1 用户控件中,我们无法像在其他某些地方那样自动访问带有 x:Name
的 XAML 元素。我们需要创建自己的 XAML 对象引用。我们只会为那些需要根据属于我们 Silverlight 用户控件的 BarHeight
属性设置其属性的元素创建这些引用。
public Bar(string ID, int BarHeight)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
FrameworkElement newElement = this.InitializeFromXaml(originalXaml);
this.ID = ID this.BarHeight = BarHeight;
SetControlReferences();
}
这些是我们需要的 XAML 元素及其类型:(以及对应的 C# 字段名到 XAML x:Name
)
private Rectangle _bar;
private ScaleTransform _barscale;
private Rectangle _barB;
private ScaleTransform _barscaleB;
private Path _bubble;
private TextBlock _bubbleText;
所以让我们在 Canvas
加载后获取对这些对象的引用。
public Bar(string ID, int BarHeight)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
FrameworkElement newElement = this.InitializeFromXaml(originalXaml);
this.ID = ID;
this.BarHeight = BarHeight;
SetControlReferences();
}
private Rectangle _bar;
private ScaleTransform _barscale;
private Rectangle _barB;
private ScaleTransform _barscaleB;
private Path _bubble;
private TextBlock _bubbleText;
private Storyboard _showBubble;
private Storyboard _hideBubble;
private void SetControlReferences()
{
_bar = FindNameByXamlID("bar") as Rectangle;
_barscale = FindNameByXamlID("barscale") as ScaleTransform;
_barB = FindNameByXamlID("barB") as Rectangle;
_barscaleB = FindNameByXamlID("barscaleB") as ScaleTransform;
_bubble = FindNameByXamlID("bubble") as Path;
_bubbleText = FindNameByXamlID("bubbleText") as TextBlock;
_showBubble = FindNameByXamlID("showBubble") as Storyboard;
_hideBubble = FindNameByXamlID("hideBubble") as Storyboard;
}
private DependencyObject FindNameByXamlID(string nameInXamlFile)
{
return this.FindName(GetIdFor(nameInXamlFile));
}
private string GetIdFor(string nameInXamlFile)
{
return String.Format("{0}_{1}", this.ID, nameInXamlFile);
}
这个类中有两个辅助方法。FindNameByXamlID
用于查找……通过 XAML ID 查找元素。GetIdFor
返回当前控件中元素的 ID。稍后我们将使用这些引用来更改这些 XAML 对象的属性和事件。
步骤 #3 阻塞脚本 - 在 Silverlight 1.0 JavaScript 中设置特定 XAML 控件引用
我们在上一段中为 C# 所做的事情,我们也将为 JavaScript 做。
XamlDownloadCompleted : function(sender, eventArgs)
{
var xamlDownloader = Convert.ToDownloader(sender);
var _parent = Convert.ToCanvas(this._parent);
var originalXaml = xamlDownloader.get_responseText();
originalXaml = originalXaml.replace("Name=\"", "Name=\"" + this._ID);
var newElement = Convert.ToCanvas(this._host.content.createFromXaml(originalXaml));
_parent.get_children().add(newElement);
this._setControlReferences();
}
我们需要在 Bar
类构造函数中添加内部变量的声明。
Bar = function(ID, Parent, BarHeight, XLocation)
{
...
this._bar = Convert.ToRectangle(null);
this._barscale = Convert.ToScaleTransform(null);
this._barB = Convert.ToRectangle(null);
this._barscaleB = Convert.ToScaleTransform(null);
this._bubble = Convert.ToPath(null);
this._bubbleText = Convert.ToTextBlock(null);
this._showBubble = Convert.ToStoryboard(null);
this._hideBubble = Convert.ToStoryboard(null);
...
}
让我们添加必要的 findNameByXamlID
和 getIdFor
函数。
_findNameByXamlID : function(nameInXamlFile)
{
/// <param name="nameInXamlFile" type="String" />
/// <returns type="DependencyObject" />
returnthis._parent.findName(this._getIdFor(nameInXamlFile));
},
_getIdFor : function(nameInXamlFile)
{
/// <param name="nameInXamlFile" type="String" />
return this._ID + nameInXamlFile;
}
最后,我们将添加并编写 setControlReferences
的代码。
_setControlReferences : function()
{
this._bar = Convert.ToRectangle(this._findNameByXamlID("bar"));
this._barscale = Convert.ToScaleTransform(this._findNameByXamlID("barscale"));
this._barB = Convert.ToRectangle(this._findNameByXamlID("barB"));
this._barscaleB = Convert.ToScaleTransform(this._findNameByXamlID("barscaleB"));
this._bubble = Convert.ToPath(this._findNameByXamlID("bubble"));
this._bubbleText = Convert.ToTextBlock(this._findNameByXamlID("bubbleText"));
this._showBubble = Convert.ToStoryboard(this._findNameByXamlID("showBubble"));
this._hideBubble = Convert.ToStoryboard(this._findNameByXamlID("hideBubble"));
},
步骤 #4 C#:在 Silverlight 1.1 中使用特定的类 XAML 控件
现在,在我们获得了将根据 Bar
对象 BarHeight
更改的 XAML 元素的引用之后,我们将根据原始 JavaScript 文件中先前编写的计算更改属性。
public Bar(string ID, int BarHeight)
{
...
this.ID = ID + "_";
this.BarHeight = BarHeight;
SetControlReferences();
SetXamlControlsPropertiesBasedOnClassProperties();
}
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
}
这是 XAML bar
元素的 JavaScript 中原始的动态 XAML。
// Draw the bar
var bar = control.content.createFromXaml(
'<Rectangle Name="' + id('barB',i) + '" Canvas.Left="' + (x-35) + '" Width="30"'
+ ' Canvas.Top="' + (304 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
+ ' RadiusX="2" RadiusY="2">'
+ ' <Rectangle.Fill>'
+ ' <SolidColorBrush Name="' + id('brushB',i) + '" Color="#7F004296" />'
+ ' </Rectangle.Fill>'
+ ' <Rectangle.RenderTransform>'
+ ' <ScaleTransform Name="' + id('barscaleB',i) + '" CenterY="' + values[i-1] +
'" ScaleY="0.0" />'
+ ' </Rectangle.RenderTransform>'
+ '</Rectangle>'
);
_sl.children.add(bar);
我们将采用相同的逻辑并用 C# 代码编写它。
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
_barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
_barB.Height = this.BarHeight + 5;
_barscaleB.CenterY = this.BarHeight;
}
请注意,任何与 x
变量相关的计算都不需要,因为我们只会移动整个 Bar
画布一次(您将看到这一点)。让我们将所有 XAML GUI 逻辑放入我们的方法中。
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
_barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
_barB.Height = this.BarHeight + 5;
_barscaleB.CenterY = this.BarHeight;
_bar.SetValue(Canvas.TopProperty, 300 - this.BarHeight);
_bar.Height = this.BarHeight + 5;
_barscale.CenterY = this.BarHeight;
_bubble.SetValue(Canvas.TopProperty, 260 - this.BarHeight);
_bubbleText.Text = this.BarHeight.ToString();
_bubbleText.SetValue(Canvas.TopProperty, 273 - this.BarHeight + 4);
}
但是我们还没有完成,仅仅设置实例属性还不够。我们还需要处理 XAML Bar
对象中的 MouseOver
和 MouseLeave
事件。让我们看看原始 JavaScript 中是如何实现的
var bar = control.content.createFromXaml(
'<Rectangle Name="' + id('bar',i) + '" Canvas.Left="' + (x-36) + '" Width="30"'
+ ' Canvas.Top="' + (300 - values[i-1]) + '" Height="' + (values[i-1] + 5) + '"'
+ ' StrokeThickness="1" RadiusX="2" RadiusY="2"'
+ ' MouseEnter="mouseenter" MouseLeave="mouseleave" Loaded="loadbar">'
因此,我们将创建自己的 C# 方法,这些方法将注册为这些事件的 EventHandlers
。事件注册如下所示
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
...
_bar.MouseEnter += new MouseEventHandler(_bar_MouseEnter);
}
void _bar_MouseEnter(object sender, MouseEventArgs e)
{
// do something
}
在我们的 EventHandler
中,我们将确保运行适当的 Storyboard。
private void SetXamlControlsPropertiesBasedOnClassProperties()
{
_barB.SetValue(Canvas.TopProperty, 304 - this.BarHeight);
_barB.Height = this.BarHeight + 5;
_barscaleB.CenterY = this.BarHeight;
_bar.SetValue(Canvas.TopProperty, 300 - this.BarHeight);
_bar.Height = this.BarHeight + 5;
_barscale.CenterY = this.BarHeight;
_bubble.SetValue(Canvas.TopProperty, 260 - this.BarHeight);
_bubbleText.Text = this.BarHeight.ToString();
_bubbleText.SetValue(Canvas.TopProperty, 273 - this.BarHeight + 4);
_bar.MouseEnter += new MouseEventHandler(_bar_MouseEnter);
_bar.MouseLeave += new EventHandler(_bar_MouseLeave);
}
private void _bar_MouseEnter(object sender, MouseEventArgs e)
{
_showBubble.Begin();
}
private void _bar_MouseLeave(object sender, EventArgs e)
{
_hideBubble.Begin();
}
步骤 #4 JavaScript - 在 Silverlight 1.0 中使用特定的类 XAML 控件
嗯,这与我们在 Silverlight 1.1 中所做的基本相同。但是有一些不同之处。首先,如果我们在 VS2008 Beta2 中尝试更改 JavaScript 类构造函数中定义的内部 JavaScript 变量,我们仍然无法在类主体中获得它们的智能感知。在使用 Convert
方法的函数内部,我们仍然拥有智能感知。
但除此之外……
希望我们能在 VS2008 RTM 中获得对
this
关键字的智能感知支持,但现在我更喜欢良好的智能感知而不是软件工程的最佳实践。所以我将把所有属性赋值写在同一个函数中,请随意不遵循我的坏榜样。
_setControlReferences : function()
{
...
var CanvasTop = new DependencyProperty("Canvas.Top");
this._barB.setValue(CanvasTop, 304 - this.get_barHeight());
this._barB.set_height(this.get_barHeight() + 5);
this._barscaleB.set_centerY(this.get_barHeight());
this._bar.setValue(CanvasTop, 300 - this.get_barHeight());
this._bar.set_height(this.get_barHeight() + 5);
this._barscale.set_centerY(this.get_barHeight());
this._bubble.setValue(CanvasTop, 260 - this.get_barHeight());
this._bubbleText.set_text(this.get_barHeight().toString());
this._bubbleText.setValue(CanvasTop, 273 - this.get_barHeight() + 4);
},
这是一个我们需要智能感知并会使用它的示例
想象一下,没有智能感知就编写这段可怕的代码……现在我们还应该处理 theMouseEnter
和 MouseOver
事件,这些事件会使 Bar
改变颜色并显示/隐藏气泡文本。
_setControlReferences : function()
{
...
this._bar.add_MouseEnter(Silverlight.createDelegate(this
,this._bar_MouseEnter));
this._bar.add_MouseLeave(Silverlight.createDelegate(this
,this._bar_MouseLeave));
},
_bar_MouseEnter : function(sender, eventArgs)
{
this._showBubble.begin();
},
_bar_MouseLeave : function(sender, eventArgs)
{
this._hideBubble.begin();
},
步骤 #5 JavaScript - 在 Silverlight 1.0 中使用我们的 JavaScript 控件和部署
在本文中,我们一直使用“C# - JavaScript”乒乓球格式,我想为没有让 C# 先发球下一轮而道歉。C# 需要更多的重构工作,并且部署过程更复杂,所以我们将首先进行 JavaScript,然后是 C#。到目前为止,我们还没有提及条形图是如何放置在表单上的。
我们提出的问题是——谁以及如何将每个单独的 Bar 控件放置在正确的 X 轴位置?在 JavaScript 的情况下,只有 JavaScript 控件应该能够改变内部 Canvas XAML 元素上实际的 Canvas.Left 的位置。因此,在 _setControlReferences 函数内部,我们将改变内部控件中 Page Canvas 上的 Canvas.Left。
/// Bar.Xaml
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Page"
Background="Transparent"
Width="48" Height="300">
....
</Canvas>
在我们的 Bar.Xaml.js 中,我们将更改 Page Canvas.Top
。
最终我们得到
Bar = function(ID, Parent, BarHeight, XLocation)
{
/// <param name="XLocation" type="Number">Canvas.Left</param>
this._XLocation = XLocation;
...
this._page = Convert.ToCanvas(null);
...
}
Bar.prototype = {
_setControlReferences : function()
{
...
var CanvasLeft = Convert.ToDependencyProperty("Canvas.Left");
this._page = Convert.ToCanvas(this._findNameByXamlID("Page"));
this._page.setValue(CanvasLeft, this._XLocation);
},
}
现在让我们处理谁初始化我们的 JavaScript 控件以及如何完成。这就是我们普通的非 Silverlight HTML 页面的样子
我们的画布是中间的空白区域。
/// Page.Xaml
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Scene"
Width="800" Height="300">
</Canvas>
这也是由 CreateSilverlight
函数调用初始化的相同 XAML 文件。
所以让我们看看创建项目时得到的 Page.Xaml.js 的默认代码隐藏。
if (!window.SilverlightOOControlsJavascript)
window.SilverlightOOControlsJavascript = {};
SilverlightOOControlsJavascript.Page = function()
{
}
SilverlightOOControlsJavascript.Page.prototype =
{
handleLoad: function(control, userContext, rootElement)
{
this.control = control;
// Sample event hookup:
rootElement.addEventListener("MouseLeftButtonDown",
Silverlight.createDelegate(this, this.handleMouseDown));
},
// Sample event handler
handleMouseDown: function(sender, eventArgs)
{
// The following line of code shows how to find an element by name
// and call a method on it.
// this.control.content.findName("Timeline1").Begin();
}
}
正如我所说,这是 Page.Xaml.js 的默认代码。它的作用是创建一个类,该类由 CreateSilverlight
函数初始化并注册到它自己的 MouseLeftButtonDown
事件。让我们从示例代码中删除所有不需要的代码。
if (!window.SilverlightOOControlsJavascript)
window.SilverlightOOControlsJavascript = {};
SilverlightOOControlsJavascript.Page = function()
{
}
SilverlightOOControlsJavascript.Page.prototype =
{
handleLoad: function(control, userContext, rootElement)
{
this.control = control;
}
}
让我们添加对 Bar.Xaml.js 文件的 JavaScript 引用,以便获得智能感知。
现在我们需要初始化 20 个不同高度的条形图。我们将使用 0 到 270 之间的随机高度。
handleLoad: function(control, userContext, rootElement)
{
this.control = control;
for(i = 0; i <= 20; i++)
{
var curBarHeight = Math.round(270*Math.random());
}
}
让我们初始化实际的 Bar
JavaScript 对象。
handleLoad: function(control, userContext, rootElement)
{
this.control = control;
for(i = 0; i <= 20; i++)
{
var curBarHeight = Math.round(270*Math.random());
var newBar = new Bar("bar" + i, rootElement, curBarHeight, 40 * i);
}
}
我们最后要做的一件事是向我们的 HTML 页面添加一个指向 Bar.Xaml.js 文件的 <script>
标签引用。
让我们在浏览器中运行它,看看我们所有 JavaScript 努力的最终结果
JavaScript 部分我们完成了。一切正常!让我们回顾一下我们的架构、文件结构以及我们在这里实际做了什么。
步骤 1) 编写了基本的无数据、无实质内容的 XAML 文件。所有 XAML 文件所做的事情就是为我们提供设计时支持。
步骤 2) 在 Expression Blend 中创建了一个新的 JavaScript Silverlight 1.0 项目(这实际上应该发生在步骤 1 中)。更重要的是,我们为每个 XAML 文件创建了一个 myXamlFileName.Xaml.js 文件,其中包含基本的构造函数、内部字段、公共属性和一两个方法。
步骤 3) 我们在 myXamlFileName.Xaml 文件中添加了对内部 Silverlight XAML 对象的引用。
步骤 4) 我们使用了上一步中的引用来更改 GUI 元素的各种 GUI 属性,以适应我们的业务逻辑。
步骤 5) 创建了初始化我们的 myXamlFileName
JavaScript 控件的代码。
因此,我们获得了一个面向对象的模式,其中没有 XAML 文件被多个 JavaScript 文件操作,这些 JavaScript 文件封装了它的所有行为和业务逻辑。让我们回顾一下我们的文件结构
- Default.html - 项目的默认起始页。在新项目打开时创建。我们向其中添加了对 Bar.Xaml.js 的引用。
- Default_html.js - 包含初始化新
Page
JavaScript 对象的CreateSilverlight
函数。与 Default.html 同时创建,当我们打开新项目时。 - Page.xaml - 默认 XAML 画布只有黑色背景。也在我们打开新项目时创建。
- Page.XAML.js - 使用 Page.XAML 文件的 JavaScript 对象。也在我们打开新项目时创建。此文件初始化一个新的
Bar
JavaScript 对象。 - Bar.Xaml - 包含我们的
Bar
对象及其所有动画的 XAML。但是,它不知道 Bar 的最终高度是多少,也不知道它将被放置在哪里。 - Bar.Xaml.js - Bar.Xaml 文件的 JavaScript 对象。这是我们的“主力”,负责所有繁重的业务逻辑工作。只有这个类才能更改
Bar.XAMLGUI
显示属性。
步骤 #5 C#:在 Silverlight 1.1 中使用我们的 JavaScript 控件和部署
让我们来看看当前的项目文件结构
我们有两个项目:Silverlight 项目和 Silverlight 类库。到目前为止,我们只处理了包含 Bar.Xaml 和 Bar.Xaml.js 的 Silverlight 类库。
类似于 JavaScript 项目,Page.Xaml 是一个空白文件,只有黑色背景。所以我们来看看 Page.Xaml.cs。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SilverlightOOControlsLibrary;
namespace SilverlightOOControlsCSharp
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
}
}
}
Page
类将初始化我们的 Bar
对象。类似于 JavaScript 代码,我们将创建 20 个具有随机高度的 Bar。
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
LoadBars();
}
private void LoadBars()
{
Random rnd = new Random();
for (int i = 0; i < 21; i++)
{
int curBarHeight = rnd.Next(0, 270);
// Create Bar
}
}
}
那么,我们如何初始化一个 Bar
对象呢?让我们尝试这种语法
private void LoadBars()
{
Random rnd = new Random();
for (int i = 0; i < 21; i++)
{
int curBarHeight = rnd.Next(0, 270);
Control newBar = new Bar("bar" + i, curBarHeight, this);
newBar.SetValue(Canvas.LeftProperty, i*40);
}
}
让我们看看 Bar
构造函数内部
public Bar(string ID, int BarHeight, Canvas parent)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("x:Name=\"",
string.Format("x:Name=\"{0}_", ID));
newElement = this.InitializeFromXaml(originalXaml);
this.ID = ID ;
this.BarHeight = BarHeight;
SetControlReferences();
SetXamlControlsPropertiesBasedOnClassProperties();
}
在构造函数内部,我们将当前的 Bar
XAML 代码初始化为一个 FrameworkElement
类。
因此,如果我们在 C# 中执行与 JavaScript 相同的操作,我们将在构造函数中将 Bar
控件添加到其父级。让我们尝试运行这个示例。
我们的构造函数内部发生了灾难性故障,指向了将
Bar
添加到Page
的那一行。在 agclr.dll 中发生了类型为System.Exception
的第一次机会异常。附加信息:灾难性故障(来自HRESULT: 0x8000FFFF (E_UNEXPECTED)
的异常)
Silverlight 1.1 抛出灾难性故障几乎总是我们的动画问题。也许我们写入了一个不存在的 TargetName
,或者我们留空了 TargetName
,或者我们做了其他一些轻微错误的事情导致了这个错误。
在我们的例子中,这是因为我们不允许在构造函数内部启动动画。所以我们必须将其移动到其他地方。让我们看看这个构造函数的另一个问题,这次是 Expression Blend。我们确实支持设计我们的 Bar.Xaml 文件。
假设我们希望在 Expression Blend 中将我们的控件添加到 Page.Xaml 中。
我们将点击屏幕左下角的箭头,然后我们得到这个屏幕。
让我们选择自定义控件。
最后,让我们向我们的 Page
中添加一个 Bar
对象,看看会得到什么。
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SilverlightOOControlsLibrary="clr-namespace:SilverlightOOControlsLibrary;
assembly=ClientBin/SilverlightOOControlsLibrary.dll"
x:Name="parentCanvas"
Loaded="Page_Loaded"
x:Class="SilverlightOOControlsCSharp.Page;
assembly=ClientBin/SilverlightOOControlsCSharp.dll"
Width="800"
Height="300"
>
<SilverlightOOControlsLibrary:Bar Width="24" Height="24" Canvas.Left="48"
Canvas.Top="88"/>
</Canvas>
这种从纯 XAML 代码初始化 Silverlight 用户控件的语法使用普通的空默认构造函数。
public class Bar : Control
{
public Bar()
{
}
private FrameworkElement newElement;
public Bar(string ID, int BarHeight, Canvas parent)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("x:Name=\"",
string.Format("x:Name=\"{0}_", ID));
newElement = this.InitializeFromXaml(originalXaml);
this.ID = ID;
this.BarHeight = BarHeight;
SetControlReferences();
SetXamlControlsPropertiesBasedOnClassProperties();
}
所以 Silverlight 绕过了我们的非默认构造函数,从不初始化 XAML 代码。此外,即使我们在类中添加了构造函数重定向,我们仍然没有正确的 ID 参数,而这对初始化 XAML 至关重要。所以我们根本不会使用非默认构造函数。
让我们使用不同的初始化模式。我们将使用默认的空构造函数,并假设使用我们控件的人(无论是解析 XAML 的 Silverlight 引擎还是另一个类的开发人员)将使用属性向我们提供数据。我们将使用 Loaded
事件来获取通知,当控件被数据填充并已添加到 Silverlight 页面上的可视控件树时。
public class Bar : Control
{
public Bar()
{
this.Loaded += new EventHandler(Bar_Loaded);
}
private FrameworkElement newElement;
void Bar_Loaded(object sender, EventArgs e)
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream
("SilverlightOOControlsLibrary.Bar.xaml");
string originalXaml = new System.IO.StreamReader(s).ReadToEnd();
originalXaml = originalXaml.Replace("Name=\"", string.Format("Name=\"{0}_", ID));
newElement = this.InitializeFromXaml(originalXaml);
SetControlReferences();
SetXamlControlsPropertiesBasedOnClassProperties();
}
此时,我们已准备就绪。让我们从 Page
类初始化我们的控件。
private void LoadBars()
{
Random rnd = new Random();
for (int i = 0; i < 21; i++)
{
int curBarHeight = rnd.Next(0, 270);
Bar newBar = (Bar)XamlReader.Load("<SilverlightOOControlsLibrary:Bar xmlns:
SilverlightOOControlsLibrary=\"clr-namespace:SilverlightOOControlsLibrary;
assembly=ClientBin/SilverlightOOControlsLibrary.dll\" ID=\"bar\"
BarHeight=\"150\" />", true);
newBar.SetValue(Canvas.LeftProperty, i*40);
this.Children.Add(newBar);
}
}
我们的第一个选项是编写 XAML 代码,使用 XamlReader.Load
方法加载它,然后我们得到一个新的 Bar
控件。请注意,无论何时使用 XamlReader.Load
语句,我们都必须声明其中使用的任何命名空间(甚至是“x”命名空间)。我们还有另一个选项是直接初始化类
private void LoadBars()
{
Random rnd = new Random();
for (int i = 0; i < 21; i++)
{
int curBarHeight = rnd.Next(0, 270);
Bar newBar = new Bar();
newBar.ID = "bar" + i;
newBar.BarHeight = curBarHeight;
newBar.SetValue(Canvas.LeftProperty, i*40);
this.Children.Add(newBar);
}
}
我个人更喜欢第二种选择,因为它类型安全且对更改更灵活,但这取决于您。两者都会有类似的结果。现在是部署我们的 Silverlight 应用程序的时候了。我们将创建一个新的网站来部署该 Web 项目。
我们将使用我们在 JavaScript 项目中使用的相同的 Default.html 和 Default_html.js 来初始化我们的 Page.Xaml 文件。现在是时候将我们的 Silverlight 项目添加到我们的网站项目(顺便说一句,该项目只有 HTML 文件)了。我们将右键单击项目节点并选择“添加 Silverlight 链接”。
我们会告诉它使用 Silverlight 项目。
Visual Studio 作为其构建过程的一部分,将确保将所有正确的文件复制到我们的 Web 项目并安排构建过程。现在,让我们运行我们的应用程序。
一切都按预期运行。C# 解决方案的架构与 JavaScript 解决方案非常相似。唯一的区别是,我们有第三个项目,主要用于在网站上下文中运行 Silverlight 应用程序。
摘要
我希望您喜欢这篇文章,并且可能学到了一些新东西。我在这篇文章中一直试图传达的不是技术,而是一种心态。让我们不要重复代码,不要动态创建 XAML,让我们充分利用面向对象能提供给我们的最好东西。我们今天在这里开发的所有代码都可以在这里下载。
历史
- 2007 年 12 月 23 日:在 The Code Project 上发布
- 2007 年 8 月 14 日:在 CodePlex 上发布