2010-09-21 15 views
6

Estoy buscando una solución elegante para el siguiente problema.WPF - Forma elegante de deshabilitar y habilitar diferentes controles basados ​​en diferentes estados del Modelo usando MVVM

Supongamos que tenemos un modelo (Ver) con las siguientes propiedades booleanas:

  • Alfa
  • Beta
  • Gamma
  • Delta

que viene tengo 5 controla el la superficie que solo será visible cuando se cumpla una condición basada en esas propiedades. Por supuesto, tan pronto como una de esas propiedades se actualiza el cambio debe ser propagado immediatelly:

  • ControlA -> Alfa & & (Beta Gamma ||)
  • ControlB -> Delta
  • ControlC -> Delta || Beta
  • CONTROLD -> Gamma & & & & Alfa Delta
  • Controle -> Alfa || Gamma

La única solución que se me ocurrió hasta ahora es usar MultiValueConverters.

Ejemplo para ControlA:

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

Este ControlAVisibilityConverter comprueba condición "Alpha & & (Beta || Gamma)" y devuelve el valor apropiado.

Funciona ... bueno ... ¿pero tal vez se te ocurra una solución más elegante?

Gracias, TwinHabit

+0

Creo que es un buen enfoque –

Respuesta

5

Escribir un convertidor para cada regla pone su lógica empresarial en dos lugares en este caso (en el convertidor y el modelo de vista). Sugiero crear una propiedad/indicador para cada control en su ViewModel con eventos INotifyPropertyChanged para decidir si el control es visible (u otro comportamiento).

Tenga en cuenta que cuando miras mi modelo de vista (a continuación) verás que expongo las propiedades de tipo bool y Visibilidad.

Si necesita utilizar la propiedad como regla general, use bool y un DataTrigger.

public bool ControlD 

Si sólo necesita para controlar la visibilidad se puede enlazar con visibilidad directa:

public Visibility ControlA 

ACTUALIZACIÓN: Debido al comentario de @Wallstreet programador, he añadido otra opción para utilizar un BooleanVisibilityConverter . Actualicé el quinto control a continuación para reflejar cómo usar un convertidor. Agregué el código para el convertidor en la parte inferior.

Aquí está una ventana de prueba 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> 

Aquí es el modelo de vista:

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

Aquí es el convertidor:

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 hablamos de las mejores prácticas cuando se trata de MVVM, las VM no deberían tener lógica de visualización específica, como los tipos de retorno de visibilidad. Esas propiedades deberían ser de tipo bool. En la vista, entonces usaría BooleanToVisibilityConverter para colapsar controles. –

+0

Estoy de acuerdo, usaría la opción bool también. – Zamboni

+0

Gracias @Wallstreet Programmer, agregué el convertidor como sugirió a esta respuesta. – Zamboni

0

Si los controles admiten los comandos (por ejemplo, si son botones), use el comando. Con RelayCommand (búscalo), puedes especificar la condición bajo la cual se habilita el control con una expresión lambda (que es exactamente lo que necesitas). Sin embargo, necesita un código subyacente.

+0

En realidad, este enfoque no funciona. Primero, no todos mis controles hacen uso del patrón ICommand. En segundo lugar, por lo que sé, ICommand CanExecute == false solo deshabilita los controles. Pero quiero ser libre de elegir si quiero esconder o deshabilitar un control. Además, necesito que la visibilidad de mi control se actualice de inmediato cuando cambie ViewModel. Eso no se proporciona con CanExecute (excepto si llama constantemente a CommandManager.Requery ...) – TwinHabit

3

Suponiendo que hay una razón lógica de negocio para si los controles deberían mostrarse, definitivamente tendría la lógica almacenada como un bool en el ViewModel (th Aunque lo llamaría de acuerdo con la lógica comercial, por ejemplo: CriteriaA no ControlAVisible). Esto permite realizar pruebas unitarias sencillas para garantizar que los Criterios se configuren correctamente como alfa, beta, gamma y delta. Aparte de eso, estaría de acuerdo con la respuesta de Wallstreet Programmers (aunque no tengo el representante para comentar o votar su respuesta).

Cuestiones relacionadas