Вопрос
Кто-нибудь знает, как я могу заставить CanExecute
чтобы получить вызов по пользовательской команде (Джоша Смита RelayCommand
)?
Обычно, CanExecute
вызывается всякий раз, когда взаимодействие происходит в пользовательском интерфейсе.Если я нажимаю на что-то, мои команды обновляются.
У меня есть ситуация, когда условие для CanExecute
включается / выключается таймером за кулисами.Потому что это не зависит от взаимодействия с пользователем, CanExecute
не вызывается до тех пор, пока пользователь не начнет взаимодействовать с пользовательским интерфейсом.Конечным результатом является то, что мой Button
остается включенным / отключенным до тех пор, пока пользователь не нажмет на него.После щелчка он обновляется корректно.Иногда в Button
отображается включенным, но когда пользователь нажимает, оно меняется на отключенное вместо срабатывания.
Как я могу принудительно обновить код, когда таймер изменяет свойство, которое влияет CanExecute
?Я попытался выстрелить PropertyChanged
(INotifyPropertyChanged
) на имущество , которое влияет CanExecute
, но это не помогло.
Пример XAML:
<Button Content="Button" Command="{Binding Cmd}"/>
Пример кода, лежащий в основе:
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; }
Другие советы
Я был осведомлен о CommandManager.InvalidateRequerySuggested() давным-давно и использовал его, но иногда у меня это не срабатывало.Я наконец-то понял, почему это было так!Даже если это не выполняется, как некоторые другие действия, вы ДОЛЖНЫ вызвать его в основном потоке.
Вызов его в фоновом потоке, по-видимому, будет работать, но иногда пользовательский интерфейс остается отключенным.Я действительно надеюсь, что это кому-то поможет и сэкономит им часы, которые я только что потратил впустую.
Обходной путь для этого является обязательным IsEnabled
к собственности:
<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>
а затем реализуйте это свойство в вашей ViewModel.Это также немного упрощает для UnitTesting работу со свойствами, а не с командами, чтобы увидеть, может ли команда быть выполнена в определенный момент времени.
Лично я нахожу это более удобным.
Вероятно, этот вариант вам подойдет:
public interface IRelayCommand : ICommand
{
void UpdateCanExecuteState();
}
Реализация:
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();
}
}
Используя простой:
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}"/>
Спасибо, ребята, за советы.Вот немного кода о том, как перенаправить этот вызов из потока BG в поток пользовательского интерфейса:
private SynchronizationContext syncCtx; // member variable
В конструкторе:
syncCtx = SynchronizationContext.Current;
В фоновом потоке, чтобы запустить запрос:
syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );
Надеюсь, это поможет.
-- Майкл
Чтобы обновить только один файл GalaSoft.MVVMLight.CommandWpf.RelayCommand, вы могли бы использовать
mycommand.RaiseCanExecuteChanged();
и для себя я создал метод расширения:
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(); }));
}
}