• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            S.l.e!ep.¢%

            像打了激速一樣,以四倍的速度運轉,開心的工作
            簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            一種全新的軟件界面設計方法_C++老窩...

            Posted on 2009-09-23 16:43 S.l.e!ep.¢% 閱讀(1069) 評論(0)  編輯 收藏 引用 所屬分類: 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 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. 使你的軟件看起來更酷,更專業。

            怎么樣?心動了嗎?趕快改善你的界面吧。

            yy6080久久| 日本精品久久久久中文字幕| 亚洲欧美精品伊人久久| 久久国语露脸国产精品电影| 午夜视频久久久久一区| 国产精品亚洲综合专区片高清久久久| 久久精品aⅴ无码中文字字幕重口| 国产激情久久久久久熟女老人| 久久久久久久免费视频| 亚洲国产精品综合久久网络| 日韩精品无码久久一区二区三| 精品国产综合区久久久久久| 狠狠色综合久久久久尤物| 久久天天躁狠狠躁夜夜av浪潮| 久久精品一区二区三区中文字幕| 99久久伊人精品综合观看| 国产巨作麻豆欧美亚洲综合久久 | 久久电影网一区| 日本免费久久久久久久网站| 岛国搬运www久久| 午夜精品久久久久成人| 亚洲国产精品无码久久久蜜芽| 7777久久久国产精品消防器材| 蜜臀av性久久久久蜜臀aⅴ麻豆 | 久久久噜噜噜www成人网| 精品久久久久久成人AV| 色成年激情久久综合| 久久综合视频网| 国产精品久久网| 久久涩综合| 狠狠色丁香婷综合久久| 久久中文精品无码中文字幕| 伊人久久大香线蕉AV色婷婷色| 97久久综合精品久久久综合| 国产精品成人精品久久久| 欧美国产成人久久精品| 亚洲国产二区三区久久| 国产激情久久久久久熟女老人| 成人亚洲欧美久久久久 | 青青草原综合久久大伊人导航| 无码国内精品久久综合88|