当用户按下键盘上的一个键时,就会发生一系列事件。下表根据他们的发生顺序列出了这些事件:

表 所有元素的键盘事件(按顺序)

  键盘处理永远不会像上面看到的这么简单。一些控件可能会挂起这些事件中的某些事件,从而可执行自己更特殊的键盘处理。最明显的例子是TextBox控件,它挂起了TextInput事件。对于一些按键,TextBox控件还挂起了KeyDown事件,如方向键。对于此类情形,通常仍可使用隧道路由事件(PreviewTextInput和PreviewKeyDown事件).

  TextBox控件还添加了名为TextChanged的新事件。在按键导致文本框中的文本发生改变之后立即引发该事件。这时,在文本框中已经可以看到新的文本,所以阻止不需要的按键已为时太晚。

一、处理按键事件

  理解键盘事件的最好方式是使用简单的示例程序,如下图所示。该例在一个文本框中监视所有可能的键盘事件,并在发生时给出报告。下图显示了文本框中输入大写A键时结果。

  上面示例的完整代码如下所示:

<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>

KeyEvents.XAML

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();
}
}
}

KeyEvents.cs

  该例演示了非常重要的一点。每次按下一个键时,都会触发PreviewKeyDown和PreviewKeyUp事件。但只有当字符可以“输入”到元素中时,才会触发TextInput事件。这一动作实际上可能涉及多个按键操作。从上图可知,为得到大写字母A,需要按下两个键。首先,按下Shift键,按着按下A键。因此,分别看到两个KeyDown和KeyUp事件,但只有一个TextInput事件。

  PreviewKeyDown、KeyDown、PreviewKeyUp和KeyUp事件都通过KeyEventArgs对象提供了相同的信息。最重要的信息是Key属性,该属性返回一个System.Windows.Input.Key枚举值,该枚举值标识了按下或释放的键。下面是上图处理键盘事件的事件处理程序:

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面板)中关联他们,这样做效率更高。

二、焦点

  在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,然后是2、3、4...等等)。如果多个元素具有相同的TabIndex值,WPF就使用自动Tab顺序,这意味着会跳过随后最靠近的元素。

  TabIndex属性是在Control类中定义的,在该类中还定义了IsTabStop属性。可通过将IsTabStop属性设置为false来阻止控件被包含进Tab键焦点顺序。IsTabStop属性和Focusable属性之间的区别在于,如果控件的IsTabStop属性被设置为false,控件仍可通过其他方式获得焦点——通过编程(使用代码调用Focus()方法)或通过鼠标单击。

  不可见或禁用的控件(“变灰的控件”)通常会忽略Tab键焦点顺序,并且不能被激活,不管TabIndex属性、IsTabStop属性以及Focusable属性如何设置。为了隐藏或禁用某个控件,可分别设置Visibility属性和IsEnabled属性。

三、获取键盘状态

  当发生按键事件时,经常需要知道更多信息,而不仅要知道按下的是那个键。而且确定其他键是否同事被按下了也非常重要。这意味着可能需要检查其他键的状态,特别是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.";
}

【WPF学习】第十七章 键盘输入的更多相关文章

  1. 【Visual C++】游戏编程学习笔记之七:键盘输入消息

     本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder  微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.c ...

  2. 【WPF学习】第十七章 鼠标输入

    鼠标事件执行几个关联的任务.当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应.这些事件是MouseEnter(当鼠标指针移到元素上时引发该事件)和MouseLeave(当鼠标指针离开元素时引发该 ...

  3. 【WPF学习】第四十一章 变换

    通过使用变换(transform),许多绘图任务将更趋简单:变换是通过不加通告地切换形状或元素使用的坐标系统来改变形状或元素绘制方式的对象.在WPF中,变换由继承自System.Windows.Med ...

  4. 【WPF学习】第一章 XAML介绍

    XAML(Extensible Application Markup Language的简写,发音为“zammel”)是用于实例化.NET对象的标记语言.尽管XAML是一种应用于诸多不同问题领域的技术 ...

  5. 【Visual C++】游戏编程学习笔记之八:鼠标输入消息(小demo)

     本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder  微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.c ...

  6. C语言程序设计(四) 键盘输入和屏幕输出

    第四章 键盘输入和屏幕输出 转义字符 \n 换行,光标移到下一行的起始位置 \r 回车(不换行),光标移到当前行的起始位置 \0 空字符 \t 水平制表 \v 垂直制表 \b 退格 \f 走纸换页 \ ...

  7. [汇编学习笔记][第十七章使用BIOS进行键盘输入和磁盘读写

    第十七章 使用BIOS进行键盘输入和磁盘读写 17.1 int 9 中断例程对键盘输入的处理 17.2 int 16 读取键盘缓存区 mov ah,0 int 16h 结果:(ah)=扫描码,(al) ...

  8. 【WPF学习】第五十七章 使用代码创建故事板

    在“[WPF学习]第五十章 故事板”中讨论了如何使用代码创建简单动画,以及如何使用XAML标记构建更复杂的故事板——具有多个动画以及播放控制功能.但有时采用更复杂的故事板例程,并在代码中实现全部复杂功 ...

  9. 【WPF学习】第五十三章 动画类型回顾

    创建动画面临的第一个挑战是为动画选择正确的属性.期望的结果(例如,在窗口中移动元素)与需要使用的属性(在这种情况下是Canvas.Left和Canvas.Top属性)之间的关系并不总是很直观.下面是一 ...

随机推荐

  1. C# 程序集数量对软件启动性能的影响

    本文通过很多的数据测试分析在一个项目引用很多个外部项目和将外部项目的类合并到一个项目之间的启动性能的不同. 通过分析知道了如果一个项目引用了很多项目,而且在启动过程会全部调用这些项目,这时的软件性能会 ...

  2. Python涉及的各个领域以及技术应用

    WEB开发 完全主义者高效率框架Django 异步高并发Tornado框架 短小精悍Flask,Bottle框架 网络编程 高并发Twisted网络框架 Python3引入的asyncio异步编程 爬 ...

  3. 根据经纬度查询附近几公里的门店(<5)代表5公里

    select * from 表名 where status=1 and isopen =0 and jingyingtype=1 and waimai=1 and bstatus = 1 and (a ...

  4. linux查看文件内容跳到文件底部和回到文件顶部的快捷键

    有时候需要查看一些日志文件,然后要从底部开始查看的话 可以按 shift+g  即可跳到文件底部 要返回文件顶部的时候 按 gg即可

  5. 聊聊多线程哪一些事儿(task)之 三 异步取消和异步方法

    hello,咋们又见面啦,通过前面两篇文章的介绍,对task的创建.运行.阻塞.同步.延续操作等都有了很好的认识和使用,结合实际的场景介绍,这样一来在实际的工作中也能够解决很大一部分的关于多线程的业务 ...

  6. ulimit -u 解决 Jenkins OOM 错误

    Apr 24, 2018 11:19:48 AM hudson.init.impl.InstallUncaughtExceptionHandler$DefaultUncaughtExceptionHa ...

  7. GNE: 4行代码实现新闻类网站通用爬虫

    GNE(GeneralNewsExtractor)是一个通用新闻网站正文抽取模块,输入一篇新闻网页的 HTML, 输出正文内容.标题.作者.发布时间.正文中的图片地址和正文所在的标签源代码.GNE在提 ...

  8. 爬虫 -- JS调试

    开发者工具(F12) 其中常用的有Elements(元素面板).Console(控制台面板).Sources(源代码面板).Network(网络面板) 找 JS 文件的几种方法 1.找发起地址 2.设 ...

  9. MapReduce-自动化运行配置

    1.打包时指定main  Class信息 注意:默认直接通过maven插件打成jar包中没有指定main class信息,因此在运行mapreduce的jar包时必须在指令后明确main class信 ...

  10. Eclipse和Tomcat的版本问题---已解决

    Eclipse和Tomcat的版本问题---已解决 这篇文章主要是解决版本匹配的问题 我的电脑上装的是jdk10,如图: Tomcat装的是9: 接着配置好环境变量,直接上图: 然后启功Tomcat, ...