超酷代码

针对 Silverlight 开发的 3 个重要技巧

Jeff Prosise

代码下载可从 MSDN 代码库
浏览代码联机

内容

按需程序集加载
实时呈现
避免区域的依赖项
打开新页

我撰写本文,Silverlight 2 是在按下禁用热,并开发人员都获得第一个看什么许多认为表示 Web 编程的未来。无论您是 Silverlight proponent 中竞争的技术,如 Adobe 弹性找到更多的 allure,很令人兴奋若要查看替代 JavaScript,HTML,和 AJAX 我们可以看到获得 mindshare 用于创建 Web 应用程序。并与 Microsoft 已经硬盘工作 Silverlight 3,将来有永远不会似乎变亮。

适用的任何平台,成为 Silverlight 开发人员在路途不没有几个 potholes。您知道,是例如 XamlReader.load 正常测试,在美国的 PC 上的多个调用会失败,在其他国家 / 地区的 PC 上吗?未实现的 Silverlight 的呈现引擎 intimately 到 UI 线程并且此事实 profoundly 会影响您的代码的结构?您知道您可以通过动态加载程序集,减少 XAP 文件的大小,但是这样不会丢失的强类型化优点需要知识库的 CLR 内部吗?如果此 intrigues 您,阅读。我有一些提示并共享的技巧将进行 Silverlight 的生命稍低 bumpy,并让您一个好并且多得到的通知 Silverlight 程序员,太。

有关更多信息打包更快的传递的 Silverlight 内容,请参阅在1 月 2009 期,领先的.

按需程序集加载

精心设计的 Silverlight 应用程序的 hallmarks 之一是以前称为应用程序包小 XAP 文件。XAP 文件作为嵌入的资源 (尤其是图像) 和程序集引用的结果的所有太通常 swell 到 unmanageable 的大小。大 XAP 文件,在长花下载,如果它增长太大,Silverlight 可能无法加载它。

更大的应用程序可以将打包在小 XAP 文件中,如果您仔细考虑资源和应用程序使用的程序集,并保留那些可以延迟加载或下载根据 Web 服务器上。您可以使用 WebClient 或其他类 Silverlight 的网络堆栈中下载的其他资源和程序集在下载应用程序包。通常最好上获取应用程序的 UI 并运行快速而且然后启动异步网络请求其他资产需要比发布 100MB XAP 文件和强制用户花五分钟等待到达 100%的进度指示器。

请求资源在 Silverlight 中加载往往是简单和简单。图 1 中的代码段是例如下载 JPEG 在原站点的部署中,并通过将下载的位分配给名为 MyImage 一个 XAML 图像显示。

图 1 从原始格式的站点下载图像

WebClient wc = new WebClient();
wc.OpenReadCompleted +=
    new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
wc.OpenReadAsync(new Uri("JetCat.jpg", UriKind.Relative));
  ...
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        BitmapImage bi = new BitmapImage();
        bi.SetSource(e.Result);
        MyImage.Source = bi;
    }
}

按需集加载会但是应更加困难。 乍一似乎简单: 使用 WebClient 下载程序集和 AssemblyPart.load 其加载到在 Appdomain。 问题是 Silverlight 的 JIT 编译器可以获取导致许多开发人员认为它是无法下载请求的程序集,并享受强类型,过的好处,种方法中。 在现实,但是,您可以执行两。 但您需要知道您正在执行和具有程序集加载 CLR 中的工作方式基本了解。

为了说明,请看下列代码:

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    Calendar cal = new Calendar();
    cal.Width = 300.0;
    cal.Height = 200.0;
    cal.SelectedDatesChanged += new
        EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
    LayoutRoot.Children.RemoveAt(0);
    LayoutRoot.Children.Add(cal);
}

fig02.gif

图 2 保持超出该 XAP 程序集

它是一个按钮单击处理程序动态创建一个日历控件,并将其添加 XAML 场景到。 (它也会删除触发该事件假定为 LayoutRoot 的子级集合中的该 0th 项的按钮)。 因为日历实现在 System.Windows.controls.dll 这不嵌入插件 silver­light 中但属于而该扩展的 BCL 中此代码运行良好,只要将对 System.Windows.controls.dll 引用添加到项目。 该引用导致 System.Windows.controls.dll XAP 文件中包含并自动载入该 Appdomain。

现在假设您想来将智能化并只加载 System.Windows.controls.dll 如果它的需要,这就是如果用户单击按钮。 因此您将对 System.Windows.controls.dll 引用添加到该项目以满足编译器 (否则,编译器不会编译对日历因为编译器不知道日历类型是),并且,Visual Studio 的属性窗口中中, 您将 System.Windows.controls.dll 的 Copy Local 属性设置为 False,以防止它被嵌入到 XAP 文件中,(如我那样 图 2 中)。

下一步,您部署 System.Windows.controls.dll 的副本与服务器上应用程序的 ClientBin 文件夹中 XAP 文件。 最后,您重组您的代码,如 图 3 所示。 按钮单击处理程序现在下载 System.Windows.controls.dll 从 Web 服务器、 将其加载到与 assembly­part.load,appdomain 和实例化一个日历控件。

图 3 按需集加载的 Doesn't 工作

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted +=
        new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.OpenReadAsync(new Uri("System.Windows.Controls.dll",
        UriKind.Relative));
}

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null) 
    {
        // Load the downloaded assembly
        AssemblyPart part = new AssemblyPart();
        part.Load(e.Result);

        // Create a Calendar control
        Calendar cal = new Calendar();
        cal.Width = 300.0;
        cal.Height = 200.0;
        cal.SelectedDatesChanged += new
           EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
        LayoutRoot.Children.RemoveAt(0);
        LayoutRoot.Children.Add(cal);
    }
}        

它看上去合理和代码编译只是好但运行时单击处理程序生成类似 图 4 中的异常。 编译的代码是正确的。 不引发异常的代码。 因此,内容提供? 该错误消息似乎表明 CLR 试图加载 System.Windows.controls.dll,但因为您以编程方式加载它不需要。

这是案例的一个很好的示例,其中知识库的 CLR 内部可以让您更好的 Silverlight 程序员。 问题此处是当 JIT 编译器编译 wc_OpenReadCompleted 方法,它扫描方法,看到它引用一个名为日历的类型并且会尝试加载 System.Windows.controls.dll,以便可以解析该引用。

fig04.gif

图 4 天哪 !

遗憾的是,此时即使执行该方法之前, 您不会因此获得能够调用 AssemblyPart.load。 这是一个典型鸡彩蛋问题时。 您需要调用 AssemblyPart.load 加载在的程序集,但 JIT 编译器在可以调用它之前,intervenes 的并且尝试加载它。 尝试失败,因为 System.Windows.controls.dll 不在应用程序包。

这是的点的许多程序员引发最自己动手,和结束点播集加载不在 Silverlight 工作或求助于反射来实例化日历类型:

AssemblyPart part = new AssemblyPart();
Assembly a = part.Load(e.Result);
Object cal = (Object)a.CreateInstance("Calendar");

此方法的工作但粗心。 您不能返回 assembly.create­instance 日历因为这样做这样将导致 JIT 编译器尝试加载程序集方法执行之前将引用进行转换。 并且如果您不能强制转换到日历,然后控件的方法、 属性,和事件必须通过反射,过访问。 该代码快速增长如此不实用的吸引力只中并嵌入 System.Windows.controls.dll 应用程序包和 Live 与增加 XAP 大小。

好消息是您可以结合加载的动态集和强类型。 只需重建您的代码行中的 图 5 。 观察 wc_OpenReadCompleted 不再引用日历类型 ; 所有引用已都移动到一个名为 CreateCalendar 的不同方法。 此外,CreateCalendar 属性化方式 JIT 编译器不会尝试内联方法。 (如果内联是出现您就是右重新启动因为 wc_OpenReadCompleted 将包含对日历类型隐式引用) 现在 JIT 编译器不会检查已加载 System.Windows.controls.dll,直到调用 CreateCalendar,并按该时间,您已经已加载它到在 Appdomain。

图 5 按需集加载的工作原理

private void CreateCalendarButton_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.OpenReadCompleted +=
        new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
    wc.OpenReadAsync(new Uri("System.Windows.Controls.dll",
        UriKind.Relative));
}

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        // Load the downloaded assembly
        AssemblyPart part = new AssemblyPart();
        part.Load(e.Result);

        // Create a Calendar control
        CreateCalendar();
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void CreateCalendar()
{
    Calendar cal = new Calendar();
    cal.Width = 300.0;
    cal.Height = 200.0;
    cal.SelectedDatesChanged += new
        EventHandler<SelectionChangedEventArgs>(cal_SelectedDatesChanged);
    LayoutRoot.Children.RemoveAt(0);
    LayoutRoot.Children.Add(cal);
}

见解: 呈现和用户界面线程

通知 Jeff 说的不注意大量的一个方面是 Silverlight 的在 Silverlight 中的所有呈现都完成应用程序的 UI 线程上,如果 hog UI 线程,则防止从发生的任何呈现。因为 WPF 提供了一个呈现线程,很可能意外 Silverlight 不。您可能想知道原因。

决定系统开销和 decoupling framerates 之间附到一个一权衡能力下。Silverlight,我们的线程上亮方法重量并没有不隔离应用程序代码从呈现系统。这意味着您可以执行多个将动画 (例如具有布局基于动画或运行的自定义代码) 中,并且没有最小的延迟和开销获取呈现系统。在关闭的端是如果您做过多,您可能影响视频播放等操作。

也就是说,Silverlight 呈现系统将利用多核处理,并用许多线程加速为它的呈现。因此,呈现是很少"线程上,,但它与您的应用程序以避免同步和数据的副本同步。

—Ashraf Michail,主体级结构设计版 Silverlight

顺便说一下,如果这是 Windows Presentation Foundation (WPF),而不是 Silverlight,您可以通过解决此问题更完善的方式注册 AppDomain.AssemblyResolve 事件处理程序,并有加载 System.Windows.controls.dll。在 Silverlight 中, 存在的 AppDomain.AssemblyResolve,但它的属性化这意味着用户代码不能为其注册处理程序的 SecurityCritical。

图 5 假定您包括在项目中对 System.Windows.Controls.dll,但是您设置复制本地为 False (参见 图 2 ) 和部署 ClientBin 文件夹中的程序集。证明它可以工作,下载本专栏附带 OnDemandAssemblyDemo 应用程序,然后单击该的按钮标记为日历控件。日历控件显示位置按钮。显著,OnDemandAssemblyDemo.xap 不包含您可以轻松地验证通过使用 WinZip 打开 XAP 文件的 System.Windows.controls.dll 的副本。时间 !这将是一个很好冰-器在下一个 Silverlight 方。

实时呈现

不按大量的 Silverlight 的一个方面是在 Silverlight 中的所有呈现都完成应用程序的 UI 线程上的并如果 hog UI 线程,您将防止从所都进行的任何呈现。这意味着要避免在 UI 线程上的长时间运行循环,如果您要修改该循环中的 XAML 场景,或者如果所有动画均会发生一次。

这听起来简单,避免在 UI 线程上的长时间运行循环,但实际上,它可能对您编写的代码有渊博的影响。请考虑应用程序调用 OpenFileDialogDemo, 图 6 中的说明。它将演示如何使用 Silverlight 的 OpenFileDialog 类来允许用户浏览他或她的硬盘上的图像文件然后将图像加载到 XAML 图像对象。运行应用程序,单击打开按钮在页面的顶部,选择多个图像文件 (在更大好),然后单击 OpenFileDialog 的打开按钮。

fig06.gif

图 6 中的 OpenFileDialogDemo

您会发现一个由一个图像您选择使用与 XamlReader.load 动态创建的对象的场景到弹出,假设在页面上的随机位置。一旦该图像将显示,您可以单击他们进行到前面,,甚至使用鼠标将它们拖动在页周围。

尽管它明显的简单性 OpenFileDialogDemo 提供在审慎与 UI 线程中的实际课程。当我最初编写代码以显示该 OpenFileDialog,并加载图像文件时,我结构它类似于 图 7 中代码段。一旦用户已关闭对话框,简单的 foreach 循环将遍历所选文件,并加载逐个。

图 7 加载图像文件的简单方法

OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
    "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
ofd.FilterIndex = 1;
ofd.Multiselect = true;

if ((bool)ofd.ShowDialog())
{
    foreach (FileInfo fi in ofd.Files)
    {
        using (Stream stream = fi.OpenRead())
        {
            BitmapImage bi = new BitmapImage();
            bi.SetSource(stream);
            GetNextImage().Source = bi;
        }
    }
}

遗憾的是,没有图像出现在屏幕上直到所有已加载。 如果用户选择一个或两个图像文件,但它是 intolerable 选定 40 或 50 个文件,延迟并不很了不起。 以是短应用程序未满足最低要求我为其,设置,因为我希望"弹出"在屏幕上为它们所加载的图像。 获取它? 弹出 ! 弹出 ! 弹出 !

该的问题当然,是在 UI 线程上运行的 foreach 循环中,并运行该循环时 Silverlight 无法呈现图像,它们已将其添加到场景,这意味着它是时间步骤后,需要一个的一和重组代码以使其能够及时地呈现图像。

图 8 显示了一个问题的解决方案。 修改后的 foreach 循环不执行任何操作多个将 FileInfo 对象添加到一个 System.Collections.generic.queue。 这使该循环,以快速地运行,并手动编写控件重新向 Silverlight 以便它可以向下呈现的业务。 或许,最值得关注的 restructured 代码是如何 dequeues 和处理响应 CompositionTarget.rendering 事件中的 FileInfo 对象。

图 8 A 更佳方法加载图像文件

private Queue<FileInfo> _files = new Queue<FileInfo>();
 ...
public Page()
{
    InitializeComponent();

    // Register a handler for Rendering events
    CompositionTarget.Rendering +=
        new EventHandler(CompositionTarget_Rendering);
}
 ...
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
    "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
ofd.FilterIndex = 1;
ofd.Multiselect = true;

if ((bool)ofd.ShowDialog())
{
    // Reset the queue
    _files.Clear();

    // Place each FileInfo in a queue
    foreach (FileInfo fi in ofd.Files)
    {
        _files.Enqueue(fi);
    }
}
 ...
private void CompositionTarget_Rendering(Object sender, EventArgs e)
{
    if (_files.Count != 0)
    {
        FileInfo fi = _files.Dequeue();
        using (Stream stream = fi.OpenRead())
        {
            BitmapImage bi = new BitmapImage();
            bi.SetSource(stream);
            GetNextImage().Source = bi;
        }
    }
}

CompositionTarget.rendering 是回一个每个帧呈现调通常用于实现游戏的循环。 从 WPF 借用并显示最迟在 Silverlight 2 开发周期。 Silverlight 已准备好 re-render 场景每次引发该事件。

OpenFileDialogDemo 注册 composition­target.rendering 事件 (CompositionTarget_Rendering) 处理程序,并 dequeues 一个的 FileInfo 对象将其转换为是 XAML 图像每次调用该处理程序时。 结果? 为已加载它们由于 Silverlight 现在具有可以更新以下每个新的映像的场景,图像中弹出在屏幕上。 这是 OpenFileDialogDemo 的最终版本中的 foreach 循环构造方式,并且很为什么当您在运行,您看到逐个代替同时屏幕上显示的图像。

要注意,不要 overuse CompositionTarget.rendering。 如果 OpenFileDialogDemo 有作为运行的动画它添加图像到该的场景动画可能会 stutter 因为每个帧将被延迟按照加载图像位,并将其分配给图像对象所需的时间量。 但需要、 格式,需要和 OpenFileDialogDemo 是 CompositionTarget.rendering—indeed 可接受的利用一个很好示例时, 此目标将是难否则完成。

避免区域的依赖项

最终的提示将使用 XamlReader.load 动态创建 XAML 对象。 您可以发现有何不妥此代码?

Rectangle rect = (Rectangle)XamlReader.Load(
    String.Format(
        "<Rectangle xmlns=\"https://schemas.microsoft.com/client/2007\" " +
        "Width=\"{0}\" Height=\"{1}\" Stroke=\"Black\" Fill=\"Yellow\" />",
        100.5, 100.0
    )
); 

如果您识别此代码在美国的大多数 PC 上运行,但将无法在欧洲和世界上的其他部分中的大多数 PC 上,授予您自己一个 pat 上。要演示,请首先配置操作系统以显示在美国的数字、 货币、 日期,和时间格式如果没有已配置这种方式。(在 Vista,转到可以通过控制面板区域和语言选项对话框的格式选项卡)。 执行 XamlReader.load 调用,并验证该调用成功执行。现在将区域的格式更改为法语,并再次执行该调用。这次 XamlReader.load 引发异常:"无效的属性值 100,5 属性宽度"( 图 9 )。问题是该十进制数字,如 100.5 写入 100,5 (请注意小数点位置的逗号) 在许多国家 / 地区。并且因为主机 PC 上 String.Format 认可的区域设置,十进制 100.5 成为"100,5。遗憾的是,XamlReader.load 不知道如何进行的"100,5",以便它将引发异常。

fig09.gif

图 9 由 XamlReader.Load 引发的异常

下面的代码演示调用 XamlReader.load,以便它工作运行 Silverlight 的任何 PC 上正确的方法:

Rectangle rect = (Rectangle)XamlReader.Load(
    String.Format(
        CultureInfo.InvariantCulture,
        "<Rectangle xmlns=\"https://schemas.microsoft.com/client/2007\" " +
        "Width=\"{0}\" Height=\"{1}\" Stroke=\"Black\" Fill=\"Yellow\" />",
        100.5, 100.0
    )
);

传递给 String.Format 在第一个参数是引用固定区域性的 CultureInfo 对象。 XamlReader.load 需要区域性的不变量的字符串,以便使用 CultureInfo.InvariantCulture 可确保该 String.Format 生成格式正确的十进制值 (以及格式正确的日期和时间) 如果您使用的。 不 coincidentally 前, 一节 OpenFileDialogDemo 应用程序使用此技术确保自己对 XamlReader.load 工作无论区域设置。

如果的字符串传递给 XamlReader.load 包含小数由 String.Format,始终使用 CultureInfo.InvariantCulture 将正确格式。 您的应用程序运行在该通配符后, 就 liable 在各种区域设置下执行。

打开新页

如果您要开始使用 Silverlight,并且您是经验丰富的.NET 开发人员,您已经知道您需要了解的 90%。 但您应该了解的.NET Silverlight 使 nuances。

说的页面,许多读者已要求我要更新, Silverlight 1.0 页面打开框架在 2008 年显示 Silverlight 2。 和,迁移已完成。 您可以查看一个示例使用在更新的 Framework 的应用程序 wintellect.com/silverlight/pageturndemo/您可以下载该源代码 wintellect.com/Downloads/PageTurnDemo2.zip.

在页面打开 Framework 居住在 PageTurn.cs,和 page.xaml.cs 中的代码说明了其工作原理。 API 类似于 (在 C# 而不是 JavaScript),Silverlight 1.0 版本,并且我更改以便将更好的页面。 我提供的触发框架使您可以更新用户界面页打开已完成每次一个 PageTurned 事件假设显示当前页码。

将您的问题和意见发送 Jeff 到 wicked@Microsoft.com.

Jeff ProsiseMSDN Magazine 一个软件编辑器和有多部书籍,包括 Programming Microsoft.NET (2002,Microsoft Press) 的作者。 他还是 Wintellect (的创始人之一 wintellect.com),软件咨询和培训公司专门研究 Microsoft.NET 的。 与在 Jeff wicked@Microsoft.com.