一种全新的软件界面设计方法
一种全新的软件界面设计方法
撰文:Aweay
你可转载,拷贝,但必须加入作者署名Aweay,如果用于商业目的,必须经过作者同意。
下载实例代码
关键字:COM MySpy IE SetUIHanlder IcustomDoc IDocHostUIHandler GetExternal
前言
作者在解决各种问题的时候喜欢首先使用C++ Builder来尝试,这篇文章也是这样,但这并不影响其他开发工具的使用者阅读,因为这都是微软的开发技术,选择什么工具并不重要,我们理解了他的原理可以使用任何工具实现同样的功能。
正文
使用过VC.Net的朋友可能知道,在VC.Net中全新提供了一种基于Web的界面设计方法,不过可能真正用到的人很少,至少我在国内的软件中没有看到过这样的界面设计方法。当初使用VC.net的时候就希望BCB的下个版本可以加入这样灵活的界面设计方法,但是到现在还没有等到,我想也不能一直这样等下去,于是就自己研究其中的实现方法,终于让我研究出来。这篇文章就是讨论这样方法,以及在软件设计设计中的可行性。
说了这么多,可能还有朋友不知道这样的界面到底有什么不同,有什么优点呢?如果你也有同样的好奇感的话,请你继续看下去。
在Windows2000下,大家经常使用控制面板/添加、卸载软件的对话框就是基于这样的界面(Xp下暂时不清楚),我不说出来可能很少有人知道-那个对话框整个就是个网页?什么你不相信?如果是网页为什么能和本地的计算机程序交互?为什么不能选择网页里面的文字?为什么不能弹出右键菜单?如果是网页,那它的html代码在那里?
为了证明上面的说法,我们需要一些特殊的软件,这个软件就是作者写的MySpy,可以到作者的站点()免费下载使用,我们可以从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提供了很多的虚拟方法,需要程序员来重载他们实现不同的定制功能,这里有一篇文章详细介绍了这些信息,在这里我们需要重载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. 使你的软件看起来更酷,更专业。
怎么样?心动了吗?赶快改善你的界面吧。如果你有更多想法,可以通过 siney@yeah.net 取得联系。
一种全新的软件界面设计方法相关推荐
- 一种全新的软件界面设计方法(续)
一种全新的软件界面设计方法(续) 点击下载文章所附代码 撰文:Aweay 你可转载,拷贝,但必须加入作者署名Aweay,如果用于商业目的,必须经过作者同意. 前段时间,笔者写了一篇关于使用Web页面设 ...
- 一种全新的深亚微米IC设计方法
一种全新的深亚微米IC设计方法 类别:电子综合 阅读:910 本文分析了传统IC设计流程存在的一些缺陷,并且提出了一种基于Logical Effort理论的全新IC设计方法. 众所周知 ...
- 如何做好软件界面设计?2个方法一次掌握!
软件界面设计通常是指 UI 设计-- User Interface 的简称.UI 设计是指对软件的人机交互.操作逻辑.界面美观的整体设计. 好的软件界面设计要做到清晰.舒适.美观.要想做好软件界面设计 ...
- 界面设计方法 (1) — 2.活动功能的设计
前文已介绍过了,业务功能分为4大类,其中"活动功能"是界面设计中工作量最大的部分,每个活动功能都是客户一个/类实际工作在系统中的映射,客户对包括对业务处理.管理控制方面的需求.优化 ...
- 界面设计方法(2)— 5.功能按钮设计(新增,查询)
对界面上功能按钮的设计,很多人认为:这是技术问题,交给程序员就行了.这个想法只对了一半,因其仅考虑了按钮做为"操作系统"的功能(如:保存数据),而忽略了按钮作为承载"业务 ...
- 界面设计方法 (2) — 3.卡式, 列表, 主细表, 树形, 页签
企业管理类型系统采用的PC端界面形式常见的有5种 (卡式.列表.主细表.树形.页签),以及以这5种形式为基础的组合形式.确定一个业务功能采用什么样的界面形式表达的效果最佳,一般是综合了客户的业务需求. ...
- 界面设计方法(2) — 2.界面的布局
为了满足客户的需求,软件界面的表达形式千差万别.但与网站的界面形式(电子商务.政府政务.各类网站等)相比,作为企业管理(ERP)类系统的界面形式比较低调,由于需要长时间对着屏幕操作.观看.思考,因此要 ...
- 界面设计方法 (2) — 1. 界面与组件的概念
■ 一个完整的界面设计需要有两个层面:业务功能.应用功能.系列"面设计方法(一)"已经从业务功能(活动.字典.看板和表单)层面介绍了4种功能的设计方法,它们的重点是如何完成不同类型 ...
- 界面设计方法 (1) — 3. 字典功能的设计
理解字典功能,首先要理解它是建立企业基础数据标准的手段,其次才是它对基础数据的记录和维护功能.构建企业信息系统过程中有两个重要的标准化工作:业务流程的标准化.基础数据的标准化.其中:业务流程标准化是通 ...
最新文章
- BERT-Pytorch demo初探
- 电路与电子学-第一章直流电路分析方法小概括
- html用颜色区分不同区间数据_最新数据可视化指南
- 物联网 终端设备_您拥有多少个物联网设备?
- 学习 | Spring Cloud Config 从入门到精通
- php 编程祝新年快乐_第一门编程语言选什么好?
- 4-1 :input表单选择器 jQuery第四章 很关键 好像 刚好可以解决 微信自动回复...
- 字符串的concat方法_字符串concat()方法
- 【javaEE】——多线程进阶(锁策略:面试相关考点)04
- SPECT/PECT成像原理
- 从 Exadata 到 TiDB,中通快递 HTAP 实践
- HTML5+CSS3新特性
- SSL与数字证书,Htpps
- 论“渤海—黄海开凿人工运河”
- 搞机器学习需要哪些技能
- QT 实现Label上画线
- 《数据结构与算法基础 严蔚敏版》第三章 堆栈与队列
- 本机修改虚拟机linux中的代码文件
- 删除计算机的用户凭据,windows凭据怎么填写? 电脑添加删除Windows凭据的技巧
- jsp处理的生命周期
热门文章
- 互联网晚报 | 10月23日 星期六 | 恒大宣布全面实施现楼销售;华为开发者大会2021开幕;家乐福中国首家会员店正式开业...
- 2021上半年短视频及电商生态研究报告
- 输入学生的个数,姓名,成绩,然后按照学生的成绩的降序来打印学生的姓名
- css元素隐藏不可获取,Css隐藏元素(display,visibility)的区别
- python输出关键字的行号_python – 使用关键字行号创建字典
- 客座编辑:杜小勇(1963-),男,中国人民大学信息学院教授,博士生导师,教育部数据工程与知识工程重点实验室主任。...
- 作者:蓝梦微, 女, 中国人民大学信息学院博士生,CCF学生会员。
- 约数研究问题的算法优化和推导证明(洛谷P1403题题解,Java语言描述,含Latex公式编辑知识)
- Python suds error “'NoneType' object has no attribute 'promotePrefixes'”
- 聊聊zxing的qrcode