程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WPF學習之綁定—Validation Rule和Binding Group

WPF學習之綁定—Validation Rule和Binding Group

編輯:關於.NET

在上一篇文章中我們討論了有關WPF綁定的知識點,現在我們可以很容易的將業務數據作為源綁定到WPF控件並可以通過創建不同的Data Template後來以特定的樣式展現。而作為這個最常用的功能我們可以通過Two Way的綁定模式與界面交互,而在這個時候我們就需要類似於ASP.NET中Validator一樣的東西來限制或者說校驗數據有效性。ValidationRule就是為這樣的應用而生的。本文將詳細討論如何應用驗證規則和自定義錯誤模板。

我們首先來看看WPF中有關數據驗證規則相關的幾個常用類:

· ValidationRule : 所有自定義驗證規則的基類。提供了讓用戶定義驗證規則的入口。

· ExceptionValidation :表示一個規則,該規則檢查在綁定源屬性更新過程中引發的異常。它是一個內置的規則,它檢查在綁定源屬性更新過程中引發的異常。

· ValidationResult : 數據驗證結果的表現方式。ValidationRule對象的Validate方法執行完畢後通過ValidationResult來表示驗證的結果。這裡包含了錯誤信息—ErrorContent,數據是否有效—IsValid。ValidResult 為 ValidationResult 的有效實例。

· ValidationError :表示一個驗證錯誤,該錯誤在 ValidationRule 報告驗證錯誤時由綁定引擎創建。

對於WPF中綁定的驗證和轉換值我們需要注意:

1.在將值從目標屬性傳輸到源屬性時,數據綁定引擎首先移除可能已添加到所綁定元素的 Validation.Errors附加屬性的任何 ValidationError。然後,數據綁定引擎檢查是否為該 Binding 定義了自定義驗證規則;如果定義了自定義驗證規則,那麼它將調用每個 ValidationRule 上的 Validate 方法,直到其中一個規則失敗或者全部規則都通過為止。如果某個自定義規則未通過,則綁定引擎會創建一個 ValidationError 對象,並將該對象添加到綁定元素的 Validation.Errors 集合。如果 Validation.Errors 不為空,則元素的 Validation.HasError附加屬性被設置為 true。此外,如果 Binding 的 NotifyOnValidationError 屬性設置為 true,則綁定引擎將引發該元素上的 Validation.Error附加事件。

2.如果所有規則都通過,則綁定引擎會調用轉換器(如果存在的話)。

3.如果轉換器通過,則綁定引擎會調用源屬性的 setter。

4.如果綁定具有與其關聯的 ExceptionValidationRule,並且在步驟 3 或 4 中引發異常,則綁定引擎將檢查是否存在 UpdateSourceExceptionFilter。使用 UpdateSourceExceptionFilter 回調可以提供用於處理異常的自定義處理程序。如果未對 Binding 指定 UpdateSourceExceptionFilter,則綁定引擎將對異常創建 ValidationError 並將其添加到綁定元素的 Validation.Errors 集合中。

任何方向(目標到源或源到目標)的有效值傳輸操作都將清除 Validation.Errors附加屬性。

· 簡單驗證:使用ExceptionValidationRule

對於大多數驗證來說我們都是在驗證用戶輸入。ExceptionValidateRule作為WPF內置的簡單驗證器可以捕捉在綁定上發生的任何異常。我們可以用ExceptionValidateRule來作為一個籠統的錯誤收集器,來暴露出內部數據驗證規則的異常信息。

模擬一個Employee信息更改的窗體。假設我們定義了一個Employee實體類,在實體類中我們對數據的有效性都做了簡單的驗證。

public string Title
{
  get { return strTitle; }
  set
  {
    strTitle = value;
    if (String.IsNullOrEmpty(strTitle))
    {
      throw new ApplicationException("Please input Title.");
    }
  }
}
public DateTime Birthday
{
  get { return objBirthday; }
  set
  {
    objBirthday =value;
    if (objBirthday.Year >= DateTime.Now.Year)
    {
      throw new ApplicationException("Please enter a valid date.");
    }
  }
}

在XAML中添加對字段的綁定,並對各個單獨的控件的Text屬性設置Bindg.ValidationRules.這樣當這個綁定的實體對象對應的屬性出現驗證錯誤時,錯誤信息會被ExceptionValidationRule拋出來。其默認行為是在當前控件LostFocus時觸發驗證,並將出錯源控件加亮。

<TextBox Grid.Column="1" Grid.Row="2" Width="200" HorizontalAlignment="Left">
      <TextBox.Text>
        <Binding Path="Title">
          <Binding.ValidationRules>
            <ExceptionValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>

上邊的驗證是正確了,但是我們並不知道具體的錯誤信息。這對於一個String類型的字段來說可能是可以理解的,我可以試出來。但對於特殊的字段來說,我們根本無法得知到底發生了什麼,我怎麼填寫數據才能正確。那麼如何去控制顯示呢?

· 自定義錯誤驗證規則和定制顯示

我們在上一篇裡邊提到過,所有的驗證其實是有一個System.Windows.Controls.Validation類來完成的,對於這個類我們可以看到它有幾個附加屬性和一個附加事件Error.所謂附加事件/屬性就是說可以被加載到另外的控件上來觸發某個對象的事件或操作其屬性,類似於我們有一個接口,通過這個接口你可以操作別的對象。在這裡我們著重需要注意下邊幾個屬性:

Errors Gets the collection of all active ValidationError objects on the bound element. HasError Gets a value that indicates whether any binding on the binding target element has a ValidationError. ErrorTemplate Gets or sets the ControlTemplate used to generate validation error feedback on the adorner layer.

回想一下上一篇的最後部分,我們用到了觸發器,用觸發器來判斷HasError屬性,然後設置其錯誤模板。很容易我們可以做到這一點,這也是我們經常用到的辦法。為了其復用性,我們可以將它定義為一個公用的Style。

<Style TargetType="{x:Type 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="12pt"
                Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                  </TextBlock>
                  <Border BorderBrush="Red" BorderThickness="1">
                    <AdornedElementPlaceholder Name="MyAdorner" />
                  </Border>
                </DockPanel>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Trigger>
      </Style.Triggers>
    </Style>

樣式屬於一個implicit key的樣式,表明它針對的是所有的TargetType所指明的類型,這裡是TextBox.有個問題是,我們想在這裡邊的值改變時就需要觸發驗證而不是LostFocus,怎麼改變呢?Binding對象有一個屬性叫UpdateSourceTrigger,這是一個枚舉值來指定什麼時候觸發數據源更新,更新的時候才會調用驗證。

Members Description Default The default

UpdateSourceTrigger value of the binding target property.The default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus.

PropertyChanged Updates the binding source immediately whenever the binding target property changes. LostFocus Updates the binding source whenever the binding target element loses focus. Explicit Updates the binding source only when you call the UpdateSource method.

<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">就可以幫我們完成任務。

接下來我們可以定義一個對象化一些的錯誤處理類了。定義自己的規則來驗證數據是ValidationRule所處理的主要任務,擴展這些規則就要使我們的類繼承於這個父類:

public class EnumValidationRule : ValidationRule
  {
    private string _enumClass;
    private string _errorMessage;
    public string EnumClass
    {
      get { return "Allan.WPFBinding.ValidationDemo." + _enumClass; }
      set { _enumClass = value; }
    }
    public string ErrorMessage
    {
      get { return _errorMessage; }
      set { _errorMessage = value; }
    }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
      ValidationResult result = new ValidationResult(true, null);
      string inputString = (value ?? string.Empty).ToString();
      Type type = Type.GetType(EnumClass);
      if (string.IsNullOrEmpty(inputString) || !ValidateInput(type,inputString))
      {
        result = new ValidationResult(false, this.ErrorMessage);
      }
      return result;
    }
}

更改綁定參數設置:將驗證規則換成EnumValidationRule並設置相關參數。

<Binding Path="Title" UpdateSourceTrigger="PropertyChanged">
  
  <Binding.ValidationRules>
  
      <local:EnumValidationRule EnumClass="TitleEnum" ErrorMessage="輸入值不存在。請重新輸入。" />
  
   </Binding.ValidationRules>
  
</Binding>

· 用BindingGroups實現對List的驗證

到目前為止我們所有看到的都是針對每個單個的控件輸入的驗證,那麼有沒有辦法對比如某個ListView的每個item進行綁定驗證呢?用上邊的方法是不可以的,否則我們也沒必要針對每個空間都去那麼繁瑣的設置各種信息啊。隨同.NET Framework SP1發布了新的功能點,那就是BindingGroups。BindingGroups是對list列表中的綁定之間的關系進行了封裝,使得你在獲得一個上下文對象時可以和BIdingGroups關聯來得到與當前上下文對象相關聯的所有綁定。而這個關系是你需要顯式去設定的—通過BindingGroupName。

FrameworkElement和FrameworkContentElement有一個叫做BindingGroup的依賴屬性,而ItemsControl繼承於FrameworkElement,所以其具有BindingGroup屬性。而對於BindingBase對象添加了BindingGroupName屬性。

public class BindingBase
{
  public string BindingGroupName { get; set; }
}

我們剛才講過,BindingGroup是需要顯式的去聲明,並且對於一個BindingGroup來說,拿到它就相當於拿到了當前對象相關上下文的所有與其有關系的綁定。而它依然本質上又回到了基本的ValidationRule。

在上邊的類圖中我們可以看到幾個很有意思的東西:

· BindingExpressions: 保存了與當前上下文有關的綁定對象。我們給DateContext中綁定了四個字段的值(對應四個控件),BindingExpressions就保存了所有歸屬到當前BindingGroup的所有Binding對象。例如你有四個字段,那就有四個BidingExpression,有5個字段綁定,你這裡就有5個BindingExpression,即便他們很可能都是一樣的。

· Items :這個屬性保存了與當前上下文對象有關的綁定。例如,我們在示例程序中只綁定了一個單獨的Employee對象,那麼BindingExpressions裡就保留了這個上下文對象。換句話講,當我們按照對象的方式來多個驗證的時候(因為Item在ListView, DataGrid等控件裡就代表一個單個對象),我們所操作的對象傳遞到驗證規則時是以BindingExpression的target來表示的。這時,你得到了行的源,就可以做你想做的事情了。

· ValidationRules : 關聯到我們給bindingGroup加載的驗證規則。

· BeginEdit, CancelEdit, CommitEdit : 實際上這些是和IEditableCollectionView關聯的一些事件,表明對數據視圖的操作。這裡我們可以簡單認為對數據進行更新和提交。

· UpdateSources : 這是一個方法,表明我們要調用ValidationRule來驗證數據。

· ValidateWithoutUpdate : 方法:驗證數據而不更新數據。

現在我們來應用BindingGroup給基於一個Form的綁定來做驗證吧。首先是定義BindingGroup.

<Grid.BindingGroup>
      <BindingGroup>
        <BindingGroup.ValidationRules>
          <local:EmployeeValidationRule ValidationStep="ConvertedProposedValue" />
        </BindingGroup.ValidationRules>
      </BindingGroup>
    </Grid.BindingGroup>

注意這裡應用了ValidationStep,和前邊的UpdateSourceTrigger很相似,它是用來表示在什麼時候來應用驗證規則的。是個枚舉值:

· RawProposedValue = 0,  在沒有變化的原數據上驗證

· ConvertedProposedValue = 1,用戶更改過數據後驗證 default

· UpdatedValue = 2, 在將數據提交給Source時驗證

· CommittedValue = 3 在將數據提交到數據庫後驗證

接下來定義我們的規則。現在的規則是針對每個對象而不是單個屬性的,所以與前邊的有些許不同,但很容易,因為你可以拿到綁定到當前項的對象。

public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
      BindingGroup bindingGroup = (BindingGroup)value;
      Employee emp = (Employee)bindingGroup.Items[0];
      object startDateObj = bindingGroup.GetValue(emp, "StartDate");
      DateTime? startDate = (DateTime)startDateObj;
      object endDateObj = bindingGroup.GetValue(emp, "EndDate");
      DateTime? endDate = (DateTime)endDateObj;
      // check start and end date together
      if (startDate.Value > endDate.Value)
      {
        return new ValidationResult(false, string.Format("StartDate: {0}, cannot be greater than EndDate: {1}",startDate,endDate));
      }
      else
      {
        return new ValidationResult(true, null);
      }
    }

Ok了,你可以再去定義一下出錯時的ErrorTempalte,然後去享受這樣的成果了:)

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved