基になるデータが変更されたときに自分を再ソートする方法を探しています。DataGrid
(設定は非常に標準的です:DataGridのItemSource
プロパティは;にバインドされていObservableCollection
ます;列はDataGridTextColumns
; DataGrid内のデータはObservableCollection内の変更に正しく反応します;マウスでクリックすると並べ替えは正常に機能します)
何か案は ?
基になるデータが変更されたときに自分を再ソートする方法を探しています。DataGrid
(設定は非常に標準的です:DataGridのItemSource
プロパティは;にバインドされていObservableCollection
ます;列はDataGridTextColumns
; DataGrid内のデータはObservableCollection内の変更に正しく反応します;マウスでクリックすると並べ替えは正常に機能します)
何か案は ?
午後中ずっとかかりましたが、驚くほどシンプルで、短く、効率的な解決策をついに見つけました。
問題の UI コントロール (ここでは a DataGrid
) の動作を制御するには、単純にCollectionViewSource
. これは、MVMM パターンを完全に壊すことなく、ViewModel 内の UI コントロールの一種の代表として機能します。
ViewModel で、 aCollectionViewSource
と通常の両方を宣言し、 を次のようObservableCollection<T>
にラップしCollectionViewSource
ますObservableCollection
。
// Gets or sets the CollectionViewSource
public CollectionViewSource ViewSource { get; set; }
// Gets or sets the ObservableCollection
public ObservableCollection<T> Collection { get; set; }
// Instantiates the objets.
public ViewModel () {
this.Collection = new ObservableCollection<T>();
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.Collection;
}
次に、アプリケーションの View 部分では、 のを に直接バインドするのではなくItemsSource
、CollectionControl
の View プロパティにバインドする以外に何もする必要はありません。CollectionViewSource
ObservableCollection
<DataGrid ItemsSource="{Binding ViewSource.View}" />
この時点から、ViewModel 内のオブジェクトを使用しCollectionViewSource
て、View 内の UI コントロールを直接操作できます。
たとえば、私の主な問題であったように、並べ替えは次のようになります。
// Specify a sorting criteria for a particular column
ViewSource.SortDescriptions.Add(new SortDescription ("columnName", ListSortDirection.Ascending));
// Let the UI control refresh in order for changes to take place.
ViewSource.View.Refresh();
ご覧のとおり、非常にシンプルで直感的です。これが他の人が私を助けてくれたのと同じように役立つことを願っています.
これは答えというよりも明確にするためのものですが、WPFは常にICollectionView
ソースコレクションではなく、にバインドします。CollectionViewSource
コレクションビューを作成/取得するために使用されるメカニズムにすぎません。
WPFでコレクションビューをより有効に活用するのに役立つトピックに関する優れたリソースは次のとおりです。http://bea.stollnitz.com/blog/ ?p = 387
XAMLで使用CollectionViewSource
すると、実際にコードをいくつか簡略化できます。
<Window.Resources>
<CollectionViewSource Source="{Binding MySourceCollection}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ColumnName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<DataGrid ItemsSource="{Binding Source={StaticResource cvs}}">
</DataGrid>
MVVMパターンに従う場合、ビューモデルは常にコレクションビューを公開する必要があると主張する人もいますが、私の意見では、それはユースケースに依存します。ビューモデルがコレクションビューと直接対話することがない場合は、XAMLで構成する方が簡単です。
Sellmeadog による回答は、過度に複雑であるか、時代遅れです。とてもシンプルです。あなたがしなければならないことは次のとおりです。
<UserControl.Resources>
<CollectionViewSource
Source="{Binding MyCollection}"
IsLiveSortingRequested="True"
x:Key="MyKey" />
</UserControl.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource MyKey} }" >...
明らかに簡単な方法が見当たらないので、Attached Behavior を試してみます。それはちょっと野郎化ですが、あなたが望むものを与えるでしょう:
public static class DataGridAttachedProperty
{
public static DataGrid _storedDataGrid;
public static Boolean GetResortOnCollectionChanged(DataGrid dataGrid)
{
return (Boolean)dataGrid.GetValue(ResortOnCollectionChangedProperty);
}
public static void SetResortOnCollectionChanged(DataGrid dataGrid, Boolean value)
{
dataGrid.SetValue(ResortOnCollectionChangedProperty, value);
}
/// <summary>
/// Exposes attached behavior that will trigger resort
/// </summary>
public static readonly DependencyProperty ResortOnCollectionChangedProperty =
DependencyProperty.RegisterAttached(
"ResortOnCollectionChangedProperty", typeof (Boolean),
typeof(DataGridAttachedProperty),
new UIPropertyMetadata(false, OnResortOnCollectionChangedChange));
private static void OnResortOnCollectionChangedChange
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
_storedDataGrid = dependencyObject as DataGrid;
if (_storedDataGrid == null)
return;
if (e.NewValue is Boolean == false)
return;
var observableCollection = _storedDataGrid.ItemsSource as ObservableCollection;
if(observableCollection == null)
return;
if ((Boolean)e.NewValue)
observableCollection.CollectionChanged += OnCollectionChanged;
else
observableCollection.CollectionChanged -= OnCollectionChanged;
}
private static void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.OldItems == e.NewItems)
return;
_storedDataGrid.Items.Refresh()
}
}
次に、次の方法でアタッチできます。
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter
Property="AttachedProperties:DataGridAttachedProperty.ResortOnCollectionChangedProperty"
Value="true"
/>
</Style>
</DataGrid.Style>
この問題を抱えている他の人にとっては、これで始めることができます... INotifyPropertyChanged アイテムのコレクションがある場合は、ObservableCollection の代わりにこれを使用できます - コレクション内の個々のアイテムが変更されると更新されます: 注: これはアイテムにフラグを立てるため削除してから再度追加すると (実際には削除および追加されていなくても)、選択内容が同期しなくなる場合があります。私の小さな個人的なプロジェクトには十分ですが、顧客にリリースする準備はできていません...
public class ObservableCollection2<T> : ObservableCollection<T>
{
public ObservableCollection2()
{
this.CollectionChanged += ObservableCollection2_CollectionChanged;
}
void ObservableCollection2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (object o in e.OldItems)
remove(o);
if (e.NewItems != null)
foreach (object o in e.NewItems)
add(o);
}
void add(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if(ipc!=null)
ipc.PropertyChanged += Ipc_PropertyChanged;
}
void remove(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if (ipc != null)
ipc.PropertyChanged -= Ipc_PropertyChanged;
}
void Ipc_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs f;
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, sender);
base.OnCollectionChanged(f);
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
base.OnCollectionChanged(f);
}
}