72、第20章:数据绑定

第20章:数据绑定

1. 列表信息显示

在列表中显示信息时,可以采用以下两种方法:
- 重写 ToString() 方法 :重写 Product.ToString() 方法以返回更有用的信息,例如返回包含每个商品型号和名称的字符串。这种方法可以在列表中显示多个属性,就像在 Customer 类中组合 FirstName LastName 属性一样。
- 提供数据模板 :通过提供数据模板,可以展示任意排列的属性值(还能包含固定文本)。

确定列表信息的显示方式后,接下来要解决的问题是在列表下方的网格中显示当前所选项目的详细信息。这需要响应 SelectionChanged 事件,并更改包含产品详细信息的网格的 DataContext ,示例代码如下:

Private Sub lstProducts_SelectionChanged(ByVal sender As Object, _
  ByVal e As SelectionChangedEventArgs)
    gridProductDetails.DataContext = lstProducts.SelectedItem
End Sub

提示 :若要防止字段被编辑,可将 TextBox.IsReadOnly 属性设置为 True ,或者使用 TextBlock 等只读控件。

此示例已具备完整功能,可编辑产品项、切换列表项,编辑内容会成功提交到内存中的数据对象。修改影响列表显示文本的值时,列表中的对应项会自动刷新。不过,更改仅在控件失去焦点时才会提交。若更改文本框中的值后点击列表中的新项,编辑的值会被丢弃,所选数据对象的信息将被加载。若不希望出现这种情况,可以添加代码强制提交更改。在 Silverlight 中,可通过编程方式将焦点发送到另一个控件(必要时可以是不可见的控件),调用其 Focus() 方法提交更改,再将焦点返回到原文本框。也可以在 TextChanged 事件中使用此代码,或者添加“保存”或“更新”按钮,点击按钮会自动更改焦点并触发更新。

2. 集合项的插入和移除

Silverlight 在生成与 Web 服务通信的客户端代码时会进行转换,Web 服务可能返回数组或 List 集合,但客户端代码会将对象放入 ObservableCollection 中。即使返回的对象具有集合属性,也会进行同样的转换。这是因为客户端不清楚 Web 服务器返回的集合类型,为确保安全,Silverlight 选择使用功能更丰富的 ObservableCollection

ObservableCollection 具备添加和移除项的功能,例如使用删除按钮执行以下代码删除项:

Private Sub cmdDeleteProduct_Click(ByVal sender As Object, _
  ByVal e As RoutedEventArgs)
    products.Remove(CType(lstProducts.SelectedItem, Product))
End Sub

此代码对数组无效,对 List 集合虽能移除项,但绑定列表中被删除的项仍会显示。若要实现集合更改跟踪,需使用实现 INotifyCollectionChanged 接口的集合,在 Silverlight 中,只有 ObservableCollection 类满足此要求。使用 ObservableCollection 执行上述代码时,绑定列表会立即刷新。当然,还需创建能永久提交此类更改的数据访问代码,如从后端数据库插入和移除产品的 Web 服务方法。

3. 绑定到 LINQ 表达式

Silverlight 支持 Language Integrated Query(LINQ),这是 .NET 3.5 引入的通用查询语法。LINQ 可用于任何有 LINQ 提供程序的数据源,借助 Silverlight 内置的支持,可使用结构相似的 LINQ 查询从内存集合或 XML 文件中检索数据,还能对检索的数据进行过滤、排序、分组和转换。

例如,有一个名为 products Product 对象集合,要创建一个仅包含成本超过 100 美元产品的新集合,使用过程式代码可这样写:

' 获取完整的产品列表。
Dim products As List(Of Product) = App.StoreDb.GetProducts()

' 创建一个包含匹配产品的新集合。
Dim matches As New List(Of Product)()

For Each product As Product In products
    If product.UnitCost >= 100 Then
        matches.Add(product)
    End If
Next

使用 LINQ 则更简洁:

' 获取完整的产品列表。
Dim products As List(Of Product) = App.StoreDb.GetProducts()

' 创建一个包含匹配产品的新集合。
Dim matches As IEnumerable(Of Product) = _
  From product In products _
  Where product.UnitCost >= 100 _
  Select product

此示例使用 LINQ to Objects,即使用 LINQ 表达式查询内存集合中的数据。LINQ 表达式使用 From In Where Select 等新语言关键字,这些关键字是 VB 语言的一部分。

LINQ 围绕 IEnumerable(Of T) 接口,无论使用何种数据源,每个 LINQ 表达式都会返回实现 IEnumerable(Of T) 接口的对象。由于 IEnumerable(Of T) 扩展自 IEnumerable ,因此可像绑定普通集合一样在 Silverlight 页面中绑定它:

lstProducts.ItemsSource = matches

List ObservableCollection 类不同, IEnumerable(Of T) 接口不提供添加或移除项的方法。若需要此功能,需使用 ToArray() ToList() 方法将 IEnumerable(Of T) 对象转换为数组或 List 集合。例如,使用 ToList() 方法将 LINQ 查询结果转换为强类型的 Product 对象 List 集合:

Dim productMatches As List(Of Product) = matches.ToList()

ToList() 是扩展方法,定义在 System.Linq.Enumerable 辅助类中,所有 IEnumerable(Of T) 对象都可使用,但需导入 System.Linq 命名空间。 ToList() 方法会立即计算 LINQ 表达式,最终得到一个普通的 List 集合。若要使集合可编辑并让更改立即在绑定控件中显示,需将 List 的内容复制到新的 ObservableCollection 中。

4. 主 - 详细信息显示

可以将其他元素绑定到列表的 SelectedItem 属性,以显示当前所选项目的更多详细信息。利用类似技术可构建数据的主 - 详细信息显示界面,例如创建一个显示类别列表和产品列表的页面,用户选择一个类别时,第二个列表将显示该类别下的产品。

要实现此功能,需要一个父数据对象通过属性提供相关子数据对象的集合。例如,创建一个 Category 类,该类具有一个名为 Category.Products 的属性,用于存储属于该类别的产品。 Category 类可实现 INotifyPropertyChanged 接口以提供更改通知,完整代码如下:

Public Class Category
    Implements INotifyPropertyChanged
    Private _categoryName As String
    Public Property CategoryName() As String
        Get
            Return _categoryName
        End Get
        Set(ByVal value As String)
            _categoryName = value
            OnPropertyChanged(New PropertyChangedEventArgs("CategoryName"))
        End Set
    End Property

    Private _products As List(Of Product)
    Public Property Products() As List(Of Product)
        Get
            Return _products
        End Get
        Set(ByVal value As List(Of Product))
            _products = value
            OnPropertyChanged(New PropertyChangedEventArgs("Products"))
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler
    Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
        If PropertyChangedEvent IsNot Nothing Then
            RaiseEvent PropertyChanged(Me, e)
        End If
    End Sub

    Public Sub New(ByVal categoryName As String, _
      ByVal products As List(Of Product))
        Me.CategoryName = categoryName
        Me.Products = products
    End Sub

    Public Sub New()
    End Sub
End Class

使用 Category 类时,还需修改之前的数据访问代码,从数据库查询产品和类别的信息。示例中使用名为 GetCategoriesWithProducts() 的 Web 服务方法,该方法返回一个 Category 对象集合,每个 Category 对象包含一个嵌套的 Product 对象集合:

<OperationContract()> _
Public Function GetCategoriesWithProducts() As List(Of Category)
    ' 使用 GetProducts 存储过程查询产品信息。
    Dim con As New SqlConnection(connectionString)
    Dim cmd As New SqlCommand("GetProducts", con)
    cmd.CommandType = CommandType.StoredProcedure

    ' 将结果临时存储在 DataSet 中。
    Dim adapter As New SqlDataAdapter(cmd)
    Dim ds As New DataSet()
    adapter.Fill(ds, "Products")

    ' 使用 GetCategories 存储过程查询类别信息。
    cmd.CommandText = "GetCategories"
    adapter.Fill(ds, "Categories")

    ' 在这些表之间建立关系。
    ' 这便于发现每个类别中的产品。
    Dim relCategoryProduct As New DataRelation("CategoryProduct", _
      ds.Tables("Categories").Columns("CategoryID"), _
      ds.Tables("Products").Columns("CategoryID"))
    ds.Relations.Add(relCategoryProduct)

    ' 构建 Category 对象集合。
    Dim categories As List(Of Category) = New List(Of Category)()

    For Each categoryRow As DataRow In ds.Tables("Categories").Rows
        ' 为该类别添加嵌套的 Product 对象集合。
        Dim products As List(Of Product) = New List(Of Product)()
        For Each productRow As DataRow In _
          categoryRow.GetChildRows(relCategoryProduct)
            products.Add(New Product(productRow("ModelNumber").ToString(), _
              productRow("ModelName").ToString(), _
              Convert.ToDouble(productRow("UnitCost")), _
              productRow("Description").ToString()))
        Next

        categories.Add( _
          New Category(categoryRow("CategoryName").ToString(), products))
    Next
    Return categories
End Function

要显示这些数据,需要两个列表:

<ListBox x:Name="lstCategories" DisplayMemberPath="CategoryName"
  SelectionChanged="lstCategories_SelectionChanged"></ListBox>
<ListBox x:Name="lstProducts" Grid.Row="1" DisplayMemberPath="ModelName">
</ListBox>

调用 GetCategoriesWithProducts() 方法获取集合后,设置最上面列表的 ItemsSource 以显示类别:

lstCategories.ItemsSource = e.Result

要显示相关产品,需在第一个列表中的项目被点击时做出响应,并将第二个列表的 ItemsSource 属性设置为所选 Category 对象的 Category.Products 属性:

lstProducts.ItemsSource = (CType(lstCategories.SelectedItem, Category)).Products

也可以在 XAML 中使用另一个绑定表达式实现此步骤,关键是使用 ListBox ComboBox 提供的 SelectedValuePath 属性。通过 SelectedValuePath ,可让列表控件通过 SelectedValue 属性公开绑定对象的特定属性。例如,可让 ListBox 通过 ListBox.SelectedValue 属性提供 Category.Products 集合:

<ListBox x:Name="lstCategories" DisplayMemberPath="CategoryName"
  SelectedValuePath="Products"></ListBox>

然后使用 SelectedValuePath 为第二个列表框编写绑定表达式:

<ListBox x:Name="lstProducts" Grid.Row="1" DisplayMemberPath="ModelName"
 ItemsSource="{Binding ElementName=lstCategories, Path=SelectedValue}">
</ListBox>

这样,在第一个列表框中选择一个类别,第二个列表框将显示该类别的产品列表,无需编写代码。

注意 :此示例会一次性从 Web 服务下载完整的类别和产品信息。若应用程序使用的产品目录很大,且大多数用户只需查看一两个类别,可考虑仅下载需要查看的产品子集。此时,需响应类别列表框的 SelectionChanged 事件,检索当前所选类别的 CategoryID ,然后进行新的 Web 服务调用以获取该类别中的产品。但要注意,这种方式会增加数据库服务器的工作量,可能会降低应用程序的性能,需谨慎权衡,并考虑在首次下载产品信息后在客户端进行缓存。

5. 验证

Silverlight 数据绑定系统遇到无效数据时通常会忽略。编辑双向字段时可能出现以下三种错误:
- 数据类型不正确 :例如,像 UnitCost 这样的数值属性无法容纳字母或特殊字符,也不能存储极大的数字(大于 1.79769313486231570E+308)。
- 属性设置器异常 :例如, UnitCost 属性可能会进行范围检查,若尝试设置负数会抛出异常。
- 只读属性 :只读属性根本无法设置。

遇到这些错误时,可能难以察觉,因为 Silverlight 数据绑定系统不会提供任何视觉反馈,错误值会保留在绑定控件中,但不会应用到绑定对象。为避免混淆,应尽快提醒用户错误。

6. 错误通知

实现错误通知的第一步是将绑定的 ValidatesOnExceptions 属性设置为 True ,这会让数据绑定系统关注所有错误,包括类型转换器或属性设置器中出现的错误。若 ValidatesOnException 设置为 False (默认值),数据绑定系统遇到这些情况时会静默失败,数据对象不会更新,但错误值会保留在绑定控件中。

以下是将此属性应用于 UnitCost 绑定的示例:

<TextBox Margin="5" Grid.Row="2" Grid.Column="1" x:Name="txtUnitCost"
  Text="{Binding UnitCost, Mode=TwoWay, ValidatesOnExceptions=True}"></TextBox>

此简单更改使应用程序能够捕获并显示错误,但前提是使用支持 ValidationState 组控件状态的控件进行双向数据绑定。以下是一些支持此功能的控件(无需额外工作):
- TextBox
- PasswordBox
- CheckBox
- RadioButton
- ListBox
- ComboBox

验证时,控件必须支持三种状态: Valid InvalidUnfocused InvalidFocused ,这些状态构成 ValidationState 组,使控件在包含无效数据时外观可以变化。

以包含无效数据的文本框为例,假设 Product 类使用以下代码捕获负价格并抛出异常:

Private _unitCost As Double
Public Property UnitCost() As Double
    Get
        Return _unitCost
    End Get
    Set(ByVal value As Double)
        If value < 0 Then
            Throw New ArgumentException("Can't be less than 0.")
        End If

        _unitCost = value
    End Set
End Property

若用户输入负数,属性设置器会抛出 ArgumentException 。由于 ValidatesOnException 设置为 True ,此异常会被数据绑定系统捕获,文本框的 ValidationState 会从 Valid 切换到 InvalidFocused (若文本框当前有焦点)或 InvalidUnfocused (若文本框无焦点)。

提示 :若在 Visual Studio 中设置为中断所有异常,当抛出 ArgumentException 时,Visual Studio 会通知并进入中断模式。若要继续查看异常到达数据绑定系统时的情况,可选择“调试” -> “继续”或按快捷键 F5。

在未聚焦状态下,文本框会显示深红色边框,右上角有错误通知图标(小红三角形);在聚焦状态下,或用户将鼠标悬停在错误图标上时,异常消息文本会显示在弹出的红色警告气球中。

总结

本文详细介绍了 Silverlight 中的数据绑定相关知识,包括列表信息显示、集合项操作、LINQ 表达式绑定、主 - 详细信息显示以及数据验证和错误通知等内容。通过这些技术,可以构建功能强大、交互性好的数据显示和编辑界面。在实际应用中,需根据具体需求选择合适的方法,并注意处理数据绑定过程中可能出现的问题,如数据验证和性能优化等。

第20章:数据绑定

7. 数据绑定的流程图分析

为了更清晰地理解数据绑定的流程,下面给出一个简单的 mermaid 流程图:

graph LR
    A[开始] --> B[确定列表显示方式]
    B --> C{选择方法}
    C -->|重写 ToString() 方法| D[返回有用信息]
    C -->|提供数据模板| E[展示属性值排列]
    D --> F[处理列表选择事件]
    E --> F
    F --> G[设置网格 DataContext]
    G --> H[编辑数据]
    H --> I{控件失去焦点?}
    I -->|是| J[提交更改]
    I -->|否| K[数据未提交]
    J --> L[更新内存数据对象]
    L --> M[列表项自动刷新?]
    M -->|是| N[列表更新]
    M -->|否| O[列表未更新]
    N --> P[插入或移除集合项]
    P --> Q{使用 ObservableCollection?}
    Q -->|是| R[绑定列表立即刷新]
    Q -->|否| S[绑定列表不刷新]
    R --> T[绑定到 LINQ 表达式]
    S --> T
    T --> U[获取查询结果]
    U --> V{结果为 IEnumerable(Of T)?}
    V -->|是| W[转换为 List 或数组]
    V -->|否| X[直接使用]
    W --> Y[主 - 详细信息显示]
    X --> Y
    Y --> Z[设置列表 ItemsSource]
    Z --> AA[选择类别]
    AA --> AB[显示相关产品]
    AB --> AC[数据验证]
    AC --> AD{是否设置 ValidatesOnExceptions?}
    AD -->|是| AE[捕获并显示错误]
    AD -->|否| AF[静默失败]
    AE --> AG[结束]
    AF --> AG

这个流程图展示了从确定列表显示方式开始,到数据验证结束的整个数据绑定流程。它涵盖了前面提到的各个技术点,帮助我们更直观地理解它们之间的关系。

8. 不同数据绑定场景对比
场景 实现方法 优点 缺点
列表信息显示 重写 ToString() 方法或提供数据模板 可显示多个属性,灵活展示信息 需编写额外代码
集合项操作 使用 ObservableCollection 绑定列表可实时刷新 需额外处理数据访问代码
绑定到 LINQ 表达式 编写 LINQ 查询 代码简洁,可进行数据过滤等操作 需了解 LINQ 语法
主 - 详细信息显示 绑定 SelectedItem 属性或使用 SelectedValuePath 实现关联数据显示,可减少代码量 数据量大时可能影响性能
数据验证 设置 ValidatesOnExceptions 属性 可捕获并显示错误,提醒用户 部分控件需支持 ValidationState 组状态
9. 数据绑定的最佳实践
  • 选择合适的集合类型 :对于需要实时更新绑定列表的场景,优先使用 ObservableCollection ,避免使用普通数组或 List 集合导致绑定列表不刷新的问题。
  • 合理使用 LINQ 表达式 :LINQ 可简化数据查询和处理,但对于复杂的查询,要注意性能问题。在使用 ToList() 等方法时,确保已导入 System.Linq 命名空间。
  • 及时处理数据验证 :在数据绑定过程中,尽早设置 ValidatesOnExceptions 属性,让用户及时了解输入错误,提高用户体验。
  • 优化主 - 详细信息显示 :如果数据量较大,考虑按需加载数据,避免一次性下载过多信息,可通过响应 SelectionChanged 事件进行部分数据的获取。
10. 常见问题及解决方案
  • 绑定列表不刷新 :检查是否使用了 ObservableCollection ,若使用普通集合,需将其替换为 ObservableCollection 以支持集合更改跟踪。
  • LINQ 查询无法使用 ToList() 方法 :确保已导入 System.Linq 命名空间,因为 ToList() 是扩展方法,依赖该命名空间。
  • 数据验证无反馈 :检查绑定的 ValidatesOnExceptions 属性是否设置为 True ,以及使用的控件是否支持 ValidationState 组控件状态。
11. 总结与展望

通过前面的介绍,我们了解了 Silverlight 数据绑定的多种技术,包括列表信息显示、集合项操作、LINQ 表达式绑定、主 - 详细信息显示以及数据验证等。这些技术为我们构建功能丰富、交互性强的数据显示和编辑界面提供了有力支持。

在未来的开发中,随着技术的不断发展,数据绑定可能会有更多的优化和改进。例如,可能会出现更简洁的语法来实现复杂的数据绑定逻辑,或者在性能方面有更大的提升。同时,对于大数据量的处理和实时更新,也需要不断探索更好的解决方案。开发者需要持续关注这些变化,以便在实际项目中更好地应用数据绑定技术。

总之,掌握数据绑定技术对于开发高质量的 Silverlight 应用程序至关重要,我们应不断学习和实践,以应对各种复杂的开发需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值