2010年12月9日 星期四

WPF TreeView 的 DataBinding (2)

上一回的 WPF TreeView 的 DataBinding (1),TreeView繫結的是單一型別物件的樹狀結構。這一次要挑戰的是同時繫結到兩種型別。

資料

和上次不同的,這一次目錄 (MyFolder)這個容器(container)中,可同時持有MyFolder 與 MyFile。為此,我們修改程式如下

public static List<MyFolder> GetAllFolder()
{
    var folders = new List<MyFolder>()
                      {
                          new MyFolder("A")
                              {
                                                Files = new List<MyFile>() { new MyFile("d_1")},
                                                SubFolders = new List<MyFolder>(){
                                  new MyFolder("A1")
                                      {
                                                        Files = new List<MyFile>() { new MyFile("d1_1"), new MyFile("d1_2")},
                                                        SubFolders = new List<MyFolder>()
                                                                         {
                                                                             new MyFolder("A11"),
                                                                                        new MyFolder("A12"),
                                                                                        new MyFolder("A13"),
                                                                                        new MyFolder("A14"),
                                                                         }
                                      },
                                                new MyFolder("A2"),
                                                new MyFolder("A3")
                                                }
                              },
                                            new MyFolder("B")
                                                {
                                                    Files = new List<MyFile>() { new MyFile("db_1"), new MyFile("db_2")},
                                                    SubFolders = new List<MyFolder>(){
                                  new MyFolder("B1"),
                                                new MyFolder("B2"),
                                                new MyFolder("B3"),
                                                new MyFolder("B4"),
                                                }
                                            }
                      };
    return folders;
}

繫結

.NET Framework 的元件(control)在進行繫結時,只能繫結到單一物件或同一類別的集合。因此,TreeView 要同時顯示 MyFolder 或 MyFile 時,ItemsSource 給什麼值呢?是 SubFolders 嗎?還是 Files 嗎?都只能顯示一部份。為了讓 ItemsSource 在繫結時可以繫結到所有的children,我們必須製作一個新的屬性 Items,可讀取到所有的children,包含MyFolder 及 MyFiles。故程式碼修改如下

public abstract class MyItem
{
    public string Name { get; set; }
}
public class MyFolder : MyItem
{
    public MyFolder(string name)
    {
        Name = name;
    }

    public List<MyFolder> SubFolders { get; set; }
    public List<MyFile> Files { get; set; }

    public List<MyItem> Items
    {
        get
        {
            var items = new List<MyItem>();
            items.AddRange(SubFolders.AsEnumerable());
            items.AddRange(Files.AsEnumerable());
            return items;
        }
    }
}

xaml 修改如下

<Window x:Class="WpfTreeView1.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfTreeView1="clr-namespace:WpfTreeView1" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <WpfTreeView1:FolderHelper x:Key="helper" />
    </Window.Resources>
    <Grid>
        <TreeView Name="treeView1" ItemsSource="{Binding Source={StaticResource ResourceKey=helper}, Path=Items}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Items}">
                    <TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>
</Window>

注意到我們ItemsSource改繫結到 Items。程式運作結如果附圖image

圖示

上面運作的結果是正確的,但風格還是無法讓使用者接受。原因在於無法分辨何者為目錄,何者為檔案。

但怎麼改呢?HierarchicalDataTemplate 只有一個,如何才能分開顯示目錄及檔案呢?

image

答案是實作一個 DataTemplateSelector。

DataTemplateSelector 的想法是這樣:資料繫結的過程中,當遇到某類型的物件時,套用指定的樣版。而這樣的邏輯是完全客製化的,因此必須自己實作。在 WPF 中,就是繼承DataTemplateSelector, 實作自己的 SelectTemplate。程式如下

using System.Windows;
using System.Windows.Controls;

namespace WpfTreeView1
{
    public class MyDataTemplateSelector : DataTemplateSelector
    {
        public HierarchicalDataTemplate FileTemplate { get; set; }
        public HierarchicalDataTemplate FolderTemplate { get; set; }
        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
        {
            if (item is MyFolder)
                return FolderTemplate;
            else
                return FileTemplate;
        }
    }
}

xaml

<Window x:Class="WpfTreeView1.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfTreeView1="clr-namespace:WpfTreeView1" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <WpfTreeView1:FolderHelper x:Key="helper" />
        <HierarchicalDataTemplate x:Key="folder" ItemsSource="{Binding Items}">
            <StackPanel Orientation="Horizontal">
                <Image Source="/WpfTreeView1;component/Images/folder.png" />
                <TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate x:Key="file">
            <StackPanel Orientation="Horizontal">
                <Image Source="/WpfTreeView1;component/Images/doc.png" />
                <TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
        <WpfTreeView1:MyDataTemplateSelector x:Key="selector" FolderTemplate="{StaticResource ResourceKey=folder}" FileTemplate="{StaticResource ResourceKey=file}" />
    </Window.Resources>
    <Grid>
        <TreeView Name="treeView1" ItemsSource="{Binding Source={StaticResource ResourceKey=helper}, Path=Items}"
                            ItemTemplateSelector="{StaticResource ResourceKey=selector}">
        </TreeView>
    </Grid>
</Window>
程式運行結果如下

image

結論

WPF 已經幫我們想到非常多的問題與解決方式。在解決問題的時候,千萬不要一味的自己發明輪子,最後又難以維護。

程式碼下載

沒有留言:

Share with Facebook