Improve standard WPF DataGrid - 1/3 Show or hide columns by user

by Dawid Łaziński 19. May 2010 04:20

Microsoft has provided DataGrid for WPF within the Framework 4.0, but unfortunately it is lacking some useful features.

This article demonstrates how to enhance the DataGrid with the following features:

  • Showing and hiding columns by user
  • Freezing columns
  • Saving settings

The first feature described will be the ability of show or hides columns on user’s demand.

clip_image002

To resolve this problem I used an attached property. This technique makes it possible to add this functionality to existing controls in a non-invasive and clean way.

In case you want to add the functionality to an existing DataGrid the following xaml code should be used:

<DataGrid ItemsSource="{Binding}" 
                  AutoGenerateColumns="False"
                  x:Name="grid"
                  l:DataGridColumnChooser.IsEnabled="True"
                  >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Prop1}"
                                    Header="Prop1" />
                <DataGridTextColumn Binding="{Binding Prop2}"
                                    Header="Prop2" />
                <DataGridTextColumn Binding="{Binding Prop3}"
                                    Header="Prop3" />
                <DataGridTextColumn Binding="{Binding Prop4}"
                                    Header="Prop4" />
            </DataGrid.Columns>
</DataGrid>

In the above code one line is sufficient to turn on column chooser functionality on the DataGrid.

l:DataGridColumnChooser.IsEnabled="True"

To implement this functionality you have to create a new class, I called it DataGridColumnCooser, and then implement IsEnable attached property.

 
public static bool GetIsEnabled(DependencyObject obj)
{
    return (bool)obj.GetValue(IsEnabledProperty);
}
 
public static void SetIsEnabled(DependencyObject obj, bool value)
{
    obj.SetValue(IsEnabledProperty, value);
}
 
// Using a DependencyProperty as the backing store for IsEnabledProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnabledProperty =
    DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(DataGridColumnChooser), new UIPropertyMetadata(false, OnIsEnabledChanged));
 

You can see that there is an event handler, called whenever the functionality is turned on or off on the grid.

private static void OnIsEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    if (e.Property != IsEnabledProperty)
    {
        return;
    }
 
    if (!(e.OldValue is bool) || !(e.NewValue is bool))
    {
        return;
    }
 
    bool oldIsEnabled = (bool)e.OldValue;
    bool newIsEnabled = (bool)e.NewValue;
 
    if (oldIsEnabled == newIsEnabled)
    {
        return;
    }
 
    if (dependencyObject == null)
    {
        throw new ArgumentNullException("dependencyObject");
    }
 
    DataGrid dataGrid = dependencyObject as DataGrid;
 
    if (dataGrid == null)
    {
        string message = String.Format(CultureInfo.CurrentCulture, "Parameter must be of type '{0}'.", typeof(DataGrid).FullName);
 
        throw new ArgumentException(message, "dependencyObject");
    }
 
    if (oldIsEnabled)
    {
        if (choosers.ContainsKey(dataGrid))
        {
            choosers[dataGrid].Unregister();
 
            choosers.Remove(dataGrid);
        }
    }
    else
    {
        if (!choosers.ContainsKey(dataGrid))
        {
            DataGridColumnChooser chooser = new DataGridColumnChooser(dataGrid);
 
            choosers[dataGrid] = chooser;
 
            chooser.Register();
        }
    }
}

Full listing:

public class DataGridColumnChooser
{
 
    private DataGridColumnChooser(DataGrid dataGrid)
    {
        if (dataGrid == null)
        {
            throw new ArgumentNullException("dataGrid");
        }
 
        this.dataGrid = dataGrid;
    }
 
    private static readonly Dictionary<DataGrid, DataGridColumnChooser> choosers = new Dictionary<DataGrid, DataGridColumnChooser>();
 
    private readonly DataGrid dataGrid;
    private ContextMenu contextMenu;
    private DataGridColumn currentColumn;
    private bool isUnregistered;
 
 
 
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }
 
    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }
 
    // Using a DependencyProperty as the backing store for IsEnabledProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(DataGridColumnChooser), new UIPropertyMetadata(false, OnIsEnabledChanged));
 
    private static void OnIsEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        if (e.Property != IsEnabledProperty)
        {
            return;
        }
 
        if (!(e.OldValue is bool) || !(e.NewValue is bool))
        {
            return;
        }
 
        bool oldIsEnabled = (bool)e.OldValue;
        bool newIsEnabled = (bool)e.NewValue;
 
        if (oldIsEnabled == newIsEnabled)
        {
            return;
        }
 
        if (dependencyObject == null)
        {
            throw new ArgumentNullException("dependencyObject");
        }
 
        DataGrid dataGrid = dependencyObject as DataGrid;
 
        if (dataGrid == null)
        {
            string message = String.Format(CultureInfo.CurrentCulture, "Parameter must be of type '{0}'.", typeof(DataGrid).FullName);
 
            throw new ArgumentException(message, "dependencyObject");
        }
 
        if (oldIsEnabled)
        {
            if (choosers.ContainsKey(dataGrid))
            {
                choosers[dataGrid].Unregister();
 
                choosers.Remove(dataGrid);
            }
        }
        else
        {
            if (!choosers.ContainsKey(dataGrid))
            {
                DataGridColumnChooser chooser = new DataGridColumnChooser(dataGrid);
 
                choosers[dataGrid] = chooser;
 
                chooser.Register();
            }
        }
    }
 
 
    private void Unregister()
    {
        dataGrid.ColumnReordered -= OnColumnsReordered;
        dataGrid.AutoGeneratedColumns -= OnAutogeneratedColumns;
        dataGrid.ColumnDisplayIndexChanged -= OnColumnDisplayIndexChanged;
        dataGrid.Columns.CollectionChanged -= OnColumnsCollectionChanged;
 
        isUnregistered = true;
    }
 
 
    private void Register()
    {
        dataGrid.ColumnReordered += OnColumnsReordered;
        dataGrid.AutoGeneratedColumns += OnAutogeneratedColumns;
        dataGrid.ColumnDisplayIndexChanged += OnColumnDisplayIndexChanged;
        dataGrid.Columns.CollectionChanged += OnColumnsCollectionChanged;
 
        dataGrid.MouseRightButtonUp += OnMouseRightButtonUp;
 
        BuildContextMenu();
    }
 
 
 
 
    private void BuildContextMenu()
    {
        if (contextMenu == null)
        {
            contextMenu = new ContextMenu();
        }
 
        foreach (MenuItem menuItem in contextMenu.Items.OfType<MenuItem>())
        {
            BindingOperations.ClearBinding(menuItem, MenuItem.IsCheckedProperty);
        }
 
        contextMenu.Items.Clear();
 
        foreach (DataGridColumn dataGridColumn in dataGrid.Columns)
        {
            MenuItem menuItem = new MenuItem
            {
                Header = dataGridColumn.Header,
                IsCheckable = true,
                IsChecked = dataGridColumn.Visibility == Visibility.Visible
            };
 
            DataGridColumn tempColumn = dataGridColumn;
 
            Binding binding = new Binding("Visibility")
            {
                Mode = BindingMode.TwoWay,
                Converter = VisibilityToBooleanConverter.Instance,
                Source = tempColumn,
            };
 
            BindingOperations.SetBinding(menuItem, MenuItem.IsCheckedProperty, binding).UpdateTarget();
 
            contextMenu.Items.Add(menuItem);
        }
 
 
    }
 
 
 
 
    private void OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
    {
        DependencyObject dependencyObject = (DependencyObject)e.OriginalSource;
 
        while ((dependencyObject != null) && !(dependencyObject is DataGridColumnHeader))
        {
            dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
        }
 
        if (dependencyObject == null)
        {
            return;
        }
 
        if (dependencyObject is DataGridColumnHeader)
        {
            DataGridColumnHeader columnHeader = dependencyObject as DataGridColumnHeader;
 
            currentColumn = columnHeader.Column;
 
            columnHeader.ContextMenu = isUnregistered ? null : contextMenu;
        }
        else
        {
            currentColumn = null;
 
        }
    }
 
 
    private void OnColumnsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        BuildContextMenu();
    }
 
    private void OnColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e)
    {
        BuildContextMenu();
    }
 
    private void OnAutogeneratedColumns(object sender, EventArgs e)
    {
        BuildContextMenu();
    }
 
    private void OnColumnsReordered(object sender, DataGridColumnEventArgs e)
    {
        BuildContextMenu();
    }
 
 
    #region Nested type: VisibilityToBooleanConverter
    private class VisibilityToBooleanConverter : IValueConverter
    {
        #region Public Static Fields
        public static readonly VisibilityToBooleanConverter Instance = new VisibilityToBooleanConverter();
        #endregion
 
        #region IValueConverter Members
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Visibility)
            {
                return ((Visibility)value) == Visibility.Visible;
            }
 
            return DependencyProperty.UnsetValue;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool)
            {
                return (bool)value ? Visibility.Visible : Visibility.Collapsed;
            }
 
            return DependencyProperty.UnsetValue;
        }
        #endregion
    }
    #endregion
 
}

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.NET | WPF

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen