[a new version of the WPF listview is availbale here ]
The WPF Listview does not support enable – disable of columns and not even sorting…. here is a list view that supports these features
The IDataGridViewPresenter is an inetrafce that a objects need to implement in order to use the DatGridView
I am going to post a new version of this control soon that can auto generate the column for the grid view by reflection
#region Interface to implement for use of the Listview
/// <summary>
/// Identifies if the object is of the same family
/// </summary>
public interface IDataGridViewPresenter
{
/// <summary>
/// Unique Id to represnt the object identity
/// </summary>
string UniqueId { get; }
/// <summary>
/// Gets and sets a list of columns that represent the resource
/// </summary>
ICollection<GridViewColumn> Columns { get; }
/// <summary>
/// Loads the columns that represent the object
/// </summary>
void InitializeColumns();
}
#endregion
/// <summary>
/// Enum to state the action for the Coulmn State
/// </summary>
public enum ColumnStateChangeAction
{
/// <summary>
/// columns is enabled
/// </summary>
Enabled,
/// <summary>
/// columns is disabled
/// </summary>
Disabled
}
/// <summary>
/// Event arguments for the ColumnStateChanged event
/// </summary>
public class ColumnStateChangedEventArgs : EventArgs
{
private ColumnStateChangeAction action;
/// <summary>
/// The action for the event
/// </summary>
public ColumnStateChangeAction Action
{
get { return action; }
set { action = value; }
}
private DataGridViewColumn columnChanged;
/// <summary>
/// The column that has changed
/// </summary>
public DataGridViewColumn ColumnChanged
{
get { return columnChanged; }
set { columnChanged = value; }
}
/// <summary>
/// default ctor
/// </summary>
/// <param name=”columnChanged”>The column that has changed</param>
/// <param name=”action”>The action for the event</param>
public ColumnStateChangedEventArgs( DataGridViewColumn columnChanged, ColumnStateChangeAction action )
{
this.action = action;
this.columnChanged = columnChanged;
}
}
/// <summary>
/// Extended list view control that supports dynamic columns
/// This listview also supports enabling and disabling of grid view columns
/// </summary>
public class DataGridView : ListView
{
private Dictionary<string, GridView> typesLoaded = new Dictionary<string, GridView>();
string currentViewId = “”;
PropertyChangedEventHandler gridViewColumnPropertyChanged;
Dictionary<string, DataGridViewSettings> settings = new Dictionary<string, DataGridViewSettings>();
//handler for the sorting
ListViewSortHandler sortHandler;
/// <summary>
/// Default ctor
/// </summary>
public DataGridView()
{
sortHandler = new ListViewSortHandler(this);
gridViewColumnPropertyChanged = new PropertyChangedEventHandler(DataGridViewPropertyChanged);
}
/// <summary>
/// Returns true if there are multiple items selected
/// </summary>
public bool MultipleItemsSelected
{
get { return (bool)GetValue(MultipleItemsSelectedProperty); }
}
/// <summary>
/// Returns true if there are multiple items selected
/// </summary>
public static readonly DependencyProperty MultipleItemsSelectedProperty =
DependencyProperty.Register(“MultipleItemsSelected”, typeof(bool), typeof(DataGridView), new UIPropertyMetadata(false));
/// <summary>
/// override the selection changed to change the multiple select property
/// </summary>
/// <param name=”e”>The event arguments passed</param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
SetValue(MultipleItemsSelectedProperty, (SelectedItems.Count > 1));
base.OnSelectionChanged(e);
}
/// <summary>
/// event raised to notify listeners that a column state has changed
/// </summary>
public event EventHandler<ColumnStateChangedEventArgs> ColumnStateChanged;
/// <summary>
/// raises the ColumnStateChanged event
/// </summary>
/// <param name=”e”>The event arguments for the event</param>
protected virtual void OnColumnStateChanged(ColumnStateChangedEventArgs e)
{
if (ColumnStateChanged != null)
ColumnStateChanged(this, e);
}
//flag to mark the grid view columns as generated for the new item source
private bool hasGridViewBeenSet = false;
/// <summary>
/// Check that the grid view columns where generated for this type if not create them
/// This usually occurs if data comes in late after binding…
/// </summary>
/// <param name=”e”>The arguments for the notification change of the binded collection</param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
//if the columns were not created, create them
if (!hasGridViewBeenSet)
GenerateGridViewColumnsForNewDataType((IList)e.NewItems);
base.OnItemsChanged(e);
}
/// <summary>
/// Repopulate the columns for the listview when the item source changes
/// </summary>
/// <param name=”oldValue”>The old items</param>
/// <param name=”newValue”>The new items</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
GenerateGridViewColumnsForNewDataType((IList)newValue);
base.OnItemsSourceChanged(oldValue, newValue);
sortHandler.DefaultCollectionSource = (IList)newValue;
}
//generate the columns for the grid view
private void GenerateGridViewColumnsForNewDataType(IList newValues)
{
//check if the data passed is valid
if (newValues == null || newValues.Count == 0)
{
//mark the gridview columns as not
hasGridViewBeenSet = false;
return;
}
IDataGridViewPresenter newTypeValue = newValues[0] as IDataGridViewPresenter;
if (newTypeValue == null)
throw new InvalidCastException(“To use the DataGridView you need to implement the IDataGridViewPresenter interface”);
hasGridViewBeenSet = true;
//Generate the grid view columns if needed
GenerateGridColumns(newTypeValue);
}
/// <summary>
/// Generate the view for the current type identty
/// </summary>
/// <param name=”newTypeValue”>The new type identity to set</param>
private void GenerateGridColumns(IDataGridViewPresenter newTypeValue)
{
//Check if the type was already loaded
if (!this.typesLoaded.ContainsKey(newTypeValue.UniqueId))
AddNewType(newTypeValue);
GridView view = this.typesLoaded[newTypeValue.UniqueId];
//get the view for the type
this.View = view;
}
/// <summary>
/// Adds the new type in the hash and generates the columns for the type
/// </summary>
/// <param name=”newTypeValue”>The type to add to the hash</param>
private void AddNewType(IDataGridViewPresenter newTypeValue)
{
newTypeValue.InitializeColumns();
UnregisterPreviusColumns();
GridView newView = new GridView();
//important first we need to register all events.
foreach (GridViewColumn column in newTypeValue.Columns)
{
((INotifyPropertyChanged)column).PropertyChanged += gridViewColumnPropertyChanged;
newView.Columns.Add(column);
}
this.typesLoaded[newTypeValue.UniqueId] = newView;
}
/// <summary>
/// Unregister all event handlers for the previous columns
/// </summary>
private void UnregisterPreviusColumns()
{
if (this.View == null)
return;
GridView currentView = this.View as GridView;
if (currentView != null)
{
foreach (GridViewColumn column in currentView.Columns)
((INotifyPropertyChanged)column).PropertyChanged -= gridViewColumnPropertyChanged;
}
}
//event handler for the property changed event for the GridViewColumn
void DataGridViewPropertyChanged(object sender, PropertyChangedEventArgs e)
{
DataGridViewColumn column = (DataGridViewColumn)sender;
if (DataGridViewColumn.IsEnabledPropertyChanged(e))
{
OnColumnStateChanged(new ColumnStateChangedEventArgs(
column, DataGridViewColumn.GetActionFromPropertyChanged(column, e)
));
}
}
#region Sorting
/// <summary>
/// Template for the column header to sort in asc order
/// </summary>
public string ColumnHeaderSortedAscendingTemplate
{
get { return sortHandler.ColumnHeaderSortedAscendingTemplate; }
set { sortHandler.ColumnHeaderSortedAscendingTemplate = value; }
}
/// <summary>
/// The template for the column header for sorting in desc order
/// </summary>
public string ColumnHeaderSortedDescendingTemplate
{
get { return sortHandler.ColumnHeaderSortedDescendingTemplate; }
set { sortHandler.ColumnHeaderSortedDescendingTemplate = value; }
}
/// <summary>
/// Template for the column header when it is not being sorted
/// </summary>
public string ColumnHeaderNotSortedTemplate
{
get { return sortHandler.ColumnHeaderNotSortedTemplate; }
set { sortHandler.ColumnHeaderNotSortedTemplate = value; }
}
//sorts the grid view by a specified column
private void ApplySort(SortableGridViewColumn sortableGridViewColumn)
{
sortHandler.ApplySort(sortableGridViewColumn);
this.settings[currentViewId].SetSortDetails((DataGridViewColumn)sortHandler.LastSortedOnColumn); //save the sort
}
#endregion
}
/// <summary>
/// Grid view column that can sort
/// </summary>
public class SortableGridViewColumn : GridViewColumn
{
/// <summary>
/// default ctor
/// </summary>
public SortableGridViewColumn()
{
this.sortDirection = ListSortDirection.Ascending;
}
private ListSortDirection sortDirection;
/// <summary>
/// Gets or sets the current sort direction
/// </summary>
public ListSortDirection SortDirection
{
get { return sortDirection; }
}
/// <summary>
/// Switches the sort direction
/// </summary>
public void SetSortDirection()
{
this.sortDirection = this.sortDirection == ListSortDirection.Ascending ?
ListSortDirection.Descending : ListSortDirection.Ascending;
}
/// <summary>
/// The property name of the column to be sorted
/// </summary>
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
/// <summary>
/// The property name of the column to be sorted
/// </summary>
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register(“SortPropertyName”, typeof(string), typeof(DataGridViewColumn), new UIPropertyMetadata(“”));
}
/// <summary>
/// Extends the GridViewColumn to include some extra features
/// </summary>
public class DataGridViewColumn : SortableGridViewColumn
{
double lastWidth = 0;
const double defaultWidth = 50;
/// <summary>
/// Default ctor
/// </summary>
public DataGridViewColumn()
{
SetLastWidth();
}
//sets the last width
private void SetLastWidth()
{
this.lastWidth = (double.IsNaN(Width) || Width == 0.0)
? (ActualWidth == 0.0 ? defaultWidth : ActualWidth) : Width;
}
private object tag;
/// <summary>
/// Gets or sets the tag for this object
/// </summary>
public object Tag
{
get { return tag; }
set { tag = value; }
}
/// <summary>
/// a dependancy property to know whether to display the column in the ListView
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.Register(“IsEnabled”, typeof(bool), typeof(DataGridViewColumn), new PropertyMetadata(true, new PropertyChangedCallback(propertyChanged)));
//raises the property changed event
static void propertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridViewColumn column = d as DataGridViewColumn;
column.HideShowColumn((bool)e.NewValue);
column.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(e.Property.Name));
}
/// <summary>
/// Dependancy property to know whether to display the column in the ListView
/// </summary>
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set
{
//don’t set the value if it is the same. this is important since on startup it could set the width as 0 for the columns
if (value == IsEnabled)
return;
SetValue(IsEnabledProperty, value);
HideShowColumn(value);
OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(“IsEnabled”));
}
}
//hides / shows the gridview column
private void HideShowColumn(bool enabled)
{
if (enabled)
this.Width = lastWidth;
else
{
SetLastWidth();
this.Width = 0;
}
}
/// <summary>
/// Checks if the property changed is the IsEnabled propety
/// </summary>
/// <param name=”e”>The event arguments to verify</param>
/// <returns>Returns true if the property is the IsEnabled</returns>
public static bool IsEnabledPropertyChanged(PropertyChangedEventArgs e)
{
if (e == null)
return false;
return e.PropertyName == “IsEnabled”;
}
/// <summary>
/// Returns a ColumnStateChangeAction that describes the property changed event
/// </summary>
/// <param name=”sender”>The DataGridViewColumn that has raised the event</param>
/// <param name=”e”>The event argmunets to parse</param>
/// <returns>Returns a ColumnStateChangeAction to represent the change of state</returns>
/// <exception cref=”ArgumentNullException”>Throws ArgumentNullException if the sender or the arguments are not valid</exception>
public static ColumnStateChangeAction GetActionFromPropertyChanged(DataGridViewColumn sender, PropertyChangedEventArgs e)
{
if (sender != null && IsEnabledPropertyChanged(e))
return sender.IsEnabled ? ColumnStateChangeAction.Enabled : ColumnStateChangeAction.Disabled;
throw new ArgumentNullException(“e”, “Invalid PropertyChangedEventArgs passed”);
}
/// <summary>
/// Gets the identifier for a specific column
/// </summary>
/// <param name=”column”>The column to get the identifier from</param>
/// <returns>Returns an identifier for the column</returns>
public static string GetColumnIdentifier(GridViewColumn column)
{
return column == null ? “” : column.Header.ToString();
}
}
/// <summary>
/// Base class for comparers that need to be used for the sorting of a control
/// </summary>
public abstract class ListViewCustomComparer : DependencyObject, IComparer
{
private bool passFullObjectForComparision = false;
/// <summary>
/// Set to true if you want the CompareOverride method to pass the full object to compare
/// Pass false if you want to pass the value of the property to compare
/// </summary>
protected bool PassFullObjectForComparision
{
get { return passFullObjectForComparision; }
set { passFullObjectForComparision = value; }
}
/// <summary>
/// Default constructor
/// </summary>
/// <param name=”passFullObjectForComparision”>Set to true if you want the CompareOverride method to pass the full object to compare
/// Pass false if you want to pass the value of the property to compare</param>
public ListViewCustomComparer(bool passFullObjectForComparision)
{
this.passFullObjectForComparision = passFullObjectForComparision;
}
private ListSortDirection direction;
/// <summary>
/// Gets or set the direction for sorting
/// </summary>
public ListSortDirection Direction
{
get { return direction; }
set { direction = value; }
}
private string sortPropertyName;
/// <summary>
/// The name of the property to sort
/// </summary>
public string SortPropertyName
{
get { return sortPropertyName; }
set
{
sortPropertyName = value;
}
}
#region IComparer Members
/// <summary>
/// The comparison for the class
/// </summary>
/// <param name=”x”>The object value 1</param>
/// <param name=”y”>The object value 2</param>
/// <returns>Returns a number taht determines the order to sort</returns>
public int Compare(object x, object y)
{
if (passFullObjectForComparision)
return CompareOverride(x, y);
//get the actual values of the property to compare
BindingOperations.ClearAllBindings(this);
Binding bindForX = new Binding(SortPropertyName);
bindForX.Source = x;
BindingOperations.SetBinding(this, ValueForXProperty, bindForX);
Binding bindForY = new Binding(SortPropertyName);
bindForY.Source = y;
BindingOperations.SetBinding(this, ValueForYProperty, bindForY);
object valueX = this.GetValue(ValueForXProperty);
object valueY = this.GetValue(ValueForYProperty);
return CompareOverride(valueX, valueY);
}
//dependency property used for getting the value of the object x
private static readonly DependencyProperty ValueForXProperty =
DependencyProperty.Register(“ValueForX”, typeof(object), typeof(ListViewCustomComparer));
//dependency property used for getting the value of the object y
private static readonly DependencyProperty ValueForYProperty =
DependencyProperty.Register(“ValueForY”, typeof(object), typeof(ListViewCustomComparer));
/// <summary>
/// The comparison for the class
/// </summary>
/// <param name=”x”>The object value 1</param>
/// <param name=”y”>The object value 2</param>
/// <returns>Returns a number taht determines the order to sort</returns>
public abstract int CompareOverride(object x, object y);
#endregion
}
/// <summary>
/// Default comparer that can only work with properties that implement the IComparable interface or the IComparer interface
/// </summary>
public class DefaultListViewComparer : ListViewCustomComparer
{
/// <summary>
/// Pass true when using the comparer with simple binding
/// Pass false when using complex binding for properties (ex MyProp[test])
/// </summary>
public bool UseSimpleBinding
{
get { return base.PassFullObjectForComparision; }
set { base.PassFullObjectForComparision = value; }
}
/// <summary>
/// default constructor
/// </summary>
public DefaultListViewComparer() : base(false) { }
/// <summary>
/// The comparison for the class
/// </summary>
/// <param name=”x”>The object value 1</param>
/// <param name=”y”>The object value 2</param>
/// <returns>Returns a number taht determines the order to sort</returns>
/// <exception cref=”ArgumentException”>Raised when one of the properties to compare does not implement the IComparer or the IComparable</exception>
public override int CompareOverride(object x, object y)
{
if (UseSimpleBinding)
{
//get property value
PropertyInfo prop = x.GetType().GetProperty(SortPropertyName);
x = prop.GetValue(x, null);
y = prop.GetValue(y, null);
}
int result = 0;
IComparer comparer = x as IComparer;
if (comparer != null)
{
result = comparer.Compare(x, y);
}
else
{
IComparable comparable = x as IComparable;
if (comparable != null)
result = comparable.CompareTo(y);
else
throw new ArgumentException(“Invalid parameteres passed. Property must implement IComparer or IComparable”,
String.Format(“Property Name – {0}”, SortPropertyName));
}
if (Direction == System.ComponentModel.ListSortDirection.Ascending)
return result;
else
return (-1) * result;
}
}
/// <summary>
/// object to make all the sorting functionality for the Listview
/// </summary>
public class ListViewSortHandler
{
/// <summary>
/// Attached property for a FrameworkElement to specify the use of the default comparer
/// </summary>
public readonly static DependencyProperty UseDefaultComparerProperty = DependencyProperty.RegisterAttached(
“UseDefaultComparer”, typeof(bool), typeof(ListViewSortHandler),
new FrameworkPropertyMetadata(false));
/// <summary>
/// Returns a flag indicating if the element uses the default comparer for sorting
/// </summary>
/// <param name=”obj”>The element to check</param>
/// <returns>Returns a bool that indicates if the element uses the default comparer to sort</returns>
public static bool GetUseDefaultComparer(DependencyObject obj)
{
return (bool)obj.GetValue(UseDefaultComparerProperty);
}
/// <summary>
/// Sets a flag indicating if the element uses the default comparer for sorting
/// </summary>
/// <param name=”obj”>The element to store the value</param>
/// <param name=”value”>The value to store</param>
public static void SetUseDefaultComparer(DependencyObject obj, bool value)
{
obj.SetValue(UseDefaultComparerProperty, value);
}
string columnHeaderSortedAscendingTemplate;
/// <summary>
/// Template for the column header to sort in asc order
/// </summary>
public string ColumnHeaderSortedAscendingTemplate
{
get { return columnHeaderSortedAscendingTemplate; }
set { columnHeaderSortedAscendingTemplate = value; }
}
string columnHeaderSortedDescendingTemplate;
/// <summary>
/// The template for the column header for sorting in desc order
/// </summary>
public string ColumnHeaderSortedDescendingTemplate
{
get { return columnHeaderSortedDescendingTemplate; }
set { columnHeaderSortedDescendingTemplate = value; }
}
string columnHeaderNotSortedTemplate;
/// <summary>
/// Template for the column header when it is not being sorted
/// </summary>
public string ColumnHeaderNotSortedTemplate
{
get { return columnHeaderNotSortedTemplate; }
set { columnHeaderNotSortedTemplate = value; }
}
//the parent control using the sort handler
FrameworkElement parentControl;
//the collection view to apply the sort upon
ListCollectionView defaultCollectionView;
/// <summary>
/// the collection view to apply the sort upon
/// </summary>
public System.Collections.IList DefaultCollectionSource
{
get { return (System.Collections.IList)defaultCollectionView.SourceCollection; }
set
{
if (value == null)
return;
ICollectionView view = CollectionViewSource.GetDefaultView(value);
ListCollectionView collectionView = view as ListCollectionView;
if (collectionView == null)
{
List<object> o = new List<object>();
foreach (object item in view.SourceCollection)
o.Add(item);
collectionView = new ListCollectionView((System.Collections.IList)o);
}
defaultCollectionView = collectionView;
}
}
/// <summary>
/// Default constructor
/// </summary>
/// <param name=”parentControl”>The parent control using this list view sort handler</param>
public ListViewSortHandler(FrameworkElement parentControl)
{
this.parentControl = parentControl;
parentControl.Initialized += new EventHandler(ParentControlInitialized);
}
//hook to the event to apply sort
void ParentControlInitialized(object sender, EventArgs e)
{
parentControl.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClicked));
}
/// Event Handler for the ColumnHeader Click Event.
private void GridViewColumnHeaderClicked(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
// ensure that we clicked on the column header and not the padding that’s added to fill the space.
if (headerClicked != null && headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = headerClicked.Column as SortableGridViewColumn;
ApplySort(sortableGridViewColumn);
}
}
SortableGridViewColumn lastSortedOnColumn;//stores the last column that was sorted
/// <summary>
/// Returns the last sorted column
/// </summary>
public SortableGridViewColumn LastSortedOnColumn
{
get { return lastSortedOnColumn; }
}
private ListViewCustomComparer comparer;
/// <summary>
/// The comparer to use for the sorting
/// </summary>
public ListViewCustomComparer Comparer
{
get
{
if (comparer == null)
{
if ((bool)parentControl.GetValue(UseDefaultComparerProperty))
comparer = new DefaultListViewComparer();
}
return comparer;
}
set { comparer = value; }
}
/// <summary>
/// sorts the grid view by a specified column
/// </summary>
/// <param name=”sortableGridViewColumn”>The column to sort</param>
public void ApplySort(SortableGridViewColumn sortableGridViewColumn)
{
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
if (Comparer == null) //default sort
{
defaultCollectionView.SortDescriptions.Clear();
SortDescription sd = new SortDescription(sortableGridViewColumn.SortPropertyName, sortableGridViewColumn.SortDirection);
defaultCollectionView.SortDescriptions.Add(sd);
}
else
{
Comparer.Direction = sortableGridViewColumn.SortDirection;
Comparer.SortPropertyName = sortableGridViewColumn.SortPropertyName;
defaultCollectionView.CustomSort = comparer;
}
sortableGridViewColumn.SetSortDirection();//reset the direction
bool newSortColumn = false;
// determine if this is a new sort
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
newSortColumn = true;
}
//apply the templates
if (sortableGridViewColumn.SortDirection == ListSortDirection.Ascending)
{
if (!String.IsNullOrEmpty(this.ColumnHeaderSortedAscendingTemplate))
sortableGridViewColumn.HeaderTemplate = parentControl.TryFindResource(ColumnHeaderSortedAscendingTemplate) as DataTemplate;
else
sortableGridViewColumn.HeaderTemplate = null;
}
else
{
if (!String.IsNullOrEmpty(this.ColumnHeaderSortedDescendingTemplate))
sortableGridViewColumn.HeaderTemplate = parentControl.TryFindResource(ColumnHeaderSortedDescendingTemplate) as DataTemplate;
else
sortableGridViewColumn.HeaderTemplate = null;
}
// Remove arrow from previously sorted header
if (newSortColumn && lastSortedOnColumn != null)
{
if (!String.IsNullOrEmpty(this.ColumnHeaderNotSortedTemplate))
lastSortedOnColumn.HeaderTemplate = parentControl.TryFindResource(ColumnHeaderNotSortedTemplate) as DataTemplate;
else
lastSortedOnColumn.HeaderTemplate = null;
}
lastSortedOnColumn = sortableGridViewColumn;
}
}
}