Skip to content

Instantly share code, notes, and snippets.

@ngbrown
Created October 5, 2012 20:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ngbrown/3842172 to your computer and use it in GitHub Desktop.
Save ngbrown/3842172 to your computer and use it in GitHub Desktop.
Wpf helpers.
namespace Extenders
{
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Documents;
/// <summary>
/// Allows a set of attached properties to automatically sort a <see cref="GridView"/>.
/// </summary>
/// <remarks>
/// From http://www.thomaslevesque.com/2009/08/04/wpf-automatically-sort-a-gridview-continued/
/// </remarks>
/// <example>
/// This class can be used as follows :
/// <code lang="xaml">
/// <![CDATA[
/// <ListView ItemsSource="{Binding Persons}"
/// IsSynchronizedWithCurrentItem="True"
/// util:GridViewSort.AutoSort="True">
/// <ListView.View>
/// <GridView>
/// <GridView.Columns>
/// <GridViewColumn Header="Name"
/// DisplayMemberBinding="{Binding Name}"
/// util:GridViewSort.PropertyName="Name"/>
/// <GridViewColumn Header="First name"
/// DisplayMemberBinding="{Binding FirstName}"
/// util:GridViewSort.PropertyName="FirstName"/>
/// <GridViewColumn Header="Date of birth"
/// DisplayMemberBinding="{Binding DateOfBirth}"
/// util:GridViewSort.PropertyName="DateOfBirth"/>
/// </GridView.Columns>
/// </GridView>
/// </ListView.View>
/// </ListView>
/// ]]>
/// </code>
/// </example>
/// <example>
/// In case you need to handle the sorting manually, use a GridViewSort.Command attached property.
/// When used with the MVVM pattern, this property allows you to bind to a command declared in the ViewModel:
/// <code lang="xaml">
/// <![CDATA[
/// <ListView ItemsSource="{Binding Persons}"
/// IsSynchronizedWithCurrentItem="True"
/// util:GridViewSort.Command="{Binding SortCommand}">
/// ]]>
/// </code>
/// </example>
/// <example>
/// You can provide your own images for the sort glyphs:
/// <code lang="xaml">
/// <![CDATA[
/// <ListView ItemsSource="{Binding Persons}"
/// IsSynchronizedWithCurrentItem="True"
/// util:GridViewSort.AutoSort="True"
/// util:GridViewSort.SortGlyphAscending="/Images/up.png"
/// util:GridViewSort.SortGlyphDescending="/Images/down.png">
/// ]]>
/// </code>
/// </example>
/// <example>
/// It is also possible to disable the sort glyphs, by setting the <see cref="ShowSortGlyphProperty"/> attached property to <c>false</c> :
/// <code lang="xaml">
/// <![CDATA[
/// <ListView ItemsSource="{Binding Persons}"
/// IsSynchronizedWithCurrentItem="True"
/// util:GridViewSort.AutoSort="True"
/// util:GridViewSort.ShowSortGlyph="False">
/// ]]>
/// </code>
/// </example>
public class GridViewSort
{
#region Public attached properties
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(GridViewSort),
new UIPropertyMetadata(
null,
(o, e) =>
{
ItemsControl listView = o as ItemsControl;
if (listView != null)
{
if (!GetAutoSort(listView)) // Don't change click handler if AutoSort enabled
{
if (e.OldValue != null && e.NewValue == null)
{
listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
if (e.OldValue == null && e.NewValue != null)
{
listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
}
}
}
)
);
public static bool GetAutoSort(DependencyObject obj)
{
return (bool)obj.GetValue(AutoSortProperty);
}
public static void SetAutoSort(DependencyObject obj, bool value)
{
obj.SetValue(AutoSortProperty, value);
}
// Using a DependencyProperty as the backing store for AutoSort. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoSortProperty =
DependencyProperty.RegisterAttached(
"AutoSort",
typeof(bool),
typeof(GridViewSort),
new UIPropertyMetadata(
false,
(o, e) =>
{
ListView listView = o as ListView;
if (listView != null)
{
if (GetCommand(listView) == null) // Don't change click handler if a command is set
{
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (oldValue && !newValue)
{
listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
if (!oldValue && newValue)
{
listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
}
}
}
)
);
public static string GetPropertyName(DependencyObject obj)
{
return (string)obj.GetValue(PropertyNameProperty);
}
public static void SetPropertyName(DependencyObject obj, string value)
{
obj.SetValue(PropertyNameProperty, value);
}
// Using a DependencyProperty as the backing store for PropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.RegisterAttached(
"PropertyName",
typeof(string),
typeof(GridViewSort),
new UIPropertyMetadata(null)
);
public static bool GetShowSortGlyph(DependencyObject obj)
{
return (bool)obj.GetValue(ShowSortGlyphProperty);
}
public static void SetShowSortGlyph(DependencyObject obj, bool value)
{
obj.SetValue(ShowSortGlyphProperty, value);
}
// Using a DependencyProperty as the backing store for ShowSortGlyph. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowSortGlyphProperty =
DependencyProperty.RegisterAttached("ShowSortGlyph", typeof(bool), typeof(GridViewSort), new UIPropertyMetadata(true));
public static ImageSource GetSortGlyphAscending(DependencyObject obj)
{
return (ImageSource)obj.GetValue(SortGlyphAscendingProperty);
}
public static void SetSortGlyphAscending(DependencyObject obj, ImageSource value)
{
obj.SetValue(SortGlyphAscendingProperty, value);
}
// Using a DependencyProperty as the backing store for SortGlyphAscending. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortGlyphAscendingProperty =
DependencyProperty.RegisterAttached("SortGlyphAscending", typeof(ImageSource), typeof(GridViewSort), new UIPropertyMetadata(null));
public static ImageSource GetSortGlyphDescending(DependencyObject obj)
{
return (ImageSource)obj.GetValue(SortGlyphDescendingProperty);
}
public static void SetSortGlyphDescending(DependencyObject obj, ImageSource value)
{
obj.SetValue(SortGlyphDescendingProperty, value);
}
// Using a DependencyProperty as the backing store for SortGlyphDescending. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortGlyphDescendingProperty =
DependencyProperty.RegisterAttached("SortGlyphDescending", typeof(ImageSource), typeof(GridViewSort), new UIPropertyMetadata(null));
#endregion
#region Private attached properties
private static GridViewColumnHeader GetSortedColumnHeader(DependencyObject obj)
{
return (GridViewColumnHeader)obj.GetValue(SortedColumnHeaderProperty);
}
private static void SetSortedColumnHeader(DependencyObject obj, GridViewColumnHeader value)
{
obj.SetValue(SortedColumnHeaderProperty, value);
}
// Using a DependencyProperty as the backing store for SortedColumn. This enables animation, styling, binding, etc...
private static readonly DependencyProperty SortedColumnHeaderProperty =
DependencyProperty.RegisterAttached("SortedColumnHeader", typeof(GridViewColumnHeader), typeof(GridViewSort), new UIPropertyMetadata(null));
#endregion
#region Column header click event handler
private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null && headerClicked.Column != null)
{
string propertyName = GetPropertyName(headerClicked.Column);
if (!string.IsNullOrEmpty(propertyName))
{
ListView listView = GetAncestor<ListView>(headerClicked);
if (listView != null)
{
ICommand command = GetCommand(listView);
if (command != null)
{
if (command.CanExecute(propertyName))
{
command.Execute(propertyName);
}
}
else if (GetAutoSort(listView))
{
ApplySort(listView.Items, propertyName, listView, headerClicked);
}
}
}
}
}
#endregion
#region Helper methods
public static T GetAncestor<T>(DependencyObject reference) where T : DependencyObject
{
DependencyObject parent = VisualTreeHelper.GetParent(reference);
while (!(parent is T))
{
parent = VisualTreeHelper.GetParent(parent);
}
if (parent != null)
return (T)parent;
else
return null;
}
public static void ApplySort(ICollectionView view, string propertyName, ListView listView, GridViewColumnHeader sortedColumnHeader)
{
ListSortDirection direction = ListSortDirection.Ascending;
if (view.SortDescriptions.Count > 0)
{
SortDescription currentSort = view.SortDescriptions[0];
if (currentSort.PropertyName == propertyName)
{
if (currentSort.Direction == ListSortDirection.Ascending)
direction = ListSortDirection.Descending;
else
direction = ListSortDirection.Ascending;
}
view.SortDescriptions.Clear();
GridViewColumnHeader currentSortedColumnHeader = GetSortedColumnHeader(listView);
if (currentSortedColumnHeader != null)
{
RemoveSortGlyph(currentSortedColumnHeader);
}
}
if (!string.IsNullOrEmpty(propertyName))
{
view.SortDescriptions.Add(new SortDescription(propertyName, direction));
if (GetShowSortGlyph(listView))
AddSortGlyph(
sortedColumnHeader,
direction,
direction == ListSortDirection.Ascending ? GetSortGlyphAscending(listView) : GetSortGlyphDescending(listView));
SetSortedColumnHeader(listView, sortedColumnHeader);
}
}
private static void AddSortGlyph(GridViewColumnHeader columnHeader, ListSortDirection direction, ImageSource sortGlyph)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
adornerLayer.Add(
new SortGlyphAdorner(
columnHeader,
direction,
sortGlyph
));
}
private static void RemoveSortGlyph(GridViewColumnHeader columnHeader)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
Adorner[] adorners = adornerLayer.GetAdorners(columnHeader);
if (adorners != null)
{
foreach (Adorner adorner in adorners)
{
if (adorner is SortGlyphAdorner)
adornerLayer.Remove(adorner);
}
}
}
#endregion
#region SortGlyphAdorner nested class
private class SortGlyphAdorner : Adorner
{
private GridViewColumnHeader _columnHeader;
private ListSortDirection _direction;
private ImageSource _sortGlyph;
public SortGlyphAdorner(GridViewColumnHeader columnHeader, ListSortDirection direction, ImageSource sortGlyph)
: base(columnHeader)
{
_columnHeader = columnHeader;
_direction = direction;
_sortGlyph = sortGlyph;
}
private Geometry GetDefaultGlyph()
{
double x1 = _columnHeader.ActualWidth - 13;
double x2 = x1 + 10;
double x3 = x1 + 5;
double y1 = _columnHeader.ActualHeight / 2 - 3;
double y2 = y1 + 5;
if (_direction == ListSortDirection.Ascending)
{
double tmp = y1;
y1 = y2;
y2 = tmp;
}
PathSegmentCollection pathSegmentCollection = new PathSegmentCollection();
pathSegmentCollection.Add(new LineSegment(new Point(x2, y1), true));
pathSegmentCollection.Add(new LineSegment(new Point(x3, y2), true));
PathFigure pathFigure = new PathFigure(
new Point(x1, y1),
pathSegmentCollection,
true);
PathFigureCollection pathFigureCollection = new PathFigureCollection();
pathFigureCollection.Add(pathFigure);
PathGeometry pathGeometry = new PathGeometry(pathFigureCollection);
return pathGeometry;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (_sortGlyph != null)
{
double x = _columnHeader.ActualWidth - 13;
double y = _columnHeader.ActualHeight / 2 - 5;
Rect rect = new Rect(x, y, 10, 10);
drawingContext.DrawImage(_sortGlyph, rect);
}
else
{
drawingContext.DrawGeometry(Brushes.LightGray, new Pen(Brushes.Gray, 1.0), GetDefaultGlyph());
}
}
}
#endregion
}
}
namespace Extenders
{
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
// Use like this:
// <ItemsControl ItemsSource="{Binding SourceCollection}">
// <i:Interaction.Behaviors>
// <Behaviors:ScrollOnNewItem/>
// </i:Interaction.Behaviors>
// </ItemsControl>
/// <remarks>
/// Idea partially from http://stackoverflow.com/a/12255329/25182 <br/>
/// Use like this:<br/>
/// &lt;ItemsControl ItemsSource=&quot;{Binding SourceCollection}&quot;&gt;<br/>
/// &lt;i:Interaction.Behaviors&gt;<br/>
/// &lt;Behaviors:ScrollOnNewItem/&gt;<br/>
/// &lt;/i:Interaction.Behaviors&gt;<br/>
/// &lt;/ItemsControl&gt;
/// </remarks>
public class ScrollOnNewItem : Behavior<ItemsControl>
{
private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;
protected override void OnAttached()
{
AssociatedObject.Loaded += this.OnLoaded;
AssociatedObject.Unloaded += this.OnUnLoaded;
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= this.OnLoaded;
AssociatedObject.Unloaded -= this.OnUnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null)
{
return;
}
incc.CollectionChanged += this.OnCollectionChanged;
this.scrollViewer = ScrollOnNewItem.FindVisualChild<ScrollViewer>(this.AssociatedObject);
}
private void OnUnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null)
{
return;
}
incc.CollectionChanged -= this.OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.scrollViewer != null)
{
this.isScrollDownEnabled = Math.Abs((this.scrollViewer.VerticalOffset + this.scrollViewer.ViewportHeight) - this.scrollViewer.ExtentHeight) < .5;
if (e.Action == NotifyCollectionChangedAction.Add && this.isScrollDownEnabled)
{
this.scrollViewer.ScrollToBottom();
}
}
}
private static TChildItem FindVisualChild<TChildItem>(DependencyObject obj) where TChildItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is TChildItem)
{
return (TChildItem)child;
}
else
{
TChildItem childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
}
}
@dtaylor-530
Copy link

For anyone else who stumbled on this
The second part needs a nuget package downloaded in order to work:
https://stackoverflow.com/questions/8360209/how-to-add-system-windows-interactivity-to-project

And here's some codebehind for an example window for the first example:

`
namespace Extenders
{
using System;
using System.Collections.ObjectModel;
using System.Windows;

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PersonsClass();
}
}

class Person
{
    public DateTime DateOfBirth { get; set; }
    public string Name { get; set; }

    public Person(int age, string name)
    {
        DateOfBirth = DateTime.Now.Subtract(new TimeSpan(age*365, 0, 0, 0));
        Name = name;
    }

}

class PersonsClass
{
    public ObservableCollection<Person> Persons { get; set; } = new ObservableCollection<Person>();

    public PersonsClass()
    {
        Person Mother = new Person(35, "Alice");
        Person Son = new Person(12, "Johny");
        Person Father = new Person(12, "Johny Snr");

        Persons.Add(Mother); Persons.Add(Son); Persons.Add(Father);
    }
}

}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment