Winforms WebBrowser control without IE popups not appearing [duplicate] - winforms

I am trying to implement a simple web browser control in one of my apps. This is to help integrate a web app into a toolset i am creating.
The problem is, this web app absolutly loves popup windows....
When a popup is opened, it opens in an IE window which is not a child of the MDI Container form that my main window is part of.
How can i get any and all popups created by clicking links in my WebBrowser to be a child of my MDI container (similar to setting the MDIParent property of a form)?
Thanks in advance.

The web browser control supports the NewWindow event to get notified about a popup window. The Winforms wrapper however does not let you do much with it, you can only cancel the popup. The native COM wrapper permits passing back a new instance of the web browser, that instance will then be used to display the popup.
Taking advantage of this requires some work. For starters, use Project + Add Reference, Browse tab and select c:\windows\system32\shdocvw.dll. That adds a reference to the native COM interface.
Create a form that acts as the popup form. Drop a WebBrowser on it and make its code look similar to this:
public partial class Form2 : Form {
public Form2() {
InitializeComponent();
}
public WebBrowser Browser {
get { return webBrowser1; }
}
}
The Browser property gives access to the browser that will be used to display the web page in the popup window.
Now back to the main form. Drop a WebBrowser on it and make its code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
webBrowser1.Url = new Uri("http://google.com");
}
SHDocVw.WebBrowser nativeBrowser;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
nativeBrowser = (SHDocVw.WebBrowser)webBrowser1.ActiveXInstance;
nativeBrowser.NewWindow2 += nativeBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e) {
nativeBrowser.NewWindow2 -= nativeBrowser_NewWindow2;
base.OnFormClosing(e);
}
void nativeBrowser_NewWindow2(ref object ppDisp, ref bool Cancel) {
var popup = new Form2();
popup.Show(this);
ppDisp = popup.Browser.ActiveXInstance;
}
}
The OnLoad method obtains a reference to the native COM interface, then subscribes an event handler to the NewWindow2 event. I made sure to unsubscribe that event in the FormClosing event handler, not 100% sure if that's necessary. Better safe then sorry.
The NewWindow2 event handler is the crux, note that the first argument allows passing back an untyped reference. That should be the native browser in the popup window. So I create an instance of Form2 and Show() it. Note the argument to Show(), that ensures that the popup is an owned window. Substitute this as necessary for your app, I assume you'd want to create an MDI child window in your case.
Do beware that this event doesn't fire for the window displayed when Javascript uses alert(). The browser doesn't treat that window as an HTML popup and doesn't use a browser window to display it so you cannot intercept or replace it.

I found that the best way to do this was to implement/sink the NewWindow3 event
Add the reference to c:\windows\system32\shdocvw.dll as mentioned in the other answers here.
Add event handler
SHDocVw.WebBrowser wbCOMmain = (SHDocVw.WebBrowser)webbrowser.ActiveXInstance;
wbCOMmain.NewWindow3 += wbCOMmain_NewWindow3;
Event method
void wbCOMmain_NewWindow3(ref object ppDisp,
ref bool Cancel,
uint dwFlags,
string bstrUrlContext,
string bstrUrl)
{
// bstrUrl is the url being navigated to
Cancel = true; // stop the navigation
// Do whatever else you want to do with that URL
// open in the same browser or new browser, etc.
}
Set "Embed Interop Types" for the "Interop.SHDocVw" assembly to false
Set the "local copy" to true.
Source for that help MSDN Post

Refining Hans answer, you can derive the WebBrowser for accessing the COM without adding the reference. It is by using the unpublished Winforms WebBrowser.AttachInterface and DetachInterface methods.
More elaborated here.
Here is the code:
Usage (change your WebBrowser instance to WebBrowserNewWindow2)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.webBrowser1.NewWindow2 += webBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
webBrowser1.NewWindow2 -= webBrowser_NewWindow2;
base.OnFormClosing(e);
}
void webBrowser_NewWindow2(object sender, WebBrowserNewWindow2EventArgs e)
{
var popup = new Form1();
popup.Show(this);
e.PpDisp = popup.Browser.ActiveXInstance;
}
public WebBrowserNewWindow2 Browser
{
get { return webBrowser1; }
}
}
Code:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SHDocVw
{
public delegate void WebBrowserNewWindow2EventHandler(object sender, WebBrowserNewWindow2EventArgs e);
public class WebBrowserNewWindow2EventArgs : EventArgs
{
public WebBrowserNewWindow2EventArgs(object ppDisp, bool cancel)
{
PpDisp = ppDisp;
Cancel = cancel;
}
public object PpDisp { get; set; }
public bool Cancel { get; set; }
}
public class WebBrowserNewWindow2 : WebBrowser
{
private AxHost.ConnectionPointCookie _cookie;
private WebBrowser2EventHelper _helper;
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
base.CreateSink();
_helper = new WebBrowser2EventHelper(this);
_cookie = new AxHost.ConnectionPointCookie(
this.ActiveXInstance, _helper, typeof(DWebBrowserEvents2));
}
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (_cookie != null)
{
_cookie.Disconnect();
_cookie = null;
}
base.DetachSink();
}
public event WebBrowserNewWindow2EventHandler NewWindow2;
private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2
{
private readonly WebBrowserNewWindow2 _parent;
public WebBrowser2EventHelper(WebBrowserNewWindow2 parent)
{
_parent = parent;
}
public void NewWindow2(ref object pDisp, ref bool cancel)
{
WebBrowserNewWindow2EventArgs arg = new WebBrowserNewWindow2EventArgs(pDisp, cancel);
_parent.NewWindow2(this, arg);
if (pDisp != arg.PpDisp)
pDisp = arg.PpDisp;
if (cancel != arg.Cancel)
cancel = arg.Cancel;
}
}
[ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
TypeLibType(TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
[DispId(0xfb)]
void NewWindow2(
[In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
[In, Out] ref bool cancel);
}
}
}

I know the question is very old but I solved it this way: add new reference, in COM choose Microsoft Internet Controls and in the code, before the click that opens a new window add the following:
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)webBrowser1.ActiveXInstance;
axBrowser.NewWindow += axBrowser_NewWindow;
and then add the following method:
void axBrowser_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
Processed = true;
webBrowser1.Navigate(URL);
}

Related

Is there a way to keep additional windows active when showing a modal window?

I'm afraid the answer is probably no...but some background. To draw a custom border on a window where the sizing logic works beyond the visible border (as it does on windows 10) I added layered windows around the edges to capture the messages and then forward them to the central window. This worked great until the form was shown modaly, at which point all the edge windows were automatically disabled. Obviously this is by design...but I'm not sure if there is some way around it. I tried making the edge windows owned by the central window, but that didn't work.
Or maybe there is a better approach entirely.
Here's a sample of the issue:
public partial class Form1 : Form
{
public Form1()
{
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
Form f2 = new Form();
f2.Text = "Non Modal";
f2.Show();
Form f3 = new Form();
f3.Text = "Modal";
f3.ShowDialog(this);
}
}
I think you can fake the modal window, so that it is not modal but disable the caller. I used this in a own project. I did it this way:
//Setup small Interface
public interface IDialog
{
//Our own Event which tell the caller if the Dialog is active/inactive
public event DialogChangedEventArgs DialogChanged;
}
//Setup EventArgs for our own Event
public class DialogChangedEventArgs : EventArgs
{
public bool DialogActive{get;}
public DialogChangedEventArgs(bool dialogActive)
{
DialogActive = dialogActive;
}
}
//Setup the Form which act as Dialog in any other form
public class Form2 : Form, IDialog
{
public event EventHandler<DialogChangedEventArgs> DialogChanged;
//If this Form is shown we fire the Event and tell subscriber we are active
private void Form2_Shown(object sender, EventArgs e)
{
DialogChanged?.Invoke(this, true);
}
//If the user close the Form we telling subscriber we go inactive
private void Form2_Closing(object sender, CancelEventArgs e)
{
DialogChanged?.Invoke(this, false);
}
}
public class Form1 : Form
{
//Setup our Form2 and show it (not modal here!!!)
private void Initialize()
{
Form2 newForm = new Form2();
newForm.DialogChanged += DialogChanged;
newForm.Show();
}
private void Form2_DialogChanged(object sender, DialogChangedEventArgs e)
{
//Now check if Form2 is active or inactive and enable/disable Form1
//So just Form1 will be disabled.
Enable = !e.DialogActive;
}
}
It's really simple. Just use an event to tell your first Form: Hey iam second Form and active. Then you can disable the first Form with while second is active. You have the full control which forms are active or not. Hope this helps.

Closing child window minimizes parent

The following code demonstrates an issue I'm having where closing a child window minimizes the parent window, which I dont want to happen.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
}
}
class CustomMessageBox : Window
{
public CustomMessageBox()
{
Owner = Application.Current.MainWindow;
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new SomeDialog() { Owner = this }.Show();
}
}
Window1 is the main application window.
SomeDialog is a window that pops up on some event within Window1(double clicking window1 in the example) that needs to be modeless.
CustomMessageBox is a window that pops up on some event within "SomeDialog" (double clicking SomeDialog in the example) that needs to be modal.
If you run the application, and then double click Window1's content to bring up SomeDialog, and then you then double click SomeDialog's content to bring up the CustomMessagebox.
Now you close CustomMessagebox. Fine.
Now if you close SomeDialog, Window1 minimizes? Why is it minimizing and how can I stop it?
Edit : It appears the workaround is rather simple, using the technique suggesrted by Viv.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
Owner = null;
}
}
Why is it minimizing and how can I stop it?
Not sure about the "Why" maybe you can report it as a bug and see what they reply with as with a non-modal dialog you do not expect this to happen.
As for a workaround, Try something like this:
public partial class MainWindow : Window {
...
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
base.OnMouseDoubleClick(e);
var x = new SomeDialog { Owner = this };
x.Closing += (sender, args) => {
var window = sender as Window;
if (window != null)
window.Owner = null;
};
x.Show();
}
}
^^ This should prevent the MainWindow(parent) from minimizing when SomeDialog is closed.
My workaround for this interesting problem is to activate the MainWindow once and after that activate the SomeDialog window again.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
Owner.Activate();
Activate();
}
}
A very late answer... I just sat having this same issue and Viv's workaround solved it for me aswell. As for the "Why" part of the answer, i believe it happens when your child window spawns a child window of its own in its lifetime.
For my experience it occured whenever pressing my Save button, which is in a flow which requires a child window to be opened. But pressing the Cancel (or escape) or the windows default quit button did not invoke the issue.
First, as your code stands, I can confirm this strange behaviour. There are two things that I noticed here. The first is that the SomeDialog.Owner is not set, or that it ends up with a null value with this code:
new SomeDialog() { Owner = this }.Show();
Adding this code fixes that problem:
public SomeDialog()
{
Owner = Application.Current.MainWindow;
}
Unfortunately, this doesn't stop the MainWindow from being minimised when the child Window is closed. Then I found that I could stop it from being minimised, but only when calling new SomeDialog().ShowDialog(); instead of new SomeDialog().Show(); However, that makes this Window into a dialog, which is not what you're after I believe.
We had similar problem, but cause was quite simple. Method Close() of Window was called twice. After we had removed second call, all got back to normal.

Devexpress PopupContainerEdit popup always open

Im'm using winform DevExpress library.
Now need to create a control, basing on PopupContainerEdit but this control must have some behaviors like when it's focused, the popup opens and when lost focus the popup closes.
This is the code I'm using but the popup dessapears after getting focus.
public class HelpEdit : PopupContainerEdit {
private PopupContainerControl _container;
private GridControl _gridControl;
private GridView _gridView;
[DefaultValue("")]
[DXCategory("Data")]
[AttributeProvider(typeof(IListSource))]
public object Datasource {
get { return _gridControl.DataSource; }
set { _gridControl.DataSource = value; }
}
public HelpEdit() : base() {
_container = new PopupContainerControl();
this.Properties.TextEditStyle = DevExpress.XtraEditors.Controls.TextEditStyles.Standard;
this._gridControl = new GridControl();
this._gridControl.Dock = DockStyle.Fill;
this._gridView = new GridView(_gridControl);
_container.Controls.Add(_gridControl);
_container.Size = new Size(this.Width, 250);
this.Properties.PopupControl = _container;
this.Properties.PopupControl.Size = new Size(this.Width, 250);
}
protected override void OnGotFocus(EventArgs e) {
base.OnGotFocus(e);
this.ShowPopup();
}
protected override void OnLostFocus(EventArgs e) {
base.OnLostFocus(e);
this.ClosePopup();
}
}
Your popup disappears because it closes by your code as soon as the popup container control(_container) got focus itself. You should not close popup within the OnLostFocus() override because the base.OnLostFocus method of PopupContainerEdit is already contains correct code for closing popup. Or close popup conditionally, using the following code:
protected override void OnLostFocus(EventArgs e) {
if(IsPopupOpen && !EditorContainsFocus)
ClosePopup(PopupCloseMode.Immediate);
base.OnLostFocus(e);
}

Custom Item Template Wizard button click doesn't fire?

I am following this exactly:
http://msdn.microsoft.com/en-us/library/ms185301.aspx
but can't get it to work. The form appears when I try and add my new item, but when I input text and click the button, nothing happens.
For posterity's sake here is my code:
The non-empty methods in the Wizard class which extends IWizard
public void RunStarted(object automationObject,
Dictionary<string, string> replacementsDictionary,
WizardRunKind runKind, object[] customParams)
{
try
{
// Display a form to the user. The form collects
// input for the custom message.
inputForm = new UserInputForm();
inputForm.ShowDialog();
customMessage = inputForm.get_CustomMessage();
// Add custom parameters.
replacementsDictionary.Add("$custommessage$",
customMessage);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
// This method is only called for item templates,
// not for project templates.
public bool ShouldAddProjectItem(string filePath)
{
return true;
}
The user input form code:
public partial class UserInputForm : Form
{
private string customMessage;
public UserInputForm()
{
InitializeComponent();
}
public string get_CustomMessage()
{
return customMessage;
}
private void button1_Click(object sender, EventArgs e)
{
customMessage = textBox1.Text;
this.Dispose();
}
}
And the button is indeed named button 1:
this.button1.Location = new System.Drawing.Point(200, 180);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(100, 40);
this.button1.TabIndex = 0;
this.button1.Text = "Click Me";
this.button1.UseVisualStyleBackColor = true;
So I don't have much experience with Windows Forms (do web apps), but I am following the directions on MSDN and it's pretty clear cut. Any suggestions? Can anyone else get this to work?
Okay I figured it out. I had to add the event handler in the form's constructor manually:
public UserInputForm()
{
InitializeComponent();
button1.Click += button1_Click;
}
Why this isn't in the documentation on MSDN boggles my mind.
If you use the WinForms designer mode to drag your button from the Toolbox, and then double-clicked the button in the designer view, it would have added the event handler and stubbed that Click method for you. Just FYI.

Silverlight DataBinding cross thread issue

I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}

Resources