组件化开发与 Visual C#






4.97/5 (76投票s)
2002年11月21日
40分钟阅读

899465
第 8 章:
![]() |
|
本章内容
- 创建简单的Web浏览器
- 在托管代码中使用ActiveX组件
- 从托管代码公开COM接口
- 自定义WebBrowser组件
第 8 章:使用 WebBrowser 组件创建前端。
十亿在这里,十亿在那里——迟早都会累积成真金白银。
—— 美国前参议员 Everett Dirksen 论公共财政
参议员 Dirksen 关于“真金白银”的概念可能与你我的理解略有不同,但有一点是肯定的:如果你在构建应用程序前端时未能利用现有组件,你的开发成本将会累积成“真金白银”。
Microsoft WebBrowser 组件
据我所知,最强大的可重用 UI 组件之一是 Internet Explorer 使用的 WebBrowser ActiveX 组件。Microsoft 设计它的目的是使其在显示内容类型方面极为灵活。显然,它可以显示 HTML 文档,但令一些人惊讶的是,它还可以显示其他常见文件类型,如 Word、Excel、PowerPoint、TXT、RTF、GIF、JPEG、XML、PDF 等。WebBrowser 通过使用嵌入的 ActiveX 组件来渲染其数据,从而实现了这种非凡的灵活性。对于 HTML 文档,组件是 MsHtml。对于更复杂的文档,如 Word 或 Excel,WebBrowser 充当 Active Document 主机,并在客户端区域嵌入 Word 或 Excel 来处理渲染。
由于 WebBrowser 是专门为被容器应用程序托管而设计的,因此它具有许多可以控制的特性。Microsoft 在多个应用程序中使用该组件,并自定义其外观和感觉,使其与主机应用程序的其他部分融为一体。主机示例包括 Windows Explorer、Outlook 甚至 VS .NET 开始页。在本章中,我将向您展示如何创建一个托管 WebBrowser 组件的 Windows 应用程序。稍后,由于自定义对许多人来说非常重要,我将创建一个第二个 Windows 应用程序,展示如何访问 WebBrowser 提供的各种自定义功能。
我将把第一个浏览器应用程序命名为 MyWebBrowser,并为其添加一个工具栏,以展示如何控制 WebBrowser 的一些常用功能。正如我之前所说,WebBrowser 不仅可以处理 HTML 文件。图 8-1 到 8-6 显示了 MyWebBrowser 显示不同类型的文件。
图 8-1: 使用 MyWebBrowser 显示 HTML 页面
图 8-2: 使用 MyWebBrowser 显示 Word 文档
图 8-3: 使用 MyWebBrowser 显示 Excel 电子表格
图 8-4: 使用 MyWebBrowser 显示 PowerPoint 文档
图 8-5: 使用 MyWebBrowser 显示 XML 文档
图 8-6: 使用 MyWebBrowser 显示 PDF 文档
这些屏幕截图希望让您对 WebBrowser 的灵活性有所了解。WebBrowser 及其 Active Document 子组件在后台完成所有工作。如果您能找到比 WebBrowser 更强大的可重用 UI 组件,请告诉我。
WebBrowser 不仅可以显示无数种文件类型,还可以让您控制文档的呈现方式。对于 HTML 文档,有大量的行为可以更改,例如
更改上下文菜单
隐藏滚动条
禁用文本选择
移除 3D 边框
使用扁平化滚动条
强制就地导航
我将向您展示如何进行这些以及其他类型的 MsHtml 自定义,但首先,我将通过一个名为 MyWebBrowser 的简单应用程序来让您熟悉 WebBrowser,该应用程序无需任何自定义即可用作简单的迷你浏览器。在描述 MyWebBrowser 之后,我将展示一个名为 MyCustomWebBrowser 的应用程序中的自定义浏览器。该应用程序将使用 COM 接口和回调来控制 MsHtml 的工作方式。
设计 MyWebBrowser
由于几乎所有您需要的功能都已内置于 WebBrowser 中,因此基于 Windows Forms 的嵌入该组件的应用程序的设计相当简单。图 8-7 显示了 MyWebBrowser 的类图的重要部分。
图 8-7: MyWebBrowser 的类图
正如我所说,在第一个示例中,我不会对 MsHtml 或 WebBrowser 进行任何特殊的自定义,因为这样做需要利用 COM 互操作功能。在展示了一个简单的示例后,我将向您展示如何使用 COM 接口来自定义许多 MsHtml 功能。
开发 MyWebBrowser
关于 WebBrowser 和 MsHtml 的优点就说这么多。它们是内置的,它们很好,它们很棒。太好了。让我们在实际程序中运用它们。MyWebBrowser 是一个简单的 Windows 应用程序,只有一个名为 MainForm 的窗体,它托管 WebBrowser 组件,并演示了如何使用它,更普遍地说,如何从托管代码访问 ActiveX 组件。
我使用“新建项目”向导创建了 MyWebBrowser,选择了“Windows 应用程序”作为项目类型。我将主窗体类从 Form1 重命名为 MainForm,并将项目名称设置为 MyWebBrowser。下一步是将 WebBrowser ActiveX 组件添加到 MainForm,这项任务值得单独解释。
导入 WebBrowser ActiveX 组件
安装 VS .NET 时,工具箱中没有 WebBrowser 组件,因此您必须将 .NET 版本的 WebBrowser 导入到您的项目中。就像生活中的几乎所有事情一样,导入过程可以简单地完成,也可以困难地完成。
简单的方法
简单的方法是使用“自定义工具箱”对话框。首先在工具箱上选择“Windows 窗体”页面,然后右键单击工具箱并选择弹出菜单上的“自定义工具箱”命令。选择“COM 组件”选项卡,向下滚动到 Microsoft Web Browser 项,然后选中其复选框,如图 8-8 所示。
图 8-8: 将 ShDocVw.WebBrowser 控件导入 VS .NET 工具箱
WebBrowser 将显示在工具箱的“Windows 窗体”选项卡中,名称为 Explorer,如图 8-9 所示。
图 8-9: 安装到工具箱后的 WebBrowser 组件
现在将 WebBrowser 的一个实例放到 MainForm 上。组件放置后,VS .NET 会执行以下任务。
它会启动一个导入过程,该过程会创建两个文件 AxInterop.SHDocVw.dll 和 Interop.SHDocVw.dll。
它会将这两个文件放在项目目录的 bin\Debug 或 bin\Release 目录中(取决于项目配置是设置为 Debug 还是 Release)。
它会将这两个文件添加到解决方案资源管理器中项目的“引用”节点下。
它会将 AxSHDocVw.AxWebBrowser 的一个实例添加到您的窗体中。
这项工作很棒,为您节省了宝贵的时间和金钱——可能不是数十亿,但嘿,没人说生活是公平的。
如果您想知道,这个过程的大部分是由一个名为 aximp 的命令行实用程序在后台完成的,稍后将进行描述。此时,解决方案资源管理器看起来如图 8-10 所示。
图 8-10: 解决方案资源管理器,显示新导入的文件
这就是导入 WebBrowser 组件的简单方法。对于那些喜欢打字的人来说,还有一种更难的方法(我知道有很多这样的人)。
困难的方法
我总是喜欢看着人们打开 DOS 窗口并疯狂地输入长命令,而这些命令可以用几个鼠标点击代替。习惯的力量是巨大的。但是,使用命令行实用程序进行导入过程并非总是坏事,因为它们可以创建必要的导入文件,而无需将任何内容添加到工具箱。虽然您可能希望在工具箱中有一个有用的组件,如 WebBrowser,但您不一定希望您将使用的每个 ActiveX 组件都将工具箱宝贵的空间弄得杂乱无章。
无论如何,有两种命令行实用程序可用于将 COM 类型转换为 .NET 兼容类型,这些类型可以在 VS .NET 项目中引用。使用哪一个取决于您想要如何使用导入的组件。
使用 TlbImp
最低级别的 ActiveX 导入命令行实用程序是 TlbImp。当导入不用于 Windows 窗体的 ActiveX 组件时,您应该使用此实用程序。TlbImp 读取包含 COM 类型库信息的文件——可以是 .tlb、.dll、.odl 或其他文件类型——并生成一个包含 .NET 兼容元数据的 DLL。然后必须将该 DLL 添加到项目的“引用”节点下。
例如,要在 MyWebBrowser 项目中使用 shdocvw.dll(包含 WebBrowser 组件,对于那些跳过本节而未阅读前几节的人来说),您需要打开一个命令提示符窗口,转到 C:\Program Files\Microsoft.NET\FrameworkSDK\Bin 文件夹,然后键入命令
tlbimp c:\winnt\system32\shdocvw.dll /out:C:\MyWebBrowser\bin\Debug\Interop.shdocvw.dll
|
您可以使用标准的 .NET 工具(如 ildasm)检查 TlbImp 生成的 DLL 中的 .NET 元数据。打开一个命令行窗口并转到包含新生成的 Shdocvw.dll 文件的文件夹,然后键入命令
"C:\Program Files\Microsoft.NET\FrameworkSDK\Bin\ildasm" shdocvw.dll
|
图 8-11 显示了 ildasm 显示的元数据文件的一些内容。
图 8-11: 检查通过对 c:\winnt\system32\shdocvw.dll 运行 TlbImp 生成的 DLL 中的元数据
元数据使得您的代码可以使用与原生 C# 组件完全相同的语法与 COM 组件的非托管代码进行交互。
使用 AxImp
如果您打算在 Windows 窗体中使用 ActiveX 组件,就像我使用 WebBrowser 一样,您需要使用 AxImp 命令行实用程序而不是 TlbImp。原因是:要在 Windows 窗体中使用 ActiveX 组件,还必须生成包装类。Windows 窗体中的所有组件都必须派生自通用基类 System.Windows.Forms.Control。AxImp 实用程序为您创建包装器。
要为 WebBrowser 创建包装器,请打开一个命令行窗口,转到项目可执行代码将要存放的文件夹。对于 MyWebBrowser 项目,该文件夹将是 MyWebBrowser\bin\debug 或 MyWebBrowser\bin\release。键入以下命令
"C:\Program Files\Microsoft.NET\FrameworkSDK\Bin\aximp" c:\winnt\system32\shdocvw.dll
|
该实用程序将生成两个文件,称为 SHDocVw.dll 和 AxSHDocVw.dll。第一个文件包含描述 c:\winnt\system32\shdocvw.dll 中 COM 类型的 .NET 元数据。该文件与上一节中 TlbImp 生成的文件相同(因为 AxImp 内部调用 TlbImp 来生成它)。第二个文件是一个 .NET 程序集,包含允许您在标准 Windows 窗体中使用 ActiveX 组件的包装类。图 8-12 显示了使用 ildasm 查看的 AxSHDocVw.dll 的内容。
图 8-12: 为 SHDocVw 创建的包装类
基本上,AxImp 创建了一个派生自 System.Windows.Forms.AxHost(它派生自 System.Windows.Forms.Control)的新类。这个新类充当 ActiveX 组件的 .NET 包装器。
运行时可调用包装器 (RCW)
当您在 C# 代码中实例化一个 COM 类型时,使用如下代码
AxSHDocVw.AxWebBrowser axWebBrowser1 = new AxSHDocVw.AxWebBrowser();
|
实际上发生的事情是这样的:编译器查看 TlbImp 或 AxImp 生成的 AxSHDocVw 程序集中关于 AxWebBrowser 类的元数据。利用这些元数据,它创建了一个称为运行时可调用包装器 (RCW) 的东西,它是一个代理组件,一方面可以从您的托管代码调用,另一方面可以处理 WebBrowser ActiveX 组件的非托管 COM 代码。RCW 作为您的代码和 ActiveX 代码之间的不可见桥梁,如图 8-13 所示。
图 8-13: 运行时可调用包装器作为 COM 组件的代理
RCW 管理所有那些您不想处理的棘手 COM 细节,例如引用计数、在不再使用组件时删除它、在方法调用中封送参数等等。如果您在代码中创建了同一 COM 组件的多个实例,所有实例将共享一个 RCW。请记住,所有这些 RCW 业务通常对您来说是完全透明的。它旨在帮助您,使访问 ActiveX 组件像访问任何其他托管组件一样容易。
添加工具栏
在我完全迷失方向之前,让我完成对 MyWebBrowser 代码的描述。首先,我将讨论工具栏按钮。大多数包含 WebBrowser 的 UI 都支持某种导航方式,无论是通过工具栏按钮、菜单命令还是其他元素。
我通过将一个类型为 ToolBar 的工具箱组件放到主窗体上,向 MyWebBrowser 添加了一个工具栏。在 ToolBar 的属性中,我点击了 Collections 字段,并添加了七个具有以下功能的按钮
Back
前进
停止
刷新
Home
搜索
打印
为了让按钮看起来像 Internet Explorer 使用的按钮,我使用屏幕捕获实用程序获取了 IE 按钮图像,然后将它们保存为位图文件,存放在 MyWebBrowser 源代码所在的文件夹中。我将它们都设置为具有绿色背景。我将一个 ImageList 组件添加到窗体,并将所有位图图像添加到其中。我将 ImageList TransparentColor 属性设置为绿色,这样位图的绿色区域将是透明的,并采用按钮面的颜色。然后,我将 ToolBar 的 ImageList 属性设置为引用 ImageList 组件。对于每个工具栏按钮,我设置了以下属性:ToolTipText、Text 和 ImageIndex。在工具栏按钮的右侧,我放置了一个 TextBox 控件。为了让 TextBox 始终拉伸到窗体的右侧,我将其 Anchor 属性设置为(Top、Left、Right)。图 8-14 显示了完成的工具栏。
图 8-14: 完成的工具栏
注意工具栏的“扁平化”外观,按钮没有边框。这种外观是通过将工具栏的 Appearance 属性设置为 Flat 来实现的。接下来,我为工具栏按钮添加了一些事件处理代码,如列表 8-1 所示。
列表 8-1:工具栏事件处理程序
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;
}
}
WebBrowser.GoSearch() 方法导航到搜索站点,该站点可以在 Internet Explorer 中通过单击搜索工具栏按钮并选择搜索窗格中的“自定义”来设置。默认情况下,WebBrowser 使用 http://search.msn.com 的搜索引擎。
WebBrowser.GoHome() 方法导航到 Internet Explorer 中的“工具”>“Internet 选项”对话框中的“常规”选项卡上设置的主页站点。
添加打印支持
除打印按钮外,所有按钮都通过调用嵌入式 WebBrowser 组件中的方法来处理。如前所述,没有 Print() 方法,因此打印的处理方式不同,代码如列表 8-2 所示。
列表 8-2:打印 WebBrowser 中显示的 HTML 页面
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);
}
列表中的最后两行显示了两种打印方式:第一种方式是静默打印,第二种方式是在提示用户进行打印设置后打印。我创建了两个常量 DontPromptUser 和 PromptUser,它们派生自 ShDocVw 枚举值。当您调用 WebBrowser.ExecWB() 时,只需传递指示您是否要让该方法显示打印对话框的常量。
添加导航支持
要导航到 Web 站点,请调用 WebBrowser 的 Navigate() 或 Navigate2() 方法。第一个方法处理导航到普通 URL,包括本地文件。第二个方法通过支持导航到 Windows 桌面和我的电脑文件夹中的项来扩展第一个方法。为了保护应用程序的其余部分免受无效 URL 或网络问题引起的错误影响,我将 WebBrowser.Navigate() 的调用包装在 GotoURL() 方法中的 try 块中,如列表 8-3 所示。
列表 8-3:设置要加载的文档的 URL
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;
}
}
为了让用户在 TextBox 中键入 URL,我为 TextBox 添加了一个事件处理程序,该处理程序调用 GotoURL(),如列表 8-4 所示。
列表 8-4:TextBox 事件处理程序
protected void textBoxAddress_KeyDown (object sender,
System.WinForms.KeyEventArgs e)
{
if (e.KeyCode == Keys.Return)
GotoURL(textBoxAddress.Text);
}
为了在页面加载期间显示沙漏光标,我在 WebBrowser 的 BeforeNavigate2() 处理程序中设置了光标,如列表 8-5 所示。
列表 8-5:在导航期间将光标设置为沙漏
protected void axWebBrowser1_BeforeNavigate2 (object sender,
AxSHDocVw.DWebBrowserEvents2_BeforeNavigate2Event e)
{
toolBarButtonStop.Enabled = true;
Cursor.Current = Cursors.WaitCursor;
}
页面加载完成后,我需要确保光标最终恢复为箭头指针。导航命令可以有两种方式结束:如果页面无法加载,则调用 NavigateError 处理程序。如果页面已加载,则调用 NavigateComplete 处理程序。我创建了一个简单的 NavigateError 处理程序,如列表 8-6 所示。
列表 8-6:恢复鼠标光标的 NavigateError 处理程序
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;
}
如果导航到某个站点成功,则会调用 NavigateComplete2 处理程序(如果可用)。我创建了一个 NavigateComplete2 处理程序,如列表 8-7 所示。
列表 8-7:NavigateComplete2 处理程序
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();
}
在 NavigateComplete2 处理程序中,我在 TextBox 中显示了当前 URL,并更新了访问 URL 的列表。每次用户转到新 URL 时,我都会将其存储在 ArrayList 中。我使用此列表来支持启用和禁用“后退”和“前进”按钮。当用户单击“后退”按钮时,我检索列表中的上一个 URL 并导航到它。当用户一直向后导航到列表的第一个 URL 时,我禁用“后退”按钮。类似地,当用户向前导航到列表末尾时,我禁用“前进”按钮。
禁用“后退”和“前进”按钮不仅仅是外观上的练习:如果您在没有上一个或下一个 URL 可以导航时调用 WebBrowser 的 GoBack() 或 GoForward() 方法,该组件将引发异常。如果您不捕获异常,将显示错误消息。
要添加的最后一个功能是当 MyWebBrowser 运行时使其转到主页的功能。您需要做的就是在 MainForm 的构造函数中调用 axWebBrowser1.GoHome() 方法。
完整代码
下一节将介绍自定义 WebBrowser 组件的方法。在我继续讨论这个新主题之前,我在列表 8-8 中包含了 MyWebBrowser 的完整代码。
列表 8-8: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;
}
}
}
下一节,我将转换方向,深入探讨 COM 互操作编程的高级主题。如果您不熟悉 COM,您可能希望完全跳过本章的其余部分。
创建自定义 Web 浏览器
MyWebBrowser 具有所有基本的浏览器功能,如导航、打印等。它还具有您可能想要更改的功能,例如如何处理快捷键或上下文菜单中可用的命令。要进行这些类型的更改,WebBrowser 会变得更复杂一些,您不得不深入研究一些 COM 互操作编程。在本节及后续各节中,我将创建另一个名为 MyCustomWebBrowser 的 Windows 应用程序,该应用程序演示了如何开发完全自定义的 WebBrowser。要开始项目,我只需将整个 MyWebBrowser 解决方案复制到一个新文件夹中,并将其重命名为 MyCustomWebBrowser。
自定义 WebBrowser 组件比想象的要复杂。问题是这样的:MsHtml 的大多数可自定义功能都依赖于必须由主机窗口(在本例中为 MainForm)处理的 COM 回调。回调是通过 COM 接口公开的方法,因此您必须将必要的接口提供给 MsHtml。如果 MsHtml 暴露了所有可自定义功能的属性列表,以便您可以通过如下简单的代码行更改某个功能,那将是多么美妙
AxWebBrowser.Use3Dborders = false; // this won’t work
|
那将过于简单!此外,每次父 WebBrowser 加载新的 HTML 页面时,它都会创建一个新的 MsHtml 实例,因此即使您可以使用前面的代码设置属性,新的 MsHtml 组件也不会受到影响。您可能会想,“为什么 Microsoft 不将 MsHtml 的可自定义属性存储在父 WebBrowser 组件中?这样您就可以设置一次,然后让 WebBrowser 在每次创建新的 MsHtml 组件时自动重新应用它们。”无论好坏,WebBrowser 都根本不关心内容的呈现。它被设计为渲染组件的主机。它处理 Web 浏览的其他方面,如导航。WebBrowser 是一个 Active Document 主机,它将所有呈现细节委托给托管的 Active Document 渲染组件(在本例中为 MsHtml)。
故事的寓意是这样的:MainForm 需要实现许多 COM 接口来支持 MsHtml 自定义。图 8-15 显示了 MainForm 的类图。
图 8-15: 完全自定义的 WebBrowser 主机 Windows 窗体的类图
让我们看看 COM 接口的用途。任何类型的 WebBrowser 自定义的第一步是建立一个组件作为“控制主机”。默认情况下,WebBrowser 没有控制主机。要将 MainForm 设置为新的控制主机,您需要通过其 OleClient 接口调用 WebBrowser,如列表 8-9 所示。
列表 8-9:将 MainForm 设置为 WebBrowser 的控制主机
object obj = axWebBrowser1.GetOcx();
IOleObject oc = obj as IOleObject;
oc.SetClientSite(this);
这三行看似简单的代码至关重要。GetOcx() 方法检索 axWebBrowser1 包装的本机 COM 对象的 IUnknown 接口。第二行隐式发出 QueryInterface,从 WebBrowser COM 对象中查找 IOleObject 接口。您可以使用 as 运算符来查询任何类型的接口。如果接口不可用,将返回 null。一旦从 WebBrowser 获取了 IOleObject 接口,就会调用 SetClientSite() 方法将 MainForm 设置为控制主机。传递给 SetClientSite() 的对象的参数必须实现 IOleClientSite,因此在图 8-15 中先前显示的类图中有 IOleClientSite。
导入和包装 COM 接口
MyCustomWebBrowser 实现 IOleClientSite 和 IDocHostUIHandler 这两个 COM 接口,并调用 IOleObject 接口的方法。不幸的是,Microsoft 没有发布包含这些接口的类型库,这意味着您必须自己动手进行一些 COM 编程来解决这个问题。事实证明,Microsoft 在其组件中使用的大多数 COM 接口都没有作为类型库发布。您要么会发现它们是 .idl 文件,要么在某些情况下是 C++ 头文件。
IOleObject 和 IOleClientSite
有时您需要做一些挖掘工作才能找到声明接口的文件。对于 IOleClientSite 和 IOleObject,我发现它们都声明在 oleidl.h 文件中,该文件位于以下文件夹
C:\Program Files\Microsoft Visual Studio.NET\Vc7\PlatformSDK\include
|
我创建了 C# 接口来包装这两个 COM 接口,如列表 8-10 和 8-11 所示。
列表 8-10:包装 COM 接口 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);
};
}
列表 8-11:包装 COM 接口 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();
}
}
关于接口声明上使用的属性,有一些解释是必要的。ComImport 属性用于标记一个接口为正在托管代码中实现的现有 COM 接口。ComImported 接口的一个要求是必须有一个 Guid 属性提供接口的全局唯一 ID。InterfaceType 属性指示接口的基本类型。选项是
InterfaceIsDual。此接口同时支持早期绑定和晚期绑定。
InterfaceIsIDispatch。此接口支持 IDispatch 接口。
InterfaceIsIUnknown。此接口仅直接或间接派生自 IUnknown。
ComInterfaceType.InterfaceIsIUnknown 值是迄今为止最常见的接口类型。
我没有足够的空间在此描述最后两个列表中公开的接口的所有方法。出于讨论的目的,唯一重要的方法是 IOleObject.SetClientSite()。
在创建将从本机 COM 代码调用的包装器接口时,指定的 GUID 必须与 COM 使用的原始 GUID 匹配,这一点至关重要。同样重要的是,包装器接口必须实现所有原始 COM 方法,并且所有方法都必须与原始方法具有相同的签名。
使用 ICustomDoc
如前所述,自定义 WebBrowser 的第一步是建立 MainForm 作为控制主机。调用 IOleObject.SetClientSite() 是做到这一点的方法。如果您不感兴趣使用 WebBrowser,而只对 MsHtml 感兴趣,那么还有另一种支持自定义的方法,而无需使用 IOleObject 或 IOleClientSite。
由于自定义 MsHtml 是一项相当常见的操作,该组件提供了一个名为 ICustomDoc 的接口,您可以调用它来指定一个 IDocHostUIHandler 对象,该对象将充当自定义器。当 MsHtml 即将以某种方式与用户交互时,它会检查 IDocHostUIHandler 对象是否可用。如果是,它会调用其方法来了解如何行为。我稍后将讨论这些方法。目前,重要的是 MainForm 如何使用 MsHtml 的 ICustomDoc 接口。列表 8-12 显示了详细信息。
列表 8-12:将组件设置为 MsHtml 的自定义器
object obj = mshtml.GetOcx(); // assume mshtml was created earlier
ICustomDoc doc = obj as ICustomDoc;
doc.SetUIHandler(this); // ‘this’ must implement IDocHostUIHandler
|
请记住,ICustomDoc 接口仅在 MsHtml 上可用,而在 WebBrowser 上不可用,因此列表所示的代码要求您拥有 MsHtml 实例的引用。有许多应用程序单独使用 MsHtml 来渲染 HTML 页面。例如,越来越多的表单和对话框包含 HTML 控件而不是 Windows 控件。通过将 MsHtml 嵌入窗体,您可以轻松访问 MsHtml 中包含的强大 HTML 渲染器。
IDocHostUIHandler
这是 MainForm 在自定义 MsHtml 方面所需的最重要的接口。在接下来的部分中,我将详细介绍该接口有哪些方法、方法的参数是什么、MsHtml 何时调用这些方法以及它们对用户界面的影响。
在我过于激动(我不是在谈论灵魂出窍)之前,让我向您展示如何将接口导入您的托管代码。事实证明,IDocHostUIHandler 引用了相当多的其他 OLE 接口和枚举,因此它不像其他接口那样容易导入。
由于我没有几天或几周的时间来为 IDocHostUIHandler 引用的所有项创建必要的包装类和接口,所以我作弊并使用了一个工具来帮助我。Borland 的 Delphi 产品有一个非常强大且易于使用的类型库编辑器。IDocHostUIHandler 使用的类型主要定义在 mshtmhst.idl 文件中,该文件可在网上找到,并在以下文件夹中可用
C:\Program Files\Microsoft Visual Studio.NET\Vc7\PlatformSDK\include
|
但是,通过从 .idl 文件复制粘贴代码到 Delphi 类型库编辑器中,我能够创建一个新的类型库文件,该文件可与 TypImp 一起使用。我将省略我的复制粘贴冒险的细节。可以说,我创建了一个名为 MsHtmlCustomization.tlb 的文件,我将其保存在“MyCustomWebBrowser\Type Libraries”文件夹中。使用 TlbImp,我轻松地将其转换为一个 .dll 文件,该文件可与 MyCustomWebBrowser 一起使用。我所要做的就是使用“添加引用”向导导入 .dll,然后在 MainForm 类中添加以下语句
using MsHtmlCustomization;
|
如果您好奇,图 8-16 显示了 Delphi 的类型库编辑器,以及库包含的接口和其他项。
图 8-16: Delphi 类型库编辑器,一个用于快速创建类型库的便捷工具
图 8-17 使用 ILDASM 显示了导入的 MsHtmlCustomization.dll 程序集。
图 8-17: 使用 ILDASM 探索 MsHtmlCustomization 的内容
正如您从图中看到的,MsHtmlCustomization 包含许多在自定义 MsHtml 时有用的接口,包括 ICustomDoc 和 IDocHostShowUI。列表 8-13 显示了如果 IDocHostUIHandler 在 C# 中声明会是什么样子。
列表 8-13:IDocHostUIHandler 的 C# 实现
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);
}
}
除了 IDocHostUIHandler 之外,还有一个 COM 接口在自定义 MsHtml 时很有用:IDocHostShowUI。尽管有限的空间阻止我详细描述此接口,但我已将其包含在 MsHtmlCustomization 中。列表 8-14 显示了如果该类在 C# 中声明会是什么样子。
列表 8-14:IDocHostShowUI 的 C# 实现
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);
}
}
该接口用于控制 MsHtml 如何处理消息框和帮助窗口。如您所见,该接口只支持两个方法。为了让 MsHtml 回调 IDocHostShowUI 方法,主机组件(在本例中为 MainForm)需要实现 IOleClientSite 和 IOleDocumentSite 接口。为了使我的讨论尽可能简短,我的示例 MyCustomWebBrowser 不支持 IOleDocumentSite 或 IDocHostShowUI。使用支持 IOleObject、IOleClientSite 和 IDocHostUIHandler 的指南,如果您需要,应该能够添加对 IDocHostShowUI 的支持。
从通过 COM 接口调用的方法返回的值
当您将 COM 方法导入托管代码时,导入方法的签名与原始方法不同。例如,列表 8-15 显示了一个本机 COM 方法及其等效的 C# 方法。
列表 8-15:COM 方法及其等效的 C# 方法
// 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);
C# 方法使用更友好的表示法,更易于处理。新 C# 方法的一个潜在问题是缺少 HRESULT 返回值。虽然 COM 方法通常返回 HRESULT 来指示成功或失败,但翻译后的 C# 方法似乎不提供对 HRESULT 的访问。如您所见,该方法的 C# 版本返回 void,而 IDL 版本返回 HRESULT。
当您使用托管代码实现 COM 接口时,有时需要设置返回的 HRESULT 值。例如,IDocHostUIHandler.ShowContextMenu 使用 HRESULT 向调用者返回一个重要值。通常,运行时可调用包装器类会自动处理 HRESULT,如下所示:如果托管方法引发异常,则向 COM 返回 S_FALSE 值;否则,返回 S_OK 值。
如果您想直接控制返回给 COM 的值,使用 HRESULT 返回参数,您需要抛出一个名为 COMException 的特殊异常。当您抛出这种类型的异常时,RCW 会拦截它并使用其参数作为返回给 COM 的 HRESULT。列表 8-16 向您展示了如何使用 COMException。
列表 8-16:使用 COMException 将 HRESULT 值返回给 COM
const int Ok = 0;
const int Error = 1;
// use one of the following statements
throw new COMException("", Ok); // HRESULT returned: S_OK
throw new COMException("", Error); // HRESULT returned: S_FALSE
|
大多数 OLE 和 COM 方法使用 HRESULT 来指示执行的成功或失败。在少数 HRESULT 用于返回参数的情况下,只需抛出 COMException。
常见自定义
虽然您可以自定义 MsHtml 的大多数用户界面功能,但有些自定义需求量很大,因此我将在本节中讨论它们,以便在书中更容易找到它们。对于其他类型的自定义,您需要查看本章后面讨论 MsHtml 调用并由 MsHtml 接口公开的各个回调方法的各节。
移除垂直滚动条
这可能是开发人员最常要求的自定义。当 MsHtml 显示页面时,总会显示一个垂直滚动条,无论页面是否完全可见。如果页面适合屏幕,将显示一个没有拇指的垂直滚动条,如图 8-18 所示。
图 8-18: MsHtml 默认在所有 HTML 页面上显示的垂直滚动条
Internet Explorer 也表现出这种行为,这是可以预料的,因为它在内部也使用 MsHtml 来显示 HTML 内容。无论您如何调整窗口大小,垂直滚动条始终会出现。一种移除滚动条的方法(如果您可以控制显示的内容)涉及设置页面
元素的属性。使用这种方法,您无需编写任何编程代码。只需将 scroll 属性添加到页面的 标签中,如下所示
<BODY scroll="NO">
|
就是这样。页面将显示没有滚动条,如图 8-19 所示。
图 8-19: 显示没有垂直滚动条的 HTML 页面
这种方法的缺点是它要求您更改要显示的页面的 HTML 代码——这并非总是可行的。移除垂直滚动条的更好方法是使用 IDocHostUIHandler 回调之一。当 MsHtml 与用户界面交互时,它会频繁调用这些回调方法来查看如何进行。通过实现 GetHostInfo() 方法,您可以禁用垂直滚动条。这种程序化自定义的好处是它不需要您对要显示的 HTML 页面内容进行任何更改。列表 8-17 显示了 GetHostInfo() 方法需要看起来像什么才能关闭垂直滚动条。
列表 8-17:以编程方式隐藏垂直滚动条
public void GetHostInfo(ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
theHostUIInfo.dwFlags |= DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO;
}
|
代码确实隐藏了垂直滚动条,但不足以阻止用户滚动。他们没有滚动条如何滚动?他们只需在 HTML 页面中的某个位置单击鼠标并将其拖出窗口的末尾。MsHtml 认为用户想要选择文本,并向下或向右滚动。要禁用此行为,您需要将 DOCHOSTUIFLAG_DIALOG 标志添加到 GetHostInfo() 返回的值中,如列表 8-18 所示。
列表 8-18:防止用户滚动 HTML 文档
public void GetHostInfo(ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
// turn two flags on
theHostUIInfo.dwFlags |= (DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_NO3DBORDER |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_DIALOG);
}
如果您想知道,DOCHOSTUIFLAG_DIALOG 这个名字来源于这样一个事实,即该标志通常用于在普通对话框中显示 HTML 页面。在这种情况下,您通常不希望用户能够滚动对话框内容。
GetHostInfo() 方法使您能够控制各种其他功能。有关详细信息,请参阅本章后面的“GETHOSTINFO”部分。请记住,如果您禁用了垂直滚动条,用户将无法在 HTML 页面上向下滚动,即使文档超出了窗口底部。
自定义上下文菜单
开发人员的另一个常见请求是隐藏或自定义用户右键单击 HTML 页面时出现的上下文菜单。图 8-20 显示了 MsHtml 显示的默认上下文菜单。
图 8-20: 默认的 MsHtml 上下文菜单
菜单中列出的项目取决于右键单击 HTML 页面中的元素类型。隐藏上下文菜单的最常见原因是阻止用户选择“查看源代码”命令以访问页面 HTML 代码。控制 MsHtml 上下文菜单的正确方法是通过 ShowContextMenu() 回调。此回调让您完全控制上下文菜单,使您能够执行以下操作
完全隐藏上下文菜单。
显示您自己的上下文菜单。
让我们来看一下这些选项。要完全隐藏上下文菜单,只需从 ShowContextMenu() 返回 S_OK 作为 HRESULT,如列表 8-19 所示。
列表 8-19:阻止 MsHtml 的上下文菜单出现
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;
}
这很容易。从回调中返回 S_OK 是告诉 MsHtml 您的主机代码已处理了上下文菜单,MsHtml 不会采取任何进一步的操作。您的回调代码是否显示了自己的上下文菜单对 MsHtml 很重要。
要显示自己的上下文菜单,您可以使用 pPoint 参数获取用户右键单击的位置。使用标准的 .NET Framework ContextMenu 组件,您可以在 MsHtml 会显示默认菜单的相同位置显示自定义菜单。列表 8-20 显示了一个示例。
列表 8-20:用自定义菜单替换 MsHtml 上下文菜单
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);
}
传递给 ShowContextMenu() 的参数 pPoint 是用户右键单击的位置。点是以屏幕坐标表示的,因此您需要调用 PointToClient 将其转换为客户端坐标。我创建了一个包含一个标有“打印”命令的简单菜单。图 8-21 显示了上下文菜单在浏览器中显示的样子。
图 8-21: 显示自定义上下文菜单
在 ShowContextMenu() 回调中,您可能希望添加逻辑,在某些条件下显示自定义菜单,而在其他条件下显示标准的 MsHtml 菜单。要让 MsHtml 显示其自己的默认上下文菜单,请抛出 COMException,如列表 8-21 所示。
列表 8-21:告诉 MsHtml 使用其自己的默认上下文菜单
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;
}
阻止打开新窗口
MsHtml 的另一个您可能希望禁用的是允许使用快捷键打开新浏览器窗口的默认行为。MsHtml 的内置行为是在按下 Ctrl-N 快捷键时在新窗口中打开当前文档。要禁用此行为,您只需向回调方法添加一些代码即可。任何时候按下快捷键(当 MsHtml 具有输入焦点时),它都会调用 TranslateAccelerator() 回调方法,传递一个包含快捷键代码的消息。从回调返回的 HRESULT 值告诉 MsHtml 该怎么做。S_OK 值告诉 MsHtml 处理快捷键。如果您不希望 MsHtml 处理快捷键,请返回 S_FALSE 值。列表 8-22 显示了如何阻止所有快捷键被处理。
列表 8-22:禁用所有快捷键
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;
}
如果您只想禁用某些快捷键,则需要使用 lpMsg 参数来查看按下了哪些键。列表 8-23 显示了如何仅禁用 Ctrl-N 快捷键。
列表 8-23:仅禁用 Ctrl-N 快捷键
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
}
该代码利用了本机 Windows API 方法 GetAsyncKeyState() 来获取 Ctrl 键的状态。使用列表 8-24 中所示的代码导入该方法。
列表 8-24:导入 Windows API 方法 GetAsyncKeyState()
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(int vKey);
我带您了解了一些最常见的自定义类型,您可以将其应用于 MsHtml,但还有更多。在接下来的章节中,我将根据 MsHtml 通过 IDocHostUIHandler 接口公开的回调方法来描述它们。
IDocHostUIHandler 方法详解
在接下来的章节中,我将描述 IDocHostUIHander 接口的各种方法。方法按字母顺序列出。
当 WebBrowser 组件使用 MsHtml 显示 HTML 文档时,会调用 IDocHostUIHandler 方法。如果加载了不同的文档类型,例如 Word 或 PDF 文件,IDocHostUIHandler 方法不一定会调用。
EnableModeless
该方法签名如下:
public void EnableModeless(int fEnable);
|
MsHtml 在各种时间调用 EnableModeless() 来告诉您禁用主机组件中可能存在的任何无模式对话框。例如,当 MsHtml 即将显示错误消息时,它会调用 EnableModeless,并将 fEnable 设置为 0。用户关闭错误消息后,MsHtml 再次调用 EnableModeless(),并将 fEnable 设置为 1,告知您可以启用可能存在的任何无模式对话框。列表 8-25 是一个简单的示例,它仅将对 EnableModeless() 的调用记录到 Trace 输出窗口。
列表 8-25:EnableModeless() 的简单示例
using System.Diagnostics;
public void EnableModeless(int fEnable) {
int i = fEnable;
Trace.WriteLine("EnableModeless: fEnable= " + i);
}
FilterDataObject
该方法签名如下:
public MsHtmlCustomization.IDataObject
FilterDataObject(MsHtmlCustomization.IDataObject pDO);
|
所述 DataObjects 通常与剪贴板操作相关。MsHtml 可能在各种时间调用 FilterDataObject 来让主机组件看到将要处理的数据类型。要阻止处理,请返回 null 值。要允许处理,请返回 pDO 对象,如列表 8-26 所示。
列表 8-26:允许处理所有数据类型
public MsHtmlCustomization.IDataObject
FilterDataObject(MsHtmlCustomization.IDataObject pDO)
{
return pDO;
}
GetDropTarget
该方法签名如下:
public MsHtmlCustomization.IdropTarget
GetDropTarget(MsHtmlCustomization.IDropTarget pDropTarget)
|
在拖放操作期间,当用户将对象拖放到目标对象上时,MsHtml 会调用 GetDropTarget()。使用此方法,您可以提供一个替代目标。更改放置目标很不寻常。列表 8-27 显示了如何接受默认目标。
列表 8-27:接受默认放置目标
public MsHtmlCustomization.IdropTarget
GetDropTarget(MsHtmlCustomization.IDropTarget pDropTarget)
{
return pDropTarget;
}
GetExternal
该方法签名如下:
public object GetExternal();
|
MsHtml 调用此方法以获取主机组件(在本例中为 MainForm)的 IDispatch 接口。如果主机未实现 IDispatch,则必须返回 null。列表 8-28 显示了如果您的主机实现了它,您将如何返回 IDispatch。
列表 8-28:返回 IDispatch 接口
public object GetExternal()
{
return this as IDispatch;
}
如果您需要在类中实现 IDispatch 接口,则 IDispatch 的托管代码包装器可在文件 StdOle.dll 中找到,位于以下文件夹
C:\Program Files\Microsoft.NET\Primary Interop Assemblies
|
GetHostInfo
这是一个重要的自定义回调。该方法签名如下:
public void GetHostInfo(
ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo);
|
传入的参数是一个 struct,其代码如列表 8-29 所示。
列表 8-29:DOCHOSTUIINFO struct 的等效 C# 代码
public struct DOCHOSTUIINFO
{
public uint cbSize;
public uint dwFlags;
public uint dwDoubleClick;
public uint pchHostCss;
public uint pchHostNS;
};
cbSize 是 struct 的字节长度。dwFlags 是最重要的字段,将在后面详细介绍。dwDoubleClick 是您指定用户双击鼠标时在 MsHtml 中显示的内容的位置。可能的值如列表 8-30 所示。
列表 8-30:可以赋给 dwDoubleClick 的值
public enum DOCHOSTUIDBLCLK: uint
{
DOCHOSTUIDBLCLK_DEFAULT = 0,
DOCHOSTUIDBLCLK_SHOWPROPERTIES = 1,
DOCHOSTUIDBLCLK_SHOWCODE = 2
};
表 8-1 描述了这些值。
表 8-1
DOCHOSTUIINFO.dwDoubleClick 的允许值
值 | 含义 |
|
执行默认双击操作。 |
|
显示双击项的属性。 |
|
显示双击项的代码。 |
在您对通过 GetHostInfo() 控制双击操作感到兴奋之前,我通知您,此功能似乎已在 MsHtml 中禁用。
回到对 DOCHOSTUIINFO 的描述,pchHostCss 字段引用用于布局当前 HTML 页面的级联样式表 (CSS)。
pchHostNS 字段引用页面上使用的命名空间的分号分隔列表。
迄今为止,DOCHOSTUIINFO 中最有用的字段是 dwFlags,它定义了一长串您可以控制以更改 MsHtml 界面元素的标志。表 8-2 描述了每个标志。
表 8-2
DOCHOSTUIINFO.dwFlags 中可用的标志
标志名称 | 设置属性时的效果 |
|
阻止用户选择文本。如果您不希望用户通过拖动鼠标来滚动内容,请使用此标志。 |
|
禁用右键弹出菜单。 |
|
禁用显示 HTML 文档周围的 3D 边框。 |
|
关闭垂直和水平滚动条。用户 then 只能看到适合窗口的 HTML 文档部分。他们仍然可以通过拖动鼠标离开文档来滚动窗口。要阻止最后一种行为,请包含 DOCHOSTUIFLAG_DIALOG 标志。 |
|
在页面加载期间禁用所有脚本的运行。 |
|
如果单击链接,则强制 WebBrowser 打开一个新的 Internet Explorer 窗口。 |
|
禁用所有滚动条的 3D 外观。如果不可见滚动条,则属性无效。 |
|
当用户编辑屏幕上的 HTML 文本并按 Enter 键时,此属性使 MsHtml 在 HTML 代码中插入一个 标签,而不是默认的 标签。 |
|
指示 MsHtml 仅在用户单击客户端区域时才获取输入焦点。默认情况下,即使用户单击非客户端区域(如滚动条),组件也会获得焦点。 |
|
禁用 IE 5 及更高版本的 DHTML 行为。(关于行为的讨论,请参见 http://msdn.microsoft.com/library/periodic/period99/HTMLbehaviors.htm。) |
|
此标志仅用于在 Outlook Express 和 Internet Explorer 这两个 Microsoft 产品之间提供通用外观和感觉。它适用于 Outlook Express 4 和 Internet Explorer 5(或更高版本)。您可能永远不会使用此标志。 |
|
禁用对不属于 UTF8 集合的字符的 URL 使用 UTF8 字符编码。默认情况下,MsHtml 始终会尝试使用 UTF8。适用于 IE 5 及更高版本。 |
|
强制对不属于 UTF8 集合的字符的 URL 使用 UTF8 字符编码。默认情况下,MsHtml 始终会尝试使用 UTF8。适用于 IE 5 及更高版本。 |
|
启用表单的自动完成功能,该功能默认启用。如果用户在 Internet Explorer 中禁用了表单的自动完成功能,则此属性设置将被忽略。要在 IE 中禁用表单的自动完成功能,请选择菜单命令“工具”>“Internet 选项”,切换到“内容”选项卡,单击“自动完成”按钮,然后取消选中“表单”复选框。 |
您可以通过布尔 OR 操作组合多个标志,如列表 8-31 所示。
列表 8-31:在 GetHostInfo() 中设置多个标志
public void GetHostInfo(
ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
// turn three flags on
theHostUIInfo.dwFlags |=
(DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_NO3DBORDER |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_DISABLE_SCRIPT_INACTIVE);
}
您可以通过将它们组合在一起并使用布尔 OR 操作来设置任何需要的标志。
GetOptionKeyPath
调用此方法以检索用于存储用户首选项的注册表路径。很少使用。
HideUI
如果主机组件绘制与 MsHtml 状态相关的 UI 元素(如工具栏或菜单),则需要确保您的 UI 元素仅在适当的时间显示。当发生 HideUI() 回调时,您需要隐藏这些元素。稍后,MsHtml 将调用 ShowUI() 方法,让您将隐藏的元素恢复到屏幕上。
OnDocWindowActivate
该方法签名如下:
public void OnDocWindowActivate(int fActivate);
|
MsHtml 在各种时间调用此方法,以告知您显示的 HTML 文档何时被激活或停用。文档基本上在获得输入焦点时被视为处于激活状态。Windows 以与非活动窗口不同的标题栏颜色显示当前活动窗口。MsHtml 调用以告知您活动状态何时发生变化的原因是为了允许您的主机组件对其 UI 进行任何必要的更改。
OnFrameWindowActivate
该方法签名如下:
public void OnFrameWindowActivate(int fActivate);
|
MsHtml 在各种时间调用此方法,以告知您包含 MsHtml 的顶层框架窗口何时被激活或停用。使用此回调方法来更改主机组件中在窗口激活与停用时外观不同的任何 UI 元素。
ResizeBorder
该方法签名如下:
public void ResizeBorder(ref MsHtmlCustomization.RECT prcBorder,
int pUIWindow, int fFrameWindow);
|
如果您的主机组件允许 MsHtml 被“就地激活”,则此方法非常有用。当嵌入式组件被就地激活时,主机通常会在嵌入式组件周围绘制一个网格边框。就地激活曾经被认为是一项非常酷的技术,它使用了菜单合并和 OLE 嵌套文档。除了 Microsoft Office 等产品之外,就地激活实际上并不常用,因此支持 ResizeBorder() 的要求有些不寻常。
ShowContextMenu
该方法签名如下:
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement);
|
这是一个常用的回调方法,因为它允许您自定义弹出菜单在 MsHtml 中的行为。您基本上有三个选项
允许 MsHtml 显示其上下文菜单。
完全禁用上下文菜单。
隐藏 MsHtml 的菜单并显示您自己的菜单。
要允许 MsHtml 显示其常规上下文菜单,请通过抛出 COMException 来返回 S_FALSE 值,如列表 8-32 所示。
列表 8-32:允许 MsHtml 显示其默认上下文菜单
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
const int Error = 1;
throw new COMException("", Error); // returns HRESULT = S_FALSE
}
要阻止 MsHtml 显示其上下文菜单,请通过抛出 COMException 来返回 S_OK 值,如列表 8-33 所示。
列表 8-33:完全禁用上下文菜单
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
}
要显示您自己的自定义菜单,请在回调中使用 ContextMenu 组件,并通过抛出 COMException 来返回 S_OK 值,如列表 8-34 所示。
列表 8-34:完全禁用上下文菜单
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
Point p = new Point(pPoint.x, pPoint.y);
p = PointToClient(p);
myCustomContextMenu.Show(this, p);
const int Ok = 0;
throw new COMException("", Ok); // return HRESULT = S_OK, so MsHtml
// doesn’t display its own menu
}
注意对 PointToClient() 的调用。此调用是必需的,因为 pPoint 是屏幕坐标,而 ContextMenu.Show() 需要客户端坐标。通过设置您自己的 ContextMenu,您可以以任何您想要的方式自定义弹出菜单。
ShowUI
如果主机组件(在本例中为 MainForm)需要绘制与 MsHtml 相关的 UI 元素(如工具栏或菜单),此回调会告诉您何时显示它们。稍后,MsHtml 将调用 HideUI() 和 UpdateUI() 方法,您应该准备好隐藏或刷新您的 UI 元素。您不需要处理 ShowUI(),除非您的 UI 元素与 MsHtml 的状态相关。
TranslateAccelerator
当用户按下加速键(如 Ctrl-O 或 Ctrl-P)时,MsHtml 会调用 TranslateAccelerator() 来查看您想要做什么。此方法签名如下:
public void TranslateAccelerator(
ref MsHtmlCustomization.MSG lpMsg,
ref MsHtmlCustomization.UUID pguidCmdGroup,
int nCmdID);
|
加速键是激活菜单命令的键。表 8-3 显示了 MsHtml 支持的重要加速键。
表 8-3
MsHtml 支持的主要加速键
加速键 | 描述 |
Ctrl-N |
在新 WebBrowser 窗口中打开当前 HTML 文档。 |
Ctrl-P |
显示打印对话框以打印 HTML 文档。 |
Ctrl-A |
选择 HTML 文档的全部内容。 |
Ctrl-F |
显示查找对话框以搜索 HTML 文档。 |
F5, Ctrl-F5 |
刷新当前加载的 HTML 文档。 |
最常禁用的加速键是 Ctrl-N 和 Ctrl-P。列表 8-35 显示了如何禁用它们。
列表 8-35:禁用 Ctrl-N 和 Ctrl-P 加速键
public void TranslateAccelerator(
ref MsHtmlCustomization.MSG lpMsg,
ref MsHtmlCustomization.UUID pguidCmdGroup,
int nCmdID)
{
const int Ok = 0;
const int Error = 1;
const int WM_KEYDOWN = 0x0100;
const int VK_CONTROL = 0x11;
if (lpMsg.message != WM_KEYDOWN)
// allow message
throw new COMException("", Error); // returns HRESULT = S_FALSE
if (GetAsyncKeyState(VK_CONTROL) >= 0)
// Ctrl key not pressed: allow message
throw new COMException("", Error); // returns HRESULT = S_FALSE
// disable the Ctrl-N and Ctrl-P accelerators
lpMsg.wParam &= 0xFF; // get the virtual keycode
if ( (lpMsg.wParam == 'N') || ((lpMsg.wParam == 'P')) )
throw new COMException("", Ok); // returns HRESULT = S_OK
// allow everything else
throw new COMException("", Error); // returns HRESULT = S_FALSE
}
TranslateUrl
当用户单击 HTML 文档中的超链接时,会调用此方法。在将链接的 URL 传递给 WebBrowser 之前,MsHtml 会调用 TranslateUrl() 以允许主机组件修改 URL。
UpdateUI
如果您绘制基于 MsHtml 状态的 UI 元素,此回调会告诉您何时刷新这些元素。当 MsHtml 中发生重大状态变化时,此回调就会发生。
完整代码
我向您展示了许多代码片段。列表 8-36 显示了 MyCustomWebBrowser 的 MainForm 的完整代码。
列表 8-36:MyCustomWebBrowser 的 MainForm 的完整代码
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using System.Diagnostics;
using MsHtmlCustomization;
namespace MyCustomWebBrowser
{
public class MainForm : System.Windows.Forms.Form,
IOleClientSite,
IDocHostUIHandler
{
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();
private System.Windows.Forms.ContextMenu myCustomContextMenu;
private System.Windows.Forms.MenuItem menuItemPrint;
int currentUrlIndex = -1; // no sites visited initially
public MainForm()
{
InitializeComponent();
// tell WebBrowser that we are its host
object obj = axWebBrowser1.GetOcx();
IOleObject oc = obj as IOleObject;
oc.SetClientSite(this);
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-generated code
}
private void InitializeComponent()
{
// standard wizard-generated code...
this.axWebBrowser1 = new AxSHDocVw.AxWebBrowser();
this.panel1 = new System.Windows.Forms.Panel();
this.textBoxAddress = new System.Windows.Forms.TextBox();
this.myCustomContextMenu = new System.Windows.Forms.ContextMenu();
this.menuItemPrint = new System.Windows.Forms.MenuItem();
//
// axWebBrowser1
//
this.axWebBrowser1.Dock = System.Windows.Forms.DockStyle.Fill;
this.axWebBrowser1.Enabled = true;
this.axWebBrowser1.Location = new System.Drawing.Point(0, 28);
this.axWebBrowser1.OcxState =
((System.Windows.Forms.AxHost.State)
(resources.GetObject("axWebBrowser1.OcxState")));
this.axWebBrowser1.Size = new System.Drawing.Size(394, 245);
this.axWebBrowser1.TabIndex = 1;
this.axWebBrowser1.NavigateError += new
AxSHDocVw.DWebBrowserEvents2_NavigateErrorEventHandler(
this.axWebBrowser1_NavigateError);
this.axWebBrowser1.NavigateComplete2 += new
AxSHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(
this.axWebBrowser1_NavigateComplete2);
this.axWebBrowser1.BeforeNavigate2 += new
AxSHDocVw.DWebBrowserEvents2_BeforeNavigate2EventHandler(
this.axWebBrowser1_BeforeNavigate2);
// more wizard-generated 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;
}
// implement IOleClientSite methods
void IOleClientSite.SaveObject() {}
void IOleClientSite.GetMoniker(uint dwAssign,
uint dwWhichMoniker,
object ppmk) {}
void IOleClientSite.GetContainer(object ppContainer)
{
ppContainer = this;
}
void IOleClientSite.ShowObject() {}
void IOleClientSite.OnShowWindow(bool fShow) {}
void IOleClientSite.RequestNewObjectLayout() {}
// implement IDocHostUIHandler methods
public void ShowContextMenu(
MsHtmlCustomization.ContextMenuTarget dwContext,
ref MsHtmlCustomization.POINT pPoint,
MsHtmlCustomization.IOleCommandTarget pCommandTarget,
object HTMLTagElement)
{
// use this code to show a custom menu
const int Ok = 0;
Point p = new Point(pPoint.x, pPoint.y);
p = PointToClient(p);
myCustomContextMenu.Show(this, p);
throw new COMException("", Ok); // HRESULT = S_OK
// use this code to let MsHtml shows its menu
// const int Error = 1;
// throw new COMException("", Error); // HRESULT = S_FALSE
}
public void GetHostInfo(
ref MsHtmlCustomization.DOCHOSTUIINFO theHostUIInfo)
{
// turn two flags on
theHostUIInfo.dwFlags |= (DOCHOSTUIFLAG.DOCHOSTUIFLAG_SCROLL_NO |
DOCHOSTUIFLAG.DOCHOSTUIFLAG_NO3DBORDER);
}
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) {
int i = fEnable;
Trace.WriteLine("EnableModeless: fEnable= " + i);
}
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)
{
const int Ok = 0;
const int Error = 1;
const int WM_KEYDOWN = 0x0100;
const int VK_CONTROL = 0x11;
if (lpMsg.message != WM_KEYDOWN)
// allow message
throw new COMException("", Error); // returns HRESULT = S_FALSE
if (GetAsyncKeyState(VK_CONTROL) >= 0)
// Ctrl key not pressed: allow message
throw new COMException("", Error); // returns HRESULT = S_FALSE
// disable the Ctrl-N and Ctrl-P accelerators
lpMsg.wParam &= 0xFF; // get the virtual keycode
if ( (lpMsg.wParam == 'N') || ((lpMsg.wParam == 'P')) )
throw new COMException("", Ok); // returns HRESULT = S_OK
// allow everything else
throw new COMException("", Error); // returns HRESULT = S_FALSE
}
public void GetOptionKeyPath(ref int pchKey, int dw) {}
public MsHtmlCustomization.IdropTarget
GetDropTarget(MsHtmlCustomization.IDropTarget pDropTarget)
{
return pDropTarget;
}
public object GetExternal()
{
return null;
}
public int TranslateUrl(int dwTranslate, int pchURLIn) {return 0;}
public MsHtmlCustomization.IDataObject
FilterDataObject(MsHtmlCustomization.IDataObject pDO)
{
return pDO;
}
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(int vKey);
}
}
摘要
通过 WebBrowser 可以进行自定义的方式之多,您现在一定意识到该组件的重要性,不仅对 Microsoft 而言,对您自己的应用程序而言也是如此。一旦您将适当的 COM 接口导入到您的托管代码中,WebBrowser 就是您的朋友。此外,它几乎安装在地球上的每台 Windows 计算机上,具有非凡的灵活性,并且使用起来非常方便。