2012-05-18 7 views
9

Angenommen, Sie haben eine Schaltfläche, deren command -Eigenschaft an einige ICommand des aktuellen Elements einer Sammlung gebunden ist.Wie deaktiviere ich eine Schaltfläche, die an den ICommand eines aktuellen Elements gebunden ist, wenn kein aktuelles Element vorhanden ist?

Wenn die Sammlung null ist, bleibt die Schaltfläche aktiviert und das Klicken scheint ein No-Op zu sein. Ich möchte stattdessen, dass die Schaltfläche deaktiviert bleibt. Ich habe Folgendes herausgefunden, um die Schaltflächen zu deaktivieren, wenn die Sammlung null ist. Es scheint jedoch etwas zu kompliziert für etwas, das vielleicht in einem natürlicheren, einfacheren und mehr MVVM durchgeführt werden könnte.

Daher die Frage: Gibt es eine einfachere Möglichkeit, diese Schaltfläche deaktiviert zu halten, idealerweise wo kein Code-Behind verwendet wird?

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

bearbeiten:

Ein paar Anmerkungen:

  1. I bin bewusst,, dass ich Lambda-Ausdrücke verwenden kann, aber in diesem Beispiel Code nicht.
  2. I wissen, was ein Prädikat ist.
  3. Ich sehe nicht, wie etwas mit etwas tun, um zu helfen, da es keine DoSomethingCmd Zugriff gibt, während es kein aktuelles Element gibt.
  4. Also werde ich meine Frage neu zentrieren: Wie kann ich die Verwendung der DisabledCmd vermeiden? Ich bin nicht daran interessiert, die DoSomethingCmd zu bewegen, da es nicht das ist, wonach ich suche. Ich würde diese Frage nicht anders stellen.

Ein weiterer edit:

Also im Grunde ich diese Antwort als eine Lösung angenommen: WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?

Es ist, glaube ich, genau das, was hbarck vorschlägt.

Antwort

6

Ich würde es ähnlich wie Akjoshi machen, nur würde ich einen normalen Trigger anstelle eines DataTriggers verwenden, und ich würde Button.Command überprüfen, um null zu sein. Da es immer sinnvoll ist, einen Button zu deaktivieren, der keinen Command hat (insbesondere in MVVM, wo es keine Click-Eventhandler gibt), wäre es auch eine gute Idee, diesen Trigger in einen Standardstil für Buttons einzufügen, um dieses Verhalten zu haben auf allen Tasten in der Anwendung ... Ich sehe keinen Grund, einen Dummy-Befehl zu verwenden.

+0

Würde es nicht alle Schaltflächen deaktivieren, die Teil von WPF-Standardsteuerelementen sind? Sie können Eventhandler anstelle von Befehlen verwenden. – STiLeTT

1

Wenn Sie sich den Delegate-Befehl ansehen, ist der zweite Parameter ein Func, mit dem Sie genau das tun können, ich bin nicht ganz sicher, warum machen Sie es so komplex. Wenn Sie das tun zum Beispiel:

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

die Schaltfläche wird deaktiviert, wenn Sie einfach seine Command-Eigenschaft auf diesen Befehl binden, etwa so:

<Button Command={Binding DoSomethingCommand} /> 

Die Schaltfläche dann automatisch deaktiviert wird, wenn die Bedingung im Delegate-Befehl wird falsch. Sie sollten auch DoSomethingCommand.RaiseCanExecuteChanged() aufrufen, wenn sich das Ergebnis der Bedingung ändern könnte, und die IsEnabled-Schaltfläche der Schaltfläche aktualisiert den aktuellen Status.

+0

Ja ich bin mir bewusst, dass ich den Befehl eine Ebene höher bringen kann, aber ich würde den aktuellen Artikel in meinem Viewmodel verfolgen müssen. –

+0

Ich würde sagen, das aktuelle Element im ViewModel zu fassen ist ein Plus! – cordialgerm

+0

@pickles: ja Ich behalte ein aktuelles Element indirekt mit einem 'IsSelected', aber ich möchte dies nicht zu einer bestimmten Eigenschaft machen, wenn ich es nicht tun muss, da die WPF-Benutzeroberfläche es bereits auf der Ansichtsebene tut. –

1

Ich habe RelayCommands verwendet und dies hat einen Konstruktor, in dem Sie ein canExecute-Prädikat erstellen können, und wenn es false zurückgibt, wird die gebundene Schaltfläche automatisch deaktiviert.

Beim Delegat-Befehl sollten Sie die CantDoAnything() -Methode neu schreiben, um Ihre Logik zum Aktivieren und Deaktivieren darzustellen. Und die Bindung sollte man einfach an den Command binden.

DelegateCommand constructor on MSDN

DelegateCommand CanExecute BugFix

+2

Wie wird auf das Prädikat zugegriffen, da kein aktuelles Objekt vorhanden ist? –

+0

@Ludo Das Prädikat sagt dem Befehl, ob es ausgeführt werden kann oder nicht, es ist im Grunde eine zweite Methode, die ausgeführt wird, wenn der Befehl beispielsweise an eine Schaltfläche gebunden ist. Und wenn sich die Sammlung ändert, können Sie die raisecanexecutechange Ihres Befehls aufrufen, oder ich denke, Sie könnten auch NotifyPropertyChanged verwenden. – BigL

+1

@Ludo Wenn Sie Ihren CanExecute-Teil Ihres Befehls schreiben, um false zurückzugeben, wenn es kein Element gibt, wird Ihre Schaltfläche so einfach deaktiviert. – BigL

3

auf dem Code der Suche in PresentationFramework.dll, ich sehe keine einfache Möglichkeit, es zu tun (siehe ButtonBase.UpdateCanExecute). Sie könnten etwas Glück haben, wenn Sie eine Klasse von Button ableiten und die Metadaten für CommandProperty überschreiben, um Änderungen selbst zu bearbeiten. Aber Sie können leicht vermeiden, diesen Do-nothing-Befehl-Code in Ihrem Ansichtsmodell zu haben: Erstellen Sie einen speziellen Konverter, der einen null Befehl in einen freigegebenen immer deaktivierten Fallback ICommand konvertiert. Wenn Sie viele Schaltflächen haben, die diese Art von Verhalten benötigen, können eine angefügte Eigenschaft und ein Stil in Ordnung sein.

+0

+1 Ja, ich habe gerade einen 'NullToFalseConverter'-Ansatz gefunden, wie er in meiner Bearbeitung gezeigt wird. –

+0

Das ist etwas anders als das, was ich vorschlage: Sie binden "IsEnabled" zusätzlich zu "Command", aber Sie können einen Konverter schreiben und Command mit diesem Konverter verbinden. Dennoch ist Ihr Ansatz einfacher - bis Sie 'IsEnabled' für etwas anderes brauchen. –

+0

O right Sie wollen eine statische Ressource als Fallback-Kommando definieren, um es wiederverwendbar zu machen. Ja, das hört sich nach einer Verbesserung an, und ich werde mich daran erinnern, dass es sich um einen Workaround handelt, wenn ich sage, dass ich 'IsEnabled' an etwas anderes binden muss. –

1

Sie können einfach die IsEnabled-Eigenschaft der Schaltfläche an das aktuelle Element binden und einen Konverter verwenden.

ist die komplette Demo:

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

Ansicht Modelle - RelayCommand von MVVM Licht

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

Der Konverter

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

Sie eine Trigger erstellen können, wenn das Element zu prüfen (Datenkontext der Schaltfläche) ist null, und setzen Sie Button (oder möglicherweise seine Eltern-Container als A nton erwähnt) IsEnabled Eigenschaft false, so etwas wie diese -

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

Ich bin nicht in der Lage es jetzt zu testen, aber ich denke, das sollte funktionieren.

Verwandte Themen