天天看點

WPF筆記--Binding補充

  • 讓資料“為我所用”的Converter
  • 讓資料“幹幹淨淨”的Validation
  • 集合控件與集合資料的Binding
  • 偷懶專用的資料中轉站——DataContext

Converter

我們用到的關卡就是“資料轉換器”(Data Converter)。Converter實際上就是一個類,它這個類有個要求——它需要實作IValueConverter這個接口。這個接口的内容非常簡單——隻有兩個方法,它們分别是:

  • Convert方法:按照你的要求,把從資料源傳來的資料轉成你想要的資料 
  • ConvertBack方法:如果Binding是TwoWay的,那麼資料目标會回傳經使用者改動後的資料,這時候你就不得不把資料轉換回資料源裡的格式——大多數情況下,它是Convert方法的逆運算,具體情況還要具體分析。 
這裡講一下,以前我們在做網站的時候頁面上顯示的資料有的時候會做個轉換,例如頁面是顯示性别是男或者女,但是資料庫裡存的是0或者1.那麼在顯示的時候就要把0-1做轉換和下面的這個是一個道理。頁面修改了性别後存放在資料庫中也會做個轉換,這裡就類似下面的twoway模式了。
  1. [ValueConversion(typeof(string), typeof(bool?))] //資料的源類型是string,目标類型是bool?  
  2. class ConverterYN2TF : IValueConverter  
  3. {  
  4.     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
  5.     {  
  6.         string str = System.Convert.ToString(value);  
  7.         switch (str)  
  8.         {  
  9.             case "Y":  
  10.                 return true;  
  11.             case "N":  
  12.                 return false;  
  13.             default:  
  14.                 return null;  
  15.         }  
  16.     }  
  17.     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
  18.     {  
  19.         bool? b = System.Convert.ToBoolean(value);  
  20.         switch (b)  
  21.         {  
  22.             case true:  
  23.                 return "Y";  
  24.             case false:  
  25.                 return "N";  
  26.             default:  
  27.                 return "Null";  
  28.         }  
  29.     }  
  30. }  

使用這個類的方法是将Binding執行個體的Converter屬性設定為這個類的一個執行個體:

  1. checkBox1.IsThreeState = true;  
  2. Binding binding = new Binding("Text");  
  3. binding.Source = textBox1;  
  4. binding.Converter = new ConverterYN2TF(); // 設定Converter  
  5. this.checkBox1.SetBinding(CheckBox.IsCheckedProperty, binding);

Validation

下面給出一個例子:我們以一個Slider為資料源,它的滑塊可以從Value=0滑到Value=100;同時,我們以一個TextBox為資料目标,并通過Validation限制它隻能将20到35之間的資料傳回資料源。現實當中恐怕很少有這麼幹的,我們這個例子隻是為了說明校驗的使用方法:)

若要建立一個自定義的校驗條件,需要聲明一個類,并讓這個類派生自ValidationRule類。ValidationRule隻有一個名為Validate的方法需要我們實作,這個方法的傳回值是一個ValidationResult類型的執行個體——這個執行個體攜帶着兩個資訊:

  • bool類型的IsValid屬性告訴Binding回傳的資料是否合法
  • object類型(一般是存儲一個string)的ErrorContent屬性告訴Binding一些資訊,比如目前是進行什麼操作而出現的校驗錯誤等等,一般我會把這些資訊寫進Log檔案裡
這個東西和我們做網站項目頁面的效果也是類似的,我們經常會用JS寫一些頁面輸入資訊的用戶端基本控制,例如輸入日期的時候會有數字的控制,輸入數字類型的資料的時候如果輸入其它的非數字字元會被過濾掉且還有提示。這裡我講的是效果是一樣的。

實作好的類是這樣的:

  1. public class MyValidationRule : ValidationRule  
  2. {  
  3.     public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)  
  4.     {  
  5.         double d = 0.0;  
  6.         if (double.TryParse((string)value, out d) && d >= 20 && d <= 35)  
  7.         {  
  8.             return new ValidationResult(true, "OK");  
  9.         }  
  10.         else  
  11.         {  
  12.             return new ValidationResult(false, "Error");  
  13.         }  
  14.     }  
  15. }  

在代碼裡這樣使用它:

  1. Binding binding = new Binding("Value");  
  2. binding.Source = slider1;  
  3. binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;  
  4. binding.ValidationRules.Add(new MyValidationRule()); // 加載校驗條件  
  5. textBox1.SetBinding(TextBox.TextProperty, binding); 

集合Binding

我們得準備一個用來存放資料的集合,對于這個集合有一個特殊的要求,那就是,這個集合一定要是 實作了IEnumerable接口的集合 。為什麼呢?原因很簡單,實作了IEnumerable接口就意味着這個集合裡的元素是可枚舉的,可枚舉就意味着這個集合裡的元素是同一個類型的(至少具有相同的父類),元素是同一個類型的就意味着在每個元素中我都能找到同樣的屬性。

如果客戶要求顯示所有資訊,那這種“簡裝版”的binding就不靈了,因為它隻能拿到一個值。這時候,我們需要這樣做:

  1. public Window1()
  2. {
  3.     InitializeComponent();
  4.     List stuList = new List() 
  5.     {
  6.         new Student{StuNum=1, Name="Tim", Age=28},
  7.         new Student{StuNum=2, Name="Ma Guo", Age=25},
  8.         new Student{StuNum=3, Name="Yan", Age=25},
  9.         new Student{StuNum=4, Name="Xaiochen", Age=28},
  10.         new Student{StuNum=5, Name="Miao miao", Age=24},
  11.         new Student{StuNum=6, Name="Ma Zhen", Age=24}
  12.     };
  13.     this.listBox1.ItemsSource = stuList;
  14.     this.listBox1.DisplayMemberPath = "Name";
  15.     //this.listBox1.SelectedValuePath = "StuNum";
  16.     this.stuNumTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.StuNum") { Source = this.listBox1 });
  17.     this.nameTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Name") { Source = this.listBox1 });
  18.     this.ageTextBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Age") { Source = this.listBox1 });
  19. }

這回,我們使用的是ListBox的SelectedItem屬性——每當我們選中ListBox(包括其它ItemsControl)中的一個Item時,ListBox都會“默默地”自動從資料源集合裡選出與目前選中Item相對應的那個條目,作為自己的SelectedItem屬性值。而且,上面這個例子裡我們使用到了“多級路徑”—— "SelectedItem.Age",實際項目中,你可以一路“點”下去,直到取出你想要的值。

DataContext

WPF也為我們準備了一個用來放置資料的“制高點”——DataContext。

怎麼了解這個資料制高點呢?讓我們接着看上面的程式。現在客戶的需求又變了:要求在窗體裡顯示兩個ListBox,一個裡面顯示學生清單,一個裡面顯示老師清單,選中任何一個ListBox裡的項,下面的TextBox都顯示相應的詳細資訊。

這時候我們遇到困難了!因為一個UI元素不可能binding到兩個資料源上啊!怎麼辦呢?這時候DataContext就派上用場了。

  1. <</span>Window x:Class="CollectionBinding.Window1"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="水之真谛" Height="300" Width="300">
  5.     <</span>StackPanel>
  6.         <</span>ListBox Name="stuListBox" Margin="5" Height="70" Background="LightBlue"/>
  7.         <</span>ListBox Name="tchrListBox" Margin="5" Height="70" Background="LightPink"/>
  8.         <</span>TextBox Name="idTextBox"  Margin="5" Background="LightGreen"/>
  9.         <</span>TextBox Name="nameTextBox"  Margin="5" Background="LightGreen"/>
  10.         <</span>TextBox Name="ageTextBox"  Margin="5" Background="LightGreen"/>
  11.     </</span>StackPanel>
  12. </</span>Window>

相應地,我們重構了一下Student類和Teacher類,讓它們趨于一緻:

  1. interface ISchoolMember
  2. {
  3.      int ID { get; set; }
  4.      string Name { get; set; }
  5.      int Age { get; set; }
  6. }
  7. class Student : ISchoolMember
  8. {
  9.     public int ID { get; set; }
  10.     public string Name { get; set; }
  11.     public int Age { get; set; }
  12. }
  13. class Teacher : ISchoolMember
  14. {
  15.     public int ID { get; set; }
  16.     public string Name { get; set; }
  17.     public int Age { get; set; }
  18. }

現在讓我們看看DataContext是怎麼玩兒的:

  1. public Window1()
  2. {
  3.     InitializeComponent();
  4.     List stuList = new List() 
  5.     {
  6.         new Student{ID=1, Name="Tim", Age=28},
  7.         new Student{ID=2, Name="Ma Guo", Age=25},
  8.         new Student{ID=3, Name="Yan", Age=25},
  9.     };
  10.     List tchrList = new List()
  11.     {
  12.         new Teacher{ID=1, Name="Ma Zhen", Age=24},
  13.         new Teacher{ID=2, Name="Miao miao", Age=24},
  14.         new Teacher{ID=3, Name="Allen", Age=26}
  15.     };
  16.     stuListBox.ItemsSource = stuList;
  17.     tchrListBox.ItemsSource = tchrList;
  18.     stuListBox.DisplayMemberPath = "Name";
  19.     tchrListBox.DisplayMemberPath = "Name";
  20.     stuListBox.SelectionChanged += (sender, e) => { this.DataContext = this.stuListBox.SelectedItem; };
  21.     tchrListBox.SelectionChanged += (sender, e) => { this.DataContext = this.tchrListBox.SelectedItem; };
  22.     this.idTextBox.SetBinding(TextBox.TextProperty, new Binding("ID"));
  23.     this.nameTextBox.SetBinding(TextBox.TextProperty, new Binding("Name"));
  24.     this.ageTextBox.SetBinding(TextBox.TextProperty, new Binding("Age"));
  25. }

讓我們來仔細品嘗這段代碼: 

stuListBox.SelectionChanged += (sender, e) => { this.DataContext = this.stuListBox.SelectedItem; };

tchrListBox.SelectionChanged += (sender, e) => { this.DataContext = this.tchrListBox.SelectedItem; };

這兩句是兩個Lambda表達式,實際上就是兩個事件處理函數的縮寫——讓下遊程式員不用跳轉就知道兩個ListBox在各自的SelectionChanged事件發生時都做什麼事情。我們這裡做的事情就是:哪個ListBox的選中項改變了,那就把選中的資料放到窗體的DataContext屬性裡,隐含地,就把前一個資料給擠走了。 

有意思的是最後三句:在為三個TextBox設定Binding的時候,我沒有提供資料源——但程式一樣work,為什麼呢?前面我說了,DataContext是“制高點”,當一個元素發現自己有Binding但這個Binding沒有Source時,它就會“向上看”——它自然會看到制高點上的資料,這時候它會拿這個資料來試一試,有沒有Binding所訓示的Path——有,就拿來用;沒有,就再往上層去找,也就是找更高的制高點  

實際項目中,我會根據資料的影響範圍來選擇在哪一級上設定DataContext,以及把什麼對象設定為DataContext。比如:一個ListBox裡的SelectedItem需要被包含它的Grid裡的其它元素共享,我就可以把ListBox.SelectedItem設定為Grid的DataContext,而沒必要把ListBox設定為最頂層Window的DataContext——原則就是“範圍正好,影響最小”。

這個玩意兒個人覺得最好少用,或者用的範圍小,否則調試或者維護非常麻煩。

繼續閱讀