Simple Column Chooser for XamWebGrid

[Infragistics] Devin Rader / Tuesday, January 19, 2010

If you’ve used the Infragistics XamDataGrid or UltraWinGrid, you are probably familiar with a feature each of those controls offers called Column Chooser (or Field Chooser).  This simple feature provides a simple UI that allows you to expose a way for end users to select the specific columns they want to see in the grid.

We’ve had a number of requests for this same feature for the XamWebGrid, so I thought I would spend a bit of time and create a simple version.  In this post, I will first show you how you can create a simple column chooser for a simple flat grid, and then show you how to create a more complex version for hierarchical grid.

Flat Grid Structure

To create a column chooser for a flat grid structure, I started by creating a simple grid connected to a service that returns data from the AdventureWorks Customers table.

XAML:

<Grid x:Name="LayoutRoot">
    <igGrid:XamWebGrid x:Name="xamwebgrid1" Grid.Column="2" 
        AutoGenerateColumns="False">
        <igGrid:XamWebGrid.Columns>
            <igGrid:TextColumn Key="CustomerID" />
            <igGrid:TextColumn Key="PersonID" />
            <igGrid:TextColumn Key="StoreID" />
            <igGrid:TextColumn Key="TerritoryID" />
            <igGrid:TextColumn Key="AccountNumber" />
            <igGrid:TextColumn Key="rowguid" />
            <igGrid:TextColumn Key="ModifiedDate" />
        </igGrid:XamWebGrid.Columns>
    </igGrid:XamWebGrid>
</Grid>

C#:

public partial class SimpleGrid : Page
{
    AdventureWorksClient client = new AdventureWorksClient();

    public SimpleGrid()
    {
        InitializeComponent();
        client.GetCustomersCompleted += 
            new EventHandler<GetCustomersCompletedEventArgs>(
                client_GetCustomersCompleted);
    }

    // Executes when the user navigates to this page.
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        client.GetCustomersAsync();
    }

    void client_GetCustomersCompleted(object sender, 
        GetCustomersCompletedEventArgs e)
    {
        this.xamwebgrid1.ItemsSource = e.Result;
    }
}

Now I need to create a list of the grid columns that the end user can choose from. What I want to do is place a Checkbox next to each item in that list that toggles the visibility of the corresponding grid column.  To do that I am going to use a ListBox with a custom DataTemplate:

<Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <controls:GridSplitter Grid.Column="1" VerticalAlignment="Stretch" 
         HorizontalAlignment="Center" 
         Width="5" Background="Gray" />
    <ListBox x:Name="columnsList">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <CheckBox IsChecked="{Binding Path=Visibility}" 
                    Content="{Binding Path=Key}" />
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <igGrid:XamWebGrid x:Name="xamwebgrid1" Grid.Column="2"
        AutoGenerateColumns="False">
        <igGrid:XamWebGrid.Columns>
            <igGrid:TextColumn Key="CustomerID" />
            <igGrid:TextColumn Key="PersonID" />
            <igGrid:TextColumn Key="StoreID" />
            <igGrid:TextColumn Key="TerritoryID" />
            <igGrid:TextColumn Key="AccountNumber" />
            <igGrid:TextColumn Key="rowguid" />
            <igGrid:TextColumn Key="ModifiedDate" />
        </igGrid:XamWebGrid.Columns>
    </igGrid:XamWebGrid>
</Grid>

Once I’ve added the ListBox, I can bind it to the grids Columns collection:

void client_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
    this.xamwebgrid1.ItemsSource = e.Result;

    this.columnsList.ItemsSource = this.xamwebgrid1.Columns;
}

The ListBoxes DataTemplate contains a CheckBox whose IsChecked property is bound to the Columns Visibility property, and Text property is bound to the Columns Key property.  By itself the binding to the to the IsChecked property won’t work because IsChecked is a boolean type, while the Columns Visibility property is of type Visibility.  To solve the type mismatch, I created a simple ValueConverter that converts between the Boolean and Visibility property, then added that to the binding:

<CheckBox 
    IsChecked="{Binding Path=Visibility, 
    Converter={StaticResource VisibilityToBool}}" Content="{Binding Path=Key}" />

That's all I have to do to create this simple column chooser UI.  Now I can run the project and see the list of columns in the grid.

As shown in the images, I can check and uncheck items in the list and the corresponding columns visibility is toggled.

Hierarchical Grid Column Chooser

The second sample will demonstrate how you can take the general idea of the previous sample and extend it to create a column choose for hierarchical grids.  The first thing I did was change my AdventureWorks query to return not only Customers, but SalesOrderHeaders and SalesOrderDetails data, and define the additional ColumnLayouts for these tables in the XamWebGrid.  Binding the new query results grid gives me a hierachical structure.

to begin to modify the column chooser to accomodate the hierachy, the first thing I need to do is provide a way for the end users to select the specific Column Layout they want to see Columns for.  To do this I put the ListBox into a StackPanel and dropped a ComboBox in above the ListBox:

<ComboBox x:Name="layoutsList" SelectionChanged="GridLayouts_SelectionChanged">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock 
            Text="{Binding Path=Key, Converter={StaticResource EmptyString}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Notice that I created a DataTemplate for the ComboBox, even though all I put in it is a simple TextBlock.  This was because I need to set a ValueConverter on the binding.  Notice that I’ve also wired SelectionChanged event up for the combo.  I will use that event later to change the ListBox contents.

In order to populate ComboBox with ColumnLayouts, I need to walk the structure of the grid.  I created a recursive method called AddLayout that adds ColumnLayouts to the ComboBox, then recurses any child layouts

public void AddLayout(ColumnLayout parent)
{
    if (parent.Key == "")
        parent.Key = "(no key)";

    this.layoutsList.Items.Add(parent);

    foreach (ColumnLayout child in parent.Columns.ColumnLayouts)
    {
        AddLayout(child);
    }
}

Couple of things to note about this method:

  1. In the 2009.2 release of XamWebGrid, the root column layout does not return a Key value, so I test for this and provide a value.  This is a bug that has been fixed and will be released in a Service Release.  To work around this issue, I created a ValueConverter that converts the null value returned from the column layout into the string “(root layout)”.
  2. For this method to work, I have to explicitly define the ColumnLayout structure in the XamWebGrid.  If you have the AutoGenerateColumns set to True, walking the layout structure will not work because of how the grid lazily loads data.  Unless explicitly defined, ColumnLayouts do not exist until they are needed for display in the grid.

The last thing I need to do is bind the ListBox to the currently selected ColumnLayout from the ComboBox in the SelectionChanged event:

this.columnsList.ItemsSource = ((ColumnLayout)e.AddedItems[0]).Columns;

Thats it!  Running the sample, I new see that I have a ComboBox above my ListBox, which initially is populated by the root layout columns.

I can change the selected item in the ComboBox to see Columns of other Layouts, and toggle the visibility of those columns.

Conclusion

Thats all there is to creating a very simple Column Chooser UI for the XamWebGrid.  The sample project with all of the source code can be downloaded here.  You will need to install the 2009.2 release of NetAdvantage WebClient in order to build the sample.