WPF 命令(Command)使用探究
本文参考微软文档,但是他写得太乱了,不便于模仿学习。重新整理了一下。
创建标准 UI 命令
标准命令(StandardUICommand,如删除、编辑等),操作系统会自动帮我们完成翻译、图标设置等。使用方法:
1 <muxcontrols:MenuBarItem Title="Edit">
2 <MenuFlyoutItem x:Name="DeleteFlyoutItem"/>
3 </muxcontrols:MenuBarItem>
1 // Create the standard Delete command.
2 var deleteCommand = new StandardUICommand(StandardUICommandKind.Delete);
3 deleteCommand.ExecuteRequested += DeleteCommand_ExecuteRequested;
4
5 DeleteFlyoutItem.Command = deleteCommand;
其中 DeleteCommand_ExecuteRequested 是具体的执行逻辑,签名:
1 private void DeleteCommand_ExecuteRequested(
2 XamlUICommand sender, ExecuteRequestedEventArgs args)
需要我们自己实现。args.Parameter
中是执行的参数。
创建自定义 UI 命令
这种方法通过 XAML 定义命令,包括名称、图标,可以顺带设置快捷键:
1 <XamlUICommand x:Name="CustomXamlUICommand" ExecuteRequested="DeleteCommand_ExecuteRequested" Description="Custom XamlUICommand" Label="Custom XamlUICommand">
2 <XamlUICommand.IconSource>
3 <FontIconSource FontFamily="Wingdings" Glyph="M"/>
4 </XamlUICommand.IconSource>
5 <XamlUICommand.KeyboardAccelerators>
6 <KeyboardAccelerator Key="D" Modifiers="Control"/>
7 </XamlUICommand.KeyboardAccelerators>
8 </XamlUICommand>
注意此处绑定了 DeleteCommand_ExecuteRequested
事件,需要在逻辑代码进行实现。
批量操作:
1 <muxcontrols:MenuBarItem Title="Edit">
2 <MenuFlyoutItem x:Name="DeleteFlyoutItem" Command="{StaticResource CustomXamlUICommand}"/>
3 </muxcontrols:MenuBarItem>
单独操作:
1 <SwipeItem x:Name="DeleteSwipeItem"
2 Background="Red"
3 Command="{x:Bind Command}"
4 CommandParameter="{x:Bind Text}"/>
然后在添加列表项的时候绑定 Command:
1 collection.Add(new ListItemData { Text = "List item " + i.ToString(), Command = CustomXamlUICommand });
当然,这个列表项的模型需要我们定义:
1 public class ListItemData
2 {
3 public String Text { get; set; }
4 public ICommand Command { get; set; }
5 }
通过实现 ICommand 接口创建命令
这个例子,推荐在你的项目里参考使用。它更规范地实现了 ViewModel。并且,在这里例子中会看到著名的 RelayCommand
类。
我们先看看 VM:
1
2namespace UICommand1.ViewModel
3{
4 public class ListItemData
5 {
6 public string ListItemText { get; set; }
7 public Symbol ListItemIcon { get; set; }
8 }
9
10 public class UICommand1ViewModel
11 {
12 public RelayCommand MoveLeftCommand { get; private set; }
13 public RelayCommand MoveRightCommand { get; private set; }
14
15 public ObservableCollection<ListItemData> ListItemLeft { get; } = new ObservableCollection<ListItemData>();
16 public ObservableCollection<ListItemData> ListItemRight { get; } = new ObservableCollection<ListItemData>();
17
18 public ListItemData listItem;
19
20 public UICommand1ViewModel()
21 {
22 MoveLeftCommand = new RelayCommand(new Action(MoveLeft), CanExecuteMoveLeftCommand);
23 MoveRightCommand = new RelayCommand(new Action(MoveRight), CanExecuteMoveRightCommand);
24
25 LoadItems();
26 }
27
28 public void LoadItems()
29
30 private bool CanExecuteMoveLeftCommand()
31 private bool CanExecuteMoveRightCommand()
32 public void MoveRight()
33 public void MoveLeft()
34 public event PropertyChangedEventHandler PropertyChanged;
35 protected void NotifyPropertyChanged(string propertyName)
36 {
37 PropertyChangedEventHandler handler = this.PropertyChanged;
38 if (handler != null)
39 {
40 PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
41 handler(this, args);
42 }
43 }
44 }
45
46 public class OpacityConverter : IValueConverter
47}
可见在 UICommand1ViewModel 中定义了相关的:
-
数据
-
命令的声明
-
命令的实现
而主类非常简单:
1public sealed partial class MainPage : Page
2 {
3 public UICommand1ViewModel ViewModel { get; set; }
4
5 public MainPage()
6 {
7 this.InitializeComponent();
8 ViewModel = new UICommand1ViewModel();
9 }
10
11 private void Page_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
12 {
13 var props = e.GetCurrentPoint(sender as UIElement).Properties;
14
15 if ((Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) !=
16 CoreVirtualKeyStates.None) && !props.IsHorizontalMouseWheel)
17 {
18 bool delta = props.MouseWheelDelta < 0 ? true : false;
19
20 switch (delta)
21 {
22 case true:
23 ViewModel.MoveRight();
24 break;
25 case false:
26 ViewModel.MoveLeft();
27 break;
28 default:
29 break;
30 }
31 }
32 }
33 }
并且我们注意到 UICommand1ViewModel 下实际上有两个命令,可以学习这种封装方式。
从 UI 是如何触发命令的呢
1<Page
2 x:Class="UICommand1.View.MainPage"
3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5 xmlns:vm="using:UICommand1.ViewModel"
此处进行了 vm 绑定,这是为了便于设计 ListView 的模板:
1 <ListView.ItemTemplate>
2 <DataTemplate x:DataType="vm:ListItemData">
3 <Grid VerticalAlignment="Center">
4 <AppBarButton Label="{x:Bind ListItemText}">
5 <AppBarButton.Icon>
6 <SymbolIcon Symbol="{x:Bind ListItemIcon}"/>
7 </AppBarButton.Icon>
8 </AppBarButton>
9 </Grid>
10 </DataTemplate>
11 </ListView.ItemTemplate>
另外在 MainPage 类中,已经创建了 ViewModel 的实例,因此可以通过这个实例引发命令:
1 <Button Name="MoveItemLeftButton"
2 Margin="0,10,0,10" Width="120" HorizontalAlignment="Center"
3 Command="{x:Bind Path=ViewModel.MoveLeftCommand}">
我们自己的例子
让我们用最近简单的代码实现一个 Command:
UI:
<AppBarButton Icon="Upload" Label="Upload" Command="{x:Bind Path=ViewModel.UploadCommand}" />
Logic:
1 public MainPageViewModel ViewModel { get; set; }
2 public MainPage()
3 {
4 // ...
5 ViewModel = new MainPageViewModel();
6 }
VM:
1 public class MainPageViewModel
2 {
3 public RelayCommand UploadCommand {get;private set;}
4 public MainPageViewModel()
5 {
6 UploadCommand = new RelayCommand(Upload);
7 }
8 public async void Upload()
9 {
10 var messageDialog = new MessageDialog("Hi!");
11 await messageDialog.ShowAsync();
12 }
13 }
题外话
尴尬的是命令模式确实比事件 Handler 费精力(对于简单项目)。所以连官方自己都用古法写代码:PowerToys/MainWindow.xaml.cs at master · microsoft/PowerToys (github.com)