数据绑定中的错误处理、验证与格式化
1. 错误弹出框相关
在进行数据绑定时,红色弹出气球框用于显示错误信息。为了让其正常显示,文本框与浏览器窗口边缘之间需要有足够的空间。若文本框右侧有空间,气球框会出现在右侧;若没有,则出现在左侧。它会显示在同一位置的其他元素(如按钮或标签)之上,但不会超出浏览器窗口。若消息过长,部分内容会被截断。
将验证显示集成到控件模板有其便利之处,控件会处理视觉细节,开发者只需关注提供有用的错误消息。但缺点也很明显,如果想改变控件显示错误消息的方式(或完全禁用错误显示),就需要替换整个控件模板,且要包含所有其他无关的状态和标记细节。由于普通的控件模板通常很长,这个过程既繁琐又可能受限。
2. BindingValidationFailed 事件
错误通知能有效告知用户潜在问题,但在很多情况下,开发者需要对后续操作有更多控制,这时就需要拦截
BindingValidationFailed
事件并使用自定义代码。
要启用
BindingValidationFailed
事件,必须将
ValidatesOnExceptions
设置为
True
以检测错误,将
NotifyOnValidationError
设置为
True
以触发验证事件。示例代码如下:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1" x:Name="txtUnitCost"
Text="{Binding UnitCost, Mode=TwoWay, ValidatesOnExceptions=True,
NotifyOnValidationError=True}"></TextBox>
BindingValidationError
是一个冒泡事件,可在错误发生处(如文本框)或更高级别(如包含的
Grid
)处理。在错误发生处处理能编写针对不同字段错误的特定错误处理逻辑;在更高级别处理则可对多种不同类型的错误重用相同的逻辑,示例代码如下:
<Grid Name="gridProductDetails"
BindingValidationError="Grid_BindingValidationError">
当问题发生时,开发者可以选择显示消息或更改应用程序部分外观,
BindingValidationError
事件的强大之处在于还能执行其他操作,如更改焦点、重置错误值、尝试纠正错误或根据具体错误提供更详细的针对性帮助。以下是一个显示错误消息并将焦点返回给出错文本框的示例:
Private Sub Grid_BindingValidationError(ByVal sender As Object, _
ByVal e As ValidationErrorEventArgs)
' Display the error.
lblInfo.Text = e.Error.Exception.Message
lblInfo.Text &= Environment.NewLine & "The stored value is still: " & _
(CType(gridProductDetails.DataContext, Product)).UnitCost
' Suggest the user try again.
txtUnitCost.Focus()
End Sub
BindingValidationError
事件仅在值更改且编辑提交时发生。对于文本框,直到文本框失去焦点才会触发。若想更快捕获错误,可使用
BindingExpression.UpdateSource()
方法在用户输入时强制立即更新。
3. Validation 类
无需等待
BindingValidationError
事件来检测无效数据,可随时使用
Validation
类的静态方法检查绑定控件。
Validation.GetHasErrors()
若控件验证失败则返回
True
,
Validation.GetErrors()
返回相应的异常对象集合。
这些方法提供了额外的灵活性,例如可检查
HasErrors()
并在存在无效数据时拒绝让用户继续到新步骤或执行特定功能;也可在数据输入过程结束时使用
GetErrors()
汇总一系列错误,以便在一处提供详细的问题列表。
4. 创建具有内置验证的数据对象
早期,将验证逻辑构建到数据对象的最简单方法是为不良数据抛出异常,但这种方式存在风险。异常抛出代码可能会意外排除数据对象的合理使用情况。
大多数开发者倾向于使用其他技术标记无效数据。在 Silverlight 中,数据对象可通过实现
IDataErrorInfo
或
INotifyDataErrorInfo
接口提供内置验证。这两个接口的目标相同,即用更温和的错误通知系统取代激进的未处理异常。
IDataErrorInfo
是最早的错误跟踪接口,为向后兼容而保留;
INotifyDataErrorInfo
是 Silverlight 4 引入的类似但更丰富的接口,支持每个属性多个错误和丰富的错误对象等额外功能。
无论使用哪个接口,都需遵循以下基本步骤:
1. 在数据对象中实现
IDataErrorInfo
或
INotifyDataErrorInfo
接口。
2. 告诉数据绑定基础结构检查数据对象的错误并显示错误通知。若使用
IDataErrorInfo
接口,将
ValidatesOnDataErrors
设置为
True
;若使用
INotifyDataErrorInfo
接口,将
ValidatesOnNotifyDataErrors
设置为
True
。
3. 若想接收
BindingValidationFailed
事件,可选择将
NotifyOnValidationError
设置为
True
。
4. 若想捕获其他类型的错误(如数据转换问题),可选择将
ValidatesOnExceptions
属性设置为
True
。
以下是使用
INotifyDataErrorInfo
接口检测
Product
对象问题的示例:
Public Class Product
Implements INotifyPropertyChanged, INotifyDataErrorInfo
...
End Class
INotifyDataErrorInfo
接口需要三个成员:
ErrorsChanged
事件在添加或移除错误时触发;
HasErrors
属性指示数据对象是否有错误;
GetErrors()
方法提供完整的错误信息。
在实现这些方法之前,需要一种方式来跟踪代码中的错误,可使用私有集合:
Private errors As New Dictionary(Of String, List(Of String))()
为方便管理错误,
Product
类添加了
SetErrors()
和
ClearErrors()
两个私有方法:
Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) _
Implements INotifyDataErrorInfo.ErrorsChanged
Private Sub SetErrors(ByVal propertyName As String, _
ByVal propertyErrors As List(Of String))
' Clear any errors that already exist for this property.
errors.Remove(propertyName)
' Add the list collection for the specified property.
errors.Add(propertyName, propertyErrors)
' Raise the error-notification event.
RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
End Sub
Private Sub ClearErrors(ByVal propertyName As String)
' Remove the error list for this property.
errors.Remove(propertyName)
' Raise the error-notification event.
RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
End Sub
以下是确保
Product.ModelNumber
属性只能包含字母和数字的错误处理逻辑:
Private _modelNumber As String
Public Property ModelNumber() As String
Get
Return _modelNumber
End Get
Set(ByVal value As String)
_modelNumber = value
Dim valid As Boolean = True
For Each c As Char In _modelNumber
If (Not Char.IsLetterOrDigit(c)) Then
valid = False
Exit For
End If
Next
If (Not valid) Then
Dim errors As List(Of String) = New List(Of String)()
errors.Add("The ModelNumber can only contain letters and numbers.")
SetErrors("ModelNumber", errors)
Else
ClearErrors("ModelNumber")
End If
OnPropertyChanged(New PropertyChangedEventArgs("ModelNumber"))
End Set
End Property
最后实现
GetErrors()
和
HasErrors()
方法:
Public Function GetErrors(ByVal propertyName As String) As IEnumerable _
Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
If propertyName = "" Then
' Provide all the error collections.
Return (errors.Values)
Else
' Provice the error collection for the requested property
' (if it has errors).
If errors.ContainsKey(propertyName) Then
Return (errors(propertyName))
Else
Return Nothing
End If
End If
End Function
Public ReadOnly Property HasErrors As Boolean _
Implements INotifyDataErrorInfo.HasErrors
Get
' Indicate whether this entire object is error-free.
Return (errors.Count > 0)
End Get
End Property
要让 Silverlight 使用
INotifyDataErrorInfo
接口并在属性修改时检查错误,绑定的
ValidatesOnNotifyDataErrors
属性必须为
True
:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1" x:Name="txtModelNumber"
Text="{Binding ModelNumber, Mode=TwoWay, ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"></TextBox>
若使用
NotifyOnValidationError
事件,当错误集合更改时
BindingValidationError
事件将触发,但
ValidationErrorEventArgs.Exception
属性不会提供异常对象,可通过
ValidationErrorEventArgs.Error.ErrorContent
属性获取从数据对象返回的错误信息。
可以将抛出异常和使用
IDataErrorInfo
或
INotifyDataErrorInfo
接口的方法结合使用,但要注意,触发异常时数据对象的属性不会更新;而使用接口时,允许无效值但会标记,数据对象会更新,可使用通知和
BindingValidationFailed
事件通知用户。
5. 数据格式化和转换
在基本绑定中,信息从源到目标不做任何更改,但这并非总是所需的行为。数据源可能使用低级表示,不适合直接在用户界面显示,如数字代码需替换为可读字符串、数字需缩小显示、日期需长格式显示等。这时就需要将这些值转换为正确的显示形式,若使用双向绑定,还需将用户输入的数据转换为适合存储在数据对象中的表示形式。
Silverlight 提供了两个工具来帮助实现:
-
字符串格式化
:通过设置
Binding.StringFormat
属性,可将以文本形式表示的数据(如包含日期和数字的字符串)进行转换,适用于至少一半的格式化任务。
-
值转换器
:功能更强大但稍复杂,可将任何类型的源数据转换为任何类型的对象表示形式,然后传递给关联的控件。
6. 字符串格式化
字符串格式化是将数字显示为文本的理想工具。例如,
Product
类的
UnitCost
属性存储为小数,在文本框中显示时可能会有过多小数位且缺少货币符号。可通过设置
Binding.StringFormat
属性解决此问题,Silverlight 会在控件显示前使用格式字符串将原始文本转换为显示值,在大多数情况下也会使用该字符串进行反向转换,以更新绑定属性。
设置
Binding.StringFormat
属性时,使用标准的 .NET 格式字符串。以下是应用货币格式字符串到
UnitCost
字段的示例:
<TextBox Margin="5" Grid.Row="2" Grid.Column="1"
Text="{Binding UnitCost, StringFormat='C' }">
</TextBox>
要使用
StringFormat
属性获得所需结果,需要正确的格式字符串。常见的数字和日期值格式字符串如下表所示:
| 类型 | 格式字符串 | 示例 |
|---|---|---|
| 货币 | C | $1,234.50 ,负数用括号表示:($1,234.50) ,货币符号与区域设置有关 |
| 科学(指数) | E | 1.234.50E+004 |
| 百分比 | P | 45.6% |
| 固定小数 | F? | 取决于设置的小数位数,F3 格式化为 123.400 ,F0 格式化为 123 |
| 类型 | 格式字符串 | 格式 |
|---|---|---|
| 短日期 | d | M/d/yyyy ,例如:10/30/2008 |
| 长日期 | D | dddd, MMMM dd, yyyy ,例如:Wednesday, January 30, 2008 |
| 长日期和短时间 | f | dddd, MMMM dd, yyyy HH:mm aa ,例如:Wednesday, January 30, 2008 10:00 AM |
| 长日期和长时间 | F | dddd, MMMM dd, yyyy HH:mm:ss aa ,例如:Wednesday, January 30, 2008 10:00:23 AM |
| ISO 可排序标准 | s | yyyy-MM-dd HH:mm:ss ,例如:2008-01-30 10:00:23 |
| 月和日 | M | MMMM dd ,例如:January 30 |
以下是使用自定义日期格式字符串格式化
OrderDate
属性的绑定表达式:
<TextBlock Text="{Binding Date, StringFormat='0:MM/dd/yyyy' }"></TextBlock>
数据绑定中的错误处理、验证与格式化
7. 值转换器
值转换器是一种更为强大且灵活的工具,它能够将任意类型的源数据转换为任意类型的目标对象表示形式,然后传递给与之关联的控件。当字符串格式化无法满足需求时,值转换器就可以发挥重要作用。
要使用值转换器,需要创建一个实现
IValueConverter
接口的类。这个接口定义了两个方法:
Convert
和
ConvertBack
。
Convert
方法用于将源数据转换为目标对象,而
ConvertBack
方法则用于将目标对象转换回源数据,适用于双向绑定的情况。
以下是一个简单的值转换器示例,用于将布尔值转换为字符串:
Imports System.Windows.Data
Public Class BooleanToStringConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
If TypeOf value Is Boolean Then
If CBool(value) Then
Return "是"
Else
Return "否"
End If
End If
Return value
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
If TypeOf value Is String Then
If value.ToString() = "是" Then
Return True
ElseIf value.ToString() = "否" Then
Return False
End If
End If
Return value
End Function
End Class
在 XAML 中使用这个值转换器的示例如下:
<Window.Resources>
<local:BooleanToStringConverter x:Key="BooleanToStringConverter" />
</Window.Resources>
<TextBlock Text="{Binding IsEnabled, Converter={StaticResource BooleanToStringConverter}}" />
在这个示例中,
IsEnabled
属性的布尔值会被转换为 “是” 或 “否” 字符串显示在
TextBlock
中。
8. 综合应用示例
为了更好地理解上述内容,下面给出一个综合应用示例,包含错误处理、验证、格式化和转换的完整场景。
假设我们有一个
Product
类,包含
UnitCost
、
ModelNumber
和
IsAvailable
等属性:
Public Class Product
Implements INotifyPropertyChanged, INotifyDataErrorInfo
Private _unitCost As Decimal
Public Property UnitCost As Decimal
Get
Return _unitCost
End Get
Set(ByVal value As Decimal)
_unitCost = value
OnPropertyChanged("UnitCost")
ValidateUnitCost()
End Set
End Property
Private _modelNumber As String
Public Property ModelNumber As String
Get
Return _modelNumber
End Get
Set(ByVal value As String)
_modelNumber = value
OnPropertyChanged("ModelNumber")
ValidateModelNumber()
End Set
End Property
Private _isAvailable As Boolean
Public Property IsAvailable As Boolean
Get
Return _isAvailable
End Get
Set(ByVal value As Boolean)
_isAvailable = value
OnPropertyChanged("IsAvailable")
End Set
End Property
Private errors As New Dictionary(Of String, List(Of String))()
Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) Implements INotifyDataErrorInfo.ErrorsChanged
Private Sub SetErrors(ByVal propertyName As String, ByVal propertyErrors As List(Of String))
errors.Remove(propertyName)
errors.Add(propertyName, propertyErrors)
RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
End Sub
Private Sub ClearErrors(ByVal propertyName As String)
errors.Remove(propertyName)
RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
End Sub
Private Sub ValidateUnitCost()
Dim errorList As New List(Of String)()
If UnitCost < 0 Then
errorList.Add("UnitCost 不能为负数。")
End If
If errorList.Count > 0 Then
SetErrors("UnitCost", errorList)
Else
ClearErrors("UnitCost")
End If
End Sub
Private Sub ValidateModelNumber()
Dim valid As Boolean = True
For Each c As Char In _modelNumber
If Not Char.IsLetterOrDigit(c) Then
valid = False
Exit For
End If
Next
Dim errorList As New List(Of String)()
If Not valid Then
errorList.Add("ModelNumber 只能包含字母和数字。")
End If
If errorList.Count > 0 Then
SetErrors("ModelNumber", errorList)
Else
ClearErrors("ModelNumber")
End If
End Sub
Public Function GetErrors(ByVal propertyName As String) As IEnumerable Implements INotifyDataErrorInfo.GetErrors
If propertyName = "" Then
Return errors.Values
Else
If errors.ContainsKey(propertyName) Then
Return errors(propertyName)
Else
Return Nothing
End If
End If
End Function
Public ReadOnly Property HasErrors As Boolean Implements INotifyDataErrorInfo.HasErrors
Get
Return errors.Count > 0
End Get
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
在 XAML 中,我们可以这样绑定这些属性,并应用格式化和验证:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
Title="Product Details" Height="350" Width="525">
<Window.Resources>
<local:BooleanToStringConverter x:Key="BooleanToStringConverter" />
</Window.Resources>
<Grid Name="gridProductDetails" BindingValidationError="Grid_BindingValidationError">
<Label Content="Unit Cost:" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
<TextBox Margin="100,10,0,0" Grid.Row="0" Grid.Column="1" x:Name="txtUnitCost"
Text="{Binding UnitCost, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True, StringFormat='C'}" />
<Label Content="Model Number:" Margin="10,40,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
<TextBox Margin="100,40,0,0" Grid.Row="1" Grid.Column="1" x:Name="txtModelNumber"
Text="{Binding ModelNumber, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" />
<Label Content="Is Available:" Margin="10,70,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
<TextBlock Margin="100,70,0,0" Text="{Binding IsAvailable, Converter={StaticResource BooleanToStringConverter}}" />
<TextBlock x:Name="lblInfo" Margin="10,100,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
</Grid>
</Window>
在代码背后,处理
BindingValidationError
事件:
Public Class MainWindow
Private Sub Grid_BindingValidationError(ByVal sender As Object, ByVal e As ValidationErrorEventArgs)
If e.Error.Exception IsNot Nothing Then
lblInfo.Text = e.Error.Exception.Message
Else
lblInfo.Text = e.Error.ErrorContent.ToString()
End If
If TypeOf e.OriginalSource Is TextBox Then
CType(e.OriginalSource, TextBox).Focus()
End If
End Sub
End Class
在这个示例中,
UnitCost
属性应用了字符串格式化,显示为货币格式;
ModelNumber
属性使用了
INotifyDataErrorInfo
接口进行验证;
IsAvailable
属性使用了值转换器将布尔值转换为字符串显示。同时,通过
BindingValidationError
事件处理错误,当出现错误时会在
lblInfo
中显示错误信息,并将焦点转移到出错的文本框。
9. 总结
数据绑定是开发过程中的重要环节,而错误处理、验证、格式化和转换则是确保数据绑定能够正确、高效工作的关键因素。通过合理运用这些技术,可以提高用户体验,减少错误发生的概率,使应用程序更加健壮和易用。
- 错误弹出框的使用需要注意空间问题,并且修改其显示方式可能会比较繁琐。
-
BindingValidationFailed事件可以让开发者对错误处理有更多的控制权,结合Validation类可以随时检查控件的验证状态。 -
创建具有内置验证的数据对象可以通过实现
IDataErrorInfo或INotifyDataErrorInfo接口来实现,避免了简单抛出异常的弊端。 - 字符串格式化和值转换器为数据的显示和存储提供了强大的转换功能,根据不同的需求选择合适的方法可以更好地满足应用程序的要求。
在实际开发中,要根据具体的场景和需求,灵活运用这些技术,不断优化数据绑定的过程,提升应用程序的质量。
下面是一个简单的 mermaid 流程图,展示了数据绑定中验证和转换的基本流程:
graph TD;
A[数据源] --> B[绑定到控件];
B --> C{是否需要验证};
C -- 是 --> D[执行验证逻辑];
D -- 验证通过 --> E{是否需要转换};
D -- 验证失败 --> F[显示错误信息];
C -- 否 --> E;
E -- 是 --> G[使用格式化或转换器];
E -- 否 --> H[直接显示数据];
G --> H;
通过这个流程图,可以更清晰地看到数据从源到显示在控件上的整个过程,以及验证和转换在其中的作用。
总之,掌握数据绑定中的错误处理、验证、格式化和转换技术,对于开发高质量的应用程序至关重要。希望本文的内容能够帮助开发者更好地理解和运用这些技术。
超级会员免费看

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



