I'm making a app with .net maui on .net 6. I have read about grouping data for a collectionview, I have tried the examples that were in this documentation and those worked perfectly. After that I needed to take it a step further and I needed to put multiple groups in the collectionview. now I have tried a lot and researched a lot but I cant find anything about putting multiple goups in the collectionview group, only one class that you can use.
my goal is to put this json file in a collectionview group:
{
"Plans": [
{
"planId": 16,
"planName": "Indoor",
"tables": [
{
"tableIdId": 77,
"tableName":"Table 1",
"tableStatus": "vrij"
},
{
"tableIdId": 78,
"tableName": "Table 2",
"tableStatus":"vrij"
}
]
},
{
"planId": 17,
"planName": "Outdoor",
"tables": [
{
"tableIdId": 177,
"tableName": "Table 11",
"tableStatus":"Bezet"
},
{
"tableIdId": 178,
"tableName": "Table 12",
"tableStatus":"vrij"
}
]
}
]
}
i have trief it like this in the class file:
public class tafelLijst : List<tafelGroep>
{
private tafelGroep tafelGroep;
public tafelLijst( List<tafelGroep> tafelGroepen) : base(tafelGroepen)
{
}
public tafelLijst(tafelGroep tafelGroep)
{
this.tafelGroep = tafelGroep;
}
}
public class tafelGroep : List<NieweTafel>
{
public string planName { get; private set; }
public tafelGroep(string name, List<NieweTafel> nieweTafels) : base(nieweTafels)
{
planName = name;
}
}
public class NieweTafel
{
public int tableIdId { get; set; }
public string tableName { get; set; }
public string tableStatus { get; set; }
}
but this doesnt work. I am still new to .net maui and .net in general so I havo no idee how to do this. Is it porible to put multiple groups in the collectionview and if the awnser is yes, how?
(This is my first question on stackoverflow so if things are not clear or you need more information please give the feedback so i can improve the question)
You can search relevant web about Convert Json to C# Classes:
public class Plan
{
public int planId { get; set; }
public string planName { get; set; }
public List<Table> tables { get; set; }
}
public class Root
{
public List<Plan> Plans { get; set; }
}
public class Table
{
public int tableIdId { get; set; }
public string tableName { get; set; }
public string tableStatus { get; set; }
}
MainPage.xaml(Plans list):
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp_loadXaml.MainPage"
x:Name="contentPage">
<StackLayout>
<CollectionView x:Name="collection_View"
Margin="10,0"
SelectionMode="Single"
SelectionChanged="collectionView_SelectionChanged">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding planName}"/>
<Label Text="{Binding planId}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
MainPage.xaml.cs, it uses JsonConvert.DeserializeObject<T>(json). Deserializes the JSON to the specified .NET type:
public partial class MainPage : ContentPage
{
string json = "{\r\n \"Plans\": [\r\n {\r\n \"planId\": 16,\r\n \"planName\": \"Indoor\",\r\n \"tables\": [\r\n {\r\n \"tableIdId\": 77,\r\n \"tableName\": \"Table 1\",\r\n \"tableStatus\": \"vrij\"\r\n },\r\n {\r\n \"tableIdId\": 78,\r\n \"tableName\": \"Table 2\",\r\n \"tableStatus\": \"vrij\"\r\n }\r\n ]\r\n },\r\n {\r\n \"planId\": 17,\r\n \"planName\": \"Outdoor\",\r\n \"tables\": [\r\n {\r\n \"tableIdId\": 177,\r\n \"tableName\": \"Table 11\",\r\n \"tableStatus\": \"Bezet\"\r\n },\r\n {\r\n \"tableIdId\": 178,\r\n \"tableName\": \"Table 12\",\r\n \"tableStatus\": \"vrij\"\r\n }\r\n ]\r\n }\r\n ]\r\n }";
Root Root;
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Root = JsonConvert.DeserializeObject<Root>(json);
var plans = new List<Plan>();
foreach (var item in Root.Plans)
{
plans.Add(new Plan
{
planId = item.planId,
planName = item.planName,
tables = item.tables
});
}
collection_View.ItemsSource = plans.ToList();
}
private async void collectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection != null)
{
Plan item = (Plan)e.CurrentSelection.FirstOrDefault();
var tables = item.tables;
await Navigation.PushAsync(new Tables(tables));
}
}}
Tables.xaml(It shows tables list):
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp_loadXaml.Tables"
Title="Table">
<StackLayout>
<CollectionView x:Name="collection_View"
Margin="10,0">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding tableName}"/>
<Label Text="{Binding tableStatus}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
Tables.xaml.cs:
public partial class Tables : ContentPage
{
List<Table> list;
public Tables(List<Table> tables)
{
InitializeComponent();
list = tables;
collection_View.ItemsSource= list.ToList();
}
}
Wish it can help you.
Related
I created a custom ContentPage to show a circle avatar with initials. When I pass a value via binding it comes up as null, I debugged to check this. Can someone help me please? Thanks.
HomePage.xaml
<views:CircleView CircleText="{Binding Initials}"/>
CircleView.xaml
<Frame x:Name="circleFrame">
<Label x:Name="circleLabel"
Text="{Binding CircleText}"/>
</Frame>
CircleView.xaml.cs
public partial class CircleView : ContentView
{
public CircleView()
{
InitializeComponent();
BindingContext = this;
}
public static readonly BindableProperty CircleTextProperty =
BindableProperty.Create(nameof(CircleText), typeof(string), typeof(CircleView), null);
public string CircleText
{
get { return (string)GetValue(CircleTextProperty); }
set { SetValue(CircleTextProperty, value); }
}
}
You could try the code below.
CircleView.xaml:
<Frame x:Name="circleFrame">
<Label x:Name="circleLabel"/>
</Frame>
CircleView.xaml.cs:
public CircleView()
{
InitializeComponent();
}
public static readonly BindableProperty CircleTextProperty =
BindableProperty.Create(nameof(CircleText), typeof(string), typeof(CircleView), propertyChanged:(b,o,n)=>(b as CircleView).OnChanged());
private void OnChanged()
{
circleLabel.Text = CircleText;
}
public string CircleText
{
get { return (string)GetValue(CircleTextProperty); }
set { SetValue(CircleTextProperty, value); }
}
MainPage.xaml:
<views:CircleView CircleText="{Binding Initials}"/>
MainPage.xaml.cs:
public string Initials { get; set; }
public MainPage()
{
InitializeComponent();
Initials = "Hello";
this.BindingContext = this;
}
In CircleView.xaml, you should include a source to the binding, would be something like:
<ContentView x:Name="Self">
<Frame x:Name="circleFrame">
<Label x:Name="circleLabel"
Text="{Binding source={x:reference Self}, Path=CircleText}"/>
</Frame>
</ContentView>
I've got a class that has an ObservableCollection of itself embedded within the class.
I'm trying to create a user control that also has a reference to itself in order to display the contents of the observable collection. However, I'm getting a runtime error whenever I'm trying to run the app.
The error is not overly meaningful:
XAML parsing failed.
E_RUNTIME_SETVALUE [Line: 91 Position: 58] (which is the line that has the recursive call to the user control)
The class looks something like this (it's been made shorter for illustration purposes)
public class BookChapterVm : IBookChapterVm
{
public int Id {get;set;}
public string ChapterText {get;set;}
public ObservableCollection<IBookChapterVm> Chapters { get; set; } = new ObservableCollection<IBookChapterVm>();
}
The user control looks something like this (again, unnecessary parts are removed)
<UserControl
x:Class="Cgs.Ux.UserControls.HelpTextEditor.BookChapterEditorCtrl">
<ListView
ItemsSource="{x:Bind Vm.Chapters, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="help:BookChapterVm">
<StackPanel Orientation="Horizontal">
<local:BookChapterEditorCtrl Vm="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
I've also tried to set up a recursive data template, but it basically ended up with the same error.
Here is a working exemple :
In your page :
<local:RecursiveContainer ViewModel="{Binding}" />
The code behind :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = BuildBookChapterVM();
}
private BookChapterVM BuildBookChapterVM()
{
BookChapterVM vm1 = new BookChapterVM { ChapterText = "1" };
BookChapterVM vm21 = new BookChapterVM { ChapterText = "21" };
BookChapterVM vm22 = new BookChapterVM { ChapterText = "22" };
BookChapterVM vm211 = new BookChapterVM { ChapterText = "211" };
vm1.Chapters.Add(vm21);
vm1.Chapters.Add(vm22);
vm21.Chapters.Add(vm211);
return vm1;
}
}
public class BookChapterVM
{
public int Id { get; set; }
public string ChapterText { get; set; }
public ObservableCollection<BookChapterVM> Chapters { get; set; } = new ObservableCollection<BookChapterVM>();
}
The UserControl XAML :
<UserControl
x:Class="WpfApp2.RecursiveContainer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel>
<TextBlock Text="{Binding ChapterText}" />
<ItemsControl HorizontalContentAlignment="Stretch" ItemsSource="{Binding Chapters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:RecursiveContainer
Margin="10,5,0,5"
HorizontalAlignment="Stretch"
ViewModel="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
The UC code behind :
public partial class RecursiveContainer : UserControl
{
public RecursiveContainer()
{
InitializeComponent();
}
public BookChapterVM ViewModel
{
get { return (BookChapterVM)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(RecursiveContainer), typeof(RecursiveContainer));
}
See image as proof of concept.
I hope it will help you ;)
I have this simple ListView filled from an ObservableCollection. Once the list is bound, I would like to access the parent vm view model from inside this ItemTemplate so that I can bind the command called cmd_delete_mesh. How is this done for a UWP Xaml app (not wpf)?
<ListView x:Name="mesh_list" SelectedItem="{x:Bind vm.selected_mesh, Mode=TwoWay}" ItemsSource="{x:Bind vm.meshes}">
<ListView.ItemTemplate>
<DataTemplate>
<ListViewItem>
<Button Command="{Binding cmd_delete_mesh}"/>
You can do like so:
<ListView x:Name="mesh_list" SelectedItem="{x:Bind vm.selected_mesh, Mode=TwoWay}" ItemsSource="{x:Bind vm.meshes}">
<ListView.ItemTemplate>
<DataTemplate>
<ListViewItem>
<Button Command="{Binding ElementName=mesh_list, Path=DataContext.vm.cmd_delete_mesh}"/>
I do this from code unfortunately... I’ll post an example of my code soon
You could define your command in your model and declare an event in it. In your ViewModel, when you initialize the 'meshes' collection, you could register this event for every item in this collection. Then, when the command is executed, you just need to raise the event and do some operations in its event handler.
I made a simple code sample for your reference:
<ListView x:Name="mesh_list" SelectedItem="{x:Bind ViewModel.selected_mesh, Mode=TwoWay}" ItemsSource="{x:Bind ViewModel.meshes,Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:SubTest">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name }"></TextBlock>
<Button Command="{x:Bind cmd_delete_mesh}" Content="delete"/>
</StackPanel>
</ListViewItem>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
ViewModel = new Test("test data");
}
private Test ViewModel { get; set; }
}
public class Test : ViewModelBase
{
public string Name { get; set; }
private SubTest _selected_mesh;
public SubTest selected_mesh
{
get { return _selected_mesh; }
set
{
if (_selected_mesh != value)
{
_selected_mesh = value;
RaisePropertyChanged("selected_mesh");
}
}
}
public ObservableCollection<SubTest> meshes { get; set; } = new ObservableCollection<SubTest>();
public Test(string name)
{
this.Name = name;
for (int i = 0; i < 10; i++)
{
var sub = new SubTest() { Name = "String " + i };
sub.DeleteParentItem += Test_DeleteParentItem;
meshes.Add(sub);
}
}
private void Test_DeleteParentItem()
{
if (selected_mesh != null)
{
DeleteItem(selected_mesh);
}
}
private void DeleteItem(SubTest subTest)
{
//TODO...
}
}
public class SubTest
{
public RelayCommand cmd_delete_mesh { get; set; }
public string Name { get; set; }
public event Action DeleteParentItem;
public SubTest()
{
cmd_delete_mesh = new RelayCommand(DeleteItem);
}
private void DeleteItem()
{
if (DeleteParentItem != null)
{
DeleteParentItem.Invoke();
}
}
}
Please note that the ViewModelBase and RelayCommand are from mvvmlight.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
Please assist:
I have implemented the MVVM design on a simple app using Xamarin.
I have one Model (User) and one ViewModel (UserViewModel).
Please note that this app is my first Xamarin/MVVM app and that I am new to this.
The issue that i have is that adding or removing a User, the View does NOT update.
When I add or remove a user I can confirm that the Database is updated, but not my view.
Please see my code below, what am i missing?
User Model:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
public List<Role> RolesList { get; set; }
}
UserViewModel Code:
public class UsersViewModel : INotifyPropertyChanged
{
private UserServices UserServ { get; set; }
public User UserSelected { get; set; }
private ObservableCollection<User> userList;
public ObservableCollection<User> UserList
{
get
{
return userList;
}
set
{
if (userList != value)
{
userList = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public UsersViewModel()
{
UserServ = new UserServices();
UsersLoadAsync();
}
public async void UsersLoadAsync()
{
UserList = await UserServ.UsersGetAsync();
}
}
User Helper Service code (Added for completeness)
public class UserServices
{
public async Task<ObservableCollection<User>> UsersGetAsync()
{
ObservableCollection<User> UserList = await App.UserService.GetAsync();
return UserList;
}
public async Task<bool> UsersAddAsync(User user)
{
bool success = await App.UserService.PostAsync(user);
return success;
}
public async Task<bool> UsersRemoveAsync(User user)
{
bool success = await App.UserService.DeleteAsync(user.Id, user);
return success;
}
}
View Xaml Code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PB_Logbook"
x:Class="PB_Logbook.MainPage"
xmlns:ViewModels="clr-namespace:PB_Logbook.ViewModels;assembly:PB_Logbook">
<ContentPage.BindingContext>
<ViewModels:UsersViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<ListView ItemsSource="{Binding UserList, Mode=TwoWay}" HasUnevenRows="True" ItemSelected="Item_SelectedAsync" IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Username}" FontSize="24"/>
<Label Text="{Binding FirstName}" FontSize="18" Opacity="0.6"/>
<Label Text="{Binding LastName}" FontSize="18" Opacity="0.6"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Add" Clicked="AddButton_ClickedAsync"></Button>
<Button Text="Remove" Clicked="RemoveButton_ClickedAsync"></Button>
</StackLayout>
</ContentPage>
View code behind:
public partial class MainPage : ContentPage
{
private UserServices UserServices { get; set; }
private UsersViewModel UsersVM { get; set; }
public MainPage()
{
InitializeComponent();
UserServices = new UserServices();
UsersVM = new UsersViewModel();
}
private async void AddButton_ClickedAsync(object sender, EventArgs e)
{
await AddUserAsync();
}
private async void RemoveButton_ClickedAsync(object sender, EventArgs e)
{
await RemoveUserAsync();
}
private async void Item_SelectedAsync(object sender, EventArgs e)
{
UsersVM.UserSelected = ((User)((ListView)sender).SelectedItem);
}
private async void Pull_RefreshAsync(object sender, EventArgs e)
{
//UsersVM.UsersLoadAsync();
}
private async Task AddUserAsync()
{
Random rnd = new Random();
int rndNumber = rnd.Next(1, 100);
User user = new User()
{
Username = "User " + rndNumber,
FirstName = "Firstname " + rndNumber,
LastName = "Surname " + rndNumber,
IsActive = true
};
bool success = await UserServices.UsersAddAsync(user);
if (success)
{
if (!UsersVM.UserList.Contains(user))
UsersVM.UserList.Add(user);
}
}
private async Task RemoveUserAsync()
{
bool success = await UserServices.UsersRemoveAsync(UsersVM.UserSelected);
if (success)
{
if (UsersVM.UserList.Contains(UsersVM.UserSelected))
UsersVM.UserList.Remove(UsersVM.UserSelected);
}
}
}
The issue is with adding/removing users that does not update in my view.
Thank you.
If you're new to Xamarin MVVM, this link will help you understand the basics of MVVM in Xamarin Forms
https://deanilvincent.github.io/2017/06/03/basic-understanding-of-mvvm-and-databinding-in-xamarin-forms/
I would suggest as well, please lessen your behind the codes and just implement everything including the commands in your ViewModel.
You've written that your codes are working when saving and updating but not reflecting the view right? You should put your method in fetching the list right after your save command.
Like this in your xaml
<Button Text="Save" Command="{Binding SaveCommand}"/>
In your ViewModel, you should use Command from Xamarin
public Command SaveCommand{
get{
return new Command(async()=>{
// your command save here
// then put your method for fetching the updated list: your UsersLoadAsync();
});
}
}
If you're new to MVVM, you can also check this link. It uses Xamarin MVVM. When you finish, you'll have simple weather app with simple mvvm implementations
I hope it helps you
I'm trying to create a TemplateSelector which recognizes if an implements an interface and applies a DataTemplate for it.
I'd like to use this selector in following way:
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" />
</DataTemplate>
<DataTemplate x:Key="IDocument">
<TextBlock Text="test" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
I end up with following implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<Type, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<Type, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var result = (
from t in Items
where t.Key.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
It of course doesn't work, otherwise I wouldn't write this question :) Application crushes with a message a xaml cannot be parsed:
A first chance exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe but was not handled in user code
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
Additional information: Unspecified error
What's wrong with that dictionary? Normally I'd use CompositeCollection and merge multiple sources but this class is missing in WinRT :(
EDIT
In terms of fixing dictionary problem I've changed dictionary to list of custom types. Still can't create a custom type with Type set from XAML. I could use a string but than I can't manage it in code unless I specify fully qualified type name.
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public List<InterfaceAwareTemplateSelectorItem> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new List<InterfaceAwareTemplateSelectorItem>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from t in Items
where t.Type.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Template).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
public class InterfaceAwareTemplateSelectorItem
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
Corresponding XAML:
// somewhere in page tag
xmlns:bo="using:/*long long namespace*/.BusinessObjects"
// somewhere in XAML file
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IMedia">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<Image Source="{Binding PreviewImage}"
Tapped="ImageTapped" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IDocument">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<TextBlock Text="pa8u4mrapwu" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Okay, so using this:
public interface IFake1 { }
public interface IFake2 { }
public class TemplateItem
{
public DataTemplate Template { get; set; }
public string Interface { get; set; }
}
public class MySelector : DataTemplateSelector
{
public List<TemplateItem> Templates { get; set; }
}
I could do this:
<GridView>
<GridView.ItemTemplateSelector>
<local:MySelector>
<local:MySelector.Templates>
<local:TemplateItem Interface="IFake1">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
<local:TemplateItem Interface="IFake2">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
</local:MySelector.Templates>
</local:MySelector>
</GridView.ItemTemplateSelector>
</GridView>
The error appears in the Type you are using. I could not get that to work. Had to use String. Should be simple to parse form there.
Best of luck!
See if it works if you replace Dictionary<Type, DataTemplate> with ResourceDictionary. I'm betting at least one of the problems is that the key in x:Key="IMedia" can't be implicitly converted to Type. You could also just try using string as the key type.
I finally managed to fix it. Unfortunately I couldn't get a conversion from string to Type in XAML so I had to stick to strings :/ Not very convenient but at least works. That's what I ended up with:
XAML
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" Tapped="ImageTapped"/>
</DataTemplate>
<DataTemplate x:Key="IDocument">
<commonItems:DocumentItemPresenter TappedCommand="{Binding DataContext.OpenDocument, ElementName=PageRoot}"/>
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Selector itself:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
I upvoted Filip & Jerry because I found your tips helpful. Thank you guys.
If anyone is interested how this problem was solved below is final implementation and a usage example.
Usage:
<GridView ....>
<GridView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="INewsContainer" selectors:InterfaceAwareTemplateSelector.Priority="1">
<ctrls:ItemsContainerTile Width="350" Height="350"
ItemTappedCommand="{Binding DataContext.OpenNewsDetails, ElementName=PageRoot}"/>
</DataTemplate>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="ISimpleMaterial" selectors:InterfaceAwareTemplateSelector.Priority="0">
<ctrls:GenericTile Width="350" Height="350"
TappedCommand="{Binding DataContext.OpenDetails, ElementName=PageRoot}" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</GridView.ItemTemplateSelector>
.... the rest of XAML
The priority is to control the order in which datatamples should be checked. This way we can control what happens if multiple keys matches the object being converted.
Implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var results = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt)
.ToArray();
if (results.Length > 1) {
var orderedResults =
from r in results
where IsPrioritySet(r.Value)
orderby GetPriority(r.Value) descending
select r;
if (orderedResults.Any()) {
return orderedResults.First().Value;
}
throw new AmbigiousResolveTemplateFound(item.GetType(), results.Select(x => x.Key));
}
else if (results.Length == 1) {
return results[0].Value;
}
return DefaultTemplate;
}
#region PriorityProperty
public static readonly DependencyProperty PriorityProperty =
DependencyProperty.RegisterAttached(
"Priority",
typeof(int),
typeof(InterfaceAwareTemplateSelector),
new PropertyMetadata(0));
public static int GetPriority(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
return (int)item.GetValue(PriorityProperty);
}
public static void SetPriority(DependencyObject item, int value) {
if (item == null) { throw new ArgumentNullException("item"); }
item.SetValue(PriorityProperty, value);
}
public static bool IsPrioritySet(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
var result = item.ReadLocalValue(PriorityProperty);
return result != DependencyProperty.UnsetValue;
}
#endregion
}
Hope someone will find this implementation helpful. Once again, many thanks to Filip and Jerry for your help.