2010-04-06 6 views
18

J'ai une zone de liste déroulante dont l'élément SelectedItem est lié au ViewModel.WPF ComboBox SelectedItem - modification à la valeur précédente

<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}"> 

Lorsque l'utilisateur sélectionne un nouvel élément dans la vue ComboBox, je veux afficher une invite et vérifiez qu'ils veulent faire le changement.

Dans le setter SetItem Property du modèle View, j'affiche une boîte de dialogue pour confirmer la sélection. Quand ils disent oui, cela fonctionne bien.

Mon problème est, lorsque l'utilisateur clique sur "Non", je ne sais pas qui doit obtenir la ComboBox pour revenir à la valeur précédente. La propriété dans le ViewModel a la valeur plus ancienne, mais dans la vue le ComboBox affiche la valeur nouvellement sélectionnée.

Je veux que l'utilisateur sélectionne un élément, confirme qu'il souhaite le poursuivre, et s'il le décide , je veux que la ComboBox revienne à l'élément précédent.

Comment puis-je accomplir ceci? Merci!

Répondre

20

Lorsque l'utilisateur dit «non», WPF ne sait pas que la valeur a changé. En ce qui concerne WPF, la valeur est celle que l'utilisateur a sélectionnée.

Vous pouvez essayer d'augmenter une propriété notification a changé:

public object SelItem 
{ 
    get { ... } 
    set 
    { 
     if (!CancelChange()) 
     { 
      this.selItem = value; 
     } 

     OnPropertyChanged("SelItem"); 
    } 
} 

Le problème est, la notification de changement se produit dans le même contexte de l'événement de sélection. Ainsi, WPF l'ignore car il sait déjà que la propriété a changé - à l'élément sélectionné par l'utilisateur!

Ce que vous devez faire est d'augmenter l'événement de notification dans un message séparé:

public object SelItem 
{ 
    get { ... } 
    set 
    { 
     if (CancelChange()) 
     { 
      Dispatcher.BeginInvoke((ThreadStart)delegate 
      { 
       OnPropertyChanged("SelItem"); 
      }); 
      return; 
     } 

     this.selItem = value; 
     OnPropertyChanged("SelItem"); 
    } 
} 

WPF traitera alors ce message après il a fait le traitement de l'événement changé de sélection et retournera donc la valeur de la voir ce qu'il devrait être.

Votre VM aura évidemment besoin d'accéder au Dispatcher actuel. Voir my blog post sur une classe VM de base si vous avez besoin de quelques conseils sur la façon de le faire.

+0

cela a très bien fonctionné -Merci! Je ne savais pas comment renvoyer le message afin que la vue soit mise à jour. –

+1

En raison de [changements dans WPF 4.0] (https://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/) suivre plus [solution complète de @ NathanAW] (http://stackoverflow.com/a/2709931/197371) –

1

Une autre façon de le faire (assurez-vous également lire les commentaires):

http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html

À partir du lien: Une autre solution pour la délivrance d'appel récursif de gestionnaire d'événements sans variable globale est d'annuler gestionnaire affectation avant la modification de la sélection par programme et réaffectez-la par la suite.

Ex:

cmb.SelectionChanged -= ComboBox_SelectionChanged; 
cmb.SelectedValue = oldSel.Key; 
cmb.SelectionChanged += ComboBox_SelectionChanged; 
11

Merci pour cette question et des réponses. Le Dispatcher.BeginInvoke m'a aidé et faisait partie de ma solution finale, mais la solution ci-dessus ne fonctionnait pas tout à fait dans mon application WPF 4.

J'ai rassemblé un petit échantillon pour comprendre pourquoi.J'ai dû ajouter du code qui a changé la valeur de la variable membre sous-jacente temporairement de sorte que lorsque WPF a re-interrogé le getter, il verrait que la valeur a chanté. Sinon, l'interface utilisateur n'a pas correctement reflété l'annulation et l'appel BeginInvoke() n'a rien fait.

Here's a my blog post avec mon exemple montrant une implémentation qui ne fonctionne pas et qui fonctionne.

Mon setter a fini par ressembler à ceci:

private Person _CurrentPersonCancellable; 
    public Person CurrentPersonCancellable 
    { 
     get 
     { 
      Debug.WriteLine("Getting CurrentPersonCancellable."); 
      return _CurrentPersonCancellable; 
     } 
     set 
     { 
      // Store the current value so that we can 
      // change it back if needed. 
      var origValue = _CurrentPersonCancellable; 

      // If the value hasn't changed, don't do anything. 
      if (value == _CurrentPersonCancellable) 
       return; 

      // Note that we actually change the value for now. 
      // This is necessary because WPF seems to query the 
      // value after the change. The combo box 
      // likes to know that the value did change. 
      _CurrentPersonCancellable = value; 

      if (
       MessageBox.Show(
        "Allow change of selected item?", 
        "Continue", 
        MessageBoxButton.YesNo 
       ) != MessageBoxResult.Yes 
      ) 
      { 
       Debug.WriteLine("Selection Cancelled."); 

       // change the value back, but do so after the 
       // UI has finished it's current context operation. 
       Application.Current.Dispatcher.BeginInvoke(
         new Action(() => 
         { 
          Debug.WriteLine(
           "Dispatcher BeginInvoke " + 
           "Setting CurrentPersonCancellable." 
          ); 

          // Do this against the underlying value so 
          // that we don't invoke the cancellation question again. 
          _CurrentPersonCancellable = origValue; 
          OnPropertyChanged("CurrentPersonCancellable"); 
         }), 
         DispatcherPriority.ContextIdle, 
         null 
        ); 

       // Exit early. 
       return; 
      } 

      // Normal path. Selection applied. 
      // Raise PropertyChanged on the field. 
      Debug.WriteLine("Selection applied."); 
      OnPropertyChanged("CurrentPersonCancellable"); 
     } 
    } 
+2

Je l'ai fait dans mon setter. BUt cela ne fonctionne pas pour moi. – Virus

+0

Quelle version de .net utilisez-vous? Quel comportement voyez-vous? – NathanAW

1

Ma façon de le faire est de laisser le changement et passer par effectuer une validation dans un lambda qui est BeginInvoked dans le répartiteur.

public ObservableCollection<string> Items { get; set; } 
    private string _selectedItem; 
    private string _oldSelectedItem; 
    public string SelectedItem 
    { 
     get { return _selectedItem; } 
     set { 
      _oldSelectedItem = _selectedItem; 
      _selectedItem = value; 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem")); 
      } 
      Dispatcher.BeginInvoke(new Action(Validate));     
     } 
    } 

    private void Validate() 
    {    
     if (SelectedItem == "Item 5") 
     { 
      if (MessageBox.Show("Keep 5?", "Title", MessageBoxButton.YesNo) == MessageBoxResult.No) 
      { 
       SelectedItem = _oldSelectedItem; 
      } 
     } 
    } 

ou dans votre ViewModel:

Synchronization.Current.Post(new SendOrPostCallback(Validate), null);