2010-11-14 29 views
4

J'ai retourné un Horizontal ItemsControl à une Listbox pour que je puisse sélectionner des éléments individuels mais que la sélection était cassée. J'ai pris le temps de distiller le bit problématique.Bogue de sélection pour la zone de liste dans laquelle les éléments de liste sont des types/structures de valeur et contiennent des doublons?

Books = new[] { new Book{Id=1, Name="Book1"}, 
           new Book{Id=2, Name="Book2"}, 
           new Book{Id=3, Name="Book3"}, 
           new Book{Id=4, Name="Book4"}, 
           new Book{Id=3, Name="Book3"}, 
      }; 

      <DataTemplate DataType="{x:Type WPF_Sandbox:Book}"> 
       <TextBlock Text="{Binding Name}"/> 
      </DataTemplate> 

<ListBox ItemsSource="{Binding Books}"/> 

Si livre est un struct, la sélection listbox (mode par défaut: simple) va mal tourné si vous sélectionnez un élément qui a une struct équivalent dans la liste. par exemple, Book3

Si Book est transformé en classe (avec une sémantique de type sans valeur), la sélection est fixe.

choix (jusqu'à présent, ne pas comme l'un d'eux):

  • J'ai choisi parce que son struct une petite structure de données et la sémantique du type de valeur sont utiles pour comparer les 2 cas pour l'égalité. Le changer en une classe me fait perdre la sémantique de type valeur. Je ne peux plus utiliser les Equals par défaut ou le remplacer pour la comparaison de membres.
  • Ajoutez un attribut Book différentiateur uniquement pour que la sélection de la zone de liste fonctionne (par exemple, un index).
  • Éliminer les doublons .. Pas possible.

WPF listbox : problem with selection: indique que la zone de liste est mise SelectedItem et en mettant à jour l'interface utilisateur pour cela, il allume juste en haut tous les éléments de la liste qui Equal(SelectedItem). Je ne sais pas pourquoi .. en soulignant SelectedIndex ferait disparaître ce problème; peut-être qu'il me manque quelque chose. ListBox is selecting many items even in SelectionMode="Single": montre le même problème lorsque les éléments de la liste sont des chaînes (sémantiques de type valeur)

+1

Vous dites que vous ne pouvez pas remplacer 'Equals' si vous utilisez un' class'. Pourquoi pas? Le remplacement de 'Equals' est fortement recommandé lors de la création d'un' struct', donc vous ne devriez pas utiliser 'struct' juste pour obtenir une implémentation' Equals' avec la sémantique des valeurs. En passant, ce comportement n'est pas limité aux structs. Si vous vous liez à un type de classe qui remplace Equals et que deux instances distinctes sont égales, vous verrez le même comportement. –

+0

@Kent - exactement. Ce que j'ai est une structure de données simple, de sorte que si 2 instances ont les mêmes membres, elles sont équivalentes. Tout allait bien jusqu'à ce que j'aie besoin d'une liste avec des éléments sélectionnables .. Je pourrais le transformer en classe .. mais si j'écarte Equals pour faire comparer memberwise, je serais de retour à la case 1 (comme je l'ai indiqué avec le second SO q link) où listbox des chaînes montre le même problème). Si je ne remplace pas, j'ai besoin d'une méthode customEquals pour comparer les membres. Donc, il semble que l'ajout d'un paramètre de différenciation (comme un horodatage ou index unique) à la structure est la meilleure option .. – Gishu

Répondre

3

Pourquoi ne pas simplement utiliser une meilleure classe de collection comme source de données pour résoudre le problème

var collection = new[] 
{ 
    new Book {Id = 1, Name = "Book1"}, 
    new Book {Id = 2, Name = "Book2"}, 
    new Book {Id = 3, Name = "Book3"}, 
    new Book {Id = 4, Name = "Book4"}, 
    new Book {Id = 3, Name = "Book3"}, 
}; 
var Books = collection.ToDictionary(b => Guid.NewGuid(), b => b); 
DataContext = Books; 

Et ce sera votre DataTemplate

<ListBox ItemsSource="{Binding}"> 
    <ListBox.ItemTemplate> 
    <DataTemplate> 
     <TextBlock Text="{Binding Value.Name}"/> 
    </DataTemplate> 
    </ListBox.ItemTemplate> 
+0

Dictionnaire perdrait l'ordre de la collection ... mais je reçois l'idée .. envelopper la structure dans une classe . – Gishu

0

Merci à Dean Chalk pour son idée.

Je l'étendre afin qu'il soit plus facile à l'utilisateur pour d'autres struct

L'idée est d'utiliser un convertisseur pour lancer la collection struct originale à une collection personnalisée, qui à son tour passer outre l'égal de comparer avec ID de Guid. Vous a encore l'ordre d'origine

public class StructListItem 
{ 
    private Guid _id = Guid.NewGuid(); 
    public Guid ID 
    { 
     get 
     { 
      return _id; 
     } 
     set 
     { 
      _id = value; 
     } 
    } 

    private object _core = default(object); 
    public object Core 
    { 
     get 
     { 
      return _core; 
     } 
     set 
     { 
      _core = value; 
     } 
    } 

    public StructListItem(object core) 
    { 
     Core = core; 
    } 

    public override bool Equals(object obj) 
    { 
     return ID.Equals(obj); 
    } 

    public override int GetHashCode() 
    { 
     return ID.GetHashCode(); 
    } 
} 

public class StructToCollConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     if (value is IEnumerable) 
     { 
      List<StructListItem> _ret = new List<StructListItem>(); 
      if (value != null) 
      { 
       IEnumerator i = ((IEnumerable)value).GetEnumerator(); 
       while (i.MoveNext()) 
       { 
        _ret.Add(new StructListItem(i.Current)); 
       } 
      } 
      return _ret.ToArray(); 
     } 

     return null; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

<ListBox ItemsSource="{Binding Books, Converter={StaticResource converter}}" SelectionMode="Single"> 
     <ListBox.ItemTemplate> 
      <DataTemplate> 
       <StackPanel> 
        <TextBlock Text="{Binding Core.Name}"/> 
       </StackPanel> 
      </DataTemplate> 
     </ListBox.ItemTemplate> 

    </ListBox> 
0

Garyx

Quelque chose peut-être un peu plus simple?

public class StructListItem<T> where T : struct 
{ 
    public T Item { get; private set; } 
    public readonly Guid Id = Guid.NewGuid(); 
    public StructListItem(T item) 
    { 
     Item = item; 
    } 

    public static IEnumerable<StructListItem<U>> 
     GetStructList<U>(IEnumerable<U> originalList) where U : struct 
    { 
     return originalList.Select(i => new StructListItem<U>(i)); 
    } 
} 
3

Je ne comprends pas pourquoi vous avez des doublons dans votre liste, si elles sont absolument identiques (à savoir, si les doublons ont tous le même contenu, et va de Egaux). Vous n'aurez aucun moyen de savoir lequel des doublons a été sélectionné par l'utilisateur.Le ListBox ne le sera pas non plus, ce qui explique probablement pourquoi vous rencontrez des problèmes.

Peut-être, au lieu de vous lier directement à une collection de structures, vous pourriez envelopper chaque structure dans une classe? Définissez simplement une classe BookWrapper qui contient une structure Book et liez-la à une collection de BookWrappers au lieu d'une collection de livres. Vous résolvez le problème de WPF ne pas être en mesure de distinguer les instances, mais le reste de votre code pourrait continuer à avoir les avantages d'une structure.

+0

Dans mon cas, j'ai une structure qui contient les résultats d'un test unitaire, donc il a [Resultat = Red/Green, NumberOfTests = int]. L'exécution d'un test deux fois entraîne l'apparition de deux structures dans la liste (la position est la façon dont je les différencie - la liste la plus haute est la plus récente). J'ai fini par ajouter un autre attribut à la structure (un horodatage) .. mais ouais l'année du q semble les entourer d'un ref-type pour les rendre non-égaux. – Gishu