using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using Developpez.Dotnet.Collections; namespace Developpez.Dotnet.Windows.Behaviors { /// /// Fournit des propriétés attachées pour ajouter des fonctionnalités à une GridView /// public static class GridViewBehavior { #region Columns binding /// /// Obtient la valeur de la propriété attachée ColumnsSource pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La valeur de la propriété attachée ColumnsSource pour cet objet [AttachedPropertyBrowsableForType(typeof(GridView))] public static object GetColumnsSource(GridView obj) { return obj.GetValue(ColumnsSourceProperty); } /// /// Définit la valeur de la propriété attachée ColumnsSource pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La nouvelle valeur de la propriété attachée ColumnsSource pour cet objet public static void SetColumnsSource(GridView obj, object value) { obj.SetValue(ColumnsSourceProperty, value); } /// /// Identifie la propriété attachée ColumnsSource /// public static readonly DependencyProperty ColumnsSourceProperty = DependencyProperty.RegisterAttached( "ColumnsSource", typeof(object), typeof(GridViewBehavior), new UIPropertyMetadata( null, ColumnsSourceChanged)); /// /// Obtient la valeur de la propriété attachée HeaderTextMember pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La valeur de la propriété attachée HeaderTextMember pour cet objet [AttachedPropertyBrowsableForType(typeof(GridView))] public static string GetHeaderTextMember(GridView obj) { return (string)obj.GetValue(HeaderTextMemberProperty); } /// /// Définit la valeur de la propriété attachée HeaderTextMember pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La nouvelle valeur de la propriété attachée HeaderTextMember pour cet objet public static void SetHeaderTextMember(GridView obj, string value) { obj.SetValue(HeaderTextMemberProperty, value); } /// /// Identifie la propriété attachée HeaderTextMember /// public static readonly DependencyProperty HeaderTextMemberProperty = DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(GridViewBehavior), new UIPropertyMetadata(null)); /// /// Obtient la valeur de la propriété attachée DisplayMemberMember pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La valeur de la propriété attachée DisplayMemberMember pour cet objet [AttachedPropertyBrowsableForType(typeof(GridView))] public static string GetDisplayMemberMember(GridView obj) { return (string)obj.GetValue(DisplayMemberMemberProperty); } /// /// Définit la valeur de la propriété attachée DisplayMemberMember pour l'objet spécifié /// /// L'objet pour lequel on veut obtenir la valeur /// La nouvelle valeur de la propriété attachée DisplayMemberMember pour cet objet public static void SetDisplayMemberMember(GridView obj, string value) { obj.SetValue(DisplayMemberMemberProperty, value); } /// /// Identifie la propriété attachée DisplayMemberMember /// public static readonly DependencyProperty DisplayMemberMemberProperty = DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(GridViewBehavior), new UIPropertyMetadata(null)); private static void ColumnsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { GridView gridView = obj as GridView; if (gridView != null) { gridView.Columns.Clear(); if (e.OldValue != null) { ICollectionView view = CollectionViewSource.GetDefaultView(e.OldValue); if (view != null) RemoveHandlers(gridView, view); } if (e.NewValue != null) { ICollectionView view = CollectionViewSource.GetDefaultView(e.NewValue); if (view != null) { AddHandlers(gridView, view); CreateColumns(gridView, view); } } } } private static readonly IDictionary> _gridViewsByColumnsSource = new DefaultDictionary>(v => new List()); private static void AddHandlers(GridView gridView, ICollectionView view) { _gridViewsByColumnsSource[view].Add(gridView); view.CollectionChanged += ColumnsSource_CollectionChanged; } private static void CreateColumns(GridView gridView, ICollectionView view) { foreach (var item in view) { GridViewColumn column = CreateColumn(gridView, item); gridView.Columns.Add(column); } } private static void RemoveHandlers(GridView gridView, ICollectionView view) { view.CollectionChanged -= ColumnsSource_CollectionChanged; _gridViewsByColumnsSource[view].Remove(gridView); } private static void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { ICollectionView view = (ICollectionView)sender; var gridViews = _gridViewsByColumnsSource[view]; if (gridViews.IsNullOrEmpty()) return; switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var gridView in gridViews) { for (int i = 0; i < e.NewItems.Count; i++) { GridViewColumn column = CreateColumn(gridView, e.NewItems[i]); gridView.Columns.Insert(e.NewStartingIndex + i, column); } } break; case NotifyCollectionChangedAction.Move: foreach (var gridView in gridViews) { List columns = new List(); for (int i = 0; i < e.OldItems.Count; i++) { GridViewColumn column = gridView.Columns[e.OldStartingIndex + i]; columns.Add(column); } for (int i = 0; i < e.NewItems.Count; i++) { GridViewColumn column = columns[i]; gridView.Columns.Insert(e.NewStartingIndex + i, column); } } break; case NotifyCollectionChangedAction.Remove: foreach (var gridView in gridViews) { for (int i = 0; i < e.OldItems.Count; i++) { gridView.Columns.RemoveAt(e.OldStartingIndex); } } break; case NotifyCollectionChangedAction.Replace: foreach (var gridView in gridViews) { for (int i = 0; i < e.NewItems.Count; i++) { GridViewColumn column = CreateColumn(gridView, e.NewItems[i]); gridView.Columns[e.NewStartingIndex + i] = column; } } break; case NotifyCollectionChangedAction.Reset: foreach (var gridView in gridViews) { gridView.Columns.Clear(); CreateColumns(gridView, sender as ICollectionView); } break; default: break; } } private static GridViewColumn CreateColumn(GridView gridView, object columnSource) { GridViewColumn column = new GridViewColumn(); string headerTextMember = GetHeaderTextMember(gridView); string displayMemberMember = GetDisplayMemberMember(gridView); if (!headerTextMember.IsNullOrEmpty()) { column.Header = GetPropertyValue(columnSource, headerTextMember); } if (!displayMemberMember.IsNullOrEmpty()) { string propertyName = GetPropertyValue(columnSource, displayMemberMember) as string; column.DisplayMemberBinding = new Binding(propertyName); } return column; } private static object GetPropertyValue(object obj, string propertyName) { if (obj != null) { PropertyInfo prop = obj.GetType().GetProperty(propertyName); if (prop != null) return prop.GetValue(obj, null); } return null; } #endregion #region GridView auto sort #region Public attached properties /// /// Obtient la valeur de la propriété attachée SortCommand pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// La commande de tri associée à l'objet spécifié [AttachedPropertyBrowsableForType(typeof(ListView))] public static ICommand GetSortCommand(ListView obj) { return (ICommand)obj.GetValue(SortCommandProperty); } /// /// Définit la valeur de la propriété attachée SortCommand pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// La commande de tri à associer à l'objet spécifié public static void SetSortCommand(ListView obj, ICommand value) { obj.SetValue(SortCommandProperty, value); } /// /// Identifie la propriété attachée SortCommand /// public static readonly DependencyProperty SortCommandProperty = DependencyProperty.RegisterAttached( "SortCommand", typeof(ICommand), typeof(GridViewBehavior), new UIPropertyMetadata( null, OnSortCommandChanged ) ); private static void OnSortCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ListView listView = o as ListView; if (listView == null) return; // Don't change click handler if AutoSort enabled if (GetAutoSort(listView)) return; if (e.OldValue != null && e.NewValue == null) { listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); } if (e.OldValue == null && e.NewValue != null) { listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); } } /// /// Obtient la valeur de la propriété attachée AutoSort pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// true si le tri automatique est activé pour l'objet spécifié, false sinon [AttachedPropertyBrowsableForType(typeof(ListView))] public static bool GetAutoSort(ListView obj) { return (bool)obj.GetValue(AutoSortProperty); } /// /// Définit la valeur de la propriété attachée AutoSort pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// true pour activer le tri automatique pour l'objet spécifié, false sinon public static void SetAutoSort(ListView obj, bool value) { obj.SetValue(AutoSortProperty, value); } /// /// Identifie la propriété attachée AutoSort /// public static readonly DependencyProperty AutoSortProperty = DependencyProperty.RegisterAttached( "AutoSort", typeof(bool), typeof(GridViewBehavior), new UIPropertyMetadata( false, OnAutoSortChanged ) ); private static void OnAutoSortChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ListView listView = o as ListView; if (listView == null) return; // Don't change click handler if a command is set if (GetSortCommand(listView) != null) return; bool oldValue = (bool) e.OldValue; bool newValue = (bool) e.NewValue; if (oldValue && !newValue) { listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); } if (!oldValue && newValue) { listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); listView.DoWhenLoaded(SetGlyphForInitialSort); } } private static void SetGlyphForInitialSort(ListView listView) { if (!GetAutoSort(listView) || !GetShowSortGlyph(listView)) return; var view = CollectionViewSource.GetDefaultView(listView.Items); if (!view.SortDescriptions.Any()) return; var headerRow = listView.FindDescendants().FirstOrDefault(); if (headerRow == null) return; var sort = view.SortDescriptions.First(); var headers = headerRow.FindDescendants(); foreach (var header in headers) { if (header.Column == null) continue; string sortPropertyName = GetSortPropertyName(header.Column); if (sortPropertyName != sort.PropertyName) continue; AddSortGlyph( header, sort.Direction, sort.Direction == ListSortDirection.Ascending ? GetSortGlyphAscending(listView) : GetSortGlyphDescending(listView)); SetSortedColumnHeader(listView, header); break; } } /// /// Obtient la valeur de la propriété attachée SortPropertyName pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// Le nom de la propriété de tri associée à l'objet spécifié [AttachedPropertyBrowsableForType(typeof(GridViewColumn))] public static string GetSortPropertyName(GridViewColumn obj) { return (string)obj.GetValue(SortPropertyNameProperty); } /// /// Définit la valeur de la propriété attachée SortPropertyName pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// La propriété de tri à associer à l'objet spécifié public static void SetSortPropertyName(GridViewColumn obj, string value) { obj.SetValue(SortPropertyNameProperty, value); } /// /// Identifie la propriété attachée SortPropertyName /// public static readonly DependencyProperty SortPropertyNameProperty = DependencyProperty.RegisterAttached( "SortPropertyName", typeof(string), typeof(GridViewBehavior), new UIPropertyMetadata(null) ); /// /// Obtient la valeur de la propriété attachée ShowSortGlyph pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// true si le symbole de tri est affiché, false sinon [AttachedPropertyBrowsableForType(typeof(ListView))] public static bool GetShowSortGlyph(ListView obj) { return (bool)obj.GetValue(ShowSortGlyphProperty); } /// /// Définit la valeur de la propriété attachée ShowSortGlyph pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// true pour afficher le symbole de tri, false sinon public static void SetShowSortGlyph(ListView obj, bool value) { obj.SetValue(ShowSortGlyphProperty, value); } /// /// Identifie la propriété attachée ShowSortGlyph /// public static readonly DependencyProperty ShowSortGlyphProperty = DependencyProperty.RegisterAttached("ShowSortGlyph", typeof(bool), typeof(GridViewBehavior), new UIPropertyMetadata(true)); /// /// Obtient la valeur de la propriété attachée SortGlyphAscending pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// Le symbole de tri croissant associé à l'objet spécifié [AttachedPropertyBrowsableForType(typeof(ListView))] public static ImageSource GetSortGlyphAscending(ListView obj) { return (ImageSource)obj.GetValue(SortGlyphAscendingProperty); } /// /// Définit la valeur de la propriété attachée SortGlyphAscending pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// Le symbole de tri croissant à associer à l'objet spécifié public static void SetSortGlyphAscending(ListView obj, ImageSource value) { obj.SetValue(SortGlyphAscendingProperty, value); } /// /// Identifie la propriété attachée SortGlyphAscending /// public static readonly DependencyProperty SortGlyphAscendingProperty = DependencyProperty.RegisterAttached("SortGlyphAscending", typeof(ImageSource), typeof(GridViewBehavior), new UIPropertyMetadata(null)); /// /// Obtient la valeur de la propriété attachée SortGlyphDescending pour l'objet spécifié /// /// Objet dont on veut obtenir la valeur de la propriété /// Le symbole de tri décroissant associé à l'objet spécifié [AttachedPropertyBrowsableForType(typeof(ListView))] public static ImageSource GetSortGlyphDescending(ListView obj) { return (ImageSource)obj.GetValue(SortGlyphDescendingProperty); } /// /// Définit la valeur de la propriété attachée SortGlyphDescending pour l'objet spécifié /// /// Objet dont on veut définir la valeur de la propriété /// Le symbole de tri décroissant à associer à l'objet spécifié public static void SetSortGlyphDescending(ListView obj, ImageSource value) { obj.SetValue(SortGlyphDescendingProperty, value); } /// /// Identifie la propriété attachée SortGlyphDescending /// public static readonly DependencyProperty SortGlyphDescendingProperty = DependencyProperty.RegisterAttached("SortGlyphDescending", typeof(ImageSource), typeof(GridViewBehavior), 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); } private static readonly DependencyProperty _sortedColumnHeaderProperty = DependencyProperty.RegisterAttached("SortedColumnHeader", typeof(GridViewColumnHeader), typeof(GridViewBehavior), 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 = GetSortPropertyName(headerClicked.Column); if (!string.IsNullOrEmpty(propertyName)) { ListView listView = headerClicked.FindAncestor(); if (listView != null) { ICommand command = GetSortCommand(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 private 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 readonly GridViewColumnHeader _columnHeader; private readonly ListSortDirection _direction; private readonly 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 #endregion } }