捕获显示 PDF 文档的 WPF WebBrowser 控件内的单击事件
Capture click events inside a WPF WebBrowser control showing a PDF document
我正在开发一个 Windows WPF 应用程序,它使用默认的 WebBrowser 控件 (System.Windows.Controls.WebBrowser) 来嵌入网页。使用作为 WPF 控件基础的 COM 对象,我可以根据需要操作控件中加载的每个 HTML 文档。作为示例,这里是我用来获取 COM 对象句柄的代码片段:
public void HookInputElementForKeyboard()
{
HTMLDocument htmlDocument = (HTMLDocument)webBrowserControl.Document;
var inputElements = htmlDocument.getElementsByTagName("input");
HTMLDocumentEvents_Event documentEvents = (HTMLDocumentEvents_Event) htmlDocument;
documentEvents.onclick += documentEvents_onclick;
DeregisterAll();
foreach (var item in inputElements)
{
DispHTMLInputElement inputElement = item as DispHTMLInputElement;
if (inputElement.type == "text" || inputElement.type == "password" || inputElement.type == "search")
{
HTMLButtonElementEvents_Event htmlButtonEvent = inputElement as HTMLButtonElementEvents_Event;
this.hookedElements.Add(htmlButtonEvent);
htmlButtonEvent.onclick += InputOnClickHandler;
htmlButtonEvent.onblur += InputOnBlurHandler;
}
}
}
我在哪里使用来自 Microsoft.mshtml.dll 的依赖项。
在这里,我将处理程序附加到 DOM 元素的事件,以便能够在 .NET 代码中管理它们(onclick 和 onblur 事件)。
有了那个对象 (HTMLDocument),我几乎可以克服 WPF 控件的所有限制。
当 WebBrowser 控件导航到 PDF 文档时(即响应 MIME 类型为 application/pdf),我的问题就出现了。在这种情况下,我必须假设默认的 Adobe Reader 插件用于显示 PDF(这是我的目标机器的行为方式)。我仍然可以获得与以下内容一起使用的底层 AcroRead ActiveX 的句柄:
private void HookHtmlDocumentOnClick()
{
var document = (IAcroAXDocShim)WebBrowserControl.Document;
// Just a test
var obj = document as AcroPDF;
obj.OnMessage += obj_OnMessage;
}
在我向项目添加适当的依赖项后 (Interop.AcroPDFLib.dll)
但此时我不知道是否有办法注册文档上发生的鼠标事件。我要做的就是处理PDF文档的点击事件。
当然,使用下面的,是行不通的。该事件不会冒泡到 .NET 代码级别。
WebBrowserControl.MouseDown += WebBrowserControl_MouseDown;
有人知道是否有办法挂接 IAcroAXDocShim 以处理鼠标单击事件吗?
任何可能的选择?我应该走一条完全不同的道路吗?
与当前情况相比,直接使用 AcroRead ActiveX 是否有一些优势?
我最终采用了不同的方法。每当识别出 PDF 内容时,我决定切换到不同的 UserControl,而不是 WPF WebBrowser。
我知道当你开发一个 Windows 商店应用程序时,一些 类 和 API 用于 PDF 渲染是可用的,所以我应用了一个解决方案以便在我的 WPF 中使用那些 类申请。
首先,我编辑了 csproj 文件,添加了以下标签作为 <PropertyGroup>
的子标签:
<TargetPlatformVersion>8.1</TargetPlatformVersion>
然后一个名为 "Windows 8.1" 的部分应该出现在“添加引用”对话框中 window。我将本节的唯一程序集添加到参考文献中。为了使解决方案有效,可能需要更多的程序集:
- System.IO
- System.Runtime
- System.Runtime.InteropServices
- System.Runtime.InteropServices.Windows运行时
- System.Runtime.Windows运行时间
- System.Threading
- System.Threading.任务
有了这些之后,我编写了一个渲染方法,遍历 PDF 文件的页面并为每个页面创建一个 png 文件。然后我将页面添加到 StackPanel。此时,您可以像通常在任何 UIElement 上那样管理 StackPanel 上的 Click(或 MouseDown)事件。
以下是渲染方法的简要示例:
async private void RenderPages(String pdfUrl)
{
try
{
HttpClient client = new HttpClient();
var responseStream = await client.GetInputStreamAsync(new Uri(pdfUrl));
InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream();
using (responseStream)
{
await responseStream.AsStreamForRead().CopyToAsync(inMemoryStream.AsStreamForWrite());
}
var pdf = await PdfDocument.LoadFromStreamAsync(inMemoryStream);
if (pdf != null && pdf.PageCount > 0)
{
AddPages(pdf);
}
}
catch (Exception e)
{
throw new CustomException("An exception has been thrown while rendering PDF document pages", e);
}
}
async public void AddPages(PdfDocument pdfDocument)
{
PagesStackPanel.Children.Clear();
if (pdfDocument == null || pdfDocument.PageCount == 0)
{
throw new ArgumentException("Invalid PdfDocument object");
}
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
StorageFolder rootFolder = await StorageFolder.GetFolderFromPathAsync(path);
var storageItemToDelete = await rootFolder.TryGetItemAsync(Constants.LOCAL_TEMP_PNG_FOLDER);
if (storageItemToDelete != null)
{
await storageItemToDelete.DeleteAsync();
}
StorageFolder tempFolder = await rootFolder.CreateFolderAsync(Constants.LOCAL_TEMP_PNG_FOLDER, CreationCollisionOption.OpenIfExists);
for (uint i = 0; i < pdfDocument.PageCount; i++)
{
logger.Info("Adding PDF page nr. " + i);
try
{
await AppendPage(pdfDocument.GetPage(i), tempFolder);
}
catch (Exception e)
{
logger.Error("Error while trying to append a page: ", e);
throw new CustomException("Error while trying to append a page: ", e);
}
}
}
我正在开发一个 Windows WPF 应用程序,它使用默认的 WebBrowser 控件 (System.Windows.Controls.WebBrowser) 来嵌入网页。使用作为 WPF 控件基础的 COM 对象,我可以根据需要操作控件中加载的每个 HTML 文档。作为示例,这里是我用来获取 COM 对象句柄的代码片段:
public void HookInputElementForKeyboard()
{
HTMLDocument htmlDocument = (HTMLDocument)webBrowserControl.Document;
var inputElements = htmlDocument.getElementsByTagName("input");
HTMLDocumentEvents_Event documentEvents = (HTMLDocumentEvents_Event) htmlDocument;
documentEvents.onclick += documentEvents_onclick;
DeregisterAll();
foreach (var item in inputElements)
{
DispHTMLInputElement inputElement = item as DispHTMLInputElement;
if (inputElement.type == "text" || inputElement.type == "password" || inputElement.type == "search")
{
HTMLButtonElementEvents_Event htmlButtonEvent = inputElement as HTMLButtonElementEvents_Event;
this.hookedElements.Add(htmlButtonEvent);
htmlButtonEvent.onclick += InputOnClickHandler;
htmlButtonEvent.onblur += InputOnBlurHandler;
}
}
}
我在哪里使用来自 Microsoft.mshtml.dll 的依赖项。 在这里,我将处理程序附加到 DOM 元素的事件,以便能够在 .NET 代码中管理它们(onclick 和 onblur 事件)。
有了那个对象 (HTMLDocument),我几乎可以克服 WPF 控件的所有限制。 当 WebBrowser 控件导航到 PDF 文档时(即响应 MIME 类型为 application/pdf),我的问题就出现了。在这种情况下,我必须假设默认的 Adobe Reader 插件用于显示 PDF(这是我的目标机器的行为方式)。我仍然可以获得与以下内容一起使用的底层 AcroRead ActiveX 的句柄:
private void HookHtmlDocumentOnClick()
{
var document = (IAcroAXDocShim)WebBrowserControl.Document;
// Just a test
var obj = document as AcroPDF;
obj.OnMessage += obj_OnMessage;
}
在我向项目添加适当的依赖项后 (Interop.AcroPDFLib.dll)
但此时我不知道是否有办法注册文档上发生的鼠标事件。我要做的就是处理PDF文档的点击事件。
当然,使用下面的,是行不通的。该事件不会冒泡到 .NET 代码级别。
WebBrowserControl.MouseDown += WebBrowserControl_MouseDown;
有人知道是否有办法挂接 IAcroAXDocShim 以处理鼠标单击事件吗? 任何可能的选择?我应该走一条完全不同的道路吗? 与当前情况相比,直接使用 AcroRead ActiveX 是否有一些优势?
我最终采用了不同的方法。每当识别出 PDF 内容时,我决定切换到不同的 UserControl,而不是 WPF WebBrowser。 我知道当你开发一个 Windows 商店应用程序时,一些 类 和 API 用于 PDF 渲染是可用的,所以我应用了一个解决方案以便在我的 WPF 中使用那些 类申请。
首先,我编辑了 csproj 文件,添加了以下标签作为 <PropertyGroup>
的子标签:
<TargetPlatformVersion>8.1</TargetPlatformVersion>
然后一个名为 "Windows 8.1" 的部分应该出现在“添加引用”对话框中 window。我将本节的唯一程序集添加到参考文献中。为了使解决方案有效,可能需要更多的程序集:
- System.IO
- System.Runtime
- System.Runtime.InteropServices
- System.Runtime.InteropServices.Windows运行时
- System.Runtime.Windows运行时间
- System.Threading
- System.Threading.任务
有了这些之后,我编写了一个渲染方法,遍历 PDF 文件的页面并为每个页面创建一个 png 文件。然后我将页面添加到 StackPanel。此时,您可以像通常在任何 UIElement 上那样管理 StackPanel 上的 Click(或 MouseDown)事件。 以下是渲染方法的简要示例:
async private void RenderPages(String pdfUrl)
{
try
{
HttpClient client = new HttpClient();
var responseStream = await client.GetInputStreamAsync(new Uri(pdfUrl));
InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream();
using (responseStream)
{
await responseStream.AsStreamForRead().CopyToAsync(inMemoryStream.AsStreamForWrite());
}
var pdf = await PdfDocument.LoadFromStreamAsync(inMemoryStream);
if (pdf != null && pdf.PageCount > 0)
{
AddPages(pdf);
}
}
catch (Exception e)
{
throw new CustomException("An exception has been thrown while rendering PDF document pages", e);
}
}
async public void AddPages(PdfDocument pdfDocument)
{
PagesStackPanel.Children.Clear();
if (pdfDocument == null || pdfDocument.PageCount == 0)
{
throw new ArgumentException("Invalid PdfDocument object");
}
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
StorageFolder rootFolder = await StorageFolder.GetFolderFromPathAsync(path);
var storageItemToDelete = await rootFolder.TryGetItemAsync(Constants.LOCAL_TEMP_PNG_FOLDER);
if (storageItemToDelete != null)
{
await storageItemToDelete.DeleteAsync();
}
StorageFolder tempFolder = await rootFolder.CreateFolderAsync(Constants.LOCAL_TEMP_PNG_FOLDER, CreationCollisionOption.OpenIfExists);
for (uint i = 0; i < pdfDocument.PageCount; i++)
{
logger.Info("Adding PDF page nr. " + i);
try
{
await AppendPage(pdfDocument.GetPage(i), tempFolder);
}
catch (Exception e)
{
logger.Error("Error while trying to append a page: ", e);
throw new CustomException("Error while trying to append a page: ", e);
}
}
}