Internet Explorer 编程简述(十二)正确地设置和转移焦点

原创 2006年05月29日 23:52:00
 
关键字:焦点,Focus,加速键,Accelerator,OLEIVERB_UIACTIVATE,IHTMLWindow2,IHTMLDocument4
 
1、概述
对于99%有UI的Windows应用程序来说,键盘操作都是不可或缺而又容易被人们遗忘的一环。如果对Windows组件作一次逐个的测试,我们会发现Microsoft提供的任何一个Windows组件都通过键盘实现完全的控制(“计算器”比较特殊,它是一个按钮很多且每个按钮都不能获得焦点的程序,但在帮助文档中我们仍然可以找到为每个按钮设置的快捷键),这对于一个专业的Windows应用程序或软件来说非常重要。换句话说,就算没有鼠标用户也不应该束手无策,用户应该可以通过键盘操作完成其希望的功能。焦点的转移无疑是键盘操作的一个重要方面,在浏览器编程中尤其如此。
 
2、焦点的基本概念
一般说来,在Windows中用户通过键盘转移焦点(Focus)有两个方法:第一,对于输入框附近有标签提示的情况,按住Alt+某个预设的字母(Accelerator,加速键)将焦点快速转移到输入框。如下图所示,按下“Alt+D”,焦点应转移到地址输入框;按下“Alt+G”,焦点应转移到搜索框(本文对此不做讨论)。第二,按住Tab键,焦点转移到由应用程序控制的下一个可获得焦点的窗口;按下Shift+Tab,焦点转移到上一个可获得焦点的窗口。如下图所示,如果地址输入框是当前获得焦点的窗口,则按下Tab时,焦点应转移到搜索框,再按下Shift+Tab,焦点应回到地址输入框。
 

 
焦点的设置和转移对于用户体验(Experience)来说是细微体贴而又重要的设计,但不幸的是不少Windows应用程序都或多或少犯了一些错误:
  1. 完全没有加速键。
这在国产信息系统中尤为常见。设计较差的信息系统常常会出现一个窗口拥有数十个输入框的情况,如果为每个编辑框都提供一个加速键的话,问题就出来了。字母键只有26个,就算把数字键也用上,也难免不能满足要求,所以很多信息系统干脆就不要加速键。
  1. 摆设用的加速键。
一些应用软件甚至不懂得加速键的意义,只知道依样画葫地在输入框的旁边用标签说明加速键,但仅此而已,用户根本无法通过Alt+加速键转移焦点到输入框。
  1. 错误地(或不能)转移焦点
对于基于对话框的应用程序来说,常犯的错误是用户按下Tab键时,焦点出乎用户意料地在输入框之间乱窜。而在上图这样的例子中,常犯的错误则是不能通过Tab转移焦点,或者按Tab能转移焦点但按Shift+Tab不能朝反方向转移焦点。
  1. 对嵌入的ActiveX控件缺乏处理
对于嵌入的ActiveX控件,尤其是WebBrowser控件来说,焦点的处理就更为麻烦了(这本是基于WebBrowser的浏览器编程的难题之一)。常见的浏览器要么不处理常规窗口与WebBrowser控件之间的焦点传递(Maxthon、Gosurf只支持在输入框之间传递焦点);要么处理不完整,焦点一旦从某个输入框转移到WebBrowser控件就再也回不来(如GreenBrowser);更有的根本就不处理任何焦点的传递(如世界之窗浏览器)。
 
按照本系列文章的惯例,本文讨论的目的将是提供一个完整(未必完美)的解决方案——:一,焦点在嵌入ReBar的各个输入框之间传递;二,焦点在普通Windows窗口(输入框)与WebBrowser控件之间传递。
 
3、设定目标
下图说明了我们希望实现的正常的焦点转移行为:
  • 从工具条上的任何一个输入框出发,按Tab将焦点转移到下一个输入框,按Shift+Tab将焦点转移到上一个输入框
  • 如果焦点所在输入框是工具条上的最后一个输入框,按Tab将焦点转移到WebBrowser控件当前的活动Html Element(上一次获得焦点的Element)
  • 如果焦点所在输入框是工具条上的第一个输入框,按Shift+Tab将焦点转移到WebBrowser控件当前活动Html Element
  • 对于上面两种情况,若WebBrowser控件没有当前活动的可获得焦点Html Element,则焦点应从输入框转移到WebBrowser控件的第一个或最后一个可获得焦点的Html Element
  • 如果焦点当前位于WebBrowser控件中,按Tab将焦点转移到下一个Html Element,按Shift+Tab将焦点转移到上一个Html Element
  • 如果焦点当前位于WebBrowser控件中,且当前的活动Html Element是最后一个可获得焦点的Html Element,按Tab将焦点转移到工具条的第一个输入框
  • 如果焦点当前位于WebBrowser控件中,且当前的活动Html Element是第一个可获得焦点的Html Element,按Shif+Tab将焦点转移到工具条的最后输入框
 
以下图为例,“Google大全”为WebBrowser当前获得焦点的Html Element,举例如下:
  • 例1:假设当前焦点位于地址输入框,按下Tab键不松开,焦点转移的顺序应是:“地址栏”,“搜索栏”,“Google大全”……“将Google设为首页”,“地址栏”,“搜索栏”,“个性化主页”,“搜索记录”……
  • 例2:假设当前焦点位于地址输入框,且WebBrowser控件没有活动的获得焦点的Html Element,按下Tab键不松开,焦点转移的顺序应是:“地址栏”,“搜索栏”,“个性化主页”,“搜索记录”……“将Google设为首页”,“地址栏”,……
  • 例3:假设当前焦点位于“搜索记录”,按下Shift+Tab键不松开,焦点转移的顺序应是:“搜索记录”,“个性化主页”,“搜索栏”,“地址栏”,“将Google设为首页”……“搜索记录”……
 

 
4、工具条输入框之间的焦点转移
为实现统一的处理,我们从CDialogBar派生一个CDialogBarEx类,由该类处理Tab/Shift Tab按键,而输入框(如EditBox,ComboBox等)则放在CDialogBarEx的派生类(如CUrlAddressBar、CSearchBar等)中,这样输入框就可以专注于其它的功能。示例代码如下:
 
BOOL CDialogBarEx::PreTranslateMessage(MSG* pMsg)
{
if ( ( pMsg->message==WM_KEYDOWN ) )
{
if ( (pMsg->wParam == VK_TAB) )
{
//由MainFrame处理如何转移焦点,按下Shift表示焦点应转移到上一个窗口
g_pMainFrame->SetFocusToNextControl( GetKeyState(VK_SHIFT) >= 0 );
return TRUE;
}
}
......
return CDialogBar::PreTranslateMessage(pMsg);
}
 
void CMainFrame::SetFocusToNextControl(bool bNext)
{
//m_wndReBar是一个CReBarEx,可从CReBar派生
if ( !m_wndReBar.SetFocusToNextControl(bNext) )
{
//如果CReBarEx在其子窗口中找不到下(上)一个可以设置焦点的窗口,则把焦点转移到WebBrowser
CChildFrame *pChildFrame = (CChildFrame *)MDIGetActive();
if ( pChildFrame && pChildFrame->GetActiveView() )
{
pChildFrame->GetActiveView()->SetFocus();
}
}
}
 
bool CReBarEx::SetFocusToNextControl(bool bNext)
{
return bNext ? FocusNextControl() : FocusPrevControl();
}
 
bool CReBarEx::FocusNextControl()
{
REBARBANDINFO rbbi;
rbbi.cbSize = sizeof( rbbi );
rbbi.fMask = RBBIM_CHILD;
 
//先找到当前获得焦点的Band
UINT nBand;
for ( nBand = 0; nBand < m_rbCtrl.GetBandCount(); nBand++ )
{
VERIFY( m_rbCtrl.GetBandInfo(nBand, &rbbi) );
if ( ::IsChild(rbbi.hwndChild, ::GetFocus()) )
{
break;
}
}
 
//如果运行到这里,必定能够找到当前获得焦点的Band
ASSERT(nBand < m_rbCtrl.GetBandCount());
 
for ( nBand = nBand + 1; nBand < m_rbCtrl.GetBandCount(); nBand++ )
{
VERIFY( m_rbCtrl.GetBandInfo(nBand, &rbbi) );
::SetFocus(rbbi.hwndChild);
if ( ::IsChild(rbbi.hwndChild, ::GetFocus()) )
{
//成功找到并设置焦点到下一个窗口
return true;
}
}
//当前获得焦点的窗口已经是ReBarEx中最后一个可获得焦点的窗口
return false;
}
 
bool CReBarEx::FocusPrevControl()
{
//实现与FocusNextControl类似,此处略去
}
 
void CReBarEx::OnSetFocus(CWnd* pOldWnd)
{
//如果此时Shift为按下的状态,表示焦点可能是从WebBrowser的第一个活动Html Element转过来,
//则将焦点转移到最后一个输入框,否则转移到第一个输入框
//SetFocusToLastControl与SetFocusToFirstControl的实现相当简单,略去
return GetKeyState(VK_SHIFT) < 0 ? SetFocusToLastControl() : SetFocusToFirstControl();
}
 
5、焦点从WebBrowser转移到工具条输入框
处理浏览器的按键也曾是嵌入WebBrowser控件的编程难题之一,Delphi对WebBrowser的封装对按键的支持就存在很大问题。在《Programming Internet Explorer》中曾提到的方法是处理MainFrame的PreTranslateMessage,并在其中从WebBrowser的Document查询得到IOleInPlaceActiveObject接口,将按键交给IOleInPlaceActiveObject的TranslateAccelerator成员区处理。查询MSDN我们可以知道,IOleInPlaceActiveObject::TranslateAccelerator被调用时,MSHTML引擎会调用IDocHostUIHandler接口的TranslateAccelerator方法,从而给开发人员一个接口来处理按键。所以对于实现了IDocHostUIHandler接口的应用程序来说,按键处理就非常简单了。
 
//在此处理将焦点从WebBrowser中转移到ReBar上的输入框
HRESULT CMyView::OnTranslateAccelerator(LPMSG lpMsg,const GUID* pguidCmdGroup, DWORD nCmdID)
{
if (lpMsg && lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_TAB)
{
LPDISPATCH lpDispatch = GetHtmlDocument();
CComQIPtr<IHTMLDocument2> pHTMLDoc = lpDispatch;
if ( pHTMLDoc )
{
CComQIPtr<IHTMLElement> pElement;
if ( SUCCEEDED(pHTMLDoc->get_activeElement(&pElement)) && !pElement )
{
//没有任何活动的Html Element,把焦点转移到ReBar
g_pMainFrame->m_wndReBar.SetFocus();
//通知MSHTML不要再继续处理按键
return S_OK;
}
}
}
return S_FALSE;
}
 
6、使WebBrowser获得焦点
使浏览器获得焦点也颇为讲究。我的一篇老文章《TWebBrowser编程简述中》写到有好几种方法可以使WebBrowser获得焦点:IOleObject::DoVerb(OLEIVERB_UIACTIVATE...)、IHTMLWindow2::focus()、IHTMLDocument4::focus()。而实际上这几种方法是有区别的(内部实现我们并不清楚,也不关心)。
  • IOleObject::DoVerb能够将焦点设置到WebBrowser上一次失去焦点时获得焦点的Html Element上。缺点在于如果WebBrowser上次失去焦点时没有任何Html Element获得焦点,则DoVerb并不能保证焦点会转移到WebBrowser中。
  • IHTMLWindow2::focus不管三七二十一,将焦点转移到WebBrowser的开头Html Element。这显然不是我们想要的。
  • 测试的结果,IHTMLDocument4::focus似乎能够满足要求:能够记住WebBrowser上次失去焦点时获得焦点的Html Element;在WebBrowser上次失去焦点时没有任何Html Element获得焦点的情况下,能够焦点转移到开头的Html Element。但事实上并不理想,假如按住Tab键不松开,反复调用IHTMLDocument4::focus多次之后,我们会发现焦点再也到不到WebBrowser中了。
 
有没有完美解决的办法呢?答案当然是Positive的,如下:
void CMyView::OnSetFocus(CWnd* pOldWnd)
{
LPDISPATCH lpDisp = GetHtmlDocument();
CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> pHTMLDoc(lpDisp);
if ( pHTMLDoc )
{
CComQIPtr<IHTMLElement> pElement;
if ( SUCCEEDED(pHTMLDoc->get_activeElement(&pElement)) && !pElement )
{
//没有任何活动元素,把焦点转移到WebBrowser的开头
CComQIPtr<IHTMLWindow2> pHTMLWnd;
if( SUCCEEDED(pHTMLDoc->get_parentWindow( &pHTMLWnd )) && pHTMLWnd )
{
pHTMLWnd->focus();
return;
}
}
}
 
//有活动的元素(上一次的焦点),直接将焦点转移过去
//CWnd::SetFocus()会调用IOleObject::DoVerb()正确地设置焦点
m_wndBrowser.SetFocus();
}
 
7、总结
至此,我们就算完整地实现了焦点在普通窗口和浏览器之间的传递,任何时候,按住Tab键不松开,焦点将会在所有可获得焦点的窗口之间循环传递;同样,按住Shift-Tab不松开,焦点会以反方向传递。而不会出现用户无法将焦点转移到浏览器窗口的情况,或者焦点无法从浏览器窗口转移到输入框的情况。当然,还有比较重要也比较抽象的一点,增强了用户体验,呵呵。
 
8、参考资料
《Programming Internet Explorer》
 
引用地址Internet Explorer 编程简述(十二)正确地设置和转移焦点
 

如何关闭Internet Explorer 增强的安全配置

什么是Internet Explorer 增强的安全配置?简单的说就是在你访问一个未标记为信任的网站的时候给你弹出一个提示,如下图:如果你认为这个网站是可信任的,就点击“添加”按钮,把它添加到信任区,...
  • testcs_dn
  • testcs_dn
  • 2015年10月26日 17:30
  • 18552

Internet Explorer 已限制此网页运行脚本或者ActiveX控件

To help protect your security, Internet explorer has restricted this web page from running scripts o...
  • kangkanglou
  • kangkanglou
  • 2016年12月13日 19:30
  • 1791

Windows 8 下Internet Explorer 10(IE10含Metro) 无法打开(闪退)或需要管理员权限打开

喜欢折腾电脑,这不今天又中着,好端端的IE10突然打不开了,Metro下都打不开,就像iOS下的闪退,图标在任务栏亮了一下,然后就没了。以为是之前安装什么国产软件修改了IE的快捷方式,但是看了一下一切...
  • ituff
  • ituff
  • 2013年02月28日 20:09
  • 12851

Internet Explorer 已不再尝试还原此网站。该网站看上去仍有问题。

近日浏览163新闻网时,发现打开一个链接后很快就会提示“Internet Explorer 已不再尝试还原此网站。该网站看上去仍有问题。”,如下图: 这个有点奇怪,打开其它网站比如csdn就没有问题...
  • jszj
  • jszj
  • 2017年07月01日 09:10
  • 1068

IE的Internet选项中,下方提示“某些设置由系统管理员管理”的解决方法

这里以我的Windows 7 x64操作系统举例,英文版的系统,大家对应自己的操作就好 我会给出相应的说明。 首先看看你出现提示的内容是什么,比如: 比如我这个,...
  • maxsky
  • maxsky
  • 2013年04月20日 19:15
  • 5638

为了有利于保护安全性,Internet Explorer 已限制此网页运行可以访问计算机的脚本或 ActiveX控件。请单击这里获取选项

在dreamweaver的“代码”顶端你会发现并找到 http://www.w3.org/1999/xhtml" lang="en"> 这个后,在dtd">...
  • ken2002
  • ken2002
  • 2015年01月24日 20:25
  • 1884

Internet Explorer 9、10、11兼容性分析处理经验谈

新业务流程平台终于上线了,虽然通过Bootstrap3做了浏览器兼容,并经过测试、验证,但是实用起来还是有些问题,分析及处理过程如下...
  • xiaoyw
  • xiaoyw
  • 2016年01月30日 23:19
  • 2524

Internet Explorer无法打开Internet 站点的原因

一、网络设置的问题  这种原因比较多出现在需要手动指定IP、网关、DNS服务器联网方式下,及使用代理服务器上网的。仔细检查计算机的网络设置。 二、DNS服务器的问题  当IE无法浏览网页...
  • xwnxwn
  • xwnxwn
  • 2013年04月20日 15:48
  • 529

Internet Explorer 升级到IE11遇到问题案例分析

使用升级到的Internet Explorer 11访问应用界面,在F12开发人员工具中跟踪到如下错误:“SCRIPT1629:提供的数据类型不对”。...
  • xiaoyw
  • xiaoyw
  • 2016年02月27日 21:27
  • 2894

IIS6.0配置正常,但是显示“网页无法访问”,Httperr.log中显示全是“Connections_refused”,问题总结

最近部门的Web服务器突然无法访问! 加班解决! 问题症状: 1、“Internet Explorer 无法显示该网页" 2、网站各项配置正常,昨天还能好好访问的 问题究竟在哪里呢? 通过...
  • foxeatapple
  • foxeatapple
  • 2014年03月24日 20:14
  • 4905
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Internet Explorer 编程简述(十二)正确地设置和转移焦点
举报原因:
原因补充:

(最多只允许输入30个字)