Reputation: 7414
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
.
Why does the ListView
not properly use one of the DataTemplate
s 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 DataTemplate
s 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
.
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>
Upvotes: 0
Views: 1112
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