WPF 的強項當然要說到 Biding 了。然而 Data Binding 在 .NET 平台上到處可見。WPF 的 Binding 到底有什麼特別的呢?
下面是一個重構的過程。由最開始由未使用 Binding,一直到使用 Binding 完成後,您可以見到該 Binding 的威力。
需求
身為一個使用者,我希望可以自行挑選表單的背景顏色
第一版:未 Binding
第一個版本是類似 Windows Form 的實作方式。首先打開 Visual Studio 2010, 建立一個 WPF 的應用程式。建立專案後,開啟 MainWindows.xaml,拖進一個 ListBox,並命名為 ColorListBox。如下圖
在 Code Behind 的 MainWindows.xaml.cs 上,將程式MainWindows 的建構子修改如下
public MainWindow()
{
InitializeComponent();
var props = typeof(Brushes).GetProperties();
var q = from p in props
select new
{
Name = p.Name,
Brush = (Brush)p.GetValue(null, null)
};
ColorListBox.ItemsSource = q;
ColorListBox.SelectedValuePath = "Brush";
ColorListBox.DisplayMemberPath = "Name";
}
在 MainWindow 的建構子中,已經使用了 DataBinding 技術,如同 Windows Form 相同。接下來在 ColorListBox 上雙擊左鍵滑鼠兩下。並修改 EventHandler 如下
private void ColorListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.Background = ColorListBox.SelectedValue as Brush;
}
到這裡,程式已經可以運作,而且程式碼相當乾淨。 Code Review 時,見到這樣的程式已經要感謝上天了。
程式運作起來,如下圖.
第二版:重構,建立一個 NamedBrush
第一版的程式可個缺點:建構子太長,而且內含非 UI 的邏輯,即使用 Reflection 讀取所有 Brushes 的 public property。
這一個重構的目的,就是將這一段程式拆出來。改放到一個 NamedBrush 類別。
using System.Linq;
using System.Windows.Media;
namespace WpfApplication2
{
public class NamedBrush
{
public Brush Brush { get; set; }
public string Name { get; set; }
public static NamedBrush[] All { get; set; }
static NamedBrush()
{
var props = typeof(Brushes).GetProperties();
var q = from p in props
select new NamedBrush
{
Name = p.Name,
Brush = (Brush)p.GetValue(null, null)
};
All = q.ToArray();
}
}
}
而 MainWindow.xaml.cs 就可以簡化如下
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ColorListBox.ItemsSource = NamedBrush.All;
ColorListBox.SelectedValuePath = "Brush";
ColorListBox.DisplayMemberPath = "Name";
}
private void ColorListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.Background = ColorListBox.SelectedValue as Brush;
}
}
第三版:在xaml中設定屬性
為了簡少Code behind 的程式,我們將建構子中的 ColorListBox 的屬性值設定陳述句移到 xaml 中。 也就是將下面這兩行
ColorListBox.SelectedValuePath = "Brush";
ColorListBox.DisplayMemberPath = "Name";
移到 xaml 中,如下
<ListBox Height="213" HorizontalAlignment="Left" Margin="76,12,0,0" Name="ColorListBox" VerticalAlignment="Top" Width="353"
SelectedValuePath="Brush" DisplayMemberPath="Name"/>
第四版:Binding
WPF 特有的 Binding 可以取代一些 Event Handler,如同在WPF 學習:Binding中已經使用過這樣的技巧。因此, ColorListBox_SelectionChanged 這樣簡單的有一些可以移到 xaml 中。
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Background="{Binding ElementName=ColorListBox, Path=SelectedValue}">
意思是:Window 的 Background 值繫結來至 ColorListBox 的 SelectedValue。
到了這個步驟,整個 Code behind 只剩下
using System.Windows;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ColorListBox.ItemsSource = NamedBrush.All;
}
}
}
換句話說,只剩一行的 ColorListBox.ItemsItemsSource = NamedBrush.All; 這一行程式要寫。其他的都移到了 xaml中。
第五版:使用靜態 Binding
雖說已經簡化到了不行,剩下的最後一行,難到無計可施嗎?WPF 還有一招:StaticBinding。在 xaml 宣告一個 Window Resource 如下
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525" Background="{Binding ElementName=ColorListBox, Path=SelectedValue}">
<Window.Resources>
<l:NamedBrush x:Key="NB" />
</Window.Resources>
注意到我增加了 xmaln:l=’clr-namespace:WpfApplication2’ 這一行 namespace 宣告。
最後,在 ColorListBox 的 ItemsSource 屬性設定如下
<ListBox Height="213" HorizontalAlignment="Left" Margin="76,12,0,0" Name="ColorListBox" VerticalAlignment="Top" Width="353"
SelectedValuePath="Brush" DisplayMemberPath="Name"
ItemsSource="{Binding Source={StaticResource ResourceKey=NB}, Path=All}"/>
這樣一來,Code Behind 的程式就與最初的相同了。
using System.Windows;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
討論
為什麼我們汲汲營營的要把 xaml.cs 的程式降到0行客製程式呢?第五版的步驟,為了將一行的建構子程式,我們修改了 xaml 三個地方,這樣划的來嗎?
其實,這樣做有兩大好處。第一項好處,是沒有 Code behind 的程式後,美工的部份可以完全交給設計人員。設計人員只需使用 Expression Blend 來設計樣式,完全不必懂程式如何寫作,就可以使用 Binding 的方式設計行為。
第二項好處,是UI 的資料(Model)完全分離,這有利於單元測試。對於容易進行測試的 NameBrush 類別進行單元測試,而難以進行測試,又常常改化的 UI就可以進行 Coded UI Test 了。
範例程式下載