UWP DataTemplates for multiple item types in ListView

How can i implement this?

Let's say this is my model:

public interface IAnimal { string Name { get; } } public class Fish : IAnimal { public string Name { get; set; } public int ScalesCount { get; set; } } public class Dog : IAnimal { public string Name { get; set; } public string CollarManufacturerName { get; set; } } public class ViewModel { public ObservableCollection<IAnimal> Animals { get; set; } public ViewModel() { this.Animals = new ObservableCollection<IAnimal>(); this.Animals.Add(new Fish { Name = "Carl", ScalesCount = 9000 }); this.Animals.Add(new Dog { Name = "Fifi", CollarManufacturerName = "Macrosoft" }); } } 

Due to the amount of code in this question, suppose that INotifyPropertyChanged is executed where necessary, and that the ViewModel is correctly initialized on the page.

How can I use my own DataTemplates? In WPF, I would simply define a few DataTemplates without x:Key , but with a specific DataType type, and let the ListView choose what to use depending on the type of element. UWP doesn't like this; the compiler simply indicates the Dictionary Item "DataTemplate" must have a Key attribute . So how can I achieve my goal?

Current attempt

My current attempt is to create a custom DataTemplateSelector , which seems pretty straightforward.

 public class MyDataTemplateSelector: Windows.UI.Xaml.Controls.DataTemplateSelector { public ObservableCollection<TemplateMatch> Matches { get; set; } public DataTemplateSelector() { this.Matches = new ObservableCollection<TemplateMatch>(); } protected override DataTemplate SelectTemplateCore(object item) { return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template; } } public class TemplateMatch { public Type TargetType { get; set; } public DataTemplate Template { get; set; } } 

Define it in XAML as follows:

 <ListView ItemsSource="{x:Bind ViewModel.Animals}"> <ListView.ItemTemplateSelector> <cmp:MyDataTemplateSelector> <cmp:MyDataTemplateSelector.Matches> <cmp:TemplateMatch TargetType="model:Dog" Template="{StaticResource DogTemplate}"/> <cmp:TemplateMatch TargetType="model:Fish" Template="{StaticResource FishTemplate}"/> </cmp:MyDataTemplateSelector.Matches> </cmp:MyDataTemplateSelector> </ListView.ItemTemplateSelector> </ListView> 

Unfortunately, when I run this, an exception occurs at runtime, indicating Failed to create a 'Ui.Components.TemplateMatch' from the text 'model:Dog'. So it seems that binding to the Type property is not so simple.

Any help is appreciated!

Note that I would like to use a property of type Type , as opposed to string , where I would pass in the name of the CLR type and use reflection to call the type, mainly because I do not want mixed CLR and XML namespaces to be displayed in XAML . If you can find a way to invoke a type using the XML namespace, I will gladly take this as an answer.

+7
listview xaml win-universal-app datatemplate
source share
2 answers

I found a workaround. If you can instantiate these types, you can use them to detect types:

 [ContentProperty(Name = nameof(Matches))] public class TypeTemplateSelector : DataTemplateSelector { public ObservableCollection<TemplateMatch> Matches { get; set; } public TypeTemplateSelector() { this.Matches = new ObservableCollection<TemplateMatch>(); } protected override DataTemplate SelectTemplateCore(object item) { return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent; } } [ContentProperty(Name = nameof(ItemOfType))] public class TemplateMatch { public object ItemOfType { get; set; } public DataTemplate TemplateContent { get; set; } } 

XAML:

 <controls:TypeTemplateSelector> <controls:TemplateMatch TemplateContent="{StaticResource FishTemplate}"> <models:Fish/> </controls:TemplateMatch> <controls:TemplateMatch TemplateContent="{StaticResource DogTemplate}"> <models:Dog/> </controls:TemplateMatch> </controls:TypeTemplateSelector> 
+2
source share

The key is in the error message.

Failed to create 'Ui.Components.TemplateMatch' from the text 'Model: Dog'

Note that "model: Dog" matches your selector as text, not a type.

Change the TargetType property of the TemplateMatch class to a string instead of a type, for example:

 public class TemplateMatch { public string TargetType { get; set; } public DataTemplate Template { get; set; } } 

Then change the template selection class to read

 public class MyDataTemplateSelector : DataTemplateSelector { public ObservableCollection<TemplateMatch> Matches { get; set; } public MyDataTemplateSelector() { Matches = new ObservableCollection<TemplateMatch>(); } protected override DataTemplate SelectTemplateCore(object item) { return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template; } } 

Finally, change your haml to read

 <ListView ItemsSource="{x:Bind ViewModel.Animals}"> <ListView.ItemTemplateSelector> <cmp:MyDataTemplateSelector> <cmp:MyDataTemplateSelector.Matches> <cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Dog" Template="{StaticResource DogTemplate}"/> <cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Fish" Template="{StaticResource FishTemplate}"/> </cmp:MyDataTemplateSelector.Matches> </cmp:MyDataTemplateSelector> </ListView.ItemTemplateSelector> </ListView> 

The point is to forget to try passing it to your selector as a type and passing the type name instead (the full namespace, not the Xaml namespace).

0
source share

All Articles