最近項目中用html 來做界面,也就折騰了一下在wxwidget中嵌入浏覽器的若幹細節工作,mfc也基本是類似的,由于wxwidget中已經做了一個封裝wxie,但是開發過程中也遇到了不少問題,在此做一下總結:
ie邊框 及上下文菜單
普通嵌入到程式裡面的浏覽器頁面都會有一個灰色的邊框,這樣放到程式裡面就很難看。目前網上流行的辦法添加css:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
body{
border:0;
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
但是目前很多頁面用了比較新的css,改成這樣後,頁面就無法正常顯示了。讓網頁美工改樣式? 真是有點困難。
後來繼續查閱了資料,發現比較好的辦法,那就是重載 IDocHostUIHandler 接口,其中,實作以下部分:
HRESULT STDMETHODCALLTYPE FrameSite::GetHostInfo(DOCHOSTUIINFO *pInfo)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
{
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)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
HRESULT result = S_FALSE; //Dont Interfere
BOOL handled = FALSE;
switch ( m_contextMenuMode )
{
case kDefaultMenuSupport:
break;
case kNoContextMenu:
result = S_OK;
handled = TRUE;
case kTextSelectionOnly:
if (dwID != CONTEXT_MENU_TEXTSELECT)
{
result = S_OK;
handled = TRUE;
}
case kAllowAllButViewSource:
if (dwID == CONTEXT_MENU_DEFAULT)
//result = ModifyContextMenu(dwID, ppt, pcmdtReserved);
case kCustomMenuSupport:
//result = CustomContextMenu(ppt, pcmdtReserved);
}
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++程式和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> &params )
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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)
// Query for Document Interface
wxAutoOleInterface<IHTMLDocument2> hd(IID_IHTMLDocument2, iDisp);
iDisp->Release();
if (! hd.Ok())
IDispatch *spScript;
hr = hd->get_Script(&spScript);
if(FAILED(hr))
BSTR bstrMember = wxConvertStringToOle(fun);
DISPID dispid = NULL;
hr = spScript->GetIDsOfNames(IID_NULL,&bstrMember,1,
LOCALE_SYSTEM_DEFAULT,&dispid);
//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;
wxConvertOleToVariant(varRet,result);
這個方法實作了C++對頁面腳本調用,而且參數個數可以任意。比如頁面腳本是 :
function fun(a,b,c)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
IDispatch * pDisp = m_window->getExternal();
if(pDisp)
pDisp->AddRef();
*ppDispatch = pDisp;
其中 m_window->getExternal();
傳回的是自定義的一個IDispatch 接口類:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/*
* 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
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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,
/* [size_is][out] */ DISPID *rgDispId);
STDMETHODIMP Invoke(
/* [in] */ DISPID dispIdMember,
/* [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(
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [size_is][in] */ OLECHAR** rgszNames,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [size_is][out] */ DISPID* rgDispId)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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(
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [in] */ REFIID /*riid*/,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [in] */ LCID /*lcid*/,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [out][in] */ DISPPARAMS* pDispParams,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [out] */ VARIANT* pVarResult,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [out] */ EXCEPINFO* /*pExcepInfo*/,
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/* [out] */ UINT* puArgErr)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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);
V_VT(pVarResult)=VT_BSTR;
wxVariant wVar(re);
VariantToMSWVariant(wVar,*pVarResult);
其中 CustomFunction 定義如下:
#pragma once
#include <wx/wx.h>
#include <vector>
class CustomFunction
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
public:
CustomFunction(void)
virtual ~CustomFunction(void)
virtual wxString execute(const wxString &cmd, const std::vector<wxString> &args) = 0;
然後隻要在自己類裡面繼承這個接口,就可以接收來之腳本的調用請求。
腳本裡面編寫函數:
window._callFun = function()
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
{
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++程式設計人員隻要定義好通訊接口,大家各自實作好接口方法就可以完成界面功能了。
前面兩個問題的解決,心裡想: 這下好了,解決了這兩個問題,下一步做界面就簡單多了,不像以前,直接用mfc或第三方庫做,要做出好看效果真是很難。編譯好,運作了,發現效果還不錯。但是實作到裡面的腳本的時候,發現出了問題,不響應鍵盤消息了
。而且在頁面文本框裡面按tab鍵,光标不是跑到下一個文本框,而是不見了。之前有這個現象,但是忙于解決前面的問題,沒有注意到。這下可完了,不會前工盡棄吧,那可麻煩大了。
google了半天,問了朋友,還是不知道什麼原因。因為wxIE及嵌入浏覽器本身是比較偏門的問題,确實很難找到答案。
山窮水盡疑無路,柳暗花明又一村。好不容易,在google上找到了一個類似的問題,回答的說,這是wxIE的bug,在wxPython的項目中,這個問題已經解決了。下載下傳下來編譯後,運作試試看,搞定了
。
但是還得和之前的修改合并,合并過程中又發現了一個問題。前文中提到過,通過IOleObject 接口來設定IDocHostUIHandler方法,我開始就是用這種方法。結果合并完後,發現還是不響應鍵盤消息
調了半天,才發現,wxIE原來已經實作過IOleClientSite 接口,我把自己的接口設上去,把wxIE的給替換掉了,是以導緻了不正常的結果。 經過一番調整後,終于正常了
自此,用html做c++程式界面的基礎工作算是告一段落了,下面就是完成接口工作和頁面腳本了。希望不要再遇到什麼問題。
我在這裡把這些問題記錄下來,以備以後查用,也願其他朋友不要再遇到我一樣的困擾。
下面截一張做出來的界面圖:
這是一個對話框,完成前面的基礎工作後,隻要設計師設計好頁面,我們幾分鐘就可以繼承到c++裡面,再花點寫接口和腳本的時間,比原來用mfc做界面,不知要節省多少時間。這個界面比較簡單,但是隻要是能設計出的界面,我們都能讓他內建進來。
有興趣的朋友也不妨試試這種做界面的方法。
前面忘了寫這個問題,就是對話框的拖動問題。就像我前面的圖檔展示的對話框,一般的windows對話框是可以拖着标題欄移動的,但是我們這裡沒有任何原來的标題欄了,隻有html頁面,怎麼拖呢? 好像有點麻煩。
冥思苦想之後,想到一種辦法,通過前面的接口給c++發指令,讓c++移動視窗,頁面上計算好拖動的距離。html裡面頁面的拖動還是比較簡單的,c++裡面移動也就是調用 MoveWindow。由于前面的通訊方法還是比較靈活,沒用多少時間,就把這個功能實作了。但是一運作看,不太對勁,拖動過程拖尾現象太明顯。可能是c++不斷調用 MoveWindow 重繪效率比較低。這可麻煩了
。這時候,突然想到普通對話框拖動的時候,是一個虛框在那裡動,原來的對話框是不動的,滑鼠放開後,對話框才移過去。 能不能做到這樣呢? 但是windows實作這個方法的細節不得而知,怎麼做呢?
還是google好啊,經過一番搜尋,找到了答案:
void TooltipDlg::moveWin( const std::vector<wxString> &args )
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuMzM1YzM3YWYjVmNmBTZ4MzMlFGN4UjNiRjY4EjY1ADNfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
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的對話框移動一模一樣了,太好了
今天一鼓作氣把前面幾天的工作都總結了下來,還真是敲得手有點累。但是這些東西确實是不太正常的方法,找解決問題的方法很難,這裡先把他們記錄下來,免得以後找不到了。以前确實有很多知識都是用了就丢一邊找不到了。 也希望給有類似疑問的朋友一個幫助。