かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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