[UWP 自定义控件]了解模板化控件(7):支持Command
程序员文章站
2022-06-16 10:22:20
1. 前言 TypeConverter是XAML解释器的幕后功臣,它做了大量工作,从WPF诞生以来,几乎每一次XAML的运作都有它的参与。虽然UWP中TypeConverter已经彻彻底底退居幕后,连自定义TypeConverver都不可以,但了解TypeConverter的原理对理解XAML解析器 ......
以我的经验来说,要让TemplatedControl支持Command的需求不会很多,大部分情况用附加属性解决这个需求会更便利些,譬如UWPCommunityToolkit的。
如果正在从头设计自定义控件并真的需要提供命令支持,可以参考这篇文章。支持Command的步骤比较简单,所以这篇文章比较简短。
要实现Command支持,控件中要执行如下步骤:
- 定义Command和CommandParameter属性。
- 监视Command的CanExecuteChanged事件。
- 在CanExecuteChanged的事件处理函数及CommandParameter的PropertyChangedCallback中,根据Command.CanExecute(CommandParameter)的结果设置控件的IsEnabled属性。
- 在某个事件(Click或者ValueChanged)中执行Command。
MenuItem是实现了Command支持的示例,重载了OnPointerPressed并且在其中执行Command:
public class MenuItem : Control { /// <summary> /// 标识 Command 依赖属性。 /// </summary> public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(MenuItem), new PropertyMetadata(null, OnCommandChanged)); private static void OnCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { MenuItem target = obj as MenuItem; ICommand oldValue = (ICommand)args.OldValue; ICommand newValue = (ICommand)args.NewValue; if (oldValue != newValue) target.OnCommandChanged(oldValue, newValue); } /// <summary> /// 标识 CommandParameter 依赖属性。 /// </summary> public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(MenuItem), new PropertyMetadata(null, OnCommandParameterChanged)); private static void OnCommandParameterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { MenuItem target = obj as MenuItem; object oldValue = (object)args.OldValue; object newValue = (object)args.NewValue; if (oldValue != newValue) target.OnCommandParameterChanged(oldValue, newValue); } public MenuItem() { this.DefaultStyleKey = typeof(MenuItem); } public event RoutedEventHandler Click; /// <summary> /// 获取或设置Command的值 /// </summary> public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } /// <summary> /// 获取或设置CommandParameter的值 /// </summary> public object CommandParameter { get { return (object)GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } protected virtual void OnCommandParameterChanged(object oldValue, object newValue) { UpdateIsEnabled(); } protected virtual void OnCommandChanged(ICommand oldValue, ICommand newValue) { if (oldValue != null) oldValue.CanExecuteChanged -= OnCanExecuteChanged; if (newValue != null) newValue.CanExecuteChanged += OnCanExecuteChanged; UpdateIsEnabled(); } protected virtual void UpdateVisualState(bool useTransitions) { if (IsEnabled) { VisualStateManager.GoToState(this, "Normal", useTransitions); } else { VisualStateManager.GoToState(this, "Disabled", useTransitions); } } protected override void OnPointerPressed(PointerRoutedEventArgs e) { base.OnPointerPressed(e); Click?.Invoke(this, new RoutedEventArgs()); if ((null != Command) && Command.CanExecute(CommandParameter)) { Command.Execute(CommandParameter); } } private void OnCanExecuteChanged(object sender, EventArgs e) { UpdateIsEnabled(); } private void UpdateIsEnabled() { IsEnabled = (null == Command) || Command.CanExecute(CommandParameter); UpdateVisualState(true); } }
以下是使用示例,作用是当TextBox的Text不为空时可以点击MenuItem,并且将Text作为MessageDialog的内容输出:
<StackPanel> <TextBox x:Name="TextElement"/> <local:MenuItem Command="{Binding}" CommandParameter="{Binding ElementName=TextElement,Path=Text}"/> </StackPanel>
public MenuItemSamplePage() { this.InitializeComponent(); var command = new DelegateCommand<object>(Click, CanExecute); this.DataContext = command; } private void Click(object parameter) { MessageDialog dialog = new MessageDialog(parameter.ToString()); dialog.ShowAsync(); } private bool CanExecute(object parameter) { string text = parameter as string; return string.IsNullOrWhiteSpace(text) == false; }
这里用到的DelegateCommand也是UWPCommunityToolkit中的类 :