当用户按下键盘上的一个键时,就会发生一系列事件。下表根据他们的发生顺序列出了这些事件:编程
表 全部元素的键盘事件(按顺序)框架
键盘处理永远不会像上面看到的这么简单。一些控件可能会挂起这些事件中的某些事件,从而可执行本身更特殊的键盘处理。最明显的例子是TextBox控件,它挂起了TextInput事件。对于一些按键,TextBox控件还挂起了KeyDown事件,如方向键。对于此类情形,一般仍可以使用隧道路由事件(PreviewTextInput和PreviewKeyDown事件).ide
TextBox控件还添加了名为TextChanged的新事件。在按键致使文本框中的文本发生改变以后当即引起该事件。这时,在文本框中已经能够看到新的文本,因此阻止不须要的按键已为时太晚。布局
1、处理按键事件测试
理解键盘事件的最好方式是使用简单的示例程序,以下图所示。该例在一个文本框中监视全部可能的键盘事件,并在发生时给出报告。下图显示了文本框中输入大写A键时结果。this
上面示例的完整代码以下所示:spa
<Window x:Class="KeyEvents.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KeyPressEvents" Height="350" Width="468.421"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0">Type Here:</Label> <TextBox Grid.Row="0" Grid.Column="1" PreviewKeyDown="KeyEvent" KeyDown="KeyEvent" PreviewKeyUp="KeyEvent" KeyUp="KeyEvent" PreviewTextInput="TextInput" TextInput="TextInput"></TextBox> <ListBox Grid.ColumnSpan="2" Grid.Row="1" Grid.Column="0" Margin="5" Name="lstMessages"></ListBox> <CheckBox Name="chkHandle" Margin="5" Grid.ColumnSpan="2" Grid.Row="2">Ignore Keys Events</CheckBox> <Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Grid.ColumnSpan="2" Name="cmdClear" Click="cmdClear_Click">Clear list</Button> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace KeyEvents { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void KeyEvent(object sender, KeyEventArgs e) { if ((bool)chkHandle.IsChecked && e.IsRepeat) return; string message = "Event:" + e.RoutedEvent + " Key:" + e.Key; this.lstMessages.Items.Add(message); } private void TextInput(object sender, TextCompositionEventArgs e) { string message = "Event:" + e.RoutedEvent + " Text:" + e.Text; this.lstMessages.Items.Add(message); } private void cmdClear_Click(object sender, RoutedEventArgs e) { this.lstMessages.Items.Clear(); } } }
该例演示了很是重要的一点。每次按下一个键时,都会触发PreviewKeyDown和PreviewKeyUp事件。但只有当字符能够“输入”到元素中时,才会触发TextInput事件。这一动做实际上可能涉及多个按键操做。从上图可知,为获得大写字母A,须要按下两个键。首先,按下Shift键,按着按下A键。所以,分别看到两个KeyDown和KeyUp事件,但只有一个TextInput事件。code
PreviewKeyDown、KeyDown、PreviewKeyUp和KeyUp事件都经过KeyEventArgs对象提供了相同的信息。最重要的信息是Key属性,该属性返回一个System.Windows.Input.Key枚举值,该枚举值标识了按下或释放的键。下面是上图处理键盘事件的事件处理程序:xml
private void KeyEvent(object sender, KeyEventArgs e) { if ((bool)chkHandle.IsChecked && e.IsRepeat) return; string message = "Event:" + e.RoutedEvent + " Key:" + e.Key; this.lstMessages.Items.Add(message); }
Key值没有考虑任何其余键的状态。例如,当按下A键时没必要关心当前是否按下了Shift键,不论是否按下了Shift键都会获得相同的Key值(Key.A).对象
这里还存在一个问题。根据Windows键盘的设置,持续按下一个键一段时间,会重复引起按键事件。例如,保持按下A键,显然会在文本框中输入一系列A字符。一样,按下Shift键一段时间也会获得多个按键和一系列KeyDown事件。按下Shift+A键进行测试的真实状况是,文本框实际上会为Shift键引起一系列KeyDown事件,而后为A键引起KeyDown事件,随后是TextInput事件(对于文本框,是TextChanged事件),最后是为Shift键和A键引起KeyUp事件。若是但愿忽略这些重复的Shift键,能够经过检查KeyEventArgs.IsRepeat属性,肯定按键是否是由于按住键致使的结果,以下所示:
if ((bool)chkHandle.IsChecked && e.IsRepeat) return;
KeyDown事件发生后,接着发生PreviewTextInput事件(由于TextBox控件挂起了TextInput事件,因此不会发生TextInput事件)。此时,文本还没有出如今控件中。
TextInput事件使用TextCompositionEventArgs对象提供代码。该对象包含Text属性,该属性提供了处理过的文本,它们是控件即将接受到得文本。下面的代码将这些文本添加到上图所示的列表中:
private void TextInput(object sender, TextCompositionEventArgs e) { string message = "Event:" + e.RoutedEvent + " Text:" + e.Text; this.lstMessages.Items.Add(message); }
理想状况下,可在控件(如TextBox控件)中使用PreviewTextInput事件执行验证工做。例如,若是构建只能输入数字的文本框,可确保当前按键不是字母,若是是就设置Handled标志。惋惜,对于某些可能但愿处理的键不会触发PreviewTextInput事件。例如,若是在文本框中按下了空格键,将直接绕过PreviewTextInput事件,这意味着还须要处理PreviewKeyDown事件。
但在PreviewKeyDown事件处理程序中编写出可靠的验证逻辑是比较困难的,由于在此只知道Key值,这是级别很低的信息。例如,Key枚举区分数字键盘和普通键盘字母以上的数字键。这意味着根据按下数字9的方式,可能获得的值Key.D9或Key.NumPad9.验证全部这些容许使用的键值至少能够说是很是枯燥的。
一种选择是使用KeyConverter类将Key值转换为更有用的字符串。例如,使用KeyConverter.ConverterToString()方法,Key.D9和Key.NumPad9都返回字符串“9”。若是只使用Key.ToString()方法,将获得不那么有用的枚举名称(D9或NumPad9):
KeyConverter converter=new KeyConverter(); string key=converter.ConverterToString(e.key);
然而,即便使用KeyConverter类也存在缺陷,由于对于不会产生文本输入的按键,会获得更长一点的文本(如Backspace).
最好同事处理PreviewTextInput事件(该事件负责大多数验证)和PreviewKeyDown事件,PreviewKeyDown用于那些在文本框中不会引起PreviewTextInput事件的按钮(例如空格键)。下面是完成这一工做的简单解决方案:
private void pnl_PreviewTextInput(object sender,TextCompositionEventArgs e) { short val; if(!Int16.TryParse(e.Text,out val)) { //Disallow non-numeric key presses. e.Handled=true; } } private void pnl_PreviewKeyDown(object sender,KeyEventArgs e) { if(e.Key==Key.Space) { // Disallow the space key,which doesn't raise a PreviewTextInput event. e.Handled=true; } }
可将这些事件处理程序关联到单个文本框,或在更高层次的容器(例如,包含几个只容许输入数字的文本框的StackPanel面板)中关联他们,这样作效率更高。
2、焦点
在Windows世界中,用户每次只能使用一个控件。当前接受用户按键的控件时具备焦点控件。有时,有焦点的控件的外观不一样。例如,WPF按钮使用蓝色阴影显示它具备焦点。
为让控件能接受焦点,必须将Focusable属性设置为true,这是全部控件的默认值。
有趣的是,Focusable属性是在UIElement类中定义的,这意味着其余非控件元素也能够得到焦点。一般,对于非控件类,Focusable属性默认设置为false,但也能够设置为true。例如,使用布局容器(如StackPanel面板)测试这一点——当它得到焦点时,会在面板边缘的周围显示一条点划线边框。
为将焦点从一个元素移到另外一个元素,用户可单击鼠标或使用Tab键和方向键。之前的开发框架强制编程人员确保Tab键以合理方式移动焦点(一般是从左项右,而后从上到下),而且确保在窗口第一次显示时正确的控件得到焦点。在WPF中,没必要在完成这些额外工做,由于WPF使用层次结构的元素布局实现了Tab键切换焦点的顺序。本质上,按下Tab键会将焦点移到当前元素的第一个子元素,若是当前元素没有子元素,会将焦点移到同级的下一个子元素。例如,若是在具备两个StackPanel面板容器的窗口中使用Tab键转移焦点,焦点首先会经过第一个StackPanel面板中的全部控件,而后经过第二个StackPanel面板中的全部控件。
若是但愿得到控制使用Tab键转移焦点顺序的功能,可按数字顺序设置每一个控件的TabIndex属性。Tablndex属性为0的控件首先得到焦点,而后是次高的TabIndex值(例如首先是1,而后是二、三、4...等等)。若是多个元素具备相同的TabIndex值,WPF就使用自动Tab顺序,这意味着会跳过随后最靠近的元素。
TabIndex属性是在Control类中定义的,在该类中还定义了IsTabStop属性。可经过将IsTabStop属性设置为false来阻止控件被包含进Tab键焦点顺序。IsTabStop属性和Focusable属性之间的区别在于,若是控件的IsTabStop属性被设置为false,控件仍可经过其余方式得到焦点——经过编程(使用代码调用Focus()方法)或经过鼠标单击。
不可见或禁用的控件(“变灰的控件”)一般会忽略Tab键焦点顺序,而且不能被激活,无论TabIndex属性、IsTabStop属性以及Focusable属性如何设置。为了隐藏或禁用某个控件,可分别设置Visibility属性和IsEnabled属性。
3、获取键盘状态
当发生按键事件时,常常须要知道更多信息,而不只要知道按下的是那个键。并且肯定其余键是否同事被按下了也很是重要。这意味着可能须要检查其余键的状态,特别是Shift、Ctrl和Alt等修饰键。
对于键盘事件(PreviewKeyDown、KeyDown、PreviewKeyUp和KeyUp),获取这些信息比较容易。首先,KeyEventArgs对象包含KeyStates属性,该属性反映触发事件的键的属性。更有用的是,KeyboardDevice属性为键盘上的全部键提供了相同的信息。
天然,KeyboardDevice属性提供了KeyboardDevice类的一个实例。它的属性包含当前是哪一个元素具备焦点(FocusedElement)以及当事件发生时按下了哪些修饰键。修饰键包括Shift、Ctrl和Alt键,而且可以使用位逻辑来检查他们的状态。以下所示:
if((e.KeyboardDevice.Modifiers&ModifiersKeys.Control)==ModifierKeys.Control) { lblInfo.Text="You held the Control Key."; }
KeyboardDevice属性还提供了几个简便方法,这些方法在下表中列出。对于这些方法中的每一个方法,须要传递一个Key枚举值。
表 KeyboardDevice属性提供的方法
当使用KeyEventArgs.KeyboardDevice属性时,代码获取虚拟键状态(virtual key state)。这意味着获取在事件发生时键盘的状态,这些状态和键盘的当前状态未必相同。例如,分析一下当用户输入速度超出代码执行速度时会发生什么状况?每次引起KeyPress事件时,都将访问触发事件的按键,而不是刚输入的字符。这几乎老是想获得的行为。
然而,没有限制在键盘事件中获取键的信息,也能够随时获取键盘状态信息。技巧是使用Keyboard类,该类和KeyboardDevice类很是相似,只是Keyboard类由静态成员构成。下面的例子使用Keyboard类检查左边Shift键的当前状态:
if(Keyboard.IsKeyDown(Key.LeftShift)) { lblInfo.Text="The left Shift is held down."; }