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="M"/>
</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 中定义了相关的:
- 数据
- 命令的声明
- 命令的实现
而主类非常简单:
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)