I have a desktop application (forms) with a tab control, I assign a tab and a new custom webrowser control. I open up about 10 of these tabs. Each one visits about 100 - 500 different pages.
The trouble is that if 1 webbrowser control has a problem it shuts down the entire program.
I want to be able to close the offending webbrowser control and open a new one in it's place.
Is there any event that I need to subscribe to catch a crashing or unresponsive webbrowser control ?
I am using C# on windows 7 (Forms), .NET framework v4
===============================================================
UPDATE: 1 - The Tabbed WebBrowser Example
Here is the code I have and How I use the webbrowser control in the most basic way.
Create a new forms project and name it SimpleWeb
Add a new class and name it myWeb.cs, here is the code to use.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security.Policy;
namespace SimpleWeb
{
//inhert all of webbrowser
class myWeb : WebBrowser
{
public myWeb()
{
//no javascript errors
this.ScriptErrorsSuppressed = true;
//Something we want set?
AssignEvents();
}
//keep near the top
private void AssignEvents()
{
//assign WebBrowser events to our custom methods
Navigated += myWeb_Navigated;
DocumentCompleted += myWeb_DocumentCompleted;
Navigating += myWeb_Navigating;
NewWindow += myWeb_NewWindow;
}
#region Events
//List of events:http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser_events%28v=vs.100%29.aspx
//Fired when a new windows opens
private void myWeb_NewWindow(object sender, System.ComponentModel.CancelEventArgs e)
{
//cancel all popup windows
e.Cancel = true;
//beep to let you know canceled new window
Console.Beep(9000, 200);
}
//Fired before page is navigated (not sure if its before or during?)
private void myWeb_Navigating(object sender, System.Windows.Forms.WebBrowserNavigatingEventArgs args)
{
}
//Fired after page is navigated (but not loaded)
private void myWeb_Navigated(object sender, System.Windows.Forms.WebBrowserNavigatedEventArgs args)
{
}
//Fired after page is loaded (Catch 22 - Iframes could be considered a page, can fire more than once. Ads are good examples)
private void myWeb_DocumentCompleted(System.Object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs args)
{
}
#endregion
//Answer supplied by mo. (modified)?
public void OpenUrl(string url)
{
try
{
//this.OpenUrl(url);
this.Navigate(url);
}
catch (Exception ex)
{
MessageBox.Show("Your App Crashed! Because = " + ex.ToString());
//MyApplication.HandleException(ex);
}
}
//Keep near the bottom
private void RemoveEvents()
{
//Remove Events
Navigated -= myWeb_Navigated;
DocumentCompleted -= myWeb_DocumentCompleted;
Navigating -= myWeb_Navigating;
NewWindow -= myWeb_NewWindow;
}
}
}
On Form1 drag a standard tabControl and set the dock to fill, you can go into the tab collection and delete the pre-populated tabs if you like.
Right Click on Form1 and Select "View Code" and replace it with this code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using mshtml;
namespace SimpleWeb
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//Load Up 10 Tabs
for (int i = 0; i <= 10; i++)
{
newTab("Test_" + i, "http://wwww.yahoo.com");
}
}
private void newTab(string Title, String Url)
{
//Create a new Tab
TabPage newTab = new TabPage();
newTab.Name = Title;
newTab.Text = Title;
//create webbrowser Instance
myWeb newWeb = new myWeb();
//Add webbrowser to new tab
newTab.Controls.Add(newWeb);
newWeb.Dock = DockStyle.Fill;
//Add New Tab to Tab Pages
tabControl1.TabPages.Add(newTab);
newWeb.OpenUrl(Url);
}
}
}
Save and Run the project.
Using the answer below by mo. , you can surf the first url with no problem, but what about all the urls the user clicks on? How do we check those?
I prefer not to add events to every single html element on a page, there has to be a way to run the new urls thru the function OpenUrl before it navigates without having an endless loop.
Thanks.
You can use the AppDomain.UnhandledException or Application.ThreadException event.
But handling exceptions this way may end up in having an invalid state in your application.
Could you describe, when these exceptions occur.
Are they a result of executing a method?
Then it is much better to handle the error in the calling method.
void OpenUrl(Url url)
{
try
{
var webBrowser = GetWebBrowser(tabControl.SelectedTab);
webBrowser.OpenUrl(url);
}
catch(SpecificException ex)
{
MyApplication.HandleException(ex);
}
}
in respect to your comment, try this:
try
{
myCustomWebbrowser.Navigate("yahoo.com");
}
catch(Exception ex) //catch specific exceptions here (catch all is a bad practice)
{
MessageBox.Alert(ex.Message); //just for testing.
}
This article should help you out:
private void AssignEvents()
{
Navigated += myWeb_Navigated;
DocumentCompleted += myWeb_DocumentCompleted;
Navigating += myWeb_Navigating;
NewWindow += myWeb_NewWindow;
DownloadComplete += myWen_DownloadComplete;
}
void myWen_DownloadComplete(object sender, EventArgs e)
{
// Check wheter the document is available (it should be)
if (Document != null)
// Subscribe to the Error event
Document.Window.Error += myWeb_Window_Error;
}
void myWeb_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;
}
Related
I have two forms, "FrmRunEntry" and "FrmPartNumEntry". When I enter a value on the FrmRunEntry form, it displays the FrmPartNumEntry from and a combobox. After selecting a value in the combobox, I want to press the ENTER key and carry the selected value from the combobox back to a textbox on the FrmRunEntry form. But I cant get it to work. My combobox and form Keydown events never get triggered. My program just sits on the combobox and does nothing after I press ENTER. I've search the forum extensively and have tried the following solutions without success:
How to pass value from one form into another's combobox
How to get selected items of Combobox from one form to another form in C#
I've also tried a few other solutions that didn't work. I'm a new C# programmer and I admit I don't have a deep understanding of how C# events work. I'm hoping someone can assist in solving this problem and help me understand what I'm doing wrong. Here's the code I'm using:
FORM 1
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace HydroProgram
{
public partial class FrmRunEntry : Form
{
public string selectedPartNumber = "";
public FrmRunEntry()
{
InitializeComponent();
this.ActiveControl = TxtHydro;
TxtHydro.Focus();
}
private void FrmRunEntry_Load(object sender, EventArgs e)
{
//Text Boxes
TxtHydro.CharacterCasing = CharacterCasing.Upper;
if (!string.IsNullOrEmpty(selectedPartNumber))
{
TxtPartNum.Text = selectedPartNumber;
}
}
private void TxtHydro_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
this.Hide();
FrmPartNumEntry f = new FrmPartNumEntry();
f.ShowDialog();
}
}
}
}
FORM 2
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace HydroProgram
{
public partial class FrmPartNumEntry : Form
{
public FrmPartNumEntry()
{
InitializeComponent();
this.ActiveControl = CboPartNum;
}
private void FrmPartNumEntry_Load(object sender, EventArgs e)
{
//Combo Box
CboPartNum.Location = new Point(668, 240);
CboPartNum.Size = new Size(255, 23);
CboPartNum.Focus();
CboPartNum.SelectedIndex = 1;
}
private void CboPartNum_KeyDown(object sender, KeyEventArgs e) <-- NOT BEING TRIGGERED
{
processRequest(e);
}
private void FrmPartNumEntry_KeyDown(object sender, KeyEventArgs e) <-- NOT BEING TRIGGERED
{
processRequest(e);
}
private void processRequest(KeyEventArgs e) <-- NEVER REACHED
{
if (e.KeyCode == Keys.Enter && this.ActiveControl == CboPartNum)
{
this.Hide();
FrmRunEntry f = new FrmRunEntry();
f.selectedPartNumber = Convert.ToString(CboPartNum.SelectedItem);
f.ShowDialog();
}
}
}
}
you can overload the constructor of the form for example
public FrmPartNumEntry()
{
InitializeComponent();
this.ActiveControl = CboPartNum;
}
To This
public public FrmPartNumEntry(int value)
{
InitializeComponent();
this.ActiveControl = CboPartNum;
}
Then store value in a private variable. Then you can use it anyway as you want.
if you want to pass anything simply use an object as a parameter then cast it inside the constructor. for example
public public FrmPartNumEntry(object value)
{
InitializeComponent();
int x = (int) value;
this.ActiveControl = CboPartNum;
}
in your situation where both forms are running.
assume you form1 contains combobox and form2 need the data of it then
Make a static field in form1 and make it public.
use textchanged event in combobox to assign the value to static field
access by form1.staticfieldname from form2.
I am not getting the same result as you describe. When I click on the combo box in the frmPartNumEntry form, then press the “Enter” key… the CboPartNum_KeyDown event fires as expected. So the posted code does not demonstrate what you describe.
In addition and more importantly, your code is “creating” and “hiding” multiple forms that are only displayed once, then hid and never used again. This certainly will cause confusion and problems. One issue is that if you execute the complete code, then try to “exit” the program by clicking the red x in the top right of the form… you may notice that execution does NOT stop. The code continues to run and this is because the hidden forms are technically still executing as they were never closed.
To show this… let us look at the code in the first form FrmRunEntry TxtHydro_KeyDown event. If the user presses the “Enter” key, then the currently displayed FrmRunEntry is hidden…
this.Hide();
Then a new second form FrmPartNumEntry is created and it is displayed using a ShowDialog();… This may appear correct, however there is one big problem with this code… when the user closes the second form and code execution returns to THIS code… the current FrmRunEntry is never UN-HIDDEN.
The code in second form’s FrmPartNumEntry processRequest method follows this same odd pattern, hide the current form, then it creates a NEW FrmRunEntry? ... This is NOT going to be the same FrmRunEntry form which was previously hidden. In reality NOW you have TWO (2) FrmRunEntry forms and one is hidden. If you continue to execute this code, each time the steps are duplicated… 2 NEW forms are created and hidden.
I am confident this is NOT what you want.
The posted duplicate link shows several ways to pass data between forms. In your particular case, one possible solution is the make the ComboBox on the second form a “public” combo box. Then make the following changes to the processRequest method. First, we do not want to “hide” this form… we want to CLOSE it. In addition we do NOT want to create a new FrmRunEntry form, we want to UN-hide the one that was previously hidden. So, in the processRequest all we need is to close the form. Something like…
private void processRequest(KeyEventArgs e) {
if (e.KeyCode == Keys.Enter && this.ActiveControl == CboPartNum) {
this.Close();
}
}
When this form closes, then execution will go back to the first form. Specifically the TxtHydro_KeyDown event. Execution will begin right after the…
f.ShowDialog();
line of code. So it is here where want to UN-Hide/Show the previously hidden FrmRunEntry form. In addition, we want to get the selected ComboBox value from the now closed FrmPartNumEntry. Fortunately, even though the second form is “technically” closed and not displayed… we should still be able to access the “public” variables on the form. As noted previously, if the ComboBox has it's Modifiers property set to public then the following code should work and give the first form access to the second forms ComboBox. Something like…
private void TxtHydro_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter) {
this.Hide();
FrmPartNumEntry f = new FrmPartNumEntry();
f.ShowDialog();
this.Show();
TxtHydro.Text = f.CboPartNum.Text;
}
}
I hope this makes sense and helps.
Thanks to JohnG, the solution to my problem was amazingly simple. All I needed was a single line of code in the form constructor to subscribe the combobox to the event handler like this:
public FrmPartNumEntry()
{
InitializeComponent();
this.ActiveControl = CboPartNum;
CboPartNum.KeyDown += new KeyEventHandler(CboPartNum_KeyDown); <-- SOLUTION
}
One solution would be to:
on form1 define the variable as static:
public partial class Form1 : Form
{
public static string name = "xxx";
}
then on form2 access the variable from form1:
public partial class Form2 : Form
{
lblName.Text = Form1.name;
}
another option is to use an event. The following is a generic code sample which has Main form with a ComboBox and button. Press the button to show the child form and when the ComboBox selection changes an event is fired for any listeners which Form1 does listen for in a method which first checks to see if the item is in the ComboBox in Form1, if not exists, add it and select it.
Child form (Form2)
using System;
using System.Linq;
using System.Windows.Forms;
using static System.Globalization.DateTimeFormatInfo;
namespace CodeSample
{
public partial class Form2 : Form
{
public delegate void SelectionChange(string sender);
public event SelectionChange OnSelectionChange;
public Form2()
{
InitializeComponent();
MonthsComboBox.DataSource =
Enumerable.Range(1, 12).Select((index) =>
CurrentInfo.GetMonthName(index)).ToList();
MonthsComboBox.SelectedIndexChanged +=
MonthsComboBoxOnSelectedIndexChanged;
}
private void MonthsComboBoxOnSelectedIndexChanged(object sender, EventArgs e)
{
OnSelectionChange?.Invoke(MonthsComboBox.Text);
}
}
}
Form1 main form
Note I use ShowDialog, Show may be used instead.
using System;
using System.Windows.Forms;
namespace CodeSample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void ShowChildForm_Click(object sender, EventArgs e)
{
var f = new Form2();
f.OnSelectionChange += OnSelectionChange;
try
{
f.ShowDialog();
}
finally
{
f.OnSelectionChange -= OnOnSelectionChange;
f.Dispose();
}
}
private void OnSelectionChange(string sender)
{
if (MonthsComboBox.FindString(sender) != -1) return;
MonthsComboBox.Items.Add(sender);
MonthsComboBox.SelectedIndex = MonthsComboBox.Items.Count - 1;
}
}
}
I have win-foam application having several buttons. When i run this application, i am able to press these buttons using mouse click also able to press these buttons using keyboard keys. I don't want to press these buttons using keyboard keys.
I also want to stop focus on buttons when i click "Tab" or arrows keys"^,v,<,>".
Regards
Instead of the standard Button, use the following when you need that behavior
public class NonSelectableButton : Button
{
public NonSelectableButton()
{
SetStyle(ControlStyles.Selectable, false);
}
}
EDIT:
Here is a little test proving that it's working
using System;
using System.Linq;
using System.Windows.Forms;
namespace Samples
{
public class NonSelectableButton : Button
{
public NonSelectableButton()
{
SetStyle(ControlStyles.Selectable, false);
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
Control[] controls = { new TextBox(), new TextBox(), };
Button[] buttons = { new NonSelectableButton { Text = "Prev" }, new NonSelectableButton { Text = "Next" }, };
foreach (var button in buttons)
button.Click += (sender, e) => MessageBox.Show("Button " + ((Button)sender).Text + " clicked!");
int y = 0;
foreach (var item in controls.Concat(buttons))
{
item.Left = 8;
item.Top = y += 8;
form.Controls.Add(item);
y = item.Bottom;
}
Application.Run(form);
}
}
}
EDIT2:: To apply the solution, you need to do the following:
(1) Add a new code file to your project, call it NonSelectableButton.cs with the following content
using System;
using System.Linq;
using System.Windows.Forms;
namespace YourNamespace
{
public class NonSelectableButton : Button
{
public NonSelectableButton()
{
SetStyle(ControlStyles.Selectable, false);
}
}
}
(2) Compile the project
(3) Now the new button will appear in the control toolbox (at the top) and you can drag it on a form instead of a standard button.
The best I could work out for stopping all buttons responding to the space-bar is this:
NB: It is a bit of a hack:
Set your form's KeyPreview property to true.
Then add this code to the form's KeyPress event:
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
if (this.ActiveControl is Button
&& e.KeyChar == (char)Keys.Space)
{
var button = this.ActiveControl;
button.Enabled = false;
Application.DoEvents();
button.Enabled = true;
button.Focus();
}
}
And to stop a control from getting focus when tabbing, simply set the button's TabStop property to false.
Looks like you have troubles applying my previous answer. Here is another way using the same idea:
Add a new code file to your project and put the following code inside (make sure to replace YourNamespace with yours!)
using System;
using System.Reflection;
using System.Windows.Forms;
namespace YourNamespace
{
public static class Utils
{
private static readonly Action<Control, ControlStyles, bool> SetStyle =
(Action<Control, ControlStyles, bool>)Delegate.CreateDelegate(typeof(Action<Control, ControlStyles, bool>),
typeof(Control).GetMethod("SetStyle", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(ControlStyles), typeof(bool) }, null));
public static void DisableSelect(this Control target)
{
SetStyle(target, ControlStyles.Selectable, false);
}
}
}
Then use it inside your Form Load event for each button you need to have that behavior.
For instance, if your form contains 2 buttons called btnPrev and btnNext, include the following lines in your form load event
btnPrev.DisableSelect();
btnNext.DisableSelect();
I'm trying to develop a simple app for Windows Phone 8, and there are many requirements for the use of the Back Button. As I don't want the Back Button to simply GoBack in back stack, I'd like to pop up a message box to warn the user that this action will bring him back to main menu.
Problem is, this page has to be reloaded some times, and the following code stop working properly after 1 reload. The messagebox opens multiple times. And the more times I reload, the more MessageBox appears.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using BackButtonTests.Resources;
namespace BackButtonTests
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
NavigationService.Navigating += NavigationService_Navigating;
}
void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back)
{
e.Cancel = true;
MessageBox.Show("Quit");
}
}
private void Restart_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/MainPage.xaml?reload=" + DateTime.Now.ToString(), UriKind.RelativeOrAbsolute));
//Use this fake reload query with unique value as a way to "deceive" the system, as windowsphone does not support NavigationService.Reload, and using simply the Uri of the same page will not properly load everything
}
private void Quit_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Quit");
}
}
}
This is just a test code I wrote, that shows exactly the problem I'm experiencing in my actual project. Of course there are 2 buttons written in xaml.
And the code won't work until you first reload the page, as it's not NavigatedTo when it's the front page (not a problem in my actual project).
Any clues of what I'm doing wrong?
NOTE: I'm not interested in changing the event handler (to OnBackKeyPress, for instance). I'm interested in understanding what's going on with the handler I chose (NavigationService.Navigating, NavigationMode.Back). Thanks
Updated following additional information that clarifies the questing
Changing your navigating event handler to will mean the event isn't fired on every page in the stack
void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
{
NavigationService.Navigating -= NavigationService_Navigating;
if (e.NavigationMode == NavigationMode.Back)
{
e.Cancel = true;
MessageBox.Show("Quit");
}
}
No longer neccessary
Override OnBackKeypress instead of navigating
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
var DoYouWantToQuit = MessageBox.Show("Are you sure you want to Quit", "Quit", MessageBoxButtons.OkCancel);
if (DoYouWantToQuit != MessageBoxButton.Ok)
{
e.Cancel = true
}
base.OnBackKeyPress(e);
}
I'm using visual studio c# 2010 for the web browser.
WebBrowser 1 navigates to this link:
http://www.costco.com/IOGEAR-Wireless-1080p-HDMI-Transmitter-and-Receiver-3D-Compatible-2x-HDMI-Ports.product.100011675.html
When it reaches the page, it loads and freezes.
I don't think there is something wrong with the web page because chrome, firefox, and the regular IE9 don't freeze at all.
Only the web browser in my c# program freezes when it navigates to this link.
How do I prevent this from freezing? The web page seems to be calling some html data from another site.
I tried adding this code to my program
this.webBrowser1.ScriptErrorsSuppressed = true;
and I also changed the registry values of the web browser so that it will use internet explorer version 9 and so far these two did not work.
this is the code i'm using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
webBrowser1.ScriptErrorsSuppressed = true;
}
private void button1_Click(object sender, EventArgs e)
{
webBrowser1.Navigate("http://www.costco.com/IOGEAR-Wireless-1080p-HDMI-Transmitter-and-Receiver-3D-Compatible-2x-HDMI-Ports.product.100011675.html");
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
}
}
}
The issue is not with the WebBrowser control per se, it is with how that particular website is trying to execute some Javascript that gets stuck in a loop.
Compare and contrast:
1) Change the url to http://google.com. Works fine.
2) Now. Add an event handler for the Navigating event. Something like:
this.webBrowser1.Navigating += new System.Windows.Forms.WebBrowserNavigatingEventHandler(this.webBrowser1_Navigating);
and
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
Console.WriteLine("Navigating to: " + e.Url);
}
You will see that there is a JavaScript function that is constantly trying to redirect the page. Here's what shows up in my console output (goes on indefinitely):
Navigating to: javascript:void((function(){document.open();document.domain='costco.com';document.write('<!DOCTYPE html>');document.close();})())
Navigating to: about:blank
Navigating to: javascript:void((function(){document.open();document.domain='costco.com';document.write('<!DOCTYPE html>');document.close();})())
Navigating to: about:blank
Navigating to: javascript:void((function(){document.open();document.domain='costco.com';document.write('<!DOCTYPE html>');document.close();})())
Navigating to: about:blank
Navigating to: javascript:void((function(){document.open();document.domain='costco.com';document.write('<!DOCTYPE html>');document.close();})())
Which makes the webBrowser control essentially unusable.
EDIT:
Ok, one stab at a workaround (this is probably terrible, but it's frustrating that the weird redirect loop is only happening in the WebBrowser control's browser).
If you block the Navigating event from being called before another Navigating event has completed, it loads the page and does not freeze, and the links appear to work. It goes something like this:
private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
Console.WriteLine("Navigated to: " + e.Url);
isNavigating = false;
webBrowser1.AllowNavigation = true;
}
bool isNavigating = false;
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
if (isNavigating && e.Url.ToString().Contains("javascript:void((function(){document.open();document.domain='costco.com'"))
{
webBrowser1.Stop();
webBrowser1.AllowNavigation = false;
return;
}
isNavigating = true;
Console.WriteLine("Navigating to: " + e.Url);
}
Does anybody know how to click on a link in the WebBrowser control in a WinForms application and then have that link open in a new tab inside my TabControl?
I've been searching for months, seen many tutorials/articles/code samples but it seems as though nobody has ever tried this in C# before.
Any advice/samples are greatly appreciated.
Thank you.
Based on your comments, I understand that you want to trap the "Open In New Window" action for the WebBrowser control, and override the default behavior to open in a new tab inside your application instead.
To accomplish this reliably, you need to get at the NewWindow2 event, which exposes ppDisp (a settable pointer to the WebBrowser control that should open the new window).
All of the other potential hacked together solutions (such as obtaining the last link selected by the user before the OpenWindow event) are not optimal and are bound to fail in corner cases.
Luckily, there is a (relatively) simple way of accomplishing this while still using the System.Windows.Forms.WebBrowser control as a base. All you need to do is extend the WebBrowser and intercept the NewWindow2 event while providing public access to the ActiveX Instance (for passing into ppDisp in new tabs). This has been done before, and Mauricio Rojas has an excellent example with a complete working class "ExtendedWebBrowser":
http://blogs.artinsoft.net/mrojas/archive/2008/09/18/newwindow2-events-in-the-c-webbrowsercontrol.aspx
Once you have the ExtendedWebBrowser class, all you need to do is setup handlers for NewWindow2 and point ppDisp to a browser in a new tab. Here's an example that I put together:
private void InitializeBrowserEvents(ExtendedWebBrowser SourceBrowser)
{
SourceBrowser.NewWindow2 += new EventHandler<NewWindow2EventArgs>(SourceBrowser_NewWindow2);
}
void SourceBrowser_NewWindow2(object sender, NewWindow2EventArgs e)
{
TabPage NewTabPage = new TabPage()
{
Text = "Loading..."
};
ExtendedWebBrowser NewTabBrowser = new ExtendedWebBrowser()
{
Parent = NewTabPage,
Dock = DockStyle.Fill,
Tag = NewTabPage
};
e.PPDisp = NewTabBrowser.Application;
InitializeBrowserEvents(NewTabBrowser);
Tabs.TabPages.Add(NewTabPage);
Tabs.SelectedTab = NewTabPage;
}
private void Form1_Load(object sender, EventArgs e)
{
InitializeBrowserEvents(InitialTabBrowser);
}
(Assumes TabControl named "Tabs" and initial tab containing child control docked ExtendedWebBrowser named "InitialWebBrowser")
Don't forget to unregister the events when the tabs are closed!
private Uri _MyUrl;
System.Windows.Forms.WebBrowser browser = new System.Windows.Forms.WebBrowser();
browser.Navigating += new System.Windows.Forms.WebBrowserNavigatingEventHandler(browser_Navigating);
void browser_Navigating(object sender, System.Windows.Forms.WebBrowserNavigatingEventArgs e)
{
_MyUrl = e.Url;
e.Cancel;
}
The following code works, just follow the first reply for creating the ExtendedWebBrowser class.
I'm using this to open a new tab but it also works to open a new window using your browser and not IE.
Hope it helps.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (current_tab_count == 10) return;
TabPage tabPage = new TabPage("Loading...");
tabpages.Add(tabPage);
tabControl1.TabPages.Add(tabPage);
current_tab_count++;
ExtendedWebBrowser browser = new ExtendedWebBrowser();
InitializeBrowserEvents(browser);
webpages.Add(browser);
browser.Parent = tabPage;
browser.Dock = DockStyle.Fill;
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
browser.DocumentTitleChanged += new EventHandler(Browser_DocumentTitleChanged);
browser.Navigated += Browser_Navigated;
browser.IsWebBrowserContextMenuEnabled = true;
public void InitializeBrowserEvents(ExtendedWebBrowser browser)
{
browser.NewWindow2 += new EventHandler<ExtendedWebBrowser.NewWindow2EventArgs>(Browser_NewWindow2);
}
void Browser_NewWindow2(object sender, ExtendedWebBrowser.NewWindow2EventArgs e)
{
if (current_tab_count == 10) return;
TabPage tabPage = new TabPage("Loading...");
tabpages.Add(tabPage);
tabControl1.TabPages.Add(tabPage);
current_tab_count++;
ExtendedWebBrowser browser = new ExtendedWebBrowser();
webpages.Add(browser);
browser.Parent = tabPage;
browser.Dock = DockStyle.Fill;
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
browser.DocumentTitleChanged += new EventHandler(Browser_DocumentTitleChanged);
browser.Navigated += Browser_Navigated;
tabControl1.SelectedTab = tabPage;
browser.Navigate(textBox.Text);
{
e.PPDisp = browser.Application;
InitializeBrowserEvents(browser);
}
I did a bit of research on this topic and one does not need to do all the COM plumbing that is present in the ExtendedWebBrowser class, as that code is already present in the generated Interop.SHDocVw. As such, I was able to use the more natural construct below to subscribe to the NewWindow2 event. In Visual Studio I had to add a reference to "Microsoft Internet Controls".
using SHDocVw;
...
internal WebBrowserSsoHost(System.Windows.Forms.WebBrowser webBrowser,...)
{
ParameterHelper.ThrowOnNull(webBrowser, "webBrowser");
...
(webBrowser.ActiveXInstance as WebBrowser).NewWindow2 += OnNewWindow2;
}
private void OnNewWindow2(ref object ppDisp, ref bool Cancel)
{
MyTabPage tabPage = TabPageFactory.CreateNewTabPage();
tabPage.SetBrowserAsContent(out ppDisp);
}
Please read http://bit.ly/IDWm5A for more info. This is page #5 in the series, for a complete understanding I had to go back and read pages 3 -> 5.
You simply cancel the new window event and handle the navigation and tab stuff yourself.
Here is a fully working example. This assumes you have a tabcontrol and at least 1 tab page in place.
using System.ComponentModel;
using System.Windows.Forms;
namespace stackoverflow2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.webBrowser1.NewWindow += WebBrowser1_NewWindow;
this.webBrowser1.Navigated += Wb_Navigated;
this.webBrowser1.DocumentText=
"<html>"+
"<head><title>Title</title></head>"+
"<body>"+
"<a href = 'http://www.google.com' target = 'abc' > test </a>"+
"</body>"+
"</html>";
}
private void WebBrowser1_NewWindow(object sender, CancelEventArgs e)
{
e.Cancel = true; //stop normal new window activity
//get the url you were trying to navigate to
var url= webBrowser1.Document.ActiveElement.GetAttribute("href");
//set up the tabs
TabPage tp = new TabPage();
var wb = new WebBrowser();
wb.Navigated += Wb_Navigated;
wb.Size = this.webBrowser1.Size;
tp.Controls.Add(wb);
wb.Navigate(url);
this.tabControl1.Controls.Add(tp);
tabControl1.SelectedTab = tp;
}
private void Wb_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
tabControl1.SelectedTab.Text = (sender as WebBrowser).DocumentTitle;
}
}
}
There is no tabbing in the web browser control, therefor you need to handle the tabs yourself. Add a tab control above the web browser control and create new web browser controls when new tabs are being opened. Catch and cancel when the user opens new windows and open new tabs instead.