Xamarin forms (Cross-Platform) : Multiple type of cells in ListView - xaml

I am new to Xamarin. I have a requirement where I have to implement a ListView or say tableView that have multiple different type-size cells.
And I also have to add Header for a particular section of cells, and some of my custom cells have a horizontal scroll in it.
I have done this thing in iOS native UITableView before, but don't know how this done in Xamarin cross platform, can anyone help me out this?

You are looking for DataTemplateSelector, which is very well documented in the official Xamarin.Forms documentation.
The basics are that you create your own DataTemplateSelector class:
public class MyDataTemplateSelector : DataTemplateSelector
{
}
In that class you override OnSelectTemplate:
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
}
By checking the type of the item argument, you should be able to figure out which template to return.
So lets say you have a ViewModel for Dog and one for Cat and want to show a different DataTemplate for each of those. You would do something like:
public class DogCatTemplateSelector : DataTemplateSelector
{
public DataTemplate DogTemplate { get; set; }
public DataTemplate CatTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is DogViewModel)
return DogTemplate;
return CatTemplate;
}
}
Then you can consume this in your XAML:
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="dogTemplate">
<ViewCell>
... <---- define your look of dog template here
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="catTemplate">
<ViewCell>
... <---- define your look of cat template here
</ViewCell>
</DataTemplate>
<local:DogCatTemplateSelector x:Key="dogCatTemplateSelector"
DogTemplate="{StaticResource dogTemplate}"
CatTemplate="{StaticResource catTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
Then simply set the ItemTemplate to your dogCatTemplateSelector instance you've defined in the resources on your ListView:
<ListView ItemsSource="{Binding DogsCatsCollection}" ItemTemplate="{StaticResource dogCatTemplateSelector}" />
Your ViewModel would then look something like:
public class Animal : INotifyPropertyChanged
{
}
public class DogViewModel : Animal
{
}
public class CatViewModel : Animal
{
}
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Animal> DogsCatsCollection { get; }
= new ObservableCollection<Animal>();
}
Then you just populate DogsCatsCollection with instances of dogs and cats.

Related

UWP XAML Intellisense DataTemplate.DataType

Why does intellisense filter out interfaces and abstract classes? If I set DataType to an abstract class, it seems to still work fine. Perhaps this is just a bug? Also, related, inside DataTemplate, when I try to {x:Bind} it filters out inherited properties, so if I have Item : Base, and Base has a property Name, and DataType="Item", it filters out property Name and if I use it anyway, it seems to resolve to the class name. Did I miss something in the docs? Should I be making special non-abstract wrapping classes for every type I want to bind to xaml controls?
After my testing, it seems that inherited interface-properties are not recognized by the compiler when using the X:Bind. But it applies to abstract classes.
You could follow the sample to check your steps.
XAML code:
<ListView x:Name="List" ItemsSource="{x:Bind Fruits}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Fruit">
<TextBlock Text="{x:Bind price}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code behind:
public sealed partial class MainPage : Page
{
public ObservableCollection<Fruit> Fruits{get;set;}
public MainPage()
{
this.InitializeComponent();
Fruits = new ObservableCollection<Fruit>()
{
new Fruit(){name="apple",price=12},
new Fruit(){name="peach",price=15},
new Fruit(){name="pear",price=8},
new Fruit(){name="banana",price=31},
new Fruit(){name="grape",price=5}
};
}
}
public class Fruit: IFruit
{
public string name { get; set;}
}
public abstract class IFruit
{
public int price { get; set;}
}

Databind properties of a class in ContentView

I'm trying to achieve what I think is probably quite simple but as I'm new to Xamarin & Databinding I think I'm getting in a spin.
I have a very simple ContentPage that just has a Databinding to my viewModel for this page and my ContentView, TotalsTemplate.
<ContentPage.BindingContext>
<vm:DealsTodayViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<template:TotalsTemplate></template:TotalsTemplate>
</StackLayout>
</ContentPage.Content>
My viewmodel has a public property of my class, Totals, which has basic int,string,decimal props.
public class DealsTodayViewModel
{
Public string ViewModelPeriod;
public PeriodTotals Totals;
public DealsTodayViewModel()
{
ViewModelPeriod = "TODAY";
Totals = new PeriodTotals
{
Period = "DAILY",
ClientServices_Deals_Chicago = 1,
ClientServices_Deals_Manchester_Na = 1,
ClientServices_Deals_Manchester_Uk = 1,
ClientServices_Ramp_Chicago = 1.2m,
ClientServices_Ramp_Manchester_Na = 1.3m,
ClientServices_Ramp_Manchester_Uk = 1.4m
};
}
}
Now in my TotalsTemplte ContentView I have a Grid with following inside.
<Label Text="{***Binding ViewModelPeriod***}" FontAttributes="Bold"/>
<Frame OutlineColor="Black" Grid.Row="0" Grid.Column="1">
<Label Text="{Binding ***Totals.Period***}" FontAttributes="Bold"/>
</Frame>
My String property on the DealsTodayViewModel is visible in my ContentView but not the Perod property from inside my Totals property, am I binding incorrectly to this?
From the document, data-binding should binding between properties instead of field:
Data binding is the technique of linking properties of two objects so
that changes in one property are automatically reflected in the other
property. Data binding is an integral part of the Model-View-ViewModel
(MVVM) application architecture.
So the solution is change the fields in your vm to properties:
public class DealsTodayViewModel
{
public string ViewModelPeriod { get; set; }
public PeriodTotals Totals { get; set; }
public DealsTodayViewModel()
{
...
}
}
Refer: field and property

Stacklayout backgroundColor binding with MVVM

I'm attempting to get my head around MVVM with XamarinForms and I'm slightly confused with regards to proper partitioning of functionality:
I have a main page, MainPage.xaml, which includes a stacklayout:
<StackLayout x:Name="MainPageStackLayout">
...
</StackLayout>
Within this stacklayout I have Picker which is bound as follows:
<Picker Title="Select a background colour"
TitleColor="Black"
TextColor="Black"
ItemsSource="{Binding MyColours}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding selectedBackGroundColour}" SelectedIndexChanged="BackGroundColourPicker_SelectedIndexChanged"/>
Following the article from microsoft (https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/userinterface-monkeyapppicker/):
I have a "View" which basically defines the layout of my page.
A "ViewModel" which holds an IList "MyColours" and a variable "SelectedBackGroundColour".
A "Model" which defines the MyColour class. A MyColour has a string name and a Xamarin.Forms.Color (from a hex value, both populated on start up).
This all works fine. I can start up the app and the Picker populates with the colours I add to "MyColours". If I change the index then my SelectedBackGroundColour also updates, has the correct name and a different RGB value.
However, I'm lost as to where I would tie in the updating of the actual background colour of the MainPageStackLayout. The View (MainPage.xaml.cs) picks up the "BackGroundColourPicker_SelectedIndexChanged" event but what is the standard practice for reading from the view model (where SelectedBackGround colour is actual defined ?)
I have a feeling I can bind Background colour in the MainPageStackLayout xaml view so I wont have to catch the selected index change event.
Thanks all.
According to your description, I guess that you want to change MainPage StackLayout BackGround color by Picker value, am I right?
If yes, please follow the steps below.
Firstly, please confirm that you implement INotifyPropertyChanged interface to notify SelectedBackGroundColour changed.
Then there are full code, please take a look:
<StackLayout x:Name="MainPageStacklayout" BackgroundColor="{Binding selectedBackGroundColour.color}">
<Picker
x:Name="picker1"
Title="Select a background colour"
ItemDisplayBinding="{Binding name}"
ItemsSource="{Binding MyColours}"
SelectedItem="{Binding selectedBackGroundColour}"
TextColor="Black"
TitleColor="Black" />
</StackLayout>
public partial class Page5 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<MyColour> MyColours { get; set; }
private MyColour _selectedBackGroundColour;
public MyColour selectedBackGroundColour
{
get { return _selectedBackGroundColour; }
set
{
_selectedBackGroundColour = value;
RaisePropertyChanged("selectedBackGroundColour");
}
}
public Page5()
{
InitializeComponent();
MyColours = new ObservableCollection<MyColour>()
{
new MyColour(){name="red",color=Color.Red},
new MyColour(){name="gray",color=Color.Gray},
new MyColour(){name="BlueViolet",color=Color.BlueViolet}
};
selectedBackGroundColour = MyColours[0];
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyColour
{
public string name { get; set; }
public Color color { get; set; }
}
The screenshot:

Xamarin.Forms (XAML): Different layouts depending on a condition

Is there a way to choose what layout initialize depending on one condition? I have a Grid for football stats but if myViewModel.Sport == Sports.Basketball I'd like to load a completely different layout.
I tried something like this with Datatrigger in each View but it seems a mess for me:
<Label Text="{Binding Goals}"
Grid.Row="1" Grid.Column="0">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Sport}"
Value="1">
<Setter Property="Text"
Value="{Binding Points}"/>
</DataTrigger>
</Label.Triggers>
</Label>
I show "goals" but if the Sports enum value is 1 (Sports.Basketball) I change to "points". I want to do this with lots of Labels and even Images so I need a proper way to do it.
Could someone help me? I need to load a different Grid depending on the Sport Property of my ViewModel.
Another thing you could do is place each separate sport into it's own view, add all the views to your page and set their IsVisible property depending on which sport you want to show.
An example would look like this in pseudo-code:
<Page>
<Grid>
<BasketballView IsVisible="{Binding IsBasketball}">
<SoccerView IsVisible="{Binding IsSoccer}">
<FootballView IsVisible="{Binding IsFootball}">
</Grid>
</Page>
Then set the appropriate boolean values from the ViewModel.
To use DataTemplateSelector to solve this, as mentioned by #StephaneDelcroix, you'll want a custom class that has ItemsSource and ItemTemplate properties.
I haven't thought through / tested how DataTemplateSelector would be used with this; anyone is welcome to add that to this answer.
using System.Collections;
using Xamarin.Forms;
namespace YourNamespace
{
// From https://forums.xamarin.com/discussion/19874/listview-inside-stacklayout-a-height-problem/p2, #maxx313.
public class TemplatedStack : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IList), typeof(TemplatedStack), propertyChanged: OnItemsSourceChanged);
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemTemplate != null)
{
layout.BuildLayout();
layout.ForceLayout();
}
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(TemplatedStack), propertyChanged: OnItemTemplateChanged);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void OnItemTemplateChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemsSource != null)
layout.BuildLayout();
}
private void BuildLayout()
{
Children.Clear();
foreach (var item in ItemsSource)
{
var view = (View)ItemTemplate.CreateContent();
view.BindingContext = item;
Children.Add(view);
}
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
}
In your XAML, do
<yourXmlns:TemplatedStack .../>
where yourXmlns must be an xmlns declaration at top of your XAML.
Usage of ItemsSource and ItemTemplate properties is similar to how you would bind an items collection and template to a ListView.
(The reason NOT to use a ListView here, is that ListView may interfere with touch events, and adds extra layout cost.)
Bind to this a collection containing a single item.
E.g. for this question, that item would be the specific sport being viewed.

Changing part of view at runtime

I show several movie items in an ObservableCollection using a typical listbox+datatemplate view.
However, I want, in the same page, to be able to quickly change the view to what I define a posterview (i.e. only the posterimages in a wrappanel).
The xaml-page uses a viewmodel as datacontext.
Is there a way to basically replace part of the XAML content with another?
And still keep as little code as possible in the codebehind of the view.
I've seen WPF examples that for example use a DataTrigger bound to a viewmodelproperty which is very clean,
such as this article
... but Windows Phone does not have a DataTriggers, correct?
I'm trying to go for an MVVM-ish approach, so as little code as possible in the view code-behind is required.
So I want to change this:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource ListView}" />
To:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource PosterView}" />
DataTemplates with a DataTemplateSelector would be the way to go around this problem.
Base Data Template Selector:
public class DataTemplateSelector : ContentControl
{
public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
{
throw new NotImplementedException();
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ContentTemplate = SelectTemplate(newContent, this);
}
}
Specialized Template Selector for your CinemaShowsOverview
public class CinemaShowsTemplateSelector : DataTemplateSelector
{
public DataTemplate ListTemplate
{
get;
set;
}
public DataTemplate PosterTemplate
{
get;
set;
}
public DataTemplate DefaultTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return DefaultTemplate;
var viewModel = item as CinemaShowsOverview;
if (viewModel != null)
return viewModel.IsPoster ? PowerTemplate : ListTemplate;
else
return DefaultTemplate;
}
}
And then in XAML (replacing your current ContentControl):
<assets:CinemaShowsTemplateSelector PosterTemplate="{StaticResource PosterView}"
ListTemplate="{StaticResource ListView}"
Content="{Binding CinemaShowsOverview}">
Just to be pedantic, the blog you mention describes typed data-templates, not datatriggers (as the author class them). No, this feature is not available in Silverlight for WP7.
You could expose the template you requires as a string within your view model, i.e. a string that is either ListView or PosterView. You then bind your Template property to this view-model property via a value converter that provides the template, which it can access via your applications Resources.