|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
In This Chapter
Chapter 8: Creating Front Ends with the WebBrowser ComponentA billion here, a billion there — sooner or later it adds up to real money. 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 ComponentOne of the most powerful reusable UI components I know of is the Since 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
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 Not only does
I’ll show you how to make these and other kinds of customizations to Designing MyWebBrowserSince 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 Developing MyWebBrowserEnough said about the glories of 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 Importing the WebBrowser ActiveX ComponentThere is no The easy wayThe 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
Figure 8-9: The WebBrowser component after installing it on the Toolbox Now drop an instance of
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 The hard wayI 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 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 TlbImpThe lowest level command-line utility for ActiveX importing is For example, to use shdocvw.dll (containing the
You can inspect the .NET metadata in the DLL generated by
Figure 8-11 shows some of the contents of the metadata file displayed by
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 AxImpIf you plan to use an ActiveX component in a Windows Form, as I did with To create the wrapper for
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
Figure 8-12: The wrapper class created for SHDocVw Basically, Runtime Callable Wrappers (RCW)When you instantiate a COM type in your C# code, using code like this:
there is more going on than meets the eye. What actually happens is this: The compiler looks at the metadata describing the class
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 toolbarBefore 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 I added a toolbar to MyWebBrowser by dropping a Toolbox component of type
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
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 Listing 8-1:: The Toolbar Event Handler
protected void toolBar1_ButtonClick(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 The method Adding support for printingAll of the buttons except Print are handled by calling a method in the embedded Listing 8-2:: Printing the HTML Page Displayed in the WebBrowser
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);
}
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 Adding navigation supportTo navigate to a Web site, you call the Listing 8-3:: Setting the URL of the Document to Load
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;
}
}
To allow the user to type a URL in the Listing 8-4:: The TextBox Event Handler
protected void textBoxAddress_KeyDown (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 Listing 8-5:: Setting the Cursor to an Hourglass during Navigation
protected void axWebBrowser1_BeforeNavigate2 (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 Listing 8-6:: The NavigateError Handler that Restores the Mouse Cursor
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;
}
If navigation to a site is successful, the Listing 8-7:: The NavigateComplete2 Handler
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();
}
In the Disabling the Back and Forward buttons is more than just a cosmetic exercise: If you call the 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 The complete codeThe next section will deal with ways to customize the Listing 8-8:: The Code for MyWebBrowser
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 BrowserMyWebBrowser 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 Customizing the
That would have been way too simple! Besides, each time the parent The moral of the story is this: MainForm needs to implement a number of COM interfaces to support
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 Listing 8-9:: Setting Up MainForm as the Controlling Host of WebBrowser
object obj = axWebBrowser1.GetOcx();
IOleObject oc = obj as IOleObject;
oc.SetClientSite(this);
These three deceptively simple lines of code are critical. The method Importing and wrapping COM interfacesMyCustomWebBrowser implements the two COM interfaces IOleObject and IOleClientSiteYou sometimes have to do a bit of digging to locate the file that declares an interface. In the case of
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
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 psizel);
void GetExtent(uint dwDrawAspect, uint psizel);
void Advise(object pAdvSink, uint pdwConnection);
void Unadvise(uint dwConnection);
void EnumAdvise(object ppenumAdvise);
void GetMiscStatus(uint dwAspect,uint pdwStatus);
void SetColorScheme(object pLogpal);
};
}
Listing 8-11:: The Interface that Wraps the COM Interface IOleClientSite
using System;
using System.Runtime.InteropServices;
namespace MyCustomWebBrowser
{
[ComImport,
Guid("00000118-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
public interface IOleClientSite
{
void SaveObject();
void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
void GetContainer(object ppContainer);
void ShowObject();
void OnShowWindow(bool fShow);
void RequestNewObjectLayout();
}
}
A few words of explanation are due regarding the attributes used on the interface declarations. The
The value I don’t have enough space here to describe all the methods exposed by the interfaces in the last two listings. For the purpose of this discussion, the only method that matters is When creating wrapper interfaces that will be called from native COM code, it is obviously critical that the GUID you specify matches the original one used by COM. It is also essential that the wrapper interface implements all the original COM methods, and that all methods have the same signature as the original method. Using ICustomDocAs stated earlier, the first step in customizing Since customizing Listing 8-12:: Setting a Component as the Customizer for MsHtml
Keep in mind that the interface IDocHostUIHandlerThis is the most important interface Before I get ahead of myself (and I’m not talking about an out-of-body experience), let me show you how to import the interface into your managed code. As it turns out, Since I didn’t have days or weeks to create the necessary wrapper classes and interfaces for all the items referenced in
But cutting and pasting code from the .idl file into the Delphi Type Library editor, I was able create a new type library file that I could use with
In case you’re curious, Figure 8-16 shows Delphi’s Type Library editor with the interfaces and other items the library contains.
Figure 8-16: The Delphi Type Library editor, which is a convenient tool for rapidly creating type libraries Figure 8-17 shows what the imported assembly MsHtmlCustomization.dll looks like with
Figure 8-17: Exploring the MsHtmlCustomization contents with ILDASM As you can see from the figure, MsHtmlCustomization contains a number of interfaces that are useful when customizing Listing 8-13:: The C# Implementation of IDocHostUIHandler
namespace MsHtmlCustomization
{
[ComImport,
Guid("BD3F23C0-D43E-11CF-893B-00AA00BDCE1A"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
public interface IDocHostUIHandler : IUnknown
{
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement);
public void GetHostInfo(
ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo);
public void ShowUI(int dwID, object pActiveObject,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object pFrame, object pDoc);
public void HideUI();
public void UpdateUI();
public void EnableModeless(int fEnable);
public void OnDocWindowActivate(int fActivate);
public void OnFrameWindowActivate(int fActivate);
public void ResizeBorder(ref MsHtmlCustomization.RECT prcBorder,
int pUIWindow, int fFrameWindow) {}
public void TranslateAccelerator(ref MsHtmlCustomization.MSG lpMsg,
ref MsHtmlCustomization.UUID pguidCmdGroup, int nCmdID);
public void GetOptionKeyPath(ref int pchKey, int dw);
public MsHtmlCustomization.IDropTarget
GetDropTarget(MsHtmlCustomization.IDropTarget pDropTarget);
public object GetExternal();
public int TranslateUrl(int dwTranslate, int pchURLIn);
public MsHtmlCustomization.IdataObject
FilterDataObject(MsHtmlCustomization.IDataObject pDO);
}
}
Besides Listing 8-14:: The C# Implementation of IDocHostShowUI
namespace MsHtmlCustomization
{
[ComImport,
Guid("C4D244B0-D43E-11CF-893B-00AA00BDCE1A"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
public interface IDocHostShowUI : IUnknown
{
public void ShowMessage(int hwnd, ref int lpstrText,
ref int lpstrCaption, uint dwType,
ref int lpstrHelpFile, uint dwHelpContext,
out int lpResult);
public void ShowHelp(uint hwnd, ref int pszHelpFile,
uint uCommand, uint dwData,
MsHtmlCustomization.POINT ptMouse,
out object pDispatchObjectHit);
}
}
This interface is used to control how message boxes and help windows are handled by Returning values from methods invoked through a COM interfaceWhen you import a COM method into managed code, the imported method’s signature is different from the original one. For example, Listing 8-15 shows a native COM method and its equivalent C# method. Listing 8-15:: A COM Method and Its Equivalent C# Method
// the IDL for a COM method
HRESULT ShowContextMenu([in] ContextMenuTarget dwContext,
[in, out] POINT* pPOINT,
[in] IOleCommandTarget* pCommandTarget,
[in] IDispatch* HTMLTagElement);
// the C# declaration of a COM method
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement);
The C# method uses a more friendly notation that is much easier to deal with. A potential problem with the new C# method is the absence of an When you implement COM interfaces using managed code, there are times when you need to set the If you want to directly control the value returned back to COM, using Listing 8-16:: Using COMException to Return HRESULT Values to COM
Most OLE and COM methods use the Common customizationsAlthough you can customize most user interface features of Removing the vertical scroll barThis is probably the number one customization that developers ask for. When
Figure 8-18: The vertical scroll bar that MsHtml shows by default on all HTML pages Internet Explorer also exhibits this behavior, which is to be expected, since it also uses
That’s it. The page will then appear with no scroll bars, as shown in Figure 8-19.
Figure 8-19: Displaying HTML pages with no vertical scroll bar The problem with this approach is that it requires you to change the HTML code of the page displayed — something that is not always feasible. A better way to remove the vertical scrollbar is by using one of the Listing 8-17:: Programmatically Hiding the Vertical Scroll Bar
The code does hide the vertical scroll bar, but is not quite sufficient to prevent users from scrolling. How could they scroll without scroll bars? All they have to do is click the mouse somewhere in the HTML page and drag it off the end of the window. Listing 8-18:: Preventing Users from Scrolling an HTML Document
public void GetHostInfo(ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
// turn two flags on
theHostUIInfo.dwFlags |= (DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_NO3DBORDER |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_DIALOG);
}
In case you’re wondering, the name The Customizing the context menuAnother common request from developers is a way to hide or customize the context menu that appears when users right-click on an HTML page. Figure 8-20 shows the default context menu shown by
Figure 8-20: The default MsHtml context menu The items listed in the menu depend on what type of element was right-clicked in the HTML page. The most popular reason for hiding the context menu is to prevent users from choosing the View Source command to gain access to the HTML code of the page. The proper way to control
Let’s take a look at each of these options. To hide the context menu altogether, just return Listing 8-19:: Preventing MsHtml’s Context Menu from Appearing
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
const int Ok = 0;
throw new COMException("", Ok); // returns HRESULT = S_OK;
}
That was easy. Returning To show your own context menu, you can use the Listing 8-20:: Replacing the MsHtml Context Menu with a Custom Menu
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
// show our custom context menu
Point p = new Point(pPoint.x, pPoint.y);
p = PointToClient(p);
myCustomContextMenu.Show(this, p);
// tell MsHtml that we handled the context menu ourselves
const int Ok = 0;
throw new COMException("", Ok);
}
The parameter
Figure 8-21: Displaying a custom context menu In the Listing 8-21:: Telling MsHtml to Use Its Own Default Context Menu
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
// tell MsHtml to show its default context menu
const int Error = 1;
throw new COMException("", Error); // returns HRESULT = S_FALSE;
}
Preventing new windows from being openedAnother default behavior of Listing 8-22:: Disabling All Accelerator Keys
public void TranslateAccelerator(
ref MsHtmlCustomization.MSG lpMsg,
ref MsHtmlCustomization.UUID pguidCmdGroup,
int nCmdID)
{
// squelch all accelerators
const int Error = 1;
throw new COMException("", Error); // HRESULT = S_FALSE;
}
If you only want to disable certain accelerators, you need to use the Listing 8-23:: Disabling Only the Ctrl-N Accelerator
public void TranslateAccelerator(
ref MsHtmlCustomization.MSG lpMsg,
ref MsHtmlCustomization.UUID pguidCmdGroup,
int nCmdID)
{
const int WM_KEYDOWN = 0x0100;
const int VK_CONTROL = 0x11;
if (lpMsg.message != WM_KEYDOWN)
// don't disable
throw new COMException("", 1); // returns HRESULT = S_FALSE
lpMsg.wParam &= 0xFF; // get the virtual keycode
if (lpMsg.wParam == 'N')
if (GetAsyncKeyState(VK_CONTROL) < 0)
// disable the Ctrl-N accelerator
throw new COMException("", 0); // returns HRESULT = S_OK
// allow everything else
throw new COMException("", 1); // returns HRESULT = S_FALSE
}
The code makes use of the native Windows API method Listing 8-24:: Importing the Windows API Method GetAsyncKeyState()
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(int vKey);
I’ve taken you through some of the most common types of customization you can apply to IDocHostUIHandler methods in detailIn the following sections, I’ll describe the various methods of the
EnableModelessThis method has the signature:
Listing 8-25:: A Simple Example with EnableModeless()
using System.Diagnostics;
public void EnableModeless(int fEnable) {
int i = fEnable;
Trace.WriteLine("EnableModeless: fEnable= " + i);
}
FilterDataObjectThis method has the signature:
The DataObjects in question are those used typically with Clipboard operations. At various times, Listing 8-26:: Allowing All Data Types to Be Handled
public MsHtmlCustomization.IDataObject
FilterDataObject(MsHtmlCustomization.IDataObject pDO)
{
return pDO;
}
GetDropTargetThis method has the signature:
During Drag and Drop operations, when the user drops an object on a target object, Listing 8-27:: Accepting the Default Drop Target
public MsHtmlCustomization.IdropTarget
GetDropTarget(MsHtmlCustomization.IDropTarget pDropTarget)
{
return pDropTarget;
}
GetExternalThis method has the signature:
Listing 8-28:: Returning the IDispatch Interface
public object GetExternal()
{
return this as IDispatch;
}
If you need to implement the
GetHostInfoThis is an important customization callback. The method has the signature:
The parameter passed in is a Listing 8-29:: The Equivalent C# Code for the DOCHOSTUIINFO struct
public struct DOCHOSTUIINFO
{
public uint cbSize;
public uint dwFlags;
public uint dwDoubleClick;
public uint pchHostCss;
public uint pchHostNS;
};
Listing 8-30: The Values that Can Be Assigned to dwDoubleClick
public enum DOCHOSTUIDBLCLK: uint
{
DOCHOSTUIDBLCLK_DEFAULT = 0,
DOCHOSTUIDBLCLK_SHOWPROPERTIES = 1,
DOCHOSTUIDBLCLK_SHOWCODE = 2
};
Table 8-1 describes the values.
Table 8-1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Value | Meaning |
|
|
Perform the default double-click action. |
|
|
Show the properties of the double-clicked item. |
|
|
Show the code for the double-clicked item. |
Before you get too excited about controlling the double-click action with GetHostInfo(), let me inform you that this feature appears to have been disabled in MsHtml.
Getting back to my description of DOCHOSTUIINFO, the field pchHostCss references the Cascading Style Sheet (CSS) used to layout the current HTML page.
The field pchHostNS references a semicolon-delimited list of namespaces used on the page.
By far the most useful field of DOCHOSTUIINFO is dwFlags, which defines a fairly long list of flags you can control to change MsHtml interface elements. Table 8-2 describes each flag.
| Flag Name | Effect When Property Is Set |
|
|
Prevents the user from selecting text. If you don’t want users to be able to scroll content by dragging the mouse, use this flag. |
|
|
Disables the right mouse button pop-up menu. |
|
|
Disables the 3D border around the HTML document displayed. |
|
|
Turns off both vertical and horizontal scroll bars. Users will then only be able to see the part of the HTML document that fits in the window. They will still be able to scroll the window by dragging the mouse off the document. To prevent this last behavior, include the flag |
|
|
Disables all scripts from being run while a page is being loaded. |
|
|
Forces |
|
|
Disables the 3D look on all scroll bars. If no scroll bars are visible, the property has no effect. |
|
|
When the user edits the HTML text on the screen and presses the Enter key, this property makes |
|
|
Tells |
|
|
Disables DHTML behaviors IE 5 and later. (For a discussion of behaviors, see |
|
|
This flag was added only to provide a common look and feel across the two Microsoft products, Outlook Express and Internet Explorer. It applies to Outlook Express 4 and Internet Explorer 5 (or later versions). You’ll probably never use this flag. |
|
|
Disables the use of UTF8 character coding for URLs that have characters that are not in the UTF8 set. By default, |
|
|
Forces the use of UTF8 character coding with URLs that have characters that are not in the UTF8 set. By default, |
|
|
Enables the AutoComplete feature for Forms, which by default is enabled. The value set for this property will be ignored if the user has disabled AutoComplete for Forms in Internet Explorer. To disable AutoComplete for Forms in IE, select the menu command Tools @@> Internet Options, switch to the Content tab, click the AutoComplete button and uncheck the Forms checkbox. |
You can combine multiple flags, as shown in Listing 8-31.
public void GetHostInfo(
ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
// turn three flags on
theHostUIInfo.dwFlags |=
(DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO |