WPF DataGrid negeert SortDescription

Ik heb hier een vreemd probleem met betrekking tot het sorteren van een wpf DataGrid (System.Windows.Controls.DataGrid in .NET 4.0).

De ItemsSource is gebonden aan een eigenschap van het datacontext-object:


FahrtenView ziet er als volgt uit:

    public ICollectionView FahrtenView
    {
        get
        {
            var view = CollectionViewSource.GetDefaultView(_fahrten);
            view.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending));
            return view;
        }
    }

De DataGrid wordt gesorteerd. Het wordt echter pas de eerste keer dat een DataContext wordt toegewezen, gesorteerd. Daarna zorgt het wijzigen van de DataContext (door het selecteren van een ander "ouderlijk" object in een gegevenshiërarchie) er nog steeds voor dat de eigenschap FahrtenView wordt geëvalueerd (ik kan een BP inbrengen en debugger stopt daar) maar de toegevoegde sorteerbeschrijving wordt volledig genegeerd, vandaar sorteren niet meer werken.

Zelfs bellen fahrtenDG.Items.Refresh() op elke DataContextChange helpt niet.

Ik ben er vrij zeker van dat dit de manier is om te gaan als het gaat om het sorteren van een wpf DataGrid, is het niet? Dus waarom weigert het om zo hardnekkig te werken nadat het zijn werk perfect gedaan heeft de eerste keer dat het gebeld wordt?

Enig idee? Ik zou heel dankbaar zijn.

cheers, Hendrik

8
Yepp, je hebt gelijk. Gedaan!
toegevoegd de auteur Hendrik Wiese, de bron
je zou je update als een antwoord moeten toevoegen en het dan accepteren (wanneer je kunt)
toegevoegd de auteur John Gardner, de bron

7 antwoord

Ik heb geërfd van DataGrid om een ​​glimp op te vangen van zijn lef. Wat ik heb gevonden is dat om enkele mysterieuze redenen, hoewel de eerste keer OnItemsSourceChanged wordt aangeroepen, alles er goed uitziet, in elke volgende aanroep van OnItemsSourceChanged de SortDescription-lijst van de ItemsSource verzamelweergave is leeg .

Om die reden heb ik een aangepaste SetupSortDescription -gebeurtenis toegevoegd die aan het einde van OnItemsSourceChanged wordt aangeroepen. Nu voeg ik de sorteerbeschrijvingen toe aan de gebeurtenishandlerfunctie, die als een charme werkt.

Ik beschouw dit als een fout in de WPF-toolkit DataGrid.

Hier is mijn onderdrukte OnItemsSourceChanged

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        if (SetupSortDescriptions != null && (newValue != null)) 
            SetupSortDescriptions(this, new ValueEventArgs((CollectionView)newValue)); 

        base.OnItemsSourceChanged(oldValue, newValue);
    }
8
toegevoegd
Graag gedaan vriend.
toegevoegd de auteur Hendrik Wiese, de bron
Bedankt! Ik heb dit een beetje verbeterd om MVVM te gebruiken in plaats van een evenement - door een koppeling op te stellen voor een lijst met Sorteerbeschrijvingen die je eenmalig instelt. Zie mijn andere antwoord.
toegevoegd de auteur Jon Barker, de bron
Heel erg bedankt voor je bericht, Hendrik! Het is inderdaad onmogelijk om deze bug te omzeilen door alleen beeldmodellen - men moet een aangepaste DataGrid implementeren. Ik maakte een kleine wijziging met behulp van bijgevoegde eigenschap in plaats van een gebeurtenis (code hieronder).
toegevoegd de auteur kat, de bron
Bedankt! Dit maakte me gek! Ik heb je code aangepast om aan mijn behoeften te voldoen. Het blijft in principe de soort beschrijvingen en herstelt ze wanneer ze worden weggeblazen. Dit kan anderen helpen: zie hieronder.
toegevoegd de auteur adamjhilton, de bron

Ik heb het antwoord van Hendrik een beetje verbeterd om MVVM te gebruiken in plaats van een evenement.

    public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register("SortDescriptions", typeof(List), typeof(ReSolverDataGrid), new PropertyMetadata(null));

    /// 
    /// Sort descriptions for when grouped LCV is being used. Due to bu*g in WCF this must be set otherwise sort is ignored.
    /// 
    /// 
    /// IN YOUR XAML, THE ORDER OF BINDINGS IS IMPORTANT! MAKE SURE SortDescriptions IS SET BEFORE ITEMSSOURCE!!! 
    /// 
    public List SortDescriptions
    {
        get { return (List)GetValue(SortDescriptionsProperty); }
        set { SetValue(SortDescriptionsProperty, value); }
    }

    protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
    {
        //Only do this if the newValue is a listcollectionview - in which case we need to have it re-populated with sort descriptions due to DG bug
        if (SortDescriptions != null && ((newValue as ListCollectionView) != null))
        {
            var listCollectionView = (ListCollectionView)newValue;
            listCollectionView.SortDescriptions.AddRange(SortDescriptions);
        }

        base.OnItemsSourceChanged(oldValue, newValue);
    }
5
toegevoegd
Werkte goed voor mij, goede oplossing.
toegevoegd de auteur willem, de bron
Werkt goed voor mij, zolang ik een CollectionViewSource aan de datagrid verstrek in plaats van de ObservableCollection . Ik heb mijn CollectionViewSource gedefinieerd in de bronnen van het besturingselement en een andere lijst met SortDescription in een statische klasse die ik aan deze nieuwe eigenschap bind.
toegevoegd de auteur Benoit Dufresne, de bron

Ik gebruikte de geïnteresseerde DataGrid van kat om een ​​gedrag voor de wpf DataGrid te creëren.

The behavior saves the initial SortDescriptions and applies them on every change of ItemsSource. You can also provide a IEnumerable which will cause a resort on every change.

Gedrag

public class DataGridSortBehavior : Behavior
{
    public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register(
        "SortDescriptions",
        typeof (IEnumerable),
        typeof (DataGridSortBehavior),
        new FrameworkPropertyMetadata(null, SortDescriptionsPropertyChanged));

    /// 
    ///     Storage for initial SortDescriptions
    /// 
    private IEnumerable _internalSortDescriptions;

    /// 
    ///     Property for providing a Binding to Custom SortDescriptions
    /// 
    public IEnumerable SortDescriptions
    {
        get { return (IEnumerable) GetValue(SortDescriptionsProperty); }
        set { SetValue(SortDescriptionsProperty, value); }
    }


    protected override void OnAttached()
    {
        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid));
        if (dpd != null)
        {
            dpd.AddValueChanged(AssociatedObject, OnItemsSourceChanged);
        }
    }

    protected override void OnDetaching()
    {
        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid));
        if (dpd != null)
        {
            dpd.RemoveValueChanged(AssociatedObject, OnItemsSourceChanged);
        }
    }

    private static void SortDescriptionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGridSortBehavior)
        {
            ((DataGridSortBehavior) d).OnItemsSourceChanged(d, EventArgs.Empty);                
        }
    }

    public void OnItemsSourceChanged(object sender, EventArgs eventArgs)
    {
       //save description only on first call, SortDescriptions are always empty after ItemsSourceChanged
        if (_internalSortDescriptions == null)
        {
           //save initial sort descriptions
            var cv = (AssociatedObject.ItemsSource as ICollectionView);
            if (cv != null)
            {
                _internalSortDescriptions = cv.SortDescriptions.ToList();
            }
        }
        else
        {
           //do not resort first time - DataGrid works as expected this time
            var sort = SortDescriptions ?? _internalSortDescriptions;

            if (sort != null)
            {
                sort = sort.ToList();
                var collectionView = AssociatedObject.ItemsSource as ICollectionView;
                if (collectionView != null)
                {
                    using (collectionView.DeferRefresh())
                    {
                        collectionView.SortDescriptions.Clear();
                        foreach (var sorter in sort)
                        {
                            collectionView.SortDescriptions.Add(sorter);
                        }
                    }
                }
            }
        }
    }
}

XAML met optionele SortDescriptions-parameter


    
        
    

ViewModel ICollectionView Setup

View = CollectionViewSource.GetDefaultView(_collection);
View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));

Optioneel: ViewModel-eigenschap voor het verstrekken van veranderbare Sorteerbeschrijvingen

public IEnumerable SortDescriptions
{
    get
    {
        return new List {new SortDescription("Sequence", ListSortDirection.Ascending)};
    }
}
3
toegevoegd

Als u CollectionViewSource.GetDefaultView (..) in dezelfde verzameling aanroept, krijgt u hetzelfde collectieweergaveobject terug, wat zou kunnen verklaren waarom het toevoegen van een identieke sortdescription struct geen wijziging teweegbrengt.

fahrtenDG.Items.Refresh() kan niet werken omdat u de gebonden verzameling niet vernieuwt.

CollectionViewSource.GetDefaultView (_fahrten) .Refresh() zou moeten werken - ik zou er een verwijzing naar houden.

Van je uitleg krijg ik niet de verandering van datacontext - verander je het in een nieuw object? Als dat het geval is, moeten al uw bindingen opnieuw worden geëvalueerd. Is dit altijd dezelfde verzameling en uw Index-eigenschap op de lijstelementen verandert, en daarom verwacht u een wijziging - zo ja, uw lijstelement heeft mogelijk een INotifyPropertyChanged-implementatie nodig, want als de verzameling niet verandert, hoeft u niet verder te gaan toevlucht.

De implementatie van OnItemsSourceChanged (..) lijkt een hack :)

1
toegevoegd

Ik probeerde dit probleem te omzeilen met het weergavemodel - door ICollectionUITZICHT opnieuw te maken in de getter en verwoed DeferRefresh() aan te roepen. Ik kan echter bevestigen dat Hendriks oplossing de enige is die betrouwbaar werkt. Ik wilde de volledige code hieronder plaatsen voor het geval het iemand zou helpen.

UITZICHT


UITZICHT MODEL

public ObservableCollection Info { get; private set; }
public ICollectionUITZICHT InfoSorted { get; private set; }
public IEnumerable PermanentSort { get; private set; }

DOUANE CONTROLE

public class SortableDataGrid : DataGrid
    {
        public static readonly DependencyProperty PermanentSortProperty = DependencyProperty.Register(
            "PermanentSort",
            typeof(IEnumerable),
            typeof(SortableDataGrid),
            new FrameworkPropertyMetadata(null));

        public IEnumerable PermanentSort
        {
            get { return (IEnumerable)this.GetValue(PermanentSortProperty); }
            set { this.SetValue(PermanentSortProperty, value); }
        }

        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            var sort = this.PermanentSort;
            if (sort != null)
            {
                sort = sort.ToList();
                var collectionUITZICHT = newValue as ICollectionUITZICHT;
                if (collectionUITZICHT != null)
                {
                    using (collectionUITZICHT.DeferRefresh())
                    {
                        collectionUITZICHT.SortDescriptions.Clear();
                        foreach (SortDescription sorter in sort)
                        {
                            collectionUITZICHT.SortDescriptions.Add(sorter);
                        }
                    }
                }
            }

            base.OnItemsSourceChanged(oldValue, newValue);
        }
    }
1
toegevoegd

Ik sta achter aanpak van Juergen voor het gebruik van bijgevoegd gedrag. Omdat mijn versie van dit probleem echter ontstond toen ik het CollectionViewSource-object in de viewmodelklasse had gedeclareerd, vond ik het directer om het probleem op te lossen door de gebeurtenishandler SortDescriptions_CollectionChanged toe te voegen, zoals weergegeven in de onderstaande code . Deze code valt volledig binnen de klasse van het weergavemodel.

public CollectionViewSource FilteredOptionsView
{
    get
    {
        if (_filteredOptionsView == null)
        {
            _filteredOptionsView = new CollectionViewSource
            {
                Source = Options,
                IsLiveSortingRequested = true
            };
            SetOptionsViewSorting(_filteredOptionsView);
            _filteredOptionsView.View.Filter = o => ((ConstantOption)o).Value != null;
        }
        return _filteredOptionsView;
    }
}
private CollectionViewSource _filteredOptionsView;

protected void SetOptionsViewSorting(CollectionViewSource viewSource)
{
   //define the sorting
    viewSource.SortDescriptions.Add(_optionsViewSortDescription);
   //subscribe to an event in order to handle a bug caused by the DataGrid that may be
   //bound to the CollectionViewSource
    ((INotifyCollectionChanged)viewSource.View.SortDescriptions).CollectionChanged
                                    += SortDescriptions_CollectionChanged;
}

protected static SortDescription _optionsViewSortDescription
                    = new SortDescription("SortIndex", ListSortDirection.Ascending);

void SortDescriptions_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
{
    var collection = sender as SortDescriptionCollection;
    if (collection == null) return;
   //The SortDescriptions collection should always contain exactly one SortDescription.
   //However, when DataTemplate containing the DataGrid bound to the ICollectionView
   //is unloaded, the DataGrid erroneously clears the collection.
    if (collection.None())
        collection.Add(_optionsViewSortDescription);
}
0
toegevoegd

Bedankt! Dit maakte me gek! Ik heb je code aangepast om aan mijn behoeften te voldoen. Het blijft in principe de soort beschrijvingen en herstelt ze wanneer ze worden weggeblazen. Dit kan anderen helpen:

private List SortDescriptions = null;

protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
 if (newValue is CollectionView collectionView)
  if (SortDescriptions == null)
   SortDescriptions = new List(collectionView.SortDescriptions);
  else
   foreach (SortDescription sortDescription in SortDescriptions)
    collectionView.SortDescriptions.Add(sortDescription);

 base.OnItemsSourceChanged(oldValue, newValue);
}
0
toegevoegd