PROWAREtech
Xamarin: 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):
- The
ListView
XAML file (a View file). - The
ListView
code-behind file. - The
TableView
XAML file (a View file). - The
TableView
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"
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
}
}
}