Tribon任意快捷键的实现
2012-01-22
(南通中远川崎船舶工程有限公司,江苏 南通 226005)
Tribon M3是AVEVA公司开发的辅助船舶设计和建造的计算机软件集成系统,广泛应用于船舶设计领域。通过使用Tribon对传统的配管现场放样等作业在计算机中进行模拟,可节省工时,减少物量浪费。然而,历经多次升级,其用户界面及界面扩展方面却始终不能令人满意。Tribon原版仅提供了用于基本操作的快捷键或工具栏,而大量操作只能通过选取菜单来完成,各公司只能根据各自的需求投入相当的人力和工时进行二次开发以实现其它常用操作的快捷键,而即便是二次开发仍难以实现单个终端用户快捷键的个性化配置(注:目前已有第三方工具如Tribon Mx Package可实现此功能)。
合理的快捷键设置可有效改善Tribon绘图作业的效率,找到Tribon自定义快捷键的通用方法,最大程度上满足用户便捷使用,成为亟待解决的问题。
1 Tribon推荐快捷操作扩展方式
Tribon主要使用Python作为其二次开发语言,其官方推荐使用Toolbar Functions及Accelerator Functions两套函数用于扩展快捷键。
Toolbar Functions以提供自定义工具栏的方式,可将Tribon菜单中已有的命令或用户自定义的模块可视化展现;Accelerator Functions则直接增加了系统的快捷键数量,将繁琐的鼠标点击工作代之以键盘操作。两套函数主要接口见表1。
其中,关键函数toolbar_button_std_add和accelerator_add都包含一个Function参数,其表示的是Tribon命令的ID号,在Tribon安装目录的Vitesse文档中可以找到大部分命令的ID号。
表1 Tribon快捷键扩展主要函数接口
在实际使用过程中,两种方式各有优缺点。Toolbar Fuctions扩展严格意义上说并不是快捷键,但却以直观的优点为大多数用户所使用,而Accelerator Functions扩展真正体现了快捷键的含义,但难以实现类似AutoCAD等主流工程软件的单键快捷键操作,使得应用具有较大的局限性。此外,由于Tribon自定义界面函数被设计为必须用于Initialize Dafting Post-trigger中,而在多用户共同使用的环境中,修改trigger的权限一般又仅限于系统管理员,且由于各工种的特殊性,使得相同操作使用频率不同,使上述两种扩展方式难以在单个终端用户的使用中得到有效利用。
2 Windows程序的加速键机制
Windows窗口程序是事件驱动的,用户在键盘上按下相应的加速键将产生相应的命令消息。通常意义上,加速键就是菜单的快捷键,其实现对程序员来说仅仅是维护特定的键盘按键组合与处理例程的一个索引。最终系统接收到按键按下消息时会检测加速键资源,看按键是否符合某个加速键,如符合则向目标窗口发送命令消息[1]。
微软在Windows平台程序开发包中提供了加速键相关的API,对程序开发而言,不仅可加载资源表中内置的加速键资源,还能创建运行时加速键表,甚至提供了相当完整的代码“Creating User-Editable Accelerators”,只需稍加修改即可使用。
3 扩展程序的设计
在Windows程序界面开发过程中,工具栏往往用作菜单的补充,为用户提供一个快捷的程序功能选择方式,而快捷键一般被定义为最常用的菜单命令。为了共用相同的代码,通常将工具栏铵钮和快捷键命令ID与对应的菜单命令ID设置为相同的。
虽然使用的是Python作为其推荐的界面开发语言,但通过对其菜单ID和Tribon可用的功能ID对比发现,Tribon仍然遵循了这一通用规则。一般情况下,直接获取其它程序的菜单命令ID需要使用第三方软件,但Tribon这种特有的扩展方式将其菜单命令ID以正式文档的形式提供给用户,从而促使其在系统升级时能很大程度保证不同版本菜单命令ID的可兼容性,从而使下面的探讨有意义。
由于Tribon系统本身没有提供良好的支持单个用户快捷键个性化定制的接口,在无源码的情况下采用非常规手段修改程序虽可达到扩展快捷键的效果(注:详细方法见tribon.cn),但风险不小,一般公司用户不会采纳。因此考虑通过开发程序,“替”Tribon实现快捷键。
Windows是消息驱动式系统,是Windows消息提供应用程序与应用程序之间、应用程序与Windows系统之间进行通信的手段[2]。Windows加速键的核心也是基于按键消息的检测,而使用系统提供的接口,通过编程完全可以检测特定按键消息,再通过窗口间通信即可控制Tribon完成相应的功能。此外,在不同应用程序之间的窗口中可以通过SendMessage或PostMessage函数互发消息 实现窗口通信,这些特性使得模拟Tribon快捷键实现成为可能。
下面介绍通过全局热键或检测特定击键的发生等方式,操控Tribon完成某一操作曲线实现Tribon的快捷键定制。
3.1 使用全局热键
Windows除了提供作用于单个应用程序的加速键机制之外,还提供了系统范围的热键响应接口。Windows对热键的处理是当每个按键按下时,系统都会首先检测已有的热键,如果找到匹配的就向注册该热键的程序发送特定的消息。应用程序通过RegisterHotKey接口注册热键,并在消息循环中响应WM_HOTKEY消息。利用此接口的优先权,Windows将在Tribon窗口产生的特定组合按键消息派送给扩展程序,由扩展程序发送操作命令给Tribon,从而利用系统的接口改变了常规的消息路由,实现了自定义Tribon快捷键。
这种方法实际是将扩展程序自身的热键转换为Tribon的快捷键,这种实现方式最为便捷,但只能定义组合键作为快捷键。下面介绍通过主动改变消息路由来扩展Tribon任意快捷键。
3.2 按键消息的截获
通常情况下,Windows会检测系统消息队列里消息的发生位置,将消息派的送到对应应用程序的消息队列。此外,Windows系统还提供了其它方式让应用程序得以在特定消息发生时截获消息。
Windows钩子可以截获系统的消息流,从而改变消息路由或者直接对消息进行修改。一种可行的方法是使用WH_KEYBOARD等钩子对键盘输入进行过滤,处理快捷键按键消息。
安装Windows钩子尤其是系统级别的钩子对系统性能有影响,而对于按键消息,Windows还提供了更为便捷的接口Raw input,即原始输入接口。原始输入的API主要是被设计来处理输入设备的数据。调用Register Raw Input Device,应用程序可以注册系统当前没有的设备,设备可用之后,Windows自动向应用程序发送WM-INPUT消息,而不管应用程序在前台还是后台。
使用原始输入接口包括设备的注册及WM_INPUT消息的处理关键代码如下:
BOOL C_AccDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//
// RAWINPUTDEVICE rid[2];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = m_hWnd;
rid[1].usUsagePage = 0x01;
rid[1].usUsage = 0x02;
rid[1].dwFlags = RIDEV_INPUTSINK;
rid[1].hwndTarget = m_hWnd;
return TRUE;
}
LRESULT C_AccDlg::OnInput(WPARAM wParam, LPARAM lParam)
{
int i;
BYTE lpKeyboard[256];
WORD wKey;
RAWINPUT *buffer;
UINT dwSize;
GetKeyState(VK_CAPITAL);
GetKeyState(VK_SCROLL);
GetKeyState(VK_NUMLOCK);
GetKeyboardState(lpKeyboard);
// request size of the raw input buffer to dwSize
GetRawInputData((HRAWINPUT)lParam,RID_INPUT,NULL,&dwSize,sizeof(RAWINPUTHEADER));
// allocate buffer for input data
buffer=(RAWINPUT*)HeapAlloc(GetProcessHeap(),0,dwSize);
if(GetRawInputData((HRAWINPUT)lParam,RID_INPUT,buffer,&dwSize,sizeof(RAWINPUTHEADER)))
{
if(buffer->header.dwType==RIM_TYPEKEYBOARD && buffer->data.keyboard.Message==WM_KEYDOWN)
{
UINT uCode=buffer->data.keyboard.VKey;
//检测当前窗口
if(ToAscii(uCode,MapVirtualKey(uCode, 0), lpKeyboard,&wKey,0)== 1)
{
//查询匹配命令ID
}
}
}
HeapFree(GetProcessHeap(), 0, buffer);
return 0;
}
3.3 按键消息的处理和转发
扩展程序接收到按键消息,需要经过处理再转发给Tribon程序,其流程见图1。
图1 扩展程序按键消息处理流程
按键消息的处理即是查找按键消息跟指定操作的对应关系。扩展程序将快捷键配置信息存放在Accesss数据库文件中,在接收到按键信息后,在配置文件中查找匹配的按键,如查到则获取其对应的命令ID,配置表包括使用全局热键和Rawinput接口两种方式的按键信息、Tribon命令ID、操作说明和其它程序控制信息,其关键结构见2。
表2 快捷键配置表的结构
在此表中,存储示例表示使用组合键Ctrl+Alt+F和单键t实现在Tribon中插入文本的功能,其中32982即Tribon插入多行文本命令对应的ID号,对应Tribon二次开发快捷键扩展函数中的Function参数。
扩展程序在完成了按键消息的处理后,必须将快捷键消息“翻译”为特定的操作转发给Tribon,否则Tribon接收到的只有普通的按键消息。查看MSDN可以知道,点击菜单或快捷键会向窗口发送WM_COMMAND消息,故使用PostMessage接口向Tribon窗口发送此消息即可实现按键消息的转发。在实际程序中可通过GetForegroundWindow函数获取前台窗口句柄并简单地通过窗口标题比较检测是否为Tribon窗口。代码如下:
CWnd* hTBWin = GetForegroundWindow ();
CString szWinTitle;
//检测当前窗口是否为Tribon窗口
hTBWin->GetWindowText(szWinTitle);
if(!szWinTitle.GetLength()|| szWinTitle.Left(9).Compare(TEXT("Tribon M3")))
{
szWinTitle.Empty();
return TRUE;
}
hTBWin->PostMessage(WM_COMMAND,Func,0);
其中:GetForegroundWindow用于获取当前窗口句柄,lstrcmp用于判断当前窗口是否为Tribon窗口,PostMessage函数的参数Func为配置表存储的命令ID号。
3 结论
通过扩展程序,可以实现Tribon的任意快捷键个性化定制,可弥补软件界面设计本身的不足,同时也为Tribon终端用户自定义程序界面提供了新的思路。
事实上,加入动态获取菜单ID的模块,上述方法可用于大部分应用程序的快捷键扩展。
[1] 罗云彬.琢石成器—Windows环境下32位汇编语言程序设计[M].北京:电子工业出版社,2009.
[2] 段 钢.加密与解密[M].3版.北京:电子工业出版社,2009.