Created
October 5, 2012 20:26
-
-
Save ngbrown/3842172 to your computer and use it in GitHub Desktop.
Wpf helpers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/> | |
/// <ItemsControl ItemsSource="{Binding SourceCollection}"><br/> | |
/// <i:Interaction.Behaviors><br/> | |
/// <Behaviors:ScrollOnNewItem/><br/> | |
/// </i:Interaction.Behaviors><br/> | |
/// </ItemsControl> | |
/// </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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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();
}
}
}
`