更好的 WPF-浏览器-控件(IE 包装器)






4.64/5 (9投票s)
本文提供了一个比 WPF 中包含的浏览器控件更好的浏览器控件。
引言
本文提供了一个比WPF自带的浏览器控件更好的控件。它提供了可绑定的属性和命令,支持文本缩放,并提供了一些其他常用的功能。
然而,这并不是一个具有WPF图形功能的完整控件(例如,不支持透明度、渲染转换、延迟绘制)。相反,它仍然使用IE-COM控件及其老式的GDI图形,尽管它托管在WPF中,但仍然存在其所有缺点。不过,它的界面相对来说还不错。
背景
提供的WPF浏览器控件 (System.Windows.WebBrowser
) 根本不是一个真正的WPF控件。它既不提供必要的可绑定属性,也不提供命令或足够的方法。因此,它几乎无法直接使用,也无法在MVVM环境中实现。定制和编程这个控件也相当繁琐,因为该控件的理念是让用户处理所有IE-COM接口的混乱。相比之下,WinForms的WebBrowser
提供了一个很好的类封装了COM接口,使其使用起来非常方便。有一些方法可以解决某些问题,例如使用附加属性实现可绑定的Source。但要实现一个功能完整的控件,则需要一个包装器。
解决方案
本文将展示如何为WPF WebBrowser控件创建一个包装器,使其在大多数情况下易于用作MVVM控件。最重要的是,它创建了一个可绑定的Source依赖项属性,并将一些NavigationCommands
命令绑定在一起。它不提供与WinForms类似的COM接口包装器——即使是微软也认为这过于复杂。当然,这个包装器也不会让嵌入的Internet Explorer成为一个可以旋转、混合和动画内容的本地WPF控件。它仍然会在自己的HWND中运行,因此不会遵守WPF中关于透明度或转换的设置。要实现这一点,建议使用Awesomium。这是一个基于Chromium的浏览器控件,它渲染到位图,并作为本地WPF控件提供。

使用代码
提供的示例可以快速粗略地使用。但它也展示了包装器控件在多大程度上是 WPF 风格的。没有代码隐藏。所有内容都通过窗口内的绑定完成。您需要在 URL 栏输入 URI 后使用 Tab 键进行导航。然而,在实际场景中,建议实现一个 ViewModel,其中包含两个 Url 属性,这些属性分别绑定到CurrentUrl
和BindableSource
。然后根据需要分配和使用它们。这种分离是必要的,因为否则对BindableSource
的更改将始终触发新的导航。
<Grid>
<Button Content="<" Command="NavigationCommands.BrowseBack"
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content="↕" Command="NavigationCommands.Refresh"
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content="►" IsDefault="True"/>
<Button Content="■" Command="NavigationCommands.BrowseStop"
CommandTarget="{Binding ElementName=webBrowser}"/>
<Button Content=">" Command="NavigationCommands.BrowseForward" CommandTarget="{Binding ElementName=webBrowser}"/>
<TextBox Text="{Binding ElementName=webBrowser,Path=BindableSource}" />
<Slider Margin="0,1,0,68" Name="slider1" Orientation="Vertical"
HorizontalAlignment="Right"
Width="25" Minimum="20" Maximum="400"
Value="{Binding ElementName=webBrowser,Path=Zoom}"/>
<local:WpfWebBrowserWrapper Margin="12,42,31,41" x:Name="webBrowser" />
<CheckBox Content="Navigating" IsChecked="{Binding ElementName=webBrowser,Path=IsNavigating}" Height="23" VerticalAlignment="Bottom" IsEnabled="False" />
<TextBlock Height="23" Margin="84,25,67,0" Name="textBlock1" Text="{Binding ElementName=webBrowser,Path=CurrentUrl}" VerticalAlignment="Top" />
<Button Command="NavigationCommands.GoToPage" CommandParameter="http://codeproject.com"
CommandTarget="{Binding ElementName=webBrowser}"
Content="Hme" Height="23" HorizontalAlignment="Left" />
</Grid>
这是基本Web浏览器核心的源代码。它几乎可以直接使用。
技术
开发的控件是一个ContentControl
。因此,它是一个完全成熟的控件,在WPF的逻辑树中扮演着正常的角色。由于不能使用继承,包装是最佳方法。Content Control的内容是原始的、大家熟知的WPF-WebBrowser控件。通过将其 Horizontal 和 Vertical 对齐方式设置为 Stretch,它将始终填充包装器控件。
属性
一旦控制了WebBrowser
控件,就可以为其添加依赖项属性。
BindableSource
属性是实际的URL,可以分配或绑定。读取该值将提供原始字符串。因此,还有另一个属性CurrentUrl
,用于显示已导航的URL。这也可以绑定,但应该是只读的(Mode=OneWayToSource
)。因此,快速绑定支持是存在的。但体验不会很棒。对于实际程序,别无选择,只能使用带有多个属性的适当 ViewModel,并使用命令。
Zoom
属性无缝封装了内置的缩放功能,隐藏了所有COM的混乱。
IsNavigating
属性在true和false之间切换,从而允许绑定到加载指示器或其他控件的可见性。这里滥用了一个复选框。
Commands
为了显式导航,添加了一个NavigationCommands.GoToPage
命令。参数是URL。可以分配给按钮的其他NavigationCommands
包括:BrowseForward
、BrowseBack
、Refresh
和BrowseStop
。同一逻辑树不同分支上的命令绑定在WPF中并不总是有效。实际上很少如此。为了克服这一点,请在这些控件上使用CommandTarget
属性。
<Button Command="NavigationCommands.GoToPage" CommandParameter="http://codeproject.com"
CommandTarget="{Binding ElementName=webBrowser}" Content="Hme" />
命令的好处是它们也可以从控件中“调用”。
COM
为了进行更多的COM编程和自定义,添加了一个ActiveXControl
属性。它暴露了原始控件的私有字段,可以直接操作SVxxxxx.InternetExplorer接口。
错误处理
此嵌入式IE的一个痛苦之处在于它的“吵闹”。默认情况下,脚本错误会弹出错误窗口,干扰使用。一种解决方法是将InternetExplorer的Silent属性设置为true。
ActiveXControl.Silent=true;
这样做的缺点是不再弹出任何对话框。甚至连HTTP身份验证对话框也不会弹出。但在大多数情况下,这是期望的。因此,此包装器控件默认会捕获并吞没脚本错误。它通过订阅DOM事件document.window.onerror
的C#事件处理程序来实现这一点,该事件也由COM接口(IHTMLWindowEvents_onerror
)公开。然而,提供的接口是错误的/不充分的,并且没有定义实际存在的bool
返回值。因此,此包装器使用自己的接口定义。在onerror处理程序中返回true可以防止这些消息弹出。它是不可配置的。相反,它会设置一个LastError
依赖项属性。这可以被订阅,从而可以收集所有错误,或者可以将最后一个错误绑定到控件。
开发WPF控件
本文还从小范围展示了如何使用依赖项属性构建WPF控件,以及如何“从内部”设置它们。自.Net 4.0起,必须使用SetCurrentValue()
。这样,相应DepProps的绑定就不会被破坏。因此,此项目最低要求为.NET 4.0。在.NET 3.5中,必须使用赋值或SetValue()
。在那种情况下,这不会破坏绑定。可以通过使用其他语义来将项目降级。
动态和.Net版本的用法
通过在代码中使用dynamic,可以避免对MSHTML(HTML文档)的引用。它允许在不知道声明的情况下深入到对象中。然而,为了支持dynamic,需要对Microsoft.CSharp进行引用。它包含了所需的反射工具。最后…它的使用将其绑定到C# 4.0。这也可以通过反射来替代实现,从而可能降级到.NET 3.5。
Assemblies
为了使所有使用的符号都可用,需要以下COM和.Net引用:
- SHDocVw / Microsoft Internet Controls: ActiveX控件
- MSHTML / Microsoft HTML Object Library (使用dynamic时是可选的)
- System.Windows.Form – 仅用于漂亮的AxHost。
谢谢
感谢我的公司给我这个任务来详细阐述这一点。任何改进或评论都非常受欢迎,并将作为更新跟进。最困难的部分是在COM接口的森林中砍柴。
历史
首次上传。
2013年8月16日:文字更新。