查看: 928|回复: 0

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

[复制链接]
发表于 2009-5-13 02:05:14 | 显示全部楼层 |阅读模式
前言 <br /><br />作者在解决各种问题的时候喜欢首先使用C++ Builder来尝试,这篇文章也是这样,但这并不影响其他开发工具的使用者阅读,因为这都是微软的开发技术,选择什么工具并不重要,我们理解了他的原理可以使用任何工具实现同样的功能。 <br /><br />正文 <br /><br />使用过VC.Net的朋友可能知道,在VC.Net中全新提供了一种基于Web的界面设计方法,不过可能真正用到的人很少,至少我在国内的软件中没有看到过这样的界面设计方法。当初使用VC.net的时候就希望BCB的下个版本可以加入这样灵活的界面设计方法,但是到现在还没有等到,我想也不能一直这样等下去,于是就自己研究其中的实现方法,终于让我研究出来。这篇文章就是讨论这样方法,以及在软件设计设计中的可行性。 <br /><br />说了这么多,可能还有朋友不知道这样的界面到底有什么不同,有什么优点呢?如果你也有同样的好奇感的话,请你继续看下去。 <br /><br />在Windows2000下,大家经常使用控制面板/添加、卸载软件的对话框就是基于这样的界面(Xp下暂时不清楚),我不说出来可能很少有人知道-那个对话框整个就是个网页?什么你不相信?如果是网页为什么能和本地的计算机程序交互?为什么不能选择网页里面的文字?为什么不能弹出右键菜单?如果是网页,那它的html代码在那里? <br /><br />为了证明上面的说法,我们需要一些特殊的软件,这个软件就是作者写的MySpy,可以到作者的站点(http://siney.yeah.net)免费下载使用,我们可以从MySpy的界面中看到添加/删除程序的对话框是个Internet Explorer_Server,这说明它是个网页, <br /><br /><br />在MySpy的Web页面还可以看到这个页面的地址是:res://sp3res.dll/default.hta, <br /><br /><br /><br /><br />近一步使用MySpy得到这个网页的代码(不能直接右键获取代码),部分如下: <br /><br /><br /><br /><br />&lt;HTML xmlns:ctls&gt;&lt;HEAD&gt;&lt;TITLE id=ARP&gt;添加/删除程序&lt;/TITLE&gt; <br /><br />&lt;META http-equiv=Content-Type content=&quot;text/html; charset=gb2312&quot;&gt;&lt;BASE href=res://appwiz.cpl/&gt;&lt;LINK href=&quot;arp.css&quot; type=text/css rel=stylesheet&gt; <br /><br />&lt;STYLE&gt;&gt;ctls\LACES { behavior: url(places.htc); }ctls\ISTBOX { behavior: url(listbox.htc); }ctls\:ACCEL { behavior: url(accel.htc); }.PlacesBar {background-color:threedshadow}.Hide {display:none}.NonClientBackground { background-color: buttonface;}.Header { padding-bottom: 5px;vertical-align: text-top; }.GroupImage { margin-right: 5px;}.GroupDesc {padding-left: 1em;padding-right: 1em;}.AppNameRow {}.AppImageTD {width: 20px; padding: &#39;4px 2px 2px 2px&#39;;}.InfoPane { padding-top:4px; vertical-align: top;}.PropLabel {width: 7em;padding-top: 2px;padding-bottom: 2px;padding-right: 3px;text-align: right;}.PropValue {width: 6em;text-align: right;padding-right: 7px;}.AddPropLabel {padding-top: 2px;padding-bottom: 2px;padding-right: 3px;text-align: right;}.AddPropValue {width: 13em;text-align: right;padding-right: 7px;}.ButtonDescPane { padding-top: 5px; padding-bottom: 7px;padding-right: 5px;}.ButtonPane { width: 15em; padding: 5px; text-align: right;}.FakeAnchor {cursor:hand;}#idClientCatName {font-weight: bold;padding-bottom: 1ex;}.disabled {color: graytext;}#idTblExtendedProps.Focus {color: highlighttext;}&lt;/STYLE&gt; <br /><br /><br /><br /><br /><br />嗬嗬,是不是很神奇呢,这只是一个应用的例子,其实还有很多软件的界面使用了上面的方法来创建界面,比如Norton AntiVirsu,MS Visual Studio.net,C# Builder等。其实深入仔细思考的话,这样的界面最困难的是如何和本地代码交互,为什么在网页里点一个按钮能执行自己的代码呢?有过COM编程经验的人,可能会想到用COM编写一个外部对象,在网页中使用脚本创建这个对象,然后调用对象的方法似乎可以完成这样的功能?但是这里有很多不好的地方: <br /><br />1. 需要注册COM的本地运行安全,否则IE会有安全警告,这肯定是最终用户不愿意看到的; <br /><br />2. 用户可以轻松从html代码里获得COM对象的使用方法(就像上面用MySpy获得代码一样),这样他们可以轻松使用你的COM对象完成他们自己的界面,这样不够隐蔽,不安全。 <br /><br />也许还有更多不好的地方,但暂时作者没有想到,因为微软及其他软件公司都不是这样做的,他们也许知道更多。下面我们就来讨论一种既安全又隐蔽的实现方法。 <br /><br />从IE4开始,微软提供了一个ICustomDoc接口,ICustomDoc的SetUIHandler允许用户设置一个基于IDocHostUIHandler的接口来接管界面处理器,在IDocHostUIHandler提供了很多的虚拟方法,需要程序员来重载他们实现不同的定制功能,这里有一篇文章详细介绍了这些信息http://msdn.microsoft.com/librar ... wbcustomization.asp,在这里我们需要重载GetExternal方法来扩展IE DOM,如果我们成功的扩展了DOM,那么我们就这可以这样编写html代码来实现与本地程序交互,例如: <br /><br /><br /><br /><br /><br /><br />&lt;html&gt; <br /><br />&lt;head&gt; <br /><br />&lt;SCRIPT language=&quot;JScript&quot;&gt; <br /><br />function MyFunc() <br /><br />{ <br /><br />external.HelloWorld(); //HelloWorld是我们扩展的方法 <br /><br />} <br /><br />&lt;/SCRIPT&gt; <br /><br />&lt;/head&gt; <br /><br />&lt;body&gt; <br /><br />&lt;input type=&quot;Button&quot; value=&quot;Show hello world&quot; onClick=&quot;MyFunc();&quot; /&gt; <br /><br />&lt;/body&gt; <br /><br />&lt;/html&gt; <br /><br /><br /><br />HelloWorld就是我们扩展的一个方法,当点击按钮的时候external对象会调用HelloWorld方法调用本地代码,对于external对象则会调用上面提到的GetExternal方法来查询是否提供了扩展,下面是如何实现GetExternal方法来实现扩展external对象,代码如下: <br /><br />class MyDocHandler :public IDocHostUIHandler <br /><br />{ <br /><br />long refcount; <br /><br />public: <br /><br />MyDocHandler() :refcount(1){ } <br /><br />virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) { <br /><br />if (classid == IID_IUnknown) <br /><br />{ <br /><br />*intf = (IUnknown*)this; <br /><br />AddRef(); <br /><br />} <br /><br />else if (classid == IID_IDocHostUIHandler) <br /><br />{ <br /><br />*intf = (IDocHostUIHandler*)this; <br /><br />AddRef(); <br /><br />} <br /><br />else if (classid == IID_IDispatch) <br /><br />{ <br /><br />*intf = (IDispatch*)this; <br /><br />AddRef(); <br /><br />} <br /><br />else <br /><br />return E_NOINTERFACE; <br /><br />return S_OK; <br /><br />} <br /><br />virtual ULONG STDMETHODCALLTYPE AddRef() { <br /><br />InterlockedIncrement(&amp;refcount); <br /><br />return refcount; <br /><br />} <br /><br />virtual ULONG STDMETHODCALLTYPE Release() { <br /><br />InterlockedDecrement(&amp;refcount); <br /><br />if (refcount == 0) <br /><br />delete this; <br /><br />return refcount; <br /><br />} <br /><br />//返回S_OK,屏蔽掉右键菜单 <br /><br />virtual HRESULT STDMETHODCALLTYPE ShowContextMenu( <br /><br />/* [in] */ DWORD dwID, <br /><br />/* [in] */ POINT __RPC_FAR *ppt, <br /><br />/* [in] */ IUnknown __RPC_FAR *pcmdtReserved, <br /><br />/* [in] */ IDispatch __RPC_FAR *pdispReserved) { <br /><br />return S_OK; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetHostInfo( <br /><br />/* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE ShowUI( <br /><br />/* [in] */ DWORD dwID, <br /><br />/* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject, <br /><br />/* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget, <br /><br />/* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame, <br /><br />/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE HideUI( void) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE UpdateUI( void) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE EnableModeless( <br /><br />/* [in] */ BOOL fEnable) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE OnDocWindowActivate( <br /><br />/* [in] */ BOOL fActivate) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE OnFrameWindowActivate( <br /><br />/* [in] */ BOOL fActivate) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE ResizeBorder( <br /><br />/* [in] */ LPCRECT prcBorder, <br /><br />/* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow, <br /><br />/* [in] */ BOOL fRameWindow) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE TranslateAccelerator( <br /><br />/* [in] */ LPMSG lpMsg, <br /><br />/* [in] */ const GUID __RPC_FAR *pguidCmdGroup, <br /><br />/* [in] */ DWORD nCmdID) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetOptionKeyPath( <br /><br />/* [out] */ LPOLESTR __RPC_FAR *pchKey, <br /><br />/* [in] */ DWORD dw) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetDropTarget( <br /><br />/* [in] */ IDropTarget __RPC_FAR *pDropTarget, <br /><br />/* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetExternal( <br /><br />/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { <br /><br />*ppDispatch = new MyCommandHandler(); <br /><br />return S_OK; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE TranslateUrl( <br /><br />/* [in] */ DWORD dwTranslate, <br /><br />/* [in] */ OLECHAR __RPC_FAR *pchURLIn, <br /><br />/* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE FilterDataObject( <br /><br />/* [in] */ IDataObject __RPC_FAR *pDO, <br /><br />/* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { <br /><br />return E_NOTIMPL; <br /><br />} <br /><br />}; <br /><br /><br />上面重载了ShowContextMenu方法屏蔽掉右键菜单,使用户不能得到网页代码,关于GetExternal是这样实现的: <br /><br />virtual HRESULT STDMETHODCALLTYPE GetExternal( <br /><br />/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { <br /><br />*ppDispatch = new MyCommandHandler(); <br /><br />return S_OK; <br /><br />} <br /><br /><br />可以看到只是简单返回了MyCommandHandler对象,MyCommandHandler必须继承自IDispatch接口来实现支持自动化的调用方式,它是这样实现的: <br /><br />class MyCommandHandler : public IDispatch <br /><br />{ <br /><br />long refcount; <br /><br />public: <br /><br />MyCommandHandler() :refcount(1){ } <br /><br />virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( <br /><br />/* [out] */ UINT *pctinfo){ <br /><br />return S_OK; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( <br /><br />/* [in] */ UINT iTInfo, <br /><br />/* [in] */ LCID lcid, <br /><br />/* [out] */ ITypeInfo **ppTInfo){ <br /><br />return S_OK; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( <br /><br />/* [in] */ REFIID riid, <br /><br />/* [size_is][in] */ LPOLESTR *rgszNames, <br /><br />/* [in] */ UINT cNames, <br /><br />/* [in] */ LCID lcid, <br /><br />/* [size_is][out] */ DISPID *rgDispId){ <br /><br />*rgDispId=1; <br /><br />return S_OK; <br /><br />} <br /><br />virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( <br /><br />/* [in] */ DISPID dispIdMember, <br /><br />/* [in] */ REFIID riid, <br /><br />/* [in] */ LCID lcid, <br /><br />/* [in] */ WORD wFlags, <br /><br />/* [out][in] */ DISPPARAMS *pDispParams, <br /><br />/* [out] */ VARIANT *pVarResult, <br /><br />/* [out] */ EXCEPINFO *pExcepInfo, <br /><br />/* [out] */ UINT *puArgErr){ <br /><br />if(dispIdMember==1) <br /><br />{ <br /><br />MessageBox(0,&quot;Hello World&quot;,&quot;Hello&quot;,0); //place your code here <br /><br />frmweb-&gt;Edit1-&gt;Text=&quot;Hello World(这也可以?)&quot;; <br /><br />} <br /><br />return S_OK; <br /><br />} <br /><br />virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID classid, void** intf) { <br /><br />if (classid == IID_IDispatch) <br /><br />{ <br /><br />*intf = (IDispatch*)this; <br /><br />AddRef(); <br /><br />} <br /><br />else <br /><br />return E_NOINTERFACE; <br /><br />return S_OK; <br /><br />} <br /><br />virtual ULONG STDMETHODCALLTYPE AddRef() { <br /><br />InterlockedIncrement(&amp;refcount); <br /><br />return refcount; <br /><br />} <br /><br />virtual ULONG STDMETHODCALLTYPE Release() { <br /><br />InterlockedDecrement(&amp;refcount); <br /><br />if (refcount == 0) <br /><br />delete this; <br /><br />return refcount; <br /><br />} <br /><br />}; <br /><br /><br />如果大家了解一些COM知识,我们知道这里关键的是GetIDsOfNames和Invoke方法的实现,因为自动化对象只能通过这样的方式来调用,而不能使用函数指针直接调用虚拟方法,GetIDsOfNames查询指定的函数名的调用ID,就是说如果有一个方法是“HelloWorld”,那么它会先调用GetIDsOfNames方法来查询这个方法是否支持,如果支持则给出该方法的调用ID(通过修改rgDispId[out]参数),如果不支持则返回E_NOTIMPL,他的实现简单如下: <br /><br />virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( <br /><br />/* [in] */ REFIID riid, <br /><br />/* [size_is][in] */ LPOLESTR *rgszNames, <br /><br />/* [in] */ UINT cNames, <br /><br />/* [in] */ LCID lcid, <br /><br />/* [size_is][out] */ DISPID *rgDispId){ <br /><br />*rgDispId=1; <br /><br />return S_OK; <br /><br />} <br /><br /><br />这里我们假定了只有一个扩展函数HelloWorld,如果有多个,我们需要比较rgszNames参数的函数名返回不同的调用ID,有了调用ID,实现Invoke方法就很简单了: <br /><br />virtual HRESULT STDMETHODCALLTYPE Invoke( <br /><br />/* [in] */ DISPID dispIdMember, <br /><br />/* [in] */ REFIID riid, <br /><br />/* [in] */ LCID lcid, <br /><br />/* [in] */ WORD wFlags, <br /><br />/* [out][in] */ DISPPARAMS *pDispParams, <br /><br />/* [out] */ VARIANT *pVarResult, <br /><br />/* [out] */ EXCEPINFO *pExcepInfo, <br /><br />/* [out] */ UINT *puArgErr){ <br /><br />if(dispIdMember==1) <br /><br />{ <br /><br />MessageBox(0,&quot;Hello World&quot;,&quot;Hello&quot;,0); //place your code here <br /><br />frmweb-&gt;Edit1-&gt;Text=&quot;Hello World(这也可以?)&quot;; <br /><br />} <br /><br />return S_OK; <br /><br />} <br /><br /><br />根据dispIdMember的不同实现不同的代码,如果方法是有参数的可以在pDispParams中取得,如果有什么不明白可以参考MSDN和一些COM的书籍,这里就不详细解释了。 <br /><br />最后我们要做的就是使我们的浏览器知道我们扩展了external,代码如下: <br /><br />dochandler = new MyDocHandler; <br /><br />webBrowser-&gt;Navigate(WideString(L&quot;E:\\Projects\\extWeb\\ext.htm&quot;)); <br /><br />while(webBrowser-&gt;Busy) <br /><br />Application-&gtrocessMessages(); <br /><br />ICustomDoc *custdoc; <br /><br />webBrowser-&gt;Document-&gt;QueryInterface(&amp;custdoc); //取得IcustomDoc接口 <br /><br />if (custdoc) <br /><br />{ <br /><br />custdoc-&gt;SetUIHandler(dochandler); //设置我们自己的界面处理器 <br /><br />custdoc-&gt;Release(); <br /><br />} <br /><br /><br />注意上面的粗体“我们的浏览器”,因为这样的扩展仅针对与自己程序里使用WebBrowser控件,不影响IE本身的扩展,也就是说那个ext.htm文件只能在我们的程序中有效,就算其他用户得到了这段htm代码也不能正常运行的,如果你想测试,你得到的是: <br /><br /><br />因为他们并不知道如何扩展external对象,这点就解决了刚才我们说的使用COM的问题。 <br /><br />说句实话设计这样界面还是有一定难度的,那么它在实际开发中到底有什么好处呢?我想至少有以下几点: <br /><br />1. 界面设计和程序逻辑设计分离,美工可以和程序员一起工作,界面设计再也不是没有审美细胞程序员的问题; <br /><br />2. 轻松实现Skin功能,界面的改变不需要重新编译代码,只需要换一个不同htm代码文件就可以; <br /><br />3. 再也无法使用Spy工具获得窗体Handler做各种Hook,使你的程序运行的更安全; <br /><br />4. 充分使用IE现有技术,搭建功能更强大的软件; <br /><br />5. 使你的软件看起来更酷,更专业。 <br /><br />怎么样?心动了吗?赶快改善你的界面吧。<br /><br /><blockquote class="blockquote">From: http://www.iconstars.cn/read.php?fid=20&tid=139&toread=1  Powered by PHPWind.com</blockquote>
回复

使用道具 举报

本版积分规则

关注公众号

相关侵权、举报、投诉及建议等,请发 E-mail:admin@discuz.vip

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖
关注公众号
QQ客服返回顶部