web-dev-qa-db-fra.com

Pourquoi ne puis-je pas sélectionner une valeur nulle dans un ComboBox?

Dans WPF, il semble impossible de sélectionner (avec la souris) une valeur "nulle" dans un ComboBox. Edit Pour clarifier, il s'agit de .NET 3.5 SP1.

Voici un code pour montrer ce que je veux dire. Tout d'abord, les déclarations C #:

public class Foo
{
    public Bar Bar { get; set; }
}

public class Bar 
{
    public string Name { get; set; }
}

Ensuite, mon Window1 XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <ComboBox x:Name="bars" 
                  DisplayMemberPath="Name" 
                  Height="21" 
                  SelectedItem="{Binding Bar}"
                  />
    </StackPanel>
</Window>

Et enfin, ma classe Window1:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        bars.ItemsSource = new ObservableCollection<Bar> 
        {
            null, 
            new Bar { Name = "Hello" }, 
            new Bar { Name = "World" } 
        };
        this.DataContext = new Foo();
    }
}

Avec moi? J'ai un ComboBox dont les éléments sont liés à une liste d'instances de Bar, dont l'un est nul. J'ai lié la fenêtre à une instance de Foo et le ComboBox affiche la valeur de sa propriété Bar.

Lorsque j'exécute cette application, la zone de liste déroulante commence avec un affichage vide car Foo.Bar est null par défaut. C'est très bien. Si j'utilise la souris pour déposer le ComboBox vers le bas et sélectionner l'élément "Bonjour", cela fonctionne aussi. Mais si j'essaie de resélectionner l'élément vide en haut de la liste, le ComboBox se ferme et revient à sa valeur précédente de "Bonjour"!

La sélection de la valeur nulle avec les touches fléchées fonctionne comme prévu et sa définition par programme fonctionne également. Il suffit de sélectionner avec une souris qui ne fonctionne pas.

Je sais qu'une solution de contournement facile consiste à avoir une instance de Bar qui représente null et à l'exécuter via un IValueConverter, mais quelqu'un peut-il expliquer pourquoi la sélection de null avec la souris ne fonctionne pas dans la zone de liste déroulante de WPF?

44
Matt Hamilton

L'élément nul n'est pas du tout sélectionné par le clavier - plutôt l'élément précédent n'est pas sélectionné et aucun élément suivant n'est (peut être) sélectionné. C'est pourquoi, après avoir "sélectionné" le élément nul avec le clavier, vous ne pourrez plus sélectionner à nouveau l'élément précédemment sélectionné ("Bonjour") - sauf via la souris!

En bref, vous ne pouvez ni sélectionner ni désélectionner un élément nul dans un ComboBox. Lorsque vous pensez que vous le faites, vous désélectionnez ou sélectionnez plutôt l'élément précédent ou un nouvel élément.

Cela peut peut-être mieux être vu en ajoutant un arrière-plan aux éléments dans la zone de liste déroulante. Vous remarquerez l'arrière-plan coloré dans la zone de liste déroulante lorsque vous sélectionnez "Bonjour", mais lorsque vous le désélectionnez via le clavier, la couleur d'arrière-plan disparaît. Nous savons que ce n'est pas l'élément nul, car l'élément nul a en fait la couleur d'arrière-plan lorsque nous déposons la liste via la souris!

Le XAML suivant, modifié par rapport à celui de la question d'origine, mettra un arrière-plan LightBlue derrière les éléments afin que vous puissiez voir ce comportement.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <ComboBox x:Name="bars" Height="21" SelectedItem="{Binding Bar}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Grid Background="LightBlue" Width="200" Height="20">
                        <TextBlock Text="{Binding Name}" />
                    </Grid>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</Window>

Si vous souhaitez une validation supplémentaire, vous pouvez gérer l'événement SelectionChanged sur le ComboBox et voir que "la sélection de l'élément nul" donne en fait un tableau vide de AddedItems dans son SelectionChangedEventArgs et "désélectionner l'élément nul en sélectionnant" Bonjour "avec la souris" donne un tableau vide de RemovedItems.

15
Tim Erickson

Eh bien, j'ai récemment rencontré le même problème avec null valeur pour ComboBox. Je l'ai résolu en utilisant deux convertisseurs:

  1. Pour la propriété ItemsSource: elle remplace les valeurs null de la collection par toute valeur passée dans le paramètre du convertisseur:

    class EnumerableNullReplaceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var collection = (IEnumerable)value;
    
            return
                collection
                .Cast<object>()
                .Select(x => x ?? parameter)
                .ToArray();
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    
  2. Pour la propriété SelectedValue: celle-ci fait de même mais pour la valeur unique et de deux manières:

    class NullReplaceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value ?? parameter;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.Equals(parameter) ? null : value;
        }
    }
    

Exemple d'utilisation:

<ComboBox 
    ItemsSource="{Binding MyValues, Converter={StaticResource EnumerableNullReplaceConverter}, ConverterParameter='(Empty)'}" 
    SelectedValue="{Binding SelectedMyValue, Converter={StaticResource NullReplaceConverter}, ConverterParameter='(Empty)'}"
    />

Résultat:

enter image description here

Remarque: Si vous vous liez à ObservableCollection, vous perdrez les notifications de modification. De plus, vous ne voulez pas avoir plus d'une valeur null dans la collection.

11
Andrew Mikhailov

J'ai obtenu une nouvelle solution pour cette question. "UTILISATION Mahapps"

  xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"


  <ComboBox x:Name="bars"  **controls:TextBoxHelper.ClearTextButton="True"**
              DisplayMemberPath="Name" 
              Height="21" 
              SelectedItem="{Binding Bar}"/>

enter image description here

enter image description here

Vous pouvez utiliser le bouton de fermeture pour effacer le contenu.

Merci.

8
Anurag

Je sais que cette réponse n'est pas ce que vous avez demandé (une explication de pourquoi cela ne fonctionne pas avec la souris), mais je pense que la prémisse est erronée:

De mon point de vue en tant que programmeur et utilisateur (pas .NET), la sélection d'une valeur nulle est une mauvaise chose. "null" est censé être l'absence d'une valeur, pas quelque chose que vous sélectionnez.

Si vous avez explicitement besoin de ne pas sélectionner quelque chose, je suggérerais la solution de contournement que vous avez mentionnée ("-", "n.a." ou "aucun" comme valeur), ou mieux

  • envelopper la zone de liste déroulante avec une case à cocher qui peut être décochée pour désactiver la zone de liste déroulante. Cela me semble être la conception la plus propre à la fois du point de vue de l'utilisateur et du programme.
5
Galghamon

J'ai passé une journée à trouver une solution à ce problème de sélection d'une valeur nulle dans la zone de liste déroulante et enfin, ouais enfin j'ai trouvé une solution dans un article écrit à cette URL:

http://remyblok.tweakblogs.net/blog/7237/wpf-combo-box-with-empty-item-using-net-4-dynamic-objects.html

public class ComboBoxEmptyItemConverter : IValueConverter 
{ 
/// <summary> 
/// this object is the empty item in the combobox. A dynamic object that 
/// returns null for all property request. 
/// </summary> 
private class EmptyItem : DynamicObject 
{ 
    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
        // just set the result to null and return true 
        result = null; 
        return true; 
    } 
} 

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
{ 
    // assume that the value at least inherits from IEnumerable 
    // otherwise we cannot use it. 
    IEnumerable container = value as IEnumerable; 

    if (container != null) 
    { 
        // everything inherits from object, so we can safely create a generic IEnumerable 
        IEnumerable<object> genericContainer = container.OfType<object>(); 
        // create an array with a single EmptyItem object that serves to show en empty line 
        IEnumerable<object> emptyItem = new object[] { new EmptyItem() }; 
        // use Linq to concatenate the two enumerable 
        return emptyItem.Concat(genericContainer); 
    } 

    return value; 
} 

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

}

 <ComboBox ItemsSource="{Binding  TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}" 
      SelectedValue="{Binding SelectedID}" 
      SelectedValuePath="ID" 
      DisplayMemberPath="Name" />
3
John

cela pourrait ne pas répondre complètement à votre réponse, mais j'espère que c'est un succès dans la bonne direction:

  1. Avez-vous installé SP1?

Du blog de Scott Gu:

  • NET 3.5 SP1 comprend plusieurs améliorations de liaison et d'édition de données pour
    WPF. Ceux-ci inclus:
  • Prise en charge de StringFormat dans les expressions {{Binding}} pour permettre un formatage facile des valeurs liées
  • Les nouvelles lignes alternées prennent en charge les contrôles dérivés de ItemsControl, ce qui facilite la définition de propriétés alternées sur les lignes (par exemple: couleurs d'arrière-plan alternées)
  • Meilleure prise en charge de la gestion et de la conversion des valeurs nulles dans les contrôles modifiables Validation au niveau de l'élément qui applique des règles de validation à un élément lié entier
  • Prise en charge de MultiSelector pour gérer les scénarios de sélection multiple et d'édition en masse
  • Prise en charge d'IEditableCollectionView pour interfacer les contrôles de données aux sources de données et permettre la modification/l'ajout/la suppression d'éléments de manière transactionnelle
  • Améliorations des performances lors de la liaison à des sources de données IEnumerable

Désolé si j'ai perdu votre temps et que ce n'était même pas proche ... mais je pense que le problème est hérité de:

contraintes de l'ensemble de données fortement typé

NullValueDataSet expliqué ici

Mais maintenant, le SP1 pour .Net 3.5 aurait dû résoudre ce problème.

1
Ric Tokyo

ComboBox a besoin d'un DataTemplate pour afficher l'élément, quelle que soit sa simplicité. DataTemplate fonctionne comme ceci: obtenir une valeur de l'instance. [Chemin], par exemple.

bar1.Car.Color

Il ne peut donc pas obtenir une valeur de

null.Car.Color

Il lèvera une exception de référence nulle. Ainsi, l'instance nulle ne sera pas affichée. Mais la couleur - s'il s'agit d'un type de référence - est autorisée à être nulle car il n'y aura aucune exception dans ce cas.

0
redjackwong

J'ai eu le même genre de problème que nous avons contourné, comme l'ajout d'une propriété value à l'élément de collection, comme ceci:

 public class Bar

   {
      public string Name { get; set; }
      public Bar Value
      {
         get { return String.IsNullOrEmpty(Name) ?  null :  this; } // you can define here your criteria for being null
      }
   }

Puis en ajoutant des éléments au lieu de null j'utilise le même objet:

  comboBox1.ItemsSource=  new ObservableCollection<Bar> 
        {
            new Bar(),
            new Bar { Name = "Hello" }, 
            new Bar { Name = "World" } 
        };

Et au lieu de l'élément sélectionné, je le lie à la valeur sélectionnée:

<ComboBox Height="23" Margin="25,40,133,0" DisplayMemberPath="Name"
              SelectedValuePath="Value" 
              SelectedValue="{Binding Bar}"
              Name="comboBox1" VerticalAlignment="Top" />

Je sais que ce n'est pas une solution complète, juste une solution de contournement que j'utilise

0
Dincer Uyav
0
rudigrobler

Juste une supposition, mais je pense que cela semble raisonnable.

Supposons que la zone de liste déroulante utilise "ListCollectionView" (lcv comme instance) comme collection d'éléments, ce qu'elle devrait être. Si vous êtes programmeur, que vas-tu faire?

Je répondrai à la fois au clavier et à la souris.

Une fois que je reçois une entrée clavier, j'utilise

lcv.MoveCurrentToNext();

ou

lcv.MoveCurrentToPrevious();

Donc, bien sûr, le clavier fonctionne bien.

Ensuite, je travaille sur les entrées respons Mouse. Et c'est le problème.

  1. Je veux écouter l'événement "MouseClick" de mon article. Mais probablement, mon article n'est pas généré, c'est juste un espace réservé. Ainsi, lorsque l'utilisateur clique sur cet espace réservé, je ne reçois rien.

  2. Si j'obtiens l'événement avec succès, quelle est la prochaine étape. Je vais invoquer

    lcv.MoveCurrentTo (selectedItem);

le "selectedItem" qui serait nul n'est pas un paramètre acceptable ici je pense.

Quoi qu'il en soit, c'est juste une supposition. Je n'ai pas le temps de le déboguer, même si j'en suis capable. J'ai un tas de défauts à corriger. Bonne chance. :)

0
redjackwong