2010-08-31 11 views
17

Używam DataGrid związany z CollectionViewSource (gracze), sam związany z aktualnie wybranego elementu ListBox (poziomy), każdy artykuł zawierający zbiór być klasyfikowane/wyświetlane w DataGrid:CollectionViewSource tylko za pierwszym razem sortowania jest związany ze źródłem

<ListBox Name="lstLevel" 
     DisplayMemberPath="Name" 
     IsSynchronizedWithCurrentItem="True" /> 

...

<!-- DataGrid source, as a CollectionViewSource to allow for sorting and/or filtering --> 
<CollectionViewSource x:Key="Players" 
         Source="{Binding ElementName=lstLevel, 
             Path=SelectedItem.Players}"> 
    <CollectionViewSource.SortDescriptions> 
    <scm:SortDescription PropertyName="Name" /> 
    </CollectionViewSource.SortDescriptions> 
</CollectionViewSource> 

...

<DataGrid Name="lstPlayers" AutoGenerateColumns="False" 
      CanUserSortColumns="False" 
      ItemsSource="{Binding Source={StaticResource Players}}"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Header="Name" 
          Binding="{Binding Path=Name, Mode=TwoWay}" 
          Width="*" /> 
     <DataGridTextColumn Header="Age" 
          Binding="{Binding Path=Age, Mode=TwoWay}" 
          Width="80"> 
     </DataGridTextColumn> 
    </DataGrid.Columns> 
    </DataGrid> 

(cały kod C# here, kod XAML here, cały projekt testowy here - oprócz DataGrid Dodałem prosty ListBox dla graczy, aby upewnić się, że nie był to problem DataGrid)

Problem polega na tym, że gracze są sortowani przy pierwszym wyświetleniu, ale gdy tylko wybiorę inny poziom z ListBox, nie są już posortowani. Ponadto modyfikowanie nazw, które pojawią się po raz pierwszy, spowoduje posortowanie ich zgodnie ze zmianami, ale już nie, gdy poziom zostanie zmieniony.

Wygląda na to, że zmiana źródła CollectionViewSource w jakiś sposób łamie funkcję sortowania, ale nie mam pojęcia, dlaczego, ani jak to naprawić. Czy ktoś wie, co robię źle?

(Zrobiłem test z filtrem, ale utrzymuje, że jeden działa zgodnie z oczekiwaniami)

Ramy jest .NET 4.

+0

Doświadczyłem już tego samego - zamiast tworzyć nowy obiekt za każdym razem, można usunąć i ponownie wstawić jego zawartość? – Dave

+0

Oprócz dodatkowej pracy, która niepotrzebnie rozdzieliłaby zarządzaną stertę przez tworzenie/zwalnianie obiektów, wolałbym tego uniknąć, gdybym mogła. – RedGlyph

Odpowiedz

10

Wielkie pytanie i ciekawe spostrzeżenie. Po dokładniejszym sprawdzeniu okazuje się, że DataGrid wyczyści opisy sortowania poprzedniego ItemsSource, zanim ustawi się nowy. Oto jego kod dla OnCoerceItemsSourceProperty:

private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue) 
{ 
    DataGrid grid = (DataGrid) d; 
    if ((baseValue != grid._cachedItemsSource) && (grid._cachedItemsSource != null)) 
    { 
     grid.ClearSortDescriptionsOnItemsSourceChange(); 
    } 
    return baseValue; 
} 

To zachowanie występuje tylko w DataGrid. Jeśli zamiast tego użyłeś ListBox (aby wyświetlić kolekcję "Players" powyżej), zachowanie będzie inne i SortDescriptions pozostaną po wybraniu różnych elementów z głównego datagridu.

Domyślam się, że rozwiązaniem jest jakoś ponowne zastosowanie opisów sortowania kolekcji Gracze, gdy zmieni się wybrany element w rodzica DataGrid (tj. "LstLevel").

Jednak nie jestem w 100% pewien i prawdopodobnie potrzebuje więcej testów/badań. Mam nadzieję, że udało mi się coś wnieść. =)

EDIT:

Jako zaproponowane rozwiązanie, które można umieścić obsługi dla lstLevel.SelectionChanged w konstruktorze, zanim ustawienie właściwości lstLevel.ItemsSource. Coś takiego:

lstLevel.SelectionChanged += 
    (sender, e) => 
    { 
     levels.ToList().ForEach((p) => 
     { 
      CollectionViewSource.GetDefaultView(p.Players) 
       .SortDescriptions 
       .Add(new SortDescription("Name", ListSortDirection.Ascending)); 
     }); 
    }; 

lstLevel.ItemsSource = levels; 

EDIT2:

W odpowiedzi na problemy jesteś napotykają w odniesieniu do nawigacji klawiatury, sugeruję, że zamiast obsługi „CurrentChanged” zdarzenie obsłużyć lstLevel Zamiast tego wybrano zdarzenie SelectedChanged. Zamieszczam niezbędne aktualizacje, które musisz wprowadzić poniżej.Wystarczy skopiować i wkleić kod i sprawdzić, czy działa poprawnie.

XAML:

<!-- Players data, with sort on the Name column --> 
<StackPanel Grid.Column="1"> 
    <Label>DataGrid:</Label> 
    <DataGrid Name="lstPlayers" AutoGenerateColumns="False" 
     CanUserSortColumns="False" 
     ItemsSource="{Binding ElementName=lstLevel, Path=SelectedItem.Players}"> 
     <DataGrid.Columns> 
      <DataGridTextColumn Header="Name" 
         Binding="{Binding Path=Name, Mode=TwoWay}" 
         Width="*" /> 
      <DataGridTextColumn Header="Age" 
         Binding="{Binding Path=Age, Mode=TwoWay}" 
         Width="80"> 
      </DataGridTextColumn> 
     </DataGrid.Columns> 
    </DataGrid> 
</StackPanel> 

<StackPanel Grid.Column="2"> 
    <Label>ListBox:</Label> 
    <ListBox ItemsSource="{Binding ElementName=lstLevel, Path=SelectedItem.Players}" DisplayMemberPath="Name" /> 
</StackPanel> 

Code-tył (konstruktor):

lstLevel.SelectionChanged += 
    (sender, e) => 
    { 
     levels.ToList().ForEach((p) => 
     { 
      CollectionViewSource.GetDefaultView(p.Players) 
       .SortDescriptions 
       .Add(new SortDescription("Name", ListSortDirection.Ascending)); 
     }); 
    }; 
lstLevel.ItemsSource = levels; 
+0

++, bardzo wnikliwe! Dodałem oddzielny ListBox, ponieważ nie ufałem DataGrid, ale nie próbowałem testować samego ListBox bez DataGrid. Im więcej używam tej kontroli, tym bardziej ekscentryczna okazuje się (3 błędy zgłosiły się w ciągu ostatnich 2 dni). Zacznę od sugestii i dam ci znać. Dzięki! – RedGlyph

+1

Dostosowano kilka bitów (nie działało jak jest, ale wystarczająco blisko), zaktualizowano pytanie z obejściem. Dzięki jeszcze raz! – RedGlyph

+0

Cieszę się, że mogę Ci pomóc. Tak, odkryłem, że to dość zaskakujące, że DataGrid wyczyściła SortDescriptions za kulisami. Nie jestem do końca pewien, dlaczego, ale prawdopodobnie istnieje ku temu dobry powód ... albo tak, może to błąd. =) – ASanch

4

Lepszym obejście: CollectionViewSource sorting only the first time it is bound to a source

zaimplementować własną DataGrid:

public class SDataGrid : DataGrid 
{ 
    static SDataGrid() 
    { 
     ItemsControl.ItemsSourceProperty.OverrideMetadata(typeof(SDataGrid), new FrameworkPropertyMetadata((PropertyChangedCallback)null, (CoerceValueCallback)null)); 
    } 
} 

Jedynym elementem wywołującym wywołanie zwrotne w bieżącej implementacji jest wyczyszczenie opisów sortowania. Możesz po prostu "wyciąć" ten kod przez nadpisując metadane. Nieosiągalny w Silverlight: OverrideMetadata API nie jest publiczny. Chociaż nie jestem pewien, czy ten błąd dotyczy Silverlight. Mogą mieć zastosowanie inne rodzaje ryzyka i skutki uboczne.

+0

Mimo że istnieją raporty, że to rozwiązanie nie działa, wydaje się, że działa to w moim kodzie. – linac

5

udało mi się rozwiązać ten problem, po prostu dzwoniąc PropertyChanged o tej własności, że naraża pogląd, pozwalając aktualizacja widoku (i wyczyść sortowania), a następnie dodając opisy rodzaju.

+0

+ 1. To również wydawało się działać dla mnie. Najprostsze rozwiązanie bez problemów związanych z kodem. Dzięki! – Anttu

Powiązane problemy