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

我在 Silverlight 2 中的第一个数据应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (109投票s)

2008年8月12日

CPOL

13分钟阅读

viewsIcon

510107

downloadIcon

4544

介绍如何从数据库检索数据并在 Silverlight 应用程序中使用它。

Output

目录

引言

我几周前开始使用 Silverlight,它看起来非常棒。Silverlight 结合了 WPF 的表现力与 C# 的强大功能,显得非常强大。本文的重点将是从数据库检索数据并在 Silverlight 应用程序中使用它。如果您是 ASP.NET 开发者并希望开始探索 Silverlight,那么这里可能是个不错的起点,因为我们还将探讨 Silverlight 和 ASP.NET 之间的相似之处。

我们将涵盖的内容

我们将开发我们在 Silverlight 中的第一个业务应用程序。由于几乎每个业务应用程序都与数据和数据库有关,我们将研究如何在 Silverlight 应用程序中显示数据(来自数据库)。以 Northwind 数据库为例,我们将生成一些 LINQ 类、一个用于检索数据的 WCF 服务,最后,还有一个 ListBox 和一个 DataGrid 以及用于数据呈现的 DataTemplate。我们的输出将是一个相当简单的“主-从”UI,包含客户、他们的订单以及订单详情。请注意,本文的源代码是针对 Silverlight 2 RC1 编写的。

运行示例应用程序

运行示例项目需要进行一些小的调整。默认情况下,解决方案中没有启动项目,因此我们必须通过打开解决方案的属性窗口手动将 DataApplication.Web 设置为启动项目。

ASP.NET 程序员请注意

如果您是 ASP.NET 程序员,您可能需要注意 Silverlight 项目中的 C# 代码在“客户端”运行,而不是在服务器上运行。也许您可以将其视为 JavaScript。因此,Silverlight 的好处是,您现在可以在不了解或编写任何 JavaScript 的情况下完全控制客户端(至少,我不擅长编写 JavaScript)。

为什么是 WCF,我不能直接访问数据库吗?

嗯,简单原因是因为 C# 代码在客户端运行,我们的服务器数据库将无法直接访问。请注意,Silverlight 项目中没有 DataSetDataSource 等项。包含我们最喜欢的类的 System.Data 命名空间也缺失。但是,好消息是还有其他选择,例如 WCF 服务。本文将演示如何使用 WCF 服务进行数据检索。

开始吧

要开始使用 Visual Studio 创建 Silverlight 应用程序,我们需要安装 Visual Studio 的 Silverlight 工具,可以 此处 下载。安装插件后,将添加两种新的项目类型:Silverlight Application 和 Silverlight Class Library。我们将从创建一个名为 DataApplication 的新“Silverlight Application”项目开始。

Creating a new Silverlight Project

Visual Studio 然后会询问我们如何部署 Silverlight 应用程序;我们将选择“ASP.NET Web Application Project”,在该项目中我们将创建 LINQ 类和 WCF 服务。

Selecting an ASP.NET Web application project to host our silverlight page

如果一切顺利,我们将在解决方案中看到两个项目:DataApplication 将是我们的客户端 Silverlight 项目,而 DataApplication.Web 将是我们的 ASP.NET 服务器端项目。

生成 LINQ 类

我们轻量级的全能选手 Northwind(可从 链接获取)一直是开始使用的最佳数据库之一。请注意,MDF 文件也包含在示例项目中,并且通过 SQLExpress 连接。要开始生成 LINQ 数据类,我们需要在 ASP.NET 应用程序中添加一个新的 LINQ Data Classes 对象。

Adding a new LINQ Data Classes object

现在,使用服务器资源管理器,创建一个到 Northwind 数据库的新数据连接(使用 SQLExpress 或 SQL Server,如果您有的话),然后将 CustomerOrdersOrderDetails 表拖到 LINQ 设计器中。一个重要的事情是允许我们的 LINQ 生成的数据类进行**序列化**,因为我们将把这些对象传输到 Silverlight 应用程序。要实现这一点,请单击 LINQ 设计器中的空白区域,并使用“属性”窗口将 Serialization 属性更改为 UniDirectional

LINQ Designer: Adding tables and enabling serialization

创建 Silverlight WCF 服务

现在,我们将为服务器项目添加一个服务来检索数据。在 Silverlight 2 Beta 2 之前,我们需要对标准 WCF 服务模板进行一些调整才能在 Silverlight 项目中使用,但幸运的是,Beta 2 及更高版本提供了“Silverlight 启用的 WCF 服务”模板,它可以自己处理所有事情。我们将为 ASP.NET 项目添加一个名为 DataService 的新 Silverlight 启用的 WCF 服务。

Adding a new Silverlight-enabled WCF Service

我们将在服务中编写三个方法:一个返回所有客户,一个返回客户的订单,最后,一个返回特定订单的订单详情。请注意,方法必须标记 [OperationContract] 属性(此属性类似于 ASMX 服务的 [WebMethod] 属性)。以下是使用非常基本的 LINQ 实现数据检索方法的快速示例。将以下内容添加到 DataService.svc.cs 文件中

[OperationContract]
public List<Customer> GetCustomers()
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return datacontext.Customers.ToList();
}

[OperationContract]
public List<Order> GetOrders(string customerID)
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return (from order in datacontext.Orders
            where order.CustomerID == customerID
            select order).ToList();
}

[OperationContract]
public List<Order_Detail> GetOrderDetails(int orderID)
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return (from orderdetail in datacontext.Order_Details
            where orderdetail.OrderID == orderID
            select orderdetail).ToList();
}

向 Silverlight 项目添加服务引用

这就是服务器端 ASP.NET 项目所需的一切。我们创建了 LINQ 数据类来从数据库获取数据,并创建了 WCF 服务来返回这些 LINQ 对象。现在,我们可以在客户端 Silverlight 项目中使用了。为此,我们需要在 DataApplication 项目中添加一个服务引用。我们可以在“添加服务引用”弹出窗口中单击**发现**,让 Visual Studio 自动找到我们新创建的 WCF 服务。

Discovering our WCF service

创建用户界面

Silverlight 页面/控件由一个布局 XAML 文件和一个代码隐藏 xaml.cs 文件组成。通常,与 ASP.NET 页面类似,XAML 文件包含布局定义(如 *.aspx 文件,我们在其中定义 UI),而 xaml.cs 文件包含逻辑和事件处理程序(如我们的 aspx.cs 文件)。让我们开始创建一些基本布局。

添加用于使用 DataGrid 控件的程序集

我们将使用 DataGrid 控件来显示我们的数据,但 Silverlight 默认不包含 DataGrid 控件的引用,因此我们需要添加一个。这个过程与在 ASP.NET 中使用自定义控件非常相似。回想一下,我们在项目中添加了对相应 DLL 的引用,然后在 aspx 页面中添加了 register 标签。要在 Silverlight 中实现这一点,请添加一个新引用(右键单击“引用”并选择“新建引用”),然后在列表中找到 System.Windows.Controls.Data(这是包含 DataGrid 的程序集)。

Referencing assembly for DataGrid control

添加此引用后,我们需要在 XAML 标记中为该程序集分配一个命名空间。为此,请将以下内容添加到 Page.Xaml 文件的命名空间声明中。

xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Visual Studio IDE 在此处提供帮助,如下面的截图所示

Adding assembly for DataGrid control to our page

UI 布局

这是操作步骤的概述:创建一个名为 LayoutRootGrid,其中包含三行:第一行用于应用程序标题(高度=50),第三行用于状态栏(高度=20),中间一行充当主内容持有者(高度=*,占据所有剩余空间)。在第一行中添加一个标题 TextBlock,在 LayoutRoot 网格的底部一行中添加一个名为 txtStatus 的空 TextBlock。在 LayoutRoot 网格的中间行(我们确定为主内容持有者)中,再添加一个名为 ContentRootGrid,包含两列和两行;左列宽度为 200,右列占据剩余空间。行应按 60% 和 40% 的比例划分。在此 ContentRoot 网格的左列中,添加一个跨越两行的 ListBox。在右列中,在第一行中添加一个 DataGrid 用于显示客户订单,在第二行中添加另一个 DataGrid 用于显示订单详情。我的疯狂描述是否让您筋疲力尽?这是 page.xaml 的 XAML 版本

<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;
	assembly=System.Windows.Controls" 
    x:Class="DataApplication.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;
		assembly=System.Windows.Controls.Data"
    Width="Auto" Height="Auto">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="55" x:Name="HeaderRow" />
            <RowDefinition Height="*" x:Name="ContentRow"/>
            <RowDefinition Height="20" x:Name="FooterRow"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <!-- Heading -->
        <TextBlock x:Name="txtHeader" Grid.Row="0" 
                   FontSize="20" Margin="5,5" Foreground="Blue"
                   Text="My First Data Application in Silverlight">
        </TextBlock>

        <!-- A textblock in the footer to be used as an Status bar -->
        <TextBlock x:Name="txtStatus" Grid.Row="2" 
               FontSize="10" Margin="5,0" Foreground="Red">
        </TextBlock>

        <!-- Content Holder -->
        <Grid x:Name="ContentGrid" Grid.Row="1" Margin="5">
            <Grid.RowDefinitions>
                <RowDefinition Height=".6*" />
                <RowDefinition Height=".4*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- Listbox for displaying customers -->
            <ListBox x:Name="lstCustomers" Grid.Column="0" Grid.RowSpan="2"
                     DisplayMemberPath="ContactName"
                     Loaded="lstCustomers_Loaded"
                     SelectionChanged="lstCustomers_SelectionChanged">
            </ListBox>

            <!-- DataGrid for displaying orders of a customer 
                (with autogenerated columns) -->
            <data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1" 
                           AutoGenerateColumns="True"
                           SelectionChanged="dgOrders_SelectionChanged">
            </data:DataGrid>

            <!-- DataGrid for displaying orderdetais for an order -->
            <data:DataGrid x:Name="dgOrderDetails" Grid.Row="1" Grid.Column="1" 
                           AutoGenerateColumns="True"
                           AutoGeneratingColumn="dgOrderDetails_AutoGeneratingColumn">
            </data:DataGrid>

        </Grid>

    </Grid>
</UserControl>

本文将不会详细解释 WPF 布局,因为 CodeProject 和其他地方都有很多资源;例如,Sacha Barber 的 这篇。我们倒是可以快速看一下 ListBoxDataGrid 的标记。

ListBox

lstCustomers 列表框将用于显示数据库中的客户列表。我们将在已注册的 Loaded 事件中绑定此列表框。请注意,如果 ListBox 绑定到某个对象源,它将为其每个项目集合显示 object.ToString() 的值。如果我们想显示任何其他值(通常是对象的 string 成员),我们有三个显而易见的选择:

  • 重写 object.ToString() 方法(我们不会仅仅为了在 ListBox 中显示正确的值而这样做)。
  • 定义一个 Data Template(这是最灵活的方法,我们将在手动定义 DataGrid 列时简要介绍 DataTemplate,但现在我们将跳过此选项)。
  • DisplayMemberPath 属性中定义要使用的属性(很简单,所以我们暂时保持如此)。

由于我们希望 ListBox 显示绑定到的 Customer 对象的 ContactName 属性,因此我们使用 DispalyMemberPath="ContactName"。此外,我们还注册了 SelectionChanged 事件,我们将在代码隐藏文件中处理该事件以使用所选客户的订单更新 DataGrid

DataGrid

目前,我们没有对 DataGrid 做任何花哨的事情。我们只配置它们在数据绑定时自动生成列。请注意,我们还订阅了 dgOrderDetailsAutoGeneratingColumns 事件。这是一个常见的做法,如果我们要快速删除某些不需要的列,并且与自动生成列结合使用。在本文中,我们还将研究如何手动定义列,但现在,让我们保持简单。

编写一些代码

填充 ListBox

我们想将所有客户加载到列表框 lstCustomers 中,为此我们使用列表框的 Loaded 事件。请注意,Silverlight 中的所有服务调用都需要异步进行,因此我们将注册一个回调函数,在该函数中我们将传入的数据绑定到 ListBox。注意我们如何使用 txtStatus 文本框(回想一下,我们将其放在 LayoutGrid 的底部一行以向用户提供更新)。

private void lstCustomers_Loaded(object sender, RoutedEventArgs e)
{
    DataServiceClient svc = new DataServiceClient();
    this.txtStatus.Text = "Loading customers...";
    svc.GetCustomersCompleted += new
      EventHandler<GetCustomersCompletedEventArgs>(svc_GetCustomersCompleted);
    svc.GetCustomersAsync();
}

void svc_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
    if (e.Error == null)
    {
        this.lstCustomers.ItemsSource = e.Result;
        this.txtStatus.Text = string.Empty;
    }
    else
    {
        this.txtStatus.Text =
            "Error occurred while loading customers from database";
    }
}

显示客户的订单

现在,我们将编写一些代码来显示当列表框中的客户被选中时发生的订单。在 SelectionChanged 事件处理程序中,我们将调用 WCF 服务,并在检索到数据时将其绑定到 dgOrders。这次,我们将使用匿名方法以使其更紧凑。

private void lstCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Customer selectedCustomer = this.lstCustomers.SelectedItem as Customer;
    if (selectedCustomer != null)
    {
        DataServiceClient svc = new DataServiceClient();
        this.txtStatus.Text = "Loading orders...";
        svc.GetOrdersCompleted +=
            delegate(object eventSender, GetOrdersCompletedEventArgs eventArgs)
            {
                if (eventArgs.Error == null)
                {
                    this.dgOrders.ItemsSource = eventArgs.Result;
                    this.txtStatus.Text = string.Empty;
                }
                else
                {
                    this.txtStatus.Text =
                        "Error occurred while loading orders from database";
                }
            };
        svc.GetOrdersAsync(selectedCustomer.CustomerID);
    }
}

显示选定订单的订单详情

与列表框 SelectionChanged 事件类似,我们将以下代码添加到 dgOrdersSelectionChanged 事件中。这次,我们将尝试使用 lambda 表达式。

private void dgOrders_SelectionChanged(object sender, EventArgs e)
{
    Order selectedOrder = this.dgOrders.SelectedItem as Order;
    if (selectedOrder != null)
    {
        DataServiceClient svc = new DataServiceClient();
        this.txtStatus.Text = "Loading order details...";
        svc.GetOrderDetailsCompleted +=
            (eventSender, eventArgs) =>
            {
                if (eventArgs.Error == null)
                {
                    this.dgOrderDetails.ItemsSource = eventArgs.Result;
                    this.txtStatus.Text = string.Empty;
                }
                else
                {
                    this.txtStatus.Text =
                        "Error occurred while loading order details from database";
                }
            };
        svc.GetOrderDetailsAsync(selectedOrder.OrderID);
    }
}

从 dgOrderDetails 中删除一些自动生成的列

请注意,在 XAML 中,我们将 DataGridAutoGenerateColumns 属性设置为 true。假设我们想摆脱 dgOrderDetails 数据网格中的 OrderID 列。这可以通过在 AutoGeneratingColumns 事件处理程序中编写以下代码来实现。

private void dgOrderDetails_AutoGeneratingColumn(object sender,
    DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.Column.Header.ToString() == "OrderID")
        e.Column.Visibility = Visibility.Collapsed;
}

检查点已到达..运行项目

现在,我们的小应用程序可以观看了。运行它..选择一些客户,查看订单,编辑 DataGrid 中显示的数据,通过单击列标题对 DataGrid 数据进行排序,调整 DataGrid 列的大小,调整浏览器窗口的大小,做任何您想做的事情。是不是很酷?我们现在将看看 DataGrid 列和模板。

Output 1 with autogenerated columns

定义列

Silverlight DataGrid 中的列可以以与 ASP.NET 中非常相似的方式进行定义。DataGrid 可以接受三种类型的列:

  • DataGridTextBoxColumn - 此列类型使用 TextBlock 来显示其数据,并使用 TextBox 来允许编辑其数据。我们需要使用 DisplayMemberPath 来告知要显示数据绑定对象的哪个属性。
  • DataGridCheckBoxColumn - 此列类型提供一个只读的 CheckBox 来显示布尔值或可空布尔值,以及一个普通的 CheckBox 来允许编辑该值。
  • DataGridTemplateColumn - 此强大的列类型允许我们定义 DataTemplate 并选择我们自己选择的控件,就像 ASP.NET 的 TemplateColumn 一样。有关 DataTemplating 的更多信息,请参阅 MSDN 此处

如果您想了解更多,Scott Morris 在列类型方面有一个很好的博客文章 此处

因此,让我们在应用程序中使用这些知识。为了简单起见,我们将只定义四列:我们将为 OrderIDEmployeeID 列使用 DataGridTextBoxColumn。我们将为 OrderDate 定义一个 DataGridTemplateColumn,在其 CellTemplate 中有一个 TextBlock,在其 CellEditingTemplate 中有一个 DatePicker 控件。最后,我们将为 Frieght 定义另一个 TemplateColumn。但是,这次我们将在其 CellEditingTemplate 中定义两个控件:一个 Slider 用于增加/减少 Frieght 值,以及一个 TextBlock 用于显示滑块的当前值。这两个控件都将放置在一个水平 StackPanel 中,因为我们在 DataTemplate 中只能定义一个项。

以下代码应替换 dgOrders 标记

<!-- DataGrid for displaying orders of a customer -->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1" 
   AutoGenerateColumns="False"
   SelectionChanged="dgOrders_SelectionChanged">
    <data:DataGrid.Columns>
        <!-- OrderID text column -->
        <data:DataGridTextColumn Header="Order ID" Binding="{Binding OrderID}" />

        <!-- EmployeeID text column -->
        <data:DataGridTextColumn Header="Employee ID" Binding="{Binding EmployeeID}" />

        <!-- OrderDate template column -->
        <data:DataGridTemplateColumn Header="Order Date" Width="150">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding OrderDate}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
            <data:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <basics:DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellEditingTemplate>
        </data:DataGridTemplateColumn>

        <!-- Freight template column -->
        <data:DataGridTemplateColumn Header="Freight" Width="150">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Freight}"></TextBlock>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
            <data:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Freight}" Width="50" />
                        <Slider Value="{Binding Freight, Mode=TwoWay}" Width="100"
                                Minimum="0" Maximum="500" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellEditingTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>

同样,我们可以为 ListBox 使用 DataTemplate。假设在我们的对象模型中,我们有一个 CustomerPictureProperty(返回 BitmapImage)。然后,我们可以像这样在 ListBox 中显示图片

<ListBox x:Name="lstCustomer">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding NameProperty}"></TextBlock>
                <Image Source="{Binding PictureProperty}"></Image>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

请注意,上面的代码只是一个示例。由于我们的对象中没有图片,因此我们无法在应用程序中使用此代码段。

第二次运行

再次运行项目。双击 OrderDate 并观察 DatePicker 如何弹出。使用滑块控件修改 Freight 值。类似地,可以在项模板中使用任何其他控件或控件组合。请注意,在第二次迭代中,我们没有修改 *.cs 文件;我们所做的所有更改都在布局方面。这就是 Windows Presentation Foundation 框架的优点。

Output 2 - with explicit columns definition

将数据写回数据库

嗯,这对于这篇入门文章来说太多了。但是,如果您已经了解了 WCF 服务如何用于在客户端和服务器之间通信,那么您可以通过在服务中创建一些函数并在 Silverlight 应用程序中调用它们来轻松地将数据写回数据库。请注意,本文中的数据绑定是双向的,也就是说,在 DataGrid 中更改值实际上会更改绑定到的 DataContext 项中的值。我们只需要使用 WCF 服务将这些更新发送到我们的 ASP.NET 项目,并在那里处理它们来更新数据库。Ronnie Saurenmann 在 此处 提供了一些视频。他使用类似 DataSet 的方法,保留数据的修改副本和原始副本,然后使用一些辅助类将相关记录仅发送到服务器。我强烈推荐观看他的视频。

结论

就这样。我写这篇文章是为了演示构建任何数据应用程序的基础是多么容易。让我们回顾一下我们学到的内容:我们在服务器端的 ASP.NET 项目中创建了一个使用 LINQ 的数据访问层,并通过 WCF 服务将其公开。我们在 Silverlight 应用程序中通过服务客户端检索了数据,最后,我们使用了一些数据模板来更精细地控制数据呈现。我希望本文能激发您开始在 Silverlight 中构建未来的应用程序。祝您 Silverlight 愉快……

历史

  • 12 Aug 08 - 文章发布
  • 19 Oct 08 - 文章更新为 Silverlight 2 RC1
© . All rights reserved.