65.9K
CodeProject 正在变化。 阅读更多。
Home

按需下载 Silverlight 中的“XAP”包

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (5投票s)

2010年9月20日

CPOL

8分钟阅读

viewsIcon

51979

downloadIcon

1400

本文介绍了一种在 Silverlight 中按需下载和使用“XAP”包的方法。

引言

本文介绍了一种在 Silverlight 中按需下载和使用“XAP”包的方法。

背景

要运行 Silverlight 应用程序,浏览器需要下载 Silverlight 项目生成的“XAP”包。在开发 Silverlight 应用程序时,开发人员可以将所有“用户控件”和其他资源放在一个“XAP”包中。他们也可以选择将这些资源放入单独的“XAP”包中,并让浏览器根据需要下载它们。在许多情况下,将资源放入单个“XAP”文件应该是正确的选择。但在某些其他情况下,将资源分离到不同的“XAP”文件可能会提供一些优势。

  • 浏览器可以实时下载应用程序所需的 Silverlight 内容,这可能会节省下载时间并改善用户体验。
  • 如果 Silverlight 应用程序的某些部分发生更改,我们可以选择只重新部署受更改影响的“XAP”包。

本文通过一个 Visual Studio 示例介绍了一种在 Silverlight 中按需下载和使用“XAP”包的方法。本文附带的 Visual Studio 解决方案是在 Visual Studio 2010 和 Silverlight 4 中开发的。本文假设读者对 Silverlight 开发有一些基本经验。如果您是 Silverlight 新手,Scott Guthrie 的博客是您入门的默认去处。

SolutionWeb.JPG

这个示例 Visual Studio 解决方案包含三个项目

  • “XapWebHost”是一个 ASP.NET Web 应用程序,用于托管 Silverlight 应用程序。
  • “XapLoader”是一个 Silverlight 项目。它将生成一个名为“XapLoader.xap”的“XAP”文件。“XapLoader.xap”文件将托管在“XapWebHost”Web 应用程序中。
  • “XapContent”也是一个 Silverlight 项目。它将生成一个名为“XapContent.xap”的“XAP”文件。“XapContent.xap”文件是本文中要按需下载的“XAP”包。

成功编译后,Visual Studio 会将“XapLoader.xap”和“XapContent.xap”都放入“XapWebHost”项目中的“ClientBin”文件夹中。如果您想下载源代码并在自己的 Visual Studio 中运行,您需要设置开发环境。您需要下载并安装“Silverlight 4 SDK”和“Silverlight 4”。

让我们首先看一下“XapWebHost”Web 项目。

宿主 Web 应用程序

正如您从图片中看到的,“XapWebHost”是一个简单的 ASP.NET Web 应用程序。Silverlight“XAP”文件“XapLoader.xap”托管在“Default.aspx”文件中

<%@ Page Language="C#" AutoEventWireup="true"
    EnableSessionState="False" EnableViewState="false"
    CodeBehind="Default.aspx.cs" Inherits="XapWebHost.Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Silverlight Download on Demand Example</title>
    <link rel="SHORTCUT ICON" href="Images/rubik.ico" />
    <link href="Styles/SilverlightDefault.css" 
               rel="stylesheet" type="text/css" />
    <script src="Scripts/Silverlight.js" 
                 type="text/javascript"></script>
</head>
<body>
    <div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2,"
            type="application/x-silverlight-2"
            width="100%" height="100%">
    <param name="source" value="ClientBin/XapLoader.xap"/>
    <param name="onError" value="onSilverlightError" />
    <param name="background" value="white" />
    <param name="minRuntimeVersion" value="4.0.50826.0" />
    <param name="autoUpgrade" value="true" />
    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0"
            style="text-decoration:none">
      <img src="http://go.microsoft.com/fwlink/?LinkId=161376"
                alt="Get Microsoft Silverlight" style="border-style:none"/>
    </a>
     </object><iframe id="_sl_historyFrame"
            style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</body>
</html>

为了确保“XapContent.xap”文件的按需下载过程始终在浏览器中可见,通过更改“Global.asax.cs”文件禁用了缓存

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
 
namespace XapWebHost
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e) { }
        protected void Session_Start(object sender, EventArgs e) { }
        protected void Application_AuthenticateRequest(object sender, EventArgs e) { }
        protected void Application_Error(object sender, EventArgs e) { }
        protected void Session_End(object sender, EventArgs e) { }
        protected void Application_End(object sender, EventArgs e) { }
 
        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            HttpContext.Current.Response.Cache.SetNoStore();
        }
    }
}

Silverlight 应用程序“XapLoader”

SolutionLoader.JPG

“XapLoader”是一个非常简单的 Silverlight 应用程序。此应用程序只在“Splash.xaml”中实现了一个“用户控件

<UserControl x:Class="XapLoader.Splash"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" Cursor="Wait">
 
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
 
        <Image Grid.Row="0" Source="Image/SmallTiger.jpg" />
        <Grid Grid.Row="1">
            <Rectangle x:Name="rectStandard" HorizontalAlignment="Stretch"
                       Fill="Beige" />
            <Rectangle x:Name="rectProgress" HorizontalAlignment="Left"
                       Width="0" Fill="LightBlue" />
            <TextBlock x:Name="txtProgress" HorizontalAlignment="Center" />
        </Grid>
    </Grid>
</UserControl>

这个“用户控件”有一个“Image”,它将显示在 Web 浏览器中。由于这个“用户控件”负责下载“XapContent.xap”文件,它还实现了一个进度条,用于通过以下 UI 组件显示下载状态

  • 一个名为“rectStandard”的“Rectangle”用作进度条的背景。
  • 一个名为“rectProgress”的“Rectangle”用于图形化显示下载进度。
  • 一个名为“txtProgress”的“TextBlock”用于以文本形式显示下载百分比。

Splash.xaml”的代码隐藏文件如下

using System;
using System.Net;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Reflection;
using System.Windows.Resources;
 
namespace XapLoader
{
    public partial class Splash : UserControl
    {
        private int downloadProgress;
        public Splash()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Splash_Loaded);
            this.SizeChanged += new SizeChangedEventHandler((Object sender,
                    SizeChangedEventArgs e)
                => { UpdateProgressBar(); });
        }
 
        private void Splash_Loaded(object sender, RoutedEventArgs e)
        {
            string contentUri = Application.Current.Host.Source
                .AbsoluteUri.Replace("XapLoader", "XapContent");
 
            WebClient wc = new WebClient();
            wc.OpenReadCompleted
                += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
            wc.DownloadProgressChanged
                += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
            wc.OpenReadAsync(new Uri(contentUri));
        }
 
        private void wc_OpenReadCompleted(Object sender, OpenReadCompletedEventArgs e)
        {
            Assembly assembly = LoadAssemblyFromXap("XapContent.dll", e.Result);
            object loadedContent = assembly.CreateInstance("XapContent.Main");
 
            App app = (App)Application.Current;
            app.ApplicationVisualRoot.Children.Clear();
            app.ApplicationVisualRoot.Children.Add((UserControl)loadedContent);
        }
 
        private void wc_DownloadProgressChanged(Object sender, 
            DownloadProgressChangedEventArgs e)
        {
            downloadProgress = e.ProgressPercentage;
            UpdateProgressBar();
        }
 
        private void UpdateProgressBar()
        {
            rectProgress.Width = rectStandard.ActualWidth * downloadProgress / 100;
            txtProgress.Text = "Loading ... " + 
                               downloadProgress.ToString() + "%";
        }
 
        private Assembly LoadAssemblyFromXap(string relativeUri,
            Stream xapPackageStream)
        {
            StreamResourceInfo xapPackageSri
                = new StreamResourceInfo(xapPackageStream, null);
            StreamResourceInfo assemblySri
                = Application.GetResourceStream(xapPackageSri,
                new Uri(relativeUri, UriKind.Relative));
            AssemblyPart assemblyPart = new AssemblyPart();
 
            return assemblyPart.Load(assemblySri.Stream);
        }
 
    }
}

在此“用户控件”的“Loaded”事件中,初始化了一个“WebClient”对象。两个事件处理程序与此对象关联

  • 当“WebClient”完成下载所需内容时,将触发“OpenReadCompleted”事件。然后我们可以在事件处理程序中处理下载的内容。
  • DownloadProgressChanged”事件用于报告下载进度。进度条在事件处理程序中进行调整,以告知用户下载状态。

当调用“WebClient”对象的“OpenReadAsync”方法时,下载开始。此方法需要一个“Uri”输入。在本例中,“Uri”是指向“XapContent.xap”包的指针。下载完成后,使用“LoadAssemblyFromXap”方法从“XapContent.xap”包中检索正确的程序集。我从这里借用了这个方法,它在我的示例中运行良好。然后使用“CreateInstance”方法创建“XapContent.Main用户控件的实例。然后将此用户控件设置为可见。用户控件“XapContent.Main”是在“XapContent”项目中实现的,我们很快将详细讨论。

Splash.xaml”在 Silverlight 应用程序的“Startup”事件中添加到应用程序的“RootVisual”中

private void Application_Startup(object sender, StartupEventArgs e)
{
        ApplicationVisualRoot = new Grid();
        ApplicationVisualRoot.Children.Add(new Splash());
        this.RootVisual = ApplicationVisualRoot;
}

“XapContent”项目

SolutionContent.JPG

“XapContent”项目为应用程序创建要下载的“XapContent.xap”包。为此,“XapContent.xap”文件应该足够大,以便下载进度足够慢以至于可见。我向此项目添加了两张图片“BigLion.jpg”和“BigTiger.jpg”。这两张图片的组合大小约为 3 MB。我最初想简单地将这两张图片添加到一个“用户控件”中,但我后来决定添加一些动画效果来增添趣味。用户控件“Main.xaml”将添加这两张图片,但只有一张可见。单击图片,它将缓慢翻转到另一面。我从此链接学习了这种翻转动画方法。本文中所做的只是将代码包装到一个易于使用的类“FlipAnimator”中。此类在“Utilities”文件夹中实现

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
 
namespace XapContent.Utilities
{
    public class FlipAnimator
    {
        private bool isFrontSide = true;
        private Storyboard ThisStoryBoard = new Storyboard();
        private TimeSpan TimeSpanLastFrame = new TimeSpan();
 
        private Panel Container;
        private UIElement FrontControl;
        private UIElement BackControl;
        public int AnimationSpeed { get; set; }
 
        public FlipAnimator(Panel Container,
            UIElement FrontControl, UIElement BackControl)
        {
            this.Container = Container;
            this.FrontControl = FrontControl;
            this.BackControl = BackControl;
            this.AnimationSpeed = 4;
 
            TransformGroup transforms = new TransformGroup();
            ScaleTransform scale = new ScaleTransform();
 
            Container.RenderTransformOrigin = new Point(0.5, 0.5);
            transforms.Children.Add(scale);
            this.Container.RenderTransform = transforms;
            AnimateFlip(ThisStoryBoard, scale, out TimeSpanLastFrame);
            this.Container.Resources.Add("Animation", ThisStoryBoard);
 
            ThisStoryBoard.Completed
                += new EventHandler(ThisStoryBoard_Completed);
        }
 
        public void Toggle()
        {
            if (isFrontSide) { GoBack(); } else { GoFront(); }
        }
 
        public void GoFront()
        {
            if (isFrontSide) return;
 
            BackControl.Visibility = Visibility.Visible;
            FrontControl.Visibility = Visibility.Visible;
            ThisStoryBoard.Pause(); ThisStoryBoard.AutoReverse = true;
            ThisStoryBoard.Seek(TimeSpanLastFrame); ThisStoryBoard.Resume();
            isFrontSide = true;
        }
 
        public void GoBack()
        {
            if (!isFrontSide) return;
 
            BackControl.Visibility = Visibility.Visible;
            FrontControl.Visibility = Visibility.Visible;
            ThisStoryBoard.Pause(); ThisStoryBoard.AutoReverse = false;
            ThisStoryBoard.Begin(); isFrontSide = false;
        }
 
        // Private methods
        private void ThisStoryBoard_Completed(object sender, EventArgs e)
        {
            if (isFrontSide) { BackControl.Visibility = Visibility.Collapsed; }
            else { FrontControl.Visibility = Visibility.Collapsed; }
        }
 
        private static KeySpline DefineKeySpline(double X1,
            double Y1, double X2, double Y2)
        {
            KeySpline ksStart = new KeySpline();
            ksStart.ControlPoint1 = new Point(X1, Y1);
            ksStart.ControlPoint2 = new Point(X2, Y2);
            return ksStart;
        }
 
        private TimeSpan AnimateFlip(Storyboard sb,
            ScaleTransform scale, out TimeSpan tsLastFrame)
        {
            double speed = (double)AnimationSpeed;
            double flipRotation = 0;
            double flipped = 2;
            tsLastFrame = new TimeSpan();
            TimeSpan tsSideFlipped = new TimeSpan();
 
            int frames = 1;
            DoubleAnimationUsingKeyFrames daX = new DoubleAnimationUsingKeyFrames();
            tsLastFrame = new TimeSpan();
            while (flipRotation != flipped * 180)
            {
                flipRotation += speed;
                double flipRadian = flipRotation * (Math.PI / 180);
                double size = Math.Cos(flipRadian);
                double scalar = (1 / (1 / size));
 
                DiscreteDoubleKeyFrame ddkfX = new DiscreteDoubleKeyFrame();
                ddkfX.Value = (size * scalar);
 
                tsLastFrame = TimeSpan.FromMilliseconds(frames * 28);
 
                flipped = (size < 0) ? +1 : +0;
                if (flipped == 1 && tsSideFlipped.Ticks == 0)
                {
                    tsSideFlipped = tsLastFrame;
                }
 
                ddkfX.KeyTime = KeyTime.FromTimeSpan(tsLastFrame);
                daX.KeyFrames.Add(ddkfX);
 
                flipRotation %= 360;
                frames++;
            }
 
            Storyboard.SetTarget(daX, scale);
            Storyboard.SetTargetProperty(daX, new PropertyPath("(ScaleX)"));
            sb.Children.Add(daX);
 
            VisualizeSide(sb, tsSideFlipped, 0,
                TimeSpan.FromMilliseconds((tsSideFlipped.Milliseconds + 100)),
                BackControl.Opacity, this.BackControl);
            VisualizeSide(sb, TimeSpan.FromMilliseconds((tsSideFlipped.Milliseconds - 100)),
                FrontControl.Opacity, tsSideFlipped, 0, this.FrontControl);
            BackControl.Opacity = 0;
 
            return tsLastFrame;
        }
 
        private void VisualizeSide(Storyboard sb, TimeSpan tsStart,
            double opacityStart, TimeSpan tsEnd, double opacityEnd,
            UIElement side)
        {
            DoubleAnimationUsingKeyFrames daOpacity =
                new DoubleAnimationUsingKeyFrames();
            SplineDoubleKeyFrame sdkfStart = new SplineDoubleKeyFrame();
            sdkfStart.Value = opacityStart;
            sdkfStart.KeyTime = tsStart;
            sdkfStart.KeySpline = DefineKeySpline(0, 0, 1, 1);
            daOpacity.KeyFrames.Add(sdkfStart);
 
            SplineDoubleKeyFrame sdkfEnd = new SplineDoubleKeyFrame();
            sdkfEnd.Value = opacityEnd;
            sdkfEnd.KeyTime = tsEnd;
            sdkfEnd.KeySpline = DefineKeySpline(0, 0, 1, 1);
            daOpacity.KeyFrames.Add(sdkfEnd);
 
            Storyboard.SetTarget(daOpacity, side);
            Storyboard.SetTargetProperty(daOpacity,
                new PropertyPath("(UIElement.Opacity)"));
            sb.Children.Add(daOpacity);
        }
    }
}

如果您想了解翻转动画是如何实现的,可以参考此链接。虽然“FlipAnimator”类看起来很复杂,但它非常易于使用,您可以从“Main.xaml”中看到

<UserControl x:Class="XapContent.Main"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
 
    <Grid x:Name="LayoutRoot" Margin="10, 10, 10, 10">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
 
        <Grid Background="Black" Opacity="0.95" Grid.RowSpan="2" />
        
        <TextBlock Grid.Row="0" HorizontalAlignment="Center"
                   VerticalAlignment="Center" FontFamily="Verdana"
                   FontWeight="Bold" Foreground="White">
            Click on the image to flip the content
        </TextBlock>
 
        <Grid Grid.Row="1" x:Name="flipContainer" Margin="20, 0, 20, 20">
            <Grid x:Name="TigerSide"
                  VerticalAlignment="Stretch" HorizontalAlignment="Center">
                <Image Source="Image/BigLion.jpg" Cursor="Hand"
                       MouseLeftButtonUp="Image_MouseLeftButtonUp" />
            </Grid>
            <Grid x:Name="LionSide"
                  VerticalAlignment="Stretch"
                  HorizontalAlignment="Center">
                <Image Source="Image/BigTiger.jpg" Cursor="Hand"
                       MouseLeftButtonUp="Image_MouseLeftButtonUp" />
            </Grid>
        </Grid>
    </Grid>
</UserControl>

在这个“XAML”文件中,我添加了一个名为“flipContainer”的“Grid”。在这个“Grid”内部,我添加了两个“GridTigerSideLionSide。我还在每个“Grid”中插入了一张图片。这就是使用类“FlipAnimator”所需的全部。此“XAML”文件的代码隐藏文件如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using XapContent.Utilities;
 
namespace XapContent
{
    public partial class Main : UserControl
    {
        private FlipAnimator animator;
 
        public Main()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Main_Loaded);
        }
 
        private void Main_Loaded(object sender, RoutedEventArgs e)
        {
            animator = new FlipAnimator(flipContainer, LionSide, TigerSide);
        }
 
        private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            animator.Toggle();
        }
    }
}

在“Loaded”事件中,通过将“Grid”s“flipContainer”、“LionSide”和“TigerSide”的引用传递给构造函数来初始化“FlipAnimator”。在“Image”s的“MouseLeftButtonUp”事件中,调用“FlipAnimator”的“Toggle”方法来翻转图片。

我们现在已经完成了三个项目的实现。编译后,“XapLoader.xap”文件约为 62 KB,“XapContent.xap”文件约为 3 MB。

运行应用程序

将“XapWebHost”项目设置为“启动”项目,将“Default.aspx”文件设置为“启动”页。我们可以调试运行应用程序。当 Silverlight 应用程序启动时,它将显示“Splash.xaml”并开始下载“XapContent.xap”包。底部的进度条显示下载状态

Splash.JPG

下载完成后,在“XapContent”项目中实现的“Main.xaml用户控件将显示在浏览器中

BigTiger.JPG

点击老虎,“FlipAnimator”开始动画并将内容翻转以显示狮子

BigLion.JPG

然后您可以点击狮子将图片翻转回老虎。根据您计算机的速度,下载速度可能非常快,以至于下载过程几乎不可见。但是如果您查看“Firebug”捕获的网络流量,您可以很容易地看出“XapContent.xap”包确实是按需下载的。

Firebug.JPG

关注点

  • 本文介绍了一种在 Silverlight 应用程序中按需下载“XAP”包的方法。在本文中,我只向您展示了如何下载和使用“用户控件”。实际上,任何资源都可以使用“WebClient”下载,类似于我在本文中所做的。
  • 如前所述,在许多情况下,将所有资源放在一个“XAP”包中可能是一个不错的选择。但在某些其他情况下,尤其是在“XAP”包变得很大且网络较慢时,将资源分离到不同的“XAP”包中可能会被证明很有用。这是一个您需要做出的设计决策。
  • 本文介绍的“FlipAnimator”借用自这里。我只是将其包装成一个类,结果它非常易于使用。如果您有兴趣了解详细信息,可以直接查看此链接。在本文中,我只展示了翻转图像。但是如果您查看“FlipAnimator”的构造函数,您可能会注意到输入参数“FrontControl”和“BackControl”是“UIElement”类型。这意味着您可以翻转几乎任何 Silverlight UI 元素。
  • 在开发环境中,如果您有一台快速的计算机,“XapContent.xap”的下载速度可能非常快,因此下载过程可能不可见。但是如果您查看“Firebug”捕获的内容,您应该会相信“XAP”文件确实是按需下载的。
  • 如果您想在自己的 Visual Studio 中编译和运行此示例应用程序,您需要设置 Silverlight 4 SDK 并在计算机上安装 Silverlight 4。由于我轻松地完成了环境设置,所以我没有任何建议。但是如果您确实遇到设置环境的问题,您只需要耐心,问题就应该会解决。
  • 我希望您喜欢我的帖子,也希望本文能以某种方式帮助到您。

历史

这是本文的第一个修订版。

© . All rights reserved.