かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

ItemsControlの劣化版を自作してみる(DataTemplate対応コントロールの作成)

DataTemplateに対応したコントロールの作り方ということで、こちらのサイトを写経させていただきました。

カスタムコントロールの作成

カスタムコントロールを新規作成して、Generic.xamlに適当にStackPanelを追加します。ここにアイテムを追加していくっていう予定です。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication4">


    <Style TargetType="{x:Type local:MyItemControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyItemControl}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <StackPanel x:Name="ItemsPanel" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

依存プロパティの作成

今回は、データを格納するためのItemsSourceプロパティと、1要素1要素に適用するDataTemplateを設定するItemTemplateプロパティを追加します。

public IEnumerable ItemsSource
{
    get { return (IEnumerable)GetValue(ItemsSourceProperty); }
    set { SetValue(ItemsSourceProperty, value); }
}

// Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
    DependencyProperty.Register("ItemsSource", 
        typeof(IEnumerable), 
        typeof(MyItemControl), 
        new PropertyMetadata(null, ItemsSourceChanged));

public DataTemplate ItemTemplate
{
    get { return (DataTemplate)GetValue(ItemTemplateProperty); }
    set { SetValue(ItemTemplateProperty, value); }
}

// Using a DependencyProperty as the backing store for ItemTemplate.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemTemplateProperty =
    DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(MyItemControl), new PropertyMetadata(null));

ItemsSourceは変更のタイミングで再描画したいので、変更時のコールバックを設定してます。

OnApplyTemplateとその他のメソッド

ということで、残りの部分です。ItemsSourceChangedは、再描画するためのメソッドを呼ぶだけのシンプル実装です。

private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((MyItemControl)d).RenderItems();
}

ControlTemplateが適用されるタイミングで呼ばれるOnApplyTemplateメソッドも、Panelをテンプレートから取得するしたら再描画するだけです。

private Panel itemsPanel;

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    this.itemsPanel = this.GetTemplateChild("ItemsPanel") as Panel;
    this.RenderItems();
}

そして大事な再描画メソッドのRenderItemsメソッドです。こいつは、DataTemplateのLoadContentメソッドを使ってテンプレートからコントロールの実体を作成して、DataContextに要素をつっこんでからPanelに追加しています。

private void RenderItems()
{
    if (this.itemsPanel == null)
    {
        return;
    }

    this.itemsPanel.Children.Clear();

    if (this.ItemsSource == null)
    {
        return;
    }

    foreach (var item in this.ItemsSource)
    {
        var elm = this.ItemTemplate.LoadContent() as FrameworkElement;
        elm.DataContext = item;
        this.itemsPanel.Children.Add(elm);
    }
}

このメソッドが今回の核ですね。LoadContentメソッドメモメモ。

実行してみる

画面に適当においてみます。

<local:MyItemControl ItemsSource="{Binding}">
    <local:MyItemControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" />
        </DataTemplate>
    </local:MyItemControl.ItemTemplate>
</local:MyItemControl>

DataContextにはNameプロパティをもったオブジェクトの配列を入れてます。実行すると、こんな感じに表示されます。

f:id:okazuki:20140623211034j:plain