Q105. ListBox 等で、コレクションを操作せずソートするには

A.ItemoSource でバインドされたコレクションを操作せずに コントロールで並び替えを行いたい場合が多々あると思います。その場合、リソース内で CollectionViewSource を使えば、コレクションを操作せずにソートすることができます。


以下サンプルです。ViewModel で用意したコレクションを、三つの ListBox を使いプロパティごとにソートして表示します。


まず、Model と ViewModel のコードです。ViewModel のコンストラクタで、従業員のコレクションを生成しています。コレクションは ListBox とバインドさせるため通知プロパティで実装しています。

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace CollectionViewSourceSample {

    /// <summary>従業員クラス</summary>
    public class Person {
        public string Name { get; set; }
        public string Post { get; set; }
        public int Age { get; set; }
    }

    /// <summary>ビューモデル</summary>
    class ViewModel : INotifyPropertyChanged {

        /// <summary>コンストラクタ</summary>
        public ViewModel() {
            this.Persons = new ObservableCollection<Person>();
            this.Persons.Add(new Person() { Name = "秋山", Post = "営業", Age = 36 });
            this.Persons.Add(new Person() { Name = "東郷", Post = "総務", Age = 22 });
            this.Persons.Add(new Person() { Name = "児玉", Post = "役員", Age = 50 });
            this.Persons.Add(new Person() { Name = "山本", Post = "開発", Age = 32 });
            this.Persons.Add(new Person() { Name = "乃木", Post = "販売", Age = 29 });
            this.Persons.Add(new Person() { Name = "大山", Post = "人事", Age = 41 });
            this.Persons.Add(new Person() { Name = "山縣", Post = "営業", Age = 24 });
        }

        Person _person;

        /// <summary>選択された従業員</summary>
        public Person Person {
            get { return _person; }
            set {
                if (_person == value) return;
                _person = value;
                OnPropertyChanged("Person");
            }
        }

        ObservableCollection<Person> _persons;

        /// <summary>従業員のコレクション</summary>
        public ObservableCollection<Person> Persons {
            get { return _persons; }
            set {
                if (_persons == value) return;
                _persons = value;
                OnPropertyChanged("Persons");
            }
        }

        #region INotifyPropertyChanged メンバ

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string name) {
            if (PropertyChanged == null) return;
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion
    }
}


XAML です。Window.Resources 内で「名前」・「役職」・「年齢」用のリソースを用意します。「年齢」のみ降順にしてみました。あとは用意したリソースを ListBox にバインドさせるだけです。

<Window x:Class="CollectionViewSourceSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        xmlns:local="clr-namespace:CollectionViewSourceSample"
        Title="MainWindow" Height="300" Width="520" >
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <!-- 名前でソート -->
        <CollectionViewSource x:Key="names"  Source="{Binding Persons}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription  PropertyName="Name" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
        <!-- 役職でソート -->
        <CollectionViewSource x:Key="posts"  Source="{Binding Persons}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription  PropertyName="Post" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
        <!-- 年齢でソート(降順) -->
        <CollectionViewSource x:Key="ages"  Source="{Binding Persons}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription  PropertyName="Age" Direction="Descending" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="4"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="4"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="4"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="22"/>
            <RowDefinition/>
            <RowDefinition Height="4"/>
        </Grid.RowDefinitions>
        <Grid Grid.ColumnSpan="5" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="22"/>
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="110"/>
                <ColumnDefinition Width="90"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="1" Text="選択された従業員" 
                       HorizontalAlignment="Right" VerticalAlignment="Center" Padding="4,0" />
            <TextBox Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"
                     Text="{Binding Person.Name}" IsReadOnly="True" />
            <TextBlock Grid.Row="1" Grid.Column="2" Text="役職" 
                       HorizontalAlignment="Right" VerticalAlignment="Center" Padding="4,0" />
            <TextBox Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" 
                     Text="{Binding Person.Post}" IsReadOnly="True" />
            <TextBlock Grid.Row="1" Grid.Column="4" Text="年齢" 
                       HorizontalAlignment="Right" VerticalAlignment="Center" Padding="4,0" />
            <TextBox Grid.Row="1" Grid.Column="5" VerticalAlignment="Center" 
                     Text="{Binding Person.Age}" IsReadOnly="True" />
        </Grid>
        <TextBlock Grid.Row="1" Grid.Column="1" 
                   Text="名前でソート" VerticalAlignment="Center" />
        <ListBox Grid.Row="2" Grid.Column="1" 
                 ItemsSource="{Binding Source={StaticResource names}}" 
                 DisplayMemberPath="Name" SelectedItem="{Binding Person}" />
        <TextBlock Grid.Row="1" Grid.Column="3" 
                   Text="役職でソート" VerticalAlignment="Center" />
        <ListBox Grid.Row="2" Grid.Column="3" 
                 ItemsSource="{Binding Source={StaticResource posts}}" 
                 DisplayMemberPath="Post" SelectedItem="{Binding Person}" />
        <TextBlock Grid.Row="1" Grid.Column="5" 
                   Text="年齢でソート(降順)" VerticalAlignment="Center" />
        <ListBox Grid.Row="2" Grid.Column="5" 
                 ItemsSource="{Binding Source={StaticResource ages}}"  
                 DisplayMemberPath="Age" SelectedItem="{Binding Person}" />
    </Grid>
</Window>


WPF FAQ の目次に戻る