Johnathon Sullinger
Johnathon Sullinger

Reputation: 7414

ContentControl won't use DataTemplates

I have two models that i want to represent as directories and files. The models both implement an interface which contains a property string Name {get;}.

public interface INode
{
    string Name { get; }
    string Path { get; }
}

public class Directory : INode
{
    public Directory(string name, string path)
    {
        this.Name = name;
        this.Path = path;
    }

    public string Name { get; }

    public string Path { get; }
}

public class File : INode
{
    public File(string name, string path)
    {
        this.Name = name;
        this.Path = path;
    }

    public string Name { get; }

    public string Path { get; }
}

I created two DataTemplates, one for each INode implementation. Each template has a ContentControl to render a Path, and a TextBlock to render the INode.Name property.

<views:NavigationAwarePage
    x:Class="OpenTasks.Views.ProviderDirectoryBrowserPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="using:OpenTasks.Views"
    xmlns:models="using:OpenTasks.DomainLogic">

    <views:NavigationAwarePage.Resources>

        <DataTemplate x:Key="FolderIconTemplate">
            <Path Data="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" 
                          Fill="{StaticResource AppTint}" 
                          HorizontalAlignment="Stretch"
                          VerticalAlignment="Stretch"
                          Width="96"
                          Height="96">
                <Path.RenderTransform>
                    <ScaleTransform ScaleX="2" ScaleY="2" />
                </Path.RenderTransform>
            </Path>
        </DataTemplate>
        <DataTemplate x:Key="FileIconTemplate">
            <Path Data="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z"
                          Fill="{StaticResource AppTint}" 
                          HorizontalAlignment="Stretch"
                          VerticalAlignment="Stretch"
                          Width="96"
                          Height="96">
                <Path.RenderTransform>
                    <ScaleTransform ScaleX="2" ScaleY="2" />
                </Path.RenderTransform>
            </Path>
        </DataTemplate>

        <DataTemplate x:DataType="models:Directory"
                              x:Key="DirectoryItemTemplate">
            <StackPanel Orientation="Horizontal">
                <ContentControl Content="{StaticResource FolderIconTemplate}" />
                <TextBlock Text="{Binding Path=Name}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:DataType="models:File"
                              x:Key="FileItemTemplate">
            <StackPanel Orientation="Horizontal">
                <ContentControl Content="{StaticResource FileIconTemplate}" />
                <TextBlock Text="{Binding Path=Name}" />
            </StackPanel>
        </DataTemplate>
    </views:NavigationAwarePage.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{Binding Path=ContentsOfPath}" />
    </Grid>
</views:NavigationAwarePage>

I then databound a ListView to a List<INode> collection. The problem however is that the ListView just renders the ToString() version of the INode implementation, instead of using the DataTemplate.

enter image description here

Why does the ListView not properly use one of the DataTemplates I have defined? I have also put together a DataTemplateSelector, but the ListView still does not use one of my templates.

public class DirectoryListingTemplateSelector : DataTemplateSelector
{
    public DataTemplate DirectoryTemplate { get; set; }
    public DataTemplate FileTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        if (item is File)
        {
            return FileTemplate;
        }

        return DirectoryTemplate;
    }
}

<views:NavigationAwarePage.Resources>
    <views:DirectoryListingTemplateSelector x:Key="DirectoryListingSelector" 
                                        FileTemplate="{StaticResource FileItemTemplate}"
                                        DirectoryTemplate="{StaticResource DirectoryItemTemplate}" />


<ListView ItemsSource="{Binding Path=ContentsOfPath}"
            ItemTemplateSelector="{StaticResource DirectoryListingSelector}" />

Do UWP apps handle DataTemplates differently than WPF does? I've noticed I can't define a template without specifying a key, even if I specify a DataType. I'm not sure what other differences there may be that's causing this problem for me. This exact example is working fine for me in a WPF app, so the issue is specific to UWP.

Edit

I'm not sure why it wasn't working the first time I tried, but the template selector now works. I'd prefer not to write template selectors for every ContentControl or ItemsControl that can support more than one template. Are template selectors the only way to go in UWP?

<views:NavigationAwarePage
    x:Class="OpenTasks.Views.ProviderDirectoryBrowserPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:views="using:OpenTasks.Views"
    xmlns:models="using:OpenTasks.DomainLogic"
    xmlns:viewModels="using:OpenTasks.ViewModels"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DataContext="viewModels:ProviderDirectoryBrowserViewModelDesignData">

    <views:NavigationAwarePage.Resources>
        <views:DirectoryListingTemplateSelector x:Key="DirectoryListingSelector" 
                                                FileTemplate="{StaticResource FileItemTemplate}"
                                                DirectoryTemplate="{StaticResource DirectoryItemTemplate}" />

        <DataTemplate x:Key="FolderIconTemplate">
            <Path Data="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" 
                  Fill="{StaticResource AppTint}" 
                  HorizontalAlignment="Stretch"
                  VerticalAlignment="Center"
                  Width="24"
                  Height="24" />
        </DataTemplate>
        <DataTemplate x:Key="FileIconTemplate">
            <Path Data="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z"
                  Fill="{StaticResource AppTint}" 
                  HorizontalAlignment="Stretch"
                  VerticalAlignment="Center"
                  Width="24"
                  Height="24" />
        </DataTemplate>

        <DataTemplate x:DataType="models:Directory"
                      x:Key="DirectoryItemTemplate">
            <StackPanel Orientation="Horizontal">
                <ContentControl ContentTemplate="{StaticResource FolderIconTemplate}"
                                VerticalAlignment="Center"
                                Margin="0 0 10 0"/>
                <TextBlock Text="{Binding Path=Name}"
                           VerticalAlignment="Center"/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:DataType="models:File"
                      x:Key="FileItemTemplate">
            <StackPanel Orientation="Horizontal">
                <ContentControl ContentTemplate="{StaticResource FileIconTemplate}" 
                                Margin="0 0 10 0"
                                VerticalAlignment="Center"/>
                <TextBlock Text="{Binding Path=Name}"
                           VerticalAlignment="Center"/>
            </StackPanel>
        </DataTemplate>
    </views:NavigationAwarePage.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{Binding Path=ContentsOfPath}"
                  ItemTemplateSelector="{StaticResource DirectoryListingSelector}" />
    </Grid>
</views:NavigationAwarePage>

enter image description here

Upvotes: 0

Views: 1112

Answers (1)

Kevin Gosse
Kevin Gosse

Reputation: 39007

I'd say there's a few ways to tackle this issue. In your case, I'd be tempted to use a custom converter and assign your icon templates to it.

The converter could look like:

public class FileIconConverter : IValueConverter
{
    public DataTemplate FileIconTemplate { get; set; }

    public DataTemplate FolderIconTemplate { get; set; }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is Directory)
        {
            return this.FolderIconTemplate;
        }

        if (value is File)
        {
            return this.FileIconTemplate;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Then, in the page resources, create the converter and assign the icon templates:

<local:FileIconConverter x:Key="FileIconConverter">
    <local:FileIconConverter.FileIconTemplate>
        <DataTemplate>
            <Path Data="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z"
                    Fill="{StaticResource AppTint}" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Width="96"
                    Height="96">
                <Path.RenderTransform>
                    <ScaleTransform ScaleX="2" ScaleY="2" />
                </Path.RenderTransform>
            </Path>
        </DataTemplate>
    </local:FileIconConverter.FileIconTemplate>
    <local:FileIconConverter.FolderIconTemplate>
        <DataTemplate>
            <Path Data="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" 
                        Fill="{StaticResource AppTint}" 
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        Width="96"
                        Height="96">
                <Path.RenderTransform>
                    <ScaleTransform ScaleX="2" ScaleY="2" />
                </Path.RenderTransform>
            </Path>
        </DataTemplate>
    </local:FileIconConverter.FolderIconTemplate>
</local:FileIconConverter>

Lastly, in your listview, bind your data and use the converter:

<ListView x:Name="ListView">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <ContentControl ContentTemplate="{Binding Converter={StaticResource FileIconConverter}}" 
                            Margin="0 0 10 0"
                            VerticalAlignment="Center"/>
                <TextBlock Text="{Binding Path=Name}"
                        VerticalAlignment="Center"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Upvotes: 4

Related Questions