2010-04-06 19 views
18

Tengo un ComboBox que tiene SelectedItem vinculado al ViewModel.WPF ComboBox SelectedItem - cambiar al valor anterior

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

Cuando el usuario selecciona un nuevo elemento de la vista de ComboBox, quiero mostrar un mensaje y verificar que quieren hacer el cambio.

En SetItem Property setter en View Model, visualizo un cuadro de diálogo para confirmar la selección. Cuando dicen que sí, funciona bien.

Mi problema es que cuando el usuario hace clic en "No", no estoy seguro de quién obtener el ComboBox para volver al valor anterior. La propiedad en ViewModel tiene el valor más antiguo , sin embargo, en la vista, el ComboBox muestra el valor recientemente seleccionado.

Quiero que el usuario seleccione un elemento, confirme que quiere seguir adelante con él, y si ellos deciden no hacerlo, quiero que el ComboBox regrese al elemento anterior.

¿Cómo puedo lograr esto? Gracias!

Respuesta

20

Cuando el usuario dice "no", WPF no sabe que el valor ha cambiado. En lo que respecta a WPF, el valor es lo que sea que el usuario haya seleccionado.

Usted puede tratar de levantar una propiedad cambió de notificación:

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

     OnPropertyChanged("SelItem"); 
    } 
} 

El problema es, la notificación de cambio que ocurre dentro del mismo contexto del evento de selección. Por lo tanto, WPF lo ignora porque ya sabe que la propiedad ha cambiado: ¡al elemento que seleccionó el usuario!

Lo que hay que hacer es elevar el evento de notificación en un mensaje aparte:

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

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

WPF procesará este mensaje después se hace el procesamiento del evento de selección cambiado y por lo tanto se revertirá el valor en el ver de nuevo a lo que debería ser.

Su máquina virtual obviamente necesitará acceder a la actual Dispatcher. Consulte my blog post en una clase base de VM si necesita algunos consejos sobre cómo hacer esto.

+0

esto funcionó muy bien -gracias! No estaba seguro de cómo volver a enviar el mensaje para que la Vista se actualizara. –

+1

Debido a [cambios en WPF 4.0] (https://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/) siga más [solución completa de @ NathanAW] (http://stackoverflow.com/a/2709931/197371) –

1

Otra manera de hacerlo (asegúrese de que también leer los comentarios):

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

Desde el enlace: Otra solución para el problema de la llamada recursiva de controlador de eventos sin variable global es cancelar manejador asignación antes del cambio de selección programática, y reasignarla después de eso.

Ex:

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

Gracias por esta pregunta y respuestas. El Dispatcher.BeginInvoke me ayudó y fue parte de mi solución final, pero la solución anterior no funcionó en mi aplicación WPF 4.

Armado una pequeña muestra para descubrir por qué.Tuve que agregar un código que en realidad cambiaba temporalmente el valor de la variable subyacente del miembro, de modo que cuando WPF volviera a consultar el getter, vería que el valor se había chained. De lo contrario, la IU no reflejaba correctamente la cancelación y la llamada a BeginInvoke() no hacía nada.

Here's a my blog post con mi ejemplo que muestra una implementación que no funciona y funciona.

Mi colocador terminó con este aspecto:

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

He hecho esto en mi setter. BUt esto no está funcionando para mí. – Virus

+0

¿Qué versión de .net estás usando? ¿Qué comportamiento estás viendo? – NathanAW

1

Mi manera de hacerlo es dejar que el cambio de ir a través y realizarse la validación en un lambda que se BeginInvoked en el despachador.

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 en su modelo de vista:

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