2010年9月5日 星期日

WPF 學習:Static Binding

WPF 的強項當然要說到 Biding 了。然而 Data Binding 在 .NET 平台上到處可見。WPF 的 Binding 到底有什麼特別的呢?

下面是一個重構的過程。由最開始由未使用 Binding,一直到使用 Binding 完成後,您可以見到該 Binding 的威力。

需求

身為一個使用者,我希望可以自行挑選表單的背景顏色

第一版:未 Binding

第一個版本是類似 Windows Form 的實作方式。首先打開 Visual Studio 2010, 建立一個 WPF 的應用程式。建立專案後,開啟 MainWindows.xaml,拖進一個 ListBox,並命名為 ColorListBox。如下圖

image

在 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 時,見到這樣的程式已經要感謝上天了。

程式運作起來,如下圖.

image

第二版:重構,建立一個 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 了。

範例程式下載

沒有留言:

Share with Facebook