|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
1: Introduction.NET 2.0 has a new For extending the This article assumes that you have already some knowledge of the browser interfaces 2: The goals, challenges, and solutionsThe goals of this component are:
This section explains the problems associated with the goals and their solutions, in a short form. The next section goes more into the coding details. Handling Script ErrorsThe The script error can be caught by the Solution:
Blocking unwanted pop-upsPop-ups are most of the time not very welcome, or could be inappropriate. To block these things, some additional information is needed.
Using Solution:
Enabling functionality for tabbed browsing or MDI browsingTabbed browsing seems to gain popularity these days. For Internet Explorer 7, this is one of the new features. The challenge in tabbed browsing is that you need to create a window when this is needed by scripts or links. Besides this, window name resolution should work over multiple windows or tabs. (For example: Solution:
Making sure that a window is closed when it is closed by scriptWhen you invoke Solution:
3: Creating the extended WebBrowser componentFrom the last section, we have seen that all of this basically comes down to two things:
Implementing IWebBrowser2The /// <summary>
/// An extended version of the <see cref="WebBrowser"/> control.
/// </summary>
public class ExtendedWebBrowser : System.Windows.Forms.WebBrowser
{
private UnsafeNativeMethods.IWebBrowser2 axIWebBrowser2;
/// <summary>
/// This method supports the .NET Framework
/// infrastructure and is not intended
/// to be used directly from your code.
/// Called by the control when the underlying
/// ActiveX control is created.
/// </summary>
/// <param name="nativeActiveXObject"></param>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void
AttachInterfaces(object nativeActiveXObject)
{
this.axIWebBrowser2 =
(UnsafeNativeMethods.IWebBrowser2)nativeActiveXObject;
base.AttachInterfaces(nativeActiveXObject);
}
/// <summary>
/// This method supports the .NET Framework infrastructure
/// and is not intended to be used directly from your code.
/// Called by the control when the underlying
/// ActiveX control is discarded.
/// </summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachInterfaces()
{
this.axIWebBrowser2 = null;
base.DetachInterfaces();
}
...
}
Next, we can add the /// <summary>
/// Returns the automation object for the web browser
/// </summary>
public object Application
{
get { return axIWebBrowser2.Application; }
}
This property can be used for creating a new window, and redirecting the browser to this new window, when a new window event is fired. Implementing DWebBrowserEvents2The following events are implemented in this sample:
To neatly implement Events are not attached at component construction, but a bit later. There are two methods here that provide this and can be overridden. These are /// <summary>
/// An extended version of the <see cref="WebBrowser"/> control.
/// </summary>
public class ExtendedWebBrowser : System.Windows.Forms.WebBrowser
{
// ... (More code here)
System.Windows.Forms.AxHost.ConnectionPointCookie cookie;
WebBrowserExtendedEvents events;
/// <summary>
/// This method will be called to give
/// you a chance to create your own event sink
/// </summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
// Make sure to call the base class or the normal events won't fire
base.CreateSink();
events = new WebBrowserExtendedEvents(this);
cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance,
events, typeof(UnsafeNativeMethods.DWebBrowserEvents2));
}
/// <summary>
/// Detaches the event sink
/// </summary>
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (null != cookie)
{
cookie.Disconnect();
cookie = null;
}
}
/// <summary>
/// Fires when downloading of a document begins
/// </summary>
public event EventHandler Downloading;
/// <summary>
/// Raises the <see cref="Downloading"/> event
/// </summary>
/// <param name="e">Empty <see cref="EventArgs"/></param>
/// <remarks>
/// You could start an animation
/// or a notification that downloading is starting
/// </remarks>
protected void OnDownloading(EventArgs e)
{
if (Downloading != null)
Downloading(this, e);
}
// ... (More code here)
#region The Implementation of DWebBrowserEvents2 for firing extra events
//This class will capture events from the WebBrowser
class WebBrowserExtendedEvents :
UnsafeNativeMethods.DWebBrowserEvents2
{
public WebBrowserExtendedEvents() { }
ExtendedWebBrowser _Browser;
public WebBrowserExtendedEvents(ExtendedWebBrowser
browser) { _Browser = browser; }
#region DWebBrowserEvents2 Members
// ... (More code here)
public void DownloadBegin()
{
_Browser.OnDownloading(EventArgs.Empty);
}
public void DownloadComplete()
{
_Browser.OnDownloadComplete(EventArgs.Empty);
}
// ... (More code here)
#endregion
}
#endregion
}
4: Using the componentIn the last section, we created a new component. Now, it's time to use the new events and get the maximum functionality out of the browser. For each of the goals, the details are explained here. Handling the script errorsIn the sample application, there is a tool window that simply shows a list of errors that occured, with their details. A single-instance class holds the script errors' information and notifies the subscribers when this information has been changed. For handling these script errors, the public partial class BrowserControl : UserControl
{
public BrowserControl()
{
InitializeComponent();
_browser = new ExtendedWebBrowser();
_browser.Dock = DockStyle.Fill;
// Here's the new DownloadComplete event
_browser.DownloadComplete +=
new EventHandler(_browser_DownloadComplete);
// Some more code here...
this.containerPanel.Controls.Add(_browser);
// Some more code here...
}
void _browser_DownloadComplete(object sender, EventArgs e)
{
// Check wheter the document is available (it should be)
if (this.WebBrowser.Document != null)
// Subscribe to the Error event
this.WebBrowser.Document.Window.Error +=
new HtmlElementErrorEventHandler(Window_Error);
}
void Window_Error(object sender, HtmlElementErrorEventArgs e)
{
// We got a script error, record it
ScriptErrorManager.Instance.RegisterScriptError(e.Url,
e.Description, e.LineNumber);
// Let the browser know we handled this error.
e.Handled = true;
}
// Some more code here
}
Blocking unwanted pop-ups, and enabling functionality for tabbed browsing or MDI browsingHandling pop-ups should be user configurable. For the purpose of demonstration, I've implemented four levels, ranging from blocking nothing to blocking every new window. This code is part of the void _browser_StartNewWindow(object sender,
BrowserExtendedNavigatingEventArgs e)
{
// Here we do the pop-up blocker work
// Note that in Windows 2000 or lower this event will fire, but the
// event arguments will not contain any useful information
// for blocking pop-ups.
// There are 4 filter levels.
// None: Allow all pop-ups
// Low: Allow pop-ups from secure sites
// Medium: Block most pop-ups
// High: Block all pop-ups (Use Ctrl to override)
// We need the instance of the main form,
// because this holds the instance
// to the WindowManager.
MainForm mf = GetMainFormFromControl(sender as Control);
if (mf == null)
return;
// Allow a popup when there is no information
// available or when the Ctrl key is pressed
bool allowPopup = (e.NavigationContext == UrlContext.None)
|| ((e.NavigationContext &
UrlContext.OverrideKey) == UrlContext.OverrideKey);
if (!allowPopup)
{
// Give None, Low & Medium still a chance.
switch (SettingsHelper.Current.FilterLevel)
{
case PopupBlockerFilterLevel.None:
allowPopup = true;
break;
case PopupBlockerFilterLevel.Low:
// See if this is a secure site
if (this.WebBrowser.EncryptionLevel !=
WebBrowserEncryptionLevel.Insecure)
allowPopup = true;
else
// Not a secure site, handle this like the medium filter
goto case PopupBlockerFilterLevel.Medium;
break;
case PopupBlockerFilterLevel.Medium:
// This is the most dificult one.
// Only when the user first inited
// and the new window is user inited
if ((e.NavigationContext & UrlContext.UserFirstInited)
== UrlContext.UserFirstInited &&
(e.NavigationContext & UrlContext.UserInited)
== UrlContext.UserInited)
allowPopup = true;
break;
}
}
if (allowPopup)
{
// Check wheter it's a HTML dialog box.
// If so, allow the popup but do not open a new tab
if (!((e.NavigationContext &
UrlContext.HtmlDialog) == UrlContext.HtmlDialog))
{
ExtendedWebBrowser ewb = mf.WindowManager.New(false);
// The (in)famous application object
e.AutomationObject = ewb.Application;
}
}
else
// Here you could notify the user that the pop-up was blocked
e.Cancel = true;
}
The reason the event is called Using the Quit eventWhen the 5: ConclusionThe The sample application and sourceThe sample application is not a flashy UI, but it does demonstrate everything about this article. The code is commented, and hopefully gives enough information for helping you put your own solution together. AcknowledgementsI would like to thank the following persons that made part of this article possible:
This is my first article on The Code Project. Please excuse me for my English. Thanks for reading! If you can add anything or have suggestions or tips, please post a message below. LicenseThis code is copyrighted by The Wheel Automatisering in The Netherlands. Some rights are reserved. The code in this license may be used for any purpose, just let your users know where it came from, and share derived code under the same license as this one. Don't blame me if something goes wrong. More information can be found here. If you wish to use and/or publish this in commercial closed-source applications, you have my consent. You may use this code under your own license when you do so. History
| ||||||||||||||||||||