GridLength Animation…

WPF Double animation is really nice yet sometime you need animation for GridLength…. Unfortunatly this is not supported in the WPF class library. yet here is the code to do this !!!

It is very simple all you have to do is to inherit from AnimationTimeline and overide the following methods

  • TargetPropertyType
  • CreateInstanceCore
  • GetCurrentValue

Have a look at the code…. sometime C# is more meaningful then english :)

Download demo project


/// <summary>


/// Animates a grid length value just like the DoubleAnimation animates a double value


/// </summary>


public class GridLengthAnimation : AnimationTimeline
{
private bool isCompleted;
/// <summary>


/// Marks the animation as completed


/// </summary>


public bool IsCompleted
{
get { return isCompleted; }
set { isCompleted = value; }
}
/// <summary>


/// Sets the reverse value for the second animation


/// </summary>


public double ReverseValue
{
get { return (double)GetValue(ReverseValueProperty); }
set { SetValue(ReverseValueProperty, value); }
}

/// <summary>


/// Dependency property. Sets the reverse value for the second animation


/// </summary>


public static readonly DependencyProperty ReverseValueProperty =
DependencyProperty.Register(
“ReverseValue”, typeof(double), typeof(GridLengthAnimation), new UIPropertyMetadata(0.0));

/// <summary>


/// Returns the type of object to animate


/// </summary>


public override Type TargetPropertyType
{
get


{
return typeof(GridLength);
}
}
/// <summary>


/// Creates an instance of the animation object


/// </summary>


/// <returns>Returns the instance of the GridLengthAnimation</returns>


protected override System.Windows.Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
/// <summary>


/// Dependency property for the From property


/// </summary>


public static readonly DependencyProperty FromProperty = DependencyProperty.Register(“From”, typeof(GridLength),
typeof(GridLengthAnimation));
/// <summary>


/// CLR Wrapper for the From depenendency property


/// </summary>


public GridLength From
{
get


{
return (GridLength)GetValue(GridLengthAnimation.FromProperty);
}
set


{
SetValue(GridLengthAnimation.FromProperty,
value);
}
}
/// <summary>


/// Dependency property for the To property


/// </summary>


public static readonly DependencyProperty ToProperty = DependencyProperty.Register(“To”, typeof(GridLength),
typeof(GridLengthAnimation));
/// <summary>


/// CLR Wrapper for the To property


/// </summary>


public GridLength To
{
get


{
return (GridLength)GetValue(GridLengthAnimation.ToProperty);
}
set


{
SetValue(GridLengthAnimation.ToProperty,
value);
}
}
AnimationClock clock;
/// <summary>


/// registers to the completed event of the animation clock


/// </summary>


/// <param name=”clock”>the animation clock to notify completion status</param>


void VerifyAnimationCompletedStatus(AnimationClock clock)
{
if (this.clock == null)
{
this.clock = clock;
this.clock.Completed += new EventHandler(delegate(object sender, EventArgs e) { isCompleted = true; });
}
}
/// <summary>


/// Animates the grid let set


/// </summary>


/// <param name=”defaultOriginValue”>The original value to animate</param>


/// <param name=”defaultDestinationValue”>The final value</param>


/// <param name=”animationClock”>The animation clock (timer)</param>


/// <returns>Returns the new grid length to set</returns>


public override object GetCurrentValue(object defaultOriginValue,
object defaultDestinationValue, AnimationClock animationClock)
{
//check the animation clock event


VerifyAnimationCompletedStatus(animationClock);
//check if the animation was completed


if (isCompleted)
return (GridLength)defaultDestinationValue;
//if not then create the value to animate


double fromVal = this.From.Value;
double toVal = this.To.Value;
//check if the value is already collapsed


if (((GridLength)defaultOriginValue).Value == toVal)
{
fromVal = toVal;
toVal =
this.ReverseValue;
}
else


//check to see if this is the last tick of the animation clock.


if (animationClock.CurrentProgress.Value == 1.0)
return To;

if (fromVal > toVal)
return new GridLength((1 – animationClock.CurrentProgress.Value) *
(fromVal – toVal) + toVal,

this.From.IsStar ? GridUnitType.Star : GridUnitType.Pixel);
else


return new GridLength(animationClock.CurrentProgress.Value *
(toVal – fromVal) + fromVal,
this.From.IsStar ? GridUnitType.Star : GridUnitType.Pixel);
}
}
/// <summary>


/// Animates a double value


/// </summary>


public class ExpanderDoubleAnimation : DoubleAnimationBase
{
/// <summary>


/// Dependency property for the From property


/// </summary>


public static readonly DependencyProperty FromProperty = DependencyProperty.Register(“From”, typeof(double?),
typeof(ExpanderDoubleAnimation));
/// <summary>


/// CLR Wrapper for the From depenendency property


/// </summary>


public double? From
{
get


{
return (double?)GetValue(ExpanderDoubleAnimation.FromProperty);
}
set


{
SetValue(ExpanderDoubleAnimation.FromProperty,
value);
}
}
/// <summary>


/// Dependency property for the To property


/// </summary>


public static readonly DependencyProperty ToProperty = DependencyProperty.Register(“To”, typeof(double?),
typeof(ExpanderDoubleAnimation));
/// <summary>


/// CLR Wrapper for the To property


/// </summary>


public double? To
{
get


{
return (double?)GetValue(ExpanderDoubleAnimation.ToProperty);
}
set


{
SetValue(ExpanderDoubleAnimation.ToProperty,
value);
}
}
/// <summary>


/// Sets the reverse value for the second animation


/// </summary>


public double? ReverseValue
{
get { return (double)GetValue(ReverseValueProperty); }
set { SetValue(ReverseValueProperty, value); }
}
/// <summary>


/// Sets the reverse value for the second animation


/// </summary>


public static readonly DependencyProperty ReverseValueProperty =
DependencyProperty.Register(
“ReverseValue”, typeof(double?), typeof(ExpanderDoubleAnimation), new UIPropertyMetadata(0.0));

/// <summary>


/// Creates an instance of the animation


/// </summary>


/// <returns></returns>


protected override Freezable CreateInstanceCore()
{
return new ExpanderDoubleAnimation();
}
/// <summary>


/// Animates the double value


/// </summary>


/// <param name=”defaultOriginValue”>The original value to animate</param>


/// <param name=”defaultDestinationValue”>The final value</param>


/// <param name=”animationClock”>The animation clock (timer)</param>


/// <returns>Returns the new double to set</returns>


protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock)
{
double fromVal = this.From.Value;
double toVal = this.To.Value;

if (defaultOriginValue == toVal)
{
fromVal = toVal;
toVal =
this.ReverseValue.Value;
}
if (fromVal > toVal)
return (1 – animationClock.CurrentProgress.Value) *
(fromVal – toVal) + toVal;
else


return (animationClock.CurrentProgress.Value *
(toVal – fromVal) + fromVal);
}
}

 

 

WPF Listview – supporting enable – disable of columns + Sorting

[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;
}
}
}

WPF ThreadableObservable Collection

Did you every get an InvalidOperationException when binding to a collection in wpf…. The reason for this is that databinding is supported only when the collection is being filled by the dispatcher thread……

Unforunatly the WPF ObservaleCollection<T> does not support cross thread operations, so ther is only one thing to do … Implement one !!!! Bea Costa, the WPF queen explains this in more detail that I am, you can have a look at this post here.

Basically this class is just like the observable collection yet it asks the Dispatcher object to send the Event notification of the Collection Changed.

here we go

using System;
using System.Collections.Generic;
using System.Text;

using System.Windows.Threading;
using System.ComponentModel;

namespace Presentation.Avalon.GuiCore
{
/// <summary>
/// ThreadableObservableCollection caters for notify the user interface with changes done in the collection (Item[])
/// This can be used when you have a multi threaded envirorment
/// </summary>
/// <typeparam name=”T”>The Type of objects that are going to be stored in this collection</typeparam>
public class ThreadableObservableCollection<T> :
System.Collections.ObjectModel.ObservableCollection<T>, INotifyPropertyChanged
{
private List<T> items = null;

/// <summary>
/// Returns the index of the item
/// </summary>
/// <param name=”item”>The item to find</param>
/// <returns>The index of the item found</returns>
public int BinarySearch(T item)
{
if(this.comparer == null)
return -1;

return this.items.BinarySearch(item, this.comparer);
}

private IComparer<T> comparer;

/// <summary>
/// Comparer used to sort the collection
/// </summary>
public IComparer<T> Comparer
{
get { return comparer; }
set { comparer = value; }
}

//Gets the syncronization object to lock
private object sync = null;

/// <summary>
/// Gets the syncronization object to lock
/// </summary>
public object SyncRoot
{
get
{
return sync;
}
}

private T defaultLastValue;

/// <summary>
/// Gets the last value of the collection
/// </summary>
public T LastValue
{
get
{
if (this.items.Count != 0)
return this.items[this.Count > 0 ? (this.Count – 1) : 0];
else
return defaultLastValue;
}
}

/// <summary>
/// controlDispatcher stored the dispatcher of the GUI control being daat bound
/// </summary>
private readonly Dispatcher controlDispatcher;

/// <summary>
/// ControlDispatcher returns the dispatcher of the GUI control being daat bound
/// </summary>
public Dispatcher ControlDispatcher
{
get {return controlDispatcher; }
}

/// <summary>
/// Full constructor
/// Sets the dispatcher for this view
/// </summary>
/// <param name=”controlDispatcher”>The dispatcher of the control being data bound</param>
/// <param name=”comparer”>Comparer to sort the collection</param>
/// <param name=”defaultValue”>The value to return from the LastValue property, if no data is present in the collection</param>
public ThreadableObservableCollection(Dispatcher controlDispatcher, IComparer<T> comparer, T defaultValue)
: this(controlDispatcher, comparer)
{
this.defaultLastValue = defaultValue;
}

/// <summary>
/// Full constructor
/// Sets the dispatcher for this view
/// </summary>
/// <param name=”controlDispatcher”>The dispatcher of the control being data bound</param>
/// <param name=”comparer”>Comparer to sort the collection</param>
public ThreadableObservableCollection(Dispatcher controlDispatcher, IComparer<T> comparer)
{
this.controlDispatcher = controlDispatcher;
this.comparer = comparer;
sync = new object();
this.items = this.Items as List<T>;
}

/// <summary>
/// Default constructor
/// Sets the dispatcher for this view
/// </summary>
/// <param name=”controlDispatcher”>The dispatcher of the control being data bound</param>
public ThreadableObservableCollection(Dispatcher controlDispatcher)
: this(controlDispatcher, null)
{}

//flag to signal if the collecion is being added with a chunk of data
bool busy = false;

/// <summary>
/// flag to signal if the collecion is being added with a chunk of data
/// </summary>
public bool Busy
{
get { return busy; }
}

/// <summary>
/// Add a collection to the list
/// </summary>
/// <param name=”items”>The collection of objects to add</param>
public void AddRange(IList<T> items)
{
this.AddRange(items, true);
}

/// <summary>
/// Add a collection to the list
/// </summary>
/// <param name=”items”>The collection of objects to add</param>
/// <param name=”resetAction”>Pass true to raise the collection action reset event argument</param>
public void AddRange(IList<T> items, bool resetAction)
{
//if only one item needs to be added call the add directly
if (items.Count == 1)
{
this.Add(items[0]);
return;
}

//set the collection as busy to turn OFF notifications
busy = true;

lock (sync) //lock here since the data is being added
{
//add all the values in the collection
foreach (T value in items)
this.Items.Insert(GetIndexForItem(value), value);
}

//set the collection as not busy to turn ON notifications
busy = false;

if (resetAction)
{
//raise the collection changed event
this.OnCollectionChanged(
new System.Collections.Specialized.NotifyCollectionChangedEventArgs(
System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
);
}
else
{
this.OnCollectionChanged(
new System.Collections.Specialized.NotifyCollectionChangedEventArgs(
System.Collections.Specialized.NotifyCollectionChangedAction.Add,
(System.Collections.IList)items));
}
}

/// <summary>
/// Removes a range of items from the list
/// </summary>
/// <param name=”indexFrom”>The index from where to begin removing</param>
/// <param name=”indexTo”>The index from where to end removing</param>
/// <param name=”notifyUIOnce”>Set to true if you want to only send one event to the UI when finished removing items</param>
public void RemoveRange(int indexFrom, int indexTo, bool notifyUIOnce)
{
if (!notifyUIOnce)
{
RemoveRange(indexFrom, indexTo, this);
return;
}

//set the collection as busy to turn OFF notifications
busy = true;

RemoveRange(indexFrom, indexTo, this.items);

//set the collection as not busy to turn ON notifications
busy = false;

//raise the collection changed event
this.OnCollectionChanged(
new System.Collections.Specialized.NotifyCollectionChangedEventArgs(
System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
);
}

/// <summary>
/// Removes a range of items from the list
/// </summary>
/// <param name=”indexFrom”>The index from where to begin removing</param>
/// <param name=”indexTo”>The index from where to end removing</param>
/// <param name=”collection”>The collection to remove items from</param>
private static void RemoveRange(int indexFrom, int indexTo, IList<T> collection)
{
for (int i = 0; i <= indexTo – indexFrom; i++)
{
if (collection.Count != 0)
{
collection.RemoveAt(indexFrom);
}
}
}

/// <summary>
/// raises the collection changed method
/// </summary>
/// <param name=”e”>The event argument to pass in the event</param>
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (!busy)
{
if (InvokeRequired)
{
controlDispatcher.BeginInvoke(DispatcherPriority.Send, new OnCollectionChangedEventHandler(OnCollectionChanged), e);
}
else
{
base.OnCollectionChanged(e);
base.OnPropertyChanged(new PropertyChangedEventArgs(“LastValue”));
}
}
}

#region Delegates for callbacks

/// <summary>
/// InvokeRequired check if there need current thread is the main thread
/// </summary>
/// <returns>Returns true if the current thread is not the main thread</returns>
private bool InvokeRequired
{
get
{
return controlDispatcher != null && controlDispatcher.Thread != System.Threading.Thread.CurrentThread;
}
}

/// <summary>
/// SetItemCallback is the delegate for when the SetItem method of the collection is invoked
/// </summary>
/// <param name=”index”>The index of the item</param>
/// <param name=”item”>The new item data to set</param>
private delegate void SetItemCallback(int index, T item);

/// <summary>
/// delegate to redirect to the correct thread
/// </summary>
/// <param name=”e”>The event argument to pass in when the event is raised</param>
private delegate void OnCollectionChangedEventHandler(System.Collections.Specialized.NotifyCollectionChangedEventArgs e);

/// <summary>
/// RemoveItemCallback is the delegate for when an item is removed from the Collection
/// </summary>
/// <param name=”index”>The index of the item to remove</param>
private delegate void RemoveItemCallback(int index);

/// <summary>
/// ClearItemsCallback is the delegate for when the clear method of the collection is called
/// </summary>
private delegate void ClearItemsCallback();

/// <summary>
/// InsertItemCallback is the delegate for when the InsertItem methdo of the collection is called
/// </summary>
/// <param name=”index”>The index where to insert the new item</param>
/// <param name=”item”>The new item to add</param>
private delegate void InsertItemCallback(int index, T item);

#endregion

#region Method To override

/// <summary>
/// InsertItem overrides the base InsertItem so to notify the GUI with the new data
/// </summary>
/// <param name=”index”>The index where the item was added</param>
/// <param name=”item”>The item object added</param>
protected override void InsertItem(int index, T item)
{
if (InvokeRequired)
controlDispatcher.Invoke(DispatcherPriority.Send, new InsertItemCallback(InsertItem), index, new object[] { item });
else
base.InsertItem(GetIndexForItem(item), item);
}

/// <summary>
/// Gets the index of where to insert the item in the list.
/// </summary>
/// <param name=”item”>The Item to search</param>
/// <returns>Return the index of the item</returns>
public int GetIndexForItem(T item)
{
if (this.items.Count == 0)
return 0;

//Compare with the last item.
if (this.comparer == null ||
Comparer.Compare(this.items[this.items.Count – 1], item) <= 0)
return this.items.Count;

int index = this.items.BinarySearch(item, this.comparer);

// Item was found. Insert new item after
if (index >= 0)
index++;

// Item was not found. Bitwise complement is where to put the new one.
if (index < 0)
index = ~index;

return index;
}

/// <summary>
/// SetItem modifies an item in the collection
/// </summary>
/// <param name=”index”>The index of the item to modify</param>
/// <param name=”item”>The new item instance</param>
protected override void SetItem(int index, T item)
{
if (InvokeRequired)
controlDispatcher.Invoke(DispatcherPriority.Send, new SetItemCallback(SetItem), index, new object[] { item });
else
base.SetItem(index, item);
}

/// <summary>
/// RemoveItem removes an item from a specific position
/// </summary>
/// <param name=”index”>The index of the item to remove</param>
protected override void RemoveItem(int index)
{
if (InvokeRequired)
controlDispatcher.Invoke(DispatcherPriority.Send, new RemoveItemCallback(RemoveItem), index);
else
base.RemoveItem(index);
}

/// <summary>
/// ClearItems will remove all the items from the collection
/// </summary>
protected override void ClearItems()
{
if (InvokeRequired)
ControlDispatcher.Invoke(DispatcherPriority.Send, new ClearItemsCallback(ClearItems));
else
base.ClearItems();
}

#endregion

#region UNIT_TEST
/// <summary>
/// Calls the InsertItem method to expose it for unit tests
/// </summary>
/// <param name=”index”>The index where the item was added</param>
/// <param name=”item”>The item object added</param>
public void InsertItemPublic(int index, T item)
{
this.InsertItem(index, item);
}

/// <summary>
/// Calls the SetItem method to expose it for unit tests
/// </summary>
/// <param name=”index”>The index of the item to modify</param>
/// <param name=”item”>The new item instance</param>
public void SetItemPublic(int index, T item)
{
this.SetItem(index, item);
}

/// <summary>
/// Calls the RemoveItem method to expose it for unit tests
/// </summary>
/// <param name=”index”>The index of the item to remove</param>
public void RemoveItemPublic(int index)
{
this.RemoveItem(index);
}

/// <summary>
/// Calls the ClearItems method to expose it for unit tests
/// </summary>
public void ClearItemsPublic()
{
this.ClearItems();
}
#endregion

}
}