CanExecute 仅部分起作用
CanExecute works only partial
我正在开发需要使用按钮命令处理较长任务的 WPF .NET 5 应用程序。在任务完成之前,该按钮应该被禁用。
我正在使用来自 Microsoft.Toolkit.Mvvm:
的 RelayCommand
BtnCmd = new RelayCommand(DoSomething, CanDoSomething);
DoSomething 方法所做的第一件事是使 CanDoSomething
的 return 值为假。这可以防止再次执行 DoSomething,但它在 Button 上不可见。
在研究时,我发现 https://github.com/CommunityToolkit/MVVM-Samples/issues/41:
确实是这种情况
"Sergio0694 commented on Mar 28, 2021": "That is correct, by default RelayCommand will not automatically update its visual state on WPF...".
他推荐的解决方案是使用:https://gist.github.com/Sergio0694/130bc344e552e501563546454bd4e62a 和
<button xmlns:input="using:Microsoft.Toolkit.Mvvm.Wpf.Input"
Command="{Binding Command}"
input:RelayCommandExtensions.IsCommandUpdateEnabled="True"/>
我的 DoSomething 方法如下所示:
private async void DoSomething()
{
PropertyCheckedByCanDoSomething = false;
await Task.Delay(1000);
PropertyCheckedByCanDoSomething = true;
}
它会给出想要的视觉效果,但仅限于:PropertyCheckedByCanDoSomething = false;
使用 PropertyCheckedByCanDoSomething = true;
效果仅在单击应用程序或执行 window 切换后可见。
我该如何解决这个问题?
非常感谢您的支持。
在不知道你所有的绑定等是如何进行的情况下,我可能会通过子类化你的中继命令来处理它,比如
using System;
using System.Windows.Input;
namespace MyTestApp
{
public class MyRelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public MyRelayCommand(Action<object> execute) : this(execute, CanAlwaysExecute)
{ }
public MyRelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
// Lamda expression to execute each respectively
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object cmdParm)
{ return _canExecute(cmdParm); }
public static bool CanAlwaysExecute(object cmdParm)
{ return true; }
public void Execute(object cmdParm)
{
if (!_doingWithCallback)
_execute(cmdParm);
else
Execute2(cmdParm);
}
// The CanExecuteChanged event handler is required from the ICommand interface
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
private bool _isMyTaskRunning = false;
public bool IsMyTaskRunning
{ get { return _isMyTaskRunning; } }
private bool _doingWithCallback;
private readonly Action<object, Action> _executeWithCallback;
public MyRelayCommand(Action<object, Action> executeWithCallback) : this( executeWithCallback, CanAlwaysExecute)
{ }
public MyRelayCommand(Action<object, Action> executeWithCallback, Func<object, bool> canExecute)
{
// new flag, so when the default "Execute" method is called, it can then redirect to
// calling the Execute2() method that checks to prevent the double-click and then
// calls your function with the additional parameter of the action method to call upon completion.
_doingWithCallback = true;
_executeWithCallback = executeWithCallback;
_canExecute = canExecute;
}
public void Execute2(object cmdParm)
{
// prevent double action if running vs not
if (_isMyTaskRunning)
return;
// flag it to prevent double action
_isMyTaskRunning = true;
// trigger raising the CanExecute changed which will update the user interface
RaiseCanExecuteChanged();
// turn off when done, but if being done from a "task()" process,
// you probably want to have a return function be called when the
// TASK is finished to re-enable the button... maybe like
// NOW, call your execute function that accepts TWO parameters.
// the first is whatever parameter MAY come from the button click itself.
// the SECOND parameter will be to accept MY ACTION HERE to reset the flag when finished
System.Threading.Tasks.Task.Run(() => _executeWithCallback(cmdParm, ButtonTaskIsComplete));
}
public void ButtonTaskIsComplete()
{
_isMyTaskRunning = false;
System.Windows.Application.Current.Dispatcher.Invoke(() => { RaiseCanExecuteChanged(); });
}
}
}
可能不是最合适的,但可能会为您提供一个可能的包装器解决方案。
这里是一个示例实现,可以在您现有的表单区域中调用它。
private MyRelayCommand _myFormButton;
public MyRelayCommand MyFormButton
{ get { return _myFormButton ?? ( _myFormButton = new MyRelayCommand( YourFormMethod )); } }
public void YourFormMethod(object cmdParm, System.Action doThisWhenFinished)
{
MessageBox.Show("Something from after the click event of the button");
// NOW, the callback function so the button re-enables itself once finished.
doThisWhenFinished();
}
我遇到了与您提到的 RelayCommandExtension 类似的问题。
有一个 Github issue regarding this. The following solution 被张贴在那里:
在 RelayCommandExtensions.cs
中添加以下行:
notifiers.Add(obj, notifier);
到 OnIsCommandUpdateEnabledChanged(...)
方法,使其看起来像这样:
// Ensure a notifier is enabled so that we can monitor for changes to the command
// property in the future. This is also needed in case this attached property is
// set before the binding engine has actually resolved the target command instance.
if (!notifiers.TryGetValue(obj, out _))
{
PropertyChangeNotifier notifier = new(obj, nameof(ICommandSource.Command));
notifier.PropertyChanged += (_, _) => ToggleIsCommandUpdateEnabled(obj);
notifiers.Add(obj, notifier);
}
这对我有用。
我正在开发需要使用按钮命令处理较长任务的 WPF .NET 5 应用程序。在任务完成之前,该按钮应该被禁用。
我正在使用来自 Microsoft.Toolkit.Mvvm:
的 RelayCommand
BtnCmd = new RelayCommand(DoSomething, CanDoSomething);
DoSomething 方法所做的第一件事是使 CanDoSomething
的 return 值为假。这可以防止再次执行 DoSomething,但它在 Button 上不可见。
在研究时,我发现 https://github.com/CommunityToolkit/MVVM-Samples/issues/41:
"Sergio0694 commented on Mar 28, 2021": "That is correct, by default RelayCommand will not automatically update its visual state on WPF...".
他推荐的解决方案是使用:https://gist.github.com/Sergio0694/130bc344e552e501563546454bd4e62a 和
<button xmlns:input="using:Microsoft.Toolkit.Mvvm.Wpf.Input"
Command="{Binding Command}"
input:RelayCommandExtensions.IsCommandUpdateEnabled="True"/>
我的 DoSomething 方法如下所示:
private async void DoSomething()
{
PropertyCheckedByCanDoSomething = false;
await Task.Delay(1000);
PropertyCheckedByCanDoSomething = true;
}
它会给出想要的视觉效果,但仅限于:PropertyCheckedByCanDoSomething = false;
使用 PropertyCheckedByCanDoSomething = true;
效果仅在单击应用程序或执行 window 切换后可见。
我该如何解决这个问题?
非常感谢您的支持。
在不知道你所有的绑定等是如何进行的情况下,我可能会通过子类化你的中继命令来处理它,比如
using System;
using System.Windows.Input;
namespace MyTestApp
{
public class MyRelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public MyRelayCommand(Action<object> execute) : this(execute, CanAlwaysExecute)
{ }
public MyRelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
// Lamda expression to execute each respectively
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object cmdParm)
{ return _canExecute(cmdParm); }
public static bool CanAlwaysExecute(object cmdParm)
{ return true; }
public void Execute(object cmdParm)
{
if (!_doingWithCallback)
_execute(cmdParm);
else
Execute2(cmdParm);
}
// The CanExecuteChanged event handler is required from the ICommand interface
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
private bool _isMyTaskRunning = false;
public bool IsMyTaskRunning
{ get { return _isMyTaskRunning; } }
private bool _doingWithCallback;
private readonly Action<object, Action> _executeWithCallback;
public MyRelayCommand(Action<object, Action> executeWithCallback) : this( executeWithCallback, CanAlwaysExecute)
{ }
public MyRelayCommand(Action<object, Action> executeWithCallback, Func<object, bool> canExecute)
{
// new flag, so when the default "Execute" method is called, it can then redirect to
// calling the Execute2() method that checks to prevent the double-click and then
// calls your function with the additional parameter of the action method to call upon completion.
_doingWithCallback = true;
_executeWithCallback = executeWithCallback;
_canExecute = canExecute;
}
public void Execute2(object cmdParm)
{
// prevent double action if running vs not
if (_isMyTaskRunning)
return;
// flag it to prevent double action
_isMyTaskRunning = true;
// trigger raising the CanExecute changed which will update the user interface
RaiseCanExecuteChanged();
// turn off when done, but if being done from a "task()" process,
// you probably want to have a return function be called when the
// TASK is finished to re-enable the button... maybe like
// NOW, call your execute function that accepts TWO parameters.
// the first is whatever parameter MAY come from the button click itself.
// the SECOND parameter will be to accept MY ACTION HERE to reset the flag when finished
System.Threading.Tasks.Task.Run(() => _executeWithCallback(cmdParm, ButtonTaskIsComplete));
}
public void ButtonTaskIsComplete()
{
_isMyTaskRunning = false;
System.Windows.Application.Current.Dispatcher.Invoke(() => { RaiseCanExecuteChanged(); });
}
}
}
可能不是最合适的,但可能会为您提供一个可能的包装器解决方案。
这里是一个示例实现,可以在您现有的表单区域中调用它。
private MyRelayCommand _myFormButton;
public MyRelayCommand MyFormButton
{ get { return _myFormButton ?? ( _myFormButton = new MyRelayCommand( YourFormMethod )); } }
public void YourFormMethod(object cmdParm, System.Action doThisWhenFinished)
{
MessageBox.Show("Something from after the click event of the button");
// NOW, the callback function so the button re-enables itself once finished.
doThisWhenFinished();
}
我遇到了与您提到的 RelayCommandExtension 类似的问题。
有一个 Github issue regarding this. The following solution 被张贴在那里:
在 RelayCommandExtensions.cs
中添加以下行:
notifiers.Add(obj, notifier);
到 OnIsCommandUpdateEnabledChanged(...)
方法,使其看起来像这样:
// Ensure a notifier is enabled so that we can monitor for changes to the command
// property in the future. This is also needed in case this attached property is
// set before the binding engine has actually resolved the target command instance.
if (!notifiers.TryGetValue(obj, out _))
{
PropertyChangeNotifier notifier = new(obj, nameof(ICommandSource.Command));
notifier.PropertyChanged += (_, _) => ToggleIsCommandUpdateEnabled(obj);
notifiers.Add(obj, notifier);
}
这对我有用。