WPF Command Sample

本文参考微软文档,但是他写得太乱了,不便于模仿学习。重新整理了一下。

创建标准 UI 命令

标准命令(StandardUICommand,如删除、编辑等),操作系统会自动帮我们完成翻译、图标设置等。使用方法:

            <muxcontrols:MenuBarItem Title="Edit">
                <MenuFlyoutItem x:Name="DeleteFlyoutItem"/>
            </muxcontrols:MenuBarItem>
            // Create the standard Delete command.
            var deleteCommand = new StandardUICommand(StandardUICommandKind.Delete);
            deleteCommand.ExecuteRequested += DeleteCommand_ExecuteRequested;

            DeleteFlyoutItem.Command = deleteCommand;

其中 DeleteCommand_ExecuteRequested 是具体的执行逻辑,签名:

        private void DeleteCommand_ExecuteRequested(
            XamlUICommand sender, ExecuteRequestedEventArgs args)

需要我们自己实现。args.Parameter 中是执行的参数。

创建自定义 UI 命令

这种方法通过 XAML 定义命令,包括名称、图标,可以顺带设置快捷键:

        <XamlUICommand x:Name="CustomXamlUICommand" ExecuteRequested="DeleteCommand_ExecuteRequested" Description="Custom XamlUICommand" Label="Custom XamlUICommand">
            <XamlUICommand.IconSource>
                <FontIconSource FontFamily="Wingdings" Glyph="&#x4D;"/>
            </XamlUICommand.IconSource>
            <XamlUICommand.KeyboardAccelerators>
                <KeyboardAccelerator Key="D" Modifiers="Control"/>
            </XamlUICommand.KeyboardAccelerators>
        </XamlUICommand>

注意此处绑定了 DeleteCommand_ExecuteRequested 事件,需要在逻辑代码进行实现。

批量操作:

            <muxcontrols:MenuBarItem Title="Edit">
                <MenuFlyoutItem x:Name="DeleteFlyoutItem" Command="{StaticResource CustomXamlUICommand}"/>
            </muxcontrols:MenuBarItem>

单独操作:

                                        <SwipeItem x:Name="DeleteSwipeItem"
                                                   Background="Red" 
                                                   Command="{x:Bind Command}" 
                                                   CommandParameter="{x:Bind Text}"/>

然后在添加列表项的时候绑定 Command:

                collection.Add(new ListItemData { Text = "List item " + i.ToString(), Command = CustomXamlUICommand });

当然,这个列表项的模型需要我们定义:

    public class ListItemData
    {
        public String Text { get; set; }
        public ICommand Command { get; set; }
    }

通过实现 ICommand 接口创建命令

这个例子,推荐在你的项目里参考使用。它更规范地实现了 ViewModel。并且,在这里例子中会看到著名的 RelayCommand 类。

让我康康

我们先看看 VM:


namespace UICommand1.ViewModel
{
    public class ListItemData
    {
        public string ListItemText { get; set; }
        public Symbol ListItemIcon { get; set; }
    }

    public class UICommand1ViewModel
    {
        public RelayCommand MoveLeftCommand { get; private set; }
        public RelayCommand MoveRightCommand { get; private set; }

        public ObservableCollection<ListItemData> ListItemLeft { get; } = new ObservableCollection<ListItemData>();
        public ObservableCollection<ListItemData> ListItemRight { get; } = new ObservableCollection<ListItemData>();

        public ListItemData listItem;
        
        public UICommand1ViewModel()
        {
            MoveLeftCommand = new RelayCommand(new Action(MoveLeft), CanExecuteMoveLeftCommand);
            MoveRightCommand = new RelayCommand(new Action(MoveRight), CanExecuteMoveRightCommand);

            LoadItems();
        }

        public void LoadItems()

        private bool CanExecuteMoveLeftCommand()
        private bool CanExecuteMoveRightCommand()
        public void MoveRight()
        public void MoveLeft()
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
                handler(this, args);
            }
        }
    }
    
    public class OpacityConverter : IValueConverter
}

可见在 UICommand1ViewModel 中定义了相关的:

  1. 数据
  2. 命令的声明
  3. 命令的实现

而主类非常简单:

public sealed partial class MainPage : Page
    {
        public UICommand1ViewModel ViewModel { get; set; }

        public MainPage()
        {
            this.InitializeComponent();
            ViewModel = new UICommand1ViewModel();
        }
    
        private void Page_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
        {
            var props = e.GetCurrentPoint(sender as UIElement).Properties;

            if ((Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) != 
                CoreVirtualKeyStates.None) && !props.IsHorizontalMouseWheel)
            {
                bool delta = props.MouseWheelDelta < 0 ? true : false;

                switch (delta)
                {
                    case true:
                        ViewModel.MoveRight();
                        break;
                    case false:
                        ViewModel.MoveLeft();
                        break;
                    default:
                        break;
                }
            }
        }
    }

并且我们注意到 UICommand1ViewModel 下实际上有两个命令,可以学习这种封装方式。

从 UI 是如何触发命令的呢

<Page
    x:Class="UICommand1.View.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="using:UICommand1.ViewModel"

此处进行了 vm 绑定,这是为了便于设计 ListView 的模板:

            <ListView.ItemTemplate>
                <DataTemplate x:DataType="vm:ListItemData">
                    <Grid VerticalAlignment="Center">
                        <AppBarButton Label="{x:Bind ListItemText}">
                            <AppBarButton.Icon>
                                <SymbolIcon Symbol="{x:Bind ListItemIcon}"/>
                            </AppBarButton.Icon>
                        </AppBarButton>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>

另外在 MainPage 类中,已经创建了 ViewModel 的实例,因此可以通过这个实例引发命令:

                <Button Name="MoveItemLeftButton" 
                            Margin="0,10,0,10" Width="120" HorizontalAlignment="Center"
                            Command="{x:Bind Path=ViewModel.MoveLeftCommand}">

我们自己的例子

让我们用最近简单的代码实现一个 Command:

UI:

<AppBarButton Icon="Upload" Label="Upload"  Command="{x:Bind Path=ViewModel.UploadCommand}"  /> 

Logic:

        public MainPageViewModel ViewModel { get; set; }
        public MainPage()
        {
            // ...
            ViewModel = new MainPageViewModel();            
        }

VM:

    public class MainPageViewModel
    {
        public RelayCommand UploadCommand {get;private set;}
        public MainPageViewModel()
        {
            UploadCommand = new RelayCommand(Upload);
        }
        public async void Upload()
        {
            var messageDialog = new MessageDialog("Hi!");
            await messageDialog.ShowAsync();
        }
    }

题外话

尴尬的是命令模式确实比事件 Handler 费精力(对于简单项目)。所以连官方自己都用古法写代码:PowerToys/MainWindow.xaml.cs at master・microsoft/PowerToys (github.com)