This answer shows how to trigger the onbeforeunload event in a WebBrowser control in the following way:
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (!this.formsWebBrowser.IsDisposed)
{
//// Generate SHDocVw.dll: Visual Studio Developer Command Prompt "tlbimp.exe ieframe.dll /out: C:\temp\SHDocVw.dll",
var activeX = this.formsWebBrowser.ActiveXInstance;
var input = Type.Missing;
object leavePage = true;
((SHDocVw.WebBrowser)activeX).ExecWB(
SHDocVw.OLECMDID.OLECMDID_ONUNLOAD,
SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT,
ref input,
ref leavePage);
if (!(bool)leavePage)
{
e.Cancel = true;
return;
}
}
base.OnFormClosing(e);
}
But now trying to move away from IE11 (as used by WebBrowser) to Edge Chromium with the WebView2 control, I can't figure out how to do the same thing in WebView2.
The dialog shows correctly when navigating the WebView2 to another page.
The problem comes when the user closes the application or the window containing the WebView2.
It then just closes without showing any dialog.
That's what the code above does for the WebBrowser control, when closing the application the (on)beforeonload event is triggered in the IE11 browser and a bool is returned. True if the user pressed "Leave" or there isn't an beforeonload event active and false if the user pressed "Stay on the page".
Short of calling ExecuteScriptAsync("onbeforeunload();") (which doesn't work when setting the event with window.addEventListener("beforeunload", function(event) { ... });) how can the same be done in WebView2?
Edit:
The problem is that I don't want to show the dialog always when closing, unless I really have to.
I only want to show it if the page has unsaved changes (and it communicates that in the beforeunload event in JavaScript).
The only way I know how handle that in the C#-code is by triggering the built in onunload event showing the beforeunload dialog in the browser.
That is exactly what the ActiveXInstance.ExecWB(OLECMDID_ONUNLOAD) does for the WebBrowser control and IE11.
It may simply not be possible to trigger that event in WebView2/Chromium in the same way? That's really what I'm asking.
I've tried calling JavaScripts in the FormClosing event, but the application just closes w/o waiting for the response.
I guess the only other option is to remove the x-close button and use a custom close button that can do the needed checks and then close the application.
This is working for me. You can combine it with a form closing or something. Don't forget to properly detach events / dispose where proper. This is just a sample.
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
DoWork();
}
private async Task DoWork()
{
await webView21.EnsureCoreWebView2Async();
webView21.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
webView21.CoreWebView2.ScriptDialogOpening += CoreWebView2_ScriptDialogOpening;
await webView21.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.addEventListener('beforeunload', function(e) { return event.returnValue = 'prompt';});");
webView21.Source = new Uri("https://www.google.com");
}
private void CoreWebView2_ScriptDialogOpening(object sender, Microsoft.Web.WebView2.Core.CoreWebView2ScriptDialogOpeningEventArgs e)
{
if (MessageBox.Show("do you want to leave", "Leave?", MessageBoxButtons.OKCancel) == DialogResult.OK)
{
e.Accept();
}
else
{
e.GetDeferral();
}
}
Related
Hey there StackOverflow community!
So I've been working on an application that checks if the user has entered valid credentials in a Login() form, then it switches over to an Intro_Sequence() form (where a .mp4 file is played in fullscreen mode) as a sort of aesthetic addition to the app. So far so good, no problems whatsoever.
The problem comes right after the Intro ends, where supposedly the application should switch over to a third form, called Main().
I have implemented a check whenever Windows Media Player (aka axWMPLib) changes its PlayState to see whether it has finished the playback.
If it has, then the Hide() event is called to conceal the current Form's window, then main.ShowDialog() should open the third form.
Afterwards, I call the Close() event to close the previous Form's window entirely.
Here is the code so far:
public partial class Intro_Sequence : Form
{
public static string Username;
public Intro_Sequence(string username)
{
InitializeComponent();
Username = username;
FormBorderStyle = FormBorderStyle.None;
Bounds = Screen.PrimaryScreen.Bounds;
TopMost = true;
intro.uiMode = "none";
intro.URL = AppDomain.CurrentDomain.BaseDirectory + "\\Intro.mp4";
intro.enableContextMenu = false;
DisableMouseClicks();
}
private void DisableMouseClicks()
{
if (this.Filter == null)
{
this.Filter = new MouseClickMessageFilter();
Application.AddMessageFilter(this.Filter);
}
}
private MouseClickMessageFilter Filter;
private const int LButtonDown = 0x201;
private const int LButtonUp = 0x202;
private const int LButtonDoubleClick = 0x203;
public class MouseClickMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref System.Windows.Forms.Message m)
{
switch (m.Msg)
{
case LButtonDown:
case LButtonUp:
case LButtonDoubleClick:
return true;
}
return false;
}
}
private void Intro_Sequence_Load(object sender, EventArgs e)
{
}
private void intro_PlayStateChange(object sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e)
{
if(intro.playState == WMPLib.WMPPlayState.wmppsMediaEnded)
{
Main main = new Main(Username);
this.Hide();
main.ShowDialog();
this.Close();
}
}
}
As you can see I have also added a filter to block clicks during playback, so as not to allow the user to pause it.
However, when I execute this code, it works perfectly fine until it finishes the video and then closes abruptly.
I tried putting breakpoints and everything seems to be fine.
It does call everything I tell it to call, yet the form doesn't even appear.
I have also tried several other alternatives, like not closing the Form at all, calling Show() instead of ShowDialog() and even not Hiding it at all.
It is as if it either freezes there or closes instantly without any sign of the Main form showing.
I also tried calling the Main() form from the Login() and it works perfectly from there.
I really don't know what is going on.
Any help would be appreciated.
How about something like this?
There are three forms. There's a Login form (in this case, it's just an empty form - you close it by clicking on the red X). It is popped up modally from within the Main form (while the main form is hidden).
There's a Splash screen on which your video is to play. I fake out the video by using await Task.Delay(4000); to get a pause. After the 4 second delay, I raise an event (equivalent to your media player event). What I do is show this modally from the main form. I put the event handler in this form; when the event is raised, I close the splash screen modal. The entire (non-designer) code for that form looks like (and, since there are no controls on this form, the designer code is pretty lean):
public partial class SplashScreen : Form
{
public event EventHandler SplashFinished;
public SplashScreen()
{
InitializeComponent();
this.SplashFinished += SplashScreen_SplashFinished;
}
private async void SplashScreen_Load(object sender, EventArgs e)
{
await Task.Delay(4000);
SplashFinished?.Invoke(this, new EventArgs());
}
private void SplashScreen_SplashFinished(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
}
Then there's the Main form. It gets fired up in the normal way from Program.cs:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
The only thing that I added to that form (from the out-of-the-box code) is:
private void Form1_Load(object sender, EventArgs e)
{
this.Hide();
var login = new LoginForm();
//should really check this, but for now
login.ShowDialog(this);
var splash = new SplashScreen();
splash.ShowDialog(this);
this.Show();
}
So, when the app starts, the user is shown the login form (the main form is hidden). He does what is needed to do (and the result is checked in the main form's Form1_Load handler.
If everything is cool, a new SplashScreen form is created and shown modally. When it pops up, the video starts (in this case, the video is simply an asynchronous timer). When the video ends, the SplashScreen handles the finished event, and uses it to close itself.
Once control returns to the main form, it displays itself.
I have an application in which I need to make sure the form opened by click on a button on a user control using ShowDialog(), will be closed and disposed when I dispose the user control.
I'm calling userControl.Dispose() in my main form through a timer.
Is there a way I can do that ?
Thanks...
Here is more details about the flow of the forms:
The MainForm of my application is creating a UserControl which has a Button. Than when the user clicks on the button of the user control, it shows a model form using ShowDialog.
Meanwhile, and after a few minutes, a timer in the main form replaces the existing user control with another instance of the user control. The main form calls the Dispose method of the previous user control, and the shows the new on.
But the problem is the modal dialog is still open on screen, blocking the main form. I want to close it, and the code placed after the ShowDialog method should not be executed.
Short answer
You can subscribe Disposed event of your UserControl and close the form which it shows. Regarding to the comments under the question, it looks like you have a UserControl containing a Button and in Click event of the button, you show a Form using ShowDialog().
To close and dispose the form, you need to subscribe Disposed event of your UserControl before showing the form as dialog.
More details
If you want to decide to run some logic depending to the dialog result of the form, you can check the dialog result and if it's OK, run the custom logic which you need.
To enhance the flow a bit, you can define some events and properties in your user control and handle them in the main form:
OKSelected event, and you can raise it immediately after closing the dialog if the dialog result is OK. It will let you to handle this event in the main form, for example to stop the timer if the user clicked OK in dialog.
ProcessingFinished, and you can raise it after you finished some processing after closing the dialog when the dialog result is OK. You can handle this in main form, for example to start the timer again.
You can define some properties in case you want to communicate some values with the main form.
Here is an example of the code in main form:
MyUserControl uc = null;
private void timer1_Tick(object sender, EventArgs e)
{
if (!(uc == null || uc.IsDisposed || uc.Disposing))
{
this.Controls.Remove(uc);
uc.Dispose();
}
uc = new MyUserControl();
this.Controls.Add(uc);
uc.OKSelected += (obj, args) => { timer1.Stop(); };
uc.ProcessingFinished += (obj, args) =>
{
MessageBox.Show(uc.Info);
timer1.Start();
};
}
And here is an example of the user control:
public partial class MyUserControl : UserControl
{
public MyUserControl() { InitializeComponent(); }
public EventHandler OKSelected;
public EventHandler ProcessingFinished;
public string Info { get; private set; }
private void button1_Click(object sender, EventArgs e)
{
using (var f = new Form()) {
var button = new Button() { Text = "OK" };
f.Controls.Add(button);
button.DialogResult = DialogResult.OK;
this.Disposed += (obj, args) => {
if (!(f.IsDisposed || f.Disposing)) {
f.Close(); f.Dispose();
}
};
if (f.ShowDialog() == DialogResult.OK) {
//If you need, raise the OKSelected event
//So you can handle it in the main form, for example to stop timer
OKSelected?.Invoke(this, EventArgs.Empty);
//
//Do whatever you need to do after user closed the dialog by OK
//
//If you need, raise the ProcessingFinished event
//So you can handle it in the main form, for example to start timer
//You can also set some properties to share information with main form
Info = "something";
ProcessingFinished?.Invoke(this, EventArgs.Empty);
}
}
}
}
Can you modify the forms that you want to close automatically? If so, try adding the following to each form:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
if (this.Owner != null)
this.Owner.HandleDestroyed += onOwnerHandleDestroyed;
}
void onOwnerHandleDestroyed(object sender, EventArgs e)
{
this.Close();
}
NOTE: You are already using Dispose() to close the main form, so this should work. If, however, you used Close() to close the main form, then it wouldn't work because Close() doesn't close a form if it is the parent of any modal dialog.
I'm using Visual Studio Community in C# (.Net 4.5).
I have a simple form, with one button and one webBrowser control.
I want to check if "tremblay jean" has a trademark registered in his name in Canada (I know he has two).
So when I click my button I load the trademarks search page in my webBrowser control, I wait for it to be complete, then I insert his name in their textbox and click their button.
If I pause the program using a MessageBox.Show after loading the page, it works, there's two documents found.
But if I don't pause the program using a MessageBox it doesn't work. It gives me 500 results, unrelated to "tremblay jean".
So the line of code waiting for the ReadyState to be Complete doen't seem to work.
Does anyone know why?
private void button1_Click(object sender, EventArgs e)
{
string website = "http://www.ic.gc.ca/app/opic-cipo/trdmrks/srch/home?lang=eng";
webBrowser1.Navigate(website);
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete) Application.DoEvents();
MessageBox.Show(webBrowser1.ReadyState.ToString()); // to pause the program
webBrowser1.Document.GetElementById("search-crit-1").SetAttribute("value", "tremblay jean");
HtmlElementCollection elc = webBrowser1.Document.GetElementsByTagName("button");
foreach (HtmlElement el in elc)
{
if (el.GetAttribute("type").Equals("submit"))
{
if (el.InnerText == " Search ")
{
el.InvokeMember("Click"); //comment this line to see if textbox is filled
break;
}
}
}
}
The first thing to do, when you're using a WebBrowser control, is to initialize it with this html string:
<meta http-equiv='x-ua-compatible' content='IE=edge,chrome=1'>
This allows to set the compatibility mode of the control's underlying activex (Internet Explorer) to the most recent locally available version.
With webBrowser1.ScriptErrorsSuppressed = true;, the scripting error popup is disabled.It's a just in case measure.
Then, subscribe the DocumentCompleted event, that will raise when the page has been loaded. As already noted in the comments, this event might be raised more than once, because of the interaction of Scripting and IFrame.
The WebBrowser.ReadyState is used to verify that page is indeed completed.
It's true that, sometimes, the inner scripting can cause some trouble
here, but since this is not the case, I'll leave it as a side
note.
One other thing you'll notice is that the DocumentCompleted event is unsubscribed after the WebForm button is clicked. This is done to avoid further notifications from the WebBrowser, as the needed action is already been performed and no further action is required on other pages.
So, the event will be active only when you'll be requesting new results to the server (e.g. clicking your UI Search button).
Here, the private string SearchName; is a placeholder for a procedure the defines the new search criteria.
private string SearchName;
private void button1_Click(object sender, EventArgs e)
{
SearchName = "tremblay jean";
webBrowser1.ScriptErrorsSuppressed = true;
webBrowser1.Navigate("");
webBrowser1.Document.Write("<!DOCTYPE html><head><meta http-equiv='x-ua-compatible' content='IE=edge,chrome=1'></head>");
webBrowser1.Navigate("http://www.ic.gc.ca/app/opic-cipo/trdmrks/srch/home?lang=eng");
webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(this.WBDocCompleted);
}
protected void WBDocCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser browser = ((WebBrowser)sender);
if (browser.ReadyState == WebBrowserReadyState.Complete)
{
if (browser.Document != null)
{
browser.Document.GetElementById("search-crit-1").SetAttribute("value", this.SearchName);
foreach (HtmlElement button in browser.Document.GetElementsByTagName("button"))
{
if (button.GetAttribute("type") == "submit" && button.Name == "")
{
browser.DocumentCompleted -= this.WBDocCompleted;
button.InvokeMember("click");
break;
}
}
}
}
}
I need to be alerted before my entire form loses focus. The Deactivate event only triggers after it loses focus. LostFocus and Leave are only for controls.
I have also tried overriding WndProc but this only triggers after the message has been processed.
overriding PreProcessMessage only can be used for keyboard stuff, not form deactivation.
Dodgy Method
Even though this is a quick and hacky way of doing it, changing Input Language is unnatural to start with..
private void Form1_Deactivate(object sender, EventArgs e)
{
((Form)sender).Activate();
System.Diagnostics.Debug.WriteLine(this.ActiveControl.Name);
//Change Input Language here..
//Alt TAB to set focus to the application selected 5 milliseconds ago
SendKeys.SendWait("%{TAB");
}
Correct and orthadox method
How to monitor focus changes? and C#: Detecting which application has focus
Its using the Automation framework, Add references to UIAutomationClient and UIAutomationTypes and use Automation.AddAutomationFocusChangedEventHandler, e.g.:
public class FocusMonitor
{
public FocusMonitor()
{
AutomationFocusChangedEventHandler focusHandler = OnFocusChanged;
Automation.AddAutomationFocusChangedEventHandler(focusHandler);
}
private void OnFocusChanged(object sender, AutomationFocusChangedEventArgs e)
{
AutomationElement focusedElement = sender as AutomationElement;
if (focusedElement != null)
{
int processId = focusedElement.Current.ProcessId;
using (Process process = Process.GetProcessById(processId))
{
Debug.WriteLine(process.ProcessName);
}
}
}
}
Got it, this hack works perfectly.
private void MyForm_Deactivate(object sender, EventArgs e)
{
Thread.Sleep(200); //delay to allow external tab time to open
Form f1 = new Form(); //create a new form that will take focus, switch input, then terminate itself
f1.Shown += new EventHandler((s, e1) => { f1.Activate(); InputLanguage.CurrentInputLanguage = InputLanguage.DefaultInputLanguage; f1.Close(); });
f1.Show();
}
EDIT: upon further testing I have found this to be equally unreliable. It doesn't seem like there is a good way to do this at all.
For now I am tracking the mouse and keyboard to detect when the user is about to deactivate it. Obviously a mouse and keyboard hook is a horrible solution but its the only reliable solution so far.
Okay, this one will be a fun one. I am building an authentication flow in my application. My application will open a modal window with a webbrowser element and browse to the auth URL. Then it wil monitor the URL changes of the webbrowser element for a specific string. When it finds the string, it does it's work to retrieve the access code, closes the window and then returns the code to the parent. My code is as follows:
The modal window:
public partial class Browser : Window
{
private string code = "";
private Uri navi;
public TwitchBrowser(Uri url)
{
InitializeComponent();
this.navi = url;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
webBrowser.Navigate(this.navi);
webBrowser.Navigating += webBrowser_Navigating;
}
void webBrowser_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (e.Uri.ToString().Contains("?code="))
{
this.code = e.Uri.ToString().Split('?')[1].Split('&')[0].Replace("code=", "");
this.DialogResult = true;
}
}
public string result
{
get { return code; }
}
}
The call from the parent:
string url = ...
Browser browser = new Browser(new Uri(url));
browser.Owner = parent;
if (browser.ShowDialog() == true)
{
password.Password = browser.result;
...
}
And of course, the error I get:
DialogResult can be set only after Window is created and shown as dialog.
The interesting thing is, the app WORKS! It gets the code and stores it in the password field as it's supposed to. So whats the point of the error? I mean, I know I can suppress it with a Try-Catch; but I'm afraid its the root of a larger problem.
You original code had a race condition in there. You were navigating in the creation of browser. The navigation complete could be fired before you called ShowDialog().
Instead stash away the url in a private variable and setup the WebBrowser in your Loaded event.
Now that you have fixed that, I am guessing that you want the Nagivated event instead of the Navigating event to check the returned URI.
It is still interesting that the Navigating event gives that error. I was able to reproduce it with a button click on the form, so I know the form is completely shown at that point.