一种全新的软件界面设计方法(摘)

80酷酷网    80kuku.com

  设计关键字:COM MySpy IE SetUIHanlder IcustomDoc IDocHostUIHandler GetExternal

前言

作者在解决各种问题的时候喜欢首先使用C++ Builder来尝试,这篇文章也是这样,但这并不影响其他开发工具的使用者阅读,因为这都是微软的开发技术,选择什么工具并不重要,我们理解了他的原理可以使用任何工具实现同样的功能。

正文

使用过VC.Net的朋友可能知道,在VC.Net中全新提供了一种基于Web的界面设计方法,不过可能真正用到的人很少,至少我在国内的软件中没有看到过这样的界面设计方法。当初使用VC.net的时候就希望BCB的下个版本可以加入这样灵活的界面设计方法,但是到现在还没有等到,我想也不能一直这样等下去,于是就自己研究其中的实现方法,终于让我研究出来。这篇文章就是讨论这样方法,以及在软件设计设计中的可行性。

说了这么多,可能还有朋友不知道这样的界面到底有什么不同,有什么优点呢?如果你也有同样的好奇感的话,请你继续看下去。

在Windows2000下,大家经常使用控制面板/添加、卸载软件的对话框就是基于这样的界面(Xp下暂时不清楚),我不说出来可能很少有人知道-那个对话框整个就是个网页?什么你不相信?如果是网页为什么能和本地的计算机程序交互?为什么不能选择网页里面的文字?为什么不能弹出右键菜单?如果是网页,那它的html代码在那里?

为了证明上面的说法,我们需要一些特殊的软件,这个软件就是作者写的MySpy,可以到作者的站点(http://siney.yeah.net)免费下载使用,我们可以从MySpy的界面中看到添加/删除程序的对话框是个Internet Explorer_Server,这说明它是个网页,


在MySpy的Web页面还可以看到这个页面的地址是:res://sp3res.dll/default.hta,




近一步使用MySpy得到这个网页的代码(不能直接右键获取代码),部分如下:




<HTML xmlns:ctls><HEAD><TITLE id=ARP>添加/删除程序</TITLE>

<META http-equiv=Content-Type content="text/html; charset=gb2312"><BASE language="JScript">

function MyFunc()

{

external.HelloWorld(); //HelloWorld是我们扩展的方法

}

</SCRIPT>

</head>

<body>

<input type="Button" value="Show hello world" onClick="MyFunc();" />

</body>

</html>



HelloWorld就是我们扩展的一个方法,当点击按钮的时候external对象会调用HelloWorld方法调用本地代码,对于external对象则会调用上面提到的GetExternal方法来查询是否提供了扩展,下面是如何实现GetExternal方法来实现扩展external对象,代码如下:

class MyDocHandler :public IDocHostUIHandler

{

long refcount;

public:

MyDocHandler() :refcount(1){ }

virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) {

if (classid == IID_IUnknown)

{

*intf = (IUnknown*)this;

AddRef();

}

else if (classid == IID_IDocHostUIHandler)

{

*intf = (IDocHostUIHandler*)this;

AddRef();

}

else if (classid == IID_IDispatch)

{

*intf = (IDispatch*)this;

AddRef();

}

else

return E_NOINTERFACE;

return S_OK;

}

virtual ULONG STDMETHODCALLTYPE AddRef() {

InterlockedIncrement(&refcount);

return refcount;

}

virtual ULONG STDMETHODCALLTYPE Release() {

InterlockedDecrement(&refcount);

if (refcount == 0)

delete this;

return refcount;

}

//返回S_OK,屏蔽掉右键菜单

virtual HRESULT STDMETHODCALLTYPE ShowContextMenu(

/* [in] */ DWORD dwID,

/* [in] */ POINT __RPC_FAR *ppt,

/* [in] */ IUnknown __RPC_FAR *pcmdtReserved,

/* [in] */ IDispatch __RPC_FAR *pdispReserved) {

return S_OK;

}

virtual HRESULT STDMETHODCALLTYPE GetHostInfo(

/* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE ShowUI(

/* [in] */ DWORD dwID,

/* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject,

/* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget,

/* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame,

/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE HideUI( void) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE UpdateUI( void) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE EnableModeless(

/* [in] */ BOOL fEnable) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE OnDocWindowActivate(

/* [in] */ BOOL fActivate) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE OnFrameWindowActivate(

/* [in] */ BOOL fActivate) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE ResizeBorder(

/* [in] */ LPCRECT prcBorder,

/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow,

/* [in] */ BOOL fRameWindow) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE TranslateAccelerator(

/* [in] */ LPMSG lpMsg,

/* [in] */ const GUID __RPC_FAR *pguidCmdGroup,

/* [in] */ DWORD nCmdID) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE GetOptionKeyPath(

/* [out] */ LPOLESTR __RPC_FAR *pchKey,

/* [in] */ DWORD dw) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE GetDropTarget(

/* [in] */ IDropTarget __RPC_FAR *pDropTarget,

/* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE GetExternal(

/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) {

*ppDispatch = new MyCommandHandler();

return S_OK;

}

virtual HRESULT STDMETHODCALLTYPE TranslateUrl(

/* [in] */ DWORD dwTranslate,

/* [in] */ OLECHAR __RPC_FAR *pchURLIn,

/* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) {

return E_NOTIMPL;

}

virtual HRESULT STDMETHODCALLTYPE FilterDataObject(

/* [in] */ IDataObject __RPC_FAR *pDO,

/* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) {

return E_NOTIMPL;

}

};


上面重载了ShowContextMenu方法屏蔽掉右键菜单,使用户不能得到网页代码,关于GetExternal是这样实现的:

virtual HRESULT STDMETHODCALLTYPE GetExternal(

/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) {

*ppDispatch = new MyCommandHandler();

return S_OK;

}


可以看到只是简单返回了MyCommandHandler对象,MyCommandHandler必须继承自IDispatch接口来实现支持自动化的调用方式,它是这样实现的:

class MyCommandHandler : public IDispatch

{

long refcount;

public:

MyCommandHandler() :refcount(1){ }

virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(

/* [out] */ UINT *pctinfo){

return S_OK;

}

virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(

/* [in] */ UINT iTInfo,

/* [in] */ LCID lcid,

/* [out] */ ITypeInfo **ppTInfo){

return S_OK;

}

virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(

/* [in] */ REFIID riid,

/* [size_is][in] */ LPOLESTR *rgszNames,

/* [in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ DISPID *rgDispId){

*rgDispId=1;

return S_OK;

}

virtual /* [local] */ HRESULT STDMETHODCALLTYPE 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==1)

{

MessageBox(0,"Hello World","Hello",0); //place your code here

frmweb->Edit1->Text="Hello World(这也可以?)";

}

return S_OK;

}

virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) {

if (classid == IID_IDispatch)

{

*intf = (IDispatch*)this;

AddRef();

}

else

return E_NOINTERFACE;

return S_OK;

}

virtual ULONG STDMETHODCALLTYPE AddRef() {

InterlockedIncrement(&refcount);

return refcount;

}

virtual ULONG STDMETHODCALLTYPE Release() {

InterlockedDecrement(&refcount);

if (refcount == 0)

delete this;

return refcount;

}

};


如果大家了解一些COM知识,我们知道这里关键的是GetIDsOfNames和Invoke方法的实现,因为自动化对象只能通过这样的方式来调用,而不能使用函数指针直接调用虚拟方法,GetIDsOfNames查询指定的函数名的调用ID,就是说如果有一个方法是“HelloWorld”,那么它会先调用GetIDsOfNames方法来查询这个方法是否支持,如果支持则给出该方法的调用ID(通过修改rgDispId[out]参数),如果不支持则返回E_NOTIMPL,他的实现简单如下:

virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(

/* [in] */ REFIID riid,

/* [size_is][in] */ LPOLESTR *rgszNames,

/* [in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ DISPID *rgDispId){

*rgDispId=1;

return S_OK;

}


这里我们假定了只有一个扩展函数HelloWorld,如果有多个,我们需要比较rgszNames参数的函数名返回不同的调用ID,有了调用ID,实现Invoke方法就很简单了:

virtual HRESULT STDMETHODCALLTYPE 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==1)

{

MessageBox(0,"Hello World","Hello",0); //place your code here

frmweb->Edit1->Text="Hello World(这也可以?)";

}

return S_OK;

}


根据dispIdMember的不同实现不同的代码,如果方法是有参数的可以在pDispParams中取得,如果有什么不明白可以参考MSDN和一些COM的书籍,这里就不详细解释了。

最后我们要做的就是使我们的浏览器知道我们扩展了external,代码如下:

dochandler = new MyDocHandler;

webBrowser->Navigate(WideString(L"E:\\Projects\\extWeb\\ext.htm"));

while(webBrowser->Busy)

Application->ProcessMessages();

ICustomDoc *custdoc;

webBrowser->Document->QueryInterface(&custdoc); //取得IcustomDoc接口

if (custdoc)

{

custdoc->SetUIHandler(dochandler); //设置我们自己的界面处理器

custdoc->Release();

}


注意上面的粗体“我们的浏览器”,因为这样的扩展仅针对与自己程序里使用WebBrowser控件,不影响IE本身的扩展,也就是说那个ext.htm文件只能在我们的程序中有效,就算其他用户得到了这段htm代码也不能正常运行的,如果你想测试,你得到的是:


因为他们并不知道如何扩展external对象,这点就解决了刚才我们说的使用COM的问题。

说句实话设计这样界面还是有一定难度的,那么它在实际开发中到底有什么好处呢?我想至少有以下几点:

1. 界面设计和程序逻辑设计分离,美工可以和程序员一起工作,界面设计再也不是没有审美细胞程序员的问题;

2. 轻松实现Skin功能,界面的改变不需要重新编译代码,只需要换一个不同htm代码文件就可以;

3. 再也无法使用Spy工具获得窗体Handler做各种Hook,使你的程序运行的更安全;

4. 充分使用IE现有技术,搭建功能更强大的软件;

5. 使你的软件看起来更酷,更专业。

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: