[Win8.1] ComboBox : ItemsSource dynamique et SelectedItem null
Dans un projet d’application Windows 8.1, nous devions mettre en place des ComboBox pouvant changer dynamiquement de source en fonction d’un choix effectué dans une liste. Parce qu’une illustration (désolé d’avance pour le design) est toujours plus parlante, voici le besoin :
Nous voulons qu’en fonction du joueur sélectionné dans la liste de gauche, la ComboBox à droite propose des nationalités propres à chaque joueur.
La mise en place (code C# et XAML)
Nous avons donc une classe Player qui représente un joueur de foot et dont le modèle est :
public class Player { public string Name { get; set; } private string _nationality; public string Nationality { get { return _nationality; } set { _nationality = value; OnPropertyChanged(); } } public List<string> Nationalities { get; set; } }
Dans notre ViewModel, nous avons une liste de Player, “Players” et une propriété “SelectedPlayer” qui représente le joueur sélectionné. Le XAML se présente sous cette forme :
<StackPanel Orientation="Horizontal"> <ListView SelectedItem="{Binding SelectedPlayer, Mode=TwoWay}" ItemsSource="{Binding Players}" ItemTemplate="{StaticResource PlayerTemplate}" Style="{StaticResource PlayerListViewStyle}" /> <Grid> <TextBlock Text="Nationalité" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Top"/> <ComboBox HorizontalAlignment="Stretch" VerticalAlignment="Center" ItemsSource="{Binding SelectedPlayer.Nationalities}" SelectedItem="{Binding SelectedPlayer.Nationality, Mode=TwoWay}" /> </Grid> </StackPanel>
Au premier essai, ça fonctionnait comme souhaité, la ComboBox proposait bien les valeurs des nationalités pour le joueur sélectionné. Cependant, quand je sélectionnais un élément dans ma ComboBox et que je passais à un autre joueur, la valeur que je venais de saisir s’effaçait.
Ce problème est dû au fonctionnement de la ComboBox qui dès que l’on change sa source, définit la valeur du SelectedItem à null.
Comment fixer ce comportement ?
Je me suis créé une classe ComboBoxExtension qui a une DependencyProperty appelée ItemsSource qui va remplacer la propriété ItemsSource de base. Lors du changement de valeur de cette propriété, je suspends temporairement le binding, voici les étapes que j’effectue :
- je sauvegarde le binding
- je supprime le binding de ma ComboBox
- je mets à jour la source de la ComboBox
- je remets le binding sauvegardé
Voici donc le code de cette classe :
public class ComboBoxExtensions { /// <summary> /// Gets the items source. /// </summary> /// <param name="obj">The object.</param> /// <returns>An object</returns> public static object GetItemsSource(DependencyObject obj) { return obj.GetValue(ItemsSourceProperty); } /// <summary> /// Sets the items source. /// </summary> /// <param name="obj">The object.</param> /// <param name="value">The value.</param> public static void SetItemsSource(DependencyObject obj, object value) { obj.SetValue(ItemsSourceProperty, value); } /// <summary> /// The items source property /// </summary> public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached("ItemsSource", typeof(object), typeof(ComboBoxExtensions), new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged))); /// <summary> /// Called when [items source changed]. /// </summary> /// <param name="sender">The sender.</param> /// <param name="args">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param> private static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { ComboBox cb = sender as ComboBox; if (cb != null) { // Save SelectedItem binding var binding = cb.GetBindingExpression(Selector.SelectedItemProperty); // Clear binding BindingOperations.SetBinding(cb, Selector.SelectedItemProperty, new Binding()); // Update ItemsSource cb.ItemsSource = args.NewValue; // Rebind SelectedItem BindingOperations.SetBinding(cb, Selector.SelectedItemProperty, binding.ParentBinding); } } }
Maintenant, il n’y a plus qu’à l’intégrer dans mon XAML. Pour cela, il suffit simplement d’importer le namespace associé à ma classe et l’ItemsSource utilisé pour prendre le mien :
<ComboBox HorizontalAlignment="Stretch" VerticalAlignment="Center" Extensions:ComboBoxExtensions.ItemsSource="{Binding SelectedPlayer.Nationalities}" SelectedItem="{Binding SelectedPlayer.Nationality, Mode=TwoWay}" />
Voilà, grâce à l’ajout de cette extension, les valeurs que je sélectionne maintenant dans ma ComboBox ne sont plus remises à zéro lorsque je change de joueur de foot !
En espérant vous avoir aidé !
Commentaires