TableView

TableView is designed to put data in a form or to display a menu. TableView is really designed to work with MVVM and ListView.

Cells

Like ListView, there are five cells: TextCell ImageCell EntryCell SwitchCell ViewCell

TableView displays a list of different cell types. ListView displays a list of the same type of cells.

The ViewCell is a custom cell. If wanting to use a Picker, like in the example below, then use a ViewCell. The other cells are self explanatory.

Example Code

<?xml version="1.0" encoding="utf-8" ?>
<!--TableViewPage.xaml-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:local="clr-namespace:ExampleTableView"
			 x:Class="ExampleTableView.TableViewPage">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
	</ContentPage.Padding>

	<StackLayout>
		<TableView Intent="Form" x:Name="tableView">

			<TableView.BindingContext>
				<local:AboutMe />
			</TableView.BindingContext>

			<TableRoot Title="Data Form">
				<TableSection Title="About Me">
					<EntryCell Label="Name:"
								 Text="{Binding Name}"
								 Placeholder="Enter name"
								 Keyboard="Text" />
					<EntryCell Label="Email:"
								 Text="{Binding Email}"
								 Placeholder="Enter email address"
								 Keyboard="Email" />
					<EntryCell Label="Phone:"
								 Text="{Binding Phone}"
								 Placeholder="Enter phone number"
								 Keyboard="Telephone" />
					<EntryCell Label="Age:"
								 Text="{Binding Age}"
								 Placeholder="Enter age"
								 Keyboard="Numeric" />
					<ViewCell>
						<ViewCell.View>
							<StackLayout Orientation="Horizontal"
										 Padding="16, 0">
								<Label Text="Language:"
										 VerticalOptions="Center" />
								<Picker Title="Choose"
										x:Name="languagePicker"
										SelectedIndex="{Binding LanguageIndex}" />
							</StackLayout>
						</ViewCell.View>
					</ViewCell>
					<SwitchCell Text="Business owner?"
								On="{Binding IsBusinessOwner}" />
					<ViewCell>
						<ViewCell.View>
							<StackLayout Orientation="Horizontal"
										 Padding="16, 0">
								<Label Text="Type:"
										 VerticalOptions="Center" />
								<Picker Title="Choose"
										x:Name="businessTypePicker"
										SelectedIndex="{Binding BusinessTypeIndex}"
										IsEnabled="{Binding IsBusinessOwner}" />
							</StackLayout>
						</ViewCell.View>
					</ViewCell>
					<ViewCell>
						<ViewCell.View>
							<StackLayout Padding="16, 0">
								<Label Text="{Binding BusinessType}"
										 HorizontalOptions="Center" />
							</StackLayout>
						</ViewCell.View>
					</ViewCell>
				</TableSection>
			</TableRoot>
		</TableView>
		<Button Text="Submit"
				HorizontalOptions="Center"
				Clicked="OnSubmit" />
	</StackLayout>

</ContentPage>
// TableViewPage.xaml.cs
using Xamarin.Forms;

namespace ExampleTableView
{
	public partial class TableViewPage : ContentPage
	{
		public TableViewPage()
		{
			InitializeComponent();

			foreach(string s in AboutMe.BusinessTypes)
			{
				businessTypePicker.Items.Add(s);
			}
			foreach (string s in AboutMe.Languages)
			{
				languagePicker.Items.Add(s);
			}
		}

		void OnSubmit(object sender, System.EventArgs e)
		{
			AboutMe aboutMe = (AboutMe)tableView.BindingContext;
			DisplayAlert("Name", aboutMe.Name, "OK");
			DisplayAlert("BusinessOwner?", aboutMe.IsBusinessOwner.ToString(), "OK");
			DisplayAlert("BusinessType", aboutMe.BusinessType, "OK");
		}
	}
}
// AboutMeViewModel.cs
using System.Collections;
using System.ComponentModel;

namespace ExampleTableView
{
	class AboutMe : INotifyPropertyChanged
	{
		private string age;
		public string Age
		{
			get
			{
				return age;
			}
			set
			{
				if (value == age)
					return;
				age = value;
				OnPropertyChanged(nameof(Age));
			}
		}

		private string name;
		public string Name
		{
			get
			{
				return name;
			}
			set
			{
				if (value == name)
					return;
				name = value;
				OnPropertyChanged(nameof(Name));
			}
		}

		private string email;
		public string Email
		{
			get
			{
				return email;
			}
			set
			{
				if (value == email)
					return;
				email = value;
				OnPropertyChanged(nameof(Email));
			}
		}

		private string phone;
		public string Phone
		{
			get
			{
				return phone;
			}
			set
			{
				if (value == phone)
					return;
				phone = value;
				OnPropertyChanged(nameof(Phone));
			}
		}

		private static string[] languages = { "English", "Spanish", "French", "Chinese", "German", "Italian", "Other" };
		public static string[] Languages
		{
			get { return languages; }
		}

		public string Language { private set; get; }

		private int languageIndex = -1;
		public int LanguageIndex
		{
			get
			{
				return languageIndex;
			}
			set
			{
				if (value == languageIndex)
					return;
				languageIndex = value;
				OnPropertyChanged(nameof(LanguageIndex));
				if (languageIndex < languages.Length && languageIndex >= 0)
				{
					Language = languages[languageIndex];
					OnPropertyChanged(nameof(Language));
				}
			}
		}

		private bool isBusinessOwner;
		public bool IsBusinessOwner
		{
			get
			{
				return isBusinessOwner;
			}
			set
			{
				if (value == isBusinessOwner)
					return;
				isBusinessOwner = value;
				OnPropertyChanged(nameof(IsBusinessOwner));
			}
		}

		private static string[] businessTypes = { "Services", "Technology", "Finance", "Other" };
		public static string[] BusinessTypes
		{
			get { return businessTypes; }
		}

		public string BusinessType { private set; get; }

		private int businessTypeIndex = -1;
		public int BusinessTypeIndex
		{
			get
			{
				return businessTypeIndex;
			}
			set
			{
				if (value == businessTypeIndex)
					return;
				businessTypeIndex = value;
				OnPropertyChanged(nameof(BusinessTypeIndex));
				if(businessTypeIndex < businessTypes.Length && businessTypeIndex >= 0)
				{
					BusinessType = businessTypes[businessTypeIndex];
					OnPropertyChanged(nameof(BusinessType));
				}
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}
}

Phonebook Example

This small phonebook app is a continuation of this code on the ListView article. It implements several of the techonologies on this site.

There are seven files listed here (download zip):

  1. The ListView XAML file (a View file).
  2. The ListView code-behind file.
  3. The TableView XAML file (a View file).
  4. The TableView code-behind file.
  5. The ViewModel file.
  6. The Model file.
  7. 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"
			 x:Name="listViewPage">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="10, 20, 10, 0" Android="10, 0" />
	</ContentPage.Padding>

	<StackLayout>
		<Label TextColor="#333399"
				 Text="{Binding MainText}"
				 HorizontalTextAlignment="Center" />
		<!--ListView is bound to People, not just Phonebook-->
		<ListView ItemsSource="{Binding Phonebook.People}"
					BackgroundColor="Transparent">
			<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="Edit"
										Command="{Binding EditCommand}"
										CommandParameter="{x:Reference Name=listViewPage}" />
										<!--Must pass the Page as a parameter so that the
											EditCommand has access to the Navigation object-->
							<MenuItem Text="Remove"
										IsDestructive="True"
										Command="{Binding RemoveCommand}"
										CommandParameter="{x:Reference Name=listViewPage}" />
										<!--Must pass the Page as a parameter so that the
											RemoveCommand has access to PhonebookViewModel-->
						</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();
		}
	}
}
<?xml version="1.0" encoding="utf-8" ?>
<!--PhonebookTableView.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.PhonebookTableView"
			 BackgroundColor="#2686DC">

	<ContentPage.Padding>
		<OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
	</ContentPage.Padding>

	<StackLayout BackgroundColor="#2686DC">
		<TableView Intent="Form" BackgroundColor="Transparent">
			<TableRoot Title="Data Form">
				<TableSection Title="Edit Phonebook Entry">
					<TextCell Text="{Binding Name}"
								TextColor="#333399" />
					<EntryCell Label="Last Name:"
								 Text="{Binding LastName}"
								 Placeholder="Enter last name"
								 Keyboard="Text" />
					<EntryCell Label="First Name:"
								 Text="{Binding FirstName}"
								 Placeholder="Enter first name"
								 Keyboard="Text" />
					<EntryCell Label="Phone:"
								 Text="{Binding Phone}"
								 Placeholder="Enter phone number"
								 Keyboard="Telephone" />
					<EntryCell Label="Image URL:"
								 Text="{Binding ImageUrl}"
								 Placeholder="Enter URL"
								 Keyboard="Url" />
					<ViewCell>
						<Image Source="{Binding ImageUrl}"
								 Aspect="AspectFit" />
					</ViewCell>
				</TableSection>
			</TableRoot>
		</TableView>
		<Button Text="Back"
				TextColor="White"
				BackgroundColor="Navy"
				HorizontalOptions="Fill"
				Clicked="OnBackClick" />
	</StackLayout>

</ContentPage>
// PhonebookTableView.xaml.cs
using Xamarin.Forms;

namespace PhonebookMvvm
{
	public partial class PhonebookTableView : ContentPage
	{
		public PhonebookTableView()
		{
			InitializeComponent();
		}

		async void OnBackClick(object sender, System.EventArgs e)
		{
			await Navigation.PopModalAsync();
		}
	}
}

This code was modified to not set the Phonebook property of the Person class because it has been removed. Also, a serializer/deserializer has been added for saving the application state and a method that downloads the phonebook from the Internet has been added. This code uses LINQ, see LINQ Tutorial.

// PhonebookViewModel.cs
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Xml.Serialization;

namespace PhonebookMvvm
{
	public class PhonebookViewModel : INotifyPropertyChanged
	{
		Phonebook phonebook = new Phonebook();
		string mainText;

		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 void DownloadPhonebook()
		{
			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;

								Phonebook.SortPeople();
							}
						}
					}
				}
				catch (System.Exception ex)
				{
					MainText = ex.Message;
				}
			}, null);
		}

		public string Serialize()
		{
			XmlSerializer xmlSerial = new XmlSerializer(typeof(PhonebookViewModel));
			using (StringWriter strWriter = new StringWriter())
			{
				xmlSerial.Serialize(strWriter, this);
				return strWriter.GetStringBuilder().ToString();
			}
		}

		public static PhonebookViewModel Deserialize(string xmlString)
		{
			XmlSerializer xmlSerial = new XmlSerializer(typeof(PhonebookViewModel));
			using (StringReader strReader = new StringReader(xmlString))
			{
				return (PhonebookViewModel)xmlSerial.Deserialize(strReader);
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}
}

The Person class no longer has a Phonebook property as it did in the ListView article. A SortPeople() method was added to Phonebook.

// PhonebookModel.cs
using System.ComponentModel;
using System.Xml.Serialization;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Linq;

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 void SortPeople()
		{
			if(People.Count > 1)
			{
				// use Linq to sort People (Person collection)
				var query = from pb in People
							orderby pb.Name
							select pb;

				ObservableCollection<Person> p =
					new ObservableCollection<Person>(query);

				People.Clear();
				People = p;
			}
		}

		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()
		{
			EditCommand = new Xamarin.Forms.Command(async (page) =>
			{
				await ((PhonebookListView)page).Navigation.PushModalAsync(new PhonebookTableView() { BindingContext = this });
			});
			RemoveCommand = new Xamarin.Forms.Command((page) =>
			{
				((PhonebookViewModel)((PhonebookListView)page).BindingContext).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 (lastName == null && firstName == null)
				{
					name.Append("UNDEFINED");
				}
				else if (lastName == null)
				{
					name.Append(firstName);
				}
				else if (firstName == null)
				{
					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 EditCommand { private set; get; }

		[XmlIgnore]
		public ICommand RemoveCommand { private set; get; }

		public event PropertyChangedEventHandler PropertyChanged;
		void OnPropertyChanged(string propName)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
		}
	}
}

Added code to save the application state. When the phonebook is empty then it will download the one on the Internet.

// App.cs
using Xamarin.Forms;

namespace PhonebookMvvm
{
	public class App : Application
	{
		public App()
		{
			PhonebookViewModel viewModel;

			if (Properties.ContainsKey(nameof(PhonebookViewModel)))
			{
				viewModel = PhonebookViewModel.Deserialize((string)Properties[nameof(PhonebookViewModel)]);
			}
			else
			{
				viewModel = new PhonebookViewModel();
			}
			if (viewModel.Phonebook.People.Count == 0)
			{
				viewModel.DownloadPhonebook();
			}
			MainPage = new PhonebookListView()
			{
				BindingContext = viewModel
			};
		}

		protected override void OnStart()
		{
			// Handle when your app starts
		}

		protected override void OnSleep()
		{
			// Handle when your app sleeps
			PhonebookViewModel viewModel = (PhonebookViewModel)((PhonebookListView)MainPage).BindingContext;
			viewModel.Phonebook.SortPeople();
			Properties[nameof(PhonebookViewModel)] = viewModel.Serialize();
		}

		protected override void OnResume()
		{
			// Handle when your app resumes
		}
	}
}