當 Mode=TwoWay 且 UpdateSourceTrigger 觸發時,WPF 繫結引擎依序執行:
Validation.Errors 集合。Validate(value, culture),任一失敗即建立 ValidationError 加入集合,停止後續規則。IValueConverter.Convert。ExceptionValidationRule,則轉為 ValidationError。任何方向的成功傳輸皆會清除 Validation.Errors,Validation.HasError 附加屬性隨集合空/非空自動切換。
實體屬性 Setter 中拋出例外即可:
public string Title {
set {
if (string.IsNullOrEmpty(value)) throw new ApplicationException("標題不可為空");
_title = value;
}
}
XAML:
<TextBox.Text>
<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules><ExceptionValidationRule/></Binding.ValidationRules>
</Binding>
</TextBox.Text>
預設 LostFocus 觸發,改 PropertyChanged 即時驗證。缺點:錯誤訊息僅能透過 ToolTip 或自訂模板顯示,無法在 XAML 直接控制樣式邏輯。
public class EnumValidationRule : ValidationRule {
public string EnumClass { get; set; }
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value, CultureInfo culture) {
var input = (value ?? "").ToString();
var type = Type.GetType("MyApp." + EnumClass);
return string.IsNullOrEmpty(input) || !Enum.IsDefined(type, input)
? new ValidationResult(false, ErrorMessage)
: ValidationResult.ValidResult;
}
}
XAML 掛載並啟用即時觸發:
<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:EnumValidationRule EnumClass="TitleEnum" ErrorMessage="請輸入有效枚舉值"/>
</Binding.ValidationRules>
</Binding>
通用錯誤樣式(置於 ResourceDictionary 復用):
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="12"
Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="adorner"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
AdornedElementPlaceholder 佔位原控件位置,紅框+右側錯誤文字,視覺效果即時呈現。
單一控件驗證無法處理「開始日期必須早於結束日期」等跨欄位規則。BindingGroup 將同一 DataContext 下多個繫結納入同一驗證單元:
<Grid>
<Grid.BindingGroup>
<BindingGroup>
<BindingGroup.ValidationRules>
<local:EmployeeValidationRule ValidationStep="ConvertedProposedValue"/>
</BindingGroup.ValidationRules>
</BindingGroup>
</Grid.BindingGroup>
<TextBox Text="{Binding Path=StartDate, ValidationStep=ConvertedProposedValue}"/>
<TextBox Text="{Binding Path=EndDate, ValidationStep=ConvertedProposedValue}"/>
<Button Content="提交" Click="Submit_Click"/>
</Grid>
驗證規則可取得完整實體物件:
public class EmployeeValidationRule : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo culture) {
var group = (BindingGroup)value;
var emp = (Employee)group.Items[0];
var start = (DateTime)group.GetValue(emp, "StartDate");
var end = (DateTime)group.GetValue(emp, "EndDate");
return start > end
? new ValidationResult(false, $"開始日期 {start:d} 不得晚於結束日期 {end:d}")
: ValidationResult.ValidResult;
}
}
提交時呼叫 group.CommitEdit() 觸發驗證並寫入源;group.ValidateWithoutUpdate() 僅驗證不寫入。
| 成員 | 類型 | 用途 |
|---|---|---|
Items | IList | 當前 DataContext 實體集合(通常單一實體) |
BindingExpressions | ReadOnlyCollection<BindingExpression> | 所有參與驗證的繫結表達式 |
GetValue(item, propertyName) | object | 取得指定實體的屬性值(已轉換後) |
ValidationRules | Collection<ValidationRule> | 掛載的驗證規則 |
CommitEdit() | void | 驗證通過後寫入源,觸發 IEditableObject.EndEdit |
CancelEdit() | void | 撤銷編輯,觸發 IEditableObject.CancelEdit |
BeginEdit() | void | 開始編輯,觸發 IEditableObject.BeginEdit |
RawProposedValue (0):原始字串,適合格式檢查(如 Regex)。ConvertedProposedValue (1,預設):經 Converter 轉換後,適合業務規則(跨欄位比較)。UpdatedValue (2):源屬性 Setter 執行後,適合資料庫唯一鍵衝突檢查。CommittedValue (3):資料庫 Commit 後,極少用。ValidationRule + 共用 ErrorTemplate Style,保持 XAML 乾淨。BindingGroup.ValidationRules,避免分散在多個控件。BindingGroup(ItemBindingGroup),隔離驗證範圍。Validation.ErrorTemplate,勿在 Trigger 中硬編碼顏色,利於主題切換。UpdatedValue 階段發起,配合 INotifyDataErrorInfo (.NET 4.5+) 實現更豐富的非同步驗證體驗。