Improve standard WPF DataGrid - 2/3 Freezing columns

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

In the section I will discuss the topic of freezing columns.

At the beginning I will demonstrate the result that I want to achieve.

Unfrozen columns:

clip_image002

Freezing second column:

clip_image004

Frozen second column:

clip_image006

You can see the bottom scrollbar became shorter.

To implement this functionality I extended DataGridColumnChooser, which implementation was described in the previous article.

I added a new attached property AllowFreezing.

public static bool GetAllowFreezing(DependencyObject obj)
{
    return (bool)obj.GetValue(AllowFreezingProperty);
}
 
public static void SetAllowFreezing(DependencyObject obj, bool value)
{
    obj.SetValue(AllowFreezingProperty, value);
}
 
public static readonly DependencyProperty AllowFreezingProperty = DependencyProperty.RegisterAttached("AllowFreezing", typeof(bool), typeof(DataGridColumnChooser), new UIPropertyMetadata(false, OnAllowFreezingChanged));
 
 
private static void OnAllowFreezingChanged(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");
    }
 
    bool isEnabled = GetIsEnabled(dataGrid);
 
    if (isEnabled)
    {
        if (choosers.ContainsKey(dataGrid))
        {
            choosers[dataGrid].BuildContextMenu();
        }
    }
}

Next I added some code to the BuildContextMenu Method.

bool allowFreezing = GetAllowFreezing(dataGrid);
 
if (allowFreezing)
{
    if (contextMenu.Items.Count > 0)
    {
        freezeSeparator = new Separator();
 
        contextMenu.Items.Add(freezeSeparator);
    }
 
    freezeMenuItem = new MenuItem {
        Header = "Freeze"
    };
 
    freezeMenuItem.Click += OnFreezeMenuItemClicked;
 
    unfreezeMenuItem = new MenuItem {
        Header = "Unfreeze"
    };
 
    unfreezeMenuItem.Click += OnUnfreezeMenuItemClicked;
 
    contextMenu.Items.Add(freezeMenuItem);
    contextMenu.Items.Add(unfreezeMenuItem);
}

Full listing:

public class DataGridColumnChooser
{
    #region Private Static Fields
    private static readonly Dictionary<DataGrid, DataGridColumnChooser> choosers = new Dictionary<DataGrid, DataGridColumnChooser>();
    #endregion
 
    #region Public Static Fields
    public static readonly DependencyProperty AllowFreezingProperty = DependencyProperty.RegisterAttached("AllowFreezing", typeof(bool), typeof(DataGridColumnChooser), new UIPropertyMetadata(false, OnAllowFreezingChanged));
    public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(DataGridColumnChooser), new UIPropertyMetadata(false, OnIsEnabledChanged));
    #endregion
 
    #region Private Fields
    private readonly DataGrid dataGrid;
    private ContextMenu contextMenu;
    private DataGridColumn currentColumn;
    private MenuItem freezeMenuItem;
    private Separator freezeSeparator;
    private bool isUnregistered;
    private MenuItem unfreezeMenuItem;
    #endregion
 
    #region Constructors
    private DataGridColumnChooser(DataGrid dataGrid)
    {
        if (dataGrid == null)
        {
            throw new ArgumentNullException("dataGrid");
        }
 
        this.dataGrid = dataGrid;
    }
    #endregion
 
    #region Public Methods
    [AttachedPropertyBrowsableForType(typeof(DataGrid))]
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }
 
    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }
 
    public static bool GetAllowFreezing(DependencyObject obj)
    {
        return (bool)obj.GetValue(AllowFreezingProperty);
    }
 
    public static void SetAllowFreezing(DependencyObject obj, bool value)
    {
        obj.SetValue(AllowFreezingProperty, value);
    }
    #endregion
 
    #region Private Methods
    private static void OnAllowFreezingChanged(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");
        }
 
        bool isEnabled = GetIsEnabled(dataGrid);
 
        if (isEnabled)
        {
            if (choosers.ContainsKey(dataGrid))
            {
                choosers[dataGrid].BuildContextMenu();
            }
        }
    }
 
    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 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);
        }
 
        bool allowFreezing = GetAllowFreezing(dataGrid);
 
        if (allowFreezing)
        {
            if (contextMenu.Items.Count > 0)
            {
                freezeSeparator = new Separator();
 
                contextMenu.Items.Add(freezeSeparator);
            }
 
            freezeMenuItem = new MenuItem {
                Header = "Freeze"
            };
 
            freezeMenuItem.Click += OnFreezeMenuItemClicked;
 
            unfreezeMenuItem = new MenuItem {
                Header = "Unfreeze"
            };
 
            unfreezeMenuItem.Click += OnUnfreezeMenuItemClicked;
 
            contextMenu.Items.Add(freezeMenuItem);
            contextMenu.Items.Add(unfreezeMenuItem);
        }
    }
 
    private void SetupFreezing()
    {
        if (currentColumn == null)
        {
            return;
        }
 
        if (dataGrid.FrozenColumnCount == 0)
        {
            unfreezeMenuItem.Visibility = Visibility.Collapsed;
 
            freezeMenuItem.Visibility = Visibility.Visible;
        }
        else
        {
            unfreezeMenuItem.Visibility = Visibility.Visible;
 
            freezeMenuItem.Visibility = currentColumn.DisplayIndex == dataGrid.FrozenColumnCount - 1 ? Visibility.Collapsed : Visibility.Visible;
        }
    }
 
    private void OnUnfreezeMenuItemClicked(object sender, RoutedEventArgs e)
    {
        dataGrid.FrozenColumnCount = 0;
    }
 
    private void OnFreezeMenuItemClicked(object sender, RoutedEventArgs e)
    {
        dataGrid.FrozenColumnCount = currentColumn.DisplayIndex + 1;
    }
 
    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;
 
            SetupFreezing();
 
            columnHeader.ContextMenu = isUnregistered ? null : contextMenu;
        }
        else
        {
            currentColumn = null;
 
            DisableFreezing();
        }
    }
 
    private void DisableFreezing()
    {
        freezeSeparator.Visibility = Visibility.Collapsed;
        freezeMenuItem.Visibility = Visibility.Collapsed;
        unfreezeMenuItem.Visibility = Visibility.Collapsed;
    }
 
    private void Unregister()
    {
        dataGrid.ColumnReordered -= OnColumnsReordered;
        dataGrid.AutoGeneratedColumns -= OnAutogeneratedColumns;
        dataGrid.ColumnDisplayIndexChanged -= OnColumnDisplayIndexChanged;
        dataGrid.Columns.CollectionChanged -= OnColumnsCollectionChanged;
 
        isUnregistered = true;
    }
 
    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();
    }
    #endregion
 
    #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
}

The next part of the article will appear soon.

Currently rated 2.6 by 5 people

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

Tags: , ,

ASP.NET | WPF

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen