Silverlight: DataGrid with integrated field (column) chooser
October 11, 2010 13 Comments
Not much to say here, it speaks for itself.
Right click on a column header to open the field (columns) chooser and show/hide columns.
The VisibleColumns property is always up-to-date and can be binded TwoWay, in my case I am saving it to the user preferences for consistency.
Take a look at the code to see how it’s done.
Notice: ContextMenu causes memory leak! http://silverlight.codeplex.com/workitem/7089. as long as you don’t need to remove the DataGrid you are OK.
the WPF version is over here
tried to convert your implemnation to WPF platform.
But, I get a null on the following row (GetTemplateChild) , why ?
DataGridColumnHeadersPresenter headersPresenter = GetTemplateChild(“PART_FillerColumnHeader”)
as DataGridColumnHeadersPresenter;
hard to say without seeing code…
why do you GetTemplateChild ?
#region Using
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
#endregion
namespace MyProject.DataGridToolkit.Controls
{
///
/// DataGrid with integrated field chooser.
/// Took from :https://shemesh.wordpress.com/category/silverlight/
/// Right click on a column header to open the field chooser
/// and show/hide columns.
/// The VisibleColumns property is always up-to-date and can
/// be binded Two ways.
/// Im my case, I’m saving it to user prefereces fo consistency.
///
public class DataGridExtended : DataGrid
{
#region Fields
private ContextMenu _theContextMenu; //context menu for the field chooser.
private bool _oneTime = true;
#endregion
#region Constrctor
public DataGridExtended()
: base()
{
_theContextMenu = new ContextMenu();
Columns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Columns_CollectionChanged);
}
#endregion
#region Properties
#region AllColumnsHeaders
private string AllColumnsHeaders { get; set; }
#endregion
#endregion
#region VisibleColumns (DependencyProperty)
///
/// 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.
///
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
#region Methods
#region ArrangeOverride
///
/// FrameworkElement.ArrangeOverride Method
/// =======================================
/// When overridden in a derived class,
/// positions child elements and determines a size for a FrameworkElement derived class.
///
/// The final area within the parent
/// that this element should use to arrange itself and its children
/// The actual size used
///
/// Notes to Inheritors:
/// Control authors who want to customize the arrange pass of layout processing
/// should override this method.
/// The implementation pattern should call Arrange on each visible child element,
/// and pass the final desired size for each child element as the finalRect parameter.
/// Parent elements should call Arrange on each child,
/// otherwise the child elements will not be rendered.
protected override Size ArrangeOverride(Size finalSize)
{
if (_oneTime)// do this only once.
{
_oneTime = false;
// Attach the context menu.
// DataGridColumnHeadersPresenter headersPresenter = GetTemplateChild(“ColumnHeadersPresenter”) as DataGridColumnHeadersPresenter;
DataGridColumnHeadersPresenter headersPresenter = GetTemplateChild(“PART_FillerColumnHeader”) as DataGridColumnHeadersPresenter;
if (headersPresenter != null)
{
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);
}
#endregion
#endregion
#region Events
#region MenuItem_Click
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;
}
#endregion
#region Columns_CollectionChanged
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 BitmapImage;
menuItem.Icon = img;
_theContextMenu.Items.Add(menuItem);
}
#endregion
#endregion
}
}
Please look on ArrangeOverride method
protected override Size ArrangeOverride(Size finalSize)
what do you need “PART_FillerColumnHeader” for?
For this lines rows:
if (headersPresenter != null)
{
ContextMenuService.SetContextMenu(headersPresenter, _theContextMenu);
…..
ok, i haven’t looked into wpf DataGrid template, your code assuming there is a inner control inside the DataGrid with x:Name of “PART_FillerColumnHeader”. does it?
namespace Microsoft.Windows.Controls.Primitives
{
[TemplatePart(Name = “PART_FillerColumnHeader”, Type = typeof(DataGridColumnHeader))]
public class DataGridColumnHeadersPresenter : ItemsControl
{
public DataGridColumnHeadersPresenter();
i m guessing here, for a real answer i’ll need some time.
your derive from DataGrid therefor you have to look for that part on the DataGrid class, the code here is from some other class
Please look on this sample:
http://www.4shared.com/file/yr5fGtJT/1_online.html
in vs2008
add ref to WPFToolkit
Here is the fix:
I looked on the structure of the grid
and use Visul Tree to find the DataGridColumnHeadersPresenter dp.
And it found using debuuging.
// attach the context menu.
// =========================
// In SL – works
// DataGridColumnHeadersPresenter headersPresenter = GetTemplateChild(“ColumnHeadersPresenter”) as DataGridColumnHeadersPresenter;
// In WPF – does not works
//DataGridColumnHeadersPresenter headersPresenter = GetTemplateChild(“PART_FillerColumnHeader”) as DataGridColumnHeadersPresenter;
New code:
Border border = this.GetVisualChild(0) as Border;
ScrollViewer scrollViewer = border.Child as ScrollViewer;
Grid grid = VisualTreeHelper.GetChild(scrollViewer, 0) as Grid;
UIElementCollection children = grid.Children;
DataGridColumnHeadersPresenter headersPresenter = children[1] as DataGridColumnHeadersPresenter;
If I understood the problem:
GetTemplateChild behave deferently in SL and WPF.
or
A template part doesn’t have to be in the template. It can be omitted in most cases.
i have posted a WPF version of this class over here:
better implementation search by type, not by name or structure.
-:)