I have a custom cell displayed in a listview. It should not be working but my concern is that it works and I don't understand why.
Let me lay things out for you, because it's a little bit complex.
Basically, I'm displaying a contact list with a search field on top. I have various other things to display that are out of the scope of this question, but you'll see them throughout the code for clarity, notably in xaml and in the Data template selector.
I'm using different types of custom cells to display each part of my contact list (there's a header cell, a search cell, and some more).
Here, ContactsPage
is holding the listview and the declaration of the datatemplate.
<ContentPage>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="HeaderTemplate">
<ViewCell>
<StackLayout>
<local:HeaderView/>
</StackLayout>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="SearchTemplate">
<local:SearchCell/> //<=== Important
</DataTemplate>
<DataTemplate x:Key="CategoryTemplate">
<ViewCell
x:Name="CategoryCell">
<Label
Text="CategoryCell" ></Label>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="SelectionTemplate">
<ViewCell
x:Name="SelectionCell">
<Label
Text="Selection Cell" ></Label>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="ContactTemplate">
<ViewCell
x:Name="ContactCell">
<Label
Text="{Binding FirstName}" ></Label>
</ViewCell>
</DataTemplate>
<local:ContactDataTemplateSelector x:Key="TemplateSelector"
HeaderTemplate="{StaticResource HeaderTemplate}"
SearchTemplate="{StaticResource SearchTemplate}"
CategoryTemplate="{StaticResource CategoryTemplate}"
SelectionTemplate="{StaticResource SelectionTemplate}"
ContactTemplate="{StaticResource ContactTemplate}"/>
</ResourceDictionary>
</ContentPage.Resources>
You see I have various Datatemplates, each for its own use. Header is working, the rest is in progress, the only thing I care about is the Search implementation. From the cell, to the viewmodel, and through the data template.
Now this was only the resources, here's the actual page UI (only relevant code)
<ContentPage.Content>
... Content of the page, including the actual listview
<ListView
x:Name="ContactsListView"
HasUnevenRows="True""
ItemTemplate="{StaticResource TemplateSelector}"
ItemsSource="{Binding ListSource}">
</ListView>
</ContentPage.Content>
Let me take you through the journey of the logic behind this, in the viewmodel of that view. The code-behind of the view itself does nothing, and here is the ViewModel of that contact list.
public class ContactsViewModel : BaseViewModel, IContactsViewModel
{
readonly IContactsService _service;
List<object> _listSource;
public List<object> ListSource
{
get => _listSource;
private set
{
_listSource = value;
OnPropertyChanged();
}
}
public string CurrentText => "HelloX"; //<=== Important
readonly ISearchViewModel _searchViewModel;
readonly ICategoryFilterViewModel _categoryFilterViewModel;
readonly ISelectionViewModel _selectionViewModel;
public ContactsViewModel()
{
_service = new();
HeaderViewModel = new HeaderViewModel();
_searchViewModel = new();
_categoryFilterViewModel = new();
_selectionViewModel = new();
ListSource = GenerateDefaultList();
}
public async Task LoadContacts() //Called when UI appears.
{
await _service.LoadContacts();
var list = GenerateDefaultList();
list.AddRange(_service.Contacts);
ListSource = list;
}
List<object> GenerateDefaultList()
{
return new List<object>()
{
HeaderViewModel,
_searchViewModel, //<===== important
_categoryFilterViewModel,
_selectionViewModel
};
}
}
I'm including most of the code for the sake of clarity ; the important part here that I want to emphasize, is that the ListSource
already has some viewmodels inside it. I'm using that source to populate my listview, and the type of object defines what type of data template i'm gonna use. This is done in the DataTemplate selector, which is here :
public class ContactDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ContactTemplate { get; set; }
public DataTemplate HeaderTemplate { get; set; }
public DataTemplate SearchTemplate { get; set; }
public DataTemplate CategoryTemplate { get; set; }
public DataTemplate SelectionTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
switch (item)
{
case HeaderViewModel _:
return HeaderTemplate;
case SearchViewModel _:
return SearchTemplate; //<==== important
case CategoryFilterViewModel _:
return CategoryTemplate;
case SelectionViewModel _:
return SelectionTemplate;
default:
return ContactTemplate;
}
}
}
So I do have an instance of the SearchViewModel
(the only one of importance to my question), but its never said anywhere that the ViewCell of the search data template actually uses an SearchViewModel. I'm just using it as a condition to my if
statement.
Here is the search cell that is used in the data template (which itself is chosen with the data template selector)
<ViewCell x:Class="MYNAMESPACE.SearchCell">
<AbsoluteLayout>
<Frame>
<StackLayout>
<Entry
Placeholder="{Binding PlaceHolderText}"/>
<Button
Text="{Binding CurrentText}"
Command="{Binding SearchCommand}"/>
</StackLayout>
</Frame>
</AbsoluteLayout>
</ViewCell>
I removed as much as I could without risking obscuring the context. I know this is a wall of code, but I believe it'll be useful if you decide to investigate.
From my understanding, I never provide a Binding Context to my custom ViewCell (the search cell). I do have bindings inside it, notably, my working example is the CurrentText
. I have it as text in my SearchViewModel
public class SearchViewModel : ISearchViewModel
{
public string CurrentText => "<TODO SEARCH>"; //<=== Important
//NotifyPropertyChanged Implementation
}
I have another CurrentText
in ContactsViewModel
, but the text that is displayed at runtime is the one from SearchViewModel
. I'm seeing "" and not "HelloX". This is what I want. I just do not understand how/why the cell uses my viewmodel.
I only use the viewmodel to chose which data template to display, nowhere that viewmodel is being set as the binding context of that data template, nor viewcell. Or am I ? Where is the binding context coming from?