Weiß jemand, wie ich CanExecute
zwingen kann, auf einen benutzerdefinierten Befehl (Josh Smiths RelayCommand
) gerufen zu werden?
Normalerweise wird CanExecute
aufgerufen, wenn auf der Benutzeroberfläche Interaktionen auftreten. Wenn ich auf etwas klicke, werden meine Befehle aktualisiert.
Ich habe eine Situation, in der die Bedingung für CanExecute
durch einen Timer im Hintergrund ein-/ausgeschaltet wird. Da dies nicht von der Benutzerinteraktion gesteuert wird, wird CanExecute
erst aufgerufen, wenn der Benutzer mit der Benutzeroberfläche interagiert. Das Endergebnis ist, dass meine Button
aktiviert/deaktiviert ist, bis der Benutzer darauf klickt. Nach dem Klick wird es korrekt aktualisiert. Manchmal scheint die Variable Button
aktiviert zu sein. Wenn der Benutzer jedoch klickt, wird er deaktiviert und nicht ausgelöst.
Wie kann ich eine Aktualisierung im Code erzwingen, wenn der Timer die Eigenschaft ändert, die CanExecute
betrifft? Ich habe versucht, PropertyChanged
(INotifyPropertyChanged
) auf die Eigenschaft abzufeuern, die CanExecute
betrifft, aber das hat nicht geholfen.
Beispiel XAML:
<Button Content="Button" Command="{Binding Cmd}"/>
Beispielcode 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; }
Ich war vor langer Zeit auf CommandManager.InvalidateRequerySuggested () aufmerksam und habe es verwendet, aber es funktionierte manchmal nicht für mich. Ich habe endlich herausgefunden, warum das so war! Obwohl es nicht wie andere Aktionen ausgelöst wird, MÜSSEN Sie es im Haupt-Thread aufrufen.
Das Aufrufen eines Hintergrund-Threads scheint zu funktionieren, aber die Benutzeroberfläche bleibt manchmal deaktiviert. Ich hoffe wirklich, dass dies jemandem hilft und die Stunden, die ich gerade verschwendet habe, rettet.
Eine Problemumgehung bindet IsEnabled
an eine Eigenschaft:
<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>
und implementieren Sie diese Eigenschaft dann in Ihrem ViewModel. Dies macht es auch für UnitTesting etwas einfacher, mit den Eigenschaften zu arbeiten, anstatt mit Befehlen zu sehen, ob der Befehl zu einem bestimmten Zeitpunkt ausgeführt werden kann.
Ich persönlich finde es bequemer.
Wahrscheinlich wird diese Variante zu Ihnen passen:
public interface IRelayCommand : ICommand
{
void UpdateCanExecuteState();
}
Implementierung:
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}"/>
Danke Jungs für die Tipps. Hier ist ein bisschen Code, wie man diesen Aufruf von einem BG-Thread an den UI-Thread marshallt:
private SynchronizationContext syncCtx; // member variable
Im Konstruktor:
syncCtx = SynchronizationContext.Current;
Im Hintergrund-Thread, um das Requery auszulösen:
syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );
Hoffentlich hilft das.
- Michael
Um nur einen einzelnen GalaSoft.MvvmLight.CommandWpf.RelayCommand zu aktualisieren, können Sie verwenden
mycommand.RaiseCanExecuteChanged();
und für mich habe ich eine Erweiterungsmethode erstellt:
public static class ExtensionMethods
{
public static void RaiseCanExecuteChangedDispatched(this RelayCommand cmd)
{
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
}
public static void RaiseCanExecuteChangedDispatched<T>(this RelayCommand<T> cmd)
{
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
}
}