最近开发过程中需要和BS结合,但是登陆页面是内嵌的一个HTML,需要控制该HTML里面的元素,例如在登陆对话框中需要初始化单位列表,当选择不同的单位时,填充部门列表,同样选择不同的部门初始化用户列表,这些信息都是从数据库中读取的,但是不能配置 ODBC 或者 JDBC 一类的信息,所以只能在程序内部进行控制;所有这一切可以使用 CDHtmlDialog 来进行,该对话框可以从资源或者网络加载一个网页,然后提供内部控制机制;
首先来说明如何对选择框的内容进行填充,参考下面的函数:
填充选择框的函数代码
//----------------------------------------------------------------------------------------------
// 概述:
// 根据 SQL 语句获取记录集并填充指定的下拉框
//
// 参数:
// szIDorName - [IN] 网页上组件的 ID 或者 NAME
// szSQL - [IN] 填充选择列表的数据库查询语句
// szDefault - [IN] 默认选项的显示字符串
//
// 返回:
// 如果匹配到默认项目,那么返回 S_OK , 否则返回 S_FALSE
//----------------------------------------------------------------------------------------------
HRESULT ResetSelectOptions( LPCTSTR szIDorName , LPCSTR szSQL , LPCTSTR szDefault = NULL );
HRESULT CDlgUserLogin::ResetSelectOptions( LPCTSTR szIDorName , LPCSTR szSQL , LPCTSTR szDefault )
{
CXnDataSet dsResult;
GetDefaultEngineRecord( dsResult , szSQL );
LONG nChildren = dsResult.GetRecordNums( );
CComPtr< IHTMLSelectElement > spDispElement;
GetElementInterface( szIDorName , & spDispElement );
CHK_EXP_RET( ! spDispElement , S_FALSE );
LONG iDefault = 0;
spDispElement->put_length( nChildren );
for( LONG iChild = 0 ; iChild < nChildren ; iChild ++ )
{
_variant_t Index = iChild ;
CComPtr< IDispatch > spDispTemp;
CComPtr< IHTMLOptionElement > spDispOption;
spDispElement->item( Index , Index , & spDispTemp );
spDispOption = spDispTemp;
CXnString strTitle , strValue ;
dsResult.GetAt( iChild , 0 , strValue );
dsResult.GetAt( iChild , 1 , strTitle );
CComBSTR bstrTitle( strTitle.GetSafeBuffer( ) );
CComBSTR bstrValue( strValue.GetSafeBuffer( ) );
spDispOption->put_value( bstrValue );
spDispOption->put_text( bstrTitle );
// 如果指定了默认选项,那么设定索引
if( szDefault && strTitle == szDefault )
{
iDefault = iChild ;
}
}
spDispElement->put_selectedIndex( iDefault );
return iDefault == 0 ? S_FALSE : S_OK ;
}
有了该函数后,后面就好办了,关键是在不同的地方加上不同的事件响应代码,这些可以使用 DHTMLDialog 的事件映射宏来定义,如下代码所示:
HTML 事件映射宏定义
BEGIN_DHTML_EVENT_MAP( CDlgUserLogin )
DHTML_EVENT_ONCLICK( TEXT("BTN_SUBMIT") , OnClickLogin )
DHTML_EVENT_ONKEYDOWN( TEXT("EDT_PSWD") , OnEnterLogin )
DHTML_EVENT_ONCHANGE( TEXT("CMB_CORP") , OnChangeCorp )
DHTML_EVENT_ONCHANGE( TEXT("CMB_DEPT") , OnChangeDept )
DHTML_EVENT_ONCHANGE( TEXT("CMB_USER") , OnChangeUser )
END_DHTML_EVENT_MAP( )
这些事件函数全部返回 HRESULT , 并且有一个 IHTMLElement 接口指针,表明是那个页面元素发生的事件,返回 S_OK ,禁用页面中的事件响应代码,如果返回 S_FALSE ,则继续执行网页中对应的响应代码;
DHTML_EVENT_ONCLICK 事件是当鼠标单击一个 HTML 元素时发生的事件,例如单击一个按钮,或者一个图片,在这里我就是使用一个图片来作为按钮的,如果需要鼠标在图片上显示为小手的形状,那么使用风格 style="cursor:hand;" 即可;
DHTML_EVENT_ONKEYDOWN 事件就是在文本或密码输入框中有一个键盘按钮被按下时发生的事件,在这里可能需要对键盘事件进行过滤,所以关键是如何获取键盘事件的按键编码,就是哪一个按键被按下,参考如下代码:
在密码框上按下键盘的代码
HRESULT CDlgUserLogin::OnEnterLogin( IHTMLElement * pElement )
{
// 用户一旦开始输入密码,不再显示错误信息
ShowLoginInfo( TEXT("ERR_PSWD") , FALSE , NULL );
// 判断当前按下的按钮是否为回车
CComPtr< IHTMLWindow2 > spDispWindow;
m_spHtmlDoc->get_parentWindow( & spDispWindow );
if( ! spDispWindow ) return S_FALSE ;
CComPtr< IHTMLEventObj > spDispEvent;
spDispWindow->get_event( & spDispEvent );
if( ! spDispEvent ) return S_FALSE ;
long KeyCode = 0;
spDispEvent->get_keyCode( & KeyCode );
if( KeyCode != VK_RETURN ) return S_OK;
// 执行登陆过程
OnClickLogin( pElement );
return S_FALSE;
}
DHTML_EVENT_ONCHANGE 事件就是选择一个下拉列表框中不同的项目时发生的事件,例如在单位列表发生变化时,我们可以取出单位的编号,然后生成一个SQL语句,然后调用 ResetSelectOptions 函数填充部门列表,在这里可能使用的函数是:
针对下拉列表框的函数
void DDX_DHtml_SelectValue(LPCTSTR szId, CString& value, BOOL bSave);
void DDX_DHtml_SelectString(LPCTSTR szId, CString& value, BOOL bSave);
void DDX_DHtml_SelectIndex(LPCTSTR szId, long& value, BOOL bSave);
上面三个函数分别获取或设置下拉列表框的当前选中项目的值、文本和索引;最后需要注意的是单位的列表必须在一开始就进行初始化,这个初始化工作不能在 InitDialog 中进行,必须在网页加载结束后进行,可以在下列虚函数中进行初始化:
//----------------------------------------------------------------------------------------------
// 概述:
// 网页加载完成后执行的虚函数
//----------------------------------------------------------------------------------------------
virtual void OnDocumentComplete( LPDISPATCH pDispatch , LPCTSTR szUrl );
在该函数中首先调用基类的函数,然后进行单位的初始化即可;
最后当用户输入错误的密码时,需要显示错误信息,错误信息事先已经在网页中定义了,但是风格设置为隐藏:style="color:#EF0000; 160px; display:none; font-size:10pt; font-weight:bold;",在程序中,如果判断用户密码错误,只需要显示该错误信息就可以了,当然当用户重新输入密码时,隐藏该错误信息,显示和隐藏错误信息的代码如下:
显示和隐藏页面元素
HRESULT CDlgUserLogin::ShowLoginInfo( LPCTSTR szIDorName , BOOL bShow , LPCTSTR szText )
{
CComPtr< IHTMLStyle > spDispStyle;
CComPtr< IHTMLElement > spDispElement;
GetElementInterface( szIDorName , & spDispElement );
CHK_EXP_RET( ! spDispElement , S_FALSE );
spDispElement->get_style( & spDispStyle );
CComBSTR strShow( "block" ), strHide( "none" );
spDispStyle->put_display( bShow ? strShow : strHide );
if( szText ) spDispElement->put_innerText( CComBSTR( szText ) );
return S_OK ;
}
使用 SetFocusToElement 可以让网页中的一个元素获取输入焦点,例如密码输入框;
最后如果要禁用网页上的右键菜单和进行文本选择可以在 BODY 中增加属性:OnContextMenu="return false" OnSelectStart="return false"