Webview2 Navigation - c#

Currently in Webview2 browser if navigated to a particular URL as written below
browser.Source = URL;
This triggers NavigatingStarting event asynchronously.
How can I trigger a synchronous call for each source being set to trigger the event?
Problem: I am keeping a bool variable to check for the if navigation triggered inside my application and resetting it at the end for the navigatingstarting event. since it is an asynchronous call it is resetting after the first call without the next call being inside my application.
void SetBrowserUrl(Uri value)
{
m_bInsideNavigateCall = true;
priorsWebBrowser.Source = value;
}
void priorsWebBrowser_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
if(m_bInsideNavigateCall)
{
e.Cancel = false;
m_bInsideNavigateCall = false; // Reset for next inside call
}
else
{
e.Cancel = true;
}
}
Here the issue is if call SetBrowserUrl twice. Navigate starting cancels the second call made because it is not synchronous

I have created a list of strings.
List<String> insideNavigatingURLS; //Class level variable
Just before calling web-browser to navigate, I add the URL to list.
internalURLs.Add(uri.AbsolutePath.ToLower());
webBrowser.Source = uri;
In the NavigationStarting Event added a check to see if list contains the navigation url if it doesn't then will cancel the request.
void webBrowser_Navigating(object sender, CoreWebView2NavigationStartingEventArgs e)
{
if (!internalUrls.Contains(e.Uri))
{
e.Cancel = true;
}
else
{
internalUrls.Remove(e.Uri);
e.Cancel = false;
}
}
So when back or forward navigation is triggered, the list doesn't contain a URL and the navigation request is cancelled

Related

How suppress multiple button clicks?

I would like to know the best way to handle an http Request on Xamarin.Forms.
For now I was handling the request this way:
First I have a button on my forms like this:
btn_1.Clicked += (sender, e) => {
Confirm(name, password);
};
My Confirm() function checks the entrees and throws the event of the request. Also it do the logic after the request event is completed. For example:
private async void Confirm(string name, string password) {
UserController user_controller = new UserController();
if (name != null && password != null) {
User user = new User(name, password);
bool ok = user_controller.Login(user);
if(ok){
Navigation.InsertPageBefore(new NextPage(), this);
await Navigation.PopAsync();
} else {
//Show error code...
}
}
}
My UserController has two functions for each http request. The first one does the request. The second one calls the first one and handles the answer.
1º:
private async Task<HttpResponseMessage> user_login(User user){
try {
Uri uri = new Uri("http://localhost/user/login");
string user_json = JsonConvert.SerializeObject(user);
StringContent content = new StringContent(user_json, Encoding.UTF8, "application/json");
return await Utilities.client.PostAsync(uri, content).ConfigureAwait(false);
} catch {
return null;
}
}
2º:
public bool Login(User user) {
http_response = user_login(user).GetAwaiter().GetResult();
//If it doesn't reach the server...
if (http_response != null) {
//Depending of the status of the response
switch (http_response.StatusCode) {
case (System.Net.HttpStatusCode)200:
try {
string content = http_response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Response response = JsonConvert.DeserializeObject<Response>(content);
return (bool) response.aux;
} catch {
}
break;
case (System.Net.HttpStatusCode)401:
...
break;
default:
...
break;
}
} else {
App.Current.MainPage.DisplayAlert("Error", "No server connection", "OK");
}
return false;
}
This completes my protocol for each request. My problem is:
1. I'm not sure if this is the correct way to do it
2. When I click several times the btn_1 it throws many times the request
How could I do to avoid this? I try to put a lock on my button but it doesn't work. I'm having many troubles with the asynchronous requests. I don't know which is the best way to handle the request to throw only one request at the time.
EDIT:
I have created this button extension:
public partial class LockableButton: Button {
public event EventHandler ThrowEvent;
public bool ShowLoading { get; set; }
public LockableSimpleButton() {
ShowLoading=false;
InitializeComponent();
this.Clicked += async (object sender,EventArgs e) => {
if (!Utilities.Unlocked) { return; }
Utilities.Unlocked=false;
try {
if (ShowLoading) {
await Navigation.PushModalAsync(new LoadingPopUp());
ThrowEvent(sender,e);
await Navigation.PopModalAsync();
} else {
ThrowEvent(sender,e);
}
} finally {
await Task.Delay(1000);
Utilities.Unlocked=true;
}
};
}
}
And now my buttons are like this:
btn_1.ThrowEvent += async (sender, e) => {
Navigation.InsertPageBefore(new Page(),this);
await Navigation.PopAsync(false);
};
How it is even posible that the error still persisting?
When I click several times the button it throws an error because it is trying to PopAsyc to many time the same page... It is the delay to short?
When I click several times the btn_1 it throws many times the request
This problem has nothing to do with handling an Async HTTP Request.
Here are two classic coding techniques for discarding extra button presses.
They are variations on having a flag set, and discarding any clicks that happen while that flag is set.
Common pseudo-code:
static bool _busy;
...click handler... {
if (_busy) return;
_busy = true;
// Special code as needed.
... handle the click ...
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
When you finish processing the first click, start a one-time timer. Until that timer fires, discard any additional clicks.
Pseudo-code:
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
var timer = new Timer(TimerTick, 250 milliseconds, one-time);
timer.Start();
}
}
void TimerTick() {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
//maybe timer.Stop();
}
When you start processing the first click, set a flag. Clear that flag when you are done processing. Discard any clicks that happen while that flag is set.
Pseudo-code:
// Must be `async` method, so events continue while processing.
// Otherwise, the second button press might simply be on hold,
// until after this finishes, so doesn't get suppressed.
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
}

Using Manual Reset Event till last property changes

I have call back that comes as Property Changed Notifications from Firmware. Now in my code I want to wait till I hit the last property changed. I read the following post Manual Reset Event regarding ManualResetEvent but it says Manual Reset Events are used in case of multi threading. I am new to ManualResetEvents. This is my following code can I use the manual reset event in my case? if so how? If not whats the best way to wait there? Please help.
//This is some button click action of RelayCommand
private void StartCurrentRun(bool obj)
{
this.worker = new BackgroundWorker();
this.worker.WorkerReportsProgress = true;
this.worker.WorkerSupportsCancellation = true;
OriginalTime = SelectedVolumeEstimatedTime();
StartTimer();
WhenCancelledBlurVolumesGrid = false;
//this.worker.DoWork += this.DoWork;
//this.worker.ProgressChanged += this.ProgressChanged;
//this.worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
//this.worker.RunWorkerAsync();
IsLiveProgress = true;
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been started", SelectedAssay, SelectedVolume), LogInformationType.Info);
var instance = ConnectToInstrument.InstrumentConnectionInstance;
instance.InitalizeRun(PopulateRespectiveVolumes());
PropertyCallBackChangedInstance.PropertyChanged += PropertyCallBackChangedInstance_PropertyChanged;
//Here I want to perform some action after I get a Processed state after the final property change event occurs.
//Can I do a manual reset event here.
}
private void PropertyCallBackChangedInstance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
try
{
if (e.PropertyName == "InstrumentStatusChanged")
{
var value = sender as InstrumentCallBackProperties;
if (value.InstrumentStatusChanged == CurrentInstrumentStatus.Busy)
{
CurrentStatus = Application.Current.TryFindResource("Wait").ToString();
}
}
if (e.PropertyName == "RunStepStatusName")
{
var value = sender as InstrumentCallBackProperties;
CurrentStatus = EnumExtensions.GetDescription(value.RunStepStatusName);
NewProgressValue += 20;
UpdateProgress = true;
}
else if (e.PropertyName == "CurrentCartridgeStatusChanged")
{
var value = sender as InstrumentCallBackProperties;
if (value.CurrentCartridgeStatusChanged == CurrentCartridgeStatus.Processed)
{
PropertyCallBackChangedInstance.PropertyChanged -= PropertyCallBackChangedInstance_PropertyChanged;
EstimatedTimeRemaining = "00:00:00";
stopWatch.Stop();
timer.Stop();
IsLiveProgress = false;
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been completed", SelectedAssay, SelectedVolume), LogInformationType.Info);
if (IsRunSuccessfullyComplete != null && !WhenCancelledBlurVolumesGrid) //This indicates that Success will only open when the run is complete
{
IsRunSuccessfullyComplete();
}
WhenCancelledBlurVolumesGrid = true;
if (ClearSelections != null)
{
ClearSelections();
}
}
}
}
catch (Exception ex)
{
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} failed", SelectedAssay, SelectedVolume), LogInformationType.Error);
}
}
It doesn't look to me like you want ManualResetEvent, which is used to signal multiple listeners. If your results were being produced by multiple tasks then you would use await WhenAll(...) however in your case the results are being informed by a property change not by a task completion. I would suggest that you simply record each property notification when it occurs, and check to see whether they are all complete. An easy way to do this is to use an enum with the [Flags] attribute: [Flags]public enum Completions { InstrumentStatusChanged, CurrentCartridgeStatusChanged, RunStepStatusName }. Just set the appropriate flag for each property callback and when you have set all the flags then you are done. If the property callbacks can occur in parallel or on different threads then you will need some form of locking such as a SemaphoreSlim(1,1).

Using C# & Xamarin Forms - How can I close one modal without setting of chain of closures

In one stage of my app (Android & iOS are the ones we care about) we've got three pages which take in details and then open a webView for the user to input their card details to take a payment - this can't be done in the app due to Apple's guidelines.
I need to format the navigation in a way that when the user has finished in the webView it closes and then closes the 3 previous modals to get back to the original page. I've got it all working with the Appearing event so each page just closes itself:
this.Appearing += async (s, e) =>
{
await Navigation.PopModalAsync();
};
The issue I'm now having is that when the user presses the back button on the phone, it closes all of the pages that they've been through already & back to the original. I thought about implementing a custom nav bar and disabling the back button on the hardware but this would cause the same problem with the Appearing event.
Is there any easy way to solve this?
EDIT: Relevant code;
async void OnButtonClicked(object sender, EventArgs eventArgs)
{
if (IsConnected)
{
ActivityIndicator.IsVisible = true;
var button = (Button) sender;
button .IsEnabled = false;
await Navigation.PushModalAsync(new Page());
this.Appearing += (s, e) =>
{
ActivityIndicator.IsVisible = false;
button.IsEnabled = true;
RefreshPage();
};
}
else
{
NoInternetLabel.IsVisible = true;
}
}
Use this:
YourButton.Clicked += OpenPage;
OpenPage looks like this:
async public void OpenPage(object sender, EventArgs args)
{
await Navigation.PushAsync(new PageToShow());
}
You don't have to do anything to handle the PageToShow() closing, that happens by itself when the user presses the back button.
Managed to solve this by using Actions. In each new Page() we passed up an async method to close it once the one after had completed;
var nextPage = new Page(async () =>
{
await Navigation.PopModalAsync();
_completedSuccessfully();
});
await Navigation.PushModalAsync(nextPage);
And in the new page class;
private readonly Action _completedSuccessfully;
public Page(Action completedSuccessfully)
{
_completedSuccessfully = completedSuccessfully;
}
This meant that when the webView closed it called the completedSuccessfully() action and then chained all of them to the original page.

Simple switch causes stack overflow

Why this simple code causes fallowing error :
Cannot evaluate expression because the current thread is in a stack overflow state.
private void barButtonPanelVisibleCheck_CheckedChanged(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
{
switch (barButtonPanelVisibleCheck.Checked)
{
case true:
this.navBarControl.Visible = false;
this.barButtonPanelVisibleCheck.Checked = false;
break;
case false:
this.navBarControl.Visible = true;
this.barButtonPanelVisibleCheck.Checked = true;
break;
}
//or
if (barButtonPanelVisibleCheck.Checked == true)
{
this.navBarControl.Visible = false;
this.barButtonPanelVisibleCheck.Checked = false;
}
else
{
this.navBarControl.Visible = true;
this.barButtonPanelVisibleCheck.Checked = true;
}
}
You are changing Checked from within the Checked handler: Checked value is set, so the handler is called, which sets Checked value etc. and you have an infinite loop.
Since you are trying to change the checked state of your checkbox while you are inside the CheckedChanged event, you raise another CheckedChanged event, and this starts an infinite loop that consume the stack memory until you reach the StackOverflow exception.
Try to stop the recursion on your CheckedChanged event with
private void barButtonPanelVisibleCheck_CheckedChanged(object sender,
DevExpress.XtraBars.ItemClickEventArgs e)
{
try
{
this.barButtonPanelVisibleCheck.CheckedChanged -=
barButtonPanelVisibleCheck_CheckedChanged;
... do your checked changed here
}
finally
{
this.barButtonPanelVisibleCheck.CheckedChanged +=
barButtonPanelVisibleCheck_CheckedChanged;
}
}
Disconnecting the Event handler allows to change the checked state without reentry the event handler, after that, reconnect the event. Probably for this scenarion there is no need to use a try/finally but using finally will ensure that the event is always reconnected in case your code fails with an exception.
When checked state of the control is changed, method barButtonPanelVisibleCheck_CheckedChanged is called. In that method you change the Checked property of that control, which causes call to method barButtonPanelVisibleCheck_CheckedChanged. In that method you change the Checked property of that control.

How to make WebBrowser wait till it loads fully?

I have a C# form with a web browser control on it.
I am trying to visit different websites in a loop.
However, I can not control URL address to load into my form web browser element.
This is the function I am using for navigating through URL addresses:
public String WebNavigateBrowser(String urlString, WebBrowser wb)
{
string data = "";
wb.Navigate(urlString);
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
data = wb.DocumentText;
return data;
}
How can I make my loop wait until it fully loads?
My loop is something like this:
foreach (string urlAddresses in urls)
{
WebNavigateBrowser(urlAddresses, webBrowser1);
// I need to add a code to make webbrowser in Form to wait till it loads
}
Add This to your code:
webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
Fill in this function
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
//This line is so you only do the event once
if (e.Url != webBrowser1.Url)
return;
//do you actual code
}
After some time of anger of the crappy IE functionality I've came across making something which is the most accurate way to judge page loaded complete.
Never use the WebBrowserDocumentCompletedEventHandler event
use WebBrowserProgressChangedEventHandler with some modifections seen below.
//"ie" is our web browser object
ie.ProgressChanged += new WebBrowserProgressChangedEventHandler(_ie);
private void _ie(object sender, WebBrowserProgressChangedEventArgs e)
{
int max = (int)Math.Max(e.MaximumProgress, e.CurrentProgress);
int min = (int)Math.Min(e.MaximumProgress, e.CurrentProgress);
if (min.Equals(max))
{
//Run your code here when page is actually 100% complete
}
}
Simple genius method of going about this, I found this question googling "How to sleep web browser or put to pause"
According to MSDN (contains sample source) you can use the DocumentCompleted event for that. Additional very helpful information and source that shows how to differentiate between event invocations can be found here.
what you experiencend happened to me . readyStete.complete doesnt work in some cases. here i used bool in document_completed to check state
button1_click(){
//go site1
wb.Navigate("site1.com");
//wait for documentCompleted before continue to execute any further
waitWebBrowserToComplete(wb);
// set some values in html page
wb.Document.GetElementById("input1").SetAttribute("Value", "hello");
// then click submit. (submit does navigation)
wb.Document.GetElementById("formid").InvokeMember("submit");
// then wait for doc complete
waitWebBrowserToComplete(wb);
var processedHtml = wb.Document.GetElementsByTagName("HTML")[0].OuterHtml;
var rawHtml = wb.DocumentText;
}
// helpers
//instead of checking readState . we get state from DocumentCompleted Event via bool value
bool webbrowserDocumentCompleted = false;
public static void waitWebBrowserToComplete(WebBrowser wb)
{
while (!webbrowserDocumentCompleted )
Application.DoEvents();
webbrowserDocumentCompleted = false;
}
form_load(){
wb.DocumentCompleted += (o, e) => {
webbrowserDocumentCompleted = true;
};
}

Categories

Resources