WPF: DataGrid ContextMenu for Column Visibility


Introduction

When dealing with software applications it is commonplace for users to have certain expectations. These expectations not only include the abilities of an application, but also include how such abilities are executed. The specific expectations that this article is focused upon directly concern those of the data grid control, and more specifically Microsoft’s implementation of such a control under the Windows Presentation Foundation (WPF) user-interface rendering sub-system for Windows-based applications.

Users have come to expect the ability to hide or show the columns of a data grid control. Such an action is typically executed by right-clicking on any of the data grid’s column headers. This results in the option to select a menu item, via a context menu, that will toggle the visibility of the column labeled on the menu item. Therefore providing this ability with access to execute its associated actions in the typical way, would be a wise and logical decision. The implementation of meeting this need should be provided through an encapsulated and reusable interface. Unfortunately, Microsoft’s implementation of the data grid control does not offer this functionally as a built-in feature and adding it has proving to be a difficult task.

In this article you will be provided with a solution that implements this functionality specifically for Microsoft’s implementation of the data grid control. This solution has proving reliable regardless of whether the data grid’s columns have been auto-generated through the data in which it represents, while still conforming to the generally accepted Model-View-ViewModel (MVVM) design pattern guidelines. This has been accomplished by utilizing WPF’s attached properties system whose means provide the ability of separate child elements to specify unique values on properties defined in a parent element; thus providing the ability to manipulate the parent element’s behavior indirectly. In this way the concepts of encapsulation and reuse have been applied.

The Attached Properties Overview

The decision of whether users are provided with the means to hide or show columns is directly specified by the Boolean value of the attached property CanUserHideColumns on the data grid control in question. A Boolean value of True enables this functionality to the user, while a value of False disables it. Customizing the way the user is expected to execute this function is accomplished with any of the three following optional attached properties: HideColumnsHeader, HideColumnsHeaderIcon, and HideColumnsHeaderTemplate.

Without specifying any of the optional attached properties, the default will be implicitly assumed. The default consists of adding a menu item to the context menu of every column header within the data grid control instance. If a column header has a context menu with existing items, they will be preserved.

To customize the placement of the menu items generated, a value for the HideColumnsHeader attached property must be specified on the data grid instance. Doing so results in the generated menu items to instead be contained within a parent menu item which is then added to end of every context menu of every column header within the data grid control. The reference type for the value of this attached property is of type Object; the base type for all .NET data types regardless of whether or not they are accessed by value or by reference. The Object instance provided is used as the source of the dependency property defined as Header, for the generated items container menu item. By default if the Object-derived instance is not of type System.Windows.Media.Visual, or any of its derivatives, it’s ToString method is called to display a string representation of the instance for the items container menu item. Otherwise, the element is then displayed as normal by the WPF rendering sub-system. This implicit functionality exists because the default control template for the menu item control uses a ContentPresenter element to display the value of it’s Header dependency property.

To customize the way the containing menu item is displayed is done so by specifying a value, of type DataTemplate, for the HideColumnsHeaderTemplate attached property on the data grid instance. This data template is used as the source for the value of the containing menu item’s HeaderTemplate dependency property. Ultimately because the default style for the menu item control displays itself using a ContentPresenter element, this value is actually the source of said ContentPresenter’s ContentTemplate dependency property. The function of this property is to allow implementers to specify a customized appearance of it’s Content dependency property. We can use the HideColumnsHeaderTemplate and HideColumnsHeader dependency properties in combination to customize the placement of the generated menu items and the display of the menu item containing them.

The Attached Properties Source Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;

namespace CanUserHideColumnDemo
{
    public static class DataGridAPs
    {
        #region HideColumns
        #region HideColumnsHeader
        public static readonly DependencyProperty HideColumnsHeaderProperty =
            DependencyProperty.RegisterAttached("HideColumnsHeader",
            typeof(object), typeof(DataGridAPs));

        public static object GetHideColumnsHeader(DataGrid obj)
        {
            return obj.GetValue(HideColumnsHeaderProperty);
        }

        public static void SetHideColumnsHeader(DataGrid obj, object value)
        {
            obj.SetValue(HideColumnsHeaderProperty, value);
        }
        #endregion HideColumnsHeader

        #region HideColumnsHeaderTemplate
        public static readonly DependencyProperty HideColumnsHeaderTemplateProperty =
            DependencyProperty.RegisterAttached("HideColumnsHeaderTemplate",
            typeof(DataTemplate), typeof(DataGridAPs));

        public static DataTemplate GetHideColumnsHeaderTemplate(DataGrid obj)
        {
            return (DataTemplate)obj.GetValue(HideColumnsHeaderTemplateProperty);
        }

        public static void SetHideColumnsHeaderTemplate(DataGrid obj, DataTemplate value)
        {
            obj.SetValue(HideColumnsHeaderTemplateProperty, value);
        }
        #endregion HideColumnsHeaderTemplate

        #region HideColumnsIcon
        public static readonly DependencyProperty HideColumnsIconProperty =
            DependencyProperty.RegisterAttached("HideColumnsIcon",
            typeof(object), typeof(DataGridAPs));

        public static object GetHideColumnsIcon(DataGrid obj)
        {
            return obj.GetValue(HideColumnsIconProperty);
        }

        public static void SetHideColumnsIcon(DataGrid obj, object value)
        {
            obj.SetValue(HideColumnsIconProperty, value);
        }
        #endregion HideColumnsIcon

        #region CanUserHideColumns
        public static readonly DependencyProperty CanUserHideColumnsProperty =
            DependencyProperty.RegisterAttached("CanUserHideColumns",
            typeof(bool), typeof(DataGridAPs),
            new UIPropertyMetadata(false, OnCanUserHideColumnsChanged));

        public static bool GetCanUserHideColumns(DataGrid obj)
        {
            return (bool)obj.GetValue(CanUserHideColumnsProperty);
        }

        public static void SetCanUserHideColumns(DataGrid obj, bool value)
        {
            obj.SetValue(CanUserHideColumnsProperty, value);
        }

        private static void OnCanUserHideColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGrid dataGrid = d as DataGrid;
            if (dataGrid == null)
                return;

            if ((bool)e.NewValue == false)
            {
                dataGrid.Loaded -= new RoutedEventHandler(dataGrid_Loaded);
                RemoveAllItems(dataGrid);
                return;
            }

            if (!dataGrid.IsLoaded)
            {
                dataGrid.Loaded -= new RoutedEventHandler(dataGrid_Loaded);
                dataGrid.Loaded += new RoutedEventHandler(dataGrid_Loaded);
            }
            else
                SetupColumnHeaders(dataGrid);
        }

        private static void dataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            DataGrid dataGrid = sender as DataGrid;
            if (dataGrid == null)
                return;

            if (BindingOperations.IsDataBound(dataGrid, DataGrid.ItemsSourceProperty))
            {
                Binding b = BindingOperations.GetBinding(dataGrid, DataGrid.ItemsSourceProperty);
                dataGrid.TargetUpdated += new EventHandler<DataTransferEventArgs>(dataGrid_TargetUpdated);

                string xaml = XamlWriter.Save(b);
                Binding b2 = XamlReader.Parse(xaml) as Binding;
                if (b2 != null)
                {
                    b2.NotifyOnTargetUpdated = true;
                    BindingOperations.ClearBinding(dataGrid, DataGrid.ItemsSourceProperty);
                    BindingOperations.SetBinding(dataGrid, DataGrid.ItemsSourceProperty, b2);
                }
            }
            else
                SetupColumnHeaders(dataGrid);
        }

        private static void dataGrid_TargetUpdated(object sender, DataTransferEventArgs e)
        {
            if (e.Property != DataGrid.ItemsSourceProperty)
                return;

            DataGrid dataGrid = sender as DataGrid;
            if (dataGrid == null)
                return;

            EventHandler handler = null;
            handler = delegate
            {
                RemoveAllItems(dataGrid);
                if (SetupColumnHeaders(dataGrid))
                    dataGrid.LayoutUpdated -= handler;
            };

            dataGrid.LayoutUpdated += handler;
        }

        private static DataGridColumnHeader[] GetAllVisibleColumnHeaders(DataGrid dataGrid)
        {
            if (dataGrid == null)
                return null;

            dataGrid.UpdateLayout();
            DataGridColumnHeader[] columnHeaders = CustomVisualTreeHelper<DataGridColumnHeader>.FindChildrenRecursive(dataGrid);

            return (from DataGridColumnHeader columnHeader in columnHeaders
                    where columnHeader != null && columnHeader.Column != null
                    select columnHeader).ToArray();
        }

        private static string GetColumnName(DataGridColumn column)
        {
            if (column == null)
                return string.Empty;

            if (column.Header != null)
                return column.Header.ToString();
            else
                return string.Format("Column {0}", column.DisplayIndex);
        }

        private static MenuItem GenerateItem(DataGrid dataGrid, DataGridColumn column)
        {
            if (column == null)
                return null;

            MenuItem item = new MenuItem();
            item.Tag = column;

            item.Header = GetColumnName(column);
            if (string.IsNullOrEmpty(item.Header as string))
                return null;

            item.ToolTip = string.Format("Toggle column '{0}' visibility.", item.Header);

            item.IsCheckable = false;
            item.IsChecked = column.Visibility == Visibility.Visible;

            item.Click += delegate
            {
                SetItemIsChecked(dataGrid, column, column.Visibility != Visibility.Visible);
                item.IsChecked = column.Visibility == Visibility.Visible;
            };

            return item;
        }

        public static MenuItem[] GetAttachedItems(DataGridColumnHeader columnHeader)
        {
            if (columnHeader == null || columnHeader.ContextMenu == null)
                return null;

            ItemsControl itemsContainer = (from object i in columnHeader.ContextMenu.Items
                                           where i is MenuItem && ((MenuItem)i).Tag != null && ((MenuItem)i).Tag.ToString() == "ItemsContainer"
                                           select i).FirstOrDefault() as MenuItem;

            if (itemsContainer == null)
                itemsContainer = columnHeader.ContextMenu;

            return (from object i in itemsContainer.Items
                    where i is MenuItem && ((MenuItem)i).Tag is DataGridColumn
                    select i).Cast<MenuItem>().ToArray();
        }

        private static DataGridColumn GetColumnFromName(DataGrid dataGrid, string columnName)
        {
            if (string.IsNullOrEmpty(columnName))
                return null;

            foreach (DataGridColumn column in dataGrid.Columns)
            {
                if (GetColumnName(column) == columnName)
                    return column;
            }

            return null;
        }

        private static DataGridColumnHeader GetColumnHeaderFromColumn(DataGrid dataGrid, DataGridColumn column)
        {
            if (dataGrid == null || column == null)
                return null;

            DataGridColumnHeader[] columnHeaders = GetAllVisibleColumnHeaders(dataGrid);
            return (from DataGridColumnHeader columnHeader in columnHeaders
                    where columnHeader.Column == column
                    select columnHeader).FirstOrDefault();
        }

        public static void RemoveAllItems(DataGrid dataGrid)
        {
            if (dataGrid == null)
                return;

            foreach (DataGridColumn column in dataGrid.Columns)
            {
                RemoveAllItems(dataGrid, column);
            }
        }

        public static void RemoveAllItems(DataGrid dataGrid, DataGridColumn column)
        {
            if (dataGrid == null || column == null)
                return;

            DataGridColumnHeader columnHeader = GetColumnHeaderFromColumn(dataGrid, column);
            List<MenuItem> itemsToRemove = new List<MenuItem>();

            if (columnHeader == null)
                return;

            // Mark items and/or items container for removal.
            if (columnHeader.ContextMenu != null)
            {
                foreach (object item in columnHeader.ContextMenu.Items)
                {
                    if (item is MenuItem && ((MenuItem)item).Tag != null
                        && (((MenuItem)item).Tag.ToString() == "ItemsContainer" || ((MenuItem)item).Tag is DataGridColumn))
                        itemsToRemove.Add((MenuItem)item);
                }
            }

            // Remove items and/or items container.
            foreach (MenuItem item in itemsToRemove)
            {
                columnHeader.ContextMenu.Items.Remove(item);
            }
        }

        public static void ResetupColumnHeaders(DataGrid dataGrid)
        {
            foreach (DataGridColumn c in dataGrid.Columns)
            {
                c.Visibility = Visibility.Visible;
            }
            RemoveAllItems(dataGrid);
            SetupColumnHeaders(dataGrid);
        }

        private static void SetItemIsChecked(DataGrid dataGrid, DataGridColumn column, bool isChecked)
        {
            if (dataGrid == null || column == null)
                return;

            // Deny request if there are no other columns visible. Otherwise,
            // they'd have no way of changing the visibility of any columns
            // again.
            if (!isChecked && (from DataGridColumn c in dataGrid.Columns
                               where c.Visibility == Visibility.Visible
                               select c).Count() < 2)
                return;

            if (isChecked && column.Visibility != Visibility.Visible)
            {
                ShowColumn(dataGrid, column);
            }
            else if (!isChecked)
                column.Visibility = Visibility.Hidden;

            DataGridColumnHeader[] columnHeaders = GetAllVisibleColumnHeaders(dataGrid);
            ItemsControl itemsContainer = null;
            object containerHeader = GetHideColumnsHeader(dataGrid);

            foreach (DataGridColumnHeader columnHeader in columnHeaders)
            {
                itemsContainer = null;
                if (columnHeader != null)
                {
                    if (columnHeader.ContextMenu == null)
                        continue;

                    itemsContainer = (from object i in columnHeader.ContextMenu.Items
                                      where i is MenuItem && ((MenuItem)i).Header == containerHeader
                                      select i).FirstOrDefault() as MenuItem;
                }

                if (itemsContainer == null)
                    itemsContainer = columnHeader.ContextMenu;

                foreach (object item in itemsContainer.Items)
                {
                    if (item is MenuItem && ((MenuItem)item).Tag != null && ((MenuItem)item).Tag is DataGridColumn
                        && ((MenuItem)item).Header.ToString() == GetColumnName(column))
                    {
                        ((MenuItem)item).IsChecked = isChecked;
                    }
                }
            }
        }

        private static void SetupColumnHeader(DataGridColumnHeader columnHeader)
        {
            if (columnHeader == null)
                return;

            DataGrid dataGrid = CustomVisualTreeHelper<DataGrid>.FindAncestor(columnHeader);
            if (dataGrid == null)
                return;

            DataGridColumnHeader[] columnHeaders = GetAllVisibleColumnHeaders(dataGrid);
            if (columnHeaders == null)
                return;

            SetupColumnHeader(dataGrid, columnHeaders, columnHeader);
        }

        private static void SetupColumnHeader(DataGrid dataGrid, DataGridColumnHeader[] columnHeaders, DataGridColumnHeader columnHeader)
        {
            if (columnHeader.ContextMenu == null)
                columnHeader.ContextMenu = new ContextMenu();

            ItemsControl itemsContainer = null;
            itemsContainer = columnHeader.ContextMenu;

            object containerHeader = GetHideColumnsHeader(dataGrid);
            if (containerHeader != null)
            {
                MenuItem ic = (from object i in columnHeader.ContextMenu.Items
                               where i is MenuItem && ((MenuItem)i).Tag != null && ((MenuItem)i).Tag.ToString() == "ItemsContainer"
                               select i).FirstOrDefault() as MenuItem;

                if (ic == null)
                {
                    itemsContainer = new MenuItem()
                    {
                        Header = containerHeader,
                        HeaderTemplate = GetHideColumnsHeaderTemplate(dataGrid) as DataTemplate,
                        Icon = GetHideColumnsIcon(dataGrid),
                        Tag = "ItemsContainer"
                    };
                    columnHeader.ContextMenu.Items.Add(itemsContainer);
                }
                else
                    return;
            }

            foreach (DataGridColumnHeader columnHeader2 in columnHeaders)
            {
                if (columnHeader2 != columnHeader
                    && itemsContainer is ContextMenu
                    && columnHeader2.ContextMenu == itemsContainer)
                {
                    continue;
                }
                itemsContainer.Items.Add(GenerateItem(dataGrid, columnHeader2.Column));
            }
        }

        public static bool SetupColumnHeaders(DataGrid dataGrid)
        {
            DataGridColumnHeader[] columnHeaders = GetAllVisibleColumnHeaders(dataGrid);
            if (columnHeaders == null || columnHeaders.Count() == 0)
                return false;

            RemoveAllItems(dataGrid);
            columnHeaders = GetAllVisibleColumnHeaders(dataGrid);
            foreach (DataGridColumnHeader columnHeader in columnHeaders)
            {
                SetupColumnHeader(dataGrid, columnHeaders, columnHeader);
            }

            return true;
        }

        /// <summary>
        /// Shows a column within the datagrid, which is not straightforward
        /// because the datagrid not only hides a column when you tell it to
        /// do so, but it also completely destroys its associated column
        /// header. Meaning we need to set it up again. Before we can do
        /// so we have to turn all columns back on again so we can get a
        /// complete list of their column headers, then turn them back off
        /// again.
        /// </summary>
        /// <param name="dataGrid"></param>
        /// <param name="column"></param>
        private static void ShowColumn(DataGrid dataGrid, DataGridColumn column)
        {
            if (dataGrid == null || column == null)
                return;

            column.Visibility = Visibility.Visible;

            // Turn all columns on, but store their original visibility so we
            // can restore it after we're done.
            Dictionary<DataGridColumn, Visibility> vis = new Dictionary<DataGridColumn, Visibility>();
            foreach (DataGridColumn c in dataGrid.Columns)
            {
                vis.Add(c, c.Visibility);
                c.Visibility = Visibility.Visible;
            }
            dataGrid.UpdateLayout();

            DataGridColumnHeader columnHeader = GetColumnHeaderFromColumn(dataGrid, column);
            SetupColumnHeader(columnHeader);

            foreach (DataGridColumn c in vis.Keys)
            {
                if ((Visibility)vis[c] != Visibility.Visible)
                {
                    c.Visibility = (Visibility)vis[c];
                }
            }
            dataGrid.UpdateLayout();

            // Now we need to uncheck items that are associated with hidden
            // columns.
            SyncItemsOnColumnHeader(columnHeader);
        }

        private static void SyncItemsOnColumnHeader(DataGridColumnHeader columnHeader)
        {
            bool isVisible;
            foreach (MenuItem item in GetAttachedItems(columnHeader))
            {
                if (item.Tag is DataGridColumn)
                {
                    isVisible = ((DataGridColumn)item.Tag).Visibility == Visibility.Visible ? true : false;
                    if (item.IsChecked != isVisible)
                    {
                        item.IsChecked = isVisible;
                    }
                }
            }
        }
        #endregion CanUserHideColumns

        #region CustomVisualTreeHelper
        private static class CustomVisualTreeHelper<TReturn> where TReturn : DependencyObject
        {
            public static TReturn FindAncestor(DependencyObject descendant)
            {
                DependencyObject parent = descendant;
                while (parent != null && !(parent is TReturn))
                {
                    parent = VisualTreeHelper.GetParent(parent);
                }

                if (parent != null)
                {
                    return (TReturn)parent;
                }
                return default(TReturn);
            }

            public static TReturn FindChild(DependencyObject parent)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(parent);
                DependencyObject child = null;

                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    child = VisualTreeHelper.GetChild(parent, childIndex);
                    if (child is TReturn)
                    {
                        return (TReturn)(object)child;
                    }
                }
                return default(TReturn);
            }

            public static TReturn FindChildRecursive(DependencyObject parent)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(parent);
                DependencyObject child = null;

                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    child = VisualTreeHelper.GetChild(parent, childIndex);
                    if (child is TReturn)
                    {
                        return (TReturn)(object)child;
                    }
                    else
                    {
                        child = CustomVisualTreeHelper<TReturn>.FindChildRecursive(child);
                        if (child is TReturn)
                        {
                            return (TReturn)(object)child;
                        }
                    }
                }
                return default(TReturn);
            }

            public static TReturn[] FindChildren(DependencyObject parent)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(parent);
                DependencyObject child = null;
                List<TReturn> children = new List<TReturn>(childCount);

                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    child = VisualTreeHelper.GetChild(parent, childIndex);
                    if (child is TReturn)
                    {
                        children[childIndex] = (TReturn)(object)child;
                    }
                }
                return children.ToArray();
            }

            public static TReturn[] FindChildrenRecursive(DependencyObject parent)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(parent);
                DependencyObject child = null;
                List<TReturn> children = new List<TReturn>();

                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    child = VisualTreeHelper.GetChild(parent, childIndex);
                    if (child is TReturn)
                    {
                        children.Add((TReturn)(object)child);
                    }

                    children.AddRange(CustomVisualTreeHelper<TReturn>.FindChildrenRecursive(child));
                }
                return children.ToArray();
            }
        }
        #endregion CustomVisualTreeHelper
        #endregion HideColumns
    }
}

EnableColumnVirtualization Limitation

If you set EnableColumnVirtualization to True on the DataGrid, the code presented here will not work correctly. This is because at the time this code is executed the DataGridColumnHeader object for a particular column may have not yet been created, which is the whole point of column virtualization. At the moment I haven’t found a way to be notified when a column header has been created and therefore I can’t setup the column header when EnableColumnVirtualization is set to True, so don’t do so when using this code; EnableRowVirtualization works fine.

Sample Project Download

Click the link below to download the sample project. Once you have the file rename the file extension to just .zip without the underscore character on the end.
http://blogfilehost.webs.com/CanUserHideColumnsDemo_v2.zip_
Dropbox link

, , , , , , , , , , , , ,

  1. #1 by Geoff on June 22, 2017 - 4:36 pm

    This was super-helpful and exactly the functionality I was looking for! Two interesting snags with it though –

    1) It doesn’t appear that you can have a column that starts hidden, but can be unhidden by the user. You added the Visibility=”Hidden” attribute to a column, but then it doesn’t appear in the context menu at all. As far as I can tell, you need to have every column visible on launch?

    2) If you unselect every column, you can’t access the context menu anymore, so you’re kind of stuck at that point.. ?

    • #2 by Tim Valentine on June 22, 2017 - 8:14 pm

      havent used this or wpf in a few years but:

      1) cant remember to be honest, sounds like something i would have added though

      2) i noticed that in the sample download i have that piece commented out in the SetItemIsChecked on line 298. i think the issue was that it would prevent user from hiding the last visible column but the checkbox would still toggle and get out of sync. the fix would probably be to have SetItemIsChecked return a boolean of false if the user isnt allowed to toggle that column or true if they can. then in the event handler for the checkbox use that return value to set the checkbox correctly in case SetItemIsChecked returns false. that way checkbox stays in sync.

      hope that helps, if you happen to create any fixes post them here for everybody to benefit, thanks

      • #3 by Geoff on June 23, 2017 - 10:07 am

        Ah – that was really helpful!

        I’ve got a fix for removing the last column now that you gave me a hint on where to look in the code. My solution’s a bit different than your, when it updates the list of checkboxes, if only 1 item is checked, it also marks that item as IsEnabled=False so you can’t uncheck it. If you check another column, it enables it again.

        While testing that, I noticed that if I start with say 4 visible and 4 hidden columns, then the context menu only has the 4 visible columns initially. However, when you remove and re-add a column, it has all 8 columns after that .. so now I’m looking to see if I can figure out why that is so I can patch both of these things up.

        Geoff

        • #4 by Geoff on June 23, 2017 - 1:10 pm

          Ok – I’ve got a version that fixes both bugs. The missing hidden columns is interesting, it’s because the ContextMenu was build by doing a recursive search for DataGridColumnHeaders, but when the column is hidden, those objects aren’t created. This was fixed by using the columns to build those headers instead, which eliminated the problem (and eliminated quite a few uses of reflection).

          I can send a new version of DataGripAPs.cs, which is the only file I modified .. but I’m not really sure how/where I can post that?

          Geoff

          • #5 by Geoff on June 27, 2017 - 9:57 pm

            Hmm, I’ve found a third bug I can’t seem to fix. If in the xaml source for the DataGrid, you have a column that is sorted by default (using say SortDirection=”Ascending”), then this gets wiped out and reset to null when this behaviour is bound to the control.

            I’ve spent a few hours trying to figure this out, and I really have no clue how to fix it.. It seems to get overwritten in the dataGrid_Loaded(..) function when it calls BindingOperations.ClearBinding() – however, I don’t really understand what this does, and I have no idea what to do to fix it.

            I eventually found a hack that half-works – the behaviour is ok if you pre-sort the dataset in the DataGrid, but it doesn’t actually trigger a sort like it’s supposed to. A real fix is really beyond me though..

            I’m happy to upload my patched DataGripAPs.cs with the 2 working fixes and this hack.. maybe someone who knows WPF better can properly fix the third case?

            Geoff

  2. #6 by Tim Valentine on October 6, 2016 - 6:24 pm

    Hi everyone! I uploaded the project download to dropbox: https://www.dropbox.com/s/0rmjuheoaj8ovu2/CanUserHideColumnsDemo_v2.zip_?dl=0

  3. #7 by Brian Catlin on October 6, 2016 - 5:23 pm

    This site is frozen. How can I get a copy of the sample project?

  4. #8 by Anonymous on May 7, 2016 - 7:00 am

    please reupload “Site is frozen”

  5. #9 by Maria on May 3, 2016 - 10:36 am

    Could you please reload sample project, cause link does not work now.

  6. #10 by Ralf on February 26, 2016 - 8:20 am

    Hi Tim!

    The download link is broken. Can you provide a new download opportunity?

  7. #11 by Ralf on February 26, 2016 - 8:16 am

    Hi Tim,
    the download link for your sample code does not work anymore. Could you provide a new download?

  8. #12 by John on February 4, 2016 - 6:35 pm

    Can someone repost this example, the link is broken..

  9. #13 by Anonymous on April 2, 2015 - 6:11 am

    Great work !
    I’ve done some changes to manage hidden columns, by displaying the columns before constructing the context Menu and hiding them after
    Your work is very useful, can i have your permission to push it on MahApps project ?
    http://mahapps.com/

    • #14 by Tim Valentine on April 8, 2015 - 10:27 am

      Glad this was useful for you! Feel free to use any of the source code for your project, looks interesting.

  10. #15 by Mateusz on September 10, 2012 - 4:44 am

    I have a problem with your code. When I change items source for my datagrid when my application is working, the right-click works only for the first time – the first source is displayed. When I change it it doesn’t work. Could you please help me with that?

    • #16 by Mateusz on September 10, 2012 - 5:00 am

      Problem fixed – used ResetupColumnHeaders(MyDataGrid) after every new binding.

  11. #17 by jobe83 on April 10, 2012 - 10:27 am

    How can I adapt this to hide specific columns on load and then have a button (not context menu) to show them. Figure I have 3 sources with similar information but source 1 is the Master source. Source 2 and 3 is complementary and should be in expandable columns next to the “master” column, meaning that on load columns containing source 2 and 3 should always be hidden but option to be set visible with a toggle button (or similar) from source 1 column header.

  12. #18 by Tim Valentine on September 14, 2011 - 8:35 pm

    The reset button is to test the scenario of the ItemsSource property on the DataGrid being changed to a new collection. The reason the DataGrids that specified their columns manually didn’t show all their columns when the rest button was clicked, is because all columns need to be visible before it can be setup a context menu-item. So, a quick fix is to change to the method below:

    public static void ResetupColumnHeaders(DataGrid dataGrid)
    {
    foreach (DataGridColumn c in dataGrid.Columns)
    {
    c.Visibility = Visibility.Visible;
    }
    RemoveAllItems(dataGrid);
    SetupColumnHeaders(dataGrid);
    }

    Just add the foreach loop to turn on all the columns before setting them up. I updated the code on this post with the changes, but it will be a few days before I update the download link, which will most likely be VS2010 solution because that’s what I’m using now. In addition to fixing the issue you mentioned I also made it so the last visible column will not hide, that way the user never ends up with no columns (which would prevent them from turning any back on).

  13. #19 by Sancio on September 14, 2011 - 5:23 am

    Why does the Reset button don’t work for datagrid 3 and datagrid 4 (so for the datagrid’s with manually created columns). After i hide a column and press the Reset button the column not just don’t appear but i can not find it in the context menu, it simply dissappear…
    Please help me with this. Thank you for your time

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

%d bloggers like this: