2012-05-18 13 views
9

Supongamos que tiene un botón cuya propiedad command está vinculada a algunos ICommand del elemento actual de alguna colección.¿Cómo desactivo un botón vinculado a ICommand de un elemento actual cuando no hay un elemento actual?

Cuando la colección es null, el botón permanece habilitado y al hacer clic parece ser una operación no operativa. En cambio, quiero que el botón permanezca desactivado. Descubrí lo siguiente para mantener los botones desactivados siempre que la colección sea nula. Sin embargo, parece un poco complicado para algo que podría quizás lograr de una manera más natural, más simple y más MVVM.

De ahí la pregunta: ¿hay alguna manera más simple de mantener ese botón deshabilitado, idealmente donde no se usa código subyacente?

.xaml:

<Button Content="Do something" > 
    <Button.Command> 
     <PriorityBinding> 
      <Binding Path="Items/DoSomethingCmd" /> 
      <Binding Path="DisabledCmd" /> 
     </PriorityBinding> 
    </Button.Command> 
</Button> 

.cs:

public class ViewModel : NotificationObject 
{ 
    ObservableCollection<Foo> _items; 

    public DelegateCommand DisabledCmd { get; private set; } 

    public ObservableCollection<Foo> Items { 
     get { return _items; } 
     set { _items = value; RaisePropertyChanged("Items"); } 
    } 

    public ViewModel() 
    { 
     DisabledCmd = new DelegateCommand(DoNothing, CantDoAnything); 
    } 

    void DoNothing() { } 
    bool CantDoAnything() 
    { 
     return false; 
    } 
} 

Editar:

Un par de notas:

  1. I soy consciente que puedo usar expresiones lambda, pero en este código de ejemplo no lo hago.
  2. I saber qué predicado es.
  3. No veo cómo hacer algo con DoSomethingCmd.CanExecute hará cualquier cosa para ayudar, ya que no hay DoSomethingCmd para acceder mientras no haya un elemento actual.
  4. Así que voy a volver a centrar mi pregunta: ¿Cómo puedo evitar el uso del DisabledCmd? No estoy interesado en subir el DoSomethingCmd ya que no es lo que estoy buscando. De lo contrario, no haría esta pregunta.

Otra edición:

Así que básicamente adoptó esta respuesta como una solución: WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?

Es, creo, exactamente lo hbarck propone.

Respuesta

6

Lo haría de forma similar a akjoshi, solo usaría un Trigger normal en lugar de un DataTrigger, y verificaría que Button.Command fuera nulo. Como siempre tiene sentido desactivar un botón que no tiene Command (especialmente en MVVM, donde no hay ningún controlador de eventos click), también sería una buena idea incluir este desencadenador en un estilo predeterminado para Buttons, para tener este comportamiento. en todos los botones de la aplicación ... No veo una razón para usar un comando ficticio.

+0

¿No desactivaría todos los botones que son parte de los controles estándar de WPF? Pueden usar eventhandlers en lugar de comandos. – STiLeTT

1

Si mira el comando delegar, el segundo parámetro es un func que le permite hacer exactamente esto, no estoy muy seguro, ¿por qué lo hace tan complejo. Si lo hace, por ejemplo:

DoSomethingCommand = new DelegateCommand(() => SampleAction,() => Items != null); 

el botón se desactivará cuando simplemente unirse a su propiedad Command a este comando, así:

<Button Command={Binding DoSomethingCommand} /> 

El botón A continuación, se desactiva automáticamente cuando la condición en el comando de delegado se convierte en falso. También debe llamar al DoSomethingCommand.RaiseCanExecuteChanged() cuando el resultado de la condición podría cambiar, que las actualizaciones IsEnabled del botón para reflejar el estado actual.

+0

Sí, soy consciente de que puedo subir el comando un nivel, pero tendría que hacer un seguimiento del elemento actual en mi modelo de vista. –

+0

¡Diría que ganar el ítem actual en ViewModel es una ventaja! – cordialgerm

+0

@pickles: sí Realizo un seguimiento de un elemento actual de forma indirecta con un 'IsSelected', pero no quiero hacer de esto una propiedad específica si no es necesario ya que la interfaz de usuario de WPF ya lo hace en el nivel de vista. –

1

Usé RelayCommands y esto tiene un constructor donde puedes crear un predicado canExecute y luego, si devuelve falso, el botón vinculado se desactivará automáticamente.

En el comando de delegado, debe volver a escribir el método CantDoAnything() para representar su activación y deshabilitar la lógica. Y el enlace debe simplemente unirse al Comando.

DelegateCommand constructor on MSDN

DelegateCommand CanExecute BugFix

+2

¿Cómo se accederá al predicado ya que no hay un elemento actual? –

+0

@Ludo El predicado le dice al comando si se puede ejecutar o no, básicamente es un segundo método que se ejecuta cuando el comando está vinculado a un botón, por ejemplo. Y si la colección cambia, puede llamar al raisecanexecutechange de su comando, o creo que también podría usar NotifyPropertyChanged. – BigL

+1

@Ludo Si escribe su parte CanExecute de su comando para devolver falso si no hay artículo, entonces su botón será diableado de manera simple. – BigL

3

Mirando el código en PresentationFramework.dll, no veo ninguna forma sencilla de hacerlo (ver ButtonBase.UpdateCanExecute). Puede tener algo de suerte derivando una clase del Button y anulando los metadatos para CommandProperty para manejar los cambios usted mismo. Pero puede evitar fácilmente tener ese código do-nothing-command en su viewmodel: cree un convertidor especial que convierta un comando null en una falla compartida ICommand siempre desactivada. Si tiene muchos botones que necesitan este tipo de comportamiento, una propiedad adjunta y un estilo pueden estar en orden.

+0

+1 Sí, acabo de figurar un enfoque 'NullToFalseConverter' como se muestra en mi edición. –

+0

Eso es un poco diferente de lo que sugiero: estás vinculando 'IsEnabled' además de' Command', pero puedes escribir un convertidor y unir 'Command' con este convertidor. Aún así, su enfoque es más simple, hasta que necesite 'IsEnabled' para otra cosa :) –

+0

O a la derecha, usted quiere definir un recurso estático como el comando alternativo para que sea reutilizable. Sí, eso suena como una mejora, y lo recordaré como una solución si, como dices, llego a necesitar unir 'IsEnabled' en otra cosa. –

1

Simplemente puede enlazar la propiedad IsEnabled del botón con el elemento actual y usar un convertidor.

He aquí una demostración completa:

<Page.Resources> 
    <Converters:NullToBoolConverter x:Key="NullToBoolConverter" 
            IsNullValue="False" IsNotNullValue="True" /> 
</Page.Resources> 

<Page.DataContext> 
    <Samples:NoCurrentItemViewModel/> 
</Page.DataContext> 

<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition /> 
     <RowDefinition Height="Auto"/> 
     <RowDefinition Height="Auto"/> 
    </Grid.RowDefinitions> 

    <ListBox 
     IsSynchronizedWithCurrentItem="True" Grid.Row="0" 
     ItemsSource="{Binding Items}" 
     DisplayMemberPath="Name"/> 

    <Button 
     Grid.Row="1" 
     Content="Do something" 
     IsEnabled="{Binding Items/, Converter={StaticResource NullToBoolConverter}}" 
     Command="{Binding Items/DoSomethingCommand}"/> 

    <Button Grid.Row="2" Content="Clear" Command="{Binding ClearCommand}"/> 
</Grid> 

Ver modelos - RelayCommand de MVVM Light

public class NoCurrentItemViewModel 
{ 
    public NoCurrentItemViewModel() 
    { 
     _items = new ObservableCollection<NoCurrentItemDetail> 
        { 
         new NoCurrentItemDetail{Name = "one"}, 
         new NoCurrentItemDetail{Name = "two"}, 
        }; 

     ClearCommand = new RelayCommand(Clear); 
    } 

    public ICommand ClearCommand { get; private set; } 

    private void Clear() 
    { 
     _items.Clear(); 
    } 

    private readonly ObservableCollection<NoCurrentItemDetail> _items; 

    public IEnumerable<NoCurrentItemDetail> Items 
    { 
     get { return _items; } 
    } 
} 

public class NoCurrentItemDetail 
{ 
    public NoCurrentItemDetail() 
    { 
     DoSomethingCommand = new RelayCommand(DoSomething); 
    } 

    private void DoSomething() 
    { 
     Debug.WriteLine("Do something: " + Name); 
    } 

    public ICommand DoSomethingCommand { get; private set; } 

    public string Name { get; set; } 
} 

El convertidor

public class NullToBoolConverter : IValueConverter 
{ 
    public NullToBoolConverter() 
    { 
     IsNullValue = true; 
     IsNotNullValue = false; 
    } 

    public bool IsNullValue { get; set; } 
    public bool IsNotNullValue { get; set; } 

    #region Implementation of IValueConverter 

    public object Convert(object value, 
     Type targetType, object parameter, CultureInfo culture) 
    { 
     return value == null ? IsNullValue : IsNotNullValue; 
    } 

    public object ConvertBack(object value, 
     Type targetType, object parameter, CultureInfo culture) 
    { 
     return Binding.DoNothing; 
    } 

    #endregion 
} 
4

Se puede crear un Trigger para comprobar si el artículo (contexto de datos del botón) es nulo, y establece el botón (o puede ser su contenedor principal como A nton mencionado) IsEnabled propiedad a false, algo como esto -

<DataTrigger 
    Binding="{Binding Path=Item}" 
    Value="{x:Null}"> 
    <Setter Property="Control.IsEnabled" Value="False" /> 
</DataTrigger> 

no estoy en condiciones de probar que en este momento, pero creo que esto debería funcionar.

Cuestiones relacionadas