web-dev-qa-db-ja.com

境界データが変更された後にWPF DataGridを再ソートします

re-sort my DataGridへの方法を探しています。基になるデータにchangedが含まれている場合。

(設定は非常に標準です。DataGridのItemSourceプロパティはObservableCollectionにバインドされています。列はDataGridTextColumnsです。DataGrid内のデータは、ObservableCollection内の変更に正しく反応します。マウスでクリックするとソートが正常に機能します)

何か案は ?

29
marc wellman

それは私に午後全体を要しましたが、私は最終的に解決策を見つけました意外にもsimpleshortおよびefficient

問題のUIコントロール(ここではDataGrid)の動作を制御するには、単にCollectionViewSourceを使用します。これは、MVMMパターンを完全に壊すことなく、ViewModel内のUIコントロールの一種の代表として機能します。

ViewModelで、CollectionViewSourceと通常のObservableCollection<T>の両方を宣言し、CollectionViewSourceObservableCollectionで囲みます。

// 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部分で、ItemsSourceCollectionControlCollectionViewSourceに直接バインドする代わりに、ObservableCollectionのViewプロパティにバインドするだけです。

<DataGrid ItemsSource="{Binding ViewSource.View}" />

この時点から、ViewModelでCollectionViewSourceオブジェクトを使用して、ビューの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();

とてもシンプルで直感的です。これが私を助けてくれたような他の人々を助けることを願っています。

28
marc wellman

これは明確にするためのものですが、WPFalwaysは、ソースコレクションではなく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で構成する方が簡単です。

18
sellmeadog

LiveSorting を使用します。 wpf.2000things.comの例

3
AnjumSKhan

Sellmeadogによる回答は、複雑すぎるか、古くなっています。とてもシンプルです。あなたがしなければならないすべてはです:

<UserControl.Resources>
    <CollectionViewSource 
        Source="{Binding MyCollection}" 
        IsLiveSortingRequested="True" 
        x:Key="MyKey" />
</UserControl.Resources>

<DataGrid ItemsSource="{Binding Source={StaticResource MyKey} }" >...
1
Anthony Nichols

明らかに簡単な方法は見当たらないので、添付動作を試してみます。これは少し粗雑ですが、あなたが望むものをあなたに与えます:

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>
0
Justin Pihony

この問題を抱えている他の人にとって、これはあなたを始めるかもしれません... 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);
    }
}
0
DanW