2009-04-23 10 views
64

Weiß jemand, wie ich CanExecute erzwingen kann, um auf einen benutzerdefinierten Befehl (Josh Smith RelayCommand) aufgerufen werden?Refresh WPF Befehl

In der Regel wird CanExecute aufgerufen, wenn eine Interaktion auf der Benutzeroberfläche auftritt. Wenn ich auf etwas klicke, werden meine Befehle aktualisiert.

Ich habe eine Situation, in der die Bedingung für CanExecute wird von einem Timer hinter den Kulissen ein-/ausgeschaltet wird. Da dies nicht durch Benutzerinteraktion ausgelöst wird, wird CanExecute nicht aufgerufen, bis der Benutzer mit der Benutzeroberfläche interagiert. Das Endergebnis ist, dass meine Button aktiviert/deaktiviert bleibt, bis der Benutzer darauf klickt. Nach dem Klick wird es korrekt aktualisiert. Manchmal scheint Button aktiviert zu sein, aber wenn der Benutzer darauf klickt, wird er deaktiviert statt zu feuern.

Wie kann ich eine Aktualisierung im Code erzwingen, wenn der Timer die Eigenschaft ändert, die sich auf CanExecute auswirkt? Ich habe versucht, PropertyChanged (INotifyPropertyChanged) auf die Eigenschaft, die CanExecute betrifft, feuern, aber das hat nicht geholfen.

Beispiel XAML:

<Button Content="Button" Command="{Binding Cmd}"/> 

Beispiel Code hinter:

private ICommand m_cmd; 
public ICommand Cmd 
{ 
    if (m_cmd == null) 
     m_cmd = new RelayCommand(
      (param) => Process(), 
      (param) => EnableButton); 

    return m_cmd; 
} 

// Gets updated from a timer (not direct user interaction) 
public bool EnableButton { get; set; } 
+0

Haben Sie versucht, INotifyPropertyChanged für den Befehl zu erhöhen? Sie brauchen kein Feld für den Befehl, sondern geben jedes Mal ein neues zurück. Diese Kombination sollte funktionieren. Oder erstellen Sie einen neuen Befehl nur für den Fall, wenn Sie das Forcen benötigen. – egaga

Antwort

98
+1

Schlägst du vor, diese aus einer ViewModel-Klasse aufzurufen? –

+2

Nicht unbedingt, da dies Ihre Klasse nur schwer testen lässt. Probieren Sie es aus und verschieben Sie es gegebenenfalls in einen Dienst. Eine weitere Option ist das Hinzufügen einer Methode zu RelayCommand, mit der Sie CanExecuteChanged nur für diesen Befehl auslösen können (CommandManager.InvalidRequerySuggested macht alle Befehle ungültig, was etwas übertrieben ist). –

+23

Interessant ... Es funktioniert, aber es muss auf dem UI-Thread aufgerufen werden. Ich bin nicht überrascht. –

27

Ich war bewusst CommandManager.InvalidateRequerySuggested() vor langer Zeit, und verwendet es, aber es funktionierte manchmal nicht für mich. Ich habe schließlich herausgefunden, warum das der Fall war! Obwohl es nicht wie andere Aktionen geworfen wird, müssen Sie es im Hauptthread aufrufen.

Das Aufrufen eines Hintergrundthreads scheint zu funktionieren, aber manchmal bleibt die Benutzeroberfläche deaktiviert. Ich hoffe wirklich, dass das jemandem hilft und erspart ihnen die Stunden, die ich gerade verschwendet habe.

4

Danke Jungs für die Tipps. Hier ist ein Stück Code wie dieser Anruf Marschall von einem BG Thread zu dem UI-Thread:

private SynchronizationContext syncCtx; // member variable 

im Konstruktor:

syncCtx = SynchronizationContext.Current; 

Auf dem Hintergrund-Thread, die requery auszulösen:

syncCtx.Post(delegate { CommandManager.InvalidateRequerySuggested(); }, null); 

Hoffe, dass hilft.

- Michael

+3

Scheint, es wäre besser, den Dispatcher aufzurufen. BeginInvoke() –

+0

Hallo Josh. Vielleicht wäre es besser. Intern verwendet Dispatcher.BeginInvoke() die SynchronizationContextSwitcher-Klasse, die trotzdem an den SynchronizationContext delegiert wird. –

14

Eine Abhilfe für die verbindlich IsEnabled auf eine Eigenschaft:

<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/> 

und dann diese Eigenschaft in Ihrem Ansichtsmodell implementieren. Dies macht es auch für UnitTesting einfacher, mit den Eigenschaften zu arbeiten als mit Befehlen, um zu sehen, ob der Befehl zu einem bestimmten Zeitpunkt ausgeführt werden kann.

Ich persönlich finde es bequemer.

+0

Wie aktualisiere ich IsEnabled? – visc

+0

Ich würde vermeiden, alles zu annullieren, das ist eine bessere und einfachere Lösung – Asheh

5

Wahrscheinlich wird diese Variante zu Ihnen passen:

public interface IRelayCommand : ICommand 
{ 
    void UpdateCanExecuteState(); 
} 

Umsetzung:

public class RelayCommand : IRelayCommand 
{ 
    public event EventHandler CanExecuteChanged; 


    readonly Predicate<Object> _canExecute = null; 
    readonly Action<Object> _executeAction = null; 

    public RelayCommand(Action<object> executeAction,Predicate<Object> canExecute = null) 
    { 
     _canExecute = canExecute; 
     _executeAction = executeAction; 
    } 


    public bool CanExecute(object parameter) 
    { 
     if (_canExecute != null) 
      return _canExecute(parameter); 
     return true; 
    } 

    public void UpdateCanExecuteState() 
    { 
     if (CanExecuteChanged != null) 
      CanExecuteChanged(this, new EventArgs()); 
    } 



    public void Execute(object parameter) 
    { 
     if (_executeAction != null) 
      _executeAction(parameter); 
     UpdateCanExecuteState(); 
    } 
} 

Mit einfachen:

public IRelayCommand EditCommand { get; protected set; } 
... 
EditCommand = new RelayCommand(EditCommandExecuted, CanEditCommandExecuted); 

protected override bool CanEditCommandExecuted(object obj) 
    { 
     return SelectedItem != null ; 
    } 

    protected override void EditCommandExecuted(object obj) 
    { 
     // Do something 
    } 

    ... 

    public TEntity SelectedItem 
    { 
     get { return _selectedItem; } 
     set 
     { 
      _selectedItem = value; 

      //Refresh can execute 
      EditCommand.UpdateCanExecuteState(); 

      RaisePropertyChanged(() => SelectedItem); 
     } 
    } 

XAML:

<Button Content="Edit" Command="{Binding EditCommand}"/> 
+1

Das ist nicht ideal, weil es starke Verweise auf die Handler erzeugt, was zu Speicherlecks führt. –

+0

funktioniert nicht, wenn executeAction einen neuen Arbeitsthread startet – Steve