Hacking the WPF GridView – How to hide columns

First part of (supposed) three about some tricky hackings around the WPF GridView, most used view mode of the very versatile ListView.
I think the ListView + GridView is a very good compromise for many grid-layered collection-presentation: simple but never uselessly complex.

In this first section I’ll show how to solve a pretty common task: control the visibility of a column.
That should be trivial if only the GridView (that is the GridViewColumn class) would have exposed any visibility property. So, it appears that the simplest way is to control the column’s width: by taking it to zero, the column actually disappear.
Note that this trick won’t prevent the columns to be part of the visual tree: it is still there, although the size is irrelevant.

Create the base application.

First off, we should create a base application to work with. That app should provide a bunch of data (the classic “Person” class), then present them in a grid-viewed fashion.
I got the data from a useful web site. The site offers several various-sized data set for a typical usage. I chose the set with 500 records. I never had to deal with huge set of data, instead with complex shaped. However, that’s not the focus.
Again, in the attached app there is the code to parse the CSV dataset: it’s simple, but this post won’t focus on how to parse it.

So, here follows the XAML source to show the data set on a GridView shaped ListView.

<Window 
    x:Class="ListViewHacking.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ListViewHacking"
    Title="ListView hacking demo" 
    Height="480" Width="900"
    WindowStartupLocation="CenterScreen"
    FontSize="14"
    Background="{StaticResource BG}"
    >
    
    <Grid
        Margin="50,40"
        >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel
            Orientation="Horizontal"
            Grid.Row="0"
            Margin="20,8"
            >
            <CheckBox Content="Show location columns" x:Name="ChkLoc" Margin="20,0" />
            <CheckBox Content="Show contact columns" x:Name="ChkCont" Margin="20,0" />
        </StackPanel>

        <ListView
            ItemsSource="{Binding Path=People, Source={x:Static local:App.Current}}"
            Grid.Row="1"
            >
            <ListView.Resources>
                <local:ColumnWidthConverter x:Key="cvColumnWidth" />
            </ListView.Resources>
            
            <ListView.View>
                <GridView
                    AllowsColumnReorder="False"
                    >
                    <GridViewColumn Header="FirstName" Width="100" DisplayMemberBinding="{Binding Path=FirstName}" />
                    <GridViewColumn Header="LastName" Width="100" DisplayMemberBinding="{Binding Path=LastName}" />

                    <GridViewColumn Header="Address" Width="200" DisplayMemberBinding="{Binding Path=Address}" />
                    <GridViewColumn Header="City" Width="120" DisplayMemberBinding="{Binding Path=City}" />
                    <GridViewColumn Header="State" Width="50" DisplayMemberBinding="{Binding Path=State}" />
                    <GridViewColumn Header="ZIP" Width="60" DisplayMemberBinding="{Binding Path=ZIP}" />
                    
                    <GridViewColumn Header="Phone" Width="150" DisplayMemberBinding="{Binding Path=Phone}" />
                    <GridViewColumn Header="Email" Width="150" DisplayMemberBinding="{Binding Path=Email}" />
                </GridView>
            </ListView.View>
        </ListView>

    </Grid>
</Window>

Note that there’s no underlying C# code behind this XAML.
The result, once started, is the following:

listview1

A converter for the column width.

As stated, the simplest way to control the column width by a boolean parameter is using a converter, as the WPF framework specifies. For simplicity, we’ll consider the visibility as a boolean “show”/”no-show”: the ability to hide a column by preserving its width (as the Visiblity enum allows) is not supported here.

    /// <summary>
    /// This converter targets a column header,
    /// in order to take its width to zero when
    /// it should be hidden
    /// </summary>
    public class ColumnWidthConverter
        : IValueConverter
    {
        public object Convert(
            object value, 
            Type targetType, 
            object parameter, 
            System.Globalization.CultureInfo culture)
        {
            var isVisible = (bool)value;
            var width = double.Parse(parameter as string);
            return isVisible ? width : 0.0;
        }


        public object ConvertBack(
            object value, 
            Type targetType, 
            object parameter, 
            System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

    }

Upon that, the XAML structure should be modified according, as below shown:

<Window 
    x:Class="ListViewHacking.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ListViewHacking"
    Title="ListView hacking demo" 
    Height="480" Width="900"
    WindowStartupLocation="CenterScreen"
    FontSize="14"
    Background="{StaticResource BG}"
    >
    
    <Grid
        Margin="50,40"
        >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel
            Orientation="Horizontal"
            Grid.Row="0"
            Margin="20,8"
            >
            <CheckBox Content="Show location columns" x:Name="ChkLoc" Margin="20,0" />
            <CheckBox Content="Show contact columns" x:Name="ChkCont" Margin="20,0" />
        </StackPanel>

        <ListView
            ItemsSource="{Binding Path=People, Source={x:Static local:App.Current}}"
            Grid.Row="1"
            x:Name="lvw1"
            >
            <ListView.Resources>
                <local:ColumnWidthConverter x:Key="cvColumnWidth" />
            </ListView.Resources>
            
            <ListView.View>
                <GridView
                    AllowsColumnReorder="False"
                    >
                    <GridViewColumn Header="FirstName" Width="100" DisplayMemberBinding="{Binding Path=FirstName}" />
                    <GridViewColumn Header="LastName" Width="100" DisplayMemberBinding="{Binding Path=LastName}" />

                    <GridViewColumn 
                        Header="Address" 
                        Width="{Binding Path=IsChecked, ElementName=ChkLoc, Converter={StaticResource cvColumnWidth}, ConverterParameter=200}" 
                        DisplayMemberBinding="{Binding Path=Address}" 
                        />
                    <GridViewColumn 
                        Header="City" 
                        Width="{Binding Path=IsChecked, ElementName=ChkLoc, Converter={StaticResource cvColumnWidth}, ConverterParameter=120}" 
                        DisplayMemberBinding="{Binding Path=City}" 
                        />
                    <GridViewColumn 
                        Header="State" 
                        Width="{Binding Path=IsChecked, ElementName=ChkLoc, Converter={StaticResource cvColumnWidth}, ConverterParameter=50}" 
                        DisplayMemberBinding="{Binding Path=State}" 
                        />
                    <GridViewColumn 
                        Header="ZIP"
                        Width="{Binding Path=IsChecked, ElementName=ChkLoc, Converter={StaticResource cvColumnWidth}, ConverterParameter=60}" 
                        DisplayMemberBinding="{Binding Path=ZIP}" 
                        />

                    <GridViewColumn 
                        Header="Phone" 
                        Width="{Binding Path=IsChecked, ElementName=ChkCont, Converter={StaticResource cvColumnWidth}, ConverterParameter=150}" 
                        DisplayMemberBinding="{Binding Path=Phone}" 
                        />
                    <GridViewColumn 
                        Header="Email" 
                        Width="{Binding Path=IsChecked, ElementName=ChkCont, Converter={StaticResource cvColumnWidth}, ConverterParameter=150}" 
                        DisplayMemberBinding="{Binding Path=Email}" 
                        />
                </GridView>
            </ListView.View>
        </ListView>

    </Grid>
</Window>

The demo above shows that there are two checkboxes: one controls the visibility of the “location” info of each person (row), and the second controls the contacts (phone and e-mail).
The visual result is pretty straightforward.

listview2

Conclusion.

The converter-way is the simplest solution we can use to solve the problem of hiding the columns of a gridview. However, there are many other features we can add, and that is what will be explained in the next articles.

Click here to download the sample application.

7 thoughts on “Hacking the WPF GridView – How to hide columns

  1. Yves

    I’ve implemented the same solution in my application. Unfortunately, it’s very slow at scrolling. A few hundred rows make it impossible to scroll interactively. It hangs for irregular times. Removing these columns helped, removing the width converter also helped. Returning a constant value in the converter already causes delays. So using a converter for the column width seems generally not recommended for longer lists. Looking for a different solution.

    • Mario Vernari

      The sample application shows 500 rows, but I don’t see any problem on scrolling. What kind of content do you place in the cells?

      • Mario Vernari

        Well, I just done a test using 8 columns, 500 rows and one checkbox per cell. The scrolling is not perfectly smooth, but acceptable for sure.
        If you need huge-data grids, you should turn to a commercial component. We use the Xceed, for instance.

      • Yves

        It seems it’s the pre-checked checkboxes, together with Windows Aero’s control animation. Disabling the (system-wide) control animation setting makes it fast again.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s