打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
“Avalon”输入系统
“Avalon”输入系统
发布日期: 9/3/2004 | 更新日期: 9/3/2004
Nick Kramer
Microsoft Corporation
摘要:“Longhorn”中的表示子系统(代号为“Avalon”)提供了功能强大的新 API 以用于输入。本文将概述这些 API:为应用程序提供哪些服务、输入系统的体系结构以及如何支持新的输入设备。
本页内容
简介
在树中输入
文本输入
键盘示例
命令
输入核心体系结构
添加新型设备
小结
简介
“Longhorn”中的表示子系统(代号为“Avalon”)提供了新的 API 以用于输入。这些主要输入 API 都在 Element 类上。请注意,在本文中,我将“FrameworkElement”或“ContentFrameworkElement”统称为“element”。虽然它们是截然不同的类,但从输入的角度讲它们却是完全相同的。元素具有您期望从 Windows 操作系统中获得的全部鼠标和键盘功能:按键、鼠标按钮、鼠标移动、焦点管理以及鼠标捕获等等。元素具有下列与输入相关的属性、方法和事件:
class Element{// non-input APIs omitted// Mouseevent MouseButtonEventHandler MouseLeftButtonDown;event MouseButtonEventHandler MouseLeftButtonUp;event MouseButtonEventHandler MouseRightButtonDown;event MouseButtonEventHandler MouseRightButtonUp;event MouseEventHandler MouseMove;bool IsMouseOver { get; }bool IsMouseDirectlyOver { get; }event MouseEventHandler MouseEnter;event MouseEventHandler MouseLeave;event MouseEventHandler GotMouseCapture;event MouseEventHandler LostMouseCapture;bool IsMouseCaptured { get; }bool CaptureMouse();void ReleaseMouseCapture();event MouseEventHandler MouseHover;event MouseWheelEventHandler MouseWheel;// Keyboardevent KeyEventHandler KeyDown;event KeyEventHandler KeyUp;event TextInputEventHandler TextInput;bool IsFocused { get; }bool Focus();event FocusChangedEventHandler GotFocus;event FocusChangedEventHandler LostFocus;bool Focusable { get; set; }bool IsFocusWithin { get; }bool KeyboardActive { get; set; }bool IsEnabled { get; }}
此外,Mouse 和 Keyboard 类提供:
class Keyboard{static Element Focused { get; }static bool Focus(Element elt)static ModifierKeys Modifiers { get; }static bool IsKeyDown(Key key)static bool IsKeyUp(Key key)static bool IsKeyToggled(Key key)static KeyState GetKeyState(Key key)static KeyboardDevice PrimaryDevice { get; }}class Mouse{static Element DirectlyOver { get; }static Element Captured { get; }static bool Capture(Element elt);static Cursor OverrideCursor { get; set; }static bool SetCursor(Cursor cursor);static MouseButtonState LeftButton { get; }static MouseButtonState RightButton { get; }static MouseButtonState MiddleButton { get; }static MouseButtonState XButton1 { get; }static MouseButtonState XButton2 { get; }static Point GetPosition(Element relativeTo);static void Synchronize(bool force);static MouseDevice PrimaryDevice { get; }static void AddAnyButtonDown(Element element,MouseButtonEventHandler handler);static void RemoveAnyButtonDown(Element element,MouseButtonEventHandler handler);}
Avalon 还具有对笔针的集成支持。笔针是指笔输入,广泛用于 Tablet PC。Avalon 应用程序通过使用鼠标 API 可将笔针视为鼠标。但是,Avalon 还公开了与键盘和鼠标 API 同等的笔针 API:
// stylus APIs on Elementevent StylusEventHandler StylusDown;event StylusEventHandler StylusUp;event StylusEventHandler StylusMove;event StylusEventHandler StylusInAirMove;bool IsStylusOver { get; }bool IsStylusDirectlyOver { get; }event StylusEventHandler StylusEnter;event StylusEventHandler StylusLeave;event StylusEventHandler StylusInRange;event StylusEventHandler StylusOutOfRange;event StylusSystemGestureEventHandler StylusSystemGesture;event StylusEventHandler GotStylusCapture;event StylusEventHandler LostStylusCapture;bool IsStylusCaptured { get; }bool CaptureStylus()void ReleaseStylusCapture()
笔针还可充当鼠标,因此,仅识别鼠标的应用程序会自动获得某一级别的笔针支持。当以这种方式使用笔针时,应用程序首先获取适当的笔针事件,然后再获取相应的鼠标事件,我们称这个过程为笔针事件提升 到鼠标事件。(我在添加新型设备中简要讨论了提升的概念。)
此外,还可以使用更高级别的服务(例如,手写输入),虽然它们超出了本文的讨论范围。
返回页首
在树中输入
元素包含其他元素(它的子元素),从而形成了通常具有数层深的元素树。在 Avalon 中,父元素始终可以参与定向到其子元素(或孙元素等)的输入。这对于控件组合(使用较小的控件来构建控件)特别有用。
Avalon 使用事件路由向父元素发出通知。路由 是指将事件传递到多个元素,直至其中一个元素将事件标记为“handled”(已处理)的过程。事件使用以下三种路由机制之一:直接通知(也称为“不路由”)、隧道和冒泡。直接通知 意味着仅通知目标元素,这种机制由 Windows 窗体和其他 .NET 库使用。冒泡 沿元素树向上通知:先通知目标元素,然后依次通知目标的父元素以及父元素的父元素等等。隧道 的通知过程相反:先通知元素树的根,然后向下通知,最后通知目标元素。
Avalon 输入事件通常是成对出现的 — 隧道事件后面跟有冒泡事件。例如,PreviewMouseMove 隧道事件就与冒泡 MouseMove 事件一同出现。作为一个示例,假设在以下树中,“叶元素 #2”是 MouseDown/PreviewMouseDown 的目标:
事件处理的顺序将为:
1.
根元素上的 PreviewMouseDown(隧道)
2.
中间元素 #1 上的 PreviewMouseDown(隧道)
3.
叶元素 #2 上的 PreviewMouseDown(隧道)
4.
叶元素 #2 上的 MouseDown(冒泡)
5.
中间元素 #1 上的 MouseDown(冒泡)
6.
根元素上的 MouseDown(冒泡)
以下为 Element 上 Preview 输入事件的列表:
// Preview events on Elementevent MouseButtonEventHandler PreviewMouseLeftButtonDown;event MouseButtonEventHandler PreviewMouseLeftButtonUp;event MouseButtonEventHandler PreviewMouseRightButtonDown;event MouseButtonEventHandler PreviewMouseRightButtonUp;event MouseEventHandler PreviewMouseMove;event MouseWheelEventHandler PreviewMouseWheel;event MouseEventHandler PreviewMouseHover;event MouseEventHandler PreviewMouseEnter;event MouseEventHandler PreviewMouseLeave;event KeyEventHandler PreviewKeyDown;event KeyEventHandler PreviewKeyUp;event FocusChangedEventHandler PreviewGotFocus;event FocusChangedEventHandler PreviewLostFocus;event TextInputEventHandler PreviewTextInput;event StylusEventHandler PreviewStylusDown;event StylusEventHandler PreviewStylusUp;event StylusEventHandler PreviewStylusMove;event StylusEventHandler PreviewStylusInAirMove;event StylusEventHandler PreviewStylusEnter;event StylusEventHandler PreviewStylusLeave;event StylusEventHandler PreviewStylusInRange;event StylusEventHandler PreviewStylusOutOfRange;event StylusSystemGestureEventHandler PreviewStylusSystemGesture;
通常,在将事件标记为已处理之后,不会调用其他处理程序。但是,当您创建处理程序时,您可以要求它通过使用 AddHandler 方法(为 handledEventsToo 参数传递“true”)来接收已处理的事件以及未处理的事件。
由于隧道和冒泡,父元素将会接收最初以其子元素为目标的事件。通常,谁为目标并不重要,毕竟事件是未处理的事件。但是,当有必要知道目标(尤其是 MouseEnter/MouseLeave 和 GotFocus/LostFocus)时,InputEventArgs.Source 将会通知您。
另一个引人注意的问题是坐标空间。坐标 (0,0) 位于左上方,但这是什么事物的左上方?是作为输入目标的元素的左上方,还是您附加有事件处理程序的元素的左上方,或是其他事物的左上方?为了避免混淆,Avalon 输入 API 要求您在处理坐标时指定您的引用框架。例如,MouseEventArgs.GetPosition 方法将 Element 作为一个参数,而且由 GetPosition 返回的 (0,0) 坐标位于该元素的左上角。
返回页首
文本输入
TextInput 事件允许组件或应用程序以与设备无关的方式侦听文本输入。键盘是 TextInput 的主要方式,但是语音、手写以及其他输入设备也可生成 TextInput。
对于键盘输入,Avalon 将首先发送适当的 KeyDown/KeyUp 事件,但如果这些事件是未处理的且键为文本键,则会发送 TextInput 事件。通常,在 KeyDown/KeyUp 和 TextInput 事件之间并不是单个的一对一映射,多个击键可以生成单个字符的 TextInput,而单个击键可以生成多字符的字符串。对于中文、日文以及韩文尤其如此,这些语言使用输入法编辑器 (IME) 生成数以千计的以字母表示的不同字符。
当 Avalon 发送 KeyDown/KeyUp 事件时,如果击键成为 TextInput 事件的一部分,那么 KeyEventArgs.Key 将被设置为 Key.TextInput,因此应用程序不会意外地处理属于较大 TextInput 一部分的击键。在这些情况下,KeyEventArgs.TextInputKey 将显示实际击键。同样,如果 IME 处于活动状态,那么 KeyEventArgs.Key 将为 Key.ImeProcessed,而 KeyEventArgs.ImeProcessedKey 将提供实际击键。
返回页首
键盘示例
让我们看一个简单的示例,在该示例中,按 CTRL+O 会打开一个文件(无论什么控件具有焦点),而按 Open 按钮也可执行该操作:
在 Win32 中,应该定义一个快捷键对应表并处理 WM_COMMAND,这通常使用 switch 语句来完成。(您也可以尝试在窗口的 WndProc 内处理 WM_KEYDOWN,但是,如果焦点不在按钮或编辑框上,您只能获取击键,除非您还修改了按钮和编辑框的 WndProc。)
// sample.rcIDC_INPUTSAMPLE2 ACCELERATORSBEGIN"O", ID_ACCELERATOR_O, VIRTKEY, CONTROL, NOINVERTEND// sample.cppint APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow){. . .MyRegisterClass(hInstance);InitInstance(hInstance, nCmdShow);HACCEL hAccelTable = LoadAccelerators(hInstance,(LPCTSTR)IDC_INPUTSAMPLE2);MSG msg;while (GetMessage(&msg, NULL, 0, 0)){if (!TranslateAccelerator(window, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;}ATODWM MyRegisterClass(HINSTANCE hInstance) { . . . }BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){window = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!window){return FALSE;}button = CreateWindow("BUTTON", "Open",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,40, 40, 90, 30, window, (HMENU) ID_BUTTON, hInstance, NULL);if (!button){return FALSE;}DWORD dwStyle = WS_CHILD | WS_VISIBLE| WS_BORDER | ES_LEFT | ES_NOHIDESEL| ES_AUTOHSCROLL | ES_AUTOVSCROLL;edit = CreateWindow("EDIT", "...", dwStyle,40, 80, 150, 40,window, (HMENU) 6, hInstance, NULL);ShowWindow(window, nCmdShow);UpdateWindow(window);return TRUE;}LRESULT CALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam){switch (message){case WM_COMMAND: {switch (LOWORD(wParam)){case ID_ACCELERATOR_O:case ID_BUTTON:MessageBox(NULL, "Pretend this opens a file", "", 0);return 0;}break;}case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hWnd, message, wParam, lParam);}
在 Windows 窗体中,应该将窗体上的 KeyPreview 设置为 true,并处理窗体上的 KeyDown 事件:
using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;public class Form1 : Form{static void Main(){Application.Run(new Form1());}private Button button1;private TextBox textBox1;public Form1(){this.button1 = new Button();this.textBox1 = new TextBox();this.SuspendLayout();//// button1//this.button1.Location = new Point(8, 40);this.button1.Name = "button1";this.button1.TabIndex = 0;this.button1.Text = "Open";this.button1.Click += new EventHandler(this.button1_Click);//// textBox1//this.textBox1.Location = new Point(8, 88);this.textBox1.Name = "textBox1";this.textBox1.TabIndex = 1;this.textBox1.Text = "...";//// Form1//this.AutoScaleBaseSize = new Size(6, 15);this.ClientSize = new System.Drawing.Size(292, 260);this.Controls.AddRange(new Control[] {this.textBox1,this.button1});this.Name = "Form1";this.Text = "Input Sample";this.KeyPreview = true;this.KeyDown += new KeyEventHandler(this.Form1_KeyDown);this.ResumeLayout(false);}private void button1_Click(object sender, EventArgs e){handle();}private void Form1_KeyDown(object sender, KeyEventArgs e){if (e.KeyCode == Keys.O && e.Modifiers == Keys.Control){handle();e.Handled = true;}}void handle(){MessageBox.Show("Pretend this opens a file");}}
在 Avalon 中,应该为 Button 的 Click 事件 (btn_Click) 定义一个处理程序,也要为 KeyDown (fp_KeyDown) 定义一个处理程序:
<Windowxmlns="http://schemas.microsoft.com/2003/xaml"xmlns:def="Definition"Text="Application1" Visible="True"><FlowPanel KeyDown="fp_KeyDown"><Button Click="btn_Click"> Open </Button><TextBox> ... </TextBox><def:Code> <![CDATA[void fp_KeyDown(object sender, KeyEventArgs e) {if (e.Key == Key.O&& Keyboard.Modifiers == ModifierKeys.Control) {handle();e.Handled = true;}}void btn_Click(object sender, ClickEventArgs e) {handle();e.Handled = true;}void handle() {MessageBox.Show("Pretend this opens a file");}]]> </def:Code></FlowPanel></Window>
请注意,KeyDown 处理程序附加到树根附近的 FlowPanel 中。(我们使用 FlowPanel 而不是 Window,这是因为 Window 类不能获取输入。)由于输入沿树向上冒泡,因此无论哪种元素具有焦点,FlowPanel 均将获取输入。
这些示例有一点略有不同,假设编辑控件要处理 CTRL+O,结果会如何呢?在 Win32 和 Windows 窗体示例中,编辑控件从未接收到 WM_KEYDOWN 或等效通知,这是因为事件是在消息循环中通过 TranslateAccelerator 的方式处理的。在 Avalon 示例中,首先通知 TextBox 控件,然后,仅当 TextBox 没有处理输入时才会调用我们的 fp_KeyDown 处理程序。或者,我们可以处理 PreviewKeyDown 而不是 KeyDown,在这种情况下,首先调用我们的 fp_KeyDown 处理程序。
在上面的 Avalon 示例中,我们两次结束编写处理逻辑,一次针对 CTRL+O,另一次针对按钮单击。我们可以使用 Avalon 命令 简化此操作。
返回页首
命令
注命令在 PDC 2003 Longhorn 预发布版本中仅部分实现。
与设备输入相比,命令使您能够在更富有语义的层次上处理输入。命令是简单的指令,例如,“cut”、“copy”、“paste”或“open”。Avalon 将提供通用命令库,但是您也可以定义自己的命令库。
命令对于集中处理逻辑十分有用。可以从菜单中、在工具栏上或者通过键盘快捷键来访问相同的命令,而且,您可以使用命令来编写适用于所有不同输入情况的单行代码。命令还提供了一种机制,当命令变得不可用时,使用此机制可以将菜单项和工具栏按钮变为灰色。
Avalon 提供的通用命令附带有一组内置的默认输入绑定,因此,当您指定应用程序处理 Copy 时,您会自动获得 CTRL+C = Copy 绑定。您还可获得用于其他输入设备的绑定,例如,Tablet 笔势输入和语音信息。最后,许多通用命令都附带有自己的图标,从而使工具栏看上去更加一致且专业。
许多控件都具有对某些命令的内置支持。例如,TextBox 理解 Cut、Copy 和 Paste。由于这些命令中的每一个都提供了默认的键绑定,因此,TextBox 会自动支持这些快捷键。
返回页首
输入核心体系结构
输入系统由内核模式组件和用户模式组件组成。输入在设备驱动程序中产生,然后,对于大多数输入设备而言,此输入会发送到 USER 和 GDI 的内核模式组件 win32k.sys 中。Win32k.sys 会对输入进行一些处理,并决定将输入发送到哪个应用程序进程。在 Longhorn 应用程序内,Avalon 会对输入执行进一步的处理,并向应用程序发送通知。
与 Win32 程序一样,Avalon 程序也具有可以轮询外部环境以获取新通知的消息循环。Avalon 可以与标准的 Win32 消息循环集成,该消息循环通过调度程序 与 Avalon 的其余部分连接。调度程序能提取特定循环的详细信息,从而也能提供服务以便处理嵌套消息循环。为了从 Win32 接收消息,Avalon 具有一个称为 HwndSource 的 hwnd。消息处理是同步的,即在 Avalon 完全处理完输入消息之前,HwndSource WndProc 不会返回。在期望 WndProc 返回一个值的情况下,这会启用与 Win32 的集成。
在 Longhorn 应用程序内,输入处理如下所示:
在核心输入系统(以灰色框表示)中,当 IInputProvider 向其相应的 InputProviderSite 通知有关可用输入报告时,输入便会开始。站点会通知 InputManager,后者将输入报告放在临时区域中。然后,会在临时区域上运行各种监视器和筛选器,从而将输入报告变成一系列事件。最后,通过元素树路由事件,并调用处理程序。
键盘和鼠标的输入提供程序通过 HwndSource(未用图表示)的方式从 Win32 USER 获取输入。其他设备可以选择此机制,也可以选择一种完全不同的机制。笔针就是并非来自 HwndSource 的输入的一个示例,笔针的输入提供程序从 wisptis.exe 获取输入,后者又通过 HID(人机接口设备 API)与设备驱动程序进行会话。InputManager 提供了用于注册新的输入提供程序的 API。
筛选器是指侦听 InputManager.PreProcessInput 或 InputManager.PostProcessInput 事件的任何代码。筛选器可以修改临时区域。取消 PreProcessInput 将会从临时区域中删除输入事件。PostProcessInput 将临时区域公开为一个堆栈,即可以将项从临时区域顶部弹出或推入。
监视器是侦听 InputManager.PreNotifyInput 或 InputManager.PostNotifyInput 的任何代码。监视器无法修改临时区域。
返回页首
添加新型设备
注我们处于本部分所讨论功能的早期设计阶段,非常感谢您的反馈。以下列出了能够启用设备扩展性的一些方案:
向键盘添加新键;向鼠标添加按钮
添加能够向应用程序公开 API 的新型设备
添加能够模拟现有设备(通常为鼠标和键盘)的新型设备
添加能够公开 API 并模拟现有设备的新型设备,以获得与不理解此类设备的应用程序的兼容性
使用 HID 添加新设备或设备功能
启用使用 HID 的应用程序以获取较低级别的“原始”输入
全局启用到操作的绑定输入序列;例如,“mail”键将会启动电子邮件程序(包括前台应用程序不理解 mail 键的情形)
返回页首
小结
使用 Avalon 可以完全访问鼠标、键盘以及笔针,从而提供了更高级别的服务,以用于文本输入和命令。
转到原英文页面
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【WPF学习】第十六章 键盘输入
DataGrid实现批量输入(like Excel)
asp.net如何在页面加载时响应回车事件
委托中使用变体
C#Threading.Suspend在过时,线程已被弃用?
jquery按回车键实现提交代码
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服