Xamarin.Forms | ComponentOne
Controls / FlexGrid / Features / Row Details
In This Topic
    Row Details
    In This Topic

    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.

    1. Create a new data source class, Customer.cs and add it to your portable project.
    2. Add the following code to the Customer.cs class.
      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
          }
      
    3. Add a new Content Page, RowDetails to your portable project.
    4. To initialize a FlexGrid control and adding row details section, modify the markup between the <ContentPage></ContentPage> tags as illustrated in the code below.

      In XAML

      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>
      
    5. In the Solution Explorer, expand the RowDetails.xaml node and open the Merging.xaml.cs to open the C# code behind.
    6. Add the following code in the RowDetails class constructor to add row details section in the FlexGrid control.

      In Code

      C#
      Copy Code
      public partial class RowDetails : ContentPage
          {
              public RowDetails()
              {
                  InitializeComponent();
                  var data = Customer.GetCustomerList(1000);
                  grid.ItemsSource = data;
              }
          }
      

    Customizing expand and collapse buttons

    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")); 
    }