Обновить команду WPF
Кто-нибудь знает, как я могу заставить CanExecute
получить вызов по пользовательской команде (Josh Smith 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; }
Ответы
Ответ 2
Я давно знал CommandManager.InvalidateRequerySposed() и использовал его, но иногда он не работал на меня. Наконец я понял, почему это так! Несмотря на то, что он не бросается, как некоторые другие действия, вы должны называть его основной темой.
Вызов его в фоновом потоке будет работать, но иногда он отключается. Я действительно надеюсь, что это поможет кому-то, и сэкономит им часы, которые я просто потратил впустую.
Ответ 3
Обходным путем для этого является привязка IsEnabled
к свойству:
<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>
а затем реализовать это свойство в ViewModel. Это также облегчает работу UnitTesting со свойствами, а не командами, чтобы увидеть, может ли команда быть выполнена в определенный момент времени.
Я лично считаю его более удобным.
Ответ 4
Вероятно, этот вариант вам подойдет:
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}"/>
Ответ 5
Спасибо, ребята, за советы. Здесь немного кода о том, как маршалировать этот вызов из потока BG в поток пользовательского интерфейса:
private SynchronizationContext syncCtx; // member variable
В конструкторе:
syncCtx = SynchronizationContext.Current;
В фоновом потоке, чтобы вызвать запрос:
syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );
Надеюсь, что это поможет.
- Майкл