Trong WPF để trình bày dữ liệu dưới dạng lưới ta có thể sử dụng GridView. Tuy nhiên, nếu bạn muốn sắp xếp nó, việc này sẽ khó hơn một chút với DataGridView trong Windows Forms (khi người dùng nhấp vào tiêu đề cột, lưới sẽ được tự động sắp xếp). Sau một thời gian tìm hiểu mình đã tìm thấy một giải pháp cho vấn đề này. Hôm nay mình sẽ chia sẻ với các bạn về nó, nó dựa trên sự kiện Click của lớp GridViewColumnHeader. Đầu tiên ta tạo ra một class để xử lý các hành động sắp xếp với column
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
}
Thuộc tính GridViewSort.AutoSort cho phép sắp xếp tự động cho ListView. Thuộc tính GridViewSort.PropertyName, được xác định cho mỗi cột, cho biết thuộc tính sẽ sử dụng làm tiêu chí sắp xếp. Khi bạn click vào header một cột sẽ kích hoạt sắp xếp trên cột đó.
Trong trường hợp bạn cần xử lý việc sắp xếp theo cách thủ công, bạn có thể sử dụng thuộc tính đính kèm GridViewSort.Command. Khi được sử dụng với mẫu MVVM, thuộc tính này cho phép bạn liên kết với command được khai báo trong ViewModel.
<ListView ItemsSource="{Binding }"
IsSynchronizedWithCurrentItem="True"
local:GridViewSort.Command="{Binding SortCommand}">
...
Nếu cả hai thuộc tính Command và AutoSort được thiết lập, Command sẽ được ưu tiên. AutoSort bị bỏ qua.
Nếu bạn muốn thay đổi icon hiển thị khi sắp xếp có thể sử dụng 2 thuộc tính SortGlyphAscending và SortGlyphDescending. Bạn cũng có thể vô hiệu hóa các glyph sắp xếp, bằng cách đặt thuộc tính đính kèm ShowSortGlyph thành false. Glyph sort chỉ được hiển thị khi sử dụng chế độ sắp xếp tự động (AutoSort = true). Trường hợp sắp xếp tùy chỉnh sử dụng thuộc tính Command chưa được xử lý.
<ListView ItemsSource="{Binding Persons}"
IsSynchronizedWithCurrentItem="True"
local:GridViewSort.AutoSort="True"
local:GridViewSort.SortGlyphAscending="/Images/sort-ascending.png"
local:GridViewSort.SortGlyphDescending="/Images/sort-descending.png">
Bài viết có tham khảo nguồn Thomas Levesque's .NET blog (opens new window)