.NET Framework已經算是一個很易用的庫了。可以自動地為我們做很多事情,而且大都做得還不錯。但是自動完成的事情很可能會有隱患,因為Framework本身是並不了解業務邏輯的。它自動完成的事情,可能會給我們幫倒忙。
RadioButton就是其中一個。
先來從設置值的角度介紹一下WPF裡的Dependency Property(以下簡稱DP)。在WPF裡控制一個控件的DP,有太多的方式。可以用Style,可以用Animation,可以用Data Binding,可以用Trigger,還有最基本直接賦值。控件會綜合上面各個方面的值,其及優先級等因素來決定一個DP的最終的值是多少。關於這方面的更多的知識可以參考雨痕關於DP的文章或MSDN。(不過不確定MSDN上有沒有有關DP設值優先級的系統的介紹哦。)
多數的控件不會自動的更改自己的某個屬性的值。但是總有一些例外。RadioButton就是其中一個。它自動設置什麼值了?答案是IsChecked屬性。RadioButton的特點是一組RadioButton只有一個被選中。當一個RadioButton被選中的時候,其它所有的RadioButton就會被自動地設置IsChecked屬性為False。
問題來了,設置一個屬性的方法那麼多,它自己自動設置這個屬性的時候,應該用什麼方法呢?這個問題早在.NET 3.0時就已經有人發現,並在微軟的論壇上討論過。MSFT的Sam Bent也承認了這個Bug的存在。但問題就在於,無論RadioButton用哪種現有的方法去使得IsChecked屬性為False,“總會有些人不高興”。3.0裡RadioButton的自動,會讓Style和Template的值失效;在3.5中,Fix了3.0中的這個Bug,但是卻導致Binding失效。
經過筆者驗證,在.NET 3.5 SP1中使用了與3.5相同的邏輯,即使Binding失效。下面我們將用一個“為微軟選CEO”的示例程序進行驗證。
首先定義一個Person類,如下:
Person
1using System.ComponentModel;
2using System.Diagnostics;
3
4namespace BindingRadioButton.Model
5{
6 /**//// <summary>
7 ///
8 /// </summary>
9 public class Person : INotifyPropertyChanged
10 {
11 Private Fields#region Private Fields
12
13 private bool isCeo;
14 private string name;
15
16 #endregion
17
18 Public Properties#region Public Properties
19
20 /**//// <summary>
21 ///
22 /// </summary>
23 public bool IsCeo
24 {
25 get { return isCeo; }
26 set
27 {
28 if (value != isCeo)
29 {
30 isCeo = value;
31 OnPropertyChanged("IsCeo");
32 }
33 }
34 }
35
36 /**//// <summary>
37 ///
38 /// </summary>
39 public string Name
40 {
41 get { return name; }
42 set
43 {
44 if (value != name)
45 {
46 name = value;
47 OnPropertyChanged("Name");
48 }
49 }
50 }
51
52 #endregion
53
54 INotifyPropertyChanged Members#region INotifyPropertyChanged Members
55
56 /**//// <summary>
57 ///
58 /// </summary>
59 public event PropertyChangedEventHandler PropertyChanged;
60
61 #endregion
62
63 protected virtual void OnPropertyChanged(string propertyName)
64 {
65 PropertyChangedEventHandler temp = PropertyChanged;
66 if (temp != null)
67 {
68 temp(this, new PropertyChangedEventArgs(propertyName));
69 Trace.WriteLine(string.Format("{0} {1} CEOn", Name, IsCeo ? "is" : "is not"));
70 }
71 }
72 }
73}
74
由於實現了INotifyPropertyChanged接口,這個類的實例可以被Binding。我們在PropertyChanged的時候,輸出一條信息,說明當前的CEO是誰。注意裡面IsCeo為False的時候也會輸出消息哦。說明誰被撤職了。
然後建立UI如下。
UI
1<Window x:Class="BindingRadioButton.DemoWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:model="clr-namespace:BindingRadioButton.Model"
5 Title="RadioButton Binding Lost"
6 Height="300" Width="300">
7 <Window.Resources>
8 <model:Person x:Key="First" Name="Bill Gates"/>
9 <model:Person x:Key="Second" Name="Steve Ballmer"/>
10 </Window.Resources>
11 <DockPanel Margin="12">
12 <GroupBox DockPanel.Dock="Top"
13 Header="Select a CEO for MSFT"
14 Padding="9" Margin="0,0,0,12">
15 <StackPanel>
16 <RadioButton DataContext="{StaticResource First}"
17 IsChecked="{Binding IsCeo}"
18 Content="{Binding Name}"
19 GroupName="ceo" Margin="0,0,0,5"/>
20 <RadioButton DataContext="{StaticResource Second}"
21 IsChecked="{Binding IsCeo}"
22 Content="{Binding Name}"
23 GroupName="ceo"/>
24 </StackPanel>
25 </GroupBox>
26
27 <Label DockPanel.Dock="Top" Content="Message:"
28 Padding="0" Margin="0,0,0,5"/>
29 <TextBox Name="messageBox"/>
30 </DockPanel>
31</Window>
其中的messageBox用於顯示Trace輸出的消息。運行結果如下。
圖1. 剛啟動
現在MSFT沒有CEO,我們點Steve Ballmer選擇一個之後如圖2。
圖2. 選擇一個
然後選擇另一個作為CEO,之後如圖3。
圖3. 選擇另一個
有心的讀者應該已經可以看出,到這裡已經可以證明有一個綁定已經失效了。不過我們繼續用更明顯的方式繼續點點。
再選回Steve Ballmer,如圖4。
圖4. 再選擇一個
結果再沒有消息出來了。很明顯,Binding失效了。