6.2 处理键盘事件

键盘事件是由wxKeyEvent类表示的.总共有三种不同类型的键盘事件,分别为:键按下,键释放和字符事件.键按下和键释放事件是原始事件,而字符事件是翻译事件,我们马上会描述字符事件,不过在这之前先要清楚,如果一个按键被长时间按下,你通常就收到很多个键按下事件,而只收到一个键释放事件,,因此不要以为一个键释放事件一定对应一个键按下事件,这种想法是错误的.

要想接收到键盘事件,你的窗口必须拥有键盘焦点,这可以通过函数wxWindow::SetFocus来设置,比如当鼠标点击窗口的时候可以调用这个函数.

下表列出了对应的事件映射宏

EVT_KEY_DOWN(func) 用来处理wxEVT_KEY_DOWN事件 (原始按键按下事件).
EVT_KEY_UP(func) 用来处理wxEVT_KEY_UP事件 (原始的按键释放).
EVT_CHAR(func) 用来处理wxEVT_CHAR事件 (已经翻译的按键按下事件).

接下来描述一下你可以在事件处理函数中使用的处理函数.

要获得按键编码,你可以使用GetKeyCode函数(在Unicode版本中,你还可以使用GetUnicodeKeyCode函数).下表列出了所有的按键编码:

WXK_BACK WXK_RIGHT
WXK_TAB WXK_DOWN
WXK_RETURN WXK_SELECT
WXK_ESCAPE WXK_PRINT
WXK_SPACE WXK_EXECUTE
WXK_DELETE WXK_SNAPSHOT
WXK_INSERT WXK_START
WXK_HELP WXK_LBUTTON
WXK_RBUTTON WXK_NUMPAD0
WXK_CANCEL WXK_NUMPAD1
WXK_MBUTTON WXK_NUMPAD2
WXK_CLEAR WXK_NUMPAD3
WXK_SHIFT WXK_NUMPAD4
WXK_CONTROL WXK_NUMPAD5
WXK_MENU WXK_NUMPAD6
WXK_PAUSE WXK_NUMPAD7
WXK_CAPITAL WXK_NUMPAD8
WXK_PRIOR WXK_NUMPAD9
WXK_NEXT WXK_END
WXK_MULTIPLY WXK_HOME
WXK_ADD WXK_LEFT
WXK_SEPARATOR WXK_UP
WXK_SUBTRACT WXK_DECIMAL
WXK_PAGEDOWN WXK_DIVIDE
WXK_NUMPAD_SPACE WXK_F1
WXK_NUMPAD_TAB WXK_F2
WXK_NUMPAD_ENTER WXK_F3 WXK_F4
WXK_NUMPAD_F1 WXK_F5
WXK_NUMPAD_F2 WXK_F6
WXK_NUMPAD_F3 WXK_F7
WXK_NUMPAD_F4 WXK_F8
WXK_NUMPAD_HOME WXK_F9
WXK_NUMPAD_LEFT WXK_F10
WXK_NUMPAD_UP WXK_F11
WXK_NUMPAD_RIGHT WXK_F12
WXK_NUMPAD_DOWN WXK_F13
WXK_NUMPAD_PRIOR WXK_F14
WXK_NUMPAD_PAGEUP WXK_F15
WXK_NUMPAD_NEXT WXK_F16
WXK_NUMPAD_PAGEDOWN WXK_F17
WXK_NUMPAD_END WXK_F18
WXK_NUMPAD_BEGIN WXK_F19
WXK_NUMPAD_INSERT WXK_F20
WXK_NUMPAD_DELETE WXK_F21
WXK_NUMPAD_EQUAL WXK_F22
WXK_NUMPAD_MULTIPLY WXK_F23
WXK_NUMPAD_ADD WXK_F24
WXK_NUMPAD_SEPARATOR WXK_NUMPAD_SUBTRACT
WXK_NUMLOCK WXK_NUMPAD_DECIMAL
WXK_SCROLL WXK_NUMPAD_DIVIDE
WXK_PAGEUP

要判断在按键的时候是否有状态键按下,可以使用AltDown, MetaDown, ControlDown或者ShiftDown函数. HasModifiers函数在有Control或者Alt键按下的时候返回True(不包括Shift和Meta键).

你可以使用CmdDown函数来代替ControlDown或者MetaDown函数,它在Mac OSX上调用MetaDown而在别的平台上调用ControlDown.在接下来的部分会对此进行解释.

GetPosition函数返回按键的时候的鼠标指针相对于窗口客户区原点的位置.

提示:如果你在键盘事件处理函数中没有调用event.Skip()函数,对应的字符事件将不会产生.在某些平台上,全局的快捷键也会不起作用.

字符事件处理的例子

下面列出的代码是随书光盘examples/chap12/thumbnail目录中wxThumbnailCtrl类的事件处理函数:

BEGIN_EVENT_TABLE( wxThumbnailCtrl, wxScrolledWindow )
    EVT_CHAR(wxThumbnailCtrl::OnChar)
END_EVENT_TABLE()
void wxThumbnailCtrl::OnChar(wxKeyEvent& event)
{
    int flags = 0;
    if (event.ControlDown())
        flags |= wxTHUMBNAIL_CTRL_DOWN;
    if (event.ShiftDown())
        flags |= wxTHUMBNAIL_SHIFT_DOWN;
    if (event.AltDown())
        flags |= wxTHUMBNAIL_ALT_DOWN;
    if (event.GetKeyCode() == WXK_LEFT ||
        event.GetKeyCode() == WXK_RIGHT ||
        event.GetKeyCode() == WXK_UP ||
        event.GetKeyCode() == WXK_DOWN ||
        event.GetKeyCode() == WXK_HOME ||
        event.GetKeyCode() == WXK_PAGEUP ||
        event.GetKeyCode() == WXK_PAGEDOWN ||
        event.GetKeyCode() == WXK_PRIOR ||
        event.GetKeyCode() == WXK_NEXT ||
        event.GetKeyCode() == WXK_END)
    {
        Navigate(event.GetKeyCode(), flags);
    }
    else if (event.GetKeyCode() == WXK_RETURN)
    {
        wxThumbnailEvent cmdEvent(
            wxEVT_COMMAND_THUMBNAIL_RETURN,
            GetId());
        cmdEvent.SetEventObject(this);
        cmdEvent.SetFlags(flags);
        GetEventHandler()->ProcessEvent(cmdEvent);
    }
    else
        event.Skip();
}

为了代码更简洁,方向键处理时候调用了另外一个单独的函数Navigate,而回车键则产生了一个更高一级的事件,这个事件可以被使用这个类的应用程序捕获并且处理,对于所有其它的按键,调用Skip函数以便应用程序的其它部分可以继续处理.

按键编码翻译

键盘事件提供的是未翻译的按键编码,而字符事件提供的是翻译以后的字符编码,对于未翻译的按键编码来说,子母永远是大些字符,其它字符则是在WXK_XXX中定义的字符.而对于已经翻译的按键编码来说,字符的值和同样的按键在一个文本编辑框中被按下以后在编辑框中产生的字符相同.

举个简单的例子,当一个单独的A键按下的时候,在KEY_DOWN事件中的字符编码是大字子母A的ASCII码65,而在相应的字符事件中的字符编码是小写的ASCII的a,编码为97.换句话说,当Shift和A键同时被按下时,上述两个事件中的编码是一样的,都是大写的A(65).

从这个小例子中我们可以清晰的看到,我们可以从键盘按下事件中的键盘编码和Shift键状态计算出相应的ASCII码,但是通常来说,如果你希望处理的是 ASCII码,你应该使用字符事件EVT_CHAR,因为对于非子母按键来说,如何翻译是和键盘布局有关的,只有系统本身才能对按键事件进行很好的翻译.

另外一种自动完成的按键翻译是那种带有Control键的翻译:比如说Ctrl+A,在KeyDown事件中,字符编码仍然是A,但是在字符事件中则为ASCII的1,因为ASCII中定义这个组合键的编码为1.

如果你对在你的系统中这种系统相关的键盘行为感兴趣,可以编译和运行键盘例子程序(在samples/keyboard目录中)然后按每个键试一下.

修饰键变量

在windows平台上,有Control和Alt两个修饰键,那个特殊的window键表现Meta键的行为.在Unix平台上,表现Meta键的按键是可以配置的(通过运行xmodmap来查看和改变现有配置).有时Numlock键也会被配置成Meta键,这是为什么在Numlock键被按下时,按下Meta键再按下别的键的时候,HasModifiers却返回False的原因.

在Mac OSX平台上,Command键(上面有一个苹果的标识)被翻译成Meta键,而Option键被翻译成Alt键.

各个平台上修饰键的不同如下表所示,其中wxWidgets采用的修饰键名放在第一栏,三个主要平台上对应的键放在后面三栏.

Modifier Key on Windows Key on Unix Key on Mac
Shift Shift Shift Shift
Control Control Control Control
Alt Alt Alt Option
Meta Windows (Configurable) Command

因为在Mac OSX上使用Command键而在别的平台上使用Control键,你可以使用wxKeyEvent的CmdDown函数来判断这个键在不同的平台上是否被按下.

另外除了在键盘事件处理函数中判断一个修饰键是否被按下以外,你还可以使用wxGetKeyState函数加上对应的键盘编码作为参数来判断某个键是否被按下,

加速键

加速键是为了实现通过某种组合键来快速执行菜单命令.加速键的处理是在所有的键盘事件(包括字符事件)之后.标准的加速键包括Ctrl+ O用来打开一个文件,Ctrl+V用来把剪贴板上的数据粘贴到应用程序中等.最简单的定义加速键的方法是在菜单项定义函数中使用下面的代码:

menu->Append(wxID_COPY, wxT("Copy\tCtrl+C"));

wxWidgets把"\t"后面的内容翻译为加速键增加到菜单的加速键表中.在上面的例子中,用户按Ctrl+C组合键的效果和用户选择这个菜单的效果是完全一样的.

你可以使用Ctrl,Alt和Shift以及它们的各种组合,然后加一个+号或者-号再跟一个字符或者功能键,比如下面的这些加速键都是合法的加速键: Ctrl+B, G, Shift-Alt-K, F9, Ctrl+F3, Esc 和Del. 在你的加速键定义中可以使用下面的名字: Del, Back, Ins, Insert, Enter, Return, PgUp, PgDn, Left, Right, Up, Down, Home, End, Space, Tab, Esc和 Escape. 这些命令是大小写无关的(你想怎样使用大小写都可以).

注意在Mac OSX平台上,一个定义为Ctrl的加速键实际上代表的是Command键.

另外一种设置加速键的方法是使用wxAcceleratorEntry对象定义一个加速键表,然后使用wxWindow:: SetAcceleratorTable函数将其和某个窗口绑定.每一个wxAcceleratorEntry的记录是由一个修饰键比特位值和一个字符或者功能键以及一个窗口标识符组成的,如下所示:

wxAcceleratorEntry entries[4];
entries[0].Set(wxACCEL_CTRL,  (int) 'N',     wxID_NEW);
entries[1].Set(wxACCEL_CTRL,  (int) 'X',     wxID_EXIT);
entries[2].Set(wxACCEL_SHIFT, (int) 'A',     wxID_ABOUT);
entries[3].Set(wxACCEL_NORMAL, WXK_DELETE,   wxID_CUT);
wxAcceleratorTable accel(4, entries);
frame->SetAcceleratorTable(accel);

你可以同时使用多个加速键表,也可以混合使用菜单项加速键和加速键表,如果你想给一个菜单项指定多个加速键,这将是非常有用的,因为你不可能在一个菜单项中指定多个加速键.