2010-09-21 2 views
6

Sto cercando una soluzione elegante per il seguente problema.WPF - Elegante modo di disabilitare e abilitare diversi controlli basati su diversi stati del modello utilizzando MVVM

Supponiamo che abbiamo un (View) Modello con le seguenti proprietà booleane:

  • Alpha
  • Beta
  • Gamma
  • Delta

successivo ho 5 controlli la superficie che deve essere visibile solo quando viene soddisfatta una condizione basata su tali proprietà. Naturalmente, non appena una di queste proprietà viene aggiornato il cambiamento dovrebbe essere propagate immediatelly:

  • ControlA -> Alpha & & (Beta || Gamma)
  • ControlB -> Delta
  • controlC -> Delta || Beta
  • controld -> Gamma & & Alpha & & Delta
  • contrôle -> Alpha || Gamma

L'unica soluzione che ho trovato finora è l'utilizzo di MultiValueConverters.

Esempio per ControlA:

<ControlA> 
    <ControlA.Visibility> 
     <MultiBinding Converter={StaticResource ControlAVisibilityConverter}> 
      <Binding Path="Alpha"/> 
      <Binding Path="Beta"/> 
      <Binding Path="Gamma"/> 
     </MultiBinding> 
    </ControlA.Visibility> 
</ControlA> 

Questo ControlAVisibilityConverter verifica la presenza di condizioni di "Alpha & & (Beta || Gamma)" e restituisce il valore appropriato.

Funziona .. beh .. ma forse puoi trovare una soluzione più elegante?

Grazie, TwinHabit

+0

Penso che sia un buon approccio –

risposta

5

Scrivere un convertitore per ogni regola mette la logica aziendale in due posizioni in questo caso (nel convertitore e nel modello di visualizzazione). Suggerisco di creare una proprietà/flag per ciascun controllo nel ViewModel con gli eventi INotifyPropertyChanged per decidere se il controllo è visibile (o altro comportamento).

Nota che quando guardi il mio modello di visualizzazione (sotto) vedrai che espongo le proprietà di tipo bool e Visibilità.

Se è necessario utilizzare la proprietà come regola generale, utilizzare bool e un DataTrigger.

public bool ControlD 

Se avete solo bisogno di controllare la visibilità è possibile associare alla visibilità diretta:

public Visibility ControlA 

UPDATE: A causa del commento di @Wallstreet programmatore, ho aggiunto un'altra opzione per utilizzare un BooleanVisibilityConverter . Ho aggiornato il quinto controllo di seguito per riflettere su come utilizzare un convertitore. Ho aggiunto il codice per il convertitore in basso.

Ecco una finestra di prova in XAML:

<Window x:Class="ControlVisibleTrigger.Views.MainView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Main Window" Height="400" Width="800"> 
    <Window.Resources> 
    <Style x:Key="DropDownStyle" TargetType="TextBox"> 
     <Setter Property="Visibility" Value="Hidden"/> 
     <Style.Triggers> 
      <DataTrigger Binding="{Binding ControlC}" Value="True"> 
       <Setter Property="Visibility" Value="Visible"/> 
      </DataTrigger> 
     </Style.Triggers> 
    </Style> 
    </Window.Resources> 
    <DockPanel> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition/> 
      <RowDefinition/> 
      <RowDefinition/> 
      <RowDefinition/> 
      <RowDefinition/> 
      <RowDefinition/> 
     </Grid.RowDefinitions> 
     <StackPanel Grid.Row="0"> 
      <CheckBox IsChecked="{Binding Path=Alpha,Mode=TwoWay}" Content="Alpha"/> 
      <CheckBox IsChecked="{Binding Path=Beta,Mode=TwoWay}" Content="Beta"/> 
      <CheckBox IsChecked="{Binding Path=Gamma,Mode=TwoWay}" Content="Gamma"/> 
      <CheckBox IsChecked="{Binding Path=Delta,Mode=TwoWay}" Content="Delta"/> 
     </StackPanel> 
     <TextBox Grid.Row="1" Visibility="{Binding Path=ControlA}" Text="Binding to visibility"/> 
     <Button Grid.Row="2" Visibility="{Binding Path=ControlB}" Content="Binding to visibility"/> 
     <TextBox Grid.Row="3" Style="{StaticResource DropDownStyle}" Text="Using WindowResource DataTrigger"/> 
     <TextBox Grid.Row="4" Text="Using Local DataTrigger"> 
      <TextBox.Style> 
       <Style TargetType="TextBox"> 
       <Setter Property="Visibility" Value="Hidden"/> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding ControlD}" Value="True"> 
         <Setter Property="Visibility" Value="Visible"/> 
        </DataTrigger> 
       </Style.Triggers> 
       </Style> 
      </TextBox.Style> 
     </TextBox> 
     <Button Grid.Row="5" 
       Content="Press me" 
       Visibility="{Binding Path=ControlE, Converter={StaticResource booleanVisibilityConverter}, ConverterParameter=True, Mode=OneWay}"> 
    </Grid> 
    </DockPanel> 
</Window> 

Ecco il ViewModel:

public class MainViewModel : ViewModelBase 
{ 
    public MainViewModel() 
    { 
    } 

    private bool _alpha = true; 
    public bool Alpha 
    { 
    get 
    { 
     return _alpha; 
    } 
    set 
    { 
     _alpha = value; 
     OnPropertyChanged("ControlA"); 
     OnPropertyChanged("ControlB"); 
     OnPropertyChanged("ControlC"); 
     OnPropertyChanged("ControlD"); 
     OnPropertyChanged("ControlE"); 
    } 
    } 

    private bool _beta = true; 
    public bool Beta 
    { 
    get 
    { 
     return _beta; 
    } 
    set 
    { 
     _beta = value; 
     OnPropertyChanged("ControlA"); 
     OnPropertyChanged("ControlB"); 
     OnPropertyChanged("ControlC"); 
     OnPropertyChanged("ControlD"); 
     OnPropertyChanged("ControlE"); 
    } 
    } 

    private bool _gamma = true; 
    public bool Gamma 
    { 
    get 
    { 
     return _gamma; 
    } 
    set 
    { 
     _gamma = value; 
     OnPropertyChanged("ControlA"); 
     OnPropertyChanged("ControlB"); 
     OnPropertyChanged("ControlC"); 
     OnPropertyChanged("ControlD"); 
     OnPropertyChanged("ControlE"); 
    } 
    } 

    private bool _delta = true; 
    public bool Delta 
    { 
    get 
    { 
     return _delta; 
    } 
    set 
    { 
     _delta = value; 
     OnPropertyChanged("ControlA"); 
     OnPropertyChanged("ControlB"); 
     OnPropertyChanged("ControlC"); 
     OnPropertyChanged("ControlD"); 
     OnPropertyChanged("ControlE"); 
    } 
    } 

    public Visibility ControlA 
    { 
    get 
    { 
     Visibility result = Visibility.Hidden; 
     if (Alpha && (Beta || Gamma)) 
     { 
      result = Visibility.Visible; 
     } 
     return result; 
    } 
    } 

    public Visibility ControlB 
    { 
    get 
    { 
     Visibility result = Visibility.Hidden; 
     if (Delta) 
     { 
      result = Visibility.Visible; 
     } 
     return result; 
    } 
    } 

    private bool _controlC = false; 
    public bool ControlC 
    { 
    get 
    { 
     return Delta || Beta; 
    } 
    } 

    private bool _controlD = false; 
    public bool ControlD 
    { 
    get 
    { 
     return Gamma && Alpha && Delta; 
    } 
    } 

    private bool _controlE = false; 
    public bool ControlE 
    { 
    get 
    { 
     return Alpha || Gamma; 
    } 
    } 
} 

Ecco il convertitore:

public class BooleanVisibilityConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
    if((value == null) || (!(value is bool))) 
     return Binding.DoNothing; 

    Visibility elementVisibility; 
    bool shouldCollapse = ((bool)value); 

    if(parameter != null) 
    { 
     try 
     { 
     bool inverse = System.Convert.ToBoolean(parameter); 

     if(inverse) 
      shouldCollapse = !shouldCollapse; 
     } 
     catch 
     { 
     } 
    } 

    elementVisibility = shouldCollapse ? Visibility.Collapsed : Visibility.Visible; 
    return elementVisibility; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
    throw new NotImplementedException(); 
    } 
} 
+3

Se si parla di best practice quando si tratta di MVVM, le VM non dovrebbero avere una logica di visualizzazione specifica, come i tipi di ritorno Visibilità. Quelle proprietà dovrebbero essere di tipo bool. Nella vista, si utilizzerà quindi un valore BooleanToVisibilityConverter per comprimere i controlli. –

+0

Sono d'accordo, vorrei usare anche l'opzione bool. – Zamboni

+0

Grazie a @Wallstreet Programmer, ho aggiunto il convertitore come suggerito a questa risposta. – Zamboni

0

Se i controlli supportano i comandi (ad esempio se sono pulsanti), utilizzare modello di comando. Con RelayCommand (cercalo), puoi specificare la condizione in cui il controllo è abilitato con un'espressione lambda (che è esattamente ciò di cui hai bisogno). Ha però bisogno di code-behind.

+0

In realtà questo approccio non funziona. Innanzitutto, non tutti i miei controlli utilizzano il modello ICommand. In secondo luogo, per quanto ne so, ICommand CanExecute == false disattiva solo i controlli. Ma voglio essere libero di scegliere se voglio nascondere o disabilitare un controllo. Inoltre ho bisogno che la visibilità del mio controllo venga aggiornata immediatamente quando cambia il ViewModel. Ciò non viene fornito con CanExecute (eccetto se si chiama costantemente CommandManager.Requery ...) – TwinHabit

3

Partendo dal presupposto che c'è una ragione logica di business per vedere se i controlli debbano essere visualizzati o meno avrei sicuramente la logica memorizzata come un bool nel ViewModel (th dovrei nominarlo secondo la logica di business ad esempio: CriteriaA non ControlAVisible). Ciò consente un facile test dell'unità per garantire che i criteri siano impostati correttamente come alfa, beta, gamma e delta change. A parte questo, sono d'accordo con la risposta di Wallstreet Programmers (anche se non ho il rappresentante per commentare o votare la sua risposta).