Create tabs using WebView2 - Edge - c#

Please provide the code snippet to create a tab instead of open a page in new window when click on the links in the webview2 - Edge in C# windows form.
Followed the below steps.
Drag the webview2 control on C# windows form and update the source property link: https://example.com
https://example.com site opened successfully in the webview2
click on few links in the site - https://example.com and it opens the new window and looking for open this one in the new tab instead of open it in new window
This event webView.CoreWebView2.NewWindowRequested never hit when debug the code. In case if this webView.CoreWebView2.NewWindowRequested event raised then no navigate method is available on webview class and its available on corewebview2 classs and getting the null reference exception if we use this.

For the sake of completeness, I was able to achieve the same thing thanks to David Risney's explanation. Unfortunately it didn't include any code, but I achieved this using 1.0.721-prerelease and Microsoft.WebView2.FixedVersionRuntime.87.0.664.66.x64:
Program.cs:
using System;
using System.Windows.Forms;
namespace TestApp1
{
static class Program
{
public static Microsoft.Web.WebView2.Core.CoreWebView2Environment WebView2Environment;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
Form1.cs:
using System;
using System.Windows.Forms;
namespace TestApp1
{
public partial class Form1 : Form
{
public Microsoft.Web.WebView2.Core.CoreWebView2Deferral Deferral;
public Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs Args;
public Form1()
{
InitializeComponent();
webView21.CoreWebView2InitializationCompleted += webView21_CoreWebView2InitializationCompleted_1;
}
private void CoreWebView2_NewWindowRequested(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
{
Form1 f = new Form1();
f.Args = e;
f.Deferral = e.GetDeferral();
f.Show();
}
private async void Form1_Load(object sender, EventArgs e)
{
if (Program.WebView2Environment == null)
Program.WebView2Environment = Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync(#"C:\Users\Dragon\Downloads\Microsoft.WebView2.FixedVersionRuntime.87.0.664.66.x64", $#"C:\Users\Dragon\Desktop\Test{Guid.NewGuid()}").Result;
await webView21.EnsureCoreWebView2Async(Program.WebView2Environment);
webView21.Source = new Uri("http://www.google.com");
}
private void webView21_CoreWebView2InitializationCompleted_1(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
{
if (!e.IsSuccess) { MessageBox.Show($"{e.InitializationException}"); }
if (Deferral != null)
{
Args.NewWindow = webView21.CoreWebView2;
Deferral.Complete();
}
webView21.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
private void button1_Click(object sender, EventArgs e)
{
webView21.ExecuteScriptAsync($#"window.open('http://www.bing.com', '_blank');");
}
}
}
So the way this works is kinda weird: To spawn a new window, you can do it through JavaScript using ExecuteScriptAsync. In this case I'm opening a new window to bing.com. So, that makes a call to CoreWebView2_NewWindowRequested. For the event to pass and the code to work (otherwise it will freeze) it must go through it all. So, you cannot set the NewWindow property of the CoreWebView2NewWindowRequestedEventArgs inside the event that's currently happening.
The solution is to take the event data (args & deferral) to the new form, show it, and upon load and after the CoreWebView2 property of the control is not null / has initialized, by calling CoreWebView2InitializationCompleted, check if args/deferral are not null and then call the defer as Complete() (this is basically like a JS promise) AND here you can set the NewWindow property as CoreWebView2 has been initialized and therefore it's not null.
Hopefully this will answer your question and future readers. With this code, I was able to make it work.

There's no built in support for tabs in WebView2. However, you can intercept new windows with the NewWindowRequested event and provide your own CoreWebView2 to be that new window, and place that CoreWebView2 how you like in your UI. For instance the new CoreWebView2 could be placed in your UI to look like a new tab. (It sounds like that's what you're doing, but declaring it explicitly here just to ensure that I'm correctly understanding your scenario.)
Regarding the null WebView2.CoreWebView2 property, you can call EnsureCoreWebView2Async and await the returned task or you can set the WebView2.Source property and wait for the CoreWebView2Ready event to dispatch in order for the WebView2.CoreWebView2 property to be filled in. Its null before that.
Additionally, if you need to get the CoreWebView2 to fill in the NewWindowRequestedEventArg's NewWindow property, since the above steps for obtaining the CoreWebView2 from a WebView2 instance are both asynchronous, you'll need to call the NewWindowRequestedEventArg's GetDeferral method before starting async work in the NewWindowRequested event handler and call Complete on the Deferral once your async work is done in the NewWindowRequested event handler.
If you find cases where the WebView2 is opening new windows but the NewWindowRequested event isn't firing please open bugs at https://github.com/MicrosoftEdge/WebViewFeedback. What version of the SDK and the browser are you using with WebView2? There are some now fixed bugs with some scenarios opening new windows not firing the NewWindowRequested event.

Related

Disallowing interaction with background form

On my application's first run, two forms open. The topmost form needs to take priority, and disallow any interaction with the form in the background. I have tried ShowDialog() as referenced here, however this hides the form in the background which I do not wish to do. Is there a method of accomplishing this?
public Form1()
{
InitializeComponent();
if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
void firstrunactions()
{
//open the get-started form and invite user to populate serialisable objects
firstrun frwindow = new firstrun();
frwindow.ShowDialog();
}
When you are using .ShowDialog() the execution of the containing method is paused until you close the newly opened window. So make sure to do everthing else before you call .ShowDialog(). Otherwise your program gets stuck in this method. If you are calling .ShowDialog() before the background window is shown will cause problems.
But using .ShowDialog() here is totally correct and has the right functionality.
Example how not to do it (causes the same behavior like in your problem):
public Form1()
{
InitializeComponent();
//this is the wrong place for showing a child window because it "hides" its parent
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
The magical place where it works:
private void Form1_Shown(object sender, EventArgs e)
{
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
Edit: In your case it is enough moving if(!fileexistst...) into the Form1_Shown()-event.
Try with frwindow.ShowDialog(this);
Or instead "this" pass the other form as parameter.
Also move this part if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
in OnLoad override.

Destructor vs App.Current.Exit vs All other exit handlers

Im having an issue where i need to get my Classes to run a piece of code on exit.
Basically the code writes the Property's and Parameters to an XML file so they can be sent to the programmer to replicate the same settings as the client.
so i have created code like this on each of my classes.
~WorkspaceViewModel()
{
this.Save("Workspace");
}
my problem is that i cannot find a handler that will run before this destructor.
i have tried the following
//App.Current.Exit += new System.Windows.ExitEventHandler(ProgramExit);
//AppDomain.CurrentDomain.ProcessExit += new EventHandler(ProgramExit);
//App.Current.MainWindow.Closed += new EventHandler(ProgramExit);
//App.Current.Windows[0].Closed += new EventHandler(ProgramExit);
//AppDomain.CurrentDomain.DomainUnload += new EventHandler(ProgramExit);
//App.Current.MainWindow.Unloaded += new System.Windows.RoutedEventHandler(ProgramExit);
//System.Windows.Forms.Application.ApplicationExit += new EventHandler(ProgramExit);
//System.Windows.Application.Current.Exit += new System.Windows.ExitEventHandler(ProgramExit);
And saw something online about modifying the App class so i did this.
public partial class App : System.Windows.Application
{
public void OnExit()
{
this.OnExit();
}
public void App_Exit(Object sender, System.Windows.ExitEventArgs Args)
{
//Somelogic here
}
public App()
{
this.Exit += new System.Windows.ExitEventHandler(App_Exit);
}
}
could someone please help.
Are you using Windows Forms? If so, you can use the Closing event of the form. More reading: Form.Closing Event
Definition:
Occurs when the form is closing.
Example:
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if(MessageBox.Show("Do you want to exit?", "Your app title", MessageBoxButtons.YesNo) == DialogResult.No)
{
e.Cancel = true;
// cancel the closing
}
//otherwise the application will exit
}
You don't really need the if-statement, you can just call the Save method in this event and let the application exit afterwards.
You could do it this way (pseudocode)
Init();
window.Show();
Deinit();
You explicitly run initializers on application start and deinitiliazer on exit.
And in WPF it's done by using Application events, overrides or by a trick (setting Page build action for App.xml). In your implementation, you can't have constructor declared, because it's already generated (file App.g.i.cs). You can use application startup event though or simply set events in xaml.
As it turns out. all i needed to do was create a destructor on the App Class like this
public partial class App : Application
{
~App()
{
Administration.Model.DataBaseModel.GlobalCatalogue.ToFile();
}
}
Not really sure this is the best approach tho
Im still open to better ideas however.. thank you all.

Mandatory Event not subscibed

Problem:
I am working on a application where in for some time consuming operation, i am supposed to show a progress bar on a form (WinForm) with a cancel button. So obviously i am using BackgroundWorker thread for it. Below is the code which simulates roughly of what i am trying to achieve.
namespace WindowsFormsApplication1
{
public delegate void SomeDelegateHandler();
public partial class Form1 : Form
{
public event SomeDelegateHandler DoSomeAction;
BackgroundWorker bgWorker;
public Form1()
{
InitializeComponent();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//Some logic code here.
for (int i = 0; i < 100; i++)
{
DoSomeAction();
}
}
private void Form1_Shown(object sender, EventArgs e)
{
if (DoSomeAction != null)
bgWorker.RunWorkerAsync();
else throw new EventNotSubscribedException();//Is this a valid style??
}
}
public class EventNotSubscribedException : ApplicationException
{
//Some custom code here
}
}
My Solution
As per the above code, as soon as the form is displayed to the user (OnShown event) i am starting the backgroundworker thread. This is because, the user need not to initiate any action for this to happen. So onshown does time consuming operation job. But the issue is, as i have shown above, the main time consuming job is executed on other class/component where it is kind of tight bounded too (legacy code: cant refactor). Hence i have subscribed to the event DoSomeAction in that legacy code class which launches this form.
Doubt/Question:
Is it valid to throw exception as shown above? (Please read my justification below).
Justification:
The OnShown event does check for null on event handler object. This is because, to make this form usable, the event has to be subscribed by the subscriber (usage code), then only it shall work. If not, then the form just displays and does noting at all and usage code may not know why it is happenings so. The usage code may assume that subscribing to the event is option just like button click events per say.
Hope my post is clear and understandable.
Thanks & Happy Coding,
Zen :)
Do you mean that you need to throw an exception to the caller of the form? Is it called using showDialog or Show?
BTW, I dont prefer to generate an exception from an event. Rather it would be rather nice to keep it such that it returns from the place with some status set on the Form class.
for instance, I would prefer using
IsEventSubscribed = false
this.Close()
rather than EventNotSubscribedException
BTW, One problem I can see in the code, when the bgWorker_DoWork is called, you should check DoSomeAction to null, because otherwise it might cause NullReferenceException.
Preferably,
Start the run the RunWorkerAsync from Form_shown
Check Delegate to null in DoWork, if it is null, do not call DoSomeAction otherwise call it.
On RunWorkerCompleted of the BackgroundWorker, close the form.
Let me know if you need anything more.
I would suggest making the consuming code construct the BackgroundWorker and pass it to the form's constructor. You can do a null test in the constructor and side-step this whole issue. Alternatively, take the delegate as a constructor argument instead. I mean, how likely is it that the consuming code will need to change the worker delegate mid-operation?
Another approach is to have the dialog monitor a task, instead of having a dialog control a task (as you have here). For example, you could have an interface like this:
public interface IMonitorableTask {
void Start();
event EventHandler<TData> TaskProgress;
}
Where TData is a type that provides any information you might need to update the dialog (such as percent completed).
The downside to this is that each task needs to be a type of its own. This can lead to very ugly, cluttered code. You could mitigate that issue somewhat by creating a helper class, something like:
public class DelegateTask : IMonitorableTask {
private Action<Action<TData>> taskDelegate;
public event EventHandler<TData> TaskProgress;
public DelegateTask(Action<Action<TData>> taskDelegate) {
if (taskDelegate == null)
throw new ArgumentNullException("taskDelegate");
this.taskDelegate = taskDelegate;
}
protected void FireTaskProgress(TData data) {
var handler = TaskProgress;
if (handler != null)
handler(this, data);
}
public void Start() {
taskDelegate(FireTaskProgress);
}
}
Then your task methods become factories:
public IMonitorableTask CreateFooTask(object argument) {
return new DelegateTask(progress => {
DoStuffWith(argument);
progress(new TData(0.5));
DoMoreStuffWith(argument);
progress(new TData(1));
});
}
And now you can easily(*) support, say, a command-line interface. Just attach a different monitor object to the task's event.
(*) Depending on how clean your UI/logic separation already is, of course.

WindowsFormsApplicationBase SplashScreen makes login form ignore keypresses until I click on it - how to debug?

My WinForms app has a simple modal login form, invoked at startup via ShowDialog(). When I run from inside Visual Studio, everything works fine. I can just type in my User ID, hit the Enter key, and get logged in.
But when I run a release build directly, everything looks normal (the login form is active, there's a blinking cursor in the User ID MaskedEditBox), but all keypresses are ignored until I click somewhere on the login form. Very annoying if you are used to doing everything from the keyboard.
I've tried to trace through the event handlers, and to set the focus directly with code, to no avail.
Any suggestions how to debug this (outside of Visual Studio), or failing that - a possible workaround?
Edit
Here's the calling code, in my Main Form:
private void OfeMainForm_Shown(object sender, EventArgs e)
{
OperatorLogon();
}
private void OperatorLogon()
{
// Modal dialogs should be in a "using" block for proper disposal
using (var logonForm = new C21CfrLogOnForm())
{
var dr = logonForm.ShowDialog(this);
if (dr == DialogResult.OK)
SaveOperatorId(logonForm.OperatorId);
else
Application.Exit();
}
}
Edit 2
Didn't think this was relevant, but I'm using Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase for it's splash screen and SingleInstanceController support.
I just commented out the splash screen code, and the problem has disappeared. So that's opened up a whole new line of inquiry...
Edit 3
Changed title to reflect better understanding of the problem
UI focus/redraw/etc. issues usually are rather straightforward to debug by using remote-debugging. I.e. use a second PC (virtual is just enough) where your application runs.
See this MSDN article for details.
Run this in your form code behind. It will tell you which control has focus by giving you the type and name of the control. Run it in form_shown because its the last event in the form load process.
private void Form1_Shown(object sender, EventArgs e)
{
Control control = FindFocusedControl(this);
MessageBox.Show("The focused control " + control.Name + " is of type " + control.GetType());
}
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
If the answer isn't obvious after that, tell us what you get.
I've found a hack...er...I mean...workaround, that fixes the problem. The solution was buried in one of the comments of this answer (thanks, P. Brian Mackey, for providing the link to the related question!)
The workaround is to minimize the main window while the splash screen is displayed, then set it's WindowState back to Normal before showing the login form.
In the code below, see the lines commented with "HACK".
public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
this.IsSingleInstance = true;
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that
/// initializes the splash screen.
/// </summary>
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new SplashScreen();
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that configures
/// the splash screen and main form.
/// </summary>
protected override void OnCreateMainForm()
{
// SplashScreen will close after MainForm_Load completed
this.MainForm = new OfeMainForm();
// HACK - gets around problem with logon form not having focus on startup
// See also OfeMainForm_Shown in OfeMainForm.cs
this.MainForm.WindowState = FormWindowState.Minimized;
}
}
public partial class OfeMainForm : Form
{
// ...
private void OfeMainForm_Shown(object sender, EventArgs e)
{
// HACK - gets around problem with logon form not having focus on startup
// See also OnCreateMainForm in Program.cs
this.WindowState = FormWindowState.Normal;
OperatorLogon();
}
// ...
}
This is working for now, but I'm wondering if I should explicitly open the Logon form from the SingleInstanceController, rather than from my main form.

Wrapping an asynchronous method synchronously in C#

I have a third party library containing a class which performs a function asynchronously. The class inherits from the Form. The function basically performs a calculation based on data stored in a database. Once it has finished, it calls a _Complete event in the calling form.
What I would like to do is call the function synchronously but from a non-windows form application. The problem is, no matter what I do, my application blocks and the _Complete event handler never fires. From a windows form I can simulate the function running synchronously by using a "complete" flag and a "while (!complete) application.doevents", but obviously application.doevents isnt available in a non-windows form application.
Is there something that would stop me using the class's method outside of a windows form application (due to it inheriting from 'Form') ?
Is there some way I can work around this ?
Thanks,
Mike
At a stab it might be worth trying something like the following which uses a WaitHandle to block the current thread rather than spinning and checking a flag.
using System;
using System.Threading;
class Program
{
AutoResetEvent _autoEvent;
static void Main()
{
Program p = new Program();
p.RunWidget();
}
public Program()
{
_autoEvent = new AutoResetEvent(false);
}
public void RunWidget()
{
ThirdParty widget = new ThirdParty();
widget.Completed += new EventHandler(this.Widget_Completed);
widget.DoWork();
// Waits for signal that work is done
_autoEvent.WaitOne();
}
// Assumes that some kind of args are passed by the event
public void Widget_Completed(object sender, EventArgs e)
{
_autoEvent.Set();
}
}
I've got some more information on this problem (I'm working in the same team as mikecamimo).
The problem also occurs in the Windows Forms application, when replicated correctly. In the original OP, the problem didn't occur in the windows form because there was no blocking. When blocking is introduced by using a ResetEvent, the same problem occurs.
This is because the event handler (Widget_Completed) is on the same thread as the method calling Widget.DoWork. The result that AutoResetEvent.WaitOne(); blocks forever because the event handler is never called to Set the event.
In a windows forms environment this can worked around by using Application.DoEvents to poll the message queue and allow the event the be handled. See below.
using System;
using System.Threading;
using System.Windows.Forms;
class Program
{
EventArgs data;
static void Main()
{
Program p = new Program();
p.RunWidget();
}
public Program()
{
_autoEvent = new AutoResetEvent(false);
}
public void RunWidget()
{
ThirdParty widget = new ThirdParty();
widget.Completed += new EventHandler(this.Widget_Completed);
data = null;
widget.DoWork();
while (data == null);
Application.DoEvents();
// do stuff with the results of DoWork that are contained in EventArgs.
}
// Assumes that some kind of args are passed by the event
public void Widget_Completed(object sender, EventArgs e)
{
data = e;
}
}
In a non windows forms application, such as a Windows Service, Application is not available so DoEvents cannot be called.
The problem is one of threading and that widget.DoWork's associated event handler somehow needs to be on another thread. This should prevent AutoResetEvent.WaitOne from blocking indefinitely. I think... :)
Any ideas on how to accomplish this would be fantastic.
AutoResetEvent _autoEvent = new AutoResetEvent(false);
public WebBrowser SyncronNavigation(string url)
{
WebBrowser wb = null;
wb = new WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
wb.ScriptErrorsSuppressed = true;
wb.Navigate(new Uri(url));
while (!_autoEvent.WaitOne(100))
Application.DoEvents();
return wb;
}
void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//throw new NotImplementedException();
_autoEvent.Set();
}
Do you have the source for the component? It sounds like it's relying on the fact it will be called from a WinForms environment (must be a good reason why a library inherits from Form!), but it's hard to know for sure.

Categories