- WPF命令模型
ICommand接口
WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理,它包含了两个方法和一个事件:public interface ICommand
{
void Execute(object parameter); //定义在调用此命令时调用的方法。
bool CanExecute(object parameter); //此方法返回命令的状态,如果命令可用则返回true,否则返回false.
event EventHandler CanExecuteChanged; //当命令状态改变时,引发该事件。
}RoutedCommand类
当创建自己的命令时,不会直接实现ICommand接口,而是使用System.Windows.Input.RoutedCommand类。它是WPF中唯一实例了ICommand接口的类,它为事件冒泡和隧道添加了一些额外的基础结构。为了支持路由事件,RoutedCommand类私有地实现了ICommand接口,并且添加了ICommand接口方法的一些不同的版本,最明显的变化是,Execute()和CanExecute()方法使用了一个额外参数。代码示例如下:public void Execute(object parameter, IInputElement target)
{
}
public bool CanExecute(object parameter, IInputElement target)
{
}参数target是开始处理事件的元素,事件从target元素开始,然后冒泡至高层的容器,直到应用程序为了执行合适的任务而处理了事件。
RoutedCommand类还引入了三个属性:Name(命令名称)、OwnerType(包含命令的类)及InputGestures集合(可以被用于触发命令的按键或鼠标操作)。RoutedUICommand类
RoutedUICommand类只增加了一个属性 Text,它是命令显示的文本。在程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand类的实例,RoutedUICommand类继承自RoutedCommand类。而WPF提供的所有预先构建好的命令都是RoutedUICommand对象。RoutedUICommand类用于具有文本的命令,这些文本显示在用户界面中的某些地方(如菜单项文本,工具栏按钮的工具提示)。命令库
因为每个应用程序可能都有大量的命令,且对于许多不同的应用程序,很多命令是通用的,为了减少创建这些命令所需要的工作,WPF提供了一个基本命令库,这些命令通过以下5个专门的静态类的静态属性提供:
许多命令对象都是有一个额外的特征:默认输入绑定,例如,ApplicationCommands.Open命令被映射到Ctrl+O组合键,只要将命令绑定到一个命令源,并为窗口添加该命令源,这个组合键就会被激活,即使没有在用户界面的任何地方显示该命令也同样如此。
-
命令源
命令源是一个实现了ICommandSource接口的控件,它定义了三个属性:
例如,下面的按钮使用Command属性连接到ApplicationCommands.New命令:
<Button Command="New">New</Button>
此时,会看到按钮是被禁用的状态,这是因为按钮查询到命令还没有进行操作绑定,命令的状态为不可用,所以按钮也设置为不可用。
-
为命令进行操作绑定
下在的代码片段为New命令创建绑定,可将这些代码添加到窗口的构造函数中:CommandBinding binding = new CommandBinding(ApplicationCommands.New);
binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
this.CommandBindings.Add(binding); void binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("New command triggered by " + e.Source.ToString());
}尽管习惯上为窗口创建所有绑定,但CommandBindings属性实际上是在UIElement基类中定义的,所以任何元素都支持该属性,但为了得到最大的灵活性,命令绑定通常被添加到*窗口,如果希望在多个窗口中使用相同的命令,就需要在这些窗口中分别创建命令绑定。
以上的命令绑定是使用代码生成的,但,如果希望精简代码隐藏文件,使用XAML以声明方式关联命令也很容易,如下所示:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="binding_Executed"></CommandBinding>
</Window.CommandBindings>
<Button Command="ApplicationCommands.New">New</Button> -
使用多命令源,如下是为命令New创建了一个MenuItem的命令源:
<Menu>
<MenuItem Header="File">
<MenuItem Command="New"></MenuItem>
</MenuItem>
</Menu>注意,没有为New命令的MenuItem对象设置Header属性,这是因为MenuItem类足够智能,如果没有设置Header属性,它将从命令中提取文本(Button不具有此特性)。虽然该特性带来的便利看起来很小,但是如果计划使用不同的语言本地化应用程序,这一特性就很重要了。MunuItem类还有另一个功能,它能够自动提取Command.InputBindings集合中的第一个快捷键,对于以上的New命令,在菜单旁边会显示快捷键:Ctrl+N。
-
使Button这种不能自动提取命令文本的控件来提取命令文本,有两种技术来重用命令文本,一种是直接从静态的命令对象中提取文本,XAML可以使用Static标记扩展完成这一任务,该方法的问题在于它只是调用命令对象的ToString()方法,因此,得到的是命令的名称,而不是命令的文本。最好的方法是使用数据绑定表达式,以下第二条代码示例绑定表达式绑定到当前元素,获取正在使用的Command对象,并且提取其Text属性:
<Button Command="ApplicationCommands.New" Content="{x:Static ApplicationCommands.New}"></Button>
<Button Command="ApplicationCommands.New" Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Command.Text}"></Button> -
直接调用命令
不是只有实现了ICommandSource接口的类才能触发命令的执行,也可以使用Execute()方法直接调用来自任何事件处理程序的方法:ApplicationCommands.New.Execute(null,targetElement);
targetElement是WPF开始查找命令绑定的地方。可以使用包含窗口(具有命令绑定)或嵌套的元素(实际引发事件的元素)。也可以在关联的CommandBinding对象中调用Execute()方法,对于这种情况,不需要提供目标元素,因为会自动公开正在使用的CommandBindings集合的元素设置为目标元素:this.CommandBindings[0].Command.Execute(null);
-
禁用命令
例如有一个由菜单、工具栏及一个大的文本框构成的文本编辑器的应用程序,该应用程序可以打开文件,创建新的文档以及保存所进行的操作。在应用程序中,只有文本框中的内容发生了变化才启用Save命令,我们可以在代码中使用一个Boolean变量isUpdate来跟踪是否发生了变化。当文本发生了变化时设置标志。private bool isUpdate = false;
private void txt_TextChanged(object sender, TextChangedEventArgs e)
{
isUpdate = true;
}现在需要从窗口向命令绑定传递信息,从而使连接的控件可以根据需要进行更新,技巧是处理命令绑定的CanExecute事件,代码如下:
CommandBinding binding = new CommandBinding(ApplicationCommands.Save);
binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
binding.CanExecute += new CanExecuteRoutedEventHandler(binding_CanExecute);
this.CommandBindings.Add(binding);或者使用声明方式:
<CommandBinding Command="Save" Executed="CommandBinding_Executed_1" CanExecute="binding_CanExecute"></CommandBinding>
在事件处理程序中,只需要检查isUpdate变量,并设置CanExecuteRoutedEventArgs.CanExecute属性:
void binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = isUpdate;
}如果isUpdate的值为false,就会禁用Save命令,否则会启用Save命令。
当使用CanExecute事件时,是由WPF负责调用RoutedCommand.CanExecute()方法触发事件处理程序,并且确定命令的状态。当WPF命令管理器探测到一个确信是重要的变化时,例如,当焦点从一个控件移动到另一个控件,或者执行了一个命令之后,WPF命令管理器就会完成该工作。控件还能引发CanExecuteChanged事件以通知WPF重新评估命令,例如,当用户在文本框中按下一个键时就会发生该事件,总之,CanExecute事件会被频繁的触发,所以不应当在该事件的处理程序中使用耗时的代码。 然而,其化因素有可能会影响命令的状态,在以上的示例中,为了响应其它操作,isUpdate标志可能会被修改,如果注意到命令状态没有在正确的时间更新,可以强制WPF为所有正在使用的命令调用CanExecute()方法,通过调用静态的CommandManager.InvalidateRequerySuggested()方法完成该工作。然后命令管理器触发RequerySuggested事件,通知窗口中的命令源。然后命令源会查询它们连接的命令并相应地更新它们的状态。 -
具有内置命令的控件
一些输入控件自身可以处理命令事件,如TextBox类的Cut、Copy及Paste命令,以及一些来自EditingCommand类的用于选择文本以及将光标移到不同位置的命令,把此类命令绑定到命令源会自动获取对应命令的功能,而不需要再为命令绑定操作。如:
<ToolBar>
<Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
</ToolBar>此外,文本框还处理了CanExecute事件,如果在文本框中当前没有选中任何内容,剪切和复制命令就会被禁用,当焦点改变到其他不支持这些命令的控件时,这些命令都会被禁用。
在以上代码中使用了ToolBar控件,它提供了一些内置逻辑,可以将它的子元素的CommandTarget属性自动设置为具有焦点的控件。但如果在不同的容器(不是ToolBar或Menu控件)中放置按钮,就不会得到这一优点而按钮不能正常工作,此时就需要手动设置CommandTarget属性,为此,必须使用命名目标元素的绑定表达式。如:<StackPanel Grid.Row="1">
<Button Command="Cut" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
<Button Command="Copy" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
<Button Command="Paste" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
</StackPanel>另一个较简单的选择是使用FocusManager.IsFocusScope附加属性创建新的焦点范围,当命令触发时,该焦点范围会通知WPF在父元素的焦点范围中查找元素:
<StackPanel FocusManager.IsFocusScope="True" Grid.Row="1">
<Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
</StackPanel>在有些情况下,可能发现控件支持内置命令,但不想启用它,此时有三种方法可以禁用命令:
理想情况下,控件会提供用于关闭命令支持的属性,例如TextBox控件的IsUndoEnabled属性。
-
如果控件没有提供关闭命令支持的属性,还可以为希望禁用的命令添加一个新的命令绑定,然后该命令绑定可以提供新的事件处理程序。且总是将CanExecute属性设置为false,下面是一个使用该技术删除文本框Cut特性支持的示例:
CommandBinding binding = new CommandBinding(ApplicationCommands.Cut, null, SuppressCommand);
this.CommandBindings.Add(binding);
private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute= false;
e.Handled= true;
}上面的代码设置了Handled标志,以阻止文本框自我执行计算,而文本框可能将CanExecute属性设置为true.
-
使用InputBinding集合删除触发命令的输入,例如,可以使用代码禁用触发TextBox控件中Cut命令的Ctrl+X组合键,如下所示:
KeyBinding keyBinding = new KeyBinding(ApplicationCommands.NotACommand, Key.X, ModifierKeys.Control);
txt.InputBindings.Add(keyBinding);ApplicationCommands.NotACommand命令不做任何事件,它专门用于禁用输入绑定。
文本框默认显示上下文菜单,可以通过将ContextMenu属性设置为null删除上下文本菜单:<TextBoxGrid.Row="3" Name="txt" ContextMenu="{x:Null}" TextWrapping="Wrap" TextChanged="txt_TextChanged" />
-
自定义命令
下面的示例定义了一个Requery的命令:public class DataCommands
{
private static RoutedUICommand requery;
static DataCommands()
{
InputGestureCollection inputs= new InputGestureCollection();
inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
requery= new RoutedUICommand("查询", "Requery", typeof(DataCommands), inputs);
}
public static RoutedUICommand Requery //通过静态属性提供自定义的命令
{
get { return requery; }
}
}使用Requery命令时需要将它的.Net名称空间映射为一个XML名称空间,XAML代码如下:
<Window x:Class="WpfApplication1.Test4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Test4" Height="300" Width="300">
<Window.CommandBindings>
<CommandBinding Command="local:DataCommands.Requery" Executed="Requery_Executed"></CommandBinding>
</Window.CommandBindings>
<Grid>
<Button Command="local:DataCommands.Requery" CommandParameter="ai" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}" />
</Grid>
</Window>在以上代码中使用CommandParameter为命令传递了参数,命令的事件处理方法中就可以使用Parameter属性获取该参数:
private void Requery_Executed(object sender, ExecutedRoutedEventArgs e)
{
string parameters = e.Parameter.ToString();
} 在不同的位置使用相同的命令
在WPF命令模型中,一个重要的思想是Scope。尽管每个命令只有一个副本,但是使用命令的效果却会根据触发命令位置而不同,例如,如果有两个文本框,它们都支持Cut、Copy、Paste命令,操作只会在当前具有焦点的文本框中发生。但是对于自定实现的命令如New、Open、Requery及Save命令就区分不出是哪一个文本框触发的命令,尽管ExecuteRoutedEventArgs对象提供了Source属性,但是该属性反映的是具有命令绑定的元素,也就是容器窗口。此问题的解决方法是使用文本框的CommandBindings集合为每个文本框分别绑定命令。-
跟踪和翻转命令
创建自己的用于支持命令翻转的数据结构,示例中定义一个名为CommandHistoryItem的类用于存储命令状态:public class CommandHistoryItem
{
public string CommandName { get; set; } //命令名称
public UIElement ElementActedOn { get; set; } //执行命令的元素
public string PropertyActedOn { get; set; } //在目标元素中被改变了的属性
public object PreviousState { get; set; } //用于保存受影响元素以前状态的对象
public CommandHistoryItem(string commandName)
: this(commandName, null, "", null)
{
} public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyActed, object previousState)
{
this.CommandName = commandName;
this.ElementActedOn = elementActedOn;
this.PropertyActedOn = propertyActed;
this.PreviousState = previousState;
} public bool CanUndo
{
get { return (ElementActedOn != null && PropertyActedOn != ""); }
} /// <summary>
/// 使用反射为修改过的属性应用以前的值
/// </summary>
public void Undo()
{
Type elementType = ElementActedOn.GetType();
PropertyInfo property = elementType.GetProperty(PropertyActedOn);
property.SetValue(ElementActedOn, PreviousState, null);
}
}需要自定义一个执行应用程序范围内翻转操作的命令,如下所示:
private static RoutedUICommand applicationUndo;
public static RoutedUICommand ApplicationUndo
{
get { return applicationUndo; }
} static ApplicationUndoDemo()
{
applicationUndo = new RoutedUICommand("Applicaion Undo", "ApplicationUndo", typeof(ApplicationUndoDemo));
}可以使用CommandManager类来跟踪任何命令的执行情况,它提供了几个静态事件:Executed及PreviewExecuted,无论何时,当执行任何一个命令时都会触发它们。 尽管CommandManager类挂起了Executed事件,但是仍然可以使用UIElement.AddHandler()方法关联事件处理程序,并且为可选的第三个参数传递true值,从而允许接收事件。下面的代码在窗口的构造函数中关联PreviewExecuted事件处理程序,且在关闭窗口时解除关联:
public ApplicationUndoDemo()
{
InitializeComponent();
this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute),true);
} private void Window_Unloaded(object sender, RoutedEventArgs e)
{
this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute));
}当触发PreviewExecute事件时,需要确定准备执行的命令是否是我们所关心的,如果是就创建CommandHistoryItem对象,且将其添加到历史命令集合中。
private void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
// Ignore menu button source.
if (e.Source is ICommandSource) return; // Ignore the ApplicationUndo command.
if (e.Command == MonitorCommands.ApplicationUndo) return; // Could filter for commands you want to add to the stack
// (for example, not selection events). TextBox txt = e.Source as TextBox;
if (txt != null)
{
RoutedCommand cmd = (RoutedCommand)e.Command; CommandHistoryItem historyItem = new CommandHistoryItem(
cmd.Name, txt, "Text", txt.Text); ListBoxItem item = new ListBoxItem();
item.Content = historyItem;
lstHistory.Items.Add(historyItem); // CommandManager.InvalidateRequerySuggested();
}
}使用CanExecute事件处理程序,确保只有当Undo历史中有一项时,才能执行翻转操作:
<Window.CommandBindings>
<CommandBinding Command="local:ApplicationUndoDemo.ApplicationUndo" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"></CommandBinding>
</Window.CommandBindings>private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
//不知lstHistory.Items[lstHistory.Items.Count - 1]为什么强制转化不成CommandHistoryItem,有待解决
CommandHistoryItem historyItem = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - ];
if (historyItem.CanUndo)
historyItem.Undo();
lstHistory.Items.Remove(historyItem);
} private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (lstHistory == null || lstHistory.Items.Count == )
e.CanExecute = false;
else
e.CanExecute = true;
}
相关文章
- 01-31shell命令--lsattr
- 01-31HDFS常用操作命令
- 01-31svn命令使用
- 01-31python学习——读取染色体长度(五:从命令行输入染色体长度)
- 01-31git add -A (用该命令添加文件时报错)
- 01-31windows 命令行打开浏览器
- 01-31cmd命令大全
- 01-31常用DOS命令小结
- 01-31netstat命令无法使用-bash netstat commad not found
- 01-31常用命令