WPF: DataGrid with integrated field (column) chooser

following a discussion in the comments of this post
this is the WPF version of a DataGrid control with integrated columns chooser that opens when right-clicking one of the headers.
(see Silverlight demo)
full source code over here

this is how it look like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WPF_DataGridWithColumnChooser.Controls
{
    public class DataGridExtended : DataGrid
    {
        public DataGridExtended()
            : base()
        {
            theContextMenu = new ContextMenu();
            this.Columns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Columns_CollectionChanged);
        }

        private ContextMenu theContextMenu; //context menu for the field chooser.
        private string AllColumnsHeaders { get; set; }
        private bool oneTime = true;

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (oneTime)// do this only once.
            {
                oneTime = false;
                var headersPresenter = FindChild(this);
                // attach the context menu.
                ContextMenuService.SetContextMenu(headersPresenter, theContextMenu);

                // update VisibleColumns as necessary.
                if (String.IsNullOrEmpty(VisibleColumns))
                {
                    VisibleColumns = AllColumnsHeaders;
                }
                else
                {
                    string s = VisibleColumns;
                    VisibleColumns = null;
                    VisibleColumns = s;
                }
            }
            return base.ArrangeOverride(finalSize);
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            Image icon = (Image)mi.Icon;
            List splited = VisibleColumns.Split(';').ToList();
            string colName = mi.Header.ToString();

            // remove empty items.
            for (int i = 0; i  1)
                {
                    splited.Remove(colName);
                }
            }

            // update the VisibleColumns.
            string build = "";
            foreach (string name in splited)
            {
                build = string.Format("{0};{1}", name, build);
            }
            VisibleColumns = build;
        }

        private void Columns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            DataGridColumn col = e.NewItems[0] as DataGridColumn;

            // keep a list of all clomuns headers for later use.
            AllColumnsHeaders = String.Format("{0};{1}", col.Header.ToString(), AllColumnsHeaders);

            // make a new menu item and add it to the context menu.
            MenuItem menuItem = new MenuItem();
            Image img;
            menuItem.Click += new RoutedEventHandler(MenuItem_Click);
            menuItem.Header = col.Header.ToString();
            img = new Image();
            img.Source = Application.Current.Resources["Vmark"] as ImageSource;
            menuItem.Icon = img;
            theContextMenu.Items.Add(menuItem);

        }

        #region VisibleColumns (DependencyProperty)

        /// <summary>
        /// Gets or sets a value indicating the names of columns (as they appear in the column header) to be visible, seperated by a semicolon.
        /// columns that whose name is not here will be hidden.
        /// </summary>
        public string VisibleColumns
        {
            get { return (string)GetValue(VisibleColumnsProperty); }
            set { SetValue(VisibleColumnsProperty, value); }
        }

        public static readonly DependencyProperty VisibleColumnsProperty =
            DependencyProperty.Register(
                "VisibleColumns",
                typeof(string),
                typeof(DataGridExtended),
                new PropertyMetadata("", new PropertyChangedCallback(OnVisibleColumnsChanged))
                );

        private static void OnVisibleColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGridExtended dg = (DataGridExtended)d;
            dg.VisibleColumnsChanged(e);

        }

        private void VisibleColumnsChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                string[] showTheseColumns = e.NewValue.ToString().Split(';');
                string colName;

                // update the columns visibility.
                foreach (DataGridColumn col in this.Columns)
                {
                    colName = col.Header.ToString();

                    if (showTheseColumns.Contains(colName))
                        col.Visibility = Visibility.Visible;
                    else
                        col.Visibility = Visibility.Collapsed;
                }

                // update the context menu items.
                if (theContextMenu != null)
                {
                    foreach (MenuItem menuItem in theContextMenu.Items)
                    {
                        colName = menuItem.Header.ToString();
                        if (showTheseColumns.Contains(colName))
                            ((Image)menuItem.Icon).Visibility = Visibility.Visible;
                        else
                            ((Image)menuItem.Icon).Visibility = Visibility.Collapsed;
                    }
                }
            }
        }

        #endregion

        public static T FindChild(DependencyObject depObj) where T : DependencyObject
        {
            // Confirm obj is valid.
            if (depObj == null) return null;

            // success case
            if (depObj is T)
                return depObj as T;

            for (int i = 0; i &lt; VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);

                T obj = FindChild(child);

                if (obj != null)
                    return obj;
            }

            return null;
        }
    }

}
About these ads

15 Responses to WPF: DataGrid with integrated field (column) chooser

  1. danies8 says:

    Your FindChild not complied,
    Couid you please send a full example ?

  2. danies8 says:

    Hi,

    Why when I used this link :

    http://blog.smoura.com/wpf-toolkit-datagrid-part-iii-playing-with-datagridcolumns-and-datagridcells/

    and use this sample code
    WPF Toolkit DataGrid Sample 11 – AutoCommitCheckBoxColumn

    and later add your code the styles disapper (:-

    Thanks,
    Daniel Shitrit

  3. danies8 says:

    The solution:
    ==========
    Add this sniped XAML in DataGrid.Generic.xaml loacted in Themes folder:
    DataGridExtended Style based on WpfToolkit:DataGrid
    Remark , there is important that this style will follow WpfToolkit:DataGrid style

    and now you can consume DataGridExtended with style integated with column chooser.
    -:)

  4. danies8 says:

    Why do the AllColumnsHeaders include the colums header info in reverse order? wjat is the moto ?

  5. danies8 says:

    Hi Shemesh,

    Do you have an example how to save/restore layout of Datagrid Toolkit ?

    Thanks,

  6. danies8 says:

    One more question :
    Does else needed ?

    // Update VisibleColumns as necessary.
    if (String.IsNullOrEmpty(VisibleColumns))
    VisibleColumns = AllColumnsHeaders;
    else
    {
    string s = VisibleColumns;
    VisibleColumns = null;
    VisibleColumns = s;
    }

  7. danies8 says:

    Your feature enforce users to use it and it does not give an
    option to choose in this grid tea in the other not
    => explose property that proggrammer will choose to chhose or not

    What do you say ?

  8. bpoojary says:

    Found good column choosser
    Column Chooser

    Project Description
    WPF Datagrid currently dosent have column chooser.
    In this column chooser you can even drag and drop the column to move its position on the grid.
    You can even update header value on the fly just by double clicking on it.

    Features include:

    Column chooser with checkbox to make column visible and invisible.
    Select All checkbox is added.
    You can drag and drop a row from column chooser to move column in the main grid.
    By double clicking on any cell of column chooser you can edit the column header values.
    Column Chooser tool is deveoped in C# Visual Studio 2010 . You can even download the application via click once installation method.

  9. Dexter says:

    Best Implementation Of WPF Extended DataGrid Can be found here

    http://wpfextendeddatagrid.codeplex.com

    Features
    Column Choosers.
    AutoFilter Control.
    Export To Excel Feature.
    Copy Paste To Excel and Copy Paste From Excel To DataGrid.
    Three State Sorting.
    Displaying Sort Order If Multiple Sort is done.
    Export To Excel with formatting.
    Export To PDF with formatting.

  10. Andries says:

    There is a quicker and better way to do this.
    Override the default style of a DataGrid and set the ContextMenu property with the following XAML:

    One more thing.. You need a VisiblityToInverseBooleanConverter.
    Like this:

    [Localizability(LocalizationCategory.NeverLocalize)]
    public sealed class VisiblityToInverseBooleanConverter : IValueConverter
    {
    // Methods
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
    return ((value is Visibility) && (((Visibility)value) != Visibility.Collapsed));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
    bool flag = false;
    if (value != null)
    {
    Type boolType = value.GetType();

    if (boolType == typeof(bool))
    {
    flag = (bool)value;
    }
    else if (boolType == typeof(bool?))
    {
    bool? nullable = (bool?)value;
    flag = nullable.HasValue ? nullable.Value : false;
    }
    }
    else
    {
    flag = true;
    }
    return (flag ? Visibility.Visible : Visibility.Collapsed);
    }
    }

  11. Andries says:

    The following XAML: (the replier removed it…)
     <Setter Property=”ContextMenu”>
    <Setter.Value>
    <ContextMenu DataContext=”{Binding Path=PlacementTarget,
    RelativeSource={RelativeSource Self}}” ItemsSource=”{Binding Columns}” >
    <ContextMenu.ItemContainerStyle>
    <Style TargetType=”MenuItem”>
    <Setter Property=”Header” Value=”{Binding Header}”/>
    <Setter Property=”IsCheckable” Value=”True” />
    <Setter Property=”IsChecked” Value=”{Binding Visibility, Mode=TwoWay,
    Converter={StaticResource VisiblityToInverseBooleanConverter}}” />
    </Style>
    </ContextMenu.ItemContainerStyle>
    </ContextMenu>
    </Setter.Value>
    </Setter>

  12. Dheeraj says:

    +1 Nice One.

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: