Reader Level:
ARTICLE

How to Bring up a Find Dialog in the WebBrowser Control using Managed C++

Posted by Mike Gold Articles | COM Interop February 07, 2007
This article will answers the question, "how do I bring up a find dialog in the Web Browser Control?". As easy as you would think this should be, it actually requires some COMplicated manipulation.
  • 0
  • 0
  • 16583

 

Figure 1 - Find Dialog for the IE WebBrowser Control

Introduction

Although this may sound like a trivial thing to do, bringing up the FindDialog in a WebBrowser control requires COM (short for complicated).   The reason that it requires COM is because the WebBrowser control shipped with Visual Studio.NET 2005 is really a wrapper around the existing internet explorer on your machine.  Microsoft didn't change IE to be pure .NET, so you are kind of stuck with accessing the control with COM Callable Wrappers (yuck).  Much of this article was deciphered from a Microsoft knowledge base article on the topic of implementing the finddialog in Managed C++.  The Microsoft knowledge base article only applies to the previous version of .NET. (2003).  This article will show you how to access the FindDialog using the new features in .NET 2005.

Marshaling through the IOleCommandTarget

In order to access various functions in Internet Explorer, you need to pick the correct COM Interface with the method you want to invoke.  You then need to marshal that interface through .NET into your application.  The find dialog inside the WebBrowser control is accessed from an interface called IOleCommandTarget by calling the Exec method on this interface.

The COM wrapper for this interface and the various structures required are shown in listing 1 below.  The first few definitions in listing 1 are marshaled structures that you need to pass into the Exec method of the IOleCommandTarget interface.  The last definition is the wrapper around the IOleCommandTarget itself.  It is renamed to IOleCommandTargetWrapper because C++ seems to get all of these redefinition compilation errors if you name it IOleCommandTarget.  (C# doesn't have these strange issues).  The Exec method takes a GUID representing the command group, a command id, an optional execution parameter, an object in and an object out. 

Note that the GUID is passed in with a interior_ptr.  What is an interior pointer (interior_ptr)?  When you are working with managed objects, they are managed on the heap by the garbage collector.  Although the garbage collector serves as an internal memory manager for all of your objects,  it tends to move the objects around on the heap.  Therefore a static unmanaged reference to the object may not be valid somewhere along the road.  An interior_ptr is a type of reference that dynamically changes as your object moves around, so you can always be sure that this "smart pointer" is always pointing to your object.  A similar type of reference is the tracking reference (%).  This allows you to pass values by reference so that the pointer pointing to them is also tracked on the heap as the garbage collector moves them.  You can think of the tracking reference (%) as a ref in C#.  Managed objects in and out of the Exec interface method are marshaled using tracking references.

Listing 1 - Marshaling the IOleCommandTarget COM Interface

#pragma once

using namespace System::Runtime::InteropServices;
using
namespace System;

public ref struct OLECMDTEXTWrapper
{

  public
:

      unsigned int cmdtextf;
      unsigned int cwActual;
      unsigned int cwBuf;
      char rgwz;
};

 

public ref struct OLECMDWrapper
{

  public
:

      long cmdID;
      System::UInt64 cmdf;
};

 

      // Constants for the commands that are named earlier

      public enum class MiscCommandTargetWrapper
      {
            Find = 1,
            ViewSource,
            Options
      };

// Interop definition for IOleCommandTarget. You need this attribute to marshal the wrapper as a COM
// interface

[ComImport,
Guid("b722bccb-4e68-101b-a2bc-00aa00404770"),
InterfaceType(ComInterfaceType::InterfaceIsIUnknown)]

public
interface class IOleCommandTargetWrapper
{
      //IMPORTANT: The order of the methods is important here.
      // Since you are doing early binding, the order of the methods
      //here MUST match the order of their vtable layout (which is determined
      //by their layout in IDL). The interop calls key off the vtable ordering,
      //not off the symbolic names, and therefore, if you switched these method
      //declarations and tried to call the Exec() function on an IOleCommandTarget
      //interface from your application, it would translate into a call to
    
 //QueryStatus() instead.

      void QueryStatus(Guid* pguidCmdGroup, System::UInt32 cCmds,
     [MarshalAs(UnmanagedType::LPArray, SizeParamIndex=1)] interior_ptr<OLECMD> prgCmds,
            interior_ptr<OLECMDTEXT> CmdText);
      void Exec(interior_ptr<System::Guid> pguidCmdGroup, long nCmdId, long nCmdExecOpt, Object^% pvaIn,
                       Object^% pvaOut);

};

Implementing the IOleCommandTarget through the WebBrowser Control

Now that we have wrapped the IOleCommandTarget COM interface, we can access its powerful set of functions.  We need to know the command guid in order to access the find dialog, and luckily this is provided to us in the Microsoft KnowledgeBase.  We also take advantage of the enumeration for miscellaneous command target options (MiscCommandTargetWrapper) to specifically call upon the find dialog.  The command execute option is the default (zero).  The objects in and out of the function are created simply as place holders and use a managed object created with a default constructor.

As Seen in Listing 2, we access the IOleCommandTarget COM interface through the IHTMLDocument2 COM interface.   In order to access the IHTMLDocument2, we  need to go through the DomDocument property, which is a property for the unmanaged interface of the HtmlDocument in the WebBrowser. 

Listing 2 - Bringing up the Find Dialog from the WebBrowser Control

               System::Object^ o = gcnew System::Object();
               Guid *cmdGuid = new Guid("ED016940-BD5B-11CF-BA4E-00C04FD70816");
               ((IOleCommandTargetWrapper^)((mshtml::IHTMLDocument2^)webBrowser1->Document->DomDocument))
                     ->Exec(cmdGuid, (System::UInt32)MiscCommandTargetWrapper::Find, 0, o, o);

Note: Accessing the COM interfaces inside of the DomDocument, requires a reference to the mshtml NET assembly  inside the project shown in figure 2.

 

Figure 2 - Adding the mshtml reference to your project.

One last thing you may need to do.  I added a class interface attribute to the form containing the WebBrowser indicating that we were using the IOleCommandTarget as a COM event source (see listing 3).

Listing 3 - COM Class Interface attribute added to the form.

[ClassInterface(ClassInterfaceType::None),ComSourceInterfaces(IOleCommandTargetWrapper::typeid)]     
public
ref class HtmlExampleForm : public System::Windows::Forms::Form

 

Conclusion

Hopefully, in the future, Microsoft will add all of the functionality of IE to the WebBrowser control methods and the Document property methods. This way, the user doesn't have to jump through hoops to get to the underlying COM functionality in the browser.  In the meantime, try to stay COM and keep on plugging away using the power of .NET.

COMMENT USING

Trending up