web-dev-qa-db-fra.com

WPF MVVM ComboBox SelectedItem ou SelectedValue ne fonctionne pas

Mettre à jour

Après un peu d'enquête. Ce qui semble être le problème, c'est que SelectedValue/SelectedItem se produit avant que le chargement de la source d'élément ne soit terminé. Si je suis assis à un point d'arrêt et que j'attends quelques secondes, cela fonctionne comme prévu. Je ne sais pas comment je vais contourner celui-ci.

Fin de mise à jour

J'ai une application utilisant WPF utilisant MVVM avec un ComboBox. Ci-dessous, l'exemple ViewModel. Le problème que je rencontre est lorsque nous quittons notre page et que nous passons en arrière, la zone de liste déroulante ne sélectionne pas la valeur actuelle sélectionnée.

Voir le modèle

public class MyViewModel
{
     private MyObject _selectedObject;
     private Collection<Object2> _objects;
     private IModel _model;

     public MyViewModel(IModel model)
    {
         _model = model;
         _objects = _model.GetObjects();
    }

    public Collection<MyObject> Objects
    {
         get
         {
              return _objects;
         }
         private set
         {
              _objects = value;
         }
     }

     public MyObject SelectedObject
     {
          get
          {
              return _selectedObject;
          }
          set
          {
               _selectedObject = value;
          }
      }
 }

Par exemple, supposons que MyObject ait deux propriétés (Text et Id). Mon XAML pour la ComboBox ressemble à ceci.

XAML

<ComboBox Name="MyComboBox" Height="23"  Width="auto" 
    SelectedItem="{Binding Path=SelectedObject,Mode=TwoWay}" 
    ItemsSource="{Binding Objects}"
    DisplayMemberPath="Text"
    SelectedValuePath="Id">

Quelle que soit la manière dont je configure cela lorsque je reviens à la page et que l'objet est réassemblé, la ComboBox ne sélectionne pas la valeur. L'objet retourne le bon objet via le get dans la propriété.

Je ne sais pas si c'est simplement un problème avec le fonctionnement du modèle ComboBox et MVVM. La liaison de zone de texte que nous effectuons fonctionne correctement.

40
cjibo

Avez-vous essayé d'implémenter INotifyPropertyChanged dans votre modèle de vue, puis déclenchez l'événement PropertyChanged lorsque la SelectedItem est définie?

Si cela ne résout pas le problème, vous pourrez alors déclencher manuellement l'événement PropertyChanged lors de la navigation précédente, ce qui devrait être suffisant pour que WPF se redessine et affiche l'élément sélectionné correct.

27
Orion Edwards

Paramétrer IsSynchronizedWithCurrentItem="True" a fonctionné pour moi!

28
rp7799

Vous devez placer la propriété ItemsSource AVANT la propriété SelectedItem. Je suis tombé sur un blog il y a quelques jours mentionnant le problème.

20
GuestPerson

J'ai eu des problèmes similaires et le problème a été résolu en m'assurant de bien mettre en œuvre IEquatable correctement. Lorsque la liaison est établie, il essaie de voir si les objets correspondent, alors assurez-vous de mettre en œuvre correctement votre contrôle d'égalité.

11

Dans ce cas, la liaison élément sélectionné ne fonctionne pas car les identifiants de hachage des objets sont différents.

Une solution possible est:

En fonction de l'id d'élément sélectionné, récupérez l'objet sur la collection d'articles-sources et définissez la propriété d'élément sélectionnée sur celle-ci.

Exemple:

<ctrls:ComboBoxControlBase SelectedItem="{Binding Path=SelectedProfile, Mode=TwoWay}" ItemsSource="{Binding Path=Profiles, Mode=OneWay}" IsEditable="False" DisplayMemberPath="Name" />

La propriété liée à ItemSource est:

public ObservableCollection<Profile> Profiles
{
   get { return this.profiles; }
   private set { profiles = value; RaisePropertyChanged("Profiles"); }
}

La propriété liée à SelectedItem est:

public Profile SelectedProfile 
{
    get { return selectedProfile; }
    set
    {
        if (this.SelectedUser != null)
        {
            this.SelectedUser.Profile = value; 
            RaisePropertyChanged("SelectedProfile");  
        } 
    } 
}

Le code de récupération est:

    [Command("SelectionChanged")]
    public void SelectionChanged(User selectedUser)
    {
        if (selectedUser != null)
        {
            if (selectedUser is User)
            {
                if (selectedUser.Profile != null)
                {
                    this.SelectedUser = selectedUser;
                    this.selectedProfile = this.Profiles.Where(p => p.Id == this.SelectedUser.Profile.Id).FirstOrDefault();
                    MessageBroker.Instance.NotifyColleagues("ShowItemDetails"); 
                }
            }
        }            
    }

J'espère que cela vous aidera ... J'ai passé beaucoup de temps à chercher des réponses, mais je ne pouvais pas le trouver.

10
Breno P. Lucena

Lorsque vous quittez la page en cours, la CollectionView associée à la propriété ItemsSource de la ComboBox est purgée. Et comme la propriété ComboBoxIsSyncronizedWithCurrent est true par défaut, les propriétés SelectedItem et SelectedValue sont réinitialisées.
Cela semble être un problème de type de données interne dans la liaison. Comme d'autres l'ont suggéré ci-dessus, si vous utilisez plutôt SelectedValue en liant une propriété int sur le modèle de vue, cela fonctionnera . Un raccourci pour vous serait de remplacer l'opérateur Equals sur MyObject afin que, lors de la comparaison de deux MyObjects, le Id actuel les propriétés sont comparées.

Autre astuce: Si vous restructurez vos modèles de vue et utilisez SelectedValue, utilisez-le uniquement lorsque SelectedValuePath=IdId est int. Si vous utilisez une clé de chaîne, liez-vous à la propriété Text de ComboBox au lieu de SelectedValue.

4
Dave

J'ai déjà remarqué ce comportement auparavant. J'ai remarqué que la propriété SelectedIndex ne provoque pas le même bogue. Si vous pouvez restructurer votre ViewModel afin d’exposer l’index de l’élément sélectionné et de le lier, vous devriez pouvoir vous en aller.

3
Abe Heidebrecht

J'ai eu le même problème. La chose est. L'élément sélectionné ne sait pas quel objet utiliser dans la collection. Donc, vous devez dire à l'élément sélectionné pour utiliser l'élément de la collection.

public MyObject SelectedObject
 {
      get
      {
          Objects.find(x => x.id == _selectedObject.id)
          return _selectedObject;
      }
      set
      {
           _selectedObject = value;
      }
 }

J'espère que ça aide.

2
StefanHa

J'ai une réponse très simple à ce problème. Ajoutez d’abord le code suivant à View IsSynchronizedWithCurrentItem = "True". 

Ensuite, chaque fois que vous affectez un nouvel objet dans le ViewModel à cette propriété, SelectedObject doit être enregistré dans cette propriété et non en tant que membre privé. 

La propriété viewmodel devrait ressembler à ceci

    public Role SelectedObject 
    {
        get { return object; }
        set
        {
            if (value != null)
            {
                if (!object.Equals(value))
                {
                    object = value;
                    OnPropertyChanged(() => SelectedObject );
                }
            }
        }
    }

Cela devrait résoudre le problème.

2
Tim

Je me suis battu avec ce problème pendant un moment. Dans mon cas, j’utilisais le type complexe (List) comme source d’élément et j’utilisais un KeyType comme valeur sélectionnée. Lors de l'événement de chargement, le type de clé était défini sur null. Cela a tout cassé. Aucun des sous-éléments ne serait mis à jour lorsque la clé a été modifiée. Il s'est avéré que lorsque j'ai ajouté un contrôle pour m'assurer que la valeur proposée pour KeyType n'était pas nulle, tout fonctionnait comme prévu.

    #region Property: SelectedKey
    // s.Append(string.Format("SelectedKey : {0} " + Environment.NewLine, SelectedKey.ToString()));

    private KeyType _SelectedKey = new KeyType();
    public KeyType SelectedKey
    {
        get { return _SelectedKey; }
        set
        {
            if(value != null )
                if (!_SelectedKey.Equals(value))
                {
                    _SelectedKey = value;
                    OnPropertyChanged("SelectedKey");
                }
        }
    }
    #endregion SelectedKey
1
Scott McFadden

Le type de SelectedValuePath et de SelectedValue doit être EXACTEMENT le même.

Si, par exemple, le type de SelectedValuePath est Int16 et que le type de propriété qui se lie à SelectedValue est int, cela ne fonctionnera pas.

Je passe des heures à trouver cela, et c'est pourquoi je réponds ici après tant de temps que la question a été posée. Peut-être qu'un autre pauvre gars avec le même problème, comme moi, peut le voir.

1
Dummy01

J'ai eu ce problème avec une ComboBox affichant une liste de couleurs (List <Brush>).
La sélection d’une couleur était possible mais elle n’était pas affichée à la fermeture de la sélection (bien que la propriété ait été modifiée!)

Le correctif remplaçait la méthode Equals (object obj) pour le type sélectionné dans le ComboBox (Brush), ce qui n'était pas simple car Brush est scellé . J'ai donc écrit une classe. ÉgalitéBrush contenant un pinceau et implémentant Equals:

public class EqualityBrush
{
    public SolidColorBrush Brush { get; set; }

    public override bool Equals(object o)
    {
        if (o is EqualityBrush)
        {
            SolidColorBrush b = ((EqualityBrush)o).Brush;
            return b.Color.R == this.Brush.Color.R && b.Color.G == this.Brush.Color.G && b.Color.B == this.Brush.Color.B;
        }
        else
            return false;
    }
}

L'utilisation d'une liste de ma nouvelle classe EqualityBrush à la place de la classe Brush normale a résolu le problème!

Ma combobox XAML ressemble à ça:

<ComboBox ItemsSource="{Binding BuerkertBrushes}" SelectedItem="{Binding Brush, Mode=TwoWay}" Width="40">
    <ComboBox.Resources>
        <DataTemplate DataType="{x:Type tree:EqualityBrush}">
            <Rectangle Width="20" Height="12" Fill="{Binding Brush}"/>
        </DataTemplate>
    </ComboBox.Resources>
</ComboBox>

Rappelez-vous que mon "pinceau" -propriété dans le ViewModel doit maintenant être de type EqualityBrush!

1
JCH2k

J'ai résolu le problème en ajoutant un répartiteur dans l'événement UserControl_Loaded

 Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
 {
     combobox.SelectedIndex = 0;
 }));
0
Peter T.

Utiliser un événement chargé: 

private void cmb_Loaded(object sender, RoutedEventArgs e) {
    if (cmb.Items.Count > 0) cmb.SelectedIndex = 0;          
}

Ça marche pour moi.

0
Vinicius

IsSyncronizedWithCurrent = False le fera fonctionner.

0
Rohit

Cela pourrait être la façon dont vous appliquez le DataContext à la page. Dans WPF, chaque fois que vous accédez à une page, tout est réinitialisé, le constructeur est appelé, les méthodes chargées, tout. Par conséquent, si vous définissez votre DataContext dans votre vue, vous supprimerez sans aucun doute cet élément sélectionné que l'utilisateur a sélectionné. Pour éviter cela, utilisez la propriété KeepAlive de vos pages.

<Page KeepAlive="True" ...>
   ...
</Page>

Ainsi, seul l'événement Loaded sera déclenché lorsque vous revenez à une page déjà visitée. Vous devez donc vous assurer que vous définissez DataContext sur Initialize (en externe ou dans le constructeur) plutôt que sur Load. 

Toutefois, cela ne fonctionnera que pour cette instance de la page. Si vous accédez à une nouvelle instance de cette page, son constructeur sera appelé à nouveau.

0
markti

ComboBox.SelectionBoxItem.ToString ()

0
Sean Campbell