2014年9月10日水曜日

WPF ListView (GridView) のソート

WPFにはListViewのGridViewモードとDataGridの2つのコントロールで表形式の表示が行えます。DataGridの方はソート機能が組み込まれていますが、ListViewの方は自前でソートコードを記述する必要があります。MSDNにも方法 : ヘッダーがクリックされたときに GridView 列を並べ替えるという記事が用意されていたりしますがいまいちパッとしません。 そこで簡単に扱えるようにライブラリ化しました。 使い方は

<ListView ItemsSource="{Binding SelectedValue.Files, ElementName=tree}" xmlns:v="clr-namespace:Sayuri.Windows;assembly=GridViewSortLibrary" v:GridViewSort.IsEnabled="True"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView> <GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}" /> <GridViewColumn Header="サイズ" v:GridViewSort.MemberPath="Length"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock TextAlignment="Right" Text="{Binding Length, StringFormat=N0}" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView>
こんな感じです。要点は
  • xmlnsでアセンブリ・名前空間を指定します
  • <ListView>に添付プロパティGridViewSort.IsEnabled="True"を指定します
  • <GridViewColumn>にDisplayMemberBindingの指定があればそのプロパティでソートが行われます
  • <GridViewColumn>にDisplayMemberBindingを指定できない場合はGridViewSort.MemberPathにプロパティ名をしてします
ソースコードはGistに貼り付けておきました。
namespace Sayuri.Windows
open System
open System.ComponentModel
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Documents
open System.Windows.Media
type GridViewSort () =
static let ascending = Geometry.Parse "M 0 4 L 3.5 0 L 7 4 Z"
static let descending = Geometry.Parse "M 0 0 L 3.5 4 L 7 0 Z"
static let applySort (listView : ListView) (columnHeader : GridViewColumnHeader) propertyName =
let adorner : Adorner = GridViewSort.GetSortIcon listView
if adorner <> null then
(AdornerLayer.GetAdornerLayer adorner.AdornedElement).Remove adorner
let items = listView.Items
let direction, geometry, insert =
if items.SortDescriptions.Count = 0 then ListSortDirection.Ascending, ascending, true
elif (let current = items.SortDescriptions.[0] in
current.PropertyName <> propertyName || current.Direction = ListSortDirection.Descending) then ListSortDirection.Ascending, ascending, false
else ListSortDirection.Descending, descending, false
if String.IsNullOrEmpty propertyName then
GridViewSort.SetSortIcon(listView, null)
else
let sortIcon = { new Adorner(columnHeader) with
override __.OnRender (drawingContext) =
base.OnRender drawingContext
if columnHeader.RenderSize.Width < 20.0 then () else
drawingContext.PushTransform <| TranslateTransform(columnHeader.RenderSize.Width - 15.0, (columnHeader.RenderSize.Height - 5.0) / 2.0)
drawingContext.DrawGeometry(Brushes.Black, null, geometry)
drawingContext.Pop() }
(AdornerLayer.GetAdornerLayer columnHeader).Add sortIcon
GridViewSort.SetSortIcon(listView, sortIcon)
let description = SortDescription(propertyName, direction)
if insert then items.SortDescriptions.Add description
else items.SortDescriptions.[0] <- description
static let columnHeaderClick = RoutedEventHandler(fun _ e ->
let clickedHeader = e.OriginalSource :?> GridViewColumnHeader
let clickedColumn = clickedHeader.Column
if clickedColumn = null then () else
let propertyName = GridViewSort.GetMemberPath clickedColumn
let propertyName = if String.IsNullOrEmpty propertyName |> not then propertyName else
match clickedColumn.DisplayMemberBinding with
| :? Binding as binding when binding.Path <> null -> binding.Path.Path
| _ -> null
if String.IsNullOrEmpty propertyName |> not then
let rec loop reference =
match VisualTreeHelper.GetParent reference with
| :? ListView as listView -> Some listView
| null -> None
| parent -> loop parent
loop clickedHeader |> Option.iter (fun listView -> applySort listView clickedHeader propertyName))
static member val SortIconProperty = DependencyProperty.RegisterAttached("SortIcon", typeof<Adorner>, typeof<GridViewSort>)
static member private GetSortIcon (target : ListView) =
target.GetValue GridViewSort.SortIconProperty :?> Adorner
static member SetSortIcon (target : ListView, value : Adorner) =
target.SetValue(GridViewSort.SortIconProperty, value)
static member val MemberPathProperty = DependencyProperty.RegisterAttached("MemberPath", typeof<string>, typeof<GridViewSort>)
static member GetMemberPath (target : GridViewColumn) =
target.GetValue GridViewSort.MemberPathProperty :?> string
static member SetMemberPath (target : GridViewColumn, value : string) =
target.SetValue(GridViewSort.MemberPathProperty, value)
static member val IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof<bool>, typeof<GridViewSort>, UIPropertyMetadata(false, fun o e ->
let listView = o :?> ListView
match downcast e.OldValue, downcast e.NewValue with
| true, false -> listView.RemoveHandler(GridViewColumnHeader.ClickEvent, columnHeaderClick)
| false, true -> listView.AddHandler(GridViewColumnHeader.ClickEvent, columnHeaderClick)
| _, _ -> ()))
static member GetIsEnabled (target : ListView) =
target.GetValue GridViewSort.IsEnabledProperty :?> bool
static member SetIsEnabled (target : ListView, value : bool) =
target.SetValue(GridViewSort.IsEnabledProperty, value)
view raw GridViewSort.fs hosted with ❤ by GitHub

作成にあたって次の2つの記事を参考にしました。

0 件のコメント: