WPF: DataGrid with integrated field (column) chooser
August 2, 2011 15 Comments
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 < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
T obj = FindChild(child);
if (obj != null)
return obj;
}
return null;
}
}
}


Your FindChild not complied,
Couid you please send a full example ?
uploaded the source code, see the link in the post.
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
i can only guess that the latest is overriding the styles of the first.
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.
-:)
Why do the AllColumnsHeaders include the colums header info in reverse order? wjat is the moto ?
Hi Shemesh,
Do you have an example how to save/restore layout of Datagrid Toolkit ?
Thanks,
One more question :
Does else needed ?
// Update VisibleColumns as necessary.
if (String.IsNullOrEmpty(VisibleColumns))
VisibleColumns = AllColumnsHeaders;
else
{
string s = VisibleColumns;
VisibleColumns = null;
VisibleColumns = s;
}
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 ?
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.
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.
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);
}
}
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>
Nice work!!
+1 Nice One.