2010-04-06 21 views
18

Ho un ComboBox che ha l'oggetto Selected associato a ViewModel.WPF ComboBox SelectedItem - passare al valore precedente

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

Quando l'utente seleziona un nuovo elemento nella vista ComboBox, voglio visualizzare un prompt e verificare che vogliono fare il cambiamento.

Nel settaggio SetItem Property nel View Model, visualizzo una finestra di dialogo per confermare la selezione. Quando dicono di sì, funziona bene.

Il mio problema è, quando l'utente fa clic su "No", non sono sicuro di chi ottiene il ComboBox per tornare al valore precedente. La proprietà nel ViewModel ha il valore precedente , tuttavia nella vista il ComboBox mostra il valore appena selezionato.

Desidero che l'utente selezioni un elemento, confermi di voler procedere con esso e, se decidono di non farlo, , voglio che il ComboBox torni all'elemento precedente.

Come posso realizzare questo? Grazie!

risposta

20

Quando l'utente dice "no", WPF non sa che il valore è cambiato. Per quanto riguarda WPF, il valore è qualunque sia l'utente selezionato.

Si potrebbe provare ad aumentare una proprietà modificate di notifica:

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

     OnPropertyChanged("SelItem"); 
    } 
} 

Il problema è, la notifica di modifica avviene all'interno dello stesso contesto dell'evento di selezione. Quindi, WPF lo ignora perché sa già che la proprietà è cambiata - all'elemento selezionato dall'utente!

Quello che dovete fare è generare l'evento di notifica in un messaggio separato:

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

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

WPF sarà poi elaborare questo messaggio dopo è fatto l'elaborazione dell'evento selezione cambiato e, pertanto, ripristinare il valore nel guarda indietro a ciò che dovrebbe essere.

La tua VM avrà ovviamente bisogno di accedere all'attuale Dispatcher. Vedere my blog post su una classe VM di base se sono necessari alcuni suggerimenti su come eseguire questa operazione.

+0

questo ha lavorato molto -Grazie! Non ero sicuro di come inviare nuovamente il messaggio in modo che la vista si aggiornasse. –

+1

A causa di [modifiche in WPF 4.0] (https://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/) segue più [soluzione completa da @ NathanAW] (http://stackoverflow.com/a/2709931/197371) –

1

Un altro modo per farlo (assicuratevi di leggere anche i commenti):

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

Dal link: Un'altra soluzione per la questione della chiamata ricorsiva del gestore di eventi senza variabile globale è di annullare gestore assegnazione prima della modifica della selezione programmatica e successivamente riassegnarla.

Es:

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

Grazie per questa domanda e le risposte. Il Dispatcher.BeginInvoke mi ha aiutato e faceva parte della mia soluzione finale, ma la soluzione di cui sopra non funzionava abbastanza nella mia app WPF 4.

Ho messo insieme un piccolo campione per capire perché.Ho dovuto aggiungere del codice che in realtà ha temporaneamente modificato il valore della variabile membro sottostante in modo che quando WPF ha interrogato nuovamente il getter, avrebbe visto che il valore cambiava. In caso contrario, l'interfaccia utente non riflette correttamente la cancellazione e la chiamata BeginInvoke() non ha fatto nulla.

Here's a my blog post con il mio esempio che mostra un'implementazione non funzionante e funzionante.

mio setter ha finito per assomigliare a questo:

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

Ho fatto questo nel mio setter. BUt questo non funziona per me. – Virus

+0

Quale versione di .net stai usando? Che comportamento stai vedendo? – NathanAW

1

mio modo di fare è quello di lasciare che il cambiamento passa attraverso ed eseguire la convalida in un lambda che viene BeginInvoked nel Dispatcher.

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; 
      } 
     } 
    } 

o nel vostro ViewModel:

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