The FlexGrid control allows you to create a hierarchical grid by adding a row details section to each row. Adding a row details sections allows you to group some data in a collapsible template and present only a summary of the data for each row. The row details section is displayed only when the user taps a row. Moreover, you can set the details visibility mode to expand single, expand multiple or selection, with the help of DetailVisibiltyMode property provided by the FlexGrid class.
The image given below shows a FlexGrid with row details section added to each row.
The following code example demonstrates how to add row details section to FlexGrid control in C# and XAML. This example uses a new data source class, Customer.cs.
C# |
Copy Code
|
---|---|
public class Customer : INotifyPropertyChanged, IEditableObject { #region ** fields int _id, _countryId, _orderCount; string _first, _last; string _address, _city, _postalCode, _email; bool _active; DateTime _lastOrderDate; double _orderTotal; static Random _rnd = new Random(); static string[] _firstNames = "Andy|Ben|Paul|Herb|Ed|Ted|Zeb".Split('|'); static string[] _lastNames "Ambers|Danson|Evers|Frommer|Griswold|Orsted|Stevens".Split('|'); static KeyValuePair<string, string[]>[] _countries = "China-Beijing,Chongqing,Chaohu|India-New Delhi,Mumbai,Delhi,Chennai,Kolkata|United States-Washington,New York,Los Angeles|Indonesia-Jakarta,South Tangerang|Brazil-Brasilia,Sao Paulo,Rio de Janeiro,|Pakistan-Islamabad,Karachi,Lahore,Quetta|Russia-Moscow,Saint Petersburg,Novosibirsk,Rostov-na-Donu|Japan-Tokyo,Yokohama,Ōsaka,Saitama|Mexico-Mexico City,Guadalajara,Monterrey".Split('|').Select(str => new KeyValuePair<string, string[]>(str.Split('-').First(), str.Split('-').Skip(1).First().Split(','))).ToArray(); static string[] _emailServers = "gmail|yahoo|outlook|aol".Split('|'); static string[] _streetNames = "Main|Broad|Grand|Panoramic|Green|Golden|Park|Fake".Split('|'); static string[] _streetTypes = "ST|AVE|BLVD".Split('|'); static string[] _streetOrientation = "S|N|W|E|SE|SW|NE|NW".Split('|'); #endregion #region ** initialization public Customer() : this(_rnd.Next(10000)) { } public Customer(int id) { Id = id; FirstName = GetRandomString(_firstNames); LastName = GetRandomString(_lastNames); Address = GetRandomAddress(); CountryId = _rnd.Next() % _countries.Length; var cities = _countries[CountryId].Value; City = GetRandomString(cities); PostalCode = _rnd.Next(10000, 99999).ToString(); Email = string.Format({0}@{1}.com, (FirstName + LastName.Substring(0, 1)).ToLower(), GetRandomString(_emailServers)); LastOrderDate = DateTime.Today.AddDays(-_rnd.Next(1, 365)).AddHours(_rnd.Next(0, 24)).AddMinutes(_rnd.Next(0, 60)); OrderCount = _rnd.Next(0, 100); OrderTotal = Math.Round(_rnd.NextDouble() * 10000.00, 2); Active = _rnd.NextDouble() >= .5; } #endregion #region ** object model public int Id { get { return _id; } set { if (value != _id) { _id = value; OnPropertyChanged("Id"); } } } public string FirstName { get { return _first; } set { if (value != _first) { _first = value; OnPropertyChanged("FirstName"); OnPropertyChanged("Name"); } } } public string LastName { get { return _last; } set { if (value != _last) { _last = value; OnPropertyChanged("LastName"); OnPropertyChanged("Name"); } } } public string Address { get { return _address; } set { if (value != _address) { _address = value; OnPropertyChanged("Address"); } } } public string City { get { return _city; } set { if (value != _city) { _city = value; OnPropertyChanged("City"); } } } public int CountryId { get { return _countryId; } set { if (value != _countryId && value > -1 && value < _countries.Length) { _countryId = value; //_city = _countries[_countryId].Value.First(); OnPropertyChanged("CountryId"); OnPropertyChanged("Country"); OnPropertyChanged("City"); } } } public string PostalCode { get { return _postalCode; } set { if (value != _postalCode) { _postalCode = value; OnPropertyChanged("PostalCode"); } } } public string Email { get { return _email; } set { if (value != _email) { _email = value; OnPropertyChanged("Email"); } } } public DateTime LastOrderDate { get { return _lastOrderDate; } set { if (value != _lastOrderDate) { _lastOrderDate = value; OnPropertyChanged("LastOrderDate"); } } } public int OrderCount { get { return _orderCount; } set { if (value != _orderCount) { _orderCount = value; OnPropertyChanged("OrderCount"); } } } public double OrderTotal { get { return _orderTotal; } set { if (value != _orderTotal) { _orderTotal = value; OnPropertyChanged("OrderTotal"); } } } public bool Active { get { return _active; } set { if (value != _active) { _active = value; OnPropertyChanged("Active"); } } } public string Name { get { return string.Format("{0} {1}", FirstName, LastName); } } public string Country { get { return _countries[_countryId].Key; } } public double OrderAverage { get { return OrderTotal / (double)OrderCount; } } #endregion #region ** implementation // ** utilities static string GetRandomString(string[] arr) { return arr[_rnd.Next(arr.Length)]; } static string GetName() { return string.Format("{0} {1}", GetRandomString(_firstNames), GetRandomString(_lastNames)); } // ** static list provider public static ObservableCollection<Customer> GetCustomerList(int count) { var list = new ObservableCollection<Customer>(); for (int i = 0; i < count; i++) { list.Add(new Customer(i)); } return list; } private static string GetRandomAddress() { if (_rnd.NextDouble() > 0.9) return string.Format("{0} {1} {2} {3}", _rnd.Next(1, 999), GetRandomString(_streetNames), GetRandomString(_streetTypes), GetRandomString(_streetOrientation)); else return string.Format("{0} {1} {2}", _rnd.Next(1, 999), GetRandomString(_streetNames), GetRandomString(_streetTypes)); } // ** static value providers public static KeyValuePair<int, string>[] GetCountries() { return _countries.Select((p, index) => new KeyValuePair<int, string>(index, p.Key)).ToArray(); } public static string[] GetFirstNames() { return _firstNames; } public static string[] GetLastNames() { return _lastNames; } #endregion #region ** INotifyPropertyChanged Members // interface allows bounds controls to react to changes in data objects. public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } protected void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) PropertyChanged(this, e); } #endregion #region IEditableObject Members // interface allows transacted edits Customer _clone; public void BeginEdit() { _clone = (Customer)this.MemberwiseClone(); } public void EndEdit() { _clone = null; } public void CancelEdit() { if (_clone != null) { foreach (var p in this.GetType().GetRuntimeProperties()) { if (p.CanRead && p.CanWrite) { p.SetValue(this, p.GetValue(_clone, null), null); } } } } #endregion } |
XAML |
Copy Code
|
---|---|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:c1="clr-namespace:C1.Xamarin.Forms.Grid;assembly=C1.Xamarin.Forms.Grid" x:Class="FlexGridRowDetails.RowDetails" x:Name="page"> <Grid RowSpacing="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition /> </Grid.RowDefinitions> <c1:FlexGrid x:Name="grid" Grid.Row="3" AutoGenerateColumns="False"> <c1:FlexGrid.Columns> <c1:GridColumn Binding="Id" Width="Auto"/> <c1:GridColumn Binding="FirstName" Width="*"/> <c1:GridColumn Binding="LastName" Width="*"/> </c1:FlexGrid.Columns> <c1:FlexGrid.Behaviors> <c1:FlexGridDetailProvider x:Name="details" Height="170"> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Label Text="Country:"/> <Label Text="{Binding Country}" Grid.Column="1"/> <Label Text="City:" Grid.Row="1"/> <Label Text="{Binding City}" Grid.Row="1" Grid.Column="1"/> <Label Text="Address:" Grid.Row="2"/> <Label Text="{Binding Address}" Grid.Row="2" Grid.Column="1"/> <Label Text="PostalCode:" Grid.Row="3"/> <Label Text="{Binding PostalCode}" Grid.Row="3" Grid.Column="1"/> </Grid> </DataTemplate> </c1:FlexGridDetailProvider> </c1:FlexGrid.Behaviors> </c1:FlexGrid> </Grid> </ContentPage> |
C# |
Copy Code
|
---|---|
public partial class RowDetails : ContentPage { public RowDetails() { InitializeComponent(); var data = Customer.GetCustomerList(1000); grid.ItemsSource = data; } } |
You can also customize the expand and collapse buttons by replacing their icons with images using the OnRowHeaderLoading event, and setting the ExpandButton.CheckedImageSource and UncheckedImageSource properties as illustrated in the following code example.
C# |
Copy Code
|
---|---|
private void OnRowHeaderLoading(object sender, GridRowHeaderLoadingEventArgs e) { e.ExpandButton.CheckedImageSource = ImageSource.FromResource("collapse.png")); e.ExpandButton.UncheckedImageSource = ImageSource.FromResource("expand.png")); } |