第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 应用程序至关重要,我们应不断学习和实践,以应对各种复杂的开发需求。
超级会员免费看

被折叠的 条评论
为什么被折叠?



