按需下载 Silverlight 中的“XAP”包
本文介绍了一种在 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 的博客是您入门的默认去处。
这个示例 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”
“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”项目
“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
”内部,我添加了两个“Grid
”TigerSide
和LionSide
。我还在每个“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”包。底部的进度条显示下载状态
下载完成后,在“XapContent”项目中实现的“Main.xaml”用户控件将显示在浏览器中
点击老虎,“FlipAnimator
”开始动画并将内容翻转以显示狮子
然后您可以点击狮子将图片翻转回老虎。根据您计算机的速度,下载速度可能非常快,以至于下载过程几乎不可见。但是如果您查看“Firebug”捕获的网络流量,您可以很容易地看出“XapContent.xap”包确实是按需下载的。
关注点
- 本文介绍了一种在 Silverlight 应用程序中按需下载“XAP”包的方法。在本文中,我只向您展示了如何下载和使用“用户控件”。实际上,任何资源都可以使用“
WebClient
”下载,类似于我在本文中所做的。 - 如前所述,在许多情况下,将所有资源放在一个“XAP”包中可能是一个不错的选择。但在某些其他情况下,尤其是在“XAP”包变得很大且网络较慢时,将资源分离到不同的“XAP”包中可能会被证明很有用。这是一个您需要做出的设计决策。
- 本文介绍的“
FlipAnimator
”借用自这里。我只是将其包装成一个类,结果它非常易于使用。如果您有兴趣了解详细信息,可以直接查看此链接。在本文中,我只展示了翻转图像。但是如果您查看“FlipAnimator
”的构造函数,您可能会注意到输入参数“FrontControl
”和“BackControl
”是“UIElement
”类型。这意味着您可以翻转几乎任何 Silverlight UI 元素。 - 在开发环境中,如果您有一台快速的计算机,“XapContent.xap”的下载速度可能非常快,因此下载过程可能不可见。但是如果您查看“Firebug”捕获的内容,您应该会相信“XAP”文件确实是按需下载的。
- 如果您想在自己的 Visual Studio 中编译和运行此示例应用程序,您需要设置 Silverlight 4 SDK 并在计算机上安装 Silverlight 4。由于我轻松地完成了环境设置,所以我没有任何建议。但是如果您确实遇到设置环境的问题,您只需要耐心,问题就应该会解决。
- 我希望您喜欢我的帖子,也希望本文能以某种方式帮助到您。
历史
这是本文的第一个修订版。