• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Component-BasedDevelopmentwithVisualC#

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

本文转自http://www.codeproject.com/KB/books/0764549146_8.aspx

 

Chapter 8: Creating Front Ends with the WebBrowser Component

 

Author Ted Faison
Title Component-Based Development with Visual C#
Publisher Wiley
Published April 2002
ISBN 0-7645-4914-6
Price US 49.99
Pages 1008

In This Chapter

  • Creating a simple Web browser
  • Using ActiveX components in managed code
  • Exposing COM interfaces from managed code
  • Customizing the WebBrowser component

Chapter 8: Creating Front Ends with the WebBrowser Component

A billion here, a billion there — sooner or later it adds up to real money.
— Former U.S. Senator Everett Dirksen on public finance

Senator Dirksen’s concept of real money may have been slightly different from yours or mine, but one thing is certain: If you fail to take advantage of existing components when building the front end of your application, you’ll find your development costs adding up to real money.

The Microsoft WebBrowser Component

One of the most powerful reusable UI components I know of is the WebBrowser ActiveX component used by Internet Explorer. Microsoft designed it to be extremely flexible in terms of the kinds of content it can display. Obviously, it can display HTML documents, but it may come as a surprise to some of you that it can also display other common file types such as Word, Excel, PowerPoint, TXT, RTF, GIF, JPEG, XML, PDF, and others. WebBrowser achieves this incredible flexibility by using an embedded ActiveX component to render its data. In the case of HTML documents, the component is MsHtml. For more complex documents like Word or Excel, WebBrowser acts as an Active Document host and embeds Word or Excel in the client area to handle the rendering.

Since WebBrowser was designed specifically to be hosted by a container application, it has a lot of features you can control. Microsoft uses the component in several different applications, and customizes its look and feel to blend in with the rest of the host application. Examples of hosts include Windows Explorer, Outlook, and even the VS .NET Start Page. In this chapter, I’ll show you how to create a Windows application that hosts the WebBrowser component. Later, since customization is very important to many of you, I’ll create a second Windows application showing how to access the various customization features that WebBrowser makes available.

I’ll call the first browser application MyWebBrowser and give it a tool bar, to show how to control some of the common functions of WebBrowser. As I said earlier, WebBrowser can handle more than just HTML files. Figures 8-1 through 8-6 show MyWebBrowser displaying different types of files.

Figure 8-1: Using MyWebBrowser to display an HTML page

Figure 8-2: Using MyWebBrowser to display a Word document

Figure 8-3: Using MyWebBrowser to display an Excel spreadsheet

Figure 8-4: Using MyWebBrowser to display a Powerpoint document

Figure 8-5: Using MyWebBrowser to display an XML document

Figure 8-6: Using MyWebBrowser to display a PDF document

These screen shots hopefully give you some sense of how flexible WebBrowser really is. WebBrowser and its Active Document subcomponents do all the work behind the scenes. If you can find a more powerful reusable UI component than WebBrowser, let me know.

Not only does WebBrowser display a zillion file types, it also lets you control the way documents are presented. In the case of HTML documents, there is a whole slew of behaviors that can be changed, such as:

  • Changing the context menu

  • Hiding scrollbars

  • Disabling text selection

  • Removing the 3D border

  • Using flat scrollbars

  • Forcing in-place navigation

I’ll show you how to make these and other kinds of customizations to MsHtml, but first, I’ll get you familiar with WebBrowser by showing you a simple application called MyWebBrowser that can be used as a simple mini-browser without any customizations. After describing MyWebBrowser, I’ll show you a customized browser in an application called MyCustomWebBrowser. This application will use COM interfaces and callbacks to control the way MsHtml works.

Designing MyWebBrowser

Since almost all the functionality you need is already built into WebBrowser, the design of an application based on Windows Forms that embeds the component is fairly trivial. Figure 8-7 shows the salient parts of the class diagram for MyWebBrowser.

Figure 8-7: The class diagram for MyWebBrowser

Like I said, in this first example, I won’t add any special customizations to MsHtml or WebBrowser, because doing so requires utilizing COM interoperability features. After showing a simple example, I’ll show you how to use COM interfaces to customize many MsHtml features.

Developing MyWebBrowser

Enough said about the glories of WebBrowser and MsHtml. They’re built-in, they’re good, they’re wonderful. Great. Let’s put them to work in a real program. MyWebBrowser is a simple Windows application that has just a single form, called MainForm, which hosts the WebBrowser component and demonstrates not only how to use it, but more generally how to access ActiveX components from managed code.

I created MyWebBrowser with the New Project Wizard, choosing Windows Application as the project type. I renamed the main form class from Form1 to MainForm, and set the project name to MyWebBrowser. The next step was to add the WebBrowser ActiveX component to MainForm, a task that deserves a separate explanation.

Importing the WebBrowser ActiveX Component

There is no WebBrowser component in the Toolbox when you install VS .NET, so you have to import a .NET version of the WebBrowser into your project. As with just about everything in life, the import process can be done the easy way or the hard way.

The easy way

The easy way is with the Customize Toolbox dialog box. First select the Windows Forms page on the Toolbox and then right-click on the Toolbox and choose the Customize Toolbox command on the pop-up menu. Select the COM Components tab, scroll down to the Microsoft Web Browser item, and check its checkbox, as shown in Figure 8-8.

Figure 8-8: Importing the ShDocVw.WebBrowser control into the VS .NET Toolbox

The WebBrowser will show up on the Windows Forms tab of the Toolbox with the name Explorer, as shown in Figure 8-9.

Figure 8-9: The WebBrowser component after installing it on the Toolbox

Now drop an instance of WebBrowser on MainForm. When the component is dropped, VS .NET performs the following tasks.

  1. It launches an import process that creates the two files AxInterop.SHDocVw.dll and Interop.SHDocVw.dll.

  2. It puts the two files in your project’s bin\Debug or bin\Release directory (depending on whether your project configuration is set to Debug or Release).

  3. It adds the two files to the References node of your project in the Solution Explorer.

  4. It adds an instance of AxSHDocVw.AxWebBrowser to your form.

A nice piece of work, saving you precious time and money — perhaps not billions, but hey, no one said life is fair.

In case you’re wondering, much of the process was performed under the covers by a command-line utility called aximp, described later. At this point the Solution Explorer looks like Figure 8-10.

Figure 8-10: The Solution Explorer, showing the newly imported files

That was the easy way to import the WebBrowser component. The hard way is for those of you that like typing (and I know there are a lot of you out there).

The hard way

I always get a kick out of watching people open DOS boxes and furiously type in long commands that could be replaced with a couple of mouse clicks. Force of habit is a powerful thing. But using command-line utilities for the import process is not always a bad thing, because they create the necessary import files without adding anything to the Toolbox. Although you may want to have a useful component like WebBrowser in the Toolbox, you don’t necessarily want every ActiveX component you’ll ever use cluttering up the precious Toolbox space.

In any case, there are two command-line utilities available for converting COM types into .NET-compatible types that can be referenced in a VS .NET project. Which one to use depends on how you want to use the imported components.

Using TlbImp

The lowest level command-line utility for ActiveX importing is TlbImp. You’ll want to use this utility when importing ActiveX components that won’t be used in a Windows Form. TlbImp reads a file containing COM Type Library information — which can be a .tlb, .dll, .odl, or other file type — and produces a DLL containing .NET-compatible metadata. The DLL must then be added to the References node of your project.

For example, to use shdocvw.dll (containing the WebBrowser component, for those of you that jumped into this section without reading the previous ones) in the MyWebBrowser project, you would open a Command-Prompt box, go to the folder C:\Program Files\Microsoft.NET\FrameworkSDK\Bin, and then type the command:

Copy Code
tlbimp c:\winnt\system32\shdocvw.dll /out:C:\MyWebBrowser\bin\Debug\Interop.shdocvw.dll

You can inspect the .NET metadata in the DLL generated by TlbImp with a standard .NET tool like ildasm. Open a Command-Line box and go to the folder containing the newly generated Shdocvw.dll file and then type the command:

Copy Code
 shdocvw.dll

Figure 8-11 shows some of the contents of the metadata file displayed by ildasm.

Figure 8-11: Inspecting the metadata in the DLL produced by running TlbImp on c:\winnt\system32\shdocvw.dll

The metadata makes it possible for your code to interact with the unmanaged code of COM components using exactly the same syntax you would use with native C# components.

Using AxImp

If you plan to use an ActiveX component in a Windows Form, as I did with WebBrowser, you’ll need to use the AxImp command-line utility instead of TlbImp. The reason is this: To use an ActiveX component in a Windows Form, a wrapper class must also be generated. All components dropped in Windows Forms are required to be derived from the common base class System.Windows.Forms.Control. The utility AxImp creates the wrapper for you.

To create the wrapper for WebBrowser, open a Command-Line box, go to the folder where the executable code of your project will go. For the project MyWebBrowser, the folder will be MyWebBrowser\bin\debug or MyWebBrowser\bin\release. Type the following command:

Copy Code
  c:\winnt\system32\shdocvw.dll

The utility will generate two files, called SHDocVw.dll and AxSHDocVw.dll. The first file contains the .NET metadata that describes the COM types contained in c:\winnt\system32\shdocvw.dll. The file is identical to the one produced by TlbImp in the previous section (because AxImp internally calls TlbImp to generate it). The second file is a .NET assembly containing the wrapper classes that allow you to use the ActiveX component in standard Windows Forms. Figure 8-12 shows the contents of AxSHDocVw.dll, as viewed with ildasm:

Figure 8-12: The wrapper class created for SHDocVw

Basically, AxImp creates a new class derived from System.Windows.Forms.AxHost, which is derived from System.Windows.Forms.Control. This new class acts as a .NET wrapper for the ActiveX component.

Runtime Callable Wrappers (RCW)

When you instantiate a COM type in your C# code, using code like this:

Copy Code
new AxSHDocVw.AxWebBrowser();

there is more going on than meets the eye. What actually happens is this: The compiler looks at the metadata describing the class AxWebBrowser, in the AxSHDocVw assembly generated by TlbImp or AxImp. Using this metadata, it creates something called a Runtime Callable Wrapper (RCW), which is a proxy component that on one side is callable from your managed code and on the other side deals with the unmanaged COM code of the WebBrowser ActiveX component. The RCW acts as an invisible bridge between your code and the ActiveX code, as shown in Figure 8-13.

Figure 8-13: The Runtime Callable Wrapper as a proxy for COM components

The RCW manages all those pesky COM details that you don’t want to deal with, such as reference counting, deleting the component when it is no longer used, marshalling parameters in method calls, and so on. If you create multiple instances of the same COM component in your code, all the instances will share a single RCW. Keep in mind that all this RCW business is generally completely transparent to you. It’s there to help you, making it as easy to access an ActiveX component as any other managed component.

Adding a toolbar

Before I completely lose track of where I am, let me finish describing the code for MyWebBrowser. First I’ll discuss the toolbar buttons. Most UIs containing WebBrowser support some means for navigating, whether it’s with toolbar buttons, menu commands, or some other element.

I added a toolbar to MyWebBrowser by dropping a Toolbox component of type ToolBar on the main form. In the ToolBar’s properties, I clicked on the Collections field and added seven buttons with the following functions:

  • Back

  • Forward

  • Stop

  • Refresh

  • Home

  • Search

  • Print

To make the buttons look like those used by Internet Explorer, I used a screen capture utility to get the IE button images, and then saved them as bitmap files in the folder containing the source code for MyWebBrowser. I set them all to have a green background. I added an ImageList component to the form and added all the bitmap images to it. I set the ImageList TransparentColor property to green, so the green areas of the bitmaps would be transparent and assume the color of the button faces. Then I set the ToolBar’s ImageList property to reference the ImageList component. For each toolbar button, I set the following properties: ToolTipText, Text, and ImageIndex. To the right of the toolbar buttons, I dropped a TextBox control. To make the TextBox always stretch to the right side of the form, I set its Anchor property to (Top, Left, Right). Figure 8-14 shows the finished toolbar.

Figure 8-14: The finished toolbar

Notice the flat look of the toolbar, with the buttons showing no borders. This look was achieved by setting the toolbar’s Appearance property to Flat. Next, I added some event handling code for the toolbar buttons, as shown in Listing 8-1.

Listing 8-1:: The Toolbar Event Handler

Copy Code
object sender,
             System.WinForms.ToolBarButtonClickEventArgs e)
{
  Cursor.Current = Cursors.WaitCursor;
  try 
  {
    if (e.button == toolBarButtonBack)
      axWebBrowser1.GoBack();
    else if (e.button == toolBarButtonForward)
      axWebBrowser1.GoForward();
    else if (e.button == toolBarButtonStop) 
    {
      axWebBrowser1.Stop();
      toolBarButtonStop.Enabled = false;
    }
    else if (e.button == toolBarButtonSearch)
      axWebBrowser1.GoSearch(); 
    else if (e.button == toolBarButtonPrint)
      PrintPage();
    else if (e.button == toolBarButtonRefresh)
    {
      object REFRESH_COMPLETELY = 3;
      axWebBrowser1.Refresh2(ref REFRESH_COMPLETELY); 
    }
    else if (e.button == toolBarButtonHome)
      axWebBrowser1.GoHome();
  }
  finally 
  {
    Cursor.Current = Cursors.Default;
  }
}

The method WebBrowser.GoSearch() navigates to the Search site, which can be set up in Internet Explorer by clicking the Search toolbar button and selecting Customize in the Search pane. By default, WebBrowser uses the search engine at http://search.msn.com.

The method WebBrowser.GoHome() navigates to the Home site set up in Internet Explorer with the Tools @@> Internet Options dialog box, on the General tab.

Adding support for printing

All of the buttons except Print are handled by calling a method in the embedded WebBrowser component. As noted earlier, there is no Print() method, so printing is handled differently, with the code shown in Listing 8-2.

Listing 8-2:: Printing the HTML Page Displayed in the WebBrowser

Copy Code
bool IsPrinterEnabled()
{
  int response = 
    (int) axWebBrowser1.QueryStatusWB(SHDocVw.OLECMDID.OLECMDID_PRINT);
  return (response & (int) SHDocVw.OLECMDF.OLECMDF_ENABLED) != 0 ? 
    true : false;
}
private void PrintPage()
{
  object o = "";
  // constants useful when printing
  SHDocVw.OLECMDID Print = SHDocVw.OLECMDID.OLECMDID_PRINT;
  // use this value to print without prompting
  // SHDocVw.OLECMDEXECOPT PromptUser =
  //  SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER;
  SHDocVw.OLECMDEXECOPT DontPromptUser =
    SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
  
  if (!IsPrinterEnabled() ) return;
  // print without prompting user
  axWebBrowser1.ExecWB(Print, DontPromptUser, ref o, ref o);
  // to prompt the user with printer settings
  // axWebBrowser1.ExecWB(Print, PromptUser, ref o, ref o);
}

The last two lines in the listing show two ways to print: The first way prints silently, the second way prints after prompting the user for printer settings. I created two constants called DontPromptUser and PromptUser, which are derived from ShDocVw enumerated values. When you call WebBrowser.ExecWB(), you simply pass the constant indicating whether you want the method to display a Print dialog box or not.

Adding navigation support

To navigate to a Web site, you call the Navigate() or Navigate2() method of the WebBrowser. The first method handles navigating to ordinary URLs, including local files. The second method extends the first by supporting navigation to items in the Windows Desktop and My Computer folders. To protect the rest of the application from errors caused by invalid URLs or network problems, I wrapped the call to WebBrowser.Navigate() in a try block in a method called GotoURL(), shown in Listing 8-3.

Listing 8-3:: Setting the URL of the Document to Load

Copy Code
String theURL) 
{
  try 
  {
    Cursor.Current = Cursors.WaitCursor;
    Object o = null;
    axWebBrowser1.Navigate(theURL, ref o, ref o, ref o, ref o);
  } 
  finally {
    Cursor.Current = Cursors.Default;
  }
}

To allow the user to type a URL in the TextBox, I added an event handler for the TextBox that calls GotoURL() as shown in Listing 8-4.

Listing 8-4:: The TextBox Event Handler

Copy Code
object sender,
                                       System.WinForms.KeyEventArgs e)
{
  if (e.KeyCode == Keys.Return) 
    GotoURL(textBoxAddress.Text);
}

To display an hourglass cursor while a page is being loaded, I set the cursor in the WebBrowser’s BeforeNavigate2() handler as shown in Listing 8-5.

Listing 8-5:: Setting the Cursor to an Hourglass during Navigation

Copy Code
object sender,
               AxSHDocVw.DWebBrowserEvents2_BeforeNavigate2Event e)
{
  toolBarButtonStop.Enabled = true;
  Cursor.Current = Cursors.WaitCursor;
}

Once a page is loaded, I need make sure the cursor is eventually restored to an arrow pointer. Navigation commands can end in one of two ways: If the page can’t be loaded, the NavigateError handler is called. If the page was loaded, the NavigateComplete handler is called. I created a simple NavigateError handler, as shown in Listing 8-6.

Listing 8-6:: The NavigateError Handler that Restores the Mouse Cursor

Copy Code
object sender,
             AxSHDocVw.DWebBrowserEvents2_NavigateErrorEvent e)
{
  Cursor.Current = Cursors.Default;
  toolBarButtonStop.Enabled = false;
  toolBarButtonHome.Enabled = true;
  toolBarButtonSearch.Enabled = true;
  toolBarButtonRefresh.Enabled = true;
}

If navigation to a site is successful, the NavigateComplete2 handler is called, if available. I created a NavigateComplete2 handler, as shown in Listing 8-7.

Listing 8-7:: The NavigateComplete2 Handler

Copy Code
object sender, 
        AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
{
  Cursor.Current = Cursors.Default;
  toolBarButtonStop.Enabled = false;
  toolBarButtonHome.Enabled = true;
  toolBarButtonSearch.Enabled = true;
  toolBarButtonRefresh.Enabled = true;
  // update the URL displayed in the address bar
  String s = e.uRL.ToString();
  textBoxAddress.Text = s;
  // update the list of visited URLs
  int i = urlsVisited.IndexOf(s);
  if (i >= 0)
    currentUrlIndex = i;
  else 
    currentUrlIndex = urlsVisited.Add(s);
  // enable / disable the Back and Forward buttons
  toolBarButtonBack.Enabled = (currentUrlIndex == 0) ? false : true;
  toolBarButtonForward.Enabled = 
    (currentUrlIndex >= urlsVisited.Count-1) ? false : true;
  // set the state of the Print button
  toolBarButtonPrint.Enabled = IsPrinterEnabled();
}

In the NavigateComplete2 handler, I displayed the current URL in the TextBox and updated the list of visited URLs. Each time the user goes to a new URL, I store it in an ArrayList. I used the list to support enabling and disabling the Back and Forward buttons. When the user clicks the Back button, I retrieve the previous URL in the list and navigate to it. When the user navigates all the way back to the first URL in the list, I disable the Back button. Similarly, when the user navigates forward to the end of the list, I disable the Forward button.

Disabling the Back and Forward buttons is more than just a cosmetic exercise: If you call the WebBrowser’s GoBack() or GoForward() method when there is no previous or next URL to navigate to, the component will raise an exception. If you don’t catch the exception, an error message will be displayed.

The last feature to add is one that will make MyWebBrowser go to the Home page when it is run. All you need to do is call the method axWebBrowser1.GoHome() in the constructor for MainForm.

The complete code

The next section will deal with ways to customize the WebBrowser component. Before I move to this new topic, I’ve included the full code for MyWebBrowser in Listing 8-8.

Listing 8-8:: The Code for MyWebBrowser

Copy Code
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace MyWebBrowser
{
  public class MainForm : System.Windows.Forms.Form
  {
    private System.Windows.Forms.Panel panel1;
    private System.Windows.Forms.ToolBar toolBar1;
    private System.Windows.Forms.ImageList imageList1;
    private System.Windows.Forms.ToolBarButton toolBarButtonBack;
    private System.Windows.Forms.ToolBarButton toolBarButtonForward;
    private System.Windows.Forms.ToolBarButton toolBarButtonStop;
    private System.Windows.Forms.ToolBarButton toolBarButtonRefresh;
    private System.Windows.Forms.ToolBarButton toolBarButtonHome;
    private System.Windows.Forms.ToolBarButton toolBarButtonSearch;
    private System.Windows.Forms.ToolBarButton toolBarButtonPrint;
    private AxSHDocVw.AxWebBrowser axWebBrowser1;
    private System.Windows.Forms.TextBox textBoxAddress;
    private System.ComponentModel.IContainer components;
    ArrayList urlsVisited = new ArrayList();
    int currentUrlIndex = -1;  // no sites visited initially
    
    public MainForm()
    {
      InitializeComponent();
      toolBarButtonBack.Enabled = false;
      toolBarButtonForward.Enabled = false;
      toolBarButtonStop.Enabled = false;
      toolBarButtonRefresh.Enabled = false;
      toolBarButtonHome.Enabled = false;
      toolBarButtonSearch.Enabled = false;
      toolBarButtonPrint.Enabled = false;
      
      axWebBrowser1.GoHome();
    }
    protected override void Dispose( bool disposing )
    {
      // standard wizard-created code
    }
    private void InitializeComponent()
    {
      // standard wizard-created code
    }
    [STAThread]
    static void Main() 
    {
         Application.Run(new MainForm());
    }
    private void toolBar1_ButtonClick(object sender,
            System.Windows.Forms.ToolBarButtonClickEventArgs e)
    {
      Cursor.Current = Cursors.WaitCursor;
      try 
      {
        if (e.Button == toolBarButtonBack)
          axWebBrowser1.GoBack();
        else if (e.Button == toolBarButtonForward)
          axWebBrowser1.GoForward();
        else if (e.Button == toolBarButtonStop) 
        {
          axWebBrowser1.Stop();
          toolBarButtonStop.Enabled = false;
        }
        else if (e.Button == toolBarButtonSearch)
          axWebBrowser1.GoSearch(); 
        else if (e.Button == toolBarButtonPrint)
          PrintPage();
        else if (e.Button == toolBarButtonRefresh)
        {
          object REFRESH_COMPLETELY = 3;
          axWebBrowser1.Refresh2(ref REFRESH_COMPLETELY); 
        }
        else if (e.Button == toolBarButtonHome)
          axWebBrowser1.GoHome();
      }
      finally 
      {
        Cursor.Current = Cursors.Default;
      }
    }
    
    private bool IsPrinterEnabled()
    {
      int response = 
        (int) axWebBrowser1.QueryStatusWB(SHDocVw.OLECMDID.OLECMDID_PRINT);
      return (response & (int) SHDocVw.OLECMDF.OLECMDF_ENABLED) != 0 ?
             true : false;
    }
    private void PrintPage()
    {
      object o = "";
      // constants useful when printing
      SHDocVw.OLECMDID Print = SHDocVw.OLECMDID.OLECMDID_PRINT;
      // use this value to print without prompting
      // SHDocVw.OLECMDEXECOPT PromptUser =
      //  SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER;
      SHDocVw.OLECMDEXECOPT DontPromptUser =
        SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
  
      if (!IsPrinterEnabled() ) return;
      // print without prompting user
      axWebBrowser1.ExecWB(Print, DontPromptUser, ref o, ref o);
      // to prompt the user with printer settings
      // axWebBrowser1.ExecWB(Print, PromptUser, ref o, ref o);
    }
    
    public void GotoURL(String theURL) 
    {
      try 
      {
        Cursor.Current = Cursors.WaitCursor;
        Object o = null;
        axWebBrowser1.Navigate(theURL, ref o, ref o, ref o, ref o);
      } 
      finally 
      {
        Cursor.Current = Cursors.Default;
      }
    }
    private void textBoxAddress_KeyDown(object sender,
            System.Windows.Forms.KeyEventArgs e)
    {
      if (e.KeyCode == Keys.Return) 
        GotoURL(textBoxAddress.Text);
    }
    private void axWebBrowser1_BeforeNavigate2(object sender,
            AxSHDocVw.DWebBrowserEvents2_BeforeNavigate2Event e)
    {
      toolBarButtonStop.Enabled = true;
      Cursor.Current = Cursors.WaitCursor;
    }
    private void axWebBrowser1_NavigateComplete2(object sender,
            AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
    {
      Cursor.Current = Cursors.Default;
 
      toolBarButtonStop.Enabled = false;
      toolBarButtonHome.Enabled = true;
      toolBarButtonSearch.Enabled = true;
      toolBarButtonRefresh.Enabled = true;
      // update the URL displayed in the address bar
      String s = e.uRL.ToString();
      textBoxAddress.Text = s;
      // update the list of visited URLs
      int i = urlsVisited.IndexOf(s);
      if (i >= 0)
        currentUrlIndex = i;
      else 
        currentUrlIndex = urlsVisited.Add(s);
      // enable / disable the Back and Forward buttons
      toolBarButtonBack.Enabled = (currentUrlIndex == 0) ? false : true;
      toolBarButtonForward.Enabled = 
        (currentUrlIndex >= urlsVisited.Count-1) ? false : true;
      // set the state of the Print button
      toolBarButtonPrint.Enabled = IsPrinterEnabled();
    }
    private void axWebBrowser1_NavigateError(object sender,
            AxSHDocVw.DWebBrowserEvents2_NavigateErrorEvent e)
    {
      Cursor.Current = Cursors.Default;
      toolBarButtonStop.Enabled = false;
      toolBarButtonHome.Enabled = true;
      toolBarButtonSearch.Enabled = true;
      toolBarButtonRefresh.Enabled = true;
    }
  }
}

In the next section, I’ll switch gears and get into advanced topics with COM Interop programming. If you’re not experienced in COM, you may want to skip the rest of the chapter entirely.

Creating a Customized Web Browser

MyWebBrowser has all the basic browser features, such as navigation, printing, and so on. It also has features you may want to change, such as how accelerator keys are handled or what commands are available on the context menu. To make these kinds of changes, things get a bit more complicated with WebBrowser, and you are forced to get your hands dirty with some COM interoperability programming. In this and the following sections, I’ll create another Windows application called MyCustomWebBrowser that demonstrates how to develop a fully customized WebBrowser. To start the project, I just copied the entire MyWebBrowser solution into a new folder and renamed it to MyCustomWebBrowser.

Customizing the WebBrowser component is more complicated than one might think. The problem is this: Most of the customizable features of MsHtml depend on COM callbacks that must be handled by the host window — MainForm in this case. Callbacks are methods exposed through COM interfaces, so you have to make the necessary interfaces available to MsHtml. It would have been nice if MsHtml had exposed a list of properties for all its customizable features, so you could change a feature with a simple line of code like this:

Copy Code
 this won�t work

That would have been way too simple! Besides, each time the parent WebBrowser loads a new HTML page, it creates a new instance of MsHtml, so even if you could set the properties using the previous code, the new MsHtml component wouldn’t be affected. You might be thinking, “Why didn’t Microsoft store the customizable properties of MsHtml in the parent WebBrowser component? This way you could set them once, and have WebBrowser automatically reapply them each time it created a new MsHtml component”. For better or for worse, WebBrowser doesn’t want anything to do with presentation of content. It was designed to act as a host for rendering components. It handles other aspects of Web browsing, like navigation. WebBrowser is an Active Document host, and it delegates all details of presentation to the hosted Active Document rendering component (MsHtml in this case).

The moral of the story is this: MainForm needs to implement a number of COM interfaces to support MsHtml customizing. Figure 8-15 shows the class diagram for MainForm.

Figure 8-15: The class diagram for a fully customized WebBrowser host Windows Form

Let’s take a look at what the COM interfaces are used for. The first step in any type of WebBrowser customization is to establish a component as the controlling host. By default, the WebBrowser has no controlling host. To set up MainForm as the new controlling host, you need to call WebBrowser through its OleClient interface as shown in Listing 8-9.

Listing 8-9:: Setting Up MainForm as the Controlling Host of WebBrowser

Copy Code
object obj = axWebBrowser1.GetOcx();
IOleObject oc = obj as IOleObject;
oc.SetClientSite(this);

These three deceptively simple lines of code are critical. The method GetOcx() retrieves the IUnknown interface of the native COM object wrapped by axWebBrowser1. The second line implicitly issues a QueryInterface, seeking an IOleObject interface from the WebBrowser COM object. You can use the as operator like this to query for any type of interface. If the interface is not available, a null will be returned. Once the IOleObject interface is obtained from WebBrowser, the method SetClientSite() is called to set MainForm as the controlling host. Objects passed as a parameter to SetClientSite() must implement the IOleClientSite, ergo the presence of IOleClientSite in the Class Diagram previously shown in Figure 8-15.

Importing and wrapping COM interfaces

MyCustomWebBrowser implements the two COM interfaces IOleClientSite and IDocHostUIHandler, and calls methods of the interface IOleObject. Unfortunately there are no type libraries published by Microsoft that contain these interfaces, meaning you have to get your hands dirty with a bit of COM programming to work around the problem. As it turns out, most of the COM interfaces that Microsoft uses in its components are not published as type libraries. You’ll either find them as .idl files, or in some cases as C++ header files.

IOleObject and IOleClientSite

You sometimes have to do a bit of digging to locate the file that declares an interface. In the case of IOleClientSite and IOleObject, I found them both declared in the file oleidl.h, which is in the following folder:

Copy Code
C:\Program Files\Microsoft Visual Studio.NET\Vc7\PlatformSDK\include

I created C# interfaces to wrap the two COM interfaces, as shown in Listings 8-10 and 8-11.

Listing 8-10:: The Interface that Wraps the COM Interface IOleObject

Copy Code
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MyCustomWebBrowser
{
  [ComImport,
  Guid("00000112-0000-0000-C000-000000000046"),
  InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
  public interface IOleObject
  {
    void SetClientSite(IOleClientSite pClientSite);
    void GetClientSite(IOleClientSite ppClientSite);
    void SetHostNames(object szContainerApp, object szContainerObj);
    void Close(uint dwSaveOption);
    void SetMoniker(uint dwWhichMoniker, object pmk);
    void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
    void InitFromData(IDataObject pDataObject, bool 
                      fCreation, uint dwReserved);
    void GetClipboardData(uint dwReserved, IDataObject ppDataObject);
    void DoVerb(uint iVerb, uint lpmsg, object pActiveSite, 
                uint lindex, uint hwndParent, uint lprcPosRect);
    void EnumVerbs(object ppEnumOleVerb);
    void Update();
    void IsUpToDate();
    void GetUserClassID(uint pClsid);
    void GetUserType(uint dwFormOfType, uint pszUserType);
    void SetExtent(uint dwDrawAspect, uint
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
c#中serialPort1_DataReceived串口接收事件处理发布时间:2022-07-10
下一篇:
C#.NET更智能的数据库操作的封装完整版(重构)发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap