2010-09-21 27 views
6

Je cherche une solution élégante pour le problème suivant.WPF - Manière élégante de désactiver et d'activer différentes commandes basées sur différents états du modèle en utilisant MVVM

Supposons que nous avons un (Voir) Modèle avec les propriétés booléennes suivantes:

  • Alpha
  • Beta
  • Gamma
  • Delta

Ensuite, j'ai 5 contrôles sur la surface qui doit être visible uniquement lorsqu'une condition basée sur ces propriétés est remplie. Bien sûr, dès que l'une de ces propriétés est mis à jour le changement devrait être propagées immediatelly:

  • ControlA -> Alpha & & (Beta Gamma ||)
  • ControlB -> Delta
  • ControlC -> Delta || Beta
  • controld -> Gamma & & Alpha & & Delta
  • controlé -> Alpha || Gamma

La seule solution que j'ai trouvée jusqu'ici est d'utiliser MultiValueConverters.

Exemple ControlA:

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

Cette vérifie ControlAVisibilityConverter pour condition "Alpha & & (Beta || Gamma)" et renvoie la valeur appropriée.

Cela fonctionne ... bien .. mais peut-être que vous pouvez trouver une solution plus élégante?

Merci, TwinHabit

+0

Je pense que c'est une bonne approche –

Répondre

5

L'écriture d'un convertisseur pour chaque règle place deux fois votre logique métier (dans le convertisseur et le modèle de vue). Je suggère de créer une propriété/un indicateur pour chaque contrôle dans votre ViewModel avec des événements INotifyPropertyChanged pour décider si le contrôle est visible (ou un autre comportement). Notez que lorsque vous regardez mon modèle (ci-dessous), vous verrez que j'expose des propriétés de type bool et Visibilty.

Si vous avez besoin d'utiliser la propriété en règle générale, utilisez bool et DataTrigger.

public bool ControlD 

Si vous avez seulement besoin de contrôler la visibilité que vous pouvez lier à la visibilité directe:

public Visibility ControlA 

MISE À JOUR: En raison du commentaire par @Wallstreet programmeur, j'ai ajouté une autre option d'utiliser un BooleanVisibilityConverter . J'ai mis à jour le cinquième contrôle ci-dessous pour refléter l'utilisation d'un convertisseur. J'ai ajouté le code pour le convertisseur en bas.

Voici une fenêtre de test en 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> 

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

Voici le convertisseur:

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

Si nous parlons des meilleures pratiques en matière de MVVM, les machines virtuelles ne devraient pas avoir de logique spécifique à la vue, comme les types de retour de visibilité. Ces propriétés doivent être de type bool. Dans la vue, vous utiliseriez alors un BooleanToVisibilityConverter pour réduire les contrôles. –

+0

Je suis d'accord, j'utiliserais aussi l'option bool. – Zamboni

+0

Merci @Wallstreet Programmer, j'ai ajouté le convertisseur que vous avez suggéré à cette réponse. – Zamboni

0

Si les contrôles prennent en charge les commandes (par exemple si elles sont des boutons), utilisez modèle de commande. Avec RelayCommand (recherchez-le), vous pouvez spécifier la condition sous laquelle le contrôle est activé avec une expression lambda (ce qui est exactement ce dont vous avez besoin). Il a cependant besoin de code-behind.

+0

En fait, cette approche ne fonctionne pas. D'abord, toutes mes commandes n'utilisent pas le pattern ICommand. Deuxièmement, autant que je sache, ICommand CanExecute == false ne désactive que les contrôles. Mais je veux être libre de choisir si je veux cacher ou désactiver un contrôle. De plus, j'ai besoin que la visibilité de mon contrôle soit mise à jour immédiatement lorsque le ViewModel change. Cela n'est pas fourni avec CanExecute (sauf si vous appelez constamment CommandManager.Requery ...) – TwinHabit

3

En supposant qu'il y ait une raison de logique métier pour savoir si les contrôles devraient être affichés ou non, j'aurais certainement la logique stockée comme bool dans le ViewModel (th Je le nommerais selon la logique métier, par exemple: CriteriaA not ControlAVisible). Cela permet de tester facilement les unités pour s'assurer que les critères sont correctement définis en tant que changements alpha, bêta, gamma et delta. À part ça, je suis d'accord avec Wallstreet Programers (bien que je n'ai pas le représentant pour commenter ou voter sa réponse).