C# WinForm WebBrowser (四) 自定义操作【转】

————————————————————————————————————————————————————————————————
  由于我在支付中通常要在程序中放到浏览器,为了契合自己的需求经常要对浏览器举行扩大和定制,
解决那个题材需在网上找材料和读书的进程,我想可能过多开发者或许会遇见同样的题材,特写此文,以供大家参考。 

在MFC中使用浏览器

  在MFC中微软为我们提供了CHtmlView、CDHtmlDialog类让大家的次序很有利的放手浏览器和展开浏览器的二次开发,这比直接使用WebBrowser控件要有利于广大,所以本文中研究的浏览器的题材都是针对CHtmlView来探究的。文师长波及一个类CLhpHtmlView,
它是CHtmlView的派生类,文中提及的壮大或定制都将在CLhpHtmlView类(或派生类)上实现。

什么样扩展或定制浏览器

  浏览器定义了有的扩大接口(如IDocHostUIHandler可以定制浏览器界面有关的所作所为),以便开发者举办定制和扩展。浏览
器会在需要的时候向她的决定站点查询这个接口,在决定站点里实现相应的接口就能够展开对应的扩张。在MFC7.01类
库中,CHtmlView使用的操纵站点是CHtmlControlSite的,在CHtmlControlSite类中
只兑现了接口IDocHostUIHandler,
而要实现更多的恢弘接口,必须用自定义的决定站类来替代CHtmlControlSite,在下文中提及的类CDocHostSite即为自定义
的控制站类。 

有关接口的牵线请参考:

http://dev.csdn.net/develop/article/48/48483.shtm 

何以使自定义的操纵站点来替换默认的操纵站点呢?在MFC7.0中只需重载CHtmlView的虚函数CreateControlSite即可: 

BOOL CLhpHtmlView::CreateControlSite(COleControlContainer * pContainer, 
COleControlSite ** ppSite, UINT /*nID*/, REFCLSID /*clsid*/)
{
    *ppSite = new CDocHostSite(pContainer, this);// 创建自己的控制站点实例
    return (*ppSite) ? TRUE : FALSE;
}

VC6.0要替换控制站要复杂的多,这里就不研商了,如需要6.0版本的请给我发邮件到yourshine@21cn.com。

定制鼠标右键弹出出菜单

  要定制浏览器的鼠标右键弹出菜谱,必须在自定义的主宰站点类中实现IDocHostUIHandler2接口,并且IE的
版本是5.5或上述。在接口IDocHostUIHandler2的ShowContextMenu方法中调用浏览器类的OnShowContextMenu虚函数,我们在浏览器类的派生类重载此虚函数即可兑现右键菜单的定制,参见代码 

HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID,
                                                         POINT * ppt,
                                                         IUnknown * pcmdtReserved,
                                                         IDispatch * pdispReserved)
{
    METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
    return pThis->m_pView->OnShowContextMenu( dwID, ppt, pcmdtReserved,pdispReserved );
}

HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID, 
                                        LPPOINT ppt,
                                        LPUNKNOWN pcmdtReserved, 
                                        LPDISPATCH pdispReserved)
{
    HRESULT result = S_FALSE;

    switch(m_ContextMenuMode)
    {
    case NoContextMenu:         // 无菜单
        result=S_OK;
        break;
    case DefaultMenu:               // 默认菜单
        break;
    case TextSelectionOnly:         // 仅文本选择菜单
        if(!(dwID == CONTEXT_MENU_TEXTSELECT || dwID == CONTEXT_MENU_CONTROL))
            result=S_OK;
        break;
    case CustomMenu:                // 自定义菜单
        if(dwID!=CONTEXT_MENU_TEXTSELECT)
            result=OnShowCustomContextMenu(ppt,pcmdtReserved,pdispReserved);
        break;
    }

    return result;
}       

在CLhpHtmlView中定义的枚举类型CONTEXT_MENU_MODE举出了定制右键弹出菜谱的四种档次 

enum CONTEXT_MENU_MODE     // 上下文菜单
{
    NoContextMenu,      // 无菜单
    DefaultMenu,        // 默认菜单
    TextSelectionOnly,      // 仅文本选择菜单
    CustomMenu      // 自定义菜单
};

由此CLhpHtmlView的函数SetContextMenuMode来设置右键菜单的连串。假设设定的右键弹出菜谱是“自定义菜单”类型,
我们假设在CLhpHtmlView的派生类中重载OnShowCustomContextMenu虚函数即可,如下代码
CDemoView是CLhpHtmlView的派生类

HRESULT CDemoView::OnShowCustomContextMenu(LPPOINT ppt, LPUNKNOWN pcmdtReserved,LPDISPATCH pdispReserved)
{
    if ((ppt==NULL)||(pcmdtReserved==NULL)||(pcmdtReserved==NULL))
        return S_OK;

    HRESULT hr=0;
    IOleWindow *oleWnd=NULL;
        hr=pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
    if((hr != S_OK)||(oleWnd == NULL))
        return S_OK;

    HWND hwnd=NULL;
    hr=oleWnd->GetWindow(&hwnd);
    if((hr!=S_OK)||(hwnd==NULL))
    {
        oleWnd->Release();
        return S_OK;
    }

    IHTMLElementPtr pElem=NULL;
    hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)&pElem);
    if(hr != S_OK)
    {
        oleWnd->Release();
        return S_OK;
    }

    IHTMLElementPtr pParentElem=NULL;

    _bstr_t tagID;
    BOOL go=TRUE;

    pElem->get_id(&tagID.GetBSTR());

    while(go && tagID.length()==0)
    {
        hr=pElem->get_parentElement(&pParentElem);
        if(hr==S_OK && pParentElem!=NULL)
        {
            pElem->Release();
            pElem=pParentElem;
            pElem->get_id(&tagID.GetBSTR());
        }
        else
            go=FALSE;
    };
    if(tagID.length()==0)
        tagID="no id";

    CMenu Menu,SubMenu;
    Menu.CreatePopupMenu();

    CString strTagID = ToStr(tagID);

    if(strTagID == "red")
        Menu.AppendMenu(MF_BYPOSITION, ID_RED, "您点击的是红色");
    else if(strTagID == "green")
        Menu.AppendMenu(MF_BYPOSITION, ID_GREEN, "您点击的是绿色");
    else if(strTagID == "blue")
        Menu.AppendMenu(MF_BYPOSITION, ID_BLUE, "您点击的是蓝色");
    else
        Menu.AppendMenu(MF_BYPOSITION, ID_NONE, "你点了也白点,请在指定的地方点击");

    int MenuID=Menu.TrackPopupMenu(TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,ppt->x, ppt->y, this);
    switch(MenuID)
    {
    case ID_RED:
        MessageBox("红色");
        break;
    case ID_GREEN:
        MessageBox("红色");
        break;
    case ID_BLUE:
        MessageBox("红色");
        break;
    case ID_NONE:
        MessageBox("haha");
        break;
    }

    oleWnd->Release();
    pElem->Release();

    return S_OK;
}       


实现脚本扩大(很要紧的external接口)

  在您置于了浏览器的工程中,假如网页的脚本中能调用C++代码,那将是一件很满足的政工,要实现那种相互,就务须贯彻脚本扩大。实现脚本扩充就是在先后中实现一个IDispatch接口,通过CHtmlView类的OnGetExternal虚函数重临此接口指针,这样就可以在本子中经过window.external.XXX(关键字window可以简简单单)来
引用接口表露的点子或性质(XXX为格局或属性名)。在MFC中从CCmdTarget派生的类都足以实现自动化,而不要在MFC工程中引入繁杂的ATL。从CCmdTarget派生的类实现自动化接口的时候绝不忘了在构造函数中调用EnableAutomation函数。
  要使虚函数OnGetExternal发挥效率必须在
自定义的决定站点类中落实IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中调用浏览器类的OnGetExternal虚函数,我们在浏览器类的派生类重载OnGetExternal虚函数,
通过参数lppDispatch重临一个IDispatch指针,这样脚本中引用window.external时就是援引的归来的接口,参见代码

HRESULT CDocHostSite::XDocHostUIHandler::GetExternal(IDispatch ** ppDispatch)
{
    METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
    return pThis->m_pView->OnGetExternal( ppDispatch );
}

CLhpHtmlView::CLhpHtmlView(BOOL isview)
{
    ......
    EnableAutomation();// 允许自动化
}

HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch)
{
    *lppDispatch = GetIDispatch(TRUE);// 返回自身的IDispatch接口
    return S_OK;
}      

请小心下边代码中,在OnGetExternal重临的是自己IDispatch接口,
这样就不比为脚本增添而除此以外写一个从CCmdTarget派生的新类,
CLhpHtmlView本身就是从CCmdTarget派生,直接在地方实现接口就是。 

下用具体示例来证实怎么着实现脚本扩张 

示例会在网页上点击一个按钮而使整个窗口发生共振

从CLhpHtmlView派生一个类CDemoView,在类中贯彻IDispatch,
并通过IDispatch暴露方法WobbleWnd

---------------------------------------------------------------------------
文件 DemoView.h
---------------------------------------------------------------------------
.......
class CDemoView : public CLhpHtmlView
{
    ......
    DECLARE_DISPATCH_MAP() // 构建dispatch映射表以暴露方法或属性
    ......
    void WobbleWnd();// 抖动窗口
};

---------------------------------------------------------------------------
文件 DemoView.cpp
---------------------------------------------------------------------------

......

// 把成员函数映射到Dispatch映射表中,暴露方法给脚本
BEGIN_DISPATCH_MAP(CDemoView, CLhpHtmlView)
    DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

......

void CDemoView::WobbleWnd()
{
    // 在这里实现抖动窗口
    ......
}

---------------------------------------------------------------------------
文件 Demo.htm
---------------------------------------------------------------------------

...... onclick="external.WobbleWnd()" ......        

此间我要介绍一下DISP_FUNCTION宏,它的功力是将一个函数映射到Dispatch映射表中,我们看

DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)

CDemoView是宿主类名,
“WobbleWnd”是表露给外界的名字(脚本调用时使用的名字),
VT_EMPTY是回去值得类型为空,VTS_NONE表达此办法没有参数,固然要映射的函数有再次来到值和参数该
如何映射,通过下边举例来表达 

DISP_FUNCTION(CCalendarView,"TestFunc",TestFunc,VT_BOOL,VTS_BSTR VTS_I4 VTS_I4)

BOOL TestFunc(LPCSTR param1, int param2, int param3)
{
    .....
}       

参数表VTS_BSTR VTS_I4
VTS_I4是用空格分隔,他们的档次映射请参考MSDN,这要指示的是无须把VTS_BSTR与CString对应,而应与LPCSTR对应。

C++代码中哪些调用网页脚本中的函数

  IHTMLDocument2::scripts属性表示HTML文档中具备脚本对象。使用脚本对象的IDispatch接口的GetIDsOfNames方法可以取得脚本函数的
DispID,得到DispID后,使用IDispatch的Invoke函数可以调用对应的脚本函数。CLhpHtmlView提供了福利的调用JavaScript的函数,请参考CLhpHtmlView中关于键字“JScript”的代码。

定制消息框的题目

  我们在本子中调用alert弹出音信框时,信息框的题目是微软预定义的“Microsoft
Internet Explorer”,如下图:

图片 1

  在自定义的控制站点类中落实IDocHostShowUI接口,在接口的ShowMessage方法中调用浏览器的OnShowMessage,我们重载
OnShowMessage虚函数即可定制音信框的标题,实现代码如下: 

// 窗口标题"Microsoft Internet Explorer"的资源标识
#define IDS_MESSAGE_BOX_TITLE 2213
HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,
                                    LPOLESTR lpstrText,
                                    LPOLESTR lpstrCaption,
                                    DWORD dwType,
                                    LPOLESTR lpstrHelpFile,
                                    DWORD dwHelpContext,
                                    LRESULT * plResult)
{
    //载入Shdoclc.dll 和IE消息框标题字符串
    HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
    if (hinstSHDOCLC == NULL)
        return S_FALSE;

    CString strBuf,strCaption(lpstrCaption);
    strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);

    // 比较IE消息框标题字符串和lpstrCaption
    // 如果相同,用自定义标题替换
    if(strBuf==lpstrCaption)
        strCaption = m_DefaultMsgBoxTitle;

    // 创建自己的消息框并且显示
    *plResult = MessageBox(CString(lpstrText), strCaption, dwType);

    //卸载Shdoclc.dll并且返回
    FreeLibrary(hinstSHDOCLC);
    return S_OK;
}       

从代码中可以看看通过设定m_DefaultMsgBoxTitle的值来改变信息宽的题目,修改此值是同过SetDefaultMsgBoxTitle来兑现

void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle)
{
    m_DefaultMsgBoxTitle=strTitle;
}


什么样定制、修改浏览器向Web服务器发送的HTTP请求头

  在合龙了WebBrowser控件的采用中,Web服务器有时可能希望客户端(浏览器)发送的HTTP请求中附带一些外加的音讯或自定义的
HTTP头字段,这样就务须在浏览器中控制向Web服务器发送的HTTP请求。
下边是捕获的一个常备的用浏览器发送的HTTP请求头:

GET /text7.htm HTTP/1.0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, \
application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Poco 0.31; LHP Browser 1.01; \
.NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive

CHtmlView的 
void Navigate2(
   LPCTSTR lpszURL,
   DWORD dwFlags = 0,
   LPCTSTR lpszTargetFrameName = NULL,
   LPCTSTR lpszHeaders = NULL,
   LPVOID lpvPostData = NULL,
   DWORD dwPostDataLen = 0 
);

函数参数lpszHeaders可以指定HTTP请求头,示例如下:

Navigate2(_T("http://localhost"),NULL,NULL, "MyDefineField: TestValue");

我们捕获的HTTP头如下: 

图片 2

**

什么样修改浏览器标识**

  在HTTP请求头中User-Agent字段讲明了浏览器的版本以及操作系统的本子等信息。WEB服务器通常索要理解用户请求页面时是发源IE仍旧来自自己的客户端中的WebBrowser控件,
以便分开处理,而WebBrowser控件向WEB服务器发送的浏览器标识(User-Agent字段)跟用IE发送的是一律的,怎么着区分自己的浏览器和IE呢?
微软尚无提供现成的章程,要团结想法解决。
前边议论的定制HTTP请求头就是为这一节准备的。 思路是这样的:
在大团结的浏览器里处理每一个U页面请求,把请求头User-Agent改成自己想要的。
在CHtmlView的OnBeforeNavigate2虚函数里来修改HTTP请求是再好不过了,

#define WM_NVTO        (WM_USER+1000)

class NvToParam
{
public:
    CString URL;
    DWORD Flags;
    CString TargetFrameName;
    CByteArray PostedData;
    CString Headers;
};


void CDemoView::OnBeforeNavigate2(LPCTSTR lpszURL, 
                                  DWORD nFlags, 
                                  LPCTSTR lpszTargetFrameName, 
                                  CByteArray& baPostedData, 
                                  LPCTSTR lpszHeaders, 
                                  BOOL* pbCancel)
{
    CString strHeaders(lpszHeaders);
    if(strHeaders.Find("User-Agent:LHPBrowser 1.0") < 0)// 检查头里有没有自定义的User-Agent串
    {
        *pbCancel = TRUE;// 没有,取消这次导航

        if(!strHeaders.IsEmpty())
            strHeaders += "\r\n";
        strHeaders += "User-Agent:LHPBrowser 1.0";// 加上自定义的User-Agent串

        NvToParam* pNvTo = new NvToParam;
        pNvTo->URL = lpszURL;
        pNvTo->Flags = nFlags;
        pNvTo->TargetFrameName = lpszTargetFrameName;
        baPostedData.Copy(pNvTo->PostedData);
        pNvTo->Headers = strHeaders;

        // 发送一个自定义的导航消息,并把参数发过去
        PostMessage(WM_NVTO,(WPARAM)pNvTo);
        return;
    }

    CHtmlView::OnBeforeNavigate2(lpszURL, 
                                 nFlags, 
                                 lpszTargetFrameName, 
                                 baPostedData, 
                                 lpszHeaders, 
                                 pbCancel);
}

LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam)
{
    NvToParam* pNvTo = (NvToParam*)wParam;
    Navigate2((LPCTSTR)pNvTo->URL, 
               pNvTo->Flags, 
               pNvTo->PostedData, 
               (LPCTSTR)pNvTo->TargetFrameName, 
               (LPCTSTR)pNvTo->Headers);
    delete pNvTo;
    return 1;
}

在OnBeforeNavigate2中只要发现并未自定义的User-Agent串,就增长这多少个串,并撤除本次导航,再Post一个音信(一定要POST,让OnBeforeNavigate2跳出未来再拓展导航
),在音信中重复导航,再一次导航时请求头已经有了上下一心的标识,所以能健康的领航。 

去掉讨厌的要命警告 

  在先后中接纳了CHtmlView未来,我们在调整窗口大小的时候通常会看出输出窗口输出的异常警告:
ReusingBrowser.exe 中的 0x77e53887 处最可能的特别: Microsoft C++
exception: COleException @ 0x0012e348 。

Warning: constructing COleException, scode = DISP_E_MEMBERNOTFOUND($80020003).

这是由于CHtmlView在处理WM_SIZE音讯时的一点小问题引起的,拔取如下代码处理WM_SIZE音信就不会有此警告了

void CLhpHtmlView::OnSize(UINT nType, int cx, int cy)
{
    CFormView::OnSize(nType, cx, cy);

    if (::IsWindow(m_wndBrowser.m_hWnd)) 
    { 
        CRect rect; 
        GetClientRect(rect); 
        // 就这一句与CHtmlView的不同
        ::AdjustWindowRectEx(rect, GetStyle(), FALSE, WS_EX_CLIENTEDGE);
        m_wndBrowser.SetWindowPos(NULL, 
                                  rect.left, 
                                  rect.top, 
                                  rect.Width(), 
                                  rect.Height(), 
                                  SWP_NOACTIVATE | SWP_NOZORDER); 
    } 
}


哪些处理浏览器内的拖放
 

  有时可能有这般的急需,我们意在在资源管理器里托一个文件到浏览器而做出相应的处理,甚至是将文件拖到某一个网页元素上来做出相应的拍卖,而浏览器默认的处理拖放文件操作是将文件打开,但WebBrowser控件给了我们一个投机处理拖放的机会。
那就是在自定义的主宰站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中调用
浏览器类的OnGetDropTarget虚函数。要拍卖网页内的拖放,必需在OnGetDropTarget函数中回到一个温馨定义的IDropTarget接口指针,
所以我们和好写一个类CMyOleDropTarget从COleDropTarget类派生,并且在促成IDropTarget接口,此类的代码在这就不列出了,请下载演示
程序,参考文件MyOleDropTarget.h和MyOleDropTarget.cpp。咱们看CLhpHtmlView中OnGetDropTarget的代码

HRESULT CLhpHtmlView::OnGetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget )
{
    m_DropTarget.SetIEDropTarget(pDropTarget);

    LPDROPTARGET pMyDropTarget;
    pMyDropTarget = (LPDROPTARGET)m_DropTarget.GetInterface(&IID_IDropTarget);
    if(pMyDropTarget)
    {
        *ppDropTarget = pMyDropTarget;
        pMyDropTarget->AddRef();
        return S_OK;
    }

    return S_FALSE;
}

m_DropTarget即为自定义的处理拖放的靶子。这样就能通过在从CLhpHtmlView派生的类中重载OnDragEnter、OnDragOver、
OnDrop、OnDragLeave虚函数来处理拖放了。在此地顺带讲一下视图是怎么处理拖放的。
要使视图处理拖放,首先在视图里添加一个COleDropTarget(或派生类)成员变量,如CLhpHtmlView中的“CMyOleDropTarget
m_DropTarget;”,再在
视图制造时调用COleDropTarget对象的Register,即把视图与COleDropTarget对象关系起来,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再对拖放
触发的风波开展相应的拍卖, OnDragEnter
把某目的拖入到视图时接触,在此检测拖入的对象是不是视图想接受的对象,如是再次回到“DROPEFFECT_MOVE”表示接受此目的,如

if(pDataObject->IsDataAvailable(CF_HDROP))// 被拖对象是文件吗?
    return DROPEFFECT_MOVE;

OnDragOver
被拖对象在视图上移步,同OnDragEnter一样检测拖入对象,即使要经受此目的回来“DROPEFFECT_MOVE”。
OnDrop 拖着被拖对象在视图上拓宽鼠标,在此间对拖入对象做出处理;
OnDragLeave 拖着被拖对象离开视图。
C++的代码写好了,但工作还没完,还非得在网页里用剧本对拖放事件举办拍卖,
即页面里哪个元素要接受拖放对象哪个元素就要处理ondragenter、ondragover、ondrop,代码其实很粗略,让事件的重返值为false即可,这样
C++的代码才有机会处理拖放事件,代码如下:

......
<td ondragenter="event.returnValue = false" ondragover="event.returnValue = false" \
ondrop="event.returnValue = false">
......

如果要使整个视图都接受拖放,则在Body元素中处理此两个事件。
注意:别忘了让工程对OLE的帮忙即在起首化应用程序时调用AfxOleInit()。

**怎么样禁止网页元素的取舍

**  用网页做界面时多数情状下是不期望网页上的要素是可以被鼠标选中的,
要使网页元素不可以被选中做法是:给浏览器的“宿主信息符号”加上DOCHOSTUIFLAG_DIALOG标记。

“宿主信息符号”用N个标记位来决定浏览器的不少属性,如:

  • 剥夺浏览器的3D的边缘;
  • 取缔滚动条;
  • 剥夺脚本;
  • 概念双击处理的法门;
  • 剥夺浏览器的机动完成效率;

…… 更多详情请参考MSDN的DOCHOSTUIFLAG援救。

何以修改“宿主消息符号”?

在CDocHostSite中实现IDocHostUIHandler,
在GetHostInfo方法中调用浏览器的OnGetHostInfo虚函数,在虚函数OnGetHostInfo中便可指定“宿主信息符号”,如:

HRESULT CLhpHtmlView::OnGetHostInfo(DOCHOSTUIINFO * pInfo)
{
    pInfo->cbSize = sizeof(DOCHOSTUIINFO);
    pInfo->dwFlags = DOCHOSTUIFLAG_DIALOG | 
                        DOCHOSTUIFLAG_THEME  | 
                        DOCHOSTUIFLAG_NO3DBORDER | 
                        DOCHOSTUIFLAG_SCROLL_NO;
    pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;

    return S_OK;
}

用剧本也可实现: 在Head中参与脚本:

document.onselectstart=new Function(''return false'');

或者

<body onselectstart="return false">。


其它
 

在CLhpHtmlView中还提供了几个函数, 修改网页元素的内容:

BOOL PutElementHtml(CString ElemID,CString Html);

取表单元素的值:

BOOL GetElementValue(CString ElemID,CString& Value);

设置表单元素的值:

BOOL PutElementValue(CString ElemID,CString Value);

给表单元素设置要点:

void ElementSetFocus(CString EleName);

转载:http://www.vckbase.com/document/viewdoc/?id=1486

————————————————————————————————————————————————————————————————

自定义浏览器本教程提供了自定义浏览器控件的行为和外观的一些方法。你将看到高级的宿主接口,IDocHostUIHandler, IDocHostUIHandler2, IDocHostShowUI, 和ICustomDoc。本文也讨论其他自定义方法,例如在宿主的IDispatch实现中处理DISPID_AMBIENT_DLCONTROL来进行下载控制;以及使用IHostDialogHelper。本文分为如下章节前提和需求介绍浏览器自定义架构IDocHostUIHandlerIDocHostUIHandler2GetOptionKeyPath 和 GetOverrideKeyPath的比较控制导航IDocHostShowUI控制下载和执行IHostDialogHelper控制新的窗口显示证书对话框(New!) 信息栏(New!)结论前提和需求为了理解和使用本教程,你需要对C++和COM的深入了解
熟悉活动模板库 (ATL)
安装了Microsoft(R) Internet Explorer (IE)6 或更高版本
开发环境具有用于IE6或更高版本的头文件和库文件;特别是Mshtmhst.h.(译者注:可以在http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ 这里下载最新的Internet Development SDK)许多自定义特性是在IE5或者5.5版本就可以使用的,但是有几个特性需要IE6。一些特性需要IE6的Windows XP SP2版本。使用某个特性之前,应该检查参考文档以获得版本信息。介绍集成浏览器控件是快速软件开发的强有力的工具。通过成为浏览器的宿主,你可以利用便于使用的Dynamic HTML (DHTML), HTML, 和Extensible Markup Language (XML)来显示信息和开发一个用户界面。但是,浏览器控件的行为可能不确切符合你的需求。例如,默认的状态允许用户通过快捷菜单的查看源代码选项查看一个显示的页面的源代码,你可能需要禁用或者干脆去掉这个选项。你可能更进一步,需要用你自己的快捷菜单替换默认的快捷菜单。在刚刚提到的自定义特性之外,高级宿主特性允许在显示的页面上的按钮和其他控件可以调用你的应用程序的内建方法,有效地扩展DHTML对象模型(DOM)。改变拖放的行为限制浏览器的导航,例如,限制于指定的页面/域,或者站点捕获用户键入,并且在需要的时候处理。比如说,你可能需要捕获CTRL+O来阻止用户在新的IE中打开网页而不是使用你的程序打开,改变默认字体和显示设置控制下载内容,以及当下载完成之后浏览器的处理。例如,你可能禁用视频的播放,脚本的执行,点击链接时打开新的窗口,或者Microsoft(R) ActiveX 控件的下载和执行。限制查看源代码捕获搜索捕获导航错误替代/修改快捷菜单或者禁用,替代,自定义,或者添加快捷菜单项为你的应用程序改变注册表设定控制和修改浏览器控件显示的消息框控制新窗口的创建方式在下列节中,我们将会看到多数,但是不是全部的这些可能性而且讨论该如何实现他们。浏览器自定义架构介绍 IDocHostUIHandler , IDocHostUIHander2 , IDocHostShowUI 和 ICustomDoc下面三个接口是浏览器控件用户界面的自定义的核心:IDocHostUIHandler ,IDocHostUIHandler2 和 IDocHostShowUI。当你修改浏览器控件的时候 , 这些是你在你的应用程序中实现的接口。也有一些服务接口。 ICustomDoc 被MSHTML实现并且提供一个方法在某些情况下能够自定义浏览器控件。IHostDialogHelper提供一个方法打开可信的对话框,没有像IE对话框那样为他们(译者注:在标题栏上)作标记。除了使用这些接口,你还可以做其他两件事。其一,你能通过在IDispatch实现中拦截环境特性的变化来控制下载;其次,你能通过在IDispatch实现中拦截DISPID_NEWWINDOW2来控制窗口的创建方式。译者注:MFC7中的DHTML类,例如CHtmlView和CDHtmlDialog实现了这些接口,但是对于使用其他的类库的程序员,可能需要自己实现这些接口。如何工作当一个容器提供对ActiveX 控件支持的时候 , 浏览器控件自定义机制被设计为被自动化。当浏览器控件被实例化的时候,如果可能的话,它尝试找来自宿主的 IDocHostUIHandler , IDocHostUIHandler2 和 IDocHostShowUI 实现。浏览器控件通过调用宿主的IOleClientSite接口的一个QueryInterface方法来查找。译者注:IE5.5有个Bug,没有查询IDocHostUIHandler2 接口的实现,这使得宿主程序不能覆盖默认的参数。需要更多信息的话,参考微软知识库文章 Q272968 BUG:IDocHostUIHandler2 没有在浏览器控件中调用。
这一个结构为一个实现一个IOleClientSite接口的应用程序自动地工作,通过调用浏览器的IOleObject::SetClientSite方法传递给浏览器控件一个IOleClientSite接口。浏览器控件的一个典型的实例化可能看起来像这样:例子//为了明确起见,省略错误检查CComPtr<IOleObject> spOleObj;//创建 WebBrowser--在类成员变量 m_spWebBrowser中保存指针CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC, IID_IWebBrowser2, (void**)&m_spWebBrowser);// 查询WebBrowser的IOleObject接口m_spWebBrowser->QueryInterface(IID_IOleObject, (void**)&spOleObj);//设置用户站点spOleObj->SetClientSite(this);//本地激活浏览器控件RECT rcClientGetClientRect(&rcClient);spOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, GetTopLevelWindow(), &rcClient);//容器拦截浏览器事件的注册AtlAdvise(m_spWebBrowser,GetUnknown(), DIID_DWebBrowserEvents2,&m_dwCookie);//导航到启动页m_spWebBrowser->Navigate(L"res://webhost.exe/startpage.htm", NULL, NULL, NULL, NULL);然而,如果你的应用程序没有一个IOleClientSite接口,你并没失去全部希望。IE提供ICustomDoc接口,这样你能自己传递你的IDocHostUIHandler接口给浏览器。你不能使用IDocHostUIHandler2和 IDocHostShowUI接口而不提供一个浏览器控件宿主的IOleClientSite接口。译者注:MFC7中引入的类COleControlContainer和一大堆DHTML类曾经搞得我晕头转向,最后我不得不放弃了自己对IOleClientSite的实现,而通过ICustomDoc来显式地设置IDocHostUIHandler接口。这样必须在第一个页面下载完成之后才能够开始自定义浏览器,因为暴露ICustomDoc接口的对象只有在第一个页面下载完成之后才可用。一个ICustomDoc的示例可以在CSDN文档中心找到,网址是http://www.csdn.net/develop/Read_Article.asp?Id=8813当浏览器控件获得了对这些接口之中的任何一个的一个指针的时候,接口的方法在适当的时候在浏览器控件的生命期中被调用。举例来说, 当用户右击在浏览器控件的客户区的任何地点时,在IE显示它的默认快捷菜单之前,你的IDocHostUIHandler::ShowContextMenu的实现将会被调用。这给你一个机会显示你自己的快捷菜单而且取消IE的快捷菜单显示。译者注:一些屏蔽快捷菜单的示例可以在CSDN文档中心找到,网址是http://www.csdn.net/develop/article/18/18541.shtm当初始化浏览器控件的时候 ,记住几个重点。你的应用程序应该使用 OleInitialize而不是CoInitialize启动COM。OleInitialize启用剪贴簿支持,拖放,对象连接与嵌入(OLE)和本地激活。当你的应用程序结束的时候使用OleUninitialize关闭COM库。ATL COM 向导使用 CoInitialize而不是OleInitialize打开COM库。 如果你使用这一个向导建立一个可运行的程序,你需要将 CoInitialize 和 CoUninitialize 调用换成 OleInitialize 和 OleUninitialize。对于一个微软基础类 (MFC) 应用程序, 确定你的应用程序调用 AfxOleInit, 它在它的初始化程序中调用OleInitialize。如果你不需要在你的应用程序中支持拖放,你可以调用IWebBrowser2::RegisterAsDropTarget,传递VARIANT_TRUE(译者注:原文如此,按照接口的文档,似乎应该传递VARIANT_FALSE), 避免任何在你的浏览器控件实例上的拖放操作。一个浏览器控件宿主应用程序也需要IOleInPlaceSite的一个实现, 由于 IOleInPlaceSite派生自IOleWindow,应用程序将需要IOleWindow的一个实现。你需要这些实现使得你的应用程序具有一个窗口,显示浏览器控件,以及处理它的显示设置。这些接口和IOleClientSite的实现在许多情况可能是最小的或不存在的。IOleClientSite的所有方法都可以返回E_NOTIMPL。 一些IOleInPlaceSite和IOleWindow的方法需要一个实现来覆盖返回值。可以在示例代码中查看IOleInPlaceSite和IOleWindow的最小实现的样例代码。既然我们已经完成了初始化的准备,让我们看一看浏览器控件自定义的每一个接口。IDocHostUIHandlerIDocHostUIHandler自IE5以后已经是可用的。它提供15个方法。大体上,一些较重要的方法是IDocHostUIHandler::GetExternal, IDocHostUIHandler::GetHostInfo, IDocHostUIHandler::GetOptionKeyPath, IDocHostUIHandler::ShowContextMenu, 和 IDocHostUIHandler::TranslateAccelerator。当然,方法对你的重要性将会依赖于你的应用程序。IDocHostUIHandler::GetHostInfo你使用IDocHostUIHandler::GetHostInfo告诉MSHTML有关你的应用程序的能力和需求。通过它你能控制很多东西, 举例来说。你能禁用浏览器的3D的边缘。你能避免滚动条或改变他们的外观。你能禁用脚本。你能定义双击处理的方式。你能禁用浏览器的自动完成功能。IDocHostUIHandler::GetHostInfo有一个参数,被 MSHTML分配的DOCHOSTUIINFO 结构的一个指针。你的工作是要将在结构中填充你传给MSHTML的信息。DOCHOSTUIINFO结构有四个成员。第一个成员是 cbSize,是结构的大小。你应该自己像下面的示例代码那样设置。第二个成员是dwFlags,由来自DOCHOSTUIFLAG枚举的数值位与组成。第三个成员是dwDoubleClick,来自DOCHOSTUIDBLCLK枚举的一个数值。第四个成员是pchHostCss。你可以将pchHostCss设定为浏览器控件显示的页面中应用的全局样式表(CSS)规则的一个字符串的指针。DOCHOSTUIINFO 的最后一个成员是pchHostNs。你可以设置为你提供的分号分隔的命名空间列表字符串。在你正在浏览器控件中显示的页上使用自定义标签的时候使用这一个成员。这样你能声明一个全局的命名空间列表,而不需要在每个显示的页面上声明他们。确定使用CoTaskMemAlloc为pchHostCss或pchHostNS分配字符串。(译者注:看起来调用者用CoTaskMemFree释放这些字符串)。例子HRESULT GetHostInfo( DOCHOSTUIINFO* pInfo){    WCHAR* szCSS = L"BODY {background-color:#ffcccc}";    WCHAR* szNS = L"IE;MyTags;MyTags2='www.microsoft.com'";    #define CCHMAX 256    size_t cchLengthCSS,cchLengthszNS;    HRESULT hr=StringCchLengthW(szCSS, CCHMAX,&cchLengthCSS)    //TODO: 在这里处理错误。    OLECHAR* pCSSBuffer=(OLECHAR*) CoTaskMemAlloc((cchLengthCSS+1)*sizeof(OLECHAR));    //TODO: 在这里处理错误,确定内存成功地被分配。    hr=StringCchLengthW(szNS, CCHMAX,&cchLengthszNS)    //TODO: 在这里处理错误。    OLECHAR* pNSBuffer=(OLECHAR*) CoTaskMemAlloc((cchLengthszNS+1)*sizeof(OLECHAR));    //TODO: 在这里处理错误,确定内存成功地被分配。    hr=StringCchCopyW(pCSSBuffer , cchLengthCSS+1,szCSS)    //TODO: 在这里处理错误。    hr=StringCchCopyW(pNSBuffer , cchLengthszNS+1,szNS)    //TODO: 在这里处理错误。    pInfo-> cbSize= sizeof(DOCHOSTUIINFO)    pInfo-> dwFlags=DOCHOSTUIFLAG_NO3DBORDER|DOCHOSTUIFLAG_SCROLL_NO|DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE;    pInfo-> dwDoubleClick= DOCHOSTUIDBLCLK_DEFAULT;    pInfo-> pchHostCss= pCSSBuffer;    pInfo-> pchHostNS= pNSBuffer;    return S_OK;}如果你没有什么需要告诉MSHTML的,你可以在这个方法中返回E_NOTIMPL 。
IDocHostUIHandler::ShowContextMenu通过实现这一个方法, 你获得在当一个用户右击时被浏览器控件显示的快捷菜单的控制。你能通过在这个方法中返回S_OK 阻止IE显示它的默认快捷菜单。返回一些其他的数值 , 像S_FALSE或E_NOTIMPL,允许IE继续执行它的默认快捷菜单行为。如果你仅仅在这个方法中返回S_OK, 你能避免任何浏览器控件的右击行为。 这可能是你在许多场合中的全部需求,但是你能做到更多。通常,你使用这一个方法在返回 S_OK 之前产生并且显示你自己的快捷菜单。如果你知道浏览器控件显示的菜单的资源,而且它如何选择他们,你能也有效地自定义默认的浏览器控件快捷菜单。让我们看看它如何工作。浏览器控件由Shdoclc.dll获得它的快捷菜单资源。这个知识和一些 #define给予你一个机会操纵浏览器的菜单。让我们举例来说,假定你对默认菜单感到满意,除了你想要除去查看源代码项之外。下列代码载入来自Shdoclc.dll的浏览器控件快捷菜单资源,根据环境选择正确的菜单,移除IDM_VIEWSOURCE命令对应的菜单项,然后显示菜单。例子HRESULT CBrowserHost::ShowContextMenu(DWORD dwID,    POINT *ppt,    IUnknown *pcmdTarget,    IDispatch *pdispObject){    #define IDR_BROWSE_CONTEXT_MENU 24641    #define IDR_FORM_CONTEXT_MENU 24640    #define SHDVID_GETMIMECSETMENU 27    #define SHDVID_ADDMENUEXTENSIONS 53    HRESULT hr;    HINSTANCE hinstSHDOCLC;    HWND hwnd;    HMENU hMenu;    CComPtr<IOleCommandTarget> spCT;    CComPtr<IOleWindow> spWnd;    MENUITEMINFO mii={0};    CComVariant var, var1, var2;    hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);    hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);    hr = spWnd->GetWindow(&hwnd);    hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));    if (hinstSHDOCLC == NULL)    {        // 载入模块错误 -- 尽可能安全地失败        return;    }    hMenu=LoadMenu(hinstSHDOCLC,        MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));    hMenu=GetSubMenu(hMenu,dwID);    //获得语言子菜单    hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);    mii.cbSize = sizeof(mii);    mii.fMask = MIIM_SUBMENU;    mii.hSubMenu = (HMENU) var.byref;    //加入语言子菜单到编码上下文菜单    SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);    //插入来自注册表的快捷菜单扩展    V_VT(&var1) = VT_INT_PTR;    V_BYREF(&var1) = hMenu;    V_VT(&var2) = VT_I4;    V_I4(&var2) = dwID;    hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);    //删除查看源代码    DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);    //显示快捷菜单    int iSelection = ::TrackPopupMenu(hMenu,        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,        ppt->x,        ppt->y,        0,        hwnd,        (RECT*)NULL);    //发送选定的快捷菜单项目指令到外壳    LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);    FreeLibrary(hinstSHDOCLC);    return S_OK;}安全警告:不正确地使用LoadLibrary能载入错误的动态链接库(DLL)来威胁你的应用程序的安全。关于该如何正确地用微软Windows 的不同版本载入DLL的信息,参照LoadLibrary的文档。IDocHostUIHandler::GetExternal: 扩充文档对象模型IDocHostUIHandler 提供一个让你用在你自己的应用程序中实现的你自己的对象,方法和特性扩充IE文档对象模型 (DOM)的方法。你的实现是提供给MSHTML一个IDispatch接口指针,指向你自定义的COM自动化对象,实现你自定义的对象、属性和方法。这些对象,特性和方法之后可以在浏览器控件显示的任何页面中通过文档的外部对象访问。这一个方法的实现可以是非常简单的, 假定你的IDispatch接口在实现IDocHostUIHandler的相同对象上。HRESULT CBrowserHost::GetExternal(IDispatch **ppDispatch){    *ppDispatch = this;    return S_OK;}只要 MSHTML有对你的 IDispatch 的一个指针,MSHTML将会传递网页上对任何外部对象的调用到你的应用程序的自动化方法:<SCRIPT language="JScript">function MyFunc(iSomeData){    external.MyCustomMethod("Some text", iSomeData);}</SCRIPT>你也能使用这技术传递整个对象到一个网页。为了实现它,在你的IDispatch实现中创建一个方法,传递回你的网页可以用的对象。<SCRIPT language="JScript">function MyFunc(iSomeData){    var oCustCalendarObj;    external.GetCustomCalender(oCustCalenderObj);    oCustCalerdarObj.doStuffWithIt();}</SCRIPT>可以看看示例代码中使用 ATL的IDispatch自动化实现的一个例子 。译者注:IE也扩展了浏览器的文档对象模型,使得你在脚本中可以通过扩展对象的menuArguments属性访问当前窗口对象。IDocHostUIHandler::GetOptionKeyPathIDocHostUIHandler::GetOptionKeyPath是自定义浏览器控件的一个非常有力的工具。 许多浏览器控件显示和行为设定被储存在注册表中HKEY_CURRENT_USER键的下面。IDocHostUIHandler::GetOptionKeyPath给你一个机会为你的浏览器控件的特定实例覆盖这些注册表设定。它通过让你提供一个替代的注册表位置来实现,浏览器控件将会在这里读取注册表设置。IDocHostUIHandler::GetOptionKeyPath的一个实现传递给你让浏览器控件读取注册表设置的位置的一个字符串。浏览器控件将会找寻在HKEY_CURRENT_USER键下面的这一个键。例子HRESULT CBrowserHost::GetOptionKeyPath(LPOLESTR *pchKey,    DWORD dwReserved){    HRESULT hr;    #define CCHMAX 256    size_t cchLength;    if (pchKey)    {        WCHAR* szMyKey = L"Software\MyCompany\MyApp";        hr = StringCchLengthW(szMyKey, CCHMAX, &cchLength);        //TODO: 在这里处理错误。        *pchKey = (LPOLESTR)CoTaskMemAlloc((cchLength + 1) * sizeof(WCHAR));        if (*pchKey)            hr = StringCchCopyW(*pchKey, cchLength + 1, szKey);        //TODO: 在这里处理错误。        hr = (*pchKey) ? S_OK : E_OUTOFMEMORY;    }    else        hr = E_INVALIDARG;    return hr;}和IDocHostUIHandler::GetHostInfo一样,确保为你的字符串使用 CoTaskMemAlloc分配内存。告诉浏览器控件该在哪里找寻你的注册表设置实际上是第一步——就程序运行来说是第二步。 你的程序必须在被IDocHostUIHandler::GetOptionKeyPath告诉的位置设置一个注册表键,这样浏览器控件才可以去读取。有多种方法来完成这个步骤。一个方法是当应用程序被安装的时候执行一个注册表脚本。另外的一个方法是当应用程序启动的时候,用代码来完成。这里是改变默认值字体,大小和颜色的一个设定。例子HRESULT SetSomeKeys(){    HKEY hKey = NULL;    HKEY hKey2 = NULL;    HKEY hKey3 = NULL;    DWORD dwDisposition = NULL;    LONG lResult = NULL;    #define CBMAX 256    size_t cbLength;    RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\MyCompany\MyApp"),        NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,        NULL, &hKey, &dwDisposition);    RegCreateKeyEx(hKey, _T("Main"), NULL, NULL, REG_OPTION_NON_VOLATILE,        KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);    RegSetValueEx(hKey2, _T("Use_DlgBox_Colors"), NULL, REG_SZ,        (CONST BYTE*)_T("no"), sizeof(_T("no")));    RegCloseKey(hKey2);    RegCreateKeyEx(hKey, _T("Settings"), NULL, NULL, REG_OPTION_NON_VOLATILE,        KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);    RegSetValueEx(hKey2, _T("Anchor Color"), NULL, REG_SZ,        (CONST BYTE*)_T("0,255,255"), sizeof(_T("0,255,255")));    RegSetValueEx(hKey2, _T("Text Color"), NULL, REG_SZ,        (CONST BYTE*)_T("255,0,255"), sizeof(_T("255,0,255")));    RegCloseKey(hKey2);    RegCreateKeyEx(hKey, _T("International\Scripts"), NULL, NULL,        REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL,        &hKey2, &dwDisposition);    BYTE bDefaultScript = 0x3;    RegSetValueEx(hKey2, _T("Default_Script"), NULL, REG_BINARY,        &bDefaultScript, sizeof(bDefaultScript));    RegCreateKeyEx(hKey2, _T("3"), NULL, NULL, REG_OPTION_NON_VOLATILE,        KEY_SET_VALUE, NULL, &hKey3, &dwDisposition);    BYTE bSize = 0x4; // Value from 0 - 4. 2 is medium.    TCHAR* szFontName = _T("Comic Sans MS");    TCHAR* szFixedFontName = _T("Courier");    HRESULT hr = StringCbLength(szFontName, CBMAX, &cbLength);    //TODO: 在这里处理错误。    RegSetValueEx(hKey3, _T("IEPropFontName"), NULL, REG_SZ,        (CONST BYTE*)szFontName, cbLength + sizeof(TCHAR));    hr = StringCbLength(szFixedFontName, CBMAX, &cbLength);    //TODO: 在这里处理错误。    RegSetValueEx(hKey3, _T("IEFixedFontName"), NULL, REG_SZ,        (CONST BYTE*)szFixedFontName, cbLength + sizeof(TCHAR));    RegSetValueEx(hKey3, _T("IEFontSize"), NULL, REG_BINARY, &bSize, sizeof(bSize));    RegCloseKey(hKey3);    RegCloseKey(hKey2);    RegCloseKey(hKey);    return S_OK;}IDocHostUIHandler2IDocHostUIHandler2 只有一个方法,IDocHostUIHandler2::GetOverrideKeyPath。它运行非常类似于IDocHostUIHandler::GetOptionKeyPath的一个功能。它指出你修改自默认注册表设置的集成浏览器使用的注册表设置的位置。IDocHostUIHandler2::GetOverrideKeyPath 的一个实现看起来会很类似于IDocHostUIHandler::GetOptionKeyPath的一个实现。GetOptionKeyPath 和 GetOverrideKeyPath 的比较你或许没看到IDocHostUIHandler::GetOptionKeyPath和IDocHostUIHandler2::GetOverrideKeyPath之间的任何不同。在他们之间的不同是微妙的, 但是重要的。如果你实现 IDocHostUIHandler::GetOptionKeyPath,你的浏览器控件实例将会忽略任何IE的用户设定。这些设定被储存在注册表的HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer下面。如果你实现IDocHostUIHandler2::GetOverrideKeyPath,你的浏览器控件实例将会合并任何的用户设定—字体设定,菜单扩展,诸如此类——到它的显示和行为中。举例说明在IDocHostUIHandler::GetOptionKeyPath和IDocHostUIHandler2::GetOverrideKeyPath之间的不同,让我们重新看看IDocHostUIHandler::ShowContextMenu那段的示例代码。记住这一行:spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);如果你已经实现IDocHostUIHandler::GetOptionKeyPath,因为菜单扩展信息被储存在当前用户的注册表信息中,所以这一行不会加入任何自定义项目到快捷菜单。如果你已经实现IDocHostUIHandler2::GetOverrideKeyPath, 这一个行会添加在HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExt面定义的任何目前用户定义的菜单扩展, 除非你明确地在你的自定义注册信息位置提供一个空的或替代的MenuExt键。控制导航你可能想知道在IDocHostUIHandler那一节为什么不提到 IDocHostUIHandler::TranslateUrl,作为在你希望控制页面导航时实现的方法。原因是这一个方法不是控制导航的最通用的技术。 除非你直接地集成MSHTML,这一个方法将没有控制导航的效果。作为替代,通过实现IDispatch::Invoke,处理DISPID_BEFORENAVIGATE2,你可以控制导航。例如,下列代码避免导航到一个特别的网址,如果使用者尝试这么做,会显示 "没有允许导航" 错误页。例子case DISPID_BEFORENAVIGATE2:{    CComBSTR url = ((*pDispParams).rgvarg)[5].pvarVal->bstrVal;    if (url == "http://www.adatum.com" || url == "http://www.adatum.com/")    {        CComPtr<IWebBrowser2> spBrowser;        CComPtr<IDispatch> spDisp = ((*pDispParams).rgvarg)[6].pdispVal;        spDisp->QueryInterface(IID_IWebBrowser2, (void**)&spBrowser);        spBrowser->Stop();        CComBSTR newURL = "L"res://webhost.exe/nonavigate.htm";        spBrowser->Navigate(newURL, NULL, NULL, NULL, NULL);        ((*pDispParams).rgvarg)[0].boolVal = TRUE;    }    break;}IDocHostShowUI这个接口给你对浏览器控件显示的信息对话框和帮助的控制。它工作机理和IDocHostUIHandler和IDocHostUIHandler2一样,你实现它,这样在浏览器控件显示它自己的任何的信息或帮助之前 ,能调用你的IDocHostShowUI的方法。这给你一个机会阻止浏览器控件显示任何东西,而且使你能够改为显示你自己的自定义信息或帮助。 IDocHostShowUI有两个方法,IDocHostShowUI::ShowMessage和IDocHostShowUI::ShowHelp。IDocHostShowUI::ShowMessage返回 S_OK禁用浏览器控件的信息对话框。任何其他的返回数值,像S_FALSE或E_NOTIMPL,允许浏览器控件显示它的信息对话框。你通过这个方法能做的一件好的事情是为你的应用程序自定义信息框标题,替代 "Microsoft Internet Explorer" 。你能通过比较lpstrCaption和储存在Shdoclc.dll中的IE使用的字符串资源来完成它。它的ID是IDS_MESSAGE_BOX_TITLE,数值是2213。下列示例代码演示你可能需要做的工作。例子HRESULT CBrowserHost::ShowMessage(HWND hwnd,    LPOLESTR lpstrText,    LPOLESTR lpstrCaption,    DWORD dwType,    LPOLESTR lpstrHelpFile,    DWORD dwHelpContext,    LRESULT *plResult){    USES_CONVERSION;    TCHAR pBuffer[50];    // 窗口标题"Microsoft Internet Explorer"的资源标识    #define IDS_MESSAGE_BOX_TITLE 2213    //载入Shdoclc.dll 和IE消息框标题字符串    HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));    if (hinstSHDOCLC == NULL)    {        // 载入模块错误 -- 尽可能安全地失败        return;    }    LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE, pBuffer, 50);    // 比较IE消息框标题字符串和lpstrCaption    // 如果相同,用自定义标题替换    if (_tcscmp(OLE2T(lpstrCaption), pBuffer) == 0)        lpstrCaption = L"Custom Caption";    // 创建自己的消息框并且显示        *plResult = MessageBox(OLE2T(lpstrText), OLE2T(lpstrCaption), dwType);    //卸载Shdoclc.dll并且返回    FreeLibrary(hinstSHDOCLC);    return S_OK;}安全警告:不正确地使用LoadLibrary能载入错误的动态链接库(DLL)来威胁你的应用程序的安全。关于该如何正确地用微软Windows的不同版本载入DLL的信息,参照 LoadLibrary的文档。IDocHostShowUI::ShowHelp这一个方法在当IE需要显示帮助时被调用,举例来说当 F1 键被按下时,而且工作方式和IDocHostShowUI::ShowMessage类似。返回S_OK覆盖IE的帮助,或另外的HRESULT值让IE执行自己的帮助。控制下载和执行浏览器控件给你它的下载,显示设置和执行的控制权。 为了要得到这些控制,你实现你的宿主的IDispatch接口,使得它处理DISPID_AMBIENT_DLCONTROL。当浏览器控件被实例化的时候,它将会以这一个ID调用你的IDispatch::Invoke。将pvarResult设置为下列的标识的一个位与的组合,指明你的配置。DLCTL_DLIMAGES , DLCTL_VIDEOS 和 DLCTL_BGSOUNDS: 如果这些标识被设定,图像,视频和背景音乐将会被从服务器下载并且显示或播放,否则将不被下载和显示。DLCTL_NO_SCRIPTS 和 DLCTL_NO_JAVA: 脚本和Java小程序将不被运行。DLCTL_NO_DLACTIVEXCTLS 和 DLCTL_NO_RUNACTIVEXCTLS: ActiveX 控件将不被下载或者运行。DLCTL_DOWNLOADONLY: 网页只将会被下载,不显示。DLCTL_NO_FRAMEDOWNLOAD:浏览器控件将会下载并且解析框架集页面,但是不会下载和解析框架集中单独的框架。DLCTL_RESYNCHRONIZE 和 DLCTL_PRAGMA_NO_CACHE: 这些标志导致Internet缓冲的刷新。通过 DLCTL_RESYNCHRONIZE,服务器将会被请求更新状态。如果服务器指出缓存信息是最新的,将会使用 缓存文件。通过DLCTL_PRAGMA_NO_CACHE,不管文件的更新状态如何,文件都会被从服务器重新下载。DLCTL_NO_BEHAVIORS: 行为不被下载并且在文件中被禁用。DLCTL_NO_METACHARSET_HTML: 忽略在META元素中指明的字符集。DLCTL_URL_ENCODING_DISABLE_UTF8 和 DLCTL_URL_ENCODING_ENABLE_UTF8: 这些标志的功能类似于IDocHostUIHandler::GetHostInfo中使用的DOCHOSTUIFLAG_URL_ENCODING_DISABLE_UTF8和DOCHOSTUIFLAG_URL_ENCODING_ENABLE_UTF8标志。不同是只有在浏览器控件被初始化的时候,DOCHOSTUIFLAG标志才会被检查。这里的环境特性变化的下载标志在每当浏览器控件需要运行一个下载时被检查。DLCTL_NO_CLIENTPULL: 不运行客户端重定位页面操作(译者注:例如<meta http-equiv="refresh" content="30"> 的默认行为)。DLCTL_SILENT: 在下载期间没有用户界面显示。DLCTL_FORCEOFFLINE: 浏览器控件总是在脱机模式中操作。DLCTL_OFFLINEIFNOTCONNECTED 和 DLCTL_OFFLINE: 这些标志是相同的。如果不连接到英特网,浏览器控件将会在脱机模式中操作。DISPID_AMBIENT_DLCONTROL和标志的数值是在mshtmdid.h被定义的。最初,当对IDispatch::Invoke调用开始的时候, pvarResult参数指向的VARIANT是VT_EMPTY类型。 你必须为任何有效的设定设置它为VT_I4类型。你可以在VARIANT的lVal成员中存储标志数值。大部份标志数值有否定的效果,也就是说,他们避免行为正常地发生。举例来说,如果你不自定义浏览器控件行为,那么通常脚本会被执行。 但是如果你设定DLCTL_NOSCRIPTS 标志,脚本将不会在控制的那个实例中运行。然而,三个标志— DLCTL_DLIMAGES , DLCTL_VIDEOS 和 DLCTL_BGSOUNDS的作用正好相反。你必须全部设置标志,使得浏览器控件以它的默认行为执行关于图像,视频和声音的处理。下列示例代码使得一个浏览器控件实例下载并且显示图像和视频,但是不处理背景音乐,因为DLCTL_BGSOUNDS没有被明确地设定。浏览器控件显示的页上的脚本运行被禁用。STDMETHODIMP CAtlBrCon::Invoke(DISPID dispidMember, REFIID riid,    LCID lcid, WORD wFlags,    DISPPARAMS* pDispParams,    VARIANT* pvarResult,    EXCEPINFO* pExcepInfo,    UINT* puArgErr){    switch (dispidMember)    {        case DISPID_AMBIENT_DLCONTROL:            pvarResult->vt = VT_I4;            pvarResult->lVal = DLCTL_DLIMAGES | DLCTL_VIDEOS | DLCTL_NO_SCRIPTS;            break;        default:            return DISP_E_MEMBERNOTFOUND;    }    return S_OK;}IHostDialogHelperIHostDialogHelper是一个你能根据你的爱好创建对话框的接口。这一个接口有一个方法,IHostDialogHelper::ShowHTMLDialog。这一个方法提供如同功能ShowHTMLDialog一般的服务,但是使用起来稍微比较容易一点。为了要使用IHostDialogHelper,你从头产生对话框辅助对象。在这里是你使用CoCreateInstance的方式创建它。接口和ID在 mshtmhst.h 中被定义。例子IHostDialogHelper* pHDH;IMoniker* pUrlMoniker;BSTR bstrOptions = SysAllocString(L"dialogHeight:30;dialogWidth:40");BSTR bstrPath = SysAllocString(L"c:\dialog.htm");CreateURLMoniker(NULL, bstrPath, &pUrlMoniker);// 创建对话框辅助对象CoCreateInstance(CLSID_HostDialogHelper,    NULL,    CLSCTX_INPROC,    IID_IHostDialogHelper,    (void**)&pHDH);//调用ShowHTMLDialog 创建对话框pHDH->ShowHTMLDialog(NULL,    pUrlMoniker,    NULL,    bstrOptions,    NULL,    NULL);//释放资源SysFreeString(bstrPath);SysFreeString(bstrOptions);pUrlMoniker->Release();pHDH->Release();
译者注:如果要使用对话框来获得用户输入,你可能需要传递两个参数到ShowHTMLDialog。关于ShowHTMLDialog参数的说明,参见Platform SDK文档。ShowHTMLDialog和ShowHTMLDialogEx 似乎一直是MSHTML.DLL导出的两个函数,微软把它封装为接口,可能是在为未来的兼容性作准备。控制新的窗口控制浏览器控件的一个重要的方法是控制导航。你在前面已经看见如何在IDispatch::Invoke中拦截DISPID_BEFORENAVIGATE2来实现控制你的浏览器控件的导航位置。另外一个导航的重要的方面是要控制导航发生方式, 尤其是打开新的窗口的时候。让我们举例来说, 使用者右击一个链接,选择 "在新窗囗中打开" 或某一页包含像这样的脚本:window.open("www.msn.com")默认地,浏览器控件对这行代码的处理是通过打开IE的一个新的实例来显示网页。这可能正好是你的应用程序需要的,但是也可能不是。也许你需要在当前的浏览器控件实例中打开所有链接,或者你将在你控制下的浏览器控件的一个新的实例——具有你的用户界面和你的商标——打开链接。你可以在你的IDispatch实现中拦截一个事件——DWebBrowserEvents2::NewWindow2——来控制它。你的控制需要连接到DWebBrowserEvents2的连接点来拦截这一个事件。你连接到了DWebBrowserEvents2之后,实现你的IDispatch::Invoke以处理 DISPID_NEWWINDOW2。在为DISPID_NEWWINDOW2的IDispatch::Invoke函数调用中,数组pDispParams包含两个参数。第一个,序号是零, 是一个布尔类型的数值,告诉浏览器控件是否取消新的窗囗。默认它是假值,而且将会打开一个新的窗囗。如果你要完全取消新窗囗的创建, 设定标志到真值。序号为一的参数是一个IDispatch接口的指针。你可以将这一个参数设定为你已经创建的浏览器控件的IDispatch。当你传回这样一个IDispatch之后,MSHTML将会使用你给出的控件打开链接。译者注:MFC中的DHTML类和类向导默认支持这个事件。需要更多信息的话,参见MSJ1998年7月份的文章Keeping an Eye on Your Browser by Monitoring Internet Explorer 4.0 Events,以及 微软知识库文章 Q184876 HOWTO: Use the WebBrowser Control NewWindow2 Event显示一个正数对话框IE6或者更高版本中,你可以在用户浏览一个合法的安全超文本传输协议(HTTPS)站点的时候显示证书对话框。这和用户点击IE中的锁图标效果相同。你可以通过 DWebBrowserEvents2::SetSecureLockIcon事件来显示你自己的图标。#define SHDVID_SSLSTATUS 33

IOleCommandTarget *pct;
if (SUCCEEDED(pWebBrowser2->QueryInterface(IID_IOleCommandTarget, (void **) &pct)))
{
   pct->Exec(&CGID_ShellDocView, SHDVID_SSLSTATUS, 0, NULL, NULL);
   pct->Release();
}

信息栏

Windows XP SP2 中的Internet Explorer 6
引入了一个新的安全用户界面元素,称为音信栏。在特定操作被阻碍的时候,音讯栏给用户呈现一个用户界面元素。特其余,它会在以下操作被截留的时候显得。

  • 弹出窗口先导化(参见 弹出窗口杀手)
  • 文本下载
    (see 文本下载的范围)
  • 安装ActiveX 控件(see ActiveX
    的限制
    )
  • ActiveX控件安全指示的原委是用户安全设置或者是控件未标记为脚本安全的。
  • 文本的扩张名和多用途因特网邮件扩张项目(MIME)不符的(参见 MIME
    处理
    )
  • 网络协议锁死的始末(参见 协议)

消息栏是Windows XP SP2 中的Internet Explorer
6引入的安全特点之一。和其它安全特点决定一样,可以由此一个登记表键来决定:(FEATURE_SECURITYBAND).
默认境况下IE(iexplorer.exe) 和Windows 资源管理器(explorer.exe)
在这多少个安全特点决定下。上面彰显注册表键和启用过程:

HKEY_LOCAL_MACHINE (or HKEY_CURRENT_USER)

SOFTWARE
Microsoft
Internet Explorer
Main
FeatureControl
FEATURE_SECURITYBAND
iexplorer.exe= 0x00000001

explorer.exe= 0x00000001

process name.exe**=**0x00000001

这个FEATURE_SECURITYBAND
安全特点决定影响IE是否出示音信栏,音信栏在一个操作被拦截的时候唤醒用户。它不控制操作的掣肘过程。

一个合一浏览器控件的次第可以通过将其过程添加到那个注册表键来启用信息栏。这足以由此调用CoInternetSetFeatureEnabled函数来在运转时实施。假若一个应用程序并未在这么些安全特点决定下,那么浏览器控件的一言一行和Internet
Explorer 6 SP1b中的一样.

不曾章程通过脚本来访问这多少个特点。

在FEATURE_SECURITYBAND及有关安全特点决定下的应用程序可以行使信息栏应用程序编程接口(API)来在一个URL
操作
被明令禁止时自定义呈现的用户界面。为信息栏引入了好多新的OLECMDID指令。头六个是属于CGID_DocHostCommandHandler组。宿主应用程序应该在它们的IDocHostUIHandler实现的同一个对象中落实IOleCommandTarget ,以接受来自浏览器控件IOleCommandTarget::Exec调用。

  • OLECMDID_PAGEACTIONBLOCKED
  • OLECMDID_PAGEACTIONUIQUERY
  • OLECMDID_FOCUSVIEWCONTROLS

宿主应用程序能够采纳下面四个新的OLECMDID 命令来施行浏览器控件IOleCommandTarget::Exec调用。

  • OLECMDID_FOCUSVIEWCONTROLSQUERY
  • OLECMDID_SHOWPAGEACTIONMENU

以此示例使用IWebBrowser2::ExecWB 来执行OLECMDID_SHOWPAGEACTIONMENU 命令。

   POINT pt = { 0 };
   GetCursorPos(&pt);
   CComVariant varHwnd((LONG)hwnd);
   CComVariant varX(pt.x);
   CComVariant varY(pt.y);
   SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 3);
   LONG lIndex = 0;
   SafeArrayPutElement(psa, &lIndex, &varHwnd);
   lIndex++;
   SafeArrayPutElement(psa, &lIndex, &varX);
   lIndex++;
   SafeArrayPutElement(psa, &lIndex, &varY);
   CComVariant varArgIn;
   V_VT(&varArgIn) = VT_ARRAY | VT_I4;
   V_ARRAY(&varArgIn) = psa;
   pBrowser->ExecWB(OLECMDID_SHOWPAGEACTIONMENU, (OLECMDEXECOPT)dwPageActionFlags, &varArgIn, NULL);

其它,应用程序可以实现IInternetSecurityManager来重载默认的防城港区域安装,参见创设一个自定义URL安全管理器以获取更多信息.

结论

你现在有众多技巧,能够遵照你的处理来自定义浏览器控件。这些著作决不是从未有过遗漏的,不过期望您现在得以自行发现领先本文的技巧。检查IE注册表设置中这么些你可以用IDocHostUIHandler::GetOptionKeyPath或IDocHostUIHandler2::GetOverrideKeyPath修改的信息。记住许多注册表设置相互依赖。你或许必须做一些尝试来发现注册表设置可以多多的灵光地自定义;如若需要控制浏览器控件的拖放行为,你也可以去探视IDocHostUIHandler::GetDropTarget。

相关文章