C++的MFC 与 HTML 双向通讯
C++中嵌入ie浏览器总结(1) - ie边框 及上下文菜单
最近项目中用html 来做界面,也就折腾了一下在wxwidget中嵌入浏览器的若干细节工作,mfc也基本是类似的,由于wxwidget中已经做了一个封装wxie,但是开发过程中也遇到了不少问题,在此做一下总结:
- ie边框 及上下文菜单
普通嵌入到程序里面的浏览器页面都会有一个灰色的边框,这样放到程序里面就很难看。目前网上流行的办法添加css:
body{
border:0;
}
但是这个方法的缺点是,必须要把页面头部的dtd申明去掉。网上的错误提法是类似以下链接的: http://www.faceker.com/200801/webbrowser-no-border.html 这里面讲,只要把头部改成:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
但是目前很多页面用了比较新的css,改成这样后,页面就无法正常显示了。让网页美工改样式? 真是有点困难。 后来继续查阅了资料,发现比较好的办法,那就是重载 IDocHostUIHandler 接口,其中,实现以下部分:
HRESULT STDMETHODCALLTYPE FrameSite::GetHostInfo(DOCHOSTUIINFO *pInfo)
{
pInfo->cbSize = sizeof(DOCHOSTUIINFO);
pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER|DOCHOSTUIFLAG_SCROLL_NO;
pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
return S_OK; }
其中 DOCHOSTUIFLAG_NO3DBORDER 就表示不要生成边框,DOCHOSTUIFLAG_SCROLL_NO 表示不要生成滚动条这样就可以比较完美的解决边框和滚动条的问题,不用依赖页面的调整 。让设计师爱用什么用什么。
另外一个就是禁用右键菜单,网上也有不少办法,但是用这个接口可以很简单的实现:
HRESULT STDMETHODCALLTYPE FrameSite::ShowContextMenu(DWORD dwID, POINT *ppt,
IUnknown *pcmdtReserved, IDispatch *pdispReserved)
{
HRESULT result = S_FALSE; //Dont Interfere
BOOL handled = FALSE; switch ( m_contextMenuMode )
{
case kDefaultMenuSupport:
break; case kNoContextMenu:
result = S_OK;
handled = TRUE;
break; case kTextSelectionOnly:
if (dwID != CONTEXT_MENU_TEXTSELECT)
{
result = S_OK;
handled = TRUE;
}
break; case kAllowAllButViewSource:
if (dwID == CONTEXT_MENU_DEFAULT)
{
//result = ModifyContextMenu(dwID, ppt, pcmdtReserved);
handled = TRUE;
}
break; case kCustomMenuSupport:
if (dwID == CONTEXT_MENU_DEFAULT)
{
//result = CustomContextMenu(ppt, pcmdtReserved);
handled = TRUE;
}
break;
} if (! handled)
{
result = S_FALSE;
} return result;
}
这里不仅可以控制右键菜单显示,m_contextMenuMode = kNoContextMenu,还可以做到自定义菜单显示,m_contextMenuMode =其他值。因为暂时还不需要自定义菜单,所以这里没有实现。
如果用wxie,就在FrameSite类增加这个接口即可,不关注的接口直接返回S_FALSE 或E_NOTIMPL;
如果用sdk或mfc,可以 调用IOleObject 的SetClientSite 方法,设置一个继承了IOleClientSite 和 IDocHostUIHandler 的接口。
C++中嵌入ie浏览器总结(2) - 双向通讯
第一步解决了边框和上下文菜单问题,第二部就是要解决c++程序和html页面交互的问题。最开始的想法是通过c++去更新页面内容的方式来完成c++->html的通讯,通过BeforeNavigate2 接口,截获页面url地址的方式来完成html->c++的通讯。但是这种方式存在以下缺点:
(1) c++->html 的问题在于导致c++代码复杂,需要通过c++代码来完成页面生成,如果修改页面,将产生很大的工作量。虽然尝试用了模板方法解决,但是还是比较繁琐,而且会导致经常通讯的时候,页面会经常刷新,产生其他的一些问题。
(2) html->c++ 的问题在于 传递参数不方便,解析也不方便、无法获取返回值、脚本中要调用不方便
为了解决这些问题,经过google后找到了问题的解决办法 :
(1) c++->html ,可以通过调用页面脚本方法来实现,调用方法如下:
wxVariant wxIEHtmlWin::ExecScript(const wxString &fun,const std::vector<wxString> ¶ms )
{
wxVariant result(false);
if (! m_webBrowser.Ok())
return result; // get document dispatch interface
IDispatch *iDisp = NULL;
HRESULT hr = m_webBrowser->get_Document(&iDisp);
if (hr != S_OK)
return result; // Query for Document Interface
wxAutoOleInterface<IHTMLDocument2> hd(IID_IHTMLDocument2, iDisp);
iDisp->Release(); if (! hd.Ok())
return result; IDispatch *spScript;
hr = hd->get_Script(&spScript); if(FAILED(hr))
return result;
BSTR bstrMember = wxConvertStringToOle(fun);
DISPID dispid = NULL;
hr = spScript->GetIDsOfNames(IID_NULL,&bstrMember,1,
LOCALE_SYSTEM_DEFAULT,&dispid);
if(FAILED(hr))
{ return result;
}
//Putting parameters
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);
dispparams.cArgs = params.size();
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
dispparams.cNamedArgs = 0; for( int i = 0; i < params.size(); i++)
{
CComBSTR bstr = wxConvertStringToOle(params[params.size() - 1 - i]);
// back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal); dispparams.rgvarg[i].vt = VT_BSTR;
}
EXCEPINFO excepInfo;
memset(&excepInfo, 0, sizeof excepInfo);
VARIANT varRet;
UINT nArgErr = (UINT)-1; // initialize to invalid arg
//Call JavaScript function
hr = spScript->Invoke(dispid,IID_NULL,0,
DISPATCH_METHOD,&dispparams,
&varRet,&excepInfo,&nArgErr);
delete [] dispparams.rgvarg;
if(FAILED(hr))
{ return result;
} wxConvertOleToVariant(varRet,result);
return result; }
这个方法实现了C++对页面脚本调用,而且参数个数可以任意。比如页面脚本是 :
function fun(a,b,c)
{
}
C++中的调用方法是 :
std::vector<wxString> params;
params.push_back("a");
params.push_back("b");
params.push_back("c");
xxx->ExecScripts("fun",params);
还可以获得脚本返回的结果。
(2) html->c++ 通过脚本的window.external 方法,首先,在前文提到过的IDocHostUIHandler 接口中,实现方法:
HRESULT STDMETHODCALLTYPE FrameSite::GetExternal(IDispatch **ppDispatch)
{ IDispatch * pDisp = m_window->getExternal();
if(pDisp)
{
pDisp->AddRef();
*ppDispatch = pDisp;
} return S_OK;
}
其中 m_window->getExternal();
返回的是自定义的一个IDispatch 接口类:
/*
* IDispimp.H
* IDispatch
*
* Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved
*/ #ifndef _IDISPIMP_H_
#define _IDISPIMP_H_
#include <oaidl.h>
class CustomFunction;
class CImpIDispatch : public IDispatch
{
protected:
ULONG m_cRef; public:
CImpIDispatch(void);
~CImpIDispatch(void); STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void); //IDispatch
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);
STDMETHODIMP GetTypeInfo(/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITypeInfo** ppTInfo);
STDMETHODIMP GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID *rgDispId);
STDMETHODIMP Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr); void setCustomFunction(CustomFunction *fun) {m_fun = fun;}
private:
CustomFunction *m_fun; };
#endif //_IDISPIMP_H_
主要实现以下两个方法:
wxString cszCB_CustomFunction = wxT("CB_CustomFunction");
#define DISPID_CB_CustomFunction 3
STDMETHODIMP CImpIDispatch::GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ OLECHAR** rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID* rgDispId)
{
HRESULT hr;
UINT i; // Assume some degree of success
hr = NOERROR; for ( i=0; i < cNames; i++) {
wxString cszName = rgszNames[i]; if(cszName == cszCB_CustomFunction)
{
rgDispId[i] = DISPID_CB_CustomFunction;
} else {
// One or more are unknown so set the return code accordingly
hr = ResultFromScode(DISP_E_UNKNOWNNAME);
rgDispId[i] = DISPID_UNKNOWN;
}
}
return hr;
} STDMETHODIMP CImpIDispatch::Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID /*riid*/,
/* [in] */ LCID /*lcid*/,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS* pDispParams,
/* [out] */ VARIANT* pVarResult,
/* [out] */ EXCEPINFO* /*pExcepInfo*/,
/* [out] */ UINT* puArgErr)
{ if(dispIdMember == DISPID_CB_CustomFunction)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
if(pVarResult != NULL)
{ VariantInit(pVarResult);
V_VT(pVarResult)=VT_BOOL;
V_BOOL(pVarResult) = true;
}
} if ( wFlags & DISPATCH_METHOD )
{
//arguments come in reverse order
//for some reason
if(!m_fun) return S_OK; wxString arg1,arg2; if(pDispParams->cArgs<1) return S_FALSE;
wxString cmd = pDispParams->rgvarg[pDispParams->cArgs-1].bstrVal;
std::vector<wxString> args;
if(pDispParams->cArgs>1)
{
for(int i=pDispParams->cArgs-2;i>=0;i--)
args.push_back(pDispParams->rgvarg[i].bstrVal);
}
wxString re = m_fun->execute(cmd,args);
if(pVarResult != NULL)
{
VariantInit(pVarResult);
V_VT(pVarResult)=VT_BSTR;
wxVariant wVar(re);
VariantToMSWVariant(wVar,*pVarResult); } } } return S_OK;
}
其中 CustomFunction 定义如下:
#pragma once
#include <wx/wx.h>
#include <vector>
class CustomFunction
{
public: CustomFunction(void)
{
} virtual ~CustomFunction(void)
{
} virtual wxString execute(const wxString &cmd, const std::vector<wxString> &args) = 0;
};
然后只要在自己类里面继承这个接口,就可以接收来之脚本的调用请求。
脚本里面编写函数:
window._callFun = function()
{ var fun = "window.external.CB_CustomFunction(";
for(i=0;i<arguments.length;i++)
{
if(i!=0)
fun = fun+",";
fun = fun+"\""+arguments[i]+"\"";
}
fun = fun+")";
//alert(fun);
return (eval(fun));
}
然后调用的地方写:
_callFun("fun","param1","param2",);
就可以调用c++的函数,并且可以得到返回值,从而解决了html->c++的通讯问题
解决了双向通讯后,页面就不需要用刷新来解决,网页设计师和c++编程人员只要定义好通讯接口,大家各自实现好接口方法就可以完成界面功能了。
C++中嵌入ie浏览器总结(3) - wxIE 的 bug解决及最后效果展示
前面两个问题的解决,心里想: 这下好了,解决了这两个问题,下一步做界面就简单多了,不像以前,直接用mfc或第三方库做,要做出好看效果真是很难。编译好,运行了,发现效果还不错。但是实现到里面的脚本的时候,发现出了问题,不响应键盘消息了。而且在页面文本框里面按tab键,光标不是跑到下一个文本框,而是不见了。之前有这个现象,但是忙于解决前面的问题,没有注意到。这下可完了,不会前工尽弃吧,那可麻烦大了。
google了半天,问了朋友,还是不知道什么原因。因为wxIE及嵌入浏览器本身是比较偏门的问题,确实很难找到答案。
山穷水尽疑无路,柳暗花明又一村。好不容易,在google上找到了一个类似的问题,回答的说,这是wxIE的bug,在wxPython的项目中,这个问题已经解决了。下载下来编译后,运行试试看,搞定了。
但是还得和之前的修改合并,合并过程中又发现了一个问题。前文中提到过,通过IOleObject 接口来设置IDocHostUIHandler方法,我开始就是用这种方法。结果合并完后,发现还是不响应键盘消息。
调了半天,才发现,wxIE原来已经实现过IOleClientSite 接口,我把自己的接口设上去,把wxIE的给替换掉了,所以导致了不正常的结果。 经过一番调整后,终于正常了。
自此,用html做c++程序界面的基础工作算是告一段落了,下面就是完成接口工作和页面脚本了。希望不要再遇到什么问题。
我在这里把这些问题记录下来,以备以后查用,也愿其他朋友不要再遇到我一样的困扰。
下面截一张做出来的界面图:
这是一个对话框,完成前面的基础工作后,只要设计师设计好页面,我们几分钟就可以继承到c++里面,再花点写接口和脚本的时间,比原来用mfc做界面,不知要节省多少时间。这个界面比较简单,但是只要是能设计出的界面,我们都能让他集成进来。
有兴趣的朋友也不妨试试这种做界面的方法。
C++中嵌入ie浏览器总结(4) - 对话框拖动
前面忘了写这个问题,就是对话框的拖动问题。就像我前面的图片展示的对话框,一般的windows对话框是可以拖着标题栏移动的,但是我们这里没有任何原来的标题栏了,只有html页面,怎么拖呢? 好像有点麻烦。
冥思苦想之后,想到一种办法,通过前面的接口给c++发指令,让c++移动窗口,页面上计算好拖动的距离。html里面页面的拖动还是比较简单的,c++里面移动也就是调用 MoveWindow。由于前面的通讯方法还是比较灵活,没用多少时间,就把这个功能实现了。但是一运行看,不太对劲,拖动过程拖尾现象太明显。可能是c++不断调用 MoveWindow 重绘效率比较低。这可麻烦了。这时候,突然想到普通对话框拖动的时候,是一个虚框在那里动,原来的对话框是不动的,鼠标放开后,对话框才移过去。 能不能做到这样呢? 但是windows实现这个方法的细节不得而知,怎么做呢?
还是google好啊,经过一番搜索,找到了答案:
void TooltipDlg::moveWin( const std::vector<wxString> &args )
{
if(args.size()<2) return;
long x,y;
args[0].ToLong(&x);
args[1].ToLong(&y);
int ix,iy;
ix = x;
iy = y;
ClientToScreen(&ix,&iy); ::SendMessage((HWND)this->GetHWND(),WM_NCMOUSEMOVE,HTCAPTION,MAKELPARAM(ix, iy)); }
搞定了,简单吧,真是没想到这么简单。运行后发现,真的和windows的对话框移动一模一样了,太好了
今天一鼓作气把前面几天的工作都总结了下来,还真是敲得手有点累。但是这些东西确实是不太常规的方法,找解决问题的方法很难,这里先把他们记录下来,免得以后找不到了。以前确实有很多知识都是用了就丢一边找不到了。 也希望给有类似疑问的朋友一个帮助。
转载自:http://www.cnblogs.com/lidabo/archive/2012/11/29/2795528.html
程序员的基础教程:菜鸟程序员
C++的MFC 与 HTML 双向通讯的更多相关文章
- 重温WCF之数单向通讯、双向通讯、回调操作(五)
一.单向通讯单向操作不等同于异步操作,单向操作只是在发出调用的瞬间阻塞客户端,但如果发出多个单向调用,WCF会将请求调用放入到服务器端的队列中,并在某个时间进行执行.队列的存储个数有限,一旦发出的调用 ...
- silverlight与wcf双向通讯 例子
本文将建立一个silverlight与wcf双向通讯的简单实例,以下是详细步骤: 新建Silverlight应用程序,名称WCFtest.解决方案中添加WCF服务应用程序,名称WcfServiceTe ...
- 我们一起学习WCF 第四篇单通讯和双向通讯
前言:由于个人原因很久没有更新这个系列了,我会继续的更新这系列的文章.这一章是单向和双向通讯.所谓的单向就是只有发送却没有回复,双向是既有发送还有回复.就是有来无往代表单向,礼尚往来表示双向.下面我用 ...
- Thrift之双向通讯
在实际应用中,却经常会有客户端建立连接后,等待服务端数据的长连接模式,也可以称为双向连接.一.双连接,服务端与客户端都开ThriftServer如果网络环境可控,可以让服务端与客户端互相访问,你可以给 ...
- iPhone 和 iPad的ios 开发中 利用 WebViewJavascriptBridge组件,通过 UIWebView 对Html进行双向通讯
本文转载至 http://blog.csdn.net/remote_roamer/article/details/7261490 WebViewJavascriptBridge 项目的 官网 http ...
- 基于grpc的流式方式实现双向通讯(python)
grpc介绍 grpc是谷歌开源的一套基于rpc实现的通讯框架(官网有更完整的定义).在搞懂grpc之前,首先要弄懂rpc是什么.下面是自己理解的rpc定义,若有不对,望指出: rpc官方称为 远程过 ...
- Oracle VirtualBox 使用桥接网络完成主机和虚拟机之间的双向通讯
最近刚换了新的笔记本电脑,终于使用上intel i7处理器,可以使用硬件虚拟化技术安装系统.配置如下: 主机 ThinkPad P50s OS Window 10 虚拟机软件 Orac ...
- 通过桥接虚拟网卡使VMWare和宿主机实现双向通讯
0.为什么选择虚拟网卡和桥接模式 首先虚拟机网络设置为NAT,虚拟机实现上网是很方便的,但是宿主机访问虚拟机就比较麻烦了(需要单独配置端口转发),桥接就能很好的解决这个问题,桥接模式会把虚拟机当做宿主 ...
- 利用STM32CubeMX生成HID双向通讯工程
使用开发板为正点原子ministm32 现在我们先使用HID descriptor Tool来生成我们需要的hid的 保存使用选择.H // D:\usb资料\HID\MSDEV\Projects\t ...
随机推荐
- JVM内部细节之二:偏向锁(Biased Locking)
在前面一片文章<JVM内部细节之一:synchronized关键字及实现细节>中已经提到过偏向锁的概念,在理解什么是偏向锁前必须先理解什么是轻量级锁(Lightweight Locking ...
- java在注解中绑定方法参数的解决方案
我们有这样子的需求,需要记录用户操作某个方法的信息并记录到日志里面,例如,用户在保存和更新任务的时候,我们需要记录下用户的ip,具体是保存还是更新,调用的是哪个方法,保存和更新的任务名称以及操作是否成 ...
- 利用goole guava 下载文件到本地
package com.road.crawler.meizitu.crawler; import java.io.File; import java.io.IOException; import ja ...
- 数据库之SQL笛卡尔积
1.笛卡尔积定义 笛卡尔积在SQL中的实现方式既是交叉连接(Cross Join).所有连接方式都会先生成临时笛卡尔积表,笛卡尔积是关系代数里的一个概念,表示两个表中的每一行数据任意组合,上图中两个表 ...
- Django静态图片参数解析
使用Django静态设置时,遇到很多问题,经过艰苦的Baidu, stack overflow, Django原档阅读,终于把静态图片给搞出来了.特记录下来. 关键的概念:Django中,静态资源的存 ...
- OpenACC 梯度下降法求解线性方程的优化
▶ 书上第二章,用一系列步骤优化梯度下降法解线性方程组.才发现 PGI community 编译器不支持 Windows 下的 C++ 编译(有 pgCC 命令但是不支持 .cpp 文件,要专业版才支 ...
- 怎样在本地windows安装和配置zookeeper
Zookeeper是什么?有什么用? Zookeeper是一个分布式协调服务. 作用:为用户的分布式应用程序提供协调服务. zookeeper在底层其实只提供了两个功能: 1.管理(存储,读取)用户 ...
- VB6 创建控制台应用程序
' 功能:为VB程序创建一个consolewindow.Private Declare Function AllocConsole Lib "kernel32" () As Lon ...
- ABAP-动态编程
转载:http://www.cnblogs.com/jiangzhengjun/p/4293407.html 动态编程 动态的基本语法 多种不同的动态编程 动态字段 动态类型 指定结构.内表组件字段的 ...
- webserive学习记录6-页面请求webservice
前面都是通过JAVA代码访问webservice服务,下面将介绍通过javascript,jquery访问webservice服务并介绍过过servlet解决跨域问题的方法. 服务端 编写服务代码,解 ...