命令是实现MVVM的基础之一,代替了click event这种紧耦合。
RoutedUICommand与RoutedCommand的不同
RoutedUICommand继承于RoutedCommand,二者相差Text以及GetText方法
<Button Command="ApplicationCommands.Copy"></Button>
<Menu>
<MenuItem Command="ApplicationCommands.Copy"></MenuItem>
</Menu>
给Button和MenuItem绑定WPF预定义好的Command,Button的Content不会获取RoutedUICommand的Text,但是MenuItem在Header没有设置情况下,会去获取Command的Text
Command使用方向
- 实现ICommandSource的控件使用RoutedCommand,父元素上通过CommandBinding设置Execute和CanExecuted的handler
- 继承ICommand实现自己的Command(通过实例讨论自定义Command)
下面这个例子,CanExecuteChanged和RaiseCanExecuteChanged没搞懂干啥的,回头再解释。但是能用,空间里面通过Binding来关联VM里面的command属性。
参考其他代码
public class ActionCommand : ICommand
{
readonly Action<object> action;
readonly Predicate<object> canExecute;
private EventHandler eventHandler;
public ActionCommand(Action<object> action) : this(action, null) { }
public ActionCommand(Action<object> action, Predicate<object> canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
eventHandler += value;
CommandManager.RequerySuggested += value;
}
remove
{
eventHandler -= value;
CommandManager.RequerySuggested -= value;
}
}
public void RaiseCanExecuteChanged()
{
eventHandler?.Invoke(this, new EventArgs());
}
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void Execute(object parameter)
{
action(parameter);
}
}
如果
public event EventHandler CanExecuteChanged
{
add
{
eventHandler += value;
CommandManager.RequerySuggested += value;
}
remove
{
eventHandler -= value;
CommandManager.RequerySuggested -= value;
}
}
变成
public event EventHandler CanExecuteChanged;
如果是button的command,那么只有点击Button时候才会trigger CanExecute, 假如在界面移动鼠标,则不会被Trigger
CommandManager.RequerySuggested这是为什么??
routedcommand的CanExecuteChange是什么情况
CommandManager.RequerySuggested += value,对于button,其中的value是什么
Command是button的依赖属性,当button初始化后,PropertyChangedCallback方法onCommandChanged
=>HookCommand
=>CanExecuteChangedEventManage.AddHandler,command,buttonbase.OnCanExecuteChanged)
=>CurrentManager.PrivateAddHandler
- 在PrivateAddHandler中,Command和Buttonbase.OnCanExecuteChanged被放入一个Hash表中
- Command的CanExecuteChange赋值为Buttonbase.OnCanExecuteChanged,也就是说CommandManager.RequerySuggested添加了Buttonbase.OnCanExecuteChanged
- Button的OnCanExecuteChanged里面的逻辑是检查command是否CanExecute
当UI的变化,会引起Buttonbase.OnCanExecuteChanged被调用,检查command是否canExecute
CommandManager.RequerySuggested
是为了强引用,防止被垃圾回收什么底,event真正被调用的地方,是那张hash表,弱引用。
internal bool DeliverEvent(ref Listener listener, object sender, EventArgs args, Type managerType)这里是调用入buttonbase的分界方法
[CommonDependencyProperty]
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(ButtonBase),
new FrameworkPropertyMetadata((ICommand)null,
new PropertyChangedCallback(OnCommandChanged)));
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
{
UnhookCommand(oldCommand);
}
if (newCommand != null)
{
HookCommand(newCommand);
}
}
private void HookCommand(ICommand command)
{
CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
UpdateCanExecute();
}
public static void AddHandler(ICommand source, EventHandler<EventArgs> handler)
{
if (source == null)
throw new ArgumentNullException("source");
if (handler == null)
throw new ArgumentNullException("handler");
CurrentManager.PrivateAddHandler(source, handler);
}
private void PrivateAddHandler(ICommand source, EventHandler<EventArgs> handler)
{
// get the list of sinks for this source, creating if necessary
List<HandlerSink> list = (List<HandlerSink>)this[source];
if (list == null)
{
list = new List<HandlerSink>();
this[source] = list;
}
// add a new sink to the list
HandlerSink sink = new HandlerSink(this, source, handler);
list.Add(sink);
// keep the handler alive
AddHandlerToCWT(handler, _cwt);
}
image.png
自定义Command的动机
参考深入浅出的自定义控件
- 自定义控件,实现自己的Command,不再是定制好的button,menuitem,ListBoxItem的固定动作event,trigger的command执行
- 想使用Target,但控件本身不是InputElement的子类。