如何:将数据服务数据绑定到控件(Silverlight 客户端)

利用 WCF 数据服务,可以将 Silverlight 控件(如 ListBox 或 ComboBox)绑定到 DataServiceCollection<T> 的实例。 此类处理控件引发的事件,以使 DataServiceContext 与控件中的数据更改同步。 DataServiceCollection<T> 是基于 DataServiceQuery<TElement> 定义的。 此查询在执行时返回一个开放式数据协议 (OData) 源,此源提供集合的对象。

本主题中的过程说明如何执行下面的任务:

  • (可选)在 Northwind 数据服务中启用分页功能。

  • 创建一个新的 Silverlight 应用程序。

  • 生成支持自动数据绑定的客户端数据服务类。

  • 查询数据服务。

  • 将结果绑定到应用程序中的控件。

使用 CollectionViewSource 实例简化主/明细 Order 和 Order_Detail 对象的绑定。 为此,将 CollectionViewSourceSource 属性设置为主 DataServiceCollection<T>。 控件的数据绑定在 XAML 中定义,这样如果更改所选的 Order,则在加载集合时,会更改显示的 Order_Details。 有关更多信息,请参见How to: Bind to Hierarchical Data and Create a Master/Details View

备注

Silverlight 3 不支持 CollectionViewSource 类。

此示例演示在数据服务启用分页的情况下,如何绑定 OData 源。 OData 协议 2.0 版及更高版本支持数据源分页。

应用程序访问的 Northwind 示例数据服务是在完成 WCF 数据服务 快速入门时创建的。 此外,还可以使用 OData 网站上发布的公共 Northwind 示例数据服务;该示例数据服务是只读的,当尝试保存更改时会返回错误。

在 Northwind 示例数据服务中启用分页

  1. 在**“解决方案资源管理器”**中,在 ASP.NET 项目下,双击“Northwind.svc”。

    这会打开 Northwind 示例数据服务的代码页。

  2. 在数据服务的代码中,将下面的代码追加到 InitializeService 方法:

    ' Set the data service version to V2 to support paging.
    config.DataServiceBehavior.MaxProtocolVersion = _
        System.Data.Services.Common.DataServiceProtocolVersion.V2
    
    ' Set paging limits for the Customers and Orders entity sets.
    ' These limits are set very low to demonstrate paging with the 
    ' Northwind database. Paging should be configured to optimize
    ' data service performance.
    config.SetEntitySetPageSize("Orders", 2)
    config.SetEntitySetPageSize("Order_Details", 2)
    
    // Set the data service version to V2 to support paging.
    config.DataServiceBehavior.MaxProtocolVersion = 
        System.Data.Services.Common.DataServiceProtocolVersion.V2;
    
    // Set paging limits for the Customers and Orders entity sets.
    // These limits are set very low to demonstrate paging with the 
    // Northwind database. Paging should be configured to optimize
    // data service performance.
    config.SetEntitySetPageSize("Orders", 2);
    config.SetEntitySetPageSize("Order_Details", 2);
    

    这将启用 Orders 和 Order_Details 实体集的分页。

创建 Silverlight 项目的应用程序

  1. 在**“解决方案资源管理器”中,右击“解决方案”,指向“添加”,然后选择“新建项目”**。

  2. 在**“添加新项目”对话框中,从“类别”窗格选择“Silverlight”,然后选择“Silverlight 应用程序”**模板。 将该项目命名为 DataBindingSample。

  3. 在**“添加 Silverlight 应用程序”对话框中,选择“在解决方案中的新网站或现有网站上承载 Silverlight 应用程序”。 选择“添加引用该应用程序的测试页”“使其成为开始页”**.

  4. 单击**“确定”**。

    这将创建 Silverlight 应用程序。

在项目中添加数据服务引用

  1. 右击 DataBindingSample 项目,单击**“添加服务引用”,然后单击“发现”**。

    这将显示在第一个任务中创建的 Northwind 数据服务。

  2. 在**“命名空间”文本框中,键入 Northwind,然后单击“确定”**。

    这将在项目中添加一个新的代码文件,其中包含用于作为对象访问数据服务资源并与其交互的数据类。 这些数据类是在命名空间 DataBindingSample.Northwind 中创建的。

定义客户端应用程序用户界面

  1. 在**“解决方案资源管理器”中的“DataBindingSample”下,右击“引用”,然后单击“添加引用”**。

    这会显示**“添加引用”**对话框。

  2. 选择 System.Windows.Controls.Data,然后单击**“确定”**。

  3. 在**“解决方案资源管理器”**中,双击 MainPage.xaml 文件。 这将打开 Page 类的 XAML 标记,该类是 Silverlight 应用程序的用户界面。

  4. 将现有 XAML 标记替换为下面的标记,这些标记定义应用程序用户界面:

    <!-- NOTE: 
      By convention, the sdk prefix indicates a URI-based XAML namespace declaration 
      for Silverlight SDK client libraries. This namespace declaration is valid for 
      Silverlight 4 only. In Silverlight 3, you must use individual XAML namespace 
      declarations for each CLR assembly and namespace combination outside the scope 
      of the default Silverlight XAML namespace. For more information, see the help 
      topic "Prefixes and Mappings for Silverlight Libraries". 
    -->
    <UserControl  x:Class="MainPage"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"            
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Loaded="MainPage_Loaded" mc:Ignorable="d" 
                  xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
                  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
                  xmlns:my="clr-namespace:DataBindingSample.Northwind" 
                  d:DesignHeight="371" d:DesignWidth="0">
        <UserControl.Resources>
            <CollectionViewSource x:Key="OrdersViewSource" 
                                  d:DesignSource="{d:DesignInstance my:Order, CreateList=True}" />
            <CollectionViewSource x:Key="OrdersOrder_DetailsViewSource" 
                                  Source="{Binding Path=Order_Details, Source={StaticResource OrdersViewSource}}" />
        </UserControl.Resources>
        <StackPanel Orientation="Vertical" Margin="10" Height="Auto" Name="LayoutRoot" Width="450">
            <StackPanel Orientation="Horizontal">
                <sdk:Label Content="Customer ID:"/>
                <TextBox Name="customerId" Text="ALFKI" Margin="10" Width="100"/>
                <Button Name="getCustomerOrders" Content="Get Orders" Height="25" Width="80" 
                        Click="getCustomerOrders_Click" />
            </StackPanel>
            <StackPanel Name="ordersStackPanel" VerticalAlignment="Top" Orientation="Vertical"  
                        DataContext="{StaticResource OrdersViewSource}">
                <Grid HorizontalAlignment="Left" Name="ordersGrid" VerticalAlignment="Top">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <sdk:Label Content="Order:" Grid.Column="0" Grid.Row="0" 
                               HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                    <ComboBox DisplayMemberPath="OrderID" Grid.Column="1" Grid.Row="0" Height="23" 
                              HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="3" 
                              Name="OrderIDComboBox" VerticalAlignment="Center" Width="120" 
                              SelectionChanged="ordersList_SelectionChanged">
                        <ComboBox.ItemsPanel>
                            <ItemsPanelTemplate>
                                <VirtualizingStackPanel />
                            </ItemsPanelTemplate>
                        </ComboBox.ItemsPanel>
                    </ComboBox>
                    <sdk:Label Content="Freight:" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" 
                               Margin="3" VerticalAlignment="Center" />
                    <TextBox Grid.Column="3" Grid.Row="0" Height="23" HorizontalAlignment="Left" 
                             Margin="3" Name="FreightTextBox" Text="{Binding Path=Freight, Mode=TwoWay, 
                        ValidatesOnExceptions=true, NotifyOnValidationError=true}" 
                             VerticalAlignment="Center" Width="120" />
                    <sdk:Label Content="Required Date:" Grid.Column="0" Grid.Row="1" 
                               HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                    <sdk:DatePicker Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" 
                                    Margin="3" Name="RequiredDateDatePicker" 
                                    SelectedDate="{Binding Path=RequiredDate, Mode=TwoWay, 
                        ValidatesOnExceptions=true, NotifyOnValidationError=true}" 
                                    VerticalAlignment="Center" Width="120" />
                    <sdk:Label Content="Order Date:" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Left" 
                               Margin="3" VerticalAlignment="Center" />
                    <TextBox Grid.Column="3" Grid.Row="1" Height="23" HorizontalAlignment="Left" 
                             Width="120" Margin="3" Name="OrderDateTextBlock" Text="{Binding Path=OrderDate}" 
                             VerticalAlignment="Center" />
                </Grid>
            </StackPanel>
            <StackPanel DataContext="{StaticResource OrdersOrder_DetailsViewSource}" Orientation="Vertical">
                <sdk:Label Content="Order items:" Margin="10"/>
                <sdk:DataGrid AutoGenerateColumns="False" Height="170" ItemsSource="{Binding}" 
                              Name="Order_DetailsDataGrid"  RowDetailsVisibilityMode="VisibleWhenSelected" 
                              Width="400">
                    <sdk:DataGrid.Columns>
                        <sdk:DataGridTextColumn x:Name="ProductIDColumn" Binding="{Binding Path=ProductID}" 
                                                Header="Product" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="QuantityColumn" Binding="{Binding Path=Quantity}" 
                                                Header="Quantity" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="DiscountColumn" Binding="{Binding Path=Discount}" 
                                                Header="Discount" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="UnitPriceColumn" Binding="{Binding Path=UnitPrice}" 
                                                Header="Unit Price" Width="SizeToHeader" />
                    </sdk:DataGrid.Columns>
                </sdk:DataGrid>
            </StackPanel>
            <Button Name="saveChangesButton" Content="Save Changes" 
                    HorizontalAlignment="Right" Click="saveChangesButton_Click"  
                    Width="100" Height="25" Margin="10"/>
    
        </StackPanel>
    </UserControl>
    

    备注

    对于 C# 应用程序,必须在 UserControl 的 Class 特性中包括命名空间。对于 Visual Basic 应用程序,默认命名空间不是必需的。

在 Silverlight 应用程序中添加将数据服务数据绑定到控件的代码

  1. 在**“解决方案资源管理器”中的“DataBindingSample”**下,打开 MainPage.xaml 文件的代码页,然后添加下列 using 语句(在 Visual Basic 中为 Imports)。

    using System.Windows.Data;
    using System.Data.Services.Client;
    using DataBindingSample.Northwind;
    
  2. 将以下声明添加到 MainPage 类中:

    Dim context As NorthwindEntities
    Dim trackedOrders As DataServiceCollection(Of Order)
    Dim selectedOrder As Order
    Dim ordersViewSource As CollectionViewSource
    
    NorthwindEntities context;
    DataServiceCollection<Order> trackedOrders;
    Order selectedOrder;
    CollectionViewSource ordersViewSource; 
    
  3. 将下面的 MainPage_Loaded 方法添加到 MainPage 类:

    Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Initialize the data service context.
        context = _
                New NorthwindEntities(New Uri("https://localhost:54321/Northwind.svc"))
    
        ' Initialize the binding and view source collections.
        trackedOrders = New DataServiceCollection(Of Order)()
        ordersViewSource = CType(Me.Resources("OrdersViewSource"), CollectionViewSource)
    
        ' Define a handler for the LoadCompleted event of the collection.
        AddHandler trackedOrders.LoadCompleted, _
        AddressOf trackedOrders_LoadCompleted
    End Sub
    
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Initialize the data service context.
        context =
            new NorthwindEntities(new Uri("Northwind.svc", UriKind.Relative));
    
        // Initialize the binding and view source collections.
        trackedOrders = new DataServiceCollection<Order>();
        ordersViewSource = (CollectionViewSource)this.Resources["OrdersViewSource"];
    
        // Define a handler for the LoadCompleted event of the binding collection.
        trackedOrders.LoadCompleted +=
            new EventHandler<LoadCompletedEventArgs>(trackedOrders_LoadCompleted);
    }
    

    加载页面后,此代码初始化绑定集合和内容,并注册相应的方法来处理绑定集合引发的 LoadCompleted 事件。

  4. 在 MainPage 类中插入以下代码:

    Private Sub getCustomerOrders_Click(ByVal sender As Object, _
                                        ByVal e As RoutedEventArgs)
    
        ' Define a query that returns orders for a give customer.
        Dim query = From orderByCustomer In context.Orders _
                        Where orderByCustomer.Customer.CustomerID = _
                        Me.customerId.Text _
                        Select orderByCustomer
    
        ' Asynchronously load the result of the query.
        trackedOrders.LoadAsync(query)
    
        ' Disable the button until the loading is complete.
        getCustomerOrders.IsEnabled = False
    End Sub
    Private Sub trackedOrders_LoadCompleted(ByVal sender As Object, _
                                            ByVal e As LoadCompletedEventArgs)
        If e.Error Is Nothing Then
            ' Load all pages of Orders before binding.
            If trackedOrders.Continuation IsNot Nothing Then
                trackedOrders.LoadNextPartialSetAsync()
            Else
    
                ' Bind the root StackPanel element to the collection
                ' related object binding paths are defined in the XAML.
                ordersViewSource.Source = trackedOrders
    
                ' Re-enable the button since the loading is complete.
                getCustomerOrders.IsEnabled = True
            End If
        Else
            MessageBox.Show(String.Format("An error has occured: {0}", e.Error.Message))
            getCustomerOrders.IsEnabled = True
        End If
    End Sub
    
    private void getCustomerOrders_Click(object sender, RoutedEventArgs e)
    {
        // Reset the grids.
        ordersViewSource.Source = null;
    
        // Define a query that returns orders for a give customer.
        var query = from orderByCustomer in context.Orders
                    where orderByCustomer.Customer.CustomerID == this.customerId.Text
                    select orderByCustomer;
    
        // Asynchronously load the result of the query.
        trackedOrders.LoadAsync(query);
    
        // Disable the button until the loading is complete.
        getCustomerOrders.IsEnabled = false;
    }
    
    private void trackedOrders_LoadCompleted(object sender, LoadCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            // Load all pages of Orders before binding.
            if (trackedOrders.Continuation != null)
            {
                trackedOrders.LoadNextPartialSetAsync();
            }
            else
            {
                // Bind the root StackPanel element to the collection;
                // related object binding paths are defined in the XAML.
                ordersViewSource.Source = trackedOrders;
    
                // Re-enable the button since the loading is complete.
                getCustomerOrders.IsEnabled = true;
            }
        }
        else
        {
            MessageBox.Show(string.Format("An error has occured: {0}",e.Error.Message));
            getCustomerOrders.IsEnabled = true;
        }
    }
    

    单击 getCustomerOrders 按钮时,将执行下面的操作:

    • 对绑定集合调用 LoadAsync 方法来执行提供的查询,该查询返回按提供的客户 ID 筛选的订单。

    • 只要 Continuation 属性返回值,就调用 LoadNextPartialSetAsync 方法加载后续结果页。

    • 加载的 Order 对象的集合绑定到 CollectionViewSourceSource 属性,CollectionViewSource 是页面中所有控件的主绑定对象。

  5. 在 MainPage 类中插入以下代码:

    Private Sub ordersList_SelectionChanged(ByVal sender As Object, _
                                            ByVal e As SelectionChangedEventArgs)
        ' Get the selected Order in the DataGrid.    
        Dim ordersList As ComboBox = CType(sender, ComboBox)
        selectedOrder = CType(ordersList.SelectedItem, Order)
    
        If Not selectedOrder Is Nothing Then
            ' Asynchronously load related items, if they are not already loaded.
            If selectedOrder.Order_Details.Count = 0 Then
    
                ' Register the method to handle the LoadCompleted event.
                AddHandler selectedOrder.Order_Details.LoadCompleted, _
                AddressOf Order_Details_LoadCompleted
    
                Try
                    ' Load the related items.
                    selectedOrder.Order_Details.LoadAsync()
                Catch ex As InvalidOperationException
                    MessageBox.Show(String.Format("An error has occured: {0}", _
                            ex.Message))
                End Try
            End If
        End If
    End Sub
    Private Sub Order_Details_LoadCompleted(ByVal sender As Object, _
                                            ByVal e As LoadCompletedEventArgs)
        Dim trackedItems As DataServiceCollection(Of Order_Detail) = _
            CType(sender, DataServiceCollection(Of Order_Detail))
    
        ' Load any remaining pages of Order_Details.
        If Not trackedItems.Continuation Is Nothing Then
            Try
                trackedItems.LoadNextPartialSetAsync()
            Catch ex As InvalidOperationException
                MessageBox.Show(String.Format("An error has occured: {0}", _
                        ex.Message))
            End Try
        End If
    End Sub
    
    private void ordersList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // Get the selected Order in the DataGrid.    
        ComboBox ordersList = sender as ComboBox;
        selectedOrder = ((Order)(ordersList.SelectedItem));
    
        if (selectedOrder != null)
        {
            // Asynchronously load related items, if they are not already loaded.
            if (selectedOrder.Order_Details.Count == 0)
            {
                // Register the method to handle the LoadCompleted event.
                selectedOrder.Order_Details.LoadCompleted +=
                    new EventHandler<LoadCompletedEventArgs>(
                        Order_Details_LoadCompleted);
                try
                {
                    // Load the related items.
                    selectedOrder.Order_Details.LoadAsync();
                }
                catch (InvalidOperationException ex)
                {
                    MessageBox.Show(string.Format("An error has occured: {0}",
                        ex.Message));
                }
            }
        }
    }
    
    void Order_Details_LoadCompleted(object sender, LoadCompletedEventArgs e)
    {
        DataServiceCollection<Order_Detail> trackedItems = 
            sender as DataServiceCollection<Order_Detail>;
    
        // Load any remaining pages of Order_Details.
        if (trackedItems.Continuation != null)
        {
            try
            {
                trackedItems.LoadNextPartialSetAsync();
            }
            catch (InvalidOperationException ex)
            {
                MessageBox.Show(string.Format("An error has occured: {0}",
                    ex.Message));
            }
        }
    }
    

    ordersList_SelectionChanged 方法处理 SelectionChanged 事件。 当用户选择 ComboBox 中的订单时,将执行下面的操作:

  6. 将用于保存更改的下列代码插入到 MainPage 类:

    ' We need to persist the result of an operation 
    ' to be able to invoke the dispatcher.
    Private currentResult As IAsyncResult
    Private Sub saveChangesButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Define the delegate to callback into the process
        Dim callback As AsyncCallback = AddressOf OnChangesSaved
        Try
            ' Start the saving changes operation. This needs to be a 
            ' batch operation in case we are added a new object with 
            ' a new relationship.
            context.BeginSaveChanges(SaveChangesOptions.Batch, _
                    callback, context)
    
        Catch ex As Exception
            MessageBox.Show(String.Format( _
                            "The changes could not be saved to the data service.\n" _
                            & "The following error occurred: {0}", ex.Message))
        End Try
    End Sub
    Private Sub OnChangesSaved(ByVal result As IAsyncResult)
        ' Persist the result for the delegate.
        currentResult = result
    
        ' Use the Dispatcher to ensure that the 
        ' asynchronous call returns in the correct thread.
        Dispatcher.BeginInvoke(AddressOf ChangesSavedByDispatcher)
    End Sub
    Private Sub ChangesSavedByDispatcher()
    
        Dim errorOccured As Boolean = False
        context = CType(currentResult.AsyncState, NorthwindEntities)
    
        Try
            ' Complete the save changes operation and display the response.
            Dim response As DataServiceResponse = _
            context.EndSaveChanges(currentResult)
    
            For Each changeResponse As ChangeOperationResponse In response
                If changeResponse.Error IsNot Nothing Then errorOccured = True
            Next
            If Not errorOccured Then
                MessageBox.Show("The changes have been saved to the data service.")
            Else
                MessageBox.Show("An error occured. One or more changes could not be saved.")
            End If
        Catch ex As Exception
            ' Display the error from the response.
            MessageBox.Show(String.Format("The following error occured: {0}", ex.Message))
        End Try
    End Sub
    
    private void saveChangesButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            // Start the saving changes operation. This needs to be a 
            // batch operation in case we are added a new object with 
            // a new relationship.
            context.BeginSaveChanges(SaveChangesOptions.Batch,
                OnChangesSaved, context);
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("The changes could not be saved to the data service.\n"
                + "The following error occurred: {0}", ex.Message));
        }
    }
    
            private void OnChangesSaved(IAsyncResult result)
            {
                bool errorOccured = false;
    
                // Use the Dispatcher to ensure that the 
                // asynchronous call returns in the correct thread.
                Dispatcher.BeginInvoke(() =>
                {
                    context = result.AsyncState as NorthwindEntities;
    
                    try
                    {
                        // Complete the save changes operation and display the response.
                        DataServiceResponse response = context.EndSaveChanges(result);
    
                        foreach (ChangeOperationResponse changeResponse in response)
                        {
                            if (changeResponse.Error != null) errorOccured = true;
                        }
                        if (!errorOccured)
                        {
                            MessageBox.Show("The changes have been saved to the data service.");
                        }
                        else
                        {
                            MessageBox.Show("An error occured. One or more changes could not be saved.");
                        }
                    }
                    catch (Exception ex)
                    {
                        // Display the error from the response.
                        MessageBox.Show(string.Format("The following error occured: {0}", ex.Message));
                    }
                }
                );
            }
    

    此代码将数据绑定控件中发生的更改异步发送回数据服务。

请参阅

其他资源

用于 Silverlight 的 WCF Data Services 快速入门

WCF Data Services (Silverlight)

WCF Data Services Tasks for Silverlight

加载延迟的内容 (WCF Data Services)

将数据绑定到控件 (WCF Data Services)