I'm trying to use WebView2 in a WPF application. I read in the docs and in other posts as well, that I should call the EnsureCoreWebView2Async() or set the Source property in order to initialize the WebView. If I set the Source, it loads the web page correctly, but I can't do this way, because I have the html content in memory (and it's not allowed to write to disk).
So I tried to call the initialization method:
var webView = new WebView2();
webView.NavigationCompleted += Navigation_Completed;
webView.Initialized += new EventHandler((object sender, EventArgs e) => {
webView.NavigateToString(myHtml);
});
await webView.EnsureCoreWebView2Async(null);
Running of this code is blocked by the EnsureCoreWebView2Async() method. I can even wait for one minute, but nothing happens, it's just stuck in initialization. No exception, no error message.
I run this code on UI thread, but the same thing happened when I called this method on another thread.
Does anyone experienced this behavior? Any ideas?
To show the WebView2 manually, you must add it to the Controls collection of the form.
When you drop a WebView2 on the form, the designer does this automatically.
Simply call:
Controls.Add(webView)
await webView.EnsureCoreWebView2Async(null);
Now you should be able to display your html.
Update:
You can still drop the WebView2 on your form, if you prefer, just don't set the Source property.
Then you can use the designer to assign event handlers and in Initialized event you call:
webView.NavigateToString(myHtml);
just as you do now.
If you need to go to a specific URI, I had to do the following calls.
As previously noted, do not set the Source attribute in your XAML.
Note that wv is the Name of my WevView2. Important part is that you still need to wait for the CoreWebView2 to be loaded. Now you can put any URI string in place of "https://www.microsoft.com"
private async void wv_Initialized(object sender, EventArgs e)
{
await wv.EnsureCoreWebView2Async(null);
wv.CoreWebView2.Navigate("https://www.microsoft.com");
}
A proper place to initialize webview2 is on WPF OnContentRendered()
Example:
protected override async void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
var webView2Environment = await CoreWebView2Environment.CreateAsync();
await webView2.EnsureCoreWebView2Async(webView2Environment);
}
Related
Scenario
I am creating a simple embedded "browser" for my app, which opens in a Popup, when the user clicks on a button with a hyperlink designed to open "in-app".
The popup is opened, and the (simply-named) WebView is navigated to the URL specified in the hyperlink.
There are the typical Back, Forward and Refresh/Stop buttons that are enabled/disabled accordingly.
Current Situation
I have wired up the necessary events for NavigationStarted, NavigationCompleted and others for Falied, Loaded etc. etc.
These are performed along with some "naughty" ViewModel wiring when the UserControl is loaded TL;DR - there is no way I can find to keep to MVVM practice with WebViews, what a PITA!:
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is IWebViewUserControlViewModel)
{
this.WebView.ContentLoading += OnWebViewContentLoading;
this.WebView.DOMContentLoaded += OnWebViewDomContentLoaded;
this.WebView.NavigationStarting += OnWebViewNavigationStarting;
this.WebView.NavigationCompleted += OnWebViewNavigationCompleted;
this.WebView.UnviewableContentIdentified += OnWebViewUnviewableContentIdentified;
this.WebView.NavigationFailed += OnWebViewNavigationFailed;
this.viewModel = DataContext as IWebViewUserControlViewModel;
NavigateToUrl(this.viewModel?.Url);
}
}
This is so that I navigate to the URL when the UserControl is loaded, and can evaluate the button states as the user navigates around using the events above.
The NavigateToUrl() method just contains a try/catch block to counteract any errors forming the Uri etc.:
private void NavigateToUrl(string url)
{
try
{
var uri = new Uri(url);
this.WebView.Navigate(uri);
}
catch (Exception ex)
{
this.WebView.NavigateToString($"An error occurred: {ex.Message}")
}
}
Back/Forward UX
In particular the Back and Forward buttons are disabled/enabled when navigation to a page is started/completed respectively.
I evaluate their IsEnabled states like so:
btnBackButton.IsEnabled = this.WebView.CanGoBack;
btnForwardButton.IsEnabled = this.WebView.CanGoForward;
This works fine throughout the entire time that the user is browsing.
The Issue
Should the user close the popup, and re-open it via the same or a different link, the correct URL is navigated to - all good.
The issue is, that their previous browsing session was never cleared from the MyWebView, and thus the btnBackButton (not the forward, as this the latest navigation in the history stack) is now enabled again - allowing them to traverse their previously visited pages.
I don't want this behaviour.
I would like it to appear that their session is a "new", fresh one - without the Back button enabled - as if it had just been opened.
What I have already tried...
I am unable to manually set the MyWebView.CanGoBack/MyWebView.CanGoForward properties to false when the popup is (re)opened.
They are get-only properties, so this is not possible.
I have tried re-initializing the WebView control when the containing UserControl is Loaded (in the same OnLoaded delegate as above):
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is IWebViewUserControlViewModel)
{
// Re-initialize the WebView
this.WebView = new WebView();
// Detect when the new control has loaded, and then wire up events/navigate as normal
this.WebView.Loaded += (sender, e) =>
{
this.WebView.ContentLoading += OnWebViewContentLoading;
this.WebView.DOMContentLoaded += OnWebViewDomContentLoaded;
this.WebView.NavigationStarting += OnWebViewNavigationStarting;
this.WebView.NavigationCompleted += OnWebViewNavigationCompleted;
this.WebView.UnviewableContentIdentified += OnWebViewUnviewableContentIdentified;
this.WebView.NavigationFailed += OnWebViewNavigationFailed;
this.viewModel = DataContext as IWebViewUserControlViewModel;
NavigateToUrl(this.viewModel?.Url);
}
}
}
In the hope that this might work - but the Loaded delegate of the WebView is never fired.
In the UI, the WebView just doesn't appear.
Help!?
Is there any way for me to clear the navigation history for the WebView control, so it appears that the browsing session is a "new" one?
Your help is appreciated, as always. Many thanks.
If your app is Windows 8.1, actually there is no programmatic way to clear the webview cache according to what #MattĀ said in thisĀ link(part 7).
And if it is a UWP app, please refer to this doc.
I have tried to different methods to get my MainPage to change and it is not happening. Basically when my App starts it needs to do some Async tasks to initialize everything, and then get the real main page to display. I first tried an event handler in App.cs. I can confirm the event handler does fire off in my debugger. However the code to switch the main page is not successfully doing so. Nothing changes in my UI. This code is below:
private void UnitStatusModelChanged(object sender, EventArgs e)
{
UnitStatusModel = _unitStatusMonitor.UnitStatusModel;
UnitStatusViewModel = new UnitStatusViewModel(UnitStatusModel, InputResourceHandler);
MainPage = new MainTabbed(UnitStatusViewModel, InputResourceHandler);
Debug.WriteLine("Switched page to " + UnitStatusModel.Version.Name);
}
protected override void OnStart()
{
_unitStatusMonitor.UnitStatusChanged += new UnitStatusModelChangedEventHandler(UnitStatusModelChanged);
_commandQueue.StartQueue();
}
I thought maybe setting MainPage this way is not the way to go. So I tried making the MainPage a blank NavigationPage, and then pushing a model page on top once the app is ready. This also had no effect.
var newPage = new MainTabbed(
new UnitStatusViewModel(_unitStatusModel, inputResourceHandler),
inputResourceHandler
);
App.Current.MainPage.Navigation.PushModalAsync(newPage);
I can confirm that if I start the app with a MainTabbed page, the page does display, so it should not be a problem with MainTabbed. Again, I also verified the in the debugger these lines of code are being executed with no exceptions being thrown. Still, no change to MainPage. What gives?
Check your thread after your async completes, when you call PushModalAsync (or any UI thing). Make sure it's on the main thread.
I have a problem that I can't resolve on the server side of my project.
I'll explain:
I have a page named Global,this is ASP.NET page.
This page uses a UserControl named CateGories.
Now I have a button on this UC page,that when I press I want to invoke a function on the Global page that makes a connection with my DB.
I decided to use delegates(events)
This is the code.
Global page:
//here i add my function to the event
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ShowCurrentTime.Text = DateTime.Now.ToString();
}
CateGories ClassCat = new CateGories();
ClassCat.MainDel += PopulateLinks;
}
//this is the function that the event will run
public void PopulateLinks(string CategoryName)
{....}
Code of the UC page (CateGories):
//delegation of the event
public delegate void Click(string ButtonName);
public event Click MainDel = null;
//function that invokes when I click a button
protected void News_Click(object sender, EventArgs e)
{
if (MainDel != null)
{
MainDel(News.Text);
}
}
Now everythig should work fine, but there is a problem, when the compiler gets to the
if(MainDel!=null)
...
It doesn't get in the function, there go MainDel is null.
I can't see the problem here, why after I insert function to MainDel, its gets null eventualy...
I'll be happy if someone can help
thanks.
Max.
I think I've encountered this problem before, when working with web applications the way I would a windows application.
The problem lies in that when the page gets reloaded, a new instance of your page class is created so any values from the last server interaction are lost.
MainDel is indeed null for what I can see.
You're creating one instance of CateGories on your Web Form and another one on your User Control. And once you check it for beeing null on the UC the reference is to the not initialized object.
One possible way to do that is creating and adding the User Control programatically to the page before PageLoad() and keeping a reference to it so you can access it's properties.
Another solution could be using Page.FindControl to find the UC and make the subscription to the event.
The method names above may be incorrect, it's been a long time without working with web forms.
I am having a rather odd problem with the Gecko Webbrowser control, I have created my own class which inherits off of the Gecko Webcontrol and within the constructor of this I have set an event:
class fooGeckoClass: Gecko.GeckoWebBrowser
{
public fooGeckoClass()
{
this.DomClick += new EventHandler<Gecko.GeckoDomEventArgs>(fooEventFunction);
}
private static void fooEventFunction(Object sender, Gecko.GeckoDomEventArgs e)
{
((Gecko.GeckoWebBrowser)sender).Navigate("www.foo.com");
}
}
I am using three of these controls in a manually created UserControl, the controls are loaded in dynamically at start up from a config file and added the the UserControl controls collection. When clicking on any of the three controls, all three will navigate to "www.foo.com" away from there original site. I had a look at:
e.StopPropagation();
Which specifies that it stops further propagation of events during an event flow, however it does also specify that it will handle all events in the current flow, I believe the events must have already been given to the controls before this has a chance to stop it as the the three controls will still fire the event. I also tried e.Handled = true to no avail. Has anyone encountered this problem before and have any kind of solution to make it only fire on the control that was clicked on?
EDIT:
It may be worth showing how the controls are added to the form seeing as this must be where the problem is occurring (it does not happen if the controls are just placed in a user control in a small test app).
private void fooUserControl_Load(object sender, EventArgs e)
{
if (!this.DesignMode)
{
for (int iControls = 0; iControls < geckObs.Count(); iControls ++)
{
fooGeckoClass geckControl = new fooGeckoClass();
this.Controls.Add(geckControl );
break;
}
}
}
Odd answer but I seem to have resolved the issue, DomClick was being called at first run, changing to DomMouseClick or DomMouseUp has completely resolved the issue. I assume DomClick must be an event unto itself as it also doesn't use the GeckoDomMouseEventArgs but the regular GeckoEventArgs.
EDIT:
To add to this, the site I was going to was actually calling DomClick when it had finished loading hence the reason it was being called at start up across all three browsers.
So I've got some serious problems with removing a Control from a Form of my application. It's kinda messed up but I can't change anything. I have a form and I have a separated user Control. The control opens an exe file and shows a progress bar while loading it's bytes. And here comes the problem. I do all of it with a BackgroundWorker and when the worker_DoWorkerCompleted method is called the original form should show a MessageBox and remove the Control.
BackGround_Loader bgLoad = new BackGround_Loader();
bgLoad.Location = new Point(this.Width/2 - bgLoad.Width/2, this.Height/2 - bgLoad.Height/2);
this.Controls.Add(bgLoad);
bgLoad.BringToFront();
bgLoad.AddReferences(this.executableFile, this.SourceReader);
bgLoad.occuredEvent();
At first I set the control's location to be in the middle of the Form itself. Then I add the control to the form, and bring it to the front. After these I send the path of the executable and a RichTextBox's reference to this. With the occuredEvent I start the BackgroundWorker itself. And here comes my problem. I should show a MessageBox in the Form when the in the bgLoad the backgroundworker gets to the DoWorkerCompleted status. Kindly I have no idea how to do it. It works just perfect however the control stays in the middle of the form.
UI actions must be performed on the main UI thread. The events that get raised from the background worker thread are (obviously) in a different thread.
You need something like the following code:
private void backgroundWorker_DoWork(object sender, AlbumInfoEventArgs e)
{
// Check with an element on the form whether this is a cross thread call
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
In this case AddToGrid is my method for adding a row to a DataGridView, but in your case it will be a method that does what you need to do.
Similarly for the backgroundWorker_RunWorkerCompleted method
See this MSDN example
I could find a way to solve the problem but I don't really like it. In the addReferences method I pass the Form itself and an object of the bgLoad class. Then in the RunWorkerCompleted I check if the control is on the form and if it is then I remove it.
bgLoad.AddReferences(this, bgLoad, this.executableFile, this.SourceReader);
...
private void worker_DoWorkerCompleted(object sender, DoWorkerEventArgs e) {
if(this.MainForm.Controls.Contains(this.Control) {
this.MainForm.Controls.Remove(this.Control);
}
}
Like this it works but it's awful for me.