首页 > 开发 > 综合 > 正文

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

2024-07-21 02:16:18
字体:
来源:转载
供稿:网友
关键字: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 href=res://appwiz.cpl/><link href="arp.css" type=text/css rel=stylesheet>

<style>>ctls/:places { behavior: url(places.htc); }ctls/:listbox { 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: '4px 2px 2px 2px';}.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;}</style>





嗬嗬,是不是很神奇呢,这只是一个应用的例子,其实还有很多软件的界面使用了上面的方法来创建界面,比如norton antivirsu,ms visual studio.net,c# builder等。其实深入仔细思考的话,这样的界面最困难的是如何和本地代码交互,为什么在网页里点一个按钮能执行自己的代码呢?有过com编程经验的人,可能会想到用com编写一个外部对象,在网页中使用脚本创建这个对象,然后调用对象的方法似乎可以完成这样的功能?但是这里有很多不好的地方:

1. 需要注册com的本地运行安全,否则ie会有安全警告,这肯定是最终用户不愿意看到的;

2. 用户可以轻松从html代码里获得com对象的使用方法(就像上面用myspy获得代码一样),这样他们可以轻松使用你的com对象完成他们自己的界面,这样不够隐蔽,不安全。

也许还有更多不好的地方,但暂时作者没有想到,因为微软及其他软件公司都不是这样做的,他们也许知道更多。下面我们就来讨论一种既安全又隐蔽的实现方法。

从ie4开始,微软提供了一个icustomdoc接口,icustomdoc的setuihandler允许用户设置一个基于idochostuihandler的接口来接管界面处理器,在idochostuihandler提供了很多的虚拟方法,需要程序员来重载他们实现不同的定制功能,这里有一篇文章详细介绍了这些信息http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/hosting/wbcustomization.asp,在这里我们需要重载getexternal方法来扩展ie dom,如果我们成功的扩展了dom,那么我们就这可以这样编写html代码来实现与本地程序交互,例如:






<html>

<head>

<script 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. 使你的软件看起来更酷,更专业。

商业源码热门下载www.html.org.cn

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表