4

My DataGrid has a default set of columns to display, but I'd also like to let the user select/un-select columns displayed on their application. Is there a relatively easy way to do that in WPF?

The DataGrid is bound to a DataTable.

Note: I may just go with a simple "Default Columns/All Columns" via RadioButton solution if the above feature is too complicated.

3 Answers 3

6

The short answer is, bind the Visibility property of each column to a boolean flag that you're able to set (via a CheckBox or some other mechanism), and use a BooleanToVisibilityConverter to make the column visibility Collapsed when the flag is unset.

Dig this similar question, and especially this answer! His blog post lists what would be my ideal solution. :)

Sign up to request clarification or add additional context in comments.

Comments

6

Bind the DataGrid.Columns to an ItemsControl with a DataTemplate that contains a CheckBox for visibility-toggling, no code required except for the VisbilityToBoolConverter:

<Window
    ...
    DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}" Loaded="Window_Loaded">
    <Window.Resources>
        <local:VisibilityToBoolConverter x:Key="VisibilityToBoolConv"/>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <DataGrid ItemsSource="{Binding Data}" Name="DGrid"/>
        <ItemsControl ItemsSource="{Binding ElementName=DGrid, Path=Columns}" Grid.IsSharedSizeScope="True" Margin="5">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition SharedSizeGroup="A"/>
                            <ColumnDefinition SharedSizeGroup="B"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding Header}" Margin="5"/>
                        <CheckBox Grid.Column="1"  IsChecked="{Binding Visibility, Converter={StaticResource VisibilityToBoolConv}}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Window>

Note: I have a TextBlock which assumes the Column-Header to be a string, might need to be adjusted if that is not the case.


VisibilityConverter:

[ValueConversion(typeof(Visibility), typeof(bool))]
public class VisibilityToBoolConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Visibility vis = (Visibility)value;
        switch (vis)
        {
            case Visibility.Collapsed:
                return false;
            case Visibility.Hidden:
                return false;
            case Visibility.Visible:
                return true;
        }
        return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        if ((bool)value) return Visibility.Visible;
        else return Visibility.Collapsed;
    }

    #endregion
}

2 Comments

+1 for effort, @H.B. That's a lot of code I didn't bother to write in my answer. :)
I definitely invest too much time in this... curse you Stack-Overflow!
3

I prefer to extend DataGrid class and add this functionality.

public class DataGridEx : DataGrid
{
    public DataGridEx() : base()
    {
        // Create event for right click on headers
        var style = new Style { TargetType = typeof(DataGridColumnHeader) };
        var eventSetter = new EventSetter(MouseRightButtonDownEvent, new MouseButtonEventHandler(HeaderClick));
        style.Setters.Add(eventSetter);
        ColumnHeaderStyle = style;
    }

    private void HeaderClick(object sender, MouseButtonEventArgs e)
    {
        ContextMenu menu = new ContextMenu();
        // Fill context menu with column names and checkboxes
        var visibleColumns = this.Columns.Where(c => c.Visibility == Visibility.Visible).Count();
        foreach (var column in this.Columns)
        {
            var menuItem = new MenuItem
            {
                Header = column.Header.ToString(),
                IsChecked = column.Visibility == Visibility.Visible,
                IsCheckable = true,
                // Don't allow user to hide all columns
                IsEnabled = visibleColumns > 1 || column.Visibility != Visibility.Visible
            };
            // Bind events
            menuItem.Checked += (object a, RoutedEventArgs ea)
                => column.Visibility = Visibility.Visible;
            menuItem.Unchecked += (object b, RoutedEventArgs eb)
                => column.Visibility = Visibility.Collapsed;
            menu.Items.Add(menuItem);
        }
        // Open it
        menu.IsOpen = true;
    }
}

Now you can use this control instead of original DataGrid:

<dg:DataGridEx ItemsSource="{Binding OfflineData, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
    <dg:DataGridEx.Columns>
        <DataGridTextColumn Binding="{Binding PortID}" Header="ID" Width="50" Visibility="Collapsed"/>
        <DataGridTextColumn Binding="{Binding SwitchIP}" Header="Switch IP" Width="100" Visibility="Visible"/>
        <DataGridTextColumn Binding="{Binding Port}" Header="Port" Width="50" Visibility="Visible"/>
    </dg:DataGridEx.Columns>
</dg:DataGridEx>

Now user can right-click on column header to open menu with checkboxes:

Now user can right-click on column header to open menu with checkboxes

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.