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.
Related
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();
}
}
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.
So essentially I'm using the dotNetBrowser for a project that i'm loading into a panel on my main form, and I have a button in a usercontrol for user input so it can interact with the browser. Here's what I have:
public partial class Form1 : Form
{
public BrowserView browserView = new WinFormsBrowserView();
public Form1()
{
InitializeComponent();
this.panel1.Controls.Add((Control)browserView);
browserView.Browser.LoadURL("URL TO BE LOADED");
browserView.Browser.FinishLoadingFrameEvent += delegate (object sender, FinishLoadingEventArgs e)
{
if (e.IsMainFrame)
{
// Do stuff when loaded
} else return;
}
};
}
}
That works fine, in my usercontrol.cs I have:
public void button1_Click(object sender, EventArgs e)
{
BrowserView br = (this.Parent as Form1).Controls["browserView"] as BrowserView;
br.Browser.LoadURL("NEW URL");
}
So that when the button is clicked it can load a new url. But this is throwing a null exception.
Basically I need these two components to be able to pass information on to eachother. The method I've used worked fine for other Form1 controls, but not the browser it seems.
Any advice?
In your case, browserView is a name of the public variable, so you can simply use(this.Parent as Form1).browserView to access it.
Your are adding browserView to Form1.panel1, but trying to get it from (this.Parent as Form1).
You don't need to search for BrowserView when you have explicit reference to it. I suggest giving this reference to the user control. User control having the knowledge of the innards of the hosting form means that information is flowing in the wrong direction.
Names of controls are given to them by IDE, and are empty when controls are created in code.
I'm building a GUI application with C# and gtk#. I've encountered an issue recently and was looking for the best solution to this problem:
I have a modal window that pops up for the user to enter a number. This window is a separate window accessed from my main window and it's set up like this:
public class MainWindow()
{
public NumberEntry numEntry;
Whenever I need numerical input from the user, I call ShowAll() on the public Window property of NumberEntry like:
numEntry.win.ShowAll();
And all of this works fine. Afterwards, to get the value they entered, I call:
int entered = numEntry.valueEntered;
The issue is obviously that code continues executing immediately after the ShowAll() line is finished, and numEntry.valueEntered is always 0. What I'd like to do (and have been trying to do), is to suspend the main thread, and open up the number entry window in a second thread, and join back to the main thread when this is complete. Suspending the main thread seems to prevent GUI changes making the program freeze when I try to open the number entry window. I'd also like to avoid callback methods if at all possible, seeing as how this would get rather complicated after awhile. Any advice? Thanks!
Seems like when GTK window is closed all its child controls are cleared. So to get the result from the custom dialog window you may do the following (I am not gtk guru but its works for me):
1. Create a new dialog window with your controls (I used Xamarin studio). Add result properties, OK and Cancel handlers and override OnDeleteEvent method:
public partial class MyDialog : Gtk.Dialog
{
public string Results {
get;
private set;
}
public MyDialog ()
{
this.Build ();
}
protected override bool OnDeleteEvent (Gdk.Event evnt)
{
Results = entry2.Text; // if user pressed on X button..
return base.OnDeleteEvent (evnt);
}
protected void OnButtonOkClicked (object sender, EventArgs e)
{
Results = entry2.Text;
Destroy ();
}
protected void OnButtonCancelClicked (object sender, EventArgs e)
{
Results = string.Empty;
Destroy ();
}
}
2. In your main window create a dialog object and attach to its Destroyed event your event handler:
protected void OnButtonClicked (object sender, EventArgs e)
{
var dialog = new MyDialog ();
dialog.Destroyed += HandleClose;
}
3. Get the results when dialog is closed:
void HandleClose (object sender, EventArgs e)
{
var dialog = sender as MyDialog;
var textResult = dialog.Results;
}
If you whant you also may specify a dialog result property and etс.
I am working on a program that generates a PDF file. Before the final generation of the file, I want to give the user the option to edit a portion of the file (The title of the graph that is about to be created). I want this to show up in a new form when the user clicks a button to export the PDF. Here is an outline of what I am trying to do...
private void button4_Click(object sender, EventArgs e) // Test PDF Code!
{
Form2 NewPDF = new Form2(chart3.Titles[chart3.Titles.IndexOf("Header")].Text.ToString().Substring(0, chart3.Titles[chart3.Titles.IndexOf("Header")].Text.ToString().Length - 4));
NewPDF.Show();
if (NewPDF.Selected == true)
{
// Create PDF, open save file dialog, etc
}
}
And here is the Form that is being opened by this button click...
public partial class Form2 : Form
{
public bool Selected
{
get;
set;
}
public String GraphName
{
get;
set;
}
public Form2(String FileName)
{
InitializeComponent();
textBox1.Text = FileName;
GraphName = FileName;
Selected = false;
}
public void button1_Click(object sender, EventArgs e)
{
GraphName = textBox1.Text;
this.Selected = true; // After the button is selected I want the code written above to continue execution, however it does not!
}
}
As of now, when I click on the button in Form2, nothing happens, there is something about the communication between the two Forms that I am not understanding!
You should change your Form2.GraphName like below
public String GraphName
{
get { return textBox1.Text }
}
then change your new Form2 creation like below, test it since I haven't run this through VS, but should work :)
private void button4_Click(object sender, EventArgs e) // Test PDF Code!
{
// why on earth were you doing .Text.ToString()? it's already string...
Form2 NewPDF = new Form2(chart3.Titles[chart3.Titles.IndexOf("Header")].Text.Substring(0, chart3.Titles[chart3.Titles.IndexOf("Header")].Text.Length - 4));
// show as a dialog form, so it will wait for it to exit, and set this form as parent
NewPDF.ShowDialog(this);
if (NewPDF.Selected == true)
{
// get the name from the other form
string fileName = NewPDF.GraphName;
// Create PDF, open save file dialog, etc
}
}
The answer to your problem is quite simple.
NewPDF.Show();
Show() does not pause execution of the calling form. Therefore, the check underneath that that verifies the Selected property if true will never execute properly, since that check is reached and verified just as the form starts appearing. ShowDialog() does pause execution and waits for the called form to close.
That aside; I would recommend one of two other ways to communicate between forms;
Use a global variable. Declare a variable holding the graph's name somewhere in a public module. Call the dialog that asks the user to input a name with ShowDialog(), since that pauses execution of the calling form until the called form returns a result.
if(Form.ShowDialog() == DialogResult.OK) {
// Save pdf, using title in global variable
}
Make sure to set the DialogResult in the called form before Close()-ing it.
Pass an instance variable of the calling form to the called name-input form to the constructor and save it. That way, if you expose the graph name property as a public property, you should be able to access it from the called form in the code that closes the form, which is your:
public void button1_Click(object sender, EventArgs e)
{
callingFormInstance.GraphNameProperty = textBox1.Text;
Close();
}
Hope that helps. Cheers!