2012-04-03 6 views
15

Ho una finestra basata su MVVM con molti controlli e il mio modello implementa IDataErrorInfo.Come forzare l'aggiornamento degli errori di convalida su View from ViewModel usando IDataErrorInfo?

C'è anche un pulsante SaveCommand, che esegue la convalida analizzando la proprietà Model.Error.

La visualizzazione mostra il bordo rosso predefinito intorno controlli con errori solo quando cambio il valore di un particolare controllo, o quando notificare il cambiamento di tale proprietà utilizzando PropertyChanged.

Come posso forzare View per visualizzare tutti gli errori di convalida anche quando non ho toccato i controlli?

Tutte le associazioni di convalida includono ValidatesOnDataErrors=True, NotifyOnValidationError=True.

so una soluzione è quella di avere una casella di aggregazione con tutti gli errori, ma io preferirei per visualizzare gli errori su base per-controllo.

Non voglio innescare Model.NotifyPropertyChanged per ogni proprietà vincolati da ViewModel.

Io uso WPF 4.0, non Silverlight, quindi INotifyDataErrorInfo non funzionerà.

risposta

12

si menziona che non si vuole alzare immobili cambiato per le proprietà si associa a, ma questo è davvero il modo più semplice per ottenere questo risultato. Chiamando PropertyChanged con nessun parametro si alza per tutte le proprietà nel tuo viewmodel.

In alternativa è possibile aggiornare le associazioni (e la forza revalidation) su alcun controllo in questo modo:

myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource(); 
+2

Grazie per il trucco con PropertyChanged. Non sapevo che fosse possibile. Ho trovato un'altra discussione su questo argomento: http://stackoverflow.com/questions/1135012/wpf-mvvm-can-a-single-propertychanged-update-all-the-data-bindings-of-a-datate if chiunque è interessato Questa è una buona risposta se qualcuno ha un solo viewModel semplice.Tuttavia ho una vista complessa con ViewModels nidificati quindi dovrei scrivere codice per chiamare PropertyChanged una volta per ogni modello annidato associato/ViewModel che implementa INotifyPropertyChanged – surfen

+0

È bello sapere questo trucco se si desidera aggiornare solo una parte della vista relativa a un particolare ViewModel – surfen

+0

myControl.GetBindingExpression (ControlType.ControlProperty) .UpdateTarget(); effettivamente ottiene la tua convalida aggiornata senza aggiornare la tua proprietà di origine. – r41n

2

La soluzione migliore che ho trovato finora che funziona è quello di cambiare DataContext a nulla e di nuovo all'istanza di ViewModel.

Questo innesca l'aggiornamento per i controlli sulla considerazione che ha DataContext legato a InnerViewModel:

public void ForceUpdateErrors() { 
    var tmpInnerVM = _mainViewModel.InnerViewModel; 
    _mainViewModel.InnerViewModel = null; 
    _mainViewModel.InnerViewModel = tmpInnerVM; 
} 

Si consiglia di verificare se i dati non vengono persi dopo questo trucco. Ho avuto un caso che questo codice ha attivato l'aggiornamento di origine per ComboBox.SelectedItem con null, ma sono riuscito a risolverlo. È stato causato dall'utilizzo di BindingProxy basato su risorse e dall'ordine di propagazione DataContext=null nella gerarchia di controllo.

1

Questo 'Hack' ha funzionato per me temporanea, per forzare l'evento InotifyChanged, basta assegnare il controllo indietro di essa la propria soddisfare. Fatelo prima di valutare la funzione HasError dei bind. Per esempio una casella di testo sarebbe:

((TextBox)child).Text = ((TextBox)child).Text; 

E poi un esempio completo (prima ho sentito questo non è vero MVVM, ho ricevuto direttamente una maniglia sulla griglia di partenza per la facilità di mostrare questo pezzo di codice presentato)

 public bool Validate() 
    {   
     bool hasErr = false; 

     for (int i = 0; i != VisualTreeHelper.GetChildrenCount(grd); ++i) 
     { 
      DependencyObject child = VisualTreeHelper.GetChild(grd, i); 
      if (child is TextBox) 
      { 
       bool pp = BindingOperations.IsDataBound(child, TextBox.TextProperty); 
       if (pp) 
       { 

        ((TextBox)child).Text = ((TextBox)child).Text; 

        hasErr = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).HasError; 
        System.Collections.ObjectModel.ReadOnlyCollection<ValidationError> errors = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).ValidationErrors; 
        if (hasErr) 
        { 
         main.BottomText.Foreground = Brushes.Red; 
         main.BottomText.Text = BindingOperations.GetBinding(child, TextBox.TextProperty).Path.Path.Replace('.', ' ') + ": " + errors[0].ErrorContent.ToString(); 
         return false; 
        } 
       } 
      } 
      if (child is DatePicker) 
      { 
       ...      
      } 
     } 

     return true; 
    }