天天看點

用浏覽器控件做界面,網頁界面中定義自己的程式事件

1 引言

在用Delphi、Visual Basic等可視化快速開發工具編寫Windows應用程式時,常會遇到這樣幾個問題:

1) 希望程式界面美觀。在Delphi中,開發人員通常使用各種控件來實作界面的風格化,但缺點是造成應用程式體積較大,且在更新時常會被控件版本與Delphi版本不相容帶來的問題所困擾。

2) 希望應用程式在功能不變的情況下具有不同的界面風格。這常常通過換"皮膚"的技術來實作,但一般實作"換膚"功能的控件體積都較大,且界面反應速度比較慢,而且 "皮膚"的制作比較麻煩。

3) 程式界面的維護困難。為了使界面與代碼實作相分離而獲得"換膚"等靈活性,通常要用到一些設計模式的技術,這對于不熟悉設計模式的開發人員來說比較困難。

推廣關鍵字:

設計線上網站群: 中國工業設計線上 | 中國平面設計線上 | 中國環境設計 線上 | 中國數位設計線上 ….. 簡體中文版 english version, 設計資訊 設計視角 設計長廊 設計引擎 設計沙龍 設計書局 設計商會. …

專業網站建設 深圳專業網站設計公司 優秀網站設計公司 網站設計公司 知名網站設計公司 深圳網站設計公司 多媒體設計公司.

這裡有數十個用psp制作的網頁精美例子。有psd格式供下載下傳,可以修改。進入…… 本站原創的與photoshop有關的技巧。有基本功能介紹、工具實用、圖象設計技巧、 字型特效、網絡上的應用和進階圖象編輯技巧等。 從此進入……

設計酷–最酷的設計資源網站,虛拟主機,網頁設計,網站建設,網站制作, 建設網站,網站策劃,電子商務,網頁制作,ASP Web,電子政務.

平面設計, 數位影像, CG交流, 互動媒體, 藝術理論, 網絡媒體, 動漫園地, Designers and creativers home! 網站首頁 », 站務Blog 注冊成為會員 站長信箱. 七色鳥設計論壇 [點選進入] 注冊 - 登陸. 平面設計 …

微軟公司預計将于2006年釋出下一代作業系統(開發代号為Longhorn)中,應用程式的結構及部署将有重大變革,其中一項就是應用程式的界面完全以XML的一個擴充集XAML語言來描述,以便達到界面的高度可定制性。這無疑能夠友善地解決上述幾個問題。問題是在目前來說有沒有類似的方法呢?答案就是使用浏覽器控件。

微軟公司的網頁浏覽器Internet Explorer的核心被設計為可以嵌入到應用程式中重用的ActiveX元件,它有極強的可程式設計能力和與容器互動的能力,使得開發人員能夠快速地開發出功能強勁的應用程式。從下面的Internet Explorer的架構圖可以看到,我們平常運作的iexplorer.exe其實隻是一個外殼程式,真正的浏覽網頁、記錄曆史等工作是由嵌入其視窗的封裝在shdocvw.dll中的WebBrowser Control來完成的。

Shdocvw.dll的功能則是調用mshtml.dll來解析網頁,以及在它的視窗中嵌入其它活動文檔元件(如Microsoft Office、Adobe Acrobat等應用程式的文檔都可以嵌入到浏覽器視窗中檢視)。而mshtml.dll一方面處理HTML解析以及作為腳本引擎、java虛拟機、ActiveX控件、插件的宿主,另一方面,它實作了活動文檔伺服器接口,允許應用程式以标準的COM接口來把它嵌入到程式中并通過它暴露的接口來通路其中的網頁及網頁元素。

通過shdocvw.dll提供的豐富接口,網頁中的元素可以通路外殼應用程式提供的屬性和方法(如window.external.AddFavorite(location.href, document.title)則是調用IE的AddFavorite方法把目前頁添加到收藏夾),而通過mshtml.dll提供的接口,外殼應用程式則反過來可以通路網頁中元素的屬性、方法、行為、事件等等。解決文章開頭提出的幾個問題的方法就是基于shdocvw.dll和mshtml.dll實作的。一些著名軟體如:Microsoft Money、Microsoft Visual Studio .NET、Macromedia Dreamweaver MX 2004等都運用了這種技術。

2 原理

1) 程式的界面完全由制作網頁來完成。網頁在文字、圖像、聲音等方面具有強大的表現能力,運用所見即所得的網頁制作工具可以輕松制作出圖文并茂的網頁。以網頁作為程式的界面,其效果勝過任何界面控件。

2) "換膚"功能容易實作。隻需制作不同風格的網頁,即可輕松實作樣式各異的程式界面。

3) 程式的功能在應用程式内部編寫代碼來實作,并通過一個自動化接口提供給網頁中的元素調用。這就實作了程式界面和代碼的分離,網頁布局及風格的改變不會影響到程式的實作。

3 從網頁調用外殼程式的屬性和方法

3.1 GetExternal接口方法

WebBrowser Control提供的接口使得外殼應用程式可以用自己的對象、方法和屬性等來擴充IE的對象模型(DOM),以達到個性化定制的目的。在網頁中通路外殼應用程式的擴充則通過文檔的"external"對象來實作,如外殼程式提供了名為AddFavorite的方法,網頁中就通過window.external.AddFavorite()來調用。實作這一功能的核心是IDocHostUIHandler接口的GetExternal方法:

HRESULT GetExternal(IDispatch **ppDispatch);

在自定義的WebBrowser Control中實作IDocHostUIHandler接口,當網頁元素通過"external"對象通路外殼擴充的屬性和方法時,GetExternal方法就會被調用,在此方法的中将實作外殼程式屬性和方法的自動化接口傳遞給ppDispatch即可。自定義的WebBrowser Control示例代碼如下,在其中将GetExternal包裝為OnGetExternal事件供外部程式調用。IDocHostUIHandler接口有15個方法,此處我們隻關心GetExternal方法,故略去其餘14個(省略号處為略去的代碼)。

unit ZoCWebBrowser;

interface

uses

Variants,IEConst, Windows, SysUtils, Classes, SHDocVw, ActiveX, shlObj, MSHTML, comobj;

type

……

TGetExternalEvent = function(out ppDispatch: IDispatch): HRESULT of object;       //定義OnGetExternal事件類型

TZoCWebBrowser = class(TWebBrowser, IDocHostUIHandler)

private

  ……

  FOnGetExternal: TGetExternalEvent;

protected

  function GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;

published

  property OnGetExternal: TGetExternalEvent read FOnGetExternal write FOnGetExternal;

end;

implementation

function TZoCWebBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT;

begin

if Assigned(FOnGetExternal) then

  Result := FOnGetExternal(ppDispatch)

else

  Result := S_FALSE;

initialization

OleInitialize(nil);

finalization

try

  OleUninitialize;

except

end.

3.2 實作外殼程式擴充自動化接口

在Delphi的"New Items"對話框中,切換到"ActiveX"頁,選擇"Automation Object",建立一個自動化對象,并在"CoClass Name"一欄中填入接口名"MyExternal","Instancing"選擇為"Internal",表示該對象隻能在程式内部被建立,外部程式不能直接建立。點選"OK"按鈕後在Type Library編輯對話框中為IMyExternal接口添加兩個方法ShowAboutBox和SwitchUI,此時代碼大緻如下所示:

unit MyExternalImpl;

{$WARN SYMBOL_PLATFORM OFF}

ComObj, ActiveX, Project1_TLB, StdVcl;

TMyExternal = class(TAutoObject, IMyExternal)

  procedure ShowAboutBox; safecall;

  procedure SwitchUI; safecall;

uses ComServ;

procedure TMyExternal.ShowAboutBox;

MessageBox(MainForm.Handle, ‘GetExternal Demo’, ‘ZoCWebBrowser’, MB_OK or MB_ICONASTERISK);

procedure TMyExternal.SwitchUI;

ShowSwitchUIForm;     //顯示切換程式界面對話框

TAutoObjectFactory.Create(ComServer, TMyExternal,

  Class_MyExternal, ciInternal, tmApartment);

3.3 從網頁中調用外殼程式接口

在程式主視窗中放置一個自定義的WebBrowser Control,命名為ZoCWebBrowser,編寫它的OnGetExternal事件(由網頁中的window.external調用觸發),代碼如下:

function TMainForm.ZoCWebBrowserGetExternal(

out ppDispatch: IDispatch): HRESULT;

var

MyExternal: TMyExternal;

MyExternal:= TMyExternal.Create;       //建立實作自動化接口的對象

ppDispatch :=MyExternal;   //将對象接口傳遞給WebBrowser Control

//這樣當"external"對象被調用時,真正被調用的是我們實作的TMyExternal對象

Result :=S_OK;

假設我們制作了兩個風格迥異的的網頁Style1.html和Style2.html作為程式界面,這兩個網頁中都有兩個按鈕(也可以是其它網頁元素),其HTML代碼示例如下:

關于

切換界面

在程式開始運作時讓WebBrowser Control布滿整個Form,且顯示Style1.html頁面,則當點選"關于"按鈕時程式将顯示一個關于資訊對話框,而點選"切換界面"按鈕時将顯示切換界面的對話框,在其中選擇Style2.html并讓WebBrowser Control顯示它即可獲得風格完全不同的界面,但在功能上與Style1.html完全一樣。

4 總結

從上面的例子可以看到,我們以及其簡單的方式實作了程式界面與實作的分離,這有利于程式的維護和擴充。傳統方式下,界面設計和編碼通常都由程式員來完成,一來造成程式員負擔較重,二來難以保證界面品質。實用上述方法,程式界面可以由專業美勞工員來設計,他可以在完全不知道程式如何實作的情況下設計出完整的界面,而程式員隻需專注于代碼的編寫,并将必要的方法和屬性通過一個自動化接口暴露出來。合并的時候,在網頁中合适的位置放入所需的按鈕或其它網頁元素,并賦予簡單的腳本調用即可。

(以上代碼均在WindowsXP+Delphi 7環境下調試通過)

5 參考文獻

《MSDN Library - July 2003》

C# 版

使web 頁面回調本地應用程式。C# 

在開放windows應用時,經常會用到浏覽器元件,使你的應用程式能夠顯示web頁面。 但是web頁面和你的應用程式是獨立的兩部分,他們無法進行互動。 能否有辦法讓你的web(html)頁面,回調你應用程式中的函數或方法呢?

在開放windows應用時,經常會用到浏覽器元件,使你的應用程式能夠顯示web頁面。 但是web頁面和你的應用程式是獨立的兩部分,他們無法進行互動。 能否有辦法讓你的web(html)頁面,回調你應用程式中的函數或方法呢? 使web程式和你的本地應用程式混為一體,下面這篇文章提供了通過microsoft api 使web 頁面回調應用程式的途徑。 相關例子到這裡下載下傳 ,本例為C#編寫。

Hosting a webpage inside a Windows Form

Introduction

This article discusses my journey toward having a web page run inside of a WinForm client and fire events back to it. The sample app which is included provides the implementation of this article. While the sample application could have been done in an easier fashion (all ASP.NET for example) the intent is to provide a working model that can implement more difficult designs.

The Application

The application consists of a WinForm application which is broken into two sections:

The right side is the web app

The left side is the winForm pieces that gets populated based on what was clicked in the web

The application implements the IDocHostUIHandler interface which will allow MSHTML to do callbacks into my code. It then displays the webpage inside the IE container. When a button is clicked, a javascript component is called with a text description of what was clicked on. The javascript passes this data to the external method which will populate the form with data. If data is not found, the purchase button is disabled.

To make the application work on your machine, alter the application config file (SampleEventProgram.exe.config) in the BINRelease directory and change the path to where the sampleweb application is located on your machine.

The Research

Not being a raw, C++ COM developer - or even knowing much about the internals of the IE engine - I began my deployment by researching on the Internet. The first day I searched the web for anything on putting IE inside a form. I bring this up because it led me to a webpage which appeared to be a BLOG. I read it out of interest to see why the heck it showed up in my search. In it, the author said:

"In my growing years of development, I have had several unanswered questions arise. [...] Why is it so hard to implement a web browser inside a windows form and take control of it?"

I probably should have taken this as a warning, but I plunged forward in my quest. After all, MS had years to improve this proces….right? Over here at CodeProject, I came upon a discussion thread that died with no conclusive help, as well as articles by Wiley Techology Publishing and Nikhil Dabas. The first article was well written, but the most important part of the piece (implementing IDocHostUIHandler and ICustomDoc) were taken offline and done in Delphi! Nikhil’s article, however, had a fine discussion on implementing the interface as well as a deployed DLL for the interfaces in his sample application!

However, his deployment for hooking events required that you know the specific controls on the webform and then sink their click events. It also did not allow the web app to send any information back to the winform client. This is great for having the click events dive directly into code. But I needed the HTML object to tell me some information about what was clicked. So while I finally got IDocHostUIHandler implemented, I still did not get my last piece done and working. I was stuck for weeks in a continuous result of ‘object null or not an object’.

I had a few hints such as looking into GetExternal and I could SWEAR that a post suggested using window.getExternal in my javascript. Obviously that didn’t get me very far since I have since learned that is not a valid javascript call. I also got some suggestions on implementing IDispatch. But nothing really seemed to take the final step of scripting my program.

A lengthy two-day discussion with CP member .S.Rod. finally led to a better understanding and a great assistance in getting everything tied together and working. The most interesting thing with all of this research is that I talked to maybe four different people and got four different implementation approaches. I am sure that in each of those, the person in the discussion had an approach that eventually worked for them. Unfortunately, it was not until my final discussion that I had one that got me past the null object problem.

The only other drawback to all of this research was that I found I was occasionally killing myself by taking input from several people, combining it all together, and having conflicts with what was already done. To make matters worse, I was given a new computer in the middle of all of this and spent two days getting everything back to normal! It was just when I was ready to walk away from this project for awhile that .S.Rod. was kind enough to pull everything together for me. Here are the final results, and a sample application to help guide others in their quest to control IE.

The Code

For this application I am going to have a webpage present buttons and graphics for a product catalog. Clicking on a button in the webpage will populate the form with descriptions and activate the purchase button. Clicking on the purchase button in the winform will send Lefty Larduchi to your front door for some money. My first step was just to build the webpage (just plain HTML and javascript) and get it to a point of displaying stuff.

Creating the form is not a problem. Just start a new C# Windows Form project, customize the toolbar and add Internet Explorer from the COM side of the fence. The form consists of a panel docked to the left, a slider, IE docked to the right, and two textboxes and a button that are inside the panel.

Now one of the first steps in taking control of IE is to implement IDocHostUIHandler. Nikhil wrote a fine article on doing this, so I won’t duplicate his efforts. You can cover that first step here.

Make sure you keep track of the MSHtmHstInterop.dll piece of his sample application. I used the sample app to copy and paste the base IDocHostUIHandler implementations into my form.

So after implementing IDocHostUIHandler, what else needs to be done? Well, in Nikhil’s article his example would require that you know the controls that will be clicked and that someone click on that control. This is the code that accomplishes that:

private void WebBrowser_DocumentComplete(object sender,    AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent e){  IHTMLDocument2 doc = (IHTMLDocument2)this.WebBrowser.Document;  HTMLButtonElement button = (HTMLButtonElement)    doc.all.item("theButton", null);  ((HTMLButtonElementEvents2_Event)button).onclick += new    HTMLButtonElementEvents2_onclickEventHandler(this.Button_onclick);}

I had to face an application requirement where we were showing major sections, with each section being just DHTML, each section had to provide me information about itself and then have the WinForm act upon that information. I found it interesting to find in all the numerous articles I read on this subject that Outlook deploys this WinForm/IE merge - just not in .NET!

In this example, we are using the javascript object window.external to interact with the form. So when a user clicks on a section it will fire a method in the script area. That method, via window.external, issues a call through MSHTML to the IDocHostUIHandler.GetExternal method, then uses the IDispatch methods to get the address of the method and call it. This next section is quoted from a discussion with .S.Rod. I couldn’t describe it better:

anyone willing to implement custom menus or external method calls should register a custom site handler. That’s what is done with the ICustomDoc.SetUIHandler(object) method call.

the passed object reference has to implement the IDocHostUIHandler interface, an IUnknown based interface. Among the methods are one which is used as an entry point for all window.external.mymethod() calls, that’s IDocHostUIHandler.GetExternal(out object ppDispatch).

the GetExternal method should return a reference to an object which implements a dispatch interface. In case you don’t know, a dispatch interface is a standard automation interface providing the ability to have methods called by their names, thanks to two helper methods : GetIdOfName() and Invoke().

the good news is that the .NET Framework provides the attribute,

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

which implements all the underlying plumbing. What’s left to do is to declare and implement the actual methods.

In the end, we have a sample html file, which reacts on clicks by calling javascript’s window.external.MyMethod(). In order for this to work, the afore mentioned object must be declared and implement the MyMethod() method. In the sample application, that method name is

public void PopulateWindow(string selectedPageItem).

It should be important to note at this point that any method which will interact at the COM level should be defined to always return void. If there is need to return data, that is done via the parameters with the return parameters marked as out. If there is a need to return an error, for example, that is done by setting an HRESULT via System.Runtime.InteropServices. Setting the HRESULT is done in C# by doing a

throw new ComException("", returnValue)

returnValue is an int value defined somewhere in your class, and is set to the value you want to raise.

In the sample application, the first step to exposing an object via IDispatch is to create the custom interface:

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]interface void ICallUIHandler{  void PopulateWindow(string selectedPageItem)}

Then we implement the interface in a class definition:

public class PopulateClass:IPopulateWindow{  SampleEventProgram.Form1 myOwner;  ///

  /// Requires a handle to the owning form  ///   public PopulateClass(SampleEventProgram.Form1 ownerForm)  {    myOwner = ownerForm;  }  ///   /// Looks up the string passed and populates the form  ///   public void PopulateWindow(string itemSelected)  {    // insert logic here  }}

So what we have done here is create an interface that exposes IDispatch, we implemented that interface in the PopulateClass class definition, and we take in the constructor logic a pointer to our form. This give access to the specific fields we choose. I’m going to need the class to be able to change the two textboxes as well as enable the button. So I have to go into the form code and change those three item definitions from private to public. So in these definitions, I have made the following connections:

My interface is bound to IDispatch

My class is bound to my interface

My class is bound to my form, acting like a bridge between the MSHTML world and C# .NET world

Finally I have to implement the last piece of code that will connect my webform to my class I defined above. In the implementation for IDocHostUIHandler.GetExternal I need to set the object passed to an instance of my class. In implementing IDocHostUIHandler, you should have taken the implementation from Nikhil’s sample app and cut/paste it into your program. Alter the necessary implementation as follows:

void IDocHostUIHandler.GetExternal(out object ppDispatch){  ppDispatch = new PopulateClass(this);}

This now ties your class to the window.external portion of mshtml, it ties the form to the new class definition, and it readies everything for processing. The class implementation basically acts as a go-between between the two worlds of System.Windows.Forms.Form and Microsoft.MSHTML and your web form. The final step - before I write my code in the PopulateWindow method - is to pick which fields I want my class to access and change their definition from private to public, or to follow better coding standards - add public accessors to those fields. In this sample, I exposed the various elements that were to be changed with public accessors.

Conclusion

Now that I have a working application as well as a working sample application, I have to wonder why it took so long to pull all of this information together. But now, here it is. In the sample application:

The WinForm loads and the constructor is called

InitializeComponents occurs. This loads the WebBrowser control.

The WebBrowser is loaded with about:blank which will initialize the document object portion of the browser.

With the document initialized I can now implement IDocHostUIHandler via the ICustomDoc interface.

Finally the html page is loaded.

When an HTML button is clicked, it calls the method CallHostUI passing it the name of the item clicked.

The CallHostUI script calls window.external.PopulateWindow() passing the text each button sends.

window.external calls through MSHTML into IDocHostUIHandler.GetExternal and gets set to an instance of the object.

The logic to set the instance also passes reference to the form. Next it uses IDispatch to discover the PopulateClass method and where it is located.

The method is called, the reference to the form gives the class access for modifying the fields and enabling the button.

With all of this working I should add a note of warning. I have found that the Visual Designer code does not expect you to have an interface and class definition in front of your form definition. The result is if you add a control or modify a control it visually appears to take, but no change in your code has actually occured and the change disappears once you close and reopen the project. More frustrating is when you add an event handler: you get the binding to the delegate, but no actual base method implementation. Fortunately, all you need to do to work around this is to move your interface and class down to the bottom of your source code.

This can provide a very rich form of client presentation as well as rich WinForm elements for processing data. In my particular example, I’m exposing webpages developed for our internal web UI presentation engine. When each section inside of a web page is moused over, the section is highlighted with a bright yellow border. Clicking on the section passes that information to my WinForm which expresses that section in a properties page display. The various built-in editors in the framework as well as custom editors we write will hook into that properties page to allow for simple modification of data. For example, changing color in a cell element pops up the color picker editor and changing a font pops up the font picker editor.

About theRealCondor

 Developer for almost 30 years.

Developed a new OS, wrote a multi-threaded, queued TP Monitor, AI, .NET application processing engine.

Interested in game development concepts, evolutionary development with genetic algorithmic patterns, 3D modeling, and raising Koi in my pond and rare African Cichlids in my aquarium.

繼續閱讀