2014年1月5日 星期日

DataTemplateSelector

在操作 ItemsControl 的子類別相關元件時,如果是使用 Binding 的方式,有一些方法可以用來控制每個 Item 的外表,如果只是單純型狀排版相同,但單偶數底色不同,用 Converter 其實比較單純,但比較複雜的狀況例如一個 ListBox 中有數種不同外型的 Item 時,使用 DataTemplateSelector 就是比較適合的方式,這裡簡單的示範在 WPF 框架下如何利用 DataTemplateSelector 來讓單偶數的底色不同之外,連樣式也有差異。

先示範一般單純指定 ItemTemplate 的情況,這種情況同一個 ListBox 中每個 Item 都是一樣的 View 故變化性低。

DataItem.cs

public class DataItem
{
    public Int32 Index;

    private Brush icon;
    public Brush Icon
    {
        get
        {
            return icon;
        }
        set
        {
            icon = value;
        }
    }

    private String title;
    public String Title
    {
        get
        {
            return title;
        }
        set
        {
            title = value;
        }
    }

    private String description;
    public String Description
    {
        get
        {
            return description;
        }
        set
        {
            description = value;
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <ListBox ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Rectangle Fill="{Binding Icon}" Width="40" Height="40"/>
                        <StackPanel Orientation="Vertical" Margin="10,0,0,0">
                            <TextBlock Text="{Binding Title}"/>
                            <TextBlock Text="{Binding Description}"/>
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        items = new List<DataItem>();
        items.Add(new DataItem { Index = 0,
                                    Icon = new SolidColorBrush(Colors.Red),
                                    Title = "Title A",
                                    Description = "Description A" });
        items.Add(new DataItem { Index = 1,
                                 Icon = new SolidColorBrush(Colors.Blue),
                                 Title = "Title B",
                                 Description = "Description B" });
        items.Add(new DataItem { Index = 2,
                                 Icon = new SolidColorBrush(Colors.Gray),
                                 Title = "Title C",
                                 Description = "Description C" });
        items.Add(new DataItem { Index = 3,
                                 Icon = new SolidColorBrush(Colors.Green),
                                 Title = "Title D",
                                 Description = "Description D" });
        items.Add(new DataItem { Index = 4,
                                 Icon = new SolidColorBrush(Colors.Yellow),
                                 Title = "Title E",
                                 Description = "Description E" });
        items.Add(new DataItem { Index = 5,
                                 Icon = new SolidColorBrush(Colors.Pink),
                                 Title = "Title F",
                                 Description = "Description F" });

        DataContext = this;
    }

    private List<DataItem> items;
    public List<DataItem> Items
    {
        get
        {
            return items;
        }
        set
        {
            items = value;
        }
    }
}

這樣子的配置下執行效果如下。





















如果我們想要讓單數行的 Icon 向左,偶數行的 Icon 向右,代表我們必須有兩個 ItemTemplate 讓 List Item 被畫在螢幕上之前可以選擇該畫成什麼 View 的樣式,而 DataTemplateSelector 就是用來實作這種不限於一個 Template 的方法,以下示範讓 ListBox 的每個 Item 可以自行選擇 Template 的方式。

首先建立一個繼承 DataTemplateSelector 的類別,並在類別中定義你期望可以使用的數個 DataTemplate Property 並在 SelectTemplate Method 中根據適當的條件回傳需使用的樣版。

MyDataTemplateSelector.cs

public class MyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate OddTemplate
    {
        get;
        set;
    }

    public DataTemplate EvenTemplate
    {
        get;
        set;
    }

    public override DataTemplate SelectTemplate(Object item, DependencyObject container)
    {
        DataTemplate template = EvenTemplate;
        DataItem obj = (DataItem)item;
        if (obj.Index % 2 > 0)
        {
            template = OddTemplate;
        }
        return template;
    }
}

接著,可以利用 C# 撰寫 View 的方式,或者在 XAML 中定義 StaticResource 的方式將這些樣版的實際外觀定義出來,我個人習慣寫在需要使用的 Page 同頁的 XAML 檔案中,如果是整個 Application 有許多頁面共同都需要用到的樣版,我習慣寫在 App.xaml 裡面,定義完後,將 ListBox 的 ItemTemplate 拿掉,改成設定 ItemTemplateSelector = {StaticResource MainSelector} 即可

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
 
    <Window.Resources>
        <local:MyDataTemplateSelector x:Key="MainSelector">
            <local:MyDataTemplateSelector.OddTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Rectangle Fill="{Binding Icon}" Width="40" Height="40"/>
                        <StackPanel Orientation="Vertical" Margin="10,0,0,0">
                            <TextBlock Text="{Binding Title}"/>
                            <TextBlock Text="{Binding Description}"/>
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </local:MyDataTemplateSelector.OddTemplate>
            <local:MyDataTemplateSelector.EvenTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                        <StackPanel Orientation="Vertical">
                            <TextBlock Text="{Binding Title}"/>
                            <TextBlock Text="{Binding Description}"/>
                        </StackPanel>
                        <Rectangle Fill="{Binding Icon}" Margin="10,0,0,0" Width="40" Height="40"/>
                    </StackPanel>
                </DataTemplate>
            </local:MyDataTemplateSelector.EvenTemplate>
        </local:MyDataTemplateSelector>
    </Window.Resources>
 
    <Grid>
        <ListBox ItemsSource="{Binding Items}"
                 ItemTemplateSelector="{StaticResource MainSelector}"/>
    </Grid>
</Window>

如此執行起來即可以讓一個 ListBox 中的各個 Item 使用不同的樣版,效果如下。





















沒有留言:

張貼留言