面向业务应用程序的 Silverlight 入门






4.71/5 (13投票s)
面向行政应用程序开发人员的 Silverlight 入门介绍。本文首先讲解 WPF 的基础知识,然后深入探讨面向业务的方面。
引言
我相信 Silverlight 最终将成为连接 Windows 和 Web 应用程序的 **终极** 解决方案,从这个角度来看,我对它非常感兴趣。因此,我开始关注 Microsoft DevDays 上关于 Silverlight 的会议、阅读教程等。我惊奇地发现关于 Silverlight 的动画和图形功能教程数量众多,而关于构建企业级应用程序的信息却少得可怜。而且,这些信息还非常分散。
由于我正在为一位客户的开发人员团队介绍 Silverlight,我开始编写一份概述 Silverlight 基本知识的小文档。本文基于该文档。
我还包含了一个小型演示应用程序,演示了本文讨论的许多主题。

请注意,您不应将此应用程序视为参考应用程序或“最佳实践”:它是为了演示一些 Silverlight 技术而构建的。
为了保持下载大小较低,我没有包含 WMV 电影文件或数据库记录,但我添加了一个 SQL 架构文件,允许您重建数据库架构。您需要自己提供一些记录。注意:如果向数据库添加图像,应使用 `DeflateStream` 类进行压缩(您可以在 `Carservice.svc.cs` 文件中找到示例)。
另外,此应用程序还在三层环境中使用以下技术:WCF 和 LINQ to SQL。
该应用程序仅使用了 Silverlight 自带的控件。如果您考虑将 Silverlight 用于您的项目,您一定想在 CodePlex 上查看 Silverlight 工具包。
背景
本入门指南假设读者对 WPF 或 Silverlight 一无所知。请注意,这不是一个教程:我认为您在阅读本文档后无法构建 Silverlight 应用程序。相反,您应该能够:
- 在 Silverlight 演示应用程序和示例代码中找到自己的方向。
- 评估 Silverlight 是否是您可以在项目中使用的一项技术。
本入门指南绝非详尽无遗。Silverlight 还有许多其他我没有强调的特性,但我相信对于使用 Silverlight 开发行政应用程序所需的大多数要素都已涵盖。
什么是 Silverlight?
Silverlight 是 Microsoft 的一项新框架,它在很大程度上基于 WPF(Silverlight 的原始名称是 WPF/E)。其主要目标是构建富 Internet 应用程序(RIA)。这归结为:基于浏览器的应用程序,具有 WinForms 应用程序的外观、性能和响应能力。从 2.0 版本开始,该框架还支持(.NET 代码的子集)在客户端运行。这意味着 .NET 代码在浏览器中执行,允许在客户端进行更多的处理,而无需与服务器进行往返通信(类似于网页中的 JavaScript)。
代码被编译并以 XAP 文件形式放置在网站上。Silverlight 应用程序通过在页面标记中使用 `asp:silverlight-control` 标签托管在 ASP.NET 网站中。
Silverlight 的浏览器支持
Silverlight 在 Windows、Mac 和 Linux 上的 Internet Explorer、Mozilla 和 Safari 中得到支持(尽管在 Linux 上的支持相当有限)。
有关支持的平台和系统要求的列表,请查看 此链接。
WPF
左键单击 gadget 并拖动以移动它。左键单击 gadget 的右下角并拖动以调整其大小。右键单击 gadget 以访问其属性。
WPF 是 Windows Presentation Foundation 的缩写,它是一种全新的 UI 构建方式。不再使用代码(WinForms)或 HTML(ASP.NET),而是使用 XML:WPF UI 称为 XAML 文件(发音为 zamel)。Silverlight 并非真正与 WPF 相同,因为 Silverlight 只是 WPF 功能的一个子集。尽管如此,您会在 Silverlight 中找到 WPF 的大部分核心技术,并且在每个连续的 Silverlight 版本中,差异都在不断缩小(预计假以时日,Silverlight 和 WPF 之间将不会有显著差异)。
XAML 只是一种声明式构建用户界面的方式。例如,看下面一段 C# 代码
TextBlock tb = new TextBlock() { Foreground = new SolidColorBrush(Colors.Black),
                   Margin = new Thickness(3), Text = "Name:" };
这相当于 XAML 中的以下标记
<TextBlock Foreground="Black" Margin="3">Name:</TextBlock>
请注意,元素名称对应于控件名称,属性对应于属性。您也可以将属性替换为子 XML 元素。下面的示例与之前的示例等效
<TextBlock Margin="3">
  <TextBlock.Foreground>
    Black
  </TextBlock.Foreground>
  Name:
</TextBlock>
如果您想在代码中引用该元素(例如,在代码中设置 `TextBlock` 的 `Text` 属性),则必须添加 `Name` 属性。该属性的值将成为代码中变量的名称。
控件上的事件声明方式与属性声明方式相同:作为属性。一个名为“`myButton`”的 `Button` 控件,带有 `Clicked` 事件的示例
<Button Name="myButton" Click="Button_Click_3"/>
最后,父控件的某些属性可以在(或必须)子控件上声明。这些属性称为“附加属性”。
例如
<Grid>
  <TextBlock Grid.Row="0" Grid.Column="1" 
           Foreground="Black" Margin="3">Name:</TextBlock>
</Grid>
(尽管 `Row` 和 `Column` 属性实际上是 `Grid` 布局控件的属性,但它们是在子 `TextBlock` 控件上声明的。)
命名空间
类似于 C#/VB.NET 中用于引用库中命名空间的 `using`/`Imports` 语句,XAML 中也必须声明此类引用。
这些命名空间引用在 XAML 文件的根元素中声明,格式如下:`xmlns:name_of_the_namespace="reference_to_the_namespace"`。默认情况下,Silverlight UserControl 已有两个命名空间引用:
<UserControl x:Class="Test.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
...
</UserControl>
假设我们要添加一个名为“`data`”的命名空间,该命名空间引用 `System.Controls.Data` 程序集,则声明如下:`xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"`。在 XAML 文件中进一步向下,来自该命名空间的元素将以“data”为前缀
<data:DataGrid x:Name="datagrid Margin="10">
</data:DataGrid>
布局控件
为了在用户界面中布局不同的控件,Silverlight 中可以使用一些布局控件。
| Grid | 此布局控件对应于 WinForms 中的 `TableLayoutPanel`,并允许使用 `Column`/`Row` 属性进行控件布局。 | 
| StackPanel | 控件放置在彼此下方(或旁边)。 | 
| DockPanel | 控件是停靠的。可能性包括:左、右、上、下和填充。**此控件在 WPF 中可用,但在 Silverlight 中(尚未)提供。** | 
| 画布 | 此布局控件允许使用 X 和 Y 坐标定位控件。 | 
(大多数情况下,您会使用布局控件,而不是根据表单的顶部和左边缘定位控件,以保持布局的动态性。)
就定位大量控件而言,`Grid` 无疑是最强大的布局控件。`StackPanel` 在定位少量控件时特别方便。
内容控件
一些 WPF 控件具有 `Content` 属性,可以包含其他控件作为“内容”。
例如 `Button` 控件。大多数情况下,它只包含一个 `TextBlock` 作为内容元素。
<Button>
  <TextBlock>Click Me</TextBlock>
</Button>
当然,该内容元素反过来又可以包含更多子元素。例如,一个 `Button` 控件,其内容是一个包含多个控件的 `StackPanel`。
<Button>
  <StackPanel Orientation="Horizontal">
    <Image Source="Img/Account16.png"/>
    <TextBlock>Click Me</TextBlock>
  </StackPanel>
</Button>
ItemsControl
除了只能包含一个元素的内容控件外,还有可以包含多个元素的内容控件。
其中两个例子是 `ComboBox` 和 `ListBox` 控件。
<ListBox>
  <ListBox.Items>
    <ListBoxItem><TextBlock>Item 1</TextBlock></ListBoxItem>
    <ListBoxItem><TextBlock>Item 2</TextBlock></ListBoxItem>
  </ListBox.Items>
</ListBox>
DataGrid
Silverlight 和 WPF 的一个(大)区别是 Silverlight 开箱即用地提供了 `DataGrid`(在 WPF 中,您必须使用 Codeplex 上的 WPF 工具包 中的 `DataGrid`)。
**注意**:Silverlight 中的 `DataGrid` 位于 `System.Windows.Controls` 程序集中,位于 `System.Windows.Controls.Data` 命名空间下。您可能需要在此项目中添加对该程序集的引用,并且还必须在 XAML 文件中添加命名空间引用:`xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"`。
基本上,`DataGrid` 的使用非常简单:您可以让 `DataGrid` 自动生成列(使用 `AutoGenerateColumns` 属性),或者手动定义列。
<data:DataGrid Name="datagrid" Margin="10" AutoGenerateColumns="False">
  <data:DataGrid.Columns>
    <data:DataGridTextColumn Header="ID" Binding="{Binding Path=ID}" IsReadOnly="True"/>
    <data:DataGridTextColumn Header="Last name" Binding="{Binding Path=Name}"
        IsReadOnly="False"/>
    <data:DataGridTextColumn Header="First name" Binding="{Binding Path=Firstname}"
        IsReadOnly="False"/>
  </data:DataGrid.Columns>
</data:DataGrid>
有三种列类型:`DataGridTextColumn`、`DataGridCheckBoxColumn` 和 `DataGridTemplateColumn`。后一种非常强大,因为它允许为列的编辑和查看模式声明任何控件。这使您可以定义一个带有 `ComboBox` 或 `Button`(例如)的 `DataGridTemplateColumn`。
资源
WPF 和 Silverlight 允许您在应用程序的 Resources 中存储元素(和变量)。这样,您只需定义一次元素,就可以在应用程序的任何地方重用它们。
例如:一个 `Ellipse` 元素,其 `Brush` 在 `Ellipse` 元素本身中定义。
<Ellipse Height="84" Width="92" Stroke="#FF000000">
  <Ellipse.Fill>
    <RadialGradientBrush GradientOrigin="0.75,0.25">
      <GradientStop Color="#FFF0F2F5"/>
      <GradientStop Color="#FF1D6DE2" Offset="1"/>
    </RadialGradientBrush>
  </Ellipse.Fill>
</Ellipse>
这段标记没有问题,但如果您在其他控件中使用相同的 `Brush`,则以后更改颜色时会遇到维护问题。它还会不必要地使您的标记混乱。
在这种情况下,建议创建 Resources 并将 `Brush` 定义在 Resources 中。每个资源都必须有一个唯一的标识符,即 `x:Key` 属性。稍后,您可以使用此标识符和 `StaticResource` 操作符引用这些资源。
<UserControl.Resources>
  <RadialGradientBrush x:Key="mybrush" GradientOrigin="0.75,0.25">
    <GradientStop Color="#FFF0F2F5"/>
    <GradientStop Color="#FF1D6DE2" Offset="1"/>
  </RadialGradientBrush>
</UserControl.Resources>
<Ellipse Height="84" Width="92" 
   Stroke="#FF000000" Fill="{StaticResource mybrush}"/>
Resources 可以定义在多个位置。如果 Resources 只在一个 XAML 文件中使用,最好在该级别定义它们。
如果 Resources 在整个应用程序中使用,它们可以在 `App.xaml` 文件(在 `Application.Resources` 中)中定义。
还有一个 Silverlight 自动使用的第二个 XAML Resources 文件:`Themes/generic.xaml`。此文件可用于您自己创建的控件的 Resources。
注意:在 WPF 中,还有一个 `MergedDictionaries` 元素,允许在应用程序中定义和使用多个 Resource 文件。此元素仅从 Silverlight 3 版本开始可用。
样式
WPF 的一个非常强大的方面是定义控件样式的可能性。样式类似于 ASP.NET 中的 CSS 技术,并且是一个定义控件的一组属性,然后可以重用到应用程序中的地方。
一个非常简单的例子:假设您想为 `Button` 定义一个 3 的 `Margin`。当然,您可以在每个按钮的基础上进行设置。
<Button Name="button1" Margin="3"><TextBlock>Button 1<>TextBlock></Button>
<Button Name="button2" Margin="3"><TextBlock>Button 2<>TextBlock></Button>
但是,您也可以定义一个将应用于 `Button` 类型控件的 `Style`,该样式定义 `Margin` 为 3。
<Style x:Key="marginButtonStyle" TargetType="Button">
  <Setter Property="Margin" Value="3"/>
</Style>
之后,`Style` 将应用于 `Button`(s)。
<Button Name="button1" Style="{StaticResource marginButtonStyle}">
    <TextBlock>Button 1</TextBlock></Button>
<Button Name="button2" Style="{StaticResource marginButtonStyle}">
    <TextBlock>Button 2</TextBlock></Button>
(样式可以在控件内内联定义,但通常会在 Resources 中定义。)
在 WPF 中,您还可以定义一个必须应用于给定类型的所有控件的 `Style`。在这种情况下,您将省略 `x:Key` 属性,并且不必在控件中引用 `Style`。
<Style TargetType="Button">
  <Setter Property="Margin" Value="3"/>
</Style>
(不过,Silverlight 中不提供此技术,您必须始终显式引用 `Style`。)
数据绑定
数据绑定是将数据绑定到控件属性的技术。在 WPF/Silverlight 中,这比 WinForms 和 ASP.NET 中可能实现的更复杂。
首先,您必须为控件分配一个数据源。一种方法是使用 `DataContext` 属性。这可以分配给控件,但也可以分配给父控件,前提是该数据源对该父控件的所有控件都相同(例如,一个布局容器)。
例如,假设 `User` 类的声明。
public class User
{
  public string Name { get; set; }
  public string Firstname { get; set; }
}
在我们的 XAML 页面的代码隐藏中,我们有一个 `User` 属性,我们在其中存储一个已初始化的 `User` 实例。最后,我们将此 `User` 分配给 UserControl 的 `DataContext` 属性(它是根元素,因此是所有包含控件的父级)。
public partial class Page : UserControl
{
  public User User { get; set; }
  public Page()
  {
    InitializeComponent();
    User = new User() { Name = "Spileers", Firstname = "Xavier" };
    this.DataContext = this.User;
  }
}
在标记本身中,我们将控件的属性绑定到 `User` 对象的属性。这通过 `Binding` 操作符完成。
<TextBlock Margin="3" Text="{Binding Name}"/>
<TextBlock Margin="3" Text="{Binding Firstname}"/>
请注意,您不仅可以绑定控件的 `Text` 属性,还可以绑定几乎任何属性。此外,数据绑定还可以应用于属性的属性(例如 `User.City.Name`),这在以前的 WinForms 中是不可能的。
还有一个 `Mode` 操作符,它指示绑定如何响应。默认模式为 `OneWay`,这意味着对数据对象的修改会传播到控件,但控件中应用的修改不会应用回数据对象。
使用 `TwoWay` 模式,对数据对象应用的修改将传播到控件,反之亦然。
在 Silverlight 中,绑定模式默认似乎是 `OneWay`。在典型的输入表单中,您需要将其更改为 `TwoWay`。一个例子。
<TextBox Margin="3" Text="{Binding Path=Name, Mode=TwoWay}"/>
在 WPF 中,您还可以将控件的属性相互绑定。请看下面的示例,其中 `Slider` 的值用于设置 `Ellipse` 元素的宽度。
<Slider Name="widthSlider" Minimum="50" Maximum="100" Value="75"/>
<Ellipse Width="{Binding ElementName=widthSlider, Path=Value}" Height="50"
    Fill="{StaticResource mybrush}"/>
Silverlight 2 中无法实现这一点,但从 3 版本开始就可以实现。
数据模板
使用数据模板,您可以定义对象在控件中的显示方式。
例如:假设一个列表框显示 `User` 对象列表(`ItemsSource` 在代码中分配)。
<ListBox Name="usersListBox">
</ListBox>
缺点是系统使用 `User` 实例的 `ToString()` 方法来决定列表框中显示的文本。
使用 `ItemTemplate` 属性,您可以为 `ListItem` 定义一个 `DataTemplate`,以便列表中的用户以更具吸引力的方式显示。
<ListBox Name="usersListBox" Height="50">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Name}"/>
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
当然,`DataTemplate` 也可以在 Resources 中定义,以便在多个控件中轻松重用。
动画
WPF 和 Silverlight 允许您定义动画。基本上,动画只是从一种状态到另一种状态的过渡,通过修改控件的一个或多个属性。
这种修改可以通过两种方式实现:渐进式(流畅)或从一种状态变为另一种状态。尽管第一种方式比第二种方式更微妙,但并非适用于所有类型的属性。最支持的类型是:`Double`、`Color` 和 `Point`。
以以下按钮为例。
<Button Name="button1><TextBlock>Hide button 2</TextBlock></Button>
<Button Name="button2"><TextBlock>Button 2</TextBlock></Button>
假设点击第一个按钮的结果是第二个按钮短暂消失。这是通过构建一个动画来实现的,该动画将第二个按钮的 `Opacity` 属性设置为零。动画基于 `Storyboard`,它可以组合多个动画。此 `Storyboard` 被命名,然后可以在代码中使用它来启动动画。
这会产生以下 XAML:
<Button Name="button1" Click="Button_Click">
  <Button.Resources>
    <Storyboard x:Name="buttonanimation">
      <DoubleAnimation Storyboard.TargetName="button2" 
         Storyboard.TargetProperty="Opacity"
         To="0" Duration="0:0:0.25" AutoReverse="True"/>
    </Storyboard>
  </Button.Resources>
  <TextBlock>Hide button 2</TextBlock>
</Button>
(`Storyboard` 在此处作为 `Button` 的 Resource 存储。当然,它也可以定义在 UserControl 级别。)上面的动画将修改 `Double` 类型(`Opacity`)的属性,并花费 25/100 秒(`Duration`)的时间达到 0.0(`To`)的值。最后,动画将属性重置为动画开始之前的值(`AutoReverse=True`)。
代码中的事件处理程序是:
private void Button_Click(object sender, RoutedEventArgs e)
{
  buttonanimation.Begin();
}
请注意,此动画非常流畅:您可以看到按钮逐渐消失。
您还可以通过从一种状态转变为另一种状态来完成消失:这些是使用关键帧的动画。关键帧在不同的时间点定义不同的状态。
<Button Name="button1" Click="Button_Click">
  <Button.Resources>
    <Storyboard x:Name="buttonanimation">
      <DoubleAnimationUsingKeyFrames Storyboard.TargetName="button2"
          Storyboard.TargetProperty="Opacity" FillBehavior="Stop">
        <DiscreteDoubleKeyFrame Value="0.5" KeyTime="0:0:0.10"/>
        <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0.25"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>
  </Button.Resources>
  <TextBlock>Hide button 2</TextBlock>
</Button>
此动画定义在 10/100 秒时,`Opacity` 属性值为 0.5;在 25/100 秒时,`Opacity` 值为 0。`FillBehavior=Stop` 表示一旦执行动画,值将被重置为初始值。
执行动画时,您会发现它比之前的动画流畅度差很多。这是因为从一种状态到另一种状态的过渡相当 abrupt。这就是为什么关键帧动画通常只用于不能以其他方式进行动画处理的属性。
注意:Expression Blend 是定义动画的必备工具。
控件模板
控件模板允许您定义控件的外观。这是非常完整的,因为您可以完全从头开始设计您的控件。
以下 XAML 片段代表了 `Button` 的控件模板。
<ControlTemplate x:Key="mybuttontemplate" TargetType="Button">
  <Grid>
    <Border Name="outerborder" BorderThickness="1" BorderBrush="Blue"
        Background="{StaticResource mybrush}">
      <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
  </Grid>
</ControlTemplate>
(`ContentPresenter` 是放置内容的占位符。)
创建控件时,可以使用 `Template` 属性分配其控件模板。
<Button Margin="3" Template="{StaticResource mybuttontemplate}">
  <TextBlock>Test</TextBlock>
</Button>
当然,也有一个机制可以让控件在鼠标悬停或点击时做出反应(例如)。
Silverlight 中的所有这些交互都由 Visual State Manager(简称 VSM)管理。在 WPF 中,这是由触发器控制的,但预计 VSM 概念也将添加到 WPF 中。
VSM 位于 `System.Windows` 命名空间中:`xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"`。
VSM 可以描述控件的不同状态。动画用于从一种状态到另一种状态的过渡:
<ControlTemplate x:Key="mybuttontemplate" TargetType="Button">
  <Grid>
    <vsm:VisualStateManager.VisualStateGroups>
      <vsm:VisualStateGroup x:Name="CommonStates">
        <vsm:VisualState x:Name="Normal"/>
        <vsm:VisualState x:Name="MouseOver">
          <Storyboard>
            <ColorAnimation Storyboard.TargetName="outerborder"
                Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
                To="GoldenRod" Duration="0:0:0.5" />
          </Storyboard>
        </vsm:VisualState>
        <vsm:VisualState x:Name="Pressed">
          <Storyboard>
             <ColorAnimationUsingKeyFrames Storyboard.TargetName="outerborder"
                  Storyboard.TargetProperty="(Border.Background).(
                  GradientBrush.GradientStops)[0].(GradientStop.Color)">
               <DiscreteColorKeyFrame Value="Yellow" KeyTime="0:0:0"/>
             </ColorAnimationUsingKeyFrames>
             <ColorAnimationUsingKeyFrames Storyboard.TargetName="outerborder"
                 Storyboard.TargetProperty="(Border.Background).(
                 GradientBrush.GradientStops)[1].(GradientStop.Color)">
               <DiscreteColorKeyFrame Value="GoldenRod" KeyTime="0:0:0"/>
             </ColorAnimationUsingKeyFrames>
           </Storyboard>
         </vsm:VisualState>
       </vsm:VisualStateGroup>
     </vsm:VisualStateManager.VisualStateGroups>
   <Border Name="outerborder" BorderThickness="1" BorderBrush="Blue"
        Background="{StaticResource mybrush}">
     <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
   </Border>
 </Grid>
</ControlTemplate>
此示例定义了两种状态:`MouseOver` 和 `Pressed`。在第一种状态下,按钮将改变颜色;在第二种状态下,按钮的背景颜色将被修改。
页面之间的导航
在 Silverlight 2 中,页面之间的导航并不容易。Silverlight 应用程序有一个根视觉元素,所有其他元素都是该根元素的子级。默认情况下,解决方案启动的页面是根视觉元素(参见 `App.xaml.cs` 文件)。
private void Application_Startup(object sender, StartupEventArgs e)
{
  this.RootVisual = new Page();
}
一旦应用程序启动,根视觉元素就不能被另一个 User Control 替换。但是,您可以通过提供自定义根视觉元素并动态设置该自定义元素的 `Content` 来模拟导航。例如,首先构建一个继承的 UserControl,其中可以传递一个 `UIElement` 并将其设置为 `Content`。
public partial class NavFrame : UserControl
{
  public NavFrame()
  {
    InitializeComponent();
  }
  public void Navigate(UserControl content)
  {
    this.Content = content;
  }
}
在 `App.xaml.cs` 中将此 User Control 设置为应用程序的根视觉元素,并提供导航到其他 User Controls 的方法。
private Page _page1;
private NavFrame _frame;
private void Application_Startup(object sender, StartupEventArgs e)
{
  _frame = new NavFrame();
  this.RootVisual = _frame;
  NavigatePage1();
}
public void NavigatePage1()
{
  if (_page1 == null) _page1 = new Page();
  _frame.Navigate(_page1);
}
在代码中,您可以调用这些方法来导航到其他页面。
private void Button_Click(object sender, RoutedEventArgs e)
{
  (Application.Current as App).NavigatePage1();
}
注意:Silverlight 3 具有一个导航框架,可以简化所有这些操作,并支持浏览器中的后退和前进按钮。
集成媒体
Silverlight 提供 `MediaElement` 控件来播放媒体。
<MediaElement Name="mediaPlayer" />
Silverlight 2 支持以下格式:WMV、WMA、MP3、WMVA、WMVC1 和 ASX。当然,将媒体文件打包到 Silverlight 项目本身中是没有意义的(否则它们将在应用程序启动前完整下载);它们应该在需要时从网站流式传输。值得注意的是,Silverlight 应用程序只知道 XAP 文件位置的路径(默认:`Clientbin`):所有路径都相对于此位置。一个好的做法是在此目录中创建子文件夹并将媒体放在其中。

可以通过以下代码访问此文件。
Uri uri = new Uri(Application.Current.Host.Source, "Media/Fiesta_car.wmv");
mediaPlayer.Source = uri;
(上面的代码位于 Play 按钮的事件处理程序中,该按钮将启动媒体播放。)
您可以将媒体放在网站上的任何其他位置,但路径应相对于 `ClientBin` 文件夹。如果媒体文件放在网站的根目录下,则代码将是:
Uri uri = new Uri(Application.Current.Host.Source, "../Fiesta_car.wmv");
mediaPlayer.Source = uri;
客户端和服务器之间的通信
Silverlight 支持的用于客户端和服务器之间通信的技术有限:SOAP 1.1 Web Services 和 REST 服务是仅有的主流可能性(还有一些其他技术,但实现起来并不容易)。
为 Silverlight 应用程序提供服务的最简单方法是使用 WCF 构建它们。考虑到上一段的限制;这意味着我们必须定义一个绑定类型为 `basicHttpBinding`(SOAP 1.1)或 `webHttpBinding`(REST)的服务。
在代码中,可以像在“普通”WinForms 或 ASP.NET 应用程序中一样引用服务:Visual Studio 会为您生成一个代理。您会注意到所有方法只能异步调用:这意味着必须使用事件来接收对服务器的调用返回状态。
默认情况下,Silverlight 只能调用位于同一域且端口号与应用程序源自的网站相同的服务(例如 `www.tri-s.be:80`)。要使用位于不同域(或具有不同端口号)的服务,必须提供一个策略文件并将其放置在服务器上 Web 应用程序的根目录中。
策略文件必须命名为 `clientaccesspolicy.xml`,其内容类似:
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>      
        <domain uri="*"/>
      </allow-from>      
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>      
    </policy>
  </cross-domain-access>
</access-policy>
上面提供的示例策略文件允许所有流量到任何域和任何端口。
**注意**:Silverlight 不支持 `DataSet`/`DataTable`/`DataRow` 对象!因此,构建返回任何这些对象的服务的意义不大。
[更新:有一些可用的替代方案可以模拟 `DataSet`/`DataTable`/`DataRow` 功能(例如,Silverlight Dataset)。感谢 Dewey 指出这一点!]
本地存储数据
Silverlight 允许应用程序将数据本地存储在用户的 PC 上。这些数据只能放置在 IsolatedStorage 中,用户无法访问该位置。您可以将 IsolatedStorage 中的数据存储与网站的 cookie 进行比较。
存储数据到 IsolatedStorage 的代码示例:
using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
{
  using (IsolatedStorageFileStream stream = file.OpenFile("app.data",
      System.IO.FileMode.Create))
  {
    using (StreamWriter writer = new StreamWriter(stream))
    {
      // write data with writer.Write
    }
  }
}
要从 IsolatedStorage 检索数据,可以使用以下代码:
using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
{
  if (file.FileExists("app.data"))
  {
    using (IsolatedStorageFileStream stream = file.OpenFile("app.data",
        System.IO.FileMode.Open))
    {
      using (StreamReader reader = new StreamReader(stream))
      {
        // read data with reader.Read
      }
    }
  }
}
如果您想读取应用程序运行所在的客户端机器上的文件,可以使用 `OpenFileDialog`(文件可以读取,但需要用户批准)。不过,没有等效的 `SaveFileDialog`。
[更新:从 Silverlight 3 开始,您可以在 Silverlight 应用程序中使用 `SaveFileDialog` 将文件本地存储。感谢 Jemery Likness 和 Dewey 指出这一点!]
Silverlight 中的打印
Silverlight 不支持将输出发送到客户端打印机。您唯一的选择是导航到一个包含要打印数据的 ASP.NET 页面。真糟糕!
[更新:您可以将数据导出到文件(例如电子表格),然后让用户使用 `SaveFileDialog` 将其保存到客户端 PC。如果您愿意,还可以将完整的视觉元素导出到图形文件(请参阅下面的评论部分)。]
历史
- 2009-08-15:提交给 CodeProject。
- 2009-08-31:添加了一些更新。


