在基于对话框的程序中高级定制Web控件
2014-02-26 08:51
471 查看
引言
我白天是一个Web开发者,但是晚上就是一个C++/MFC码农。当我做白天的工作的时候,我们的客户要求我们提供一个解决方案,该客户想网上给他们的雇员进行考试,但是却不想用IE浏览器,这样的话,雇员就不能轻易的做小抄或者卖试题。所以我想,如果我能创建自己的窗口,让它全屏,我就能够隐藏任务栏,让键盘无效,我所需要的就是当我的程序运行的时候,锁住系统。然而,我需要去深度定制IE的Web控件(定制的菜单,对话框,窗口,从Javascript调用C++函数,消息框,改写键盘)。
我想我可以用C#或者之类的.NET方法去编写,但是要比一个简单的数据库程序来说更加复杂。但是我很快意识到,这个网页不能播放视频,更别提,我还必须得为每一个原始的Win32API去做函数声明,这的确非常考验我的耐性。我甚至考虑过用.NET CF库,但是里面缺少了很多我需要的函数。我曾经写过一些C++串口代码,使用系统钩子来拦截键盘消息,应用到C#中,但是会发现程序不时的会崩溃。不消说,我开始用C++/MFC来编码。(C++6.0万岁!)
研究或者我想说(深深地陷入了MSDN)
我从研究WebBrowser控件开始,它可以让我有自己定制的消息盒子,定制的模态对话框,定制窗口,定制菜单,从Javascript中调用C++代码和取消某些组合键。我发现了成吨的来自MSDN和一些网站的资料,告诉我我不得不利用IDocHostShowUI和IDocHostUIHandler COM组件和IDispatch等。都还ok,唯一的问题是,我根本不知道这些是什么!我找到了源代码,但是这些代码也不能完全满足我的需求,当时测试的时候用的是CHtmlView。这些源代码让我在其他的任务中也陷入糊涂。
最后在Microsoft Knowledge base中发现了一篇文章(经过了很长时间的搜索),我试着写了一些里面提到的代码,最终我成功了。
怎样去创建定制web浏览器程序?
在我们开始之前,我知道我要添加的所有的头文件和cpp文件和这篇文章的篇幅看起来挺可怕的。但是它其实真的没有看起来的那么可怕。我之所以这么说,是因为我在开始阅读这样大篇幅的技术文章的时候也会头皮发麻。
我必须赞一下Microsoft Knowledge Base Article-236312.这是一个巨大的宝库可以让我随心所欲地创建自己的web浏览器同时又很灵活的满足我的需求。
第一件你必须做的事情是用MFCAppWizard去创建一个基于对话框的程序,工程名叫做CustomBrowser。然后去ResourceView标签打开对话框的设计模式。然后点击工程->添加到工程->components and controls,在Registered ActiveX controls文件夹下面找到Miscro web Browser。把该控件添加到工程当中。
现在如果你看一眼对话框设计模式下的控件,就会发现一个地球仪一样的新的控件。这就是我们的web浏览器控件。所以只需点击它并把它放到你的对话框上就ok了。现在按Ctrl+W打开类向导,为刚才的控件添加一个成员变量m_browser。
现在就由我提到过的Microsoft Knowledge Base Article上场了。我只是让你留意一下这篇文章,但是我们都知道微软的链接是很可靠的。因此我将基本上把上面的指导内容拷贝下来然后粘贴上去。
1打开你的对话框的app的头文件,添加一个公共成员变量。
3添加一个custsite.cpp的文件。
4添加一个Idispimp.h的头文件
5添加一个Idispimp.cpp的文件
6打开CustomBrowser.cpp,在initInstance函数中添加如下代码,
7在 CustomBrowser.cpp中添加
我们将通过执行IDocHostShowUI交互来开始我们的工作。
1打开Custsite.h文件在public下的
首先,让我们进入Custsite.cpp文件。在这个文件中,我们能够定制自己的消息盒子。换句话说,每当从JavaScript中调用alert("some text here");的时候,我们自己的消息对话框就会出现。找到ShowMessage函数,在这个函数里面我们将用一个严重错误的图片来替换一个标准的警告的图片。
首先我们必须把下面的代码添加到 Idispimp.cpp的头文件中,就在#include
"idispimp.h"这行代码下面。我们这样做可以让我们在主对话框中使用这些函数。
用下面的代码来替换上面的代码块
用下面的的代码替换上面的内容:
现在找到下面的Invoke函数:
下面进入到CustomBrowserDlg.cpp文件中,把下面的代码加入进去。
(经本人亲测,上述会有内存泄露的情况,需要在程序结束的时候释放,所以在C**App::ExitInstance()函数中加上)
注意:下面的函数为了简单起见只是展示了一个消息框。在示例中,这些代码用来打开一个窗口,模态对话框和非模态对话框。我不想解释怎样去创建窗口,模态和非模态对话框,这超出了本文的范围。当然你会看到这些代码是怎样起作用的。
所有我们添加的函数现在可以用javascript调用了!下面的代码是CustomTest.html的一部分。
我白天是一个Web开发者,但是晚上就是一个C++/MFC码农。当我做白天的工作的时候,我们的客户要求我们提供一个解决方案,该客户想网上给他们的雇员进行考试,但是却不想用IE浏览器,这样的话,雇员就不能轻易的做小抄或者卖试题。所以我想,如果我能创建自己的窗口,让它全屏,我就能够隐藏任务栏,让键盘无效,我所需要的就是当我的程序运行的时候,锁住系统。然而,我需要去深度定制IE的Web控件(定制的菜单,对话框,窗口,从Javascript调用C++函数,消息框,改写键盘)。
我想我可以用C#或者之类的.NET方法去编写,但是要比一个简单的数据库程序来说更加复杂。但是我很快意识到,这个网页不能播放视频,更别提,我还必须得为每一个原始的Win32API去做函数声明,这的确非常考验我的耐性。我甚至考虑过用.NET CF库,但是里面缺少了很多我需要的函数。我曾经写过一些C++串口代码,使用系统钩子来拦截键盘消息,应用到C#中,但是会发现程序不时的会崩溃。不消说,我开始用C++/MFC来编码。(C++6.0万岁!)
研究或者我想说(深深地陷入了MSDN)
我从研究WebBrowser控件开始,它可以让我有自己定制的消息盒子,定制的模态对话框,定制窗口,定制菜单,从Javascript中调用C++代码和取消某些组合键。我发现了成吨的来自MSDN和一些网站的资料,告诉我我不得不利用IDocHostShowUI和IDocHostUIHandler COM组件和IDispatch等。都还ok,唯一的问题是,我根本不知道这些是什么!我找到了源代码,但是这些代码也不能完全满足我的需求,当时测试的时候用的是CHtmlView。这些源代码让我在其他的任务中也陷入糊涂。
最后在Microsoft Knowledge base中发现了一篇文章(经过了很长时间的搜索),我试着写了一些里面提到的代码,最终我成功了。
怎样去创建定制web浏览器程序?
在我们开始之前,我知道我要添加的所有的头文件和cpp文件和这篇文章的篇幅看起来挺可怕的。但是它其实真的没有看起来的那么可怕。我之所以这么说,是因为我在开始阅读这样大篇幅的技术文章的时候也会头皮发麻。
我必须赞一下Microsoft Knowledge Base Article-236312.这是一个巨大的宝库可以让我随心所欲地创建自己的web浏览器同时又很灵活的满足我的需求。
第一件你必须做的事情是用MFCAppWizard去创建一个基于对话框的程序,工程名叫做CustomBrowser。然后去ResourceView标签打开对话框的设计模式。然后点击工程->添加到工程->components and controls,在Registered ActiveX controls文件夹下面找到Miscro web Browser。把该控件添加到工程当中。
现在如果你看一眼对话框设计模式下的控件,就会发现一个地球仪一样的新的控件。这就是我们的web浏览器控件。所以只需点击它并把它放到你的对话框上就ok了。现在按Ctrl+W打开类向导,为刚才的控件添加一个成员变量m_browser。
现在就由我提到过的Microsoft Knowledge Base Article上场了。我只是让你留意一下这篇文章,但是我们都知道微软的链接是很可靠的。因此我将基本上把上面的指导内容拷贝下来然后粘贴上去。
1打开你的对话框的app的头文件,添加一个公共成员变量。
public: class CImpIDispatch* m_pDispOM;2在工程中添加一个新的头文件 Custsite.h,并把下面的代码拷贝和粘贴过去。
//=---------------------------------------------------------------= // (C) Copyright 1996-1999 Microsoft Corporation. All Rights Reserved. //=---------------------------------------------------------------= #ifndef __CUSTOMSITEH__ #define __CUSTOMSITEH__ #include "idispimp.h" #include <MSHTMHST.H> // // NOTE: // Some of the code in this file is MFC implementation specific. // Changes in future versions of MFC implementation may require // the code to be changed. Please check the readme of this // sample for more information // class CCustomControlSite:public COleControlSite { public: CCustomControlSite(COleControlContainer *pCnt): COleControlSite(pCnt){} protected: DECLARE_INTERFACE_MAP(); BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler) STDMETHOD(ShowContextMenu)(/* [in] */ DWORD dwID, /* [in] */ POINT __RPC_FAR *ppt, /* [in] */ IUnknown __RPC_FAR *pcmdtReserved, /* [in] */ IDispatch __RPC_FAR *pdispReserved); STDMETHOD(GetHostInfo)( /* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo); STDMETHOD(ShowUI)( /* [in] */ DWORD dwID, /* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject, /* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget, /* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame, /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc); STDMETHOD(HideUI)(void); STDMETHOD(UpdateUI)(void); STDMETHOD(EnableModeless)(/* [in] */ BOOL fEnable); STDMETHOD(OnDocWindowActivate)(/* [in] */ BOOL fEnable); STDMETHOD(OnFrameWindowActivate)(/* [in] */ BOOL fEnable); STDMETHOD(ResizeBorder)( /* [in] */ LPCRECT prcBorder, /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow, /* [in] */ BOOL fRameWindow); STDMETHOD(TranslateAccelerator)( /* [in] */ LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID); STDMETHOD(GetOptionKeyPath)( /* [out] */ LPOLESTR __RPC_FAR *pchKey, /* [in] */ DWORD dw); STDMETHOD(GetDropTarget)( /* [in] */ IDropTarget __RPC_FAR *pDropTarget, /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget); STDMETHOD(GetExternal)( /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch); STDMETHOD(TranslateUrl)( /* [in] */ DWORD dwTranslate, /* [in] */ OLECHAR __RPC_FAR *pchURLIn, /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut); STDMETHOD(FilterDataObject)( /* [in] */ IDataObject __RPC_FAR *pDO, /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet); END_INTERFACE_PART(DocHostUIHandler) }; class CCustomOccManager :public COccManager { public: CCustomOccManager(){} COleControlSite* CreateSite(COleControlContainer* pCtrlCont) { CCustomControlSite *pSite = new CCustomControlSite(pCtrlCont); return pSite; } }; #endif
3添加一个custsite.cpp的文件。
//=------------------------------------------------------------------= // (C) Copyright 1996-1999 Microsoft Corporation. All Rights Reserved. //=------------------------------------------------------------------= // // NOTE: // Some of the code in this file is MFC implementation specific. // Changes in future versions of MFC implementation may require // the code to be changed. Please check the readme of this // sample for more information // #include "stdafx.h" #undef AFX_DATA #define AFX_DATA AFX_DATA_IMPORT #include "CustomBrowser.h" // NOTE: This line is a hardcoded reference to an MFC header file // this path may need to be changed // to refer to the location of VC5 install // for successful compilation. #include <..\src\occimpl.h> #undef AFX_DATA #define AFX_DATA AFX_DATA_EXPORT #include "custsite.h" BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite) INTERFACE_PART(CCustomControlSite, IID_IDocHostUIHandler, DocHostUIHandler) END_INTERFACE_MAP() ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::AddRef() { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return pThis->ExternalAddRef(); } ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::Release() { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return pThis->ExternalRelease(); } HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::QueryInterface(REFIID riid, void **ppvObj) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); return hr; } // * CImpIDocHostUIHandler::GetHostInfo // * // * Purpose: Called at initialization // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::GetHostInfo( DOCHOSTUIINFO* pInfo ) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; return S_OK; } // * CImpIDocHostUIHandler::ShowUI // * // * Purpose: Called when MSHTML.DLL shows its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowUI( DWORD dwID, IOleInPlaceActiveObject * /*pActiveObject*/, IOleCommandTarget * pCommandTarget, IOleInPlaceFrame * /*pFrame*/, IOleInPlaceUIWindow * /*pDoc*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // We've already got our own // UI in place so just return S_OK return S_OK; } // * CImpIDocHostUIHandler::HideUI // * // * Purpose: Called when MSHTML.DLL hides its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::HideUI(void) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_OK; } // * CImpIDocHostUIHandler::UpdateUI // * // * Purpose: Called when MSHTML.DLL updates its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::UpdateUI(void) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // MFC is pretty good about updating // it's UI in it's Idle loop so I don't do anything here return S_OK; } // * CImpIDocHostUIHandler::EnableModeless // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::EnableModeless // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::EnableModeless(BOOL /*fEnable*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::OnDocWindowActivate // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::OnDocWindowActivate // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL /*fActivate*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::OnFrameWindowActivate // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::OnFrameWindowActivate // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::OnFrameWindowActivate(BOOL /*fActivate*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::ResizeBorder // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::ResizeBorder // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ResizeBorder( LPCRECT /*prcBorder*/, IOleInPlaceUIWindow* /*pUIWindow*/, BOOL /*fRameWindow*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::ShowContextMenu // * // * Purpose: Called when MSHTML.DLL // * would normally display its context menu // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowContextMenu( DWORD /*dwID*/, POINT* /*pptPosition*/, IUnknown* /*pCommandTarget*/, IDispatch* /*pDispatchObjectHit*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // We've shown our own context menu. //MSHTML.DLL will no longer try return S_OK; to show its own. } // * CImpIDocHostUIHandler::TranslateAccelerator // * // * Purpose: Called from MSHTML.DLL's TranslateAccelerator routines // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_FALSE; } // * CImpIDocHostUIHandler::GetOptionKeyPath // * // * Purpose: Called by MSHTML.DLL // * to find where the host wishes to store // * its options in the registry // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::GetOptionKeyPath(BSTR* pbstrKey, DWORD) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetDropTarget( /* [in] */ IDropTarget __RPC_FAR *pDropTarget, /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetExternal( /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { // return the IDispatch we have for extending the object Model IDispatch* pDisp = (IDispatch*)theApp.m_pDispOM; pDisp->AddRef(); *ppDispatch = pDisp; return S_OK; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::TranslateUrl( /* [in] */ DWORD dwTranslate, /* [in] */ OLECHAR __RPC_FAR *pchURLIn, /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::FilterDataObject( /* [in] */ IDataObject __RPC_FAR *pDO, /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; }
4添加一个Idispimp.h的头文件
/* * IDispimp.H * IDispatch * * Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved */ #ifndef _IDISPIMP_H_ #define _IDISPIMP_H_ 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); }; #endif //_IDISPIMP_H_
5添加一个Idispimp.cpp的文件
/* * idispimp.CPP * IDispatch for Extending Dynamic HTML Object Model * * Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved */ #include "stdafx.h" #include "idispimp.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Hardcoded information for extending the Object Model // Typically this would be supplied through a TypeInfo // In this case the name "xxyyzz" maps to DISPID_Extend const WCHAR pszExtend[10]=L"xxyyzz"; #define DISPID_Extend 12345 /* * CImpIDispatch::CImpIDispatch * CImpIDispatch::~CImpIDispatch * * Parameters (Constructor): * pSite PCSite of the site we're in. * pUnkOuter LPUNKNOWN to which we delegate. */ CImpIDispatch::CImpIDispatch( void ) { m_cRef = 0; } CImpIDispatch::~CImpIDispatch( void ) { ASSERT( m_cRef == 0 ); } /* * CImpIDispatch::QueryInterface * CImpIDispatch::AddRef * CImpIDispatch::Release * * Purpose: * IUnknown members for CImpIDispatch object. */ STDMETHODIMP CImpIDispatch::QueryInterface ( REFIID riid, void **ppv ) { *ppv = NULL; if ( IID_IDispatch == riid ) { *ppv = this; } if ( NULL != *ppv ) { ((LPUNKNOWN)*ppv)->AddRef(); return NOERROR; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void) { return ++m_cRef; } STDMETHODIMP_(ULONG) CImpIDispatch::Release(void) { return --m_cRef; } //IDispatch STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT* /*pctinfo*/) { return E_NOTIMPL; } STDMETHODIMP CImpIDispatch::GetTypeInfo(/* [in] */ UINT /*iTInfo*/, /* [in] */ LCID /*lcid*/, /* [out] */ ITypeInfo** /*ppTInfo*/) { return E_NOTIMPL; } 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; // Hardcoded mapping for this sample // A more usual procedure would be to use a TypeInfo for ( i=0; i < cNames; i++) { if ( 2 == CompareString( lcid, NORM_IGNOREWIDTH, (char*)pszExtend, 3, (char*)rgszNames[i], 3 ) ) { rgDispId[i] = DISPID_Extend; } 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) { // For this sample we only support // a Property Get on DISPID_Extend // returning a BSTR with "Wibble" as the value if ( dispIdMember == DISPID_Extend ) { if ( wFlags & DISPATCH_PROPERTYGET ) { if ( pVarResult != NULL ) { WCHAR buff[10]=L"Wibble"; BSTR bstrRet = SysAllocString( buff ); VariantInit(pVarResult); V_VT(pVarResult)=VT_BSTR; V_BSTR(pVarResult) = bstrRet; } } } return S_OK; }
6打开CustomBrowser.cpp,在initInstance函数中添加如下代码,
BOOL CCustomBrowserApp::InitInstance() { CCustomOccManager *pMgr = new CCustomOccManager; // Create an IDispatch class for // extending the Dynamic HTML Object Model m_pDispOM = new CImpIDispatch; // Set our control containment up // but using our control container // management class instead of MFC's default AfxEnableControlContainer(pMgr); // AfxEnableControlContainer(); //... rest of the code here }
7在 CustomBrowser.cpp中添加
#include "afxpriv.h" #include <..\src\occimpl.h> #include "CustSite.h"8在CustomBrowser.h 的底部添加如下声明
extern CCustomBrowserApp theApp;现在我们已经把上面的都做完了,我们有了一个好的开始。但是我们依然还用做一些事情。我们目前做的基本上是让
IDocHostUIHandler和IDispatch可以交互了。这让我们可以做像定制菜单,从Javascript中调用C++函数通过使用window.external。然而,我们也想执行IDocHostShowUI交互,所以我们要提供自己的定制的消息盒子。我们依然需要写一些额外的代码能让我们自己定制的web浏览器打开模态和非模态对话框,创建Javascript可以调用的C++函数,展示一个定制的菜单。
我们将通过执行IDocHostShowUI交互来开始我们的工作。
1打开Custsite.h文件在public下的
CCustomControlSite(COleControlContainer *pCnt):COleControlSite(pCnt){}:后面添加
BEGIN_INTERFACE_PART(DocHostShowUI, IDocHostShowUI) INIT_INTERFACE_PART(CDocHostSite, DocHostShowUI) STDMETHOD(ShowHelp)( /* [in ] */ HWND hwnd, /* [in ] */ LPOLESTR pszHelpFile, /* [in ] */ UINT uCommand, /* [in ] */ DWORD dwData, /* [in ] */ POINT ptMouse, /* [out] */ IDispatch __RPC_FAR *pDispatchObjectHit); STDMETHOD(ShowMessage)( /* [in ] */ HWND hwnd, /* [in ] */ LPOLESTR lpstrText, /* [in ] */ LPOLESTR lpstrCaption, /* [in ] */ DWORD dwType, /* [in ] */ LPOLESTR lpstrHelpFile, /* [in ] */ DWORD dwHelpContext, /* [out] */ LRESULT __RPC_FAR *plResult); END_INTERFACE_PART(DocHostShowUI)2打开Custsite.cpp文件,添加下面加粗的那两行
BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite) INTERFACE_PART(CCustomControlSite, IID_IDocHostShowUI, DocHostShowUI) INTERFACE_PART(CCustomControlSite, IID_IDocHostUIHandler, DocHostUIHandler) END_INTERFACE_MAP()3在上面代码的下面直接添加如下代码
ULONG CCustomControlSite::XDocHostShowUI::AddRef() { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalAddRef(); } ULONG CCustomControlSite::XDocHostShowUI::Release() { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalRelease(); } HRESULT CCustomControlSite::XDocHostShowUI::QueryInterface (REFIID riid, void ** ppvObj) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalQueryInterface( &riid, ppvObj ); } HRESULT CCustomControlSite::XDocHostShowUI::ShowHelp( HWND hwnd, LPOLESTR pszHelpFile, UINT nCommand, DWORD dwData, POINT ptMouse, IDispatch * pDispatchObjectHit) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return S_OK; } HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage( HWND hwnd, LPOLESTR lpstrText, LPOLESTR lpstrCaption, DWORD dwType, LPOLESTR lpstrHelpFile, DWORD dwHelpContext, LRESULT * plResult) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); MessageBox(hwnd, (CString)lpstrText, "Custom Browser", dwType); return S_OK; }现在我们完成了所有的交互,我们需要定制这个浏览器和拓展我们所需要的功能。现在我们需要做的是写代码来真正定制我们的浏览器,当我们需要提供一个满足高级功能的浏览器的时候,我们已经有了一个可以利用的框架了。
首先,让我们进入Custsite.cpp文件。在这个文件中,我们能够定制自己的消息盒子。换句话说,每当从JavaScript中调用alert("some text here");的时候,我们自己的消息对话框就会出现。找到ShowMessage函数,在这个函数里面我们将用一个严重错误的图片来替换一个标准的警告的图片。
HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage(HWND hwnd, LPOLESTR lpstrText, LPOLESTR lpstrCaption, DWORD dwType, LPOLESTR lpstrHelpFile, DWORD dwHelpContext, LRESULT * plResult) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); //now all of our alerts will show the error icon instead of the //warning icon. Of course you could do a lot more here to customize //but this is just to show you for a simple example MessageBox(hwnd, (CString)lpstrText, "Custom Browser", /*dwType*/MB_ICONERROR); return S_OK; }如果你愿意,你能改变,取消或者让一些组合键干别的事情。如果你要让某些组合键失效,你就进入到Custsite.cpp文件中,找到下面的代码:
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator (LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_FALSE; }然后用下面的代码对上述代码进行替换。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) //disable F5 if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_F5) < 0) return S_OK; if(GetKeyState(VK_CONTROL) & 0x8000) { //disable ctrl + O if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x4F) < 0) return S_OK; //disable ctrl + p if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x50) < 0) return S_OK; //disable ctrl + N if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x4E) < 0) return S_OK; } //disable back space if(lpMsg->wParam == VK_BACK) return S_OK; return S_FALSE; }你也可以将你自己定制的菜单内容来替换IE自带的,只需要把下面的代码反倒ShowContextMenu函数中。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowContextMenu( DWORD /*dwID*/, POINT* pptPosition, IUnknown* /*pCommandTarget*/, IDispatch* /*pDispatchObjectHit*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) //show our custom menu CMenu menu; menu.LoadMenu(IDR_CUSTOM_POPUP); CMenu* pSubMenu = menu.GetSubMenu(0); //Because we passed in theApp.m_pMainWnd all of our //WM_COMMAND handlers for the menu items must be handled //in CCustomBrowserApp. If you want this to be your dialog //you will have to grab a pointer to your dialog class and //pass the hWnd of it into the last parameter in this call pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, pptPosition->x, pptPosition->y, theApp.m_pMainWnd); // We've shown our own context menu. //MSHTML.DLL will no longer try //to show its own. return S_OK; }现在进入牛逼的环节。让我们进入到Idispimp.cpp中来。这里就是能够让我们在html文件中调用C++函数的地方。
首先我们必须把下面的代码添加到 Idispimp.cpp的头文件中,就在#include
"idispimp.h"这行代码下面。我们这样做可以让我们在主对话框中使用这些函数。
#include "CustomBrowser.h" #include "CustomBrowserDlg.h"找到如下代码:
// Hardcoded information for extending the Object Model // Typically this would be supplied through a TypeInfo // In this case the name "xxyyzz" maps to DISPID_Extend const WCHAR pszExtend[10]=L"xxyyzz"; #define DISPID_Extend 12345
用下面的代码来替换上面的代码块
CString cszCB_IsOurCustomBrowser = "CB_IsOurCustomBrowser"; CString cszCB_Close = "CB_Close"; CString cszCB_CustomFunction = "CB_CustomFunction"; CString cszCB_CustomFunctionWithParams = "CB_CustomFunctionWithParams"; CString cszCB_OpenWindow = "CB_OpenWindow"; CString cszCB_ShowModalDialog = "CB_ShowModalDialog"; CString cszCB_ShowModelessDialog = "CB_ShowModelessDialog"; #define DISPID_CB_IsOurCustomBrowser 1 #define DISPID_CB_Close 2 #define DISPID_CB_CustomFunction 3 #define DISPID_CB_CustomFunctionWithParams 4 #define DISPID_CB_OpenWindow 5 #define DISPID_CB_ShowModalDialog 6 #define DISPID_CB_ShowModelessDialog 7现在我们需要找到GetIDsOfNames函数,现在我们需要写一些代码,让它能够传递合适的ID给调用函数,让我们决定调用哪个C++函数。找到下面的代码:
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; // Hardcoded mapping for this sample // A more usual procedure would be to use a TypeInfo for ( i=0; i < cNames; i++) { if ( 2 == CompareString( lcid, NORM_IGNOREWIDTH, (char*)pszExtend, 3, (char*)rgszNames[i], 3 ) ) { rgDispId[i] = DISPID_Extend; } 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::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++) { CString cszName = rgszNames[i]; if(cszName == cszCB_IsOurCustomBrowser) { rgDispId[i] = DISPID_CB_IsOurCustomBrowser; } else if(cszName == cszCB_Close) { rgDispId[i] = DISPID_CB_Close; } else if(cszName == cszCB_CustomFunction) { rgDispId[i] = DISPID_CB_CustomFunction; } else if(cszName == cszCB_CustomFunctionWithParams) { rgDispId[i] = DISPID_CB_CustomFunctionWithParams; } else if(cszName == cszCB_OpenWindow) { rgDispId[i] = DISPID_CB_OpenWindow; } else if(cszName == cszCB_ShowModalDialog) { rgDispId[i] = DISPID_CB_ShowModalDialog; } else if(cszName == cszCB_ShowModelessDialog) { rgDispId[i] = DISPID_CB_ShowModelessDialog; } else { // One or more are unknown so set the return code accordingly hr = ResultFromScode(DISP_E_UNKNOWNNAME); rgDispId[i] = DISPID_UNKNOWN; } } return hr; }
现在找到下面的Invoke函数:
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) { // For this sample we only support a Property Get on DISPID_Extend // returning a BSTR with "Wibble" as the value if ( dispIdMember == DISPID_Extend ) { if ( wFlags & DISPATCH_PROPERTYGET ) { if ( pVarResult != NULL ) { WCHAR buff[10]=L"Wibble"; BSTR bstrRet = SysAllocString( buff ); VariantInit(pVarResult); V_VT(pVarResult)=VT_BSTR; V_BSTR(pVarResult) = bstrRet; } } } return S_OK; }用下面的代码替换上面的代码。
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) { CCustomBrowserDlg* pDlg = (CCustomBrowserDlg*) AfxGetMainWnd(); if(dispIdMember == DISPID_CB_IsOurCustomBrowser) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { bool bResult = pDlg->CB_IsOurCustomBrowser(); VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = bResult; } } if(dispIdMember == DISPID_CB_Close) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { pDlg->CB_Close(); } } 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 ) { pDlg->CB_CustomFunction(); } } if(dispIdMember == DISPID_CB_CustomFunctionWithParams) { 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 CString cszArg1= pDispParams->rgvarg[1].bstrVal; int nArg2= pDispParams->rgvarg[0].intVal; pDlg->CB_CustomFunctionWithParams(cszArg1, nArg2); } } if(dispIdMember == DISPID_CB_OpenWindow) { 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 CString cszArg1= pDispParams->rgvarg[5].bstrVal; int nArg2= pDispParams->rgvarg[4].intVal; int nArg3= pDispParams->rgvarg[3].intVal; int nArg4= pDispParams->rgvarg[2].intVal; int nArg5= pDispParams->rgvarg[1].intVal; int nArg6 = pDispParams->rgvarg[0].intVal; pDlg->CB_OpenWindow(cszArg1, nArg2, nArg3, nArg4, nArg5, nArg6); } } if(dispIdMember == DISPID_CB_ShowModelessDialog) { 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 CString cszArg1= pDispParams->rgvarg[4].bstrVal; int nArg2= pDispParams->rgvarg[3].intVal; int nArg3= pDispParams->rgvarg[2].intVal; int nArg4= pDispParams->rgvarg[1].intVal; int nArg5= pDispParams->rgvarg[0].intVal; pDlg->CB_ShowModelessDialog(cszArg1, nArg2, nArg3, nArg4, nArg5); } } if(dispIdMember == DISPID_CB_ShowModalDialog) { 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 CString cszArg1= pDispParams->rgvarg[4].bstrVal; int nArg2= pDispParams->rgvarg[3].intVal; int nArg3= pDispParams->rgvarg[2].intVal; int nArg4= pDispParams->rgvarg[1].intVal; int nArg5= pDispParams->rgvarg[0].intVal; pDlg->CB_ShowModalDialog(cszArg1, nArg2, nArg3, nArg4, nArg5); } } return S_OK; }现在我们要做的就是把所有的这些函数增加到CCustomBrowserDlg类中。进入到CustomBrowserDlg.h头文件,把下面的代码放入到public下。
void CB_ShowModelessDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight); void CB_ShowModalDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight); void CB_OpenWindow(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight, int nResizable); void CB_CustomFunctionWithParams(CString cszString, int nNumber); void CB_CustomFunction(); void CB_Close(); BOOL CB_IsOurCustomBrowser();
下面进入到CustomBrowserDlg.cpp文件中,把下面的代码加入进去。
void CCustomBrowserDlg::CB_Close() { AfxMessageBox("Close the browser here or the current window"); //This is one way you can determine whether or not //to close a dialog or the main application depending //on if you call the CB_Close method from an html page //in a dialog/window or from an html page in the main app //for example if you launch a modal dialog from your javascript code //and you want to have your own close button in your html page as //an alternative to using the x button, then you can just call //window.external.CB_Close(); rather than window.close(); and this //function will determine which window to close based on which //window is currently active using the code below. /* CWnd* pWnd = GetActiveWindow(); if(pWnd == this) { EndDialog(0); } else { CDialog* pWin = (CDialog*)pWnd; pWin->EndDialog(1); } */ } void CCustomBrowserDlg::CB_CustomFunction() { AfxMessageBox("Do whatever you like here!"); } void CCustomBrowserDlg::CB_CustomFunctionWithParams (CString cszString, int nNumber) { CString cszParameters; cszParameters.Format ("parameter 1: %s\nparameter 2: %d", cszString, nNumber); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_OpenWindow(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight, int nResizable) { //you could launch a normal window from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight, nResizable); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_ShowModalDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight) { //you could launch a modal dialog from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_ShowModelessDialog (CString cszURL, int nLeft, int nTop, int nWidth, int nHeight) { //you could launch a modeless dialog from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight); AfxMessageBox(cszParameters); }
(经本人亲测,上述会有内存泄露的情况,需要在程序结束的时候释放,所以在C**App::ExitInstance()函数中加上)
if (m_pDispOM != NULL) { delete m_pDispOM; m_pDispOM = NULL; }
注意:下面的函数为了简单起见只是展示了一个消息框。在示例中,这些代码用来打开一个窗口,模态对话框和非模态对话框。我不想解释怎样去创建窗口,模态和非模态对话框,这超出了本文的范围。当然你会看到这些代码是怎样起作用的。
所有我们添加的函数现在可以用javascript调用了!下面的代码是CustomTest.html的一部分。
<script langauge="javascript"> function fnIsOurCustomBrowser() { //Check to see if this is our custom //browser. We just see if the function //IsOurCustomBrowser is available. By leaving //the brackets off of our function it is treated //as a property and thus allows us to check if //our method exists in the external window. If it //returns null then obviously this is not our custom //browser, so we return false. if(window.external.CB_IsOurCustomBrowser!=null) return true; else return false; } //setup a variable to let us know if this is our //custom browser. bIsCustomBrowser = fnIsOurCustomBrowser(); if(!bIsCustomBrowser) { //You can check here to see if they have our custom browser. //If they don't you can do whatever you want, redirect them //to another page or whatever. For the purposes of this example //I will just alert them alert('You must be using Custom Browser to view this page properly.'); } function fnCallFunction() { //Call c++ function that we made //in our custom browser if(bIsCustomBrowser) window.external.CB_CustomFunction(); } function fnCallFunctionWithParams(strString, nNumber) { //Call c++ function that accepts parameters //that we made in our custom browser if(bIsCustomBrowser) window.external.CB_CustomFunctionWithParams (strString, nNumber); } function fnOpenWindow(strURL, nLeft, nTop, nWidth, nHeight, nResizable) { if(bIsCustomBrowser) window.external.CB_OpenWindow(strURL, nLeft, nTop, nWidth, nHeight, nResizable); } function fnShowModalDialog(strURL, nLeft, nTop, nWidth, nHeight) { if(bIsCustomBrowser) window.external.CB_ShowModalDialog(strURL, nLeft, nTop, nWidth, nHeight); } function fnShowModelessDialog(strURL, nLeft, nTop, nWidth, nHeight) { if(bIsCustomBrowser) window.external.CB_ShowModelessDialog(strURL, nLeft, nTop, nWidth, nHeight); } </script>
相关文章推荐
- Java中的线程Thread方法之---interrupt()
- 9.3.2: 常见设计模式精讲---简单工厂
- ubuntu 12.04 命令行不显示用户名和路径了
- Java中的对象Object方法之---wait()和notifiy()
- 基于模板的通用代码生成器LKGenerator(一)-发布和主要功能介绍
- 9.3.1: 常见设计模式精讲---单例模式
- g++参数介绍
- 韩剧是女人的A片
- sharepoint 2013创建外部内容类型并创建外部列表
- 抽象语法树
- 9.2.3: 如何面对挑战---选择性地扩展
- 9.2.4: 如何面对挑战---使用代码生成器
- 9.2.2: 如何面对挑战---利用优秀的框架
- js 赋值 包含 单引号 双引号 解决方法
- 9.2.1: 如何面对挑战---使用建模工具
- Ubuntu下NFS的简单配置
- 9.1.4: 企业应用开发面临的挑战---花费最小化,利益最大化
- 在iOS上绘制自然的签名
- Tortoise SVN常见图标含义及图标无法正常解决方法!
- MongoDB-启动的时候出现了问题