PROWAREtech
Xamarin: ListView
ListView
is designed to display large amounts of data and, as a result, has scrolling
functionality built-in. It allows only single item selection and will automatically highlight the selected
item. The selected item is available through the SelectedItem
property as an object
type. An ItemSelected
event executes when the selected item changes.
<?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:HelloXamarinForms"
x:Class="HelloXamarinForms.MainPageXaml"
BackgroundColor="#2686DC">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
</ContentPage.Padding>
<ListView x:Name="listView" ItemSelected="OnItemSelected" SeparatorVisibility="None" />
</ContentPage>
Changing the usernames
array will not automatically update the screen.
using Xamarin.Forms;
namespace HelloXamarinForms
{
public partial class MainPageXaml : ContentPage
{
public MainPageXaml()
{
InitializeComponent();
string[] usernames = { "Xavier", "Aaron", "David",
"Jill", "Andrew", "John",
"James", "William", "Julie" };
System.Array.Sort(usernames);
listView.ItemsSource = usernames;
}
void OnItemSelected(object sender, System.EventArgs e)
{
if (listView.SelectedItem != null)
{
DisplayAlert("OnItemSelected", listView.SelectedItem.ToString(), "OK");
}
}
}
}
ListView
can just as easily use List
as a source because List
and
Array
both inherit the IEnumerable
interface. Changing the List
data will not automatically update the screen.
The ItemTapped
event is executed when ever the user taps on an item even if it is the item
that is already selected.
<?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:HelloXamarinForms"
x:Class="HelloXamarinForms.MainPageXaml"
BackgroundColor="#2686DC">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
</ContentPage.Padding>
<ListView x:Name="listView" ItemTapped="OnItemTapped" SeparatorVisibility="None" />
</ContentPage>
using System.Collections.Generic;
using Xamarin.Forms;
namespace HelloXamarinForms
{
public partial class MainPageXaml : ContentPage
{
public MainPageXaml()
{
InitializeComponent();
List<string> usernames = new List<string>{ "Xavier", "Aaron", "David",
"Jill", "Andrew", "John",
"James", "William", "Julie" };
usernames.Sort();
listView.ItemsSource = usernames;
}
void OnItemTapped(object sender, System.EventArgs e)
{
if (listView.SelectedItem != null)
DisplayAlert("OnItemTapped", listView.SelectedItem.ToString(), "OK");
}
}
}
Colors Example
Create a file for a class dealing with colors that will be used in the next few examples.
// Colors.cs
using System.Collections.Generic;
using Xamarin.Forms;
namespace ListViewColors
{
public class clsColor
{
Color color;
string name, hex, rgb;
byte a, r, g, b;
public clsColor(Color Color, string Name)
{
this.Color = Color;
this.Name = Name;
a = (byte)(Color.A * 255);
r = (byte)(Color.R * 255);
g = (byte)(Color.G * 255);
b = (byte)(Color.B * 255);
}
// override ToString() so that it returns the data
// of the object instead of the class name
public override string ToString()
{
return Name + " " + Hex + " " + Rgb + " " + Color.ToString();
}
public Color Color
{
get
{
return color;
}
private set
{
color = value;
}
}
public string Name
{
get
{
return name;
}
private set
{
name = value;
}
}
// display color info in hexadecimal format
public string Hex
{
get
{
if (string.IsNullOrEmpty(hex))
{
hex = a.ToString("X2") +
r.ToString("X2") +
g.ToString("X2") +
b.ToString("X2");
}
return hex;
}
}
// display color info in RGB format
public string Rgb
{
get
{
if (string.IsNullOrEmpty(rgb))
{
rgb = r.ToString() +
", " + g.ToString() +
", " + b.ToString();
}
return rgb;
}
}
}
public class clsColors
{
static public List<clsColor> GetColors
{
get
{
return new List<clsColor>
{
new clsColor(Color.Accent, nameof(Color.Accent)),
new clsColor(Color.Aqua, nameof(Color.Aqua)),
new clsColor(Color.Black, nameof(Color.Black)),
new clsColor(Color.Blue, nameof(Color.Blue)),
new clsColor(Color.FromRgb(64f/255,64f/255,64f/255), "Dark Gray"),
new clsColor(Color.Fuchsia, nameof(Color.Fuchsia)),
new clsColor(Color.Gray, nameof(Color.Gray)),
new clsColor(Color.Green, nameof(Color.Green)),
new clsColor(Color.Lime, nameof(Color.Lime)),
new clsColor(Color.Maroon, nameof(Color.Maroon)),
new clsColor(Color.Navy, nameof(Color.Navy)),
new clsColor(Color.Olive, nameof(Color.Olive)),
new clsColor(Color.Pink, nameof(Color.Pink)),
new clsColor(Color.Purple, nameof(Color.Purple)),
new clsColor(Color.Red, nameof(Color.Red)),
new clsColor(Color.Silver, nameof(Color.Silver)),
new clsColor(Color.Teal, nameof(Color.Teal)),
new clsColor(Color.Transparent, nameof(Color.Transparent)),
new clsColor(Color.White, nameof(Color.White)),
new clsColor(Color.Yellow, nameof(Color.Yellow))
};
}
}
}
}
Here is the simpliest example which just uses the overridden ToString()
method to display the data of the
clsColor
object.
<?xml version="1.0" encoding="utf-8" ?>
<!--ListViewColorsPage.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ListViewColors"
x:Class="ListViewColors.ListViewColorsPage"
BackgroundColor="#2686DC">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
</ContentPage.Padding>
<StackLayout>
<ListView ItemsSource="{x:Static local:clsColors.GetColors}" />
</StackLayout>
</ContentPage>
// ListViewColorsPage.xaml.cs
using Xamarin.Forms;
namespace ListViewColors
{
public partial class ListViewColorsPage : ContentPage
{
public ListViewColorsPage()
{
InitializeComponent();
}
}
}
Cells
There are five cells: TextCell
ImageCell
EntryCell
SwitchCell
ViewCell
ListView
displays a list of the same type of cells. TableView
displays
a list of different cell types.
The ViewCell
is a custom cell. If wanting to use a Picker
then use a ViewCell
. The other cells are
self explanatory. Try them all to get a taste for them.
Data Binding
An introduction to data binding is covered in the Xamarin Tutorial .
Still using the clsColor
class, modify the ListViewColorsPage.xaml file to use data binding. This example uses the
ViewCell
which is the most flexible of the cell types. It allows several child elements all bound to properties of
the underlying objects.
Notice that the BoxView
and three Label
are all bound to the properties of clsColor
.
<?xml version="1.0" encoding="utf-8" ?>
<!--ListViewColorsPage.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ListViewColors"
x:Class="ListViewColors.ListViewColorsPage"
BackgroundColor="#2686DC">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
</ContentPage.Padding>
<StackLayout>
<ListView ItemsSource="{x:Static local:clsColors.GetColors}"
BackgroundColor="Transparent"
RowHeight="66">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="3">
<Frame OutlineColor="Transparent"
Padding="5"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
HeightRequest="50"
WidthRequest="50" />
<StackLayout>
<Label Text="{Binding Name}"
TextColor="Black"
FontSize="10"
VerticalOptions="StartAndExpand" />
<Label Text="{Binding Hex, StringFormat='#{0}'}"
TextColor="Black"
FontSize="8"
VerticalOptions="CenterAndExpand" />
<Label Text="{Binding Rgb, StringFormat='RGB: {0}'}"
TextColor="Black"
FontSize="8"
VerticalOptions="EndAndExpand" />
</StackLayout>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
MVVM
An introduction to MVVM is covered in the Xamarin Tutorial. The
ObservableCollection
class, which is covered in the Xamarin tutorial, is required to make this work.
The problem with the previous example is that if a property of the underlying objects changes then the user interface does not reflect it. With MVVM, this is no longer a problem because the properties are linked to the user interface.
Phonebook Example
This example is more "real-world" than the one in the tutorial because
it uses Models for the data, however, a model is not required and often times the interface just binds with the
ViewModel where business rules are usually enforced. It downloads the data as an XML
file over the internet and then deserializes it using XmlSerializer
.
To add a TableView
allowing the editing of the phonebook entries, see
this code.
There are five files listed here:
- The XAML file (the View file).
- The XAML code-behind file.
- The ViewModel file.
- The Model file.
- The file for the App's main entry point.
<?xml version="1.0" encoding="utf-8" ?>
<!--PhonebookListView.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PhonebookMvvm"
x:Class="PhonebookMvvm.PhonebookListView"
BackgroundColor="#2686DC">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
</ContentPage.Padding>
<ContentPage.BindingContext>
<local:PhonebookViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Label TextColor="#333399"
Text="{Binding MainText}"
HorizontalTextAlignment="Center" />
<!--ListView is bound to People, not Phonebook-->
<ListView ItemsSource="{Binding Phonebook.People}"
BackgroundColor="Transparent"
SeperatorColor="Gray">
<ListView.ItemTemplate>
<DataTemplate>
<!--The binding context of ImageCell is Phonebook.People.Person-->
<ImageCell ImageSource="{Binding ImageUrl}"
Text="{Binding Name}"
TextColor="#333399"
Detail="{Binding Phone}"
DetailColor="#336699">
<!--User swipes the item to pull up this context menu-->
<ImageCell.ContextActions>
<!--Command is bound to the properties of the Person class-->
<MenuItem Text="Clear Phone"
Command="{Binding ClearPhoneCommand}" />
<MenuItem Text="Remove"
IsDestructive="True"
Command="{Binding RemoveCommand}" />
</ImageCell.ContextActions>
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
// PhonebookListView.xaml.cs
using Xamarin.Forms;
namespace PhonebookMvvm
{
public partial class PhonebookListView : ContentPage
{
public PhonebookListView()
{
InitializeComponent();
}
}
}
This code uses LINQ and WebRequest/WebResponse, see LINQ Tutorial and WebRequest/WebResponse.
// PhonebookViewModel.cs
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Linq;
namespace PhonebookMvvm
{
class PhonebookViewModel : INotifyPropertyChanged
{
Phonebook phonebook;
string mainText;
public PhonebookViewModel()
{
Phonebook = new Phonebook();
Phonebook.People.Add(new Person());
HttpWebRequest request =
WebRequest.CreateHttp("http://www.prowaretech.com/_phonebook.xml");
request.BeginGetResponse((arg) =>
{
try
{
using (WebResponse response = request.EndGetResponse(arg))
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(stream))
{
XmlSerializer xml = new XmlSerializer(typeof(Phonebook));
Phonebook = xml.Deserialize(reader) as Phonebook;
// use Linq to sort People (Person collection)
var query = from pb in Phonebook.People
orderby pb.LastName, pb.FirstName
select pb;
ObservableCollection<Person> people =
new ObservableCollection<Person>(query);
Phonebook.People.Clear();
Phonebook.People = people;
// set the Phonebook property in each Person
foreach (Person person in Phonebook.People)
{
person.Phonebook = Phonebook;
}
}
}
}
}
catch (System.Exception ex)
{
MainText = ex.Message;
}
}, null);
}
public string MainText
{
get
{
return mainText;
}
set
{
if (value == mainText)
return;
mainText = value;
OnPropertyChanged(nameof(MainText));
}
}
public Phonebook Phonebook
{
get
{
return phonebook;
}
private set
{
if (value == phonebook)
return;
phonebook = value;
OnPropertyChanged(nameof(Phonebook));
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
// PhonebookModel.cs
using System.ComponentModel;
using System.Xml.Serialization;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace PhonebookMvvm
{
public class Phonebook : INotifyPropertyChanged
{
ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get
{
return people;
}
set
{
if (value == people)
return;
people = value;
OnPropertyChanged(nameof(People));
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class Person : INotifyPropertyChanged
{
string lastName, firstName, phone, imageUrl;
public Person()
{
ClearPhoneCommand = new Xamarin.Forms.Command(() => this.Phone = "");
RemoveCommand = new Xamarin.Forms.Command(() => Phonebook.People.Remove(this));
}
public string ImageUrl
{
get
{
return imageUrl;
}
set
{
if (value == imageUrl)
return;
imageUrl = value;
OnPropertyChanged(nameof(ImageUrl));
}
}
[XmlIgnore]
public string Name
{
get
{
System.Text.StringBuilder name = new System.Text.StringBuilder(100);
if(string.IsNullOrEmpty(lastName) && string.IsNullOrEmpty(firstName))
{
name.Append("UNDEFINED");
}
else if(string.IsNullOrEmpty(lastName))
{
name.Append(firstName);
}
else if (string.IsNullOrEmpty(firstName))
{
name.Append(lastName);
}
else
{
name.Append(lastName);
name.Append(", ");
name.Append(firstName);
}
return name.ToString();
}
}
public string LastName
{
get
{
return lastName;
}
set
{
if (value == lastName)
return;
lastName = value;
OnPropertyChanged(nameof(LastName));
OnPropertyChanged(nameof(Name));
}
}
public string FirstName
{
get
{
return firstName;
}
set
{
if (value == firstName)
return;
firstName = value;
OnPropertyChanged(nameof(FirstName));
OnPropertyChanged(nameof(Name));
}
}
public string Phone
{
get
{
return phone;
}
set
{
if (value == phone)
return;
phone = value;
OnPropertyChanged(nameof(Phone));
}
}
[XmlIgnore]
public ICommand ClearPhoneCommand { private set; get; }
[XmlIgnore]
public ICommand RemoveCommand { private set; get; }
[XmlIgnore]
public Phonebook Phonebook { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
// App.cs
using Xamarin.Forms;
namespace PhonebookMvvm
{
public class App : Application
{
public App()
{
MainPage = new PhonebookListView();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}