2010-08-18 16 views
2

J'ai une classe encapsulant un groupe de collections. Je voudrais lier cette classe à une liste pour afficher les éléments dans les collections. La classe implémente IEnumerable. Lorsque la liste est affichée, je m'attends à ce que la méthode IEnumerable.GetEnumerator soit appelée. Ce n'est cependant pas le cas lorsque la méthode GetEnumerator utilise le mot-clé yield. Toutefois, si je renvoyais l'énumérateur d'une collection List, cela fonctionnerait correctement, la méthode GetEnumerator est appelée chaque fois qu'une fenêtre est affichée.Impossible de lier l'implémentation IEnumerable en utilisant le rendement

Qu'y a-t-il de si magique dans l'énumérateur d'une collection de List ??? Quelle est la bonne interface à implémenter pour permettre à un objet WPF d'obtenir des snapshots (mises à jour non nécessaires) ??? Est IList celui à utiliser ??? Le code ci-dessous ajoute un horodatage chaque fois qu'une nouvelle fenêtre est ouverte. Cependant, la zone de liste ne montre jamais plus d'un horodatage, qui est le nombre de timestamps le premier (et le seul temps) GetEnumerator est appelé. Le nombre augmente, donc les horodateurs sont ajoutés. Si vous changez la méthode GetEnumerator pour renvoyer l'énumérateur de la collection de listes, la méthode GetEnumerator est appelée à chaque fois qu'une nouvelle fenêtre est ouverte.

XAML:

<Window x:Class="YieldTest.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Height="300" Width="300"> 
    <StackPanel> 
     <Button Content="Open" Click="Button_Click" /> 
     <TextBlock Text="{Binding Path=Count}" /> 
     <ListBox ItemsSource="{Binding}" /> 
    </StackPanel> 
</Window> 
code

derrière:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Windows; 

namespace YieldTest 
{ 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
     } 

     private void Button_Click(object sender, RoutedEventArgs e) 
     { 
      Window1 window1 = new Window1(); 
      window1.DataContext = _timeStamps; 
      _timeStamps.Add(DateTime.Now); 
      window1.Show(); 
     } 

     private static TimeStamps _timeStamps = new TimeStamps(); 
    } 

    public class TimeStamps : IEnumerable 
    { 
     public void Add(DateTime time1) 
     { 
      _timeStamps.Add(time1); 
     } 

     public int Count { get { return _timeStamps.Count; } } 

     public IEnumerator GetEnumerator() 
     { 
      Debug.WriteLine("GetEnumerator"); 
      // Returning the enumerator of _timeStamps will result in 
      // GetEnumerator called every time a new window is opened, 
      // which is the expected result 
      // return _timeStamps.GetEnumerator(); 

      // Using yield will result in GetEnumerator is called only 
      // one time for the first window opened. This means that 
      // newer windows will have stale data. 
      foreach (DateTime timeStamp in _timeStamps) 
      { 
       yield return timeStamp; 
      } 
     } 

     private List<DateTime> _timeStamps = new List<DateTime>(); 
    } 
} 
+0

Les deux solutions que j'ai trouvées en travaillant pour obtenir une source de données pouvant être liée consistaient soit à implémenter IList et IEnumerator, soit à implémenter IEnumerable et INotifyCollectionChanged. J'ai décidé de faire en sorte que la source de données implémente IEnumerable et INotifyCollectionChanged, car elle est moins implémentable et les éléments ne peuvent pas être ajoutés par le code client. –

Répondre

2

Lorsque vous définissez l'ItemsSource WPF accède par un CollectionView pour activer le tri, le regroupement E.T.C. La vue est partagée de sorte que dans votre cas chaque ItemsControl utilise le même énumérateur, mais il n'est jamais réinitialisé, seul le dernier élément apparaît.

Si vous voulez un comportement d'instantané il existe quelques méthodes, mais la création d'une nouvelle liste pour chaque ItemsControl est la plus simple.

Si vous souhaitez que tous les éléments soient synchronisés et mis à jour automatiquement, voir ObservableCollection qui implémente INotifyCollectionChanged.

Vous devriez probablement utiliser l'interface générique IEnumerable.

+0

J'ai également vu que WPF utilise collectionviews pour accéder aux données. Pourquoi l'énumérateur du rendement n'est-il pas réinitialisé quand un énumérateur d'une liste le fait? Quel type de collection crée-t-il lorsque vous utilisez le rendement? –

+0

Trouvé cette réponse qui discuess céder et réinitialiser. Merci de m'avoir orienté dans la bonne direction. Je suppose que je vais juste mordre la balle et mettre en place mon propre recenseur au lieu d'utiliser le rendement. http://stackoverflow.com/questions/2308163/ability-to-reset-ienumerator-generated-using-yield-c –

+0

Vous pouvez utiliser yield et avoir le type de retour IEnumerable au lieu de IEnumerator et supprimer l'implémentation de IEnumerable. Définissez ensuite le DataContext pour appeler cette méthode. – Kris