しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Index を渡すと DataTemplate を返す TemplateSelector を作る

f:id:matatabi_ux:20140125140522p:plain

こんな感じのボタンを押すとアイコンを選択する Flyout を表示する場合、アイコンが画像ならば Uri を ViewModel に持っておけば Binding も簡単なんですが・・・

こういうアイコンはやっぱり拡縮で劣化しないベクター素材の Path で表示したい!

ViewModel に Path の Data プロパティ用の文字列を持たせてみましたが、 Binding でエラーになるのでうまくいきませんでした

というわけで作ってみたのが int の Index をもとに 対応する DataTemplate を選択する Selector です

<Page.Resources>
    <s:IndexToTemplateSelector x:Key="IndexToTemplateSelector">
        <s:IndexToTemplateSelector.Templates>
            <DataTemplate>
                <Grid>
                    <Viewbox Width="32" Height="32">
                        <Path Data="F1M-1706.31,-910.775C-1719.92,-906.954 -1724.69,-885.691 -1724.69,-885.691 -1725.26,-889.6 -1728.61,-897.85 -1728.61,-897.85 -1737,-915.8 -1748.87,-909.821 -1748.87,-909.821 -1762.04,-900.802 -1753.21,-884.532 -1753.21,-884.532L-1746.27,-888.586C-1750.61,-895.389 -1747.13,-901.323 -1747.13,-901.323 -1740.91,-907.259 -1735.41,-893.362 -1735.41,-893.362 -1729.77,-885.111 -1728.17,-852.253 -1728.17,-852.253 -1725.28,-850.226 -1720.5,-852.253 -1720.5,-852.253 -1719.34,-899.587 -1705.59,-902.915 -1705.59,-902.915 -1696.33,-903.061 -1702.26,-888.441 -1702.26,-888.441L-1695.75,-884.678C-1686.92,-914.784,-1706.31,-910.775,-1706.31,-910.775"
                              Fill="{Binding Foreground,
                                             RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              Stretch="Uniform" />
                    </Viewbox>
                </Grid>
            </DataTemplate>
            <DataTemplate>
                <Grid>
                    <Viewbox Width="32" Height="32">
                        <Path Data="F1M-1711.64,-1319.91C-1705.34,-1324.13,-1701.19,-1331.32,-1701.19,-1339.47L-1709.44,-1339.47C-1709.44,-1331.03 -1716.29,-1324.19 -1724.73,-1324.19 -1733.17,-1324.19 -1740.01,-1331.03 -1740.01,-1339.47L-1748.27,-1339.47C-1748.27,-1331.32 -1744.12,-1324.13 -1737.81,-1319.91 -1744.12,-1315.68 -1748.27,-1308.5 -1748.27,-1300.34 -1748.27,-1287.34 -1737.73,-1276.81 -1724.73,-1276.81 -1711.73,-1276.81 -1701.19,-1287.34 -1701.19,-1300.34 -1701.19,-1308.5 -1705.34,-1315.68 -1711.64,-1319.91 M-1724.73,-1285.06C-1733.17,-1285.06 -1740.01,-1291.91 -1740.01,-1300.34 -1740.01,-1308.78 -1733.17,-1315.63 -1724.73,-1315.63 -1716.29,-1315.63 -1709.44,-1308.78 -1709.44,-1300.34 -1709.44,-1291.91 -1716.29,-1285.06 -1724.73,-1285.06"
                              Fill="{Binding Foreground,
                                             RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              Stretch="Uniform" />
                    </Viewbox>
                </Grid>
            </DataTemplate>

            <!-- 中略 -->

        </s:IndexToTemplateSelector.Templates>
    </s:IndexToTemplateSelector>
</Page.Resources>

Templates プロパティに DataTemplate のコレクションを定義しておき、Index が渡されたら対応するコレクション位置の DataTemplate を返すという仕組みです

Path の Fill を外から変更するために TemplateParent の Foreground を Binding しています

    /// <summary>
    /// XAML 定義用 DataTemlate コレクションクラス
    /// </summary>
    [ContentProperty()]
    public class DataTemplateCollection : List<DataTemplate> { }

    /// <summary>
    /// Index を DataTemplate に変換する Selector
    /// </summary>
    public class IndexToTemplateSelector : DataTemplateSelector
    {
        /// <summary>
        /// テンプレートリスト
        /// </summary>
        public DataTemplateCollection Templates { get; private set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public IndexToTemplateSelector()
            : base()
        {
            this.Templates = new DataTemplateCollection();
        }

        /// <summary>
        /// Index から対応する DataTemplate を返却します
        /// </summary>
        /// <param name="item">int値</param>
        /// <param name="container">コンテナ</param>
        /// <returns>DataTemplate</returns>
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            int index = 0;

            if (item != null && int.TryParse(item.ToString(), out index) && Templates.Count > index)
            {
                return this.Templates[index];
            }

            return null;
        }
    }

Selector 本体はこんな感じで記述

Templates プロパティの型は List にしても一応動作はするんですが、上記のように ContentProperty の属性を持ったクラスの型で定義した方が XAML のエラーが出ないので精神衛生上よいかと思います

<ListView.ItemTemplate>
    <DataTemplate>
        <Border Background="White">
            <ContentControl HorizontalContentAlignment="Center"
                            VerticalContentAlignment="Center"
                            Content="{Binding}"
                            ContentTemplateSelector="{StaticResource IndexToTemplateSelector}" />
        </Border>
    </DataTemplate>
</ListView.ItemTemplate>

アイコンを Flyout 用に並べる部分は ContentControl の Content に Index 値、ContentTemplateSelector に今回作成した Selector を指定すれば大丈夫でした!