In this post I discuss a key feature in Xuni FlexGrid: custom cells. With custom cells you can display anything you want in a grid cell. But there are a few caveats when working in Xamarin.Forms compared to working directly in a native platform. Plus, you have a couple different ways to achieve the same result with FlexGrid, so it’s good to know your options. To customize cells in FlexGrid for Xamarin.Forms you have two options:

  1. In XAML using the CellTemplate property and optional binding Converters for coded logic.
  2. Completely in code using the Cell Factory approach.

And there is one performance limitation:

  1. The custom cell content must be a single View that's not a layout control.

If your cell content is a layout control, then the native platforms essentially are forced to create a new View for every cell and it's a big performance limitation. But there is a work around to the limitation. I will discuss both options and the limitation below.

In XAML

The CellTemplate property is used to define a cell’s content in XAML. This approach works like any data template as you can bind the inner controls to fields from the data item. Quick Example


<xuni:FlexGrid x:Name="grid" AutoGenerateColumns="False">  
  <xuni:FlexGrid.Columns>  
   <!--Custom Column-->  
    <xuni:GridColumn Header="Custom Column" Width="200">  
      <xuni:GridColumn.CellTemplate>  
        <DataTemplate>  
          <Label Text="{Binding MyNumericField}" TextColor="Green" />  
        </DataTemplate>  
      </xuni:GridColumn.CellTemplate>  
    </xuni:GridColumn>  
  </xuni:FlexGrid.Columns>  
</xuni:FlexGrid>  

If you are defining all columns in XAML, it’s important to set AutoGenerateColumns to False. This quick example above just displays a green text label for all cells in this one column. The advantage with the XAML approach is that you can very easily insert a custom UI control such as an image or a gauge into a cell. The disadvantage is that if you want conditional logic you need to supply a binding converter. At the end of this post I’ll show you how to apply conditional formatting to the label using a Converter.

In Code

Since creating data templates is not easy or fun to do in code, FlexGrid provides a simpler interface for customizing cells in code. The GridCellFactory class is used by the grid to create every cell shown in the grid. Like custom columns, custom cell factories can be highly customized, or they can be general, reusable classes for defining cell appearances. To create a custom cell in code, you first define your own class that extends GridCellFactory. Then you assign an instance of your class to the FlexGrid.CellFactory property. The FlexGrid control can only support one cell factory, however, a single cell factory can handle any number of custom cells in multiple columns. Below the code snippet I will explain the BindCellContent method. Quick Example


grid.CellFactory = new MyCellFactory();  

public class MyCellFactory : GridCellFactory  
{  
    ///  
    /// Override BindCellContent to customize the cell content  
    ///  
    public override void BindCellContent(GridCellType cellType, GridCellRange range, View cellContent)  
    {  
        base.BindCellContent(cellType, range, cellContent);  
        if (cellType == GridCellType.Cell && cellType != GridCellType.ColumnHeader && range.Column == 3)  
        {  
            var label = cellContent as Label;  
            if (label != null)  
            {  
                var originalText = label.Text;  
                double cellValue;  
                if (double.TryParse(originalText, out cellValue))  
                {  
                    label.TextColor = cellValue < 70.0 ? Color.Red : Color.Green;  
                    label.Text = String.Format("{0:n2}", cellValue);  
                }  
            }  
        }  
    }  
}  

To customize the cell content you will override BindCellContent. In the example above, we check the cell type and the column position before manipulating the text color and format. Values below 70 are colored Red. To customize the cell itself, such as the borders and background color, you should override the CreateCell method. The following example shades the cell color according to the data value. Notice that you can access FlexGrid data from the Grid property within the cell factory.


public class MyCellFactory : GridCellFactory  
{  
    /// <summary>  
    /// Creates the cell.  
    /// </summary>  
    /// <param name="cellType">Type of the cell.</param>  
    /// <param name="range">The range.</param>  
    /// <returns></returns>  
    public override GridCellView CreateCell(GridCellType cellType, GridCellRange range)  
    {  
        var cell = base.CreateCell(cellType, range);  
        if (cellType == GridCellType.Cell && range.Column == 4)  
        {  
            var cellValue = Grid[range.Row, range.Column] as int?;  
            if (cellValue.HasValue)  
            {  
                cell.BackgroundColor = cellValue < 50.0 ? Color.Red : Color.Green;  
            }  
        }  
        return cell;  
    }  
}  

Inside BindCellContent and CreateCell you are limited to customizing the default cell content. To provide your own custom UI, such as an image or other UI control, you will override the CreateCellContent method defined below.


///  
/// Override CreateCellContent to return your own custom view as a cell  
///  
public override View CreateCellContent(GridCellType cellType, GridCellRange range, object cellContentType)  
{  
    if (cellType == GridCellType.Cell)  
    {  
        if (Grid.Columns.Count > range.Column)  
        {  
            var r = Grid.Rows[range.Row];  
            var c = Grid.Columns[range.Column];  
            return base.CreateCellContent(cellType, range, cellContentType);  
        }  
        return null;  
    }  
    else if (cellType == GridCellType.ColumnHeader)  
    {  
        return new Label();  
    }  
    else  
    {  
        return null;  
    }  
}  

The above code does not return any custom View. Simply create your own View within this method and return it to customize your cell. You can download and see the full Conditional Formatting sample as part of the FlexGrid101 samples on GitHub. The advantage with the coded approach is that you can put all of your conditional logic in a single method for the entire grid (no binding converters are necessary). The disadvantage is that if you want something other than a label in the cell it must all be created in code, and thus you’re not taking advantage of the XAML platform with wonderful data templates and binding.

Xamarin.Forms Limitations

You’ll notice in each example above the custom cell content is a single View (control). That’s because in Xamarin.Forms we can directly render a large number of single Views at the native level without having to create a separate View for each cell. If you’re working directly in Android, Windows Phone or iOS you will be able to do it. We cannot, however, directly render a large number of layout controls at the native level with Xamarin.Forms. Instead, a separate View has to created for each cell that contains a layout control (such as a Grid or StackPanel) and this takes a huge hit on performance if your grid has many rows. You can work around this in your Xamarin.Forms app by writing your own renderer that returns your layout control as a single View which can then be directly rendered on every native platform. To create a renderer, see the topic Customizing Controls for Each Platform from Xamarin.

In XAML Continued…with a Converter

When you customize your cells in code with the cell factory, you can write your conditional logic within the class. If you prefer to use the CellTemplate/XAML approach you can also add some conditional code using a Converter. The following example extends the first example but now the values are only green if they are above 500 and red if below 500.


<ContentPage.Resources>  
  <ResourceDictionary>  
    <local:MyNumericColorConverter x:Key="myNumericColorConverter" />  
  </ResourceDictionary>  
</ContentPage.Resources>  

<xuni:FlexGrid x:Name="grid" AutoGenerateColumns="False">  
  <xuni:FlexGrid.Columns>  
   <!--Custom Column-->  
    <xuni:GridColumn Header="Custom Column" Width="200">  
      <xuni:GridColumn.CellTemplate>  
        <DataTemplate>  
          <Label Text="{Binding MyNumericField}" TextColor="{Binding MyNumericField, Converter={StaticResource myNumericColorConverter }}" />  
        </DataTemplate>  
      </xuni:GridColumn.CellTemplate>  
    </xuni:GridColumn>  
  </xuni:FlexGrid.Columns>  
</xuni:FlexGrid>  

We want the TextColor of our Label to be conditionally set based upon the value of MyNumericField, so that’s why we bind to it but with our converter. Our converter will take the numeric value from the data item and convert it to a Color that the TextColor property will accept.


public class MyNumericColorConverter : IValueConverter  
{  
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
    {  
        decimal d = (decimal)value;  
        if (d < 500)  
        {  
            return Color.Red;  
        }  
        else  
        {  
            return Color.Green;  
        }  
    }  

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
    {  
        return value;  
    }  
}  

Conclusion

If you’re a long time ComponentOne user, you may have noticed I actually wrote this exact same blog post exactly 4 years ago today for FlexGrid on Windows Phone. It’s the same feature we’ve ported to Android, iOS and Xamarin.Forms. A few things have changed due to Xamarin creating their own XAML platform but the concepts are the exact same. We’ve put more examples for the Xuni FlexGrid in the FlexGrid101 samples on GitHub.