Command
ICommand:
bool CanExcecute: 表示命令是否可以执行
Execute(object):执行命令,并输入object参数
CanExecuteChanged:
ICommandSource:
Command, CommandParameter, CommandTarget
CommandBinding
定义和关联Command的具体实现
<Window.CommandBindings>
<CommandBinding Command="Application.Open"
Executed="OpenCmdExecuted"
CanExecute="OpenCmdCanExecute" />
</Window.CommandBindings>
Routed Command
当调用CanExeucte方法时,分别会产生CanExecute和PreviewCanExecute
当调用Execute方法时,会产生Executed和PreviewExecuted
Command Repository
WPF中内置的命令包括:
ApplicationCommands, NavigationCommands, MediaCommands, EditingCommands, ComponentCommands
public static class ApplicationCommands{
public static RoutedUICommand Copy{get;}
}
通过CommandBindings来关联Command和具体的执行操作后,子控件会自动查找本身和父控件的CommandBindings绑定信息,从而使用同样的执行逻辑。
但是XAML中的CommandBindings关联的Executed都是事件,直接使用的话,逻辑会放在View中,而不是ViewModel中。
每个控件都继承了CommandBindings属性,比较适合在自定义控件中使用。
<Window>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" CanExecute="HelpCanExecute"
Executed="HelpExecuted" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="Help" Key="H" Modifiers="Ctrl"/>
<MouseBinding Command="Help" MouseAction="LeftDoubleClick"/>
</Window.InputBindings>
<StackPanel>
<Button Command="ApplicationCommands.New" Content="帮助"/>
</StackPanel>
</Window>
private void HelpCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute=true;
e.Handle=true;
}
自定义通用Command,并在控件范围内绑定Command:
public static class ControlCommands
{
public static RoutedCommand Prev { get; }
= new(nameof(Prev), typeof(ControlCommands));
}
在控件范围内,建立Command和Action的绑定
public class NumericUpDown : Control
{
private const string ElementTextBox = "PART_TextBox";
private TextBox _textBox;
public NumericUpDown()
{
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, (s, e) =>
{
if (IsReadOnly) return;
SetCurrentValue(ValueProperty, Value + Increment);
}));
}
}
控件的ControlTemplate中将Button的Command和自定义的Command绑定,进而Button的Command作为了一个入口来触发,同时ControlTemplate中的可视化树中的控件会依次查找父控件的CommandBinding
<ControlTemplate x:Key="NumericUpDownExtendLeftTemplate" TargetType="hc:NumericUpDown">
<Button x:Name="UpButton"
Command="interactivity:ControlCommands.Prev"/>
</ControlTemplate>
Command Parameter:
Parameter的值是支持绑定的,也可以是普通的值,如果是普通的常量值,则CommandParameter的类型会是string
<!--如果只显示写了Path,没有写数据来源,则Binding会默认从DataContext中查找对应的属性-->
<TextBlock Text="{Binding SkyColor}"/>
<Button Content="OpenWithoutParameter"
Command="{Binding OpenCommand}"/>
<!--CommandParameter传入的参数是常量,且Type是string ·-->
<Button Content="OpenWithStringParameter"
Command="{Binding OpenWithParameterCommand}"
CommandParameter="1"/>
<!--虽然Command中有参数,但是也可以不设置CommandParameter,此时参数是null -->
<Button Content="OpenWithStringParameter"
Command="{Binding OpenWithParameterCommand}"/>
<!--使用Binding来给CommandParameter赋值,则CommandParameter的类型取决于绑定值的类型,不再局限于string -->
<Button Content="OpenWithBindingParameter"
Command="{Binding OpenWithBindingParameterCommand}"
CommandParameter="{Binding Source={x:Static helpers:ParameterDefinitions.MaxCapacity}}"/>
InputBindings
可以将鼠标或者键盘的事件和Command关联起来。
<Label Style="{StaticResource FS_Label}" HorizontalContentAlignment="Left" FontSize="14" Content="{Binding ItemName}" Width="{Binding Path=ActualWidth, ElementName=deviceStatusListBox}" Background="Transparent">
<Label.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.MenuItemDoubleClickCommand, UpdateSourceTrigger=PropertyChanged, ElementName=deviceStatusListBox}" CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"/>
</Label.InputBindings>
</Label>
Event to command:
需要使用到Prism的框架。
<Window ...
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/">
<ListBox Grid.Row="1" Margin="5" ItemsSource="{Binding Items}" SelectionMode="Single">
<i:Interaction.Triggers>
<!-- This event trigger will execute the action when the corresponding event is raised by the ListBox. -->
<i:EventTrigger EventName="SelectionChanged">
<!-- This action will invoke the selected command in the view model and pass the parameters of the event to it. -->
<prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Window>
RoutedEvent
路由策略(Routing Strategies)
RoutedEvent
有三种路由策略:
- 冒泡(Bubbling):事件从事件源开始,逐级向上冒泡到根元素。大多数WPF事件使用冒泡策略
- 隧道(Tunneling):事件从根元素开始,逐级向下传递到事件源。隧道事件通常以“Preview”为前缀,例如
PreviewMouseDown
- 直接(Direct):事件仅在事件源上触发,不会传播
。
public delegate void DragStartedEventHandler(object sender, DragStartedEventArgs e);
/// <summary>
/// Event fires when user press mouse's left button on the thumb.
/// </summary>
public static readonly RoutedEvent DragStartedEvent =
EventManager.RegisterRoutedEvent("DragStarted", RoutingStrategy.Bubble,
typeof(DragStartedEventHandler),
typeof(Thumb));
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (!IsDragging)
{
e.Handled = true;
Focus();
CaptureMouse();
SetValue(IsDraggingPropertyKey, true);
_originThumbPoint = e.GetPosition(this);
_previousScreenCoordPosition = _originScreenCoordPosition = SafeSecurityHelper.ClientToScreen(this,_originThumbPoint);
bool exceptionThrown = true;
try
{
RaiseEvent(new DragStartedEventArgs(_originThumbPoint.X, _originThumbPoint.Y));
exceptionThrown = false;
}
finally
{
if (exceptionThrown)
{
CancelDrag();
}
}
}
else
{
// This is weird, Thumb shouldn't get MouseLeftButtonDown event while dragging.
// This may be the case that something ate MouseLeftButtonUp event, so Thumb never had a chance to
// reset IsDragging property
Debug.Assert(false,"Got MouseLeftButtonDown event while dragging!");
}
base.OnMouseLeftButtonDown(e);
}
EventManager.RegisterClassHandler(typeof(GridSplitter), Thumb.DragStartedEvent,
new DragStartedEventHandler(GridSplitter.OnDragStarted));
private static void OnDragStarted(object sender, DragStartedEventArgs e)
{
GridSplitter splitter = sender as GridSplitter;
splitter.OnDragStarted(e);
}