ComponentOne Xamarin Edition
Row Details
Controls > FlexGrid > Features > Row Details

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.

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