一種全新的軟體介面設計方法

來源:互聯網
上載者:User

一種全新的軟體介面設計方法

撰文:Aweay

你可轉載,拷貝,但必須加入作者署名Aweay,如果用於商業目的,必須經過作者同意。

下載執行個體代碼

關鍵字: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方法的實現,因為Automation 物件只能通過這樣的方式來調用,而不能使用函數指標直接調用虛擬方法,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 取得聯絡。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.