本主题介绍 Windows 应用程序中的命令。 具体而言,我们将讨论如何使用 XamlUICommand 和 StandardUICommand 类(以及 ICommand 接口)跨各种控件类型共享和管理命令,而不考虑所使用的设备和输入类型。
跨各种控件共享命令,而不考虑设备和输入类型
重要 API
概述
可以通过 UI 交互直接调用命令,例如单击按钮或从上下文菜单中选择项。 还可以通过输入设备间接调用它们,例如键盘加速器、手势、语音识别或自动化/辅助功能工具。 调用后,命令可由控件(编辑控件中的文本导航)、窗口(后退导航)或应用程序(退出)进行处理。
命令可以在应用中的特定上下文中操作,例如删除文本或撤消操作,也可以在无上下文中执行,例如静音音频或调整亮度。
下图显示了共享一些相同命令的两个命令接口( CommandBar 和浮动上下文 CommandBarFlyout)。
命令栏
Microsoft照片库中的上下文菜单
命令交互
由于各种设备、输入类型和 UI 图面可能会影响命令的调用方式,我们建议尽可能多地通过命令图面公开命令。 其中包括轻扫、MenuBar、CommandBar、CommandBarFlyout 和传统上下文菜单的组合。
对于关键命令,请使用特定于输入的加速器。 输入加速器允许用户根据他们所使用的输入设备更快地执行动作。
下面是各种输入类型的一些常见输入加速器:
- 指针 - 鼠标和笔悬停按钮
- 键盘 - 快捷方式(访问键和加速键)
- 触摸 - 轻扫
- 触摸 - 拉取以刷新数据
必须考虑输入类型和用户体验,才能使应用程序的功能可普遍访问。 例如,集合(尤其是用户可编辑的集合)通常包括根据输入设备以完全不同的方式执行的各种特定命令。
下表显示了一些典型的集合命令和公开这些命令的方法。
| Command | 与输入无关 | 鼠标加速器 | 键盘快捷键 | 触摸加速器 |
|---|---|---|---|---|
| 删除项目 | 上下文菜单 | 悬停按钮 | DEL 键 | 轻扫以删除 |
| 标记项 | 上下文菜单 | 悬停按钮 | Ctrl+Shift+G | 轻扫到标志 |
| 刷新数据 | 上下文菜单 | N/A | F5 键 | 下拉以刷新 |
| 收藏项目 | 上下文菜单 | 悬停按钮 | F、Ctrl+S | 轻扫到收藏夹 |
始终提供上下文菜单 建议在传统上下文菜单或 CommandBarFlyout 中包含所有相关上下文命令,因为所有输入类型都支持这两个命令。 例如,如果命令仅在指针悬停事件期间公开,则不能在仅触摸设备上使用它。
Windows 应用程序中的命令
可通过多种方式在 Windows 应用程序中共享和管理命令体验。 可以在代码隐藏中为标准交互(例如 Click)定义事件处理程序(这可能相当低效,具体取决于 UI 的复杂性),可以将标准交互的事件侦听器绑定到共享处理程序,也可以将控件的 Command 属性绑定到描述命令逻辑的 ICommand 实现。
为了高效地跨命令图面提供丰富且全面的用户体验,并且代码重复最少,我们建议使用本主题中所述的命令绑定功能(对于标准事件处理,请参阅单个事件主题)。
若要将控件绑定到共享命令资源,可以自行实现 ICommand 接口,也可以从 XamlUICommand 基类或 StandardUICommand 派生类定义的平台命令之一生成命令。
- ICommand 接口(Windows.UI.Xaml.Input.ICommand 或 System.Windows.Input.ICommand)允许跨应用创建完全自定义的可重用命令。
- XamlUICommand 还提供此功能,但通过公开一组内置命令属性(如命令行为、键盘快捷方式(访问键和快捷键)、图标、标签和说明来简化开发。
- StandardUICommand 通过允许你从一组具有预定义属性的标准平台命令中进行选择来进一步简化作。
重要
在 UWP 应用程序中,命令是 Windows.UI.Xaml.Input.ICommand (C++) 或 System.Windows.Input.ICommand (C#) 接口的实现,具体取决于所选的语言框架。
使用 StandardUICommand 类的命令体验
StandardUICommand 派生自 XamlUICommand (派生自 windows.UI.Xaml.Input.ICommand for C++ 或 System.Windows.Input.ICommand for C#), StandardUICommand 类公开一组具有预定义属性(如图标、键盘加速器和说明)的标准平台命令。
StandardUICommand 提供了一种快速且一致的方法来定义常见命令,例如Save或 Delete。 您只需提供 Execute 和 CanExecute 函数即可。
Example
StandardUICommandSample
| 下载此示例的代码 |
|---|
| UWP 命令示例 (StandardUICommand) |
在此示例中,我们演示如何使用通过 StandardUICommand 类实现的 Delete 项命令增强基本 ListView,同时使用 MenuBar、轻扫控件、悬停按钮和上下文菜单优化各种输入类型的用户体验。
注释
此示例需要 Microsoft.UI.Xaml.Controls NuGet 包,它是 WinUI 2 的一部分。
Xaml:
示例 UI 包含包含五个项目的 ListView 。 Delete StandardUICommand 绑定到 MenuBarItem、 SwipeItem、 AppBarButton 和 ContextFlyout 菜单。
<Page
x:Class="StandardUICommandSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StandardUICommandSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxcontrols="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Style x:Key="HorizontalSwipe"
TargetType="ListViewItem"
BasedOn="{StaticResource ListViewItemRevealStyle}">
<Setter Property="Height" Value="60"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</Page.Resources>
<Grid Loaded="ControlExample_Loaded">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Padding="10"
BorderThickness="0,0,0,1"
BorderBrush="LightBlue"
Background="AliceBlue">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}">
StandardUICommand sample
</TextBlock>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Margin="0,0,0,10">
This sample shows how to use the StandardUICommand class to
share a platform command and consistent user experiences
across various controls.
</TextBlock>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Margin="0,0,0,0">
Specifically, we define a standard delete command and add it
to a variety of command surfaces, all of which share a common
icon, label, keyboard accelerator, and description.
</TextBlock>
</StackPanel>
<muxcontrols:MenuBar Grid.Row="1" Padding="10">
<muxcontrols:MenuBarItem Title="File">
</muxcontrols:MenuBarItem>
<muxcontrols:MenuBarItem Title="Edit">
<MenuFlyoutItem x:Name="DeleteFlyoutItem"/>
</muxcontrols:MenuBarItem>
<muxcontrols:MenuBarItem Title="Help">
</muxcontrols:MenuBarItem>
</muxcontrols:MenuBar>
<ListView x:Name="ListViewRight" Grid.Row="2"
Loaded="ListView_Loaded"
IsItemClickEnabled="True"
SelectionMode="Single"
SelectionChanged="ListView_SelectionChanged"
ItemContainerStyle="{StaticResource HorizontalSwipe}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ListItemData">
<UserControl PointerEntered="ListViewSwipeContainer_PointerEntered"
PointerExited="ListViewSwipeContainer_PointerExited">
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}" />
</MenuFlyout>
</UserControl.ContextFlyout>
<Grid AutomationProperties.Name="{x:Bind Text}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsHidden" />
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="HoverButton.Visibility"
Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SwipeControl x:Name="ListViewSwipeContainer" >
<SwipeControl.RightItems>
<SwipeItems Mode="Execute">
<SwipeItem x:Name="DeleteSwipeItem"
Background="Red"
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}"/>
</SwipeItems>
</SwipeControl.RightItems>
<Grid VerticalAlignment="Center">
<TextBlock Text="{x:Bind Text}"
Margin="10"
FontSize="18"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<AppBarButton x:Name="HoverButton"
IsTabStop="False"
HorizontalAlignment="Right"
Visibility="Collapsed"
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}"/>
</Grid>
</SwipeControl>
</Grid>
</UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
代码隐藏
- 首先,我们定义一个
ListItemData类,该类包含 ListView 中每个 ListViewItem 的文本字符串和 ICommand。
public class ListItemData
{
public String Text { get; set; }
public ICommand Command { get; set; }
}
- 在 MainPage 类中,我们为
ListItemDataItemTemplate 的 DataTemplate 定义对象集合。 然后,使用五个项目的初始集合填充它(文本和关联的 StandardUICommand Delete)。
/// <summary>
/// ListView item collection.
/// </summary>
ObservableCollection<ListItemData> collection =
new ObservableCollection<ListItemData>();
/// <summary>
/// Handler for the layout Grid control load event.
/// </summary>
/// <param name="sender">Source of the control loaded event</param>
/// <param name="e">Event args for the loaded event</param>
private void ControlExample_Loaded(object sender, RoutedEventArgs e)
{
// Create the standard Delete command.
var deleteCommand = new StandardUICommand(StandardUICommandKind.Delete);
deleteCommand.ExecuteRequested += DeleteCommand_ExecuteRequested;
DeleteFlyoutItem.Command = deleteCommand;
for (var i = 0; i < 5; i++)
{
collection.Add(
new ListItemData {
Text = "List item " + i.ToString(),
Command = deleteCommand });
}
}
/// <summary>
/// Handler for the ListView control load event.
/// </summary>
/// <param name="sender">Source of the control loaded event</param>
/// <param name="e">Event args for the loaded event</param>
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
var listView = (ListView)sender;
// Populate the ListView with the item collection.
listView.ItemsSource = collection;
}
- 接下来,我们定义 ICommand ExecuteRequested 处理程序,并在其中实现项删除命令。
/// <summary>
/// Handler for the Delete command.
/// </summary>
/// <param name="sender">Source of the command event</param>
/// <param name="e">Event args for the command event</param>
private void DeleteCommand_ExecuteRequested(
XamlUICommand sender, ExecuteRequestedEventArgs args)
{
// If possible, remove specified item from collection.
if (args.Parameter != null)
{
foreach (var i in collection)
{
if (i.Text == (args.Parameter as string))
{
collection.Remove(i);
return;
}
}
}
if (ListViewRight.SelectedIndex != -1)
{
collection.RemoveAt(ListViewRight.SelectedIndex);
}
}
- 最后,我们定义各种 ListView 事件的处理程序,包括 PointerEntered、 PointerExited 和 SelectionChanged 事件。 指针事件处理程序用于显示或隐藏每个项目的“删除”按钮。
/// <summary>
/// Handler for the ListView selection changed event.
/// </summary>
/// <param name="sender">Source of the selection changed event</param>
/// <param name="e">Event args for the selection changed event</param>
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListViewRight.SelectedIndex != -1)
{
var item = collection[ListViewRight.SelectedIndex];
}
}
/// <summary>
/// Handler for the pointer entered event.
/// Displays the delete item "hover" buttons.
/// </summary>
/// <param name="sender">Source of the pointer entered event</param>
/// <param name="e">Event args for the pointer entered event</param>
private void ListViewSwipeContainer_PointerEntered(
object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType ==
Windows.Devices.Input.PointerDeviceType.Mouse ||
e.Pointer.PointerDeviceType ==
Windows.Devices.Input.PointerDeviceType.Pen)
{
VisualStateManager.GoToState(
sender as Control, "HoverButtonsShown", true);
}
}
/// <summary>
/// Handler for the pointer exited event.
/// Hides the delete item "hover" buttons.
/// </summary>
/// <param name="sender">Source of the pointer exited event</param>
/// <param name="e">Event args for the pointer exited event</param>
private void ListViewSwipeContainer_PointerExited(
object sender, PointerRoutedEventArgs e)
{
VisualStateManager.GoToState(
sender as Control, "HoverButtonsHidden", true);
}
使用 XamlUICommand 类的命令体验
如果需要创建不由 StandardUICommand 类定义的命令,或者希望更好地控制命令外观, 则 XamlUICommand 类派生自 ICommand 接口,添加各种 UI 属性(例如图标、标签、说明和键盘快捷方式)、方法和事件,以便快速定义自定义命令的 UI 和行为。
XamlUICommand 允许通过控件绑定指定 UI,例如图标、标签、说明和键盘快捷方式(访问键和键盘快捷键),而无需设置各个属性。
Example
XamlUICommandSample
| 下载此示例的代码 |
|---|
| UWP 命令示例 (XamlUICommand) |
此示例共享上一 个 StandardUICommand 示例的 Delete 功能,但显示 XamlUICommand 类如何让你使用自己的字体图标、标签、键盘快捷键和说明定义自定义删除命令。 与 StandardUICommand 示例一样,我们使用通过 XamlUICommand 类实现的 Delete 项命令增强基本 ListView,同时使用 MenuBar、轻扫控件、悬停按钮和上下文菜单优化各种输入类型的用户体验。
许多平台控件在封面下使用 XamlUICommand 属性,就像上一部分中的 StandardUICommand 示例一样。
注释
此示例需要 WinUI 2 的 Microsoft.UI.Xaml.Controls NuGet 包。
Xaml:
示例 UI 包含包含五个项目的 ListView 。 自定义 XamlUICommand CustomXamlUICommand 绑定到 MenuBarItem、 SwipeItem、 AppBarButton 和 ContextFlyout 菜单。
<Page
x:Class="XamlUICommand_Sample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XamlUICommand_Sample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxcontrols="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<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>
<Style x:Key="HorizontalSwipe"
TargetType="ListViewItem"
BasedOn="{StaticResource ListViewItemRevealStyle}">
<Setter Property="Height" Value="70"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</Page.Resources>
<Grid Loaded="ControlExample_Loaded" Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Padding="10"
BorderThickness="0,0,0,1"
BorderBrush="LightBlue"
Background="AliceBlue">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}">
XamlUICommand sample
</TextBlock>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Margin="0,0,0,10">
This sample shows how to use the XamlUICommand class to
share a custom command with consistent user experiences
across various controls.
</TextBlock>
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Margin="0,0,0,0">
Specifically, we define a custom delete command and add it
to a variety of command surfaces, all of which share a common
icon, label, keyboard accelerator, and description.
</TextBlock>
</StackPanel>
<muxcontrols:MenuBar Grid.Row="1">
<muxcontrols:MenuBarItem Title="File">
</muxcontrols:MenuBarItem>
<muxcontrols:MenuBarItem Title="Edit">
<MenuFlyoutItem x:Name="DeleteFlyoutItem"
Command="{StaticResource CustomXamlUICommand}"/>
</muxcontrols:MenuBarItem>
<muxcontrols:MenuBarItem Title="Help">
</muxcontrols:MenuBarItem>
</muxcontrols:MenuBar>
<ListView x:Name="ListViewRight" Grid.Row="2"
Loaded="ListView_Loaded"
IsItemClickEnabled="True"
SelectionMode="Single"
SelectionChanged="ListView_SelectionChanged"
ItemContainerStyle="{StaticResource HorizontalSwipe}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ListItemData">
<UserControl PointerEntered="ListViewSwipeContainer_PointerEntered"
PointerExited="ListViewSwipeContainer_PointerExited">
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}" />
</MenuFlyout>
</UserControl.ContextFlyout>
<Grid AutomationProperties.Name="{x:Bind Text}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsHidden" />
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="HoverButton.Visibility"
Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SwipeControl x:Name="ListViewSwipeContainer">
<SwipeControl.RightItems>
<SwipeItems Mode="Execute">
<SwipeItem x:Name="DeleteSwipeItem"
Background="Red"
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}"/>
</SwipeItems>
</SwipeControl.RightItems>
<Grid VerticalAlignment="Center">
<TextBlock Text="{x:Bind Text}"
Margin="10"
FontSize="18"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<AppBarButton x:Name="HoverButton"
IsTabStop="False"
HorizontalAlignment="Right"
Visibility="Collapsed"
Command="{x:Bind Command}"
CommandParameter="{x:Bind Text}"/>
</Grid>
</SwipeControl>
</Grid>
</UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
后台代码
- 首先,我们定义一个
ListItemData类,该类包含 ListView 中每个 ListViewItem 的文本字符串和 ICommand。
public class ListItemData
{
public String Text { get; set; }
public ICommand Command { get; set; }
}
- 在 MainPage 类中,我们为
ListItemDataItemTemplate 的 DataTemplate 定义对象集合。 然后,使用五个项目的初始集合(文本和关联的 XamlUICommand)填充它。
ObservableCollection<ListItemData> collection = new ObservableCollection<ListItemData>();
private void ControlExample_Loaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 5; i++)
{
collection.Add(
new ListItemData { Text = "List item " + i.ToString(), Command = CustomXamlUICommand });
}
}
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
var listView = (ListView)sender;
listView.ItemsSource = collection;
}
- 接下来,定义实现项删除命令的 ICommand ExecuteRequested 处理程序。
private void DeleteCommand_ExecuteRequested(
XamlUICommand sender, ExecuteRequestedEventArgs args)
{
if (args.Parameter != null)
{
foreach (var i in collection)
{
if (i.Text == (args.Parameter as string))
{
collection.Remove(i);
return;
}
}
}
if (ListViewRight.SelectedIndex != -1)
{
collection.RemoveAt(ListViewRight.SelectedIndex);
}
}
- 最后,我们定义各种 ListView 事件的处理程序,包括 PointerEntered、 PointerExited 和 SelectionChanged 事件。 指针事件处理程序用于显示或隐藏每个项目的“删除”按钮。
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListViewRight.SelectedIndex != -1)
{
var item = collection[ListViewRight.SelectedIndex];
}
}
private void ListViewSwipeContainer_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType ==
Windows.Devices.Input.PointerDeviceType.Mouse ||
e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Pen)
{
VisualStateManager.GoToState(sender as Control, "HoverButtonsShown", true);
}
}
private void ListViewSwipeContainer_PointerExited(object sender, PointerRoutedEventArgs e)
{
VisualStateManager.GoToState(sender as Control, "HoverButtonsHidden", true);
}
使用 ICommand 接口的命令体验
标准 UWP 控件(按钮、列表、选择、日历、预测文本)为许多常见命令体验提供了基础。 有关控件类型的完整列表,请参阅 Windows 应用的控件和模式。
支持结构化命令体验的最基本方法是定义 ICommand 接口的实现(适用于 C++ 的 Windows.UI.Xaml.Input.ICommand 或适用于 C# 的 System.Windows.Input.ICommand )。 然后,此 ICommand 实例可以绑定到按钮等控件。
注释
在某些情况下,将方法绑定到 Click 事件和将属性绑定到 IsEnabled 属性可能同样有效。
Example
ICommand 示例
| 下载此示例的代码 |
|---|
| UWP 命令示例 (ICommand) |
在此基本示例中,我们演示了如何使用按钮单击、键盘快捷键和旋转鼠标滚轮调用单个命令。
我们使用两个ListView,一个填充了五个项目,另一个是空的。我们还有两个按钮,一个用于将项目从左侧的 ListView 移动到右侧的 ListView,另一个用于将项目从右侧移动到左侧。 每个按钮都绑定到相应的命令(分别 ViewModel.MoveRightCommand 和 ViewModel.MoveLeftCommand),并根据关联的 ListView 中的项数自动启用和禁用。
以下 XAML 代码定义示例的 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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<vm:OpacityConverter x:Key="opaque" />
</Page.Resources>
<Grid Name="ItemGrid"
Background="AliceBlue"
PointerWheelChanged="Page_PointerWheelChanged">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" VerticalAlignment="Center"
x:Name="CommandListView"
ItemsSource="{x:Bind Path=ViewModel.ListItemLeft}"
SelectionMode="None" IsItemClickEnabled="False"
HorizontalAlignment="Right">
<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>
</ListView>
<Grid Grid.Column="1" Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="40" Glyph=""
Opacity="{x:Bind Path=ViewModel.ListItemLeft.Count,
Mode=OneWay, Converter={StaticResource opaque}}"/>
<Button Name="MoveItemRightButton"
Margin="0,10,0,10" Width="120" HorizontalAlignment="Center"
Command="{x:Bind Path=ViewModel.MoveRightCommand}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator
Modifiers="Control"
Key="Add" />
</Button.KeyboardAccelerators>
<StackPanel>
<SymbolIcon Symbol="Next"/>
<TextBlock>Move item right</TextBlock>
</StackPanel>
</Button>
<Button Name="MoveItemLeftButton"
Margin="0,10,0,10" Width="120" HorizontalAlignment="Center"
Command="{x:Bind Path=ViewModel.MoveLeftCommand}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator
Modifiers="Control"
Key="Subtract" />
</Button.KeyboardAccelerators>
<StackPanel>
<SymbolIcon Symbol="Previous"/>
<TextBlock>Move item left</TextBlock>
</StackPanel>
</Button>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="40" Glyph=""
Opacity="{x:Bind Path=ViewModel.ListItemRight.Count,
Mode=OneWay, Converter={StaticResource opaque}}"/>
</StackPanel>
</Grid>
<ListView Grid.Column="2"
x:Name="CommandListViewRight"
VerticalAlignment="Center"
IsItemClickEnabled="False"
SelectionMode="None"
ItemsSource="{x:Bind Path=ViewModel.ListItemRight}"
HorizontalAlignment="Left">
<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>
</ListView>
</Grid>
</Page>
下面是上述 UI 的后台代码。
在代码隐藏中,我们连接到包含命令代码的视图模型。 此外,我们定义了一个处理程序,用于处理鼠标滚轮输入,并将其与我们的命令代码关联起来。
using Windows.UI.Xaml;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Controls;
using UICommand1.ViewModel;
using Windows.System;
using Windows.UI.Core;
namespace UICommand1.View
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
// Reference to our view model.
public UICommand1ViewModel ViewModel { get; set; }
// Initialize our view and view model.
public MainPage()
{
this.InitializeComponent();
ViewModel = new UICommand1ViewModel();
}
/// <summary>
/// Handle mouse wheel input and assign our
/// commands to appropriate direction of rotation.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Page_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var props = e.GetCurrentPoint(sender as UIElement).Properties;
// Require CTRL key and accept only vertical mouse wheel movement
// to eliminate accidental wheel input.
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;
}
}
}
}
}
下面是视图模型中的代码
我们的视图模型用于定义应用中这两个命令的执行详细信息,填充一个 ListView,并提供不透明度值转换器,用于根据每个 ListView 的项计数隐藏或显示一些附加 UI。
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace UICommand1.ViewModel
{
/// <summary>
/// UI properties for our list items.
/// </summary>
public class ListItemData
{
/// <summary>
/// Gets and sets the list item content string.
/// </summary>
public string ListItemText { get; set; }
/// <summary>
/// Gets and sets the list item icon.
/// </summary>
public Symbol ListItemIcon { get; set; }
}
/// <summary>
/// View Model that sets up a command to handle invoking the move item buttons.
/// </summary>
public class UICommand1ViewModel
{
/// <summary>
/// The command to invoke when the Move item left button is pressed.
/// </summary>
public RelayCommand MoveLeftCommand { get; private set; }
/// <summary>
/// The command to invoke when the Move item right button is pressed.
/// </summary>
public RelayCommand MoveRightCommand { get; private set; }
// Item collections
public ObservableCollection<ListItemData> ListItemLeft { get; } =
new ObservableCollection<ListItemData>();
public ObservableCollection<ListItemData> ListItemRight { get; } =
new ObservableCollection<ListItemData>();
public ListItemData listItem;
/// <summary>
/// Sets up a command to handle invoking the move item buttons.
/// </summary>
public UICommand1ViewModel()
{
MoveLeftCommand =
new RelayCommand(new Action(MoveLeft), CanExecuteMoveLeftCommand);
MoveRightCommand =
new RelayCommand(new Action(MoveRight), CanExecuteMoveRightCommand);
LoadItems();
}
/// <summary>
/// Populate our list of items.
/// </summary>
public void LoadItems()
{
for (var x = 0; x <= 4; x++)
{
listItem = new ListItemData();
listItem.ListItemText = "Item " + (ListItemLeft.Count + 1).ToString();
listItem.ListItemIcon = Symbol.Emoji;
ListItemLeft.Add(listItem);
}
}
/// <summary>
/// Move left command valid when items present in the list on right.
/// </summary>
/// <returns>True, if count is greater than 0.</returns>
private bool CanExecuteMoveLeftCommand()
{
return ListItemRight.Count > 0;
}
/// <summary>
/// Move right command valid when items present in the list on left.
/// </summary>
/// <returns>True, if count is greater than 0.</returns>
private bool CanExecuteMoveRightCommand()
{
return ListItemLeft.Count > 0;
}
/// <summary>
/// The command implementation to execute when the Move item right button is pressed.
/// </summary>
public void MoveRight()
{
if (ListItemLeft.Count > 0)
{
listItem = new ListItemData();
ListItemRight.Add(listItem);
listItem.ListItemText = "Item " + ListItemRight.Count.ToString();
listItem.ListItemIcon = Symbol.Emoji;
ListItemLeft.RemoveAt(ListItemLeft.Count - 1);
MoveRightCommand.RaiseCanExecuteChanged();
MoveLeftCommand.RaiseCanExecuteChanged();
}
}
/// <summary>
/// The command implementation to execute when the Move item left button is pressed.
/// </summary>
public void MoveLeft()
{
if (ListItemRight.Count > 0)
{
listItem = new ListItemData();
ListItemLeft.Add(listItem);
listItem.ListItemText = "Item " + ListItemLeft.Count.ToString();
listItem.ListItemIcon = Symbol.Emoji;
ListItemRight.RemoveAt(ListItemRight.Count - 1);
MoveRightCommand.RaiseCanExecuteChanged();
MoveLeftCommand.RaiseCanExecuteChanged();
}
}
/// <summary>
/// Views subscribe to this event to get notified of property updates.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify subscribers of updates to the named property
/// </summary>
/// <param name="propertyName">The full, case-sensitive, name of a property.</param>
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
handler(this, args);
}
}
}
/// <summary>
/// Convert a collection count to an opacity value of 0.0 or 1.0.
/// </summary>
public class OpacityConverter : IValueConverter
{
/// <summary>
/// Converts a collection count to an opacity value of 0.0 or 1.0.
/// </summary>
/// <param name="value">The count passed in</param>
/// <param name="targetType">Ignored.</param>
/// <param name="parameter">Ignored</param>
/// <param name="language">Ignored</param>
/// <returns>1.0 if count > 0, otherwise returns 0.0</returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
return ((int)value > 0 ? 1.0 : 0.0);
}
/// <summary>
/// Not used, converter is not intended for two-way binding.
/// </summary>
/// <param name="value">Ignored</param>
/// <param name="targetType">Ignored</param>
/// <param name="parameter">Ignored</param>
/// <param name="language">Ignored</param>
/// <returns></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
最后,下面是 ICommand 接口的实现
在这里,我们定义一个命令,该命令实现 ICommand 接口,并只是将其功能中继到其他对象。
using System;
using System.Windows.Input;
namespace UICommand1
{
/// <summary>
/// A command whose sole purpose is to relay its functionality
/// to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require
/// data to be passed, this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require
/// data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
_execute();
}
/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
概要
通用 Windows 平台提供了一个可靠且灵活的命令系统,可用于生成跨控件类型、设备和输入类型共享和管理命令的应用。
为 Windows 应用生成命令时,请使用以下方法:
- 侦听和处理 XAML/后台代码中的事件
- 绑定到事件处理方法,如 Click
- 定义自己的 ICommand 实现
- 为预定义的属性集创建具有自己的值的 XamlUICommand 对象
- 使用一组预定义的平台属性和值创建 StandardUICommand 对象
后续步骤
有关演示 XamlUICommand 和 StandardUICommand 实现的完整示例,请参阅 WinUI 2 画廊。