[SECTION-RELATED-BLOGS]

WPF Resources

[/SECTION-RELATED-BLOGS]

Datagrids are normally used for data visualization in a tabular format, where columns are displayed vertically and rows horizontally. Sometimes, we have a requirement to show data horizontally. Let's look at how we transpose a matrix so that rows become columns, and columns become rows, creating an inverted WPF Datagrid using ComponentOne FlexGrid.

transposed
This sample tutorial uses the .NET 6 FlexGrid, which is a WPF datagrid UI control you can get on NuGet in the C1.WPF.Grid package.

Try ComponentOne Studio

Download the latest version of ComponentOne Studio Enterprise

Download Now!

Step 1: Create a FlexGrid Extension

Creating a FlexGrid extension is a streamlined way to create a reusable FlexGrid across your apps as well as override key events. The transposed datagrid extension overrides the OnCellTapped event so that it can transpose the cell types and ranges that the user clicks.

using C1.WPF.Core;  
using C1.WPF.Grid;

public class TransposedGrid : FlexGrid  
{  
    public TransposedGrid()  
    {  
        CellFactory = **new** TransposedCellFactory();  
        MergeManager = **new** TransposedMergeManager();  
    }

    protected override void OnCellTapped(GridCellType cellType, GridCellRange range, C1TappedEventArgs te)  
    {  
        **if** (cellType == GridCellType.RowHeader)  
        {  
            base.OnCellTapped(TransposeCellType(cellType), TransposeRange(range), te);  
        }  
        **else**  
        {  
            base.OnCellTapped(cellType, range, te);  
        }  
    }

    public static GridCellType TransposeCellType(GridCellType cellType)  
    {  
        **if** (cellType == GridCellType.ColumnHeader)  
            **return** GridCellType.RowHeader;  
        **else** **if** (cellType == GridCellType.RowHeader)  
            **return** GridCellType.ColumnHeader;  
        **return** cellType;  
    }

    public static GridCellRange TransposeRange(GridCellRange range)  
    {  
        **return** **new** GridCellRange(range.Column, range.Row, range.Column2, range.Row2);  
    }  
}

The transposed FlexGrid extension also initiates a custom Cell Factory and Merge Manager that will transpose the display by by swapping row and columns. Next, we will define these classes.

Step 2: Define the Transposed Cell Factory

Cell Factories are similar to value converters but they are special to the FlexGrid control. The ICellFactory interface is used to create the FrameworkElement objects that represent the cells, optimized for UI virtualization when scrolling. In our sample, the TransposedCellFactory basically swaps the rows and column information for every purpose and interaction, so that the FlexGrid will display and scroll as a transposed grid.

internal class TransposedCellFactory : GridCellFactory  
{  
    public override int ColumnHeaderRowsCount => base.RowHeaderColumnsCount;  
    public override int RowHeaderColumnsCount => base.ColumnHeaderRowsCount;  
    public override int CellsColumnsCount => base.CellsRowsCount;  
    public override int CellsRowsCount => base.CellsColumnsCount;  
    protected override ColumnInfo GetColumnInfo(bool isHeader, int column)  
    {  
        **if** (isHeader)  
        {  
            **return** **new** ColumnInfo(Grid.DefaultRowHeaderColumnWidth, Grid.MinColumnWidth, Grid.MaxColumnWidth, **true**);  
        }  
        **else**  
        {  
            **return** **new** ColumnInfo(Grid.DefaultColumnWidth, Grid.MinColumnWidth, Grid.MaxColumnWidth, **true**);  
        }  
    }  
    protected override RowInfo GetRowInfo(bool isHeader, int row)  
    {  
        **return** DefaultRow;  
    }  
    public override void PrepareCell(GridCellType cellType, GridCellRange range, GridCellView cell, Thickness internalBorders)  
    {  
        base.PrepareCell(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range), cell, internalBorders);  
    }  
    public override object GetCellContentType(GridCellType cellType, GridCellRange range)  
    {  
        **return** base.GetCellContentType(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range));  
    }  
    public override FrameworkElement CreateCellContent(GridCellType cellType, GridCellRange range, object cellContentType)  
    {  
        **return** base.CreateCellContent(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range), cellContentType);  
    }  
    public override void BindCellContent(GridCellType cellType, GridCellRange range, FrameworkElement cellContent)  
    {  
        base.BindCellContent(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range), cellContent);  
    }  
    public override void UnbindCellContent(GridCellType cellType, GridCellRange range, FrameworkElement cellContent)  
    {  
        base.UnbindCellContent(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range), cellContent);  
    }  
    protected override bool CanSetColumnWidth(bool isHeader, int column)  
    {  
        **return** **false**;  
    }  
    protected override bool CanSetRowHeight(bool isHeader, int row)  
    {  
        **return** **false**;  
    }  
    public override bool AllowEditing(GridCellRange range)  
    {  
        **return** base.AllowEditing(TransposedGrid.TransposeRange(range));  
    }  
    public override FrameworkElement CreateCellEditor(GridCellRange range)  
    {  
        **return** base.CreateCellEditor(TransposedGrid.TransposeRange(range));  
    }  
    public override void OnEditEnded(GridCellRange range, FrameworkElement editor, bool editCancelled)  
    {  
        base.OnEditEnded(TransposedGrid.TransposeRange(range), editor, editCancelled);  
    }  
}

As you can see, the TransposeCellType and TransposeRange methods in our FlexGrid extension are called within every event override to create our transposed grid. The merge manager class is used for cell merging logic, and it too needs to be transposed.

public class TransposedMergeManager : GridMergeManager  
{  
    public override GridCellRange GetMergedRange(GridCellType cellType, GridCellRange range)  
    {  
        **return** TransposedGrid.TransposeRange(base.GetMergedRange(TransposedGrid.TransposeCellType(cellType), TransposedGrid.TransposeRange(range)));  
    }  
}

Step 3: Initiate TransposedGrid in XAML

Now that the extension is created, build the project and create the local component on your XAML page. Even though our extension and Cell Factory have swapped row and column logic, that only applies at runtime. The rest of the API still uses "columns" as the vertical stack of cells, and "rows" refers to the horizontal cells.

In this case we should set HeadersVisibility to "Row" and set the DefaultRowHeaderColumnWidth to some larger value such as "100". This will give us the look of fixed column headers (technically, still row headers) along the left edge of the grid.

<local:TransposedGrid x:Name="flexGrid"  
                      AutoGenerateColumns="True"  
                      ColumnHeaderBackground="LightGray"  
                      DefaultRowHeaderColumnWidth="100"  
                      GridLinesVisibility="All"  
                      HeadersVisibility="Row"  
                      HorizontalScrollBarVisibility="Visible"  
                      VerticalScrollBarVisibility="Visible">  
</local:TransposedGrid></td>

Step 4: Set your Datasource and Build!

Lastly, databind the FlexGrid using whatever approach you want. You may want to define the columns in XAML rather than have them automatically generated so that you have more control over their configuration in XAML. To set the FlexGrid datasource, set the ItemsSource property to your collection of business objects or enumerable list. Check out the documentation for more ways to databind.

The scrolling performance is super fast and smooth! You have to try it yourself.

Download the WPF Transposed FlexGrid Sample (using Cell Factory)

transposed

Alternative Approach using Rotate Transform and RTL

I played around with an alternative approach that uses RightToLeft (RTL) flow direction and basic transforms to transpose the FlexGrid. Using rotate transforms alone can transpose the grid, but the scrollbars are in the wrong location. RTL solves the scrollbar problem but it also introduces more issues - RTL flips everything else too. With some additional scale transforms, we can make most things behave as expected.

I would recommend using the Cell Factory approach described above, but you can download this alternative sample and see how it's done if the Cell Factory solution does not work for you.

Download the WPF Transposed FlexGrid Sample (alternative using Transforms and RTL)

Try ComponentOne Studio

Download the latest version of ComponentOne Studio Enterprise

Download Now!