Skip to main content Skip to footer

FlexGrid Migration to WPF and Silverlight

FlexGrid is a lightweight and flexible datagrid control with an easy-to-use object model. It offers unique features like true unbound mode, cell merging, flexible styling, multi-cell row and column headers as well as quick and simple printing. What really makes the FlexGrid unique is the power and simplicity of its object model and great performance.

History

FlexGrid was first developed back in the Visual Basic days (now referred to as Active X) and was even licensed by Microsoft and bundled with VB6 (MSFlexGrid control). Since then ComponentOne has ported the grid to its WinForms and Mobile studios. FlexGrid later became the basis for the C1OlapGrid included in OLAP for WinForms. Over the past 20 years FlexGrid has become one of the most popular and powerful grids for desktop development. A large number of users rely on the FlexGrid's lightweight and flexible object model. So now the new addition of FlexGrid in Silverlight and WPF (the "XAML" platforms) really marks an important milestone in ComponentOne history. The control has been completely rewritten to take advantage of the newer technologies, but with the same goal and concept in mind: create a lightweight grid that not only works great in data bound scenarios, but is also flexible enough that it can work completely unbound. In this blog post I will discuss several key features from the classic WinForms FlexGrid control and show how you can accomplish the same functionality using the new XAML versions (which are nearly 100% compatible to each other). First, I'll cover some basics, then show-off the new printing support, and then I'll touch on three classic key features: cell editors, subtotals, and cell merging.

The Basics

In the new FlexGrid controls, a lot of the root level stuff is the same as the older versions you're used to. For instance, you can easily access columns by name (i.e., c1FlexGrid1.Columns["ColumnName"]) and you can access each individual cell with simple, coordinate indexing (i.e. c1FlexGrid1[rowIndex, columnIndex]). Instead of a DataSource property there is an ItemsSource property. This is to keep consistent with all Silverlight and WPF controls. This table summarizes the main properties, methods and events in FlexGrid for WinForms and the corresponding members in FlexGrid for Silverlight/WPF. One major change is how fixed rows and columns are handled. In the ActiveX, WinForms, and Mobile versions, fixed rows were used for column headers and row headers are fixed columns. If you wanted 2 fixed rows and 2 fixed columns, you would set these properties: C1FlexGrid1.Rows.Fixed = 2 C1FlexGrid1.Cols.Fixed = 2 And the value at C1FlexGrid1[0,0] was the left-uppermost fixed cell for WinForms, as you can see in the following image.

FlexGrid for WinForms

In the XAML version of FlexGrid this is very different. Now we have RowHeaders and ColumnHeaders collections which act as fixed cells (keep in mind fixing is completely different from freezing, which is another feature all-together). To fix cells we simply add Row and Column objects to these header collections as such:


//add 1 more fixed row  
c1FlexGrid1.RowHeaders.Columns.Add(new Column());  
//add 1 more fixed column  
c1FlexGrid1.ColumnHeaders.Rows.Add(new Row());  

By default, the RowHeaders collection already contains 1 Column, and the ColumnHeaders contains 1 Row, so we only have to add 1 more of each to get 2 total. We can then access these cells to update their values like:


c1FlexGrid1.ColumnHeaders[0, 0] = "first row";  
c1FlexGrid1.ColumnHeaders[1, 0] = "second row";  

FlexGrid for Silverlight

So now this means that the cell at c1FlexGrid1[0,0] is actually our first scrollable cell and not the left-uppermost fixed cell.

Printing

One of the coolest features with the Silverlight and WPF FlexGrid is its foolproof printing support. The main challenge with printing a datagrid is breaking it up across multiple pages. FlexGrid solves this issue by providing a GetPageImages method that breaks the grid into a list of visual elements for each page. Each page is an exact duplication of the grid including all custom styling, and column headers are repeated on each page for best readability. FlexGrid also caters to scaling the pages so the entire grid can be scaled to fit the width of a page or a single page. You can use the GetPageImages method to customize printing your way (i.e. maybe you want to add other content or a title page, etc) or just simply call the Print method in code for easy printing.


c1FlexGrid1.Print("My Document", ScaleMode.PageWidth, new Thickness(96), 20);  

The Print method can take several arguments including document title, scale mode (PageWidth, ActualSize, SinglePage, Selection), margin thickness (96 = 1 inch), and maximum page count. Print to XPS and get text search and select (WPF only). In Silverlight, FlexGrid takes advantage of the native printing support introduced in Silverlight 4.

Custom Cell Editors

One of the handiest features of the WinForms FlexGrid is the column Editor property which allows you to use external controls as specialized cell editors. For example, you may want to use the C1NumbericBox control that provides up/down spin buttons for entering numbers, or a drop-down for selecting from multi-column lists, or a specialized control that you wrote to edit your business objects. In FlexGrid for WinForms, you can easily enable custom editors by just associating an instance of the editor control with a grid column by using its Editor property. FlexGrid for Silverlight/WPF takes more of a web approach to this feature by supplying cell templates. Rather than just supplying a template column type like the Silverlight DataGrid or the ASP.NET GridView, each regular FlexGrid Column has CellTemplate and CellEditingTemplate properties. Use these properties to specify the visual elements responsible for showing and editing cells in the column. The element only need be of type DataTemplate. For example, below we define a column editor using C1NumericBox (part of the ComponentOne basic library). First, we define the template in XAML. This template is very simple as it only includes 1 element, but we could make this a lot more complex very easily.


<UserControl.Resources>  
    <DataTemplate x:Key="c1FlexNumber">  
        <c1basic:C1NumericBox Value="{Binding Q1, Mode=TwoWay}" />  
    </DataTemplate>  
</UserControl.Resources>  

Then we assign this to the CellEditingTemplate in code for our Q1 column.


c1FlexGrid1.Columns["Q1"].CellEditingTemplate = (DataTemplate)this.Resources["c1FlexNumber"];  

You can also set up columns and cell templates completely in XAML. This approach is cleaner if you are defining all of your columns in XAML too. This snippet just declares the Q1 column and notice we also must specify the non-editing, display CellTemplate. For that we just use a simple TextBlock.


<c1:C1FlexGrid Name="c1FlexGrid1" AutoGenerateColumns="False">  
    <c1:C1FlexGrid.Columns>  
       ...  
<c1:Column ColumnName="Q1">  
            <c1:Column.CellTemplate>  
                <DataTemplate>  
                    <TextBlock Text="{Binding Q1}" />  
                </DataTemplate>  
            </c1:Column.CellTemplate>  
            <c1:Column.CellEditingTemplate>  
                <DataTemplate>  
                    <c1sl:C1NumericBox Value="{Binding Q1, Mode=TwoWay}" />  
                </DataTemplate>  
            </c1:Column.CellEditingTemplate>  
        </c1:Column>  
    </c1:C1FlexGrid.Columns>  
</c1:C1FlexGrid>  

Download Cell Editors Sample: Silverlight 4/VS2010, WPF 3.5/VS2008

Creating Subtotals

In FlexGrid for WinForms, the Subtotal method generates hierarchical aggregates. It adds subtotal rows that contain aggregate data for the regular data rows, providing totals and grand totals of your data. In FlexGrid for Silverlight/WPF this feature can be performed if your ItemsSource implements ICollectionView. The ICollectionView interface is the main data-binding interface in Silverlight and WPF (in WinForms, that role is played by the IBindingList interface). ICollectionView is a rich interface. It includes support for grouping, which means you can easily create hierarchical views of your data. Silverlight and WPF provide common classes which implement the ICollectionView interface. Use one of these to turn a simple List into your ItemsSource. For Silverlight we use PagedCollectionView. For WPF you would use ListCollectionView.


List<Sales> salesList = GetSalesList();  
PagedCollectionView view = new PagedCollectionView(salesList);  
c1FlexGrid1.ItemsSource = view;  

Add each column you want to group by to the view.GroupDescriptions collection like this:


view.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));  
view.GroupDescriptions.Add(new PropertyGroupDescription("Region"));  

So far the ICollectionView provides us the grouping, but it does not automatically calculate and display the totals for us. This is where FlexGrid comes into play. Simply set the GroupAggregate property on any column you want to display totals on. You can choose among Count, Sum, and Average and so on.


//Set columns to display aggregate totals when data is grouped  
c1FlexGrid1.Columns["Q1"].GroupAggregate = C1.Silverlight.FlexGrid.Aggregate.Sum;  
c1FlexGrid1.Columns["Q2"].GroupAggregate = C1.Silverlight.FlexGrid.Aggregate.Sum;  
...  

The above screenshot looks almost identical to the same sample provided with FlexGrid for WinForms. To format the group headers to say "Total for ____", like in the above image, we provide a custom IValueConverter and assign it to the FlexGrid's GroupHeaderConverter property. For example, here we format the group nodes to display "Total for {Group.Name}".


c1FlexGrid1.GroupHeaderConverter = new TotalsGroupHeader();  
...  
// class used to format group captions for display  
public class TotalsGroupHeader : IValueConverter  
{  
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
    {  
        var gr = parameter as GroupRow;  
        var group = gr.Group;  
        if (group != null && gr != null && targetType == typeof(string))  
        {  
            return string.Format("Total for {0}", group.Name);  
        }   return value;  
    }  
    public object ConvertBack(object value, Type targetType,  
        object parameter,  
        System.Globalization.CultureInfo culture)  
    {  
        return value;  
    }  
}  

To expand/collapse a grouped nodes in code, just set the GroupRow.IsCollapsed property. Or use the handy CollapseGroupsToLevel method. For example, to collapse ALL groups just pass in 0 as the level.


foreach (GroupRow gr in c1FlexGrid1.Rows.OfType<GroupRow>())  
{  
    gr.IsCollapsed = true;  
}  

Download Subtotals Sample: Silverlight 4/VS2010, WPF 3.5/VS2008

Merging Cells

The FlexGrid control allows you to merge cells, making them span multiple rows or columns. Merging will occur if adjacent cells contain the same non-empty string. Standard cell merging is handled the same way across all versions of FlexGrid. First, you set the FlexGrid's AllowMerging property to a value other than None. Then, you set the AllowMerging property on any columns and rows you want to merge. For example, here we allow merging in two columns:


//Turn on cell merging  
c1FlexGrid1.AllowMerging = AllowMerging.All;  
//Allow merging in some columns  
c1FlexGrid1.Columns["Direction"].AllowMerging = true;  
c1FlexGrid1.Columns["Region"].AllowMerging = true;  

If we apply this code to the grid created in the previous sample, it will look like the image below. Notice how much the readability has improved with cell merging.

What's Different from WinForms?

The only difference in cell merging from the WinForms version is which merging rules are supported. The Silverlight/WPF version does not support restricted cell merging or "spilling." In addition, the FixedOnly rule, which is useful for setting up complex merged headers, is now logically separated into ColumnHeaders, RowHeaders and AllHeaders for more flexibility in merging the cells used to create bands across multiple columns or rows.

Custom Merging

The built-in merging functionality described is only intended to merge cells down columns or across rows. If you need to merge adjacent cells in both directions you have to add a bit more code from some our samples, as this functionality has always been left to the developer to customize. In FlexGrid for WinForms, you can perform custom merging logic by overriding the grid's GetMergedRange method. This method determines whether or not a cell should be merged with adjacent cells. To override a method basically means you have to create your own version of C1FlexGrid which inherits the original. This is not the easiest task because it means you have to replace your existing grids or start over. In FlexGrid for Silverlght/WPF, the code is practically the same; however you can now implement custom cell merging by creating a class that implements the IMergeManager interface and assigning an instance of this class to the FlexGrid's MergeManager property. No more overriding necessary.


// activate custom merge manager  
flex.AllowMerging = AllowMerging.All;  
flex.MergeManager = new MyMergeManager();  
...  
/// <summary>  
/// Custom merge manager that creates cell ranges spanning multiple rows and columns  
/// </summary>  
public class MyMergeManager : IMergeManager  
{  
    public CellRange GetMergedRange(C1FlexGrid grid, CellType cellType, CellRange rg)  
    {  
        // we are only interested in data cells  
        // (not merging row or column headers)  
        if (cellType == CellType.Cell)  
        {  
            // expand left/right  
            for (int i = rg.Column; i < grid.Columns.Count - 1; i++)  
            {  
                if (GetDataDisplay(grid, rg.Row, i) != GetDataDisplay(grid, rg.Row, i + 1)) break;  
                rg.Column2 = i + 1;  
            }  
            for (int i = rg.Column; i > 0; i--)  
            {  
                if (GetDataDisplay(grid, rg.Row, i) != GetDataDisplay(grid, rg.Row, i - 1)) break;  
                rg.Column = i - 1;  
            }  

            // expand up/down  
            for (int i = rg.Row; i < grid.Rows.Count - 1; i++)  
            {  
                if (GetDataDisplay(grid, i, rg.Column) != GetDataDisplay(grid, i + 1, rg.Column)) break;  
                rg.Row2 = i + 1;  
            }  
            for (int i = rg.Row; i > 0; i--)  
            {  
                if (GetDataDisplay(grid, i, rg.Column) != GetDataDisplay(grid, i - 1, rg.Column)) break;  
                rg.Row = i - 1;  
            }  
        }  

        // done  
        return rg;  
    }  
    string GetDataDisplay(C1FlexGrid grid, int r, int c)  
    {  
        return grid[r, c].ToString();  
    }  
}  

When we fill the FlexGrid with many adjacent equal cells, both vertically and horizontally, we get a grid like below.

Download Custom Cell Merging Sample: Silverlight 4/VS2010, WPF 3.5/VS2008

Conclusion

So far we've just tackled some basics and how to accomplish four classic features of the FlexGrid. A full migration guide would be too large a scope for this single blog post, but I plan to continue adding more topics over time. Leave feedback on which topics you'd like to see discussed in the next post. Of course, a lot of these features can be discovered in the samples which are included in the download with the control. And, as always, you can request more information and help on the FlexGrid for Silverlight/WPF forum. If you have not already, download Studio for Silverlight (includes FlexGrid) and Studio for WPF (includes FlexGrid) to work with these samples I've just shared with you. Note: To see the Print and CollapseGroupsToLevel methods, you will need version 20202.45 or higher.

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus