EvaluateScriptAsync waiting in Cefsharp - c#

I am having a problem about using EvaluateScriptAsync in Cefsharp. I want to run a javascript code with EvaluateScriptAsync and then, after this is completed I want my code continue to work, but ContinueWith isn't waiting to EvaluateScriptAsync complete. Here is my code, and don't know what's the problem:
private void WebBrowserFrameLoadEnded(object sender, FrameLoadEndEventArgs e)
{
if (e.Frame.IsMain)
{
var task = browser.EvaluateScriptAsync("(function() { function xx() { $('body').animate({scrollTop: $('body').prop(\"scrollHeight\")}, 8000, function() { var win = $(window); if ($(document).height() - win.height() == win.scrollTop()) { return true; } else { xx(); } }); }; xx(); })();");
task.ContinueWith(taskHtml =>
{
if (taskHtml.IsCompleted)
{
/* Some code working */
}
this.DialogResult = DialogResult.OK;
});
}
}

You Should use the ExecuteSciptAsyc() method of the Browser Tab Control.
1st you have to get the Current tab page Control and then call the ExecuteSciptAsyc() method from browser control.
var control = GetCurrentTabControl();
control.Browser.ExecuteScriptAsync("alert('It's Working')");

Related

How can I stop the Task.Run()?

I'm newer to the concept of threading and I would like to use Task that is a component of Thread in my application because the save task takes time for executing.
This is my code:
private void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
Task.Run(() =>
{
var itemsS = Gridview.Items;
Dispatcher.Invoke(() =>
{
foreach (ItemsModel item in itemsS)
{
PleaseWaittxt.Visibility = Visibility.Visible;
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
PleaseWaittxt.Visibility = Visibility.Hidden;
});
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem is that when I save all items, I got duplicate data in the database. Otherwise, the count of ItemsS is fixed to 300, but after the saving, I got 600,
Did Task.Run() repeat the save task to the database ?
NB: I'm working on UI project ( WPF Desktop app )
I'm thinking you'd need something along the lines of this.
I quickly whipped it up but i hope its enough to attempt a fix yourself.
private async void SaveItem(object sender, RoutedEventArgs e)
{
try {
var itemsS = GridviewServices.Items.ToList(); // to list makes shallow copy
await Task.Run(() => {
foreach (ItemsModel item in itemsS)
{
bool testAdd = new Controller().AddItem(item);
}
});
// Dont update ui in task.run, because only the ui thread may access UI items
// Do so here - after the await. (or use dispatcher.invoke).
GridviewServices.Items.Clear();
GridviewServices.Items = itemsS;
} catch { ... } // Handle exceptions, log them or something. Dont throw in async void!
}
I'm also thinking this would work:
private async void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
var itemsS = GridviewServices.Items;
await Task.Run(() =>
{
foreach (ItemsModel item in itemsS)
{
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Visible;})
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Hidden;})
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem you're running in to, is because the Task you're executing isn't running in parallel, but synchronously to the rest of your application.
When you're running CPU-intensive tasks in the background of your UI-application, you'll want to either work with actual threads or async/await - which is what you attempted with your code.
What you'll want to do is something similar to this:
private async void SaveItem(object sender, RoutedEventArgs e) => await Task.Run(
/*optionally make this async too*/() => {
// Execute your CPU-intensive task here
Dispatcher.Invoke(() => {
// Handle your UI updates here
});
});
This is just a general overview, I don't know your exact use-case, but this should get you started in the right direction.
One thing to be weary of when using Lambdas and such, is closures.
If your application tends to use a lot of memory, you might want to re-think the structure of your calltree and minimize closures in your running application.

C# Application, Tick events and waiting

I'm writing an application that will automate one of our manual webform input processes
Everything is working good except for one problem.
I have a Timer setup, that becomes enabled on a certain page. The Timer tick event is checking the page every 100 milliseconds for ajax changes applied to the page. Once the ajax updates are detected, the Timer is disabled, the result is stored, and the program SHOULD continue executing code beyond that point.
The problem is the code continues to execute while the Timer is enabled.
In the logic, as soon as the appropriate page loads, I have
t2.Enabled = true;
Which immediately works as it should, looking at the page until the update is discovered
But the code immediately following the Enabled property is executing without pause, causing many issues, such as variables changing before the result is discovered.
How can I have the code following this line wait until the t2.Enabled is set back to false (which is done within the t2_Tick(object sender, EventArgs e) method
void t2_Tick(object sender, EventArgs e)
{
string postVerifyHTML = string.Empty;
try
{
postVerifyHTML = wb.Document.Body.InnerHtml;
}
// if page fails, restart
catch
{
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
if (postVerifyHTML.IndexOf("indentifier html") != -1)
{
NameSearchResults[nameCounter].Visited = true;
nameCounter++;
ResultFound = true;
t2.Enabled = false;
}
t2TimerCount++;
if (t2TimerCount >= 100)
{
// TRY AGAIN
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
}
protected void wb_SearchForm_DocumentCompleted(object sender, EventArgs e)
{
string pageHTML = wb.Document.Body.InnerHtml;
// Look at the page with the name result
if (pageHTML.IndexOf("Search Results: Verify") != -1)
{
//If the page has this input, a verification is available
if (pageHTML.IndexOf("txtSSN") != -1)
{
HtmlElement txtSSN = wb.Document.GetElementById("txtSSN");
txtSSN.SetAttribute("value", curSearchRecord.UniqueId.Replace("-", "").Replace(" ", ""));
HtmlElement submitBtn = wb.Document.GetElementById("ibtnVerify");
submitBtn.InvokeMember("click");
t2.Enabled = true;
// I need the code after this point to wait until the Timer is disabled
}
The Timer is running on a different thread to your UI code, which is why your execution is continuing. Why don't you simply check the Enabled state of the Timer to determine whether or not to continue the execution? Alternatively use the callback of your ajax code to fire off the continuation code.
Im not sure this is the best method to to it but you can do a do an if like so :
if (t2.Enabled=False)
{
//the code you want to run when the timer is off
}
but you have to make sure that it is in another timer (t3 in this case if you want) otherwise it wont check every tick if t2 is off to run the code while it is.
sorry if the answer is not more detailed, I lacked details in your question as well.
Good programing :)
You could try to use a ManualResetEvent as a member of your class
After you enable the Timer, you call the WaitOne method
After your disable the Timer, you call the Set method
private ManualResetevent mre = new ManualResetEvent(false);
void t2_Tick(object sender, EventArgs e)
{
string postVerifyHTML = string.Empty;
try
{
postVerifyHTML = wb.Document.Body.InnerHtml;
}
// if page fails, restart
catch
{
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
if (postVerifyHTML.IndexOf("indentifier html") != -1)
{
NameSearchResults[nameCounter].Visited = true;
nameCounter++;
ResultFound = true;
t2.Enabled = false;
//Set the mre to unblock the blocked code
mre.Set();
}
t2TimerCount++;
if (t2TimerCount >= 100)
{
// TRY AGAIN
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
}
protected void wb_SearchForm_DocumentCompleted(object sender, EventArgs e)
{
string pageHTML = wb.Document.Body.InnerHtml;
// Look at the page with the name result
if (pageHTML.IndexOf("Search Results: Verify") != -1)
{
//If the page has this input, a verification is available
if (pageHTML.IndexOf("txtSSN") != -1)
{
HtmlElement txtSSN = wb.Document.GetElementById("txtSSN");
txtSSN.SetAttribute("value", curSearchRecord.UniqueId.Replace("-", "").Replace(" ", ""));
HtmlElement submitBtn = wb.Document.GetElementById("ibtnVerify");
submitBtn.InvokeMember("click");
t2.Enabled = true;
//The code will block until Set() is called on mre
mre.WaitOne();
//The rest of your code here
}

SystemMediaTransportControls Not Working

I'm having issues with the new SystemMediaTransportControls that replace MediaControl.
Currently, I have my app set up with:-
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsPlayEnabled = true;
systemControls.IsStopEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.ButtonPressed += SystemControls_ButtonPressed;
And
async void SystemControls_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
System.Diagnostics.Debug.WriteLine(args.Button);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
if (mediaElement1.CurrentState != MediaElementState.Playing)
{
restartSource();
}
else
{
completeClosure();
}
break;
case SystemMediaTransportControlsButton.Pause:
case SystemMediaTransportControlsButton.Stop:
completeClosure();
break;
default:
break;
}
});
}
And:
private async void completeClosure()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Stop();
mediaElement1.Source = null;
timer.Stop();
});
}
private async void restartSource()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Source = new Uri(holdThisSource, UriKind.Absolute);
mediaElement1.Play();
timer.Start();
});
}
When a user presses the Pause Button, args.Button shows up as "Play", hence the need for the checking for MediaElement's state. However, when I attempt to resume to media, it successfully resumes in restartSource() and updates the app accordingly but the icon on the Volume Control does not change from the Play sign, although hardware buttons still work.
Along with this, pressing the hardware Stop button NEVER works, and fails to even show up in Debug.WriteLine.
This is an online streaming app where the source does not allow resuming and thus I have to close the stream this way.
I'd love some help on this.
Since you did not update the systemControls.PlaybackStatus, the control button on transport control will not auto change to correct status.
You should always update the systemControls.PlaybackStatus property when the playback state has changed.
May this could solve your problems.

Using FileSavePicker with MessageDialog's IUICommand event

Individually, all code works perfectly. The snippet for saving the file, the snippet for picking a directory to save it to and also the message dialog works great.
But when I tie it all together, I get an access denied. I am not using the DocumentsLibrary capability since it is not required of me to do so in this case, however, enabling this capability after running into issues confirmed that it is not the issue.
Scenario:
User wants to create a new document after entering text in the text box. A MessageDialog appears, asking them if they want to save changes to the existing file first - the user clicks Yes (save file).
Now, here is where you handle the event that was raised by the MessageDialog.
Inside the IUICommand command event handler, you test for which button was clicked, and act accordingly.
I did this with a switch statement:
switch(command.Label) {
case "Yes":
SaveFile(); // extension method containing save file code that works on its own
break;
case "No":
ClearDocument();
break;
default:
break;
}
Now, each case works great except for the Yes button. When you click yes, an e tension method is called which has code that saves to a file
It is when you click yes button that you get the ACCESS DENIED exception. Details of the exception didn't reveal anything.
I think that it has something to do with how I am using the MesaageDialog. But after searching for hours I have yet to find a sample on how to save a file with the FileSavePicker when a MesaageDialog button is pressed.
Any ideas in how this should be done?
Update w/ Code
When the user clicks the New document button on the AppBar, this method fires:
async private void New_Click(object sender, RoutedEventArgs e)
{
if (NoteHasChanged)
{
// Prompt to save changed before closing the file and creating a new one.
if (!HasEverBeenSaved)
{
MessageDialog dialog = new MessageDialog("Do you want to save this file before creating a new one?",
"Confirmation");
dialog.Commands.Add(new UICommand("Yes", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("No", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 2;
// Show it.
await dialog.ShowAsync();
}
else { }
}
else
{
// Discard changes and create a new file.
RESET();
}
}
And the FileSavePicker stuff:
private void CommandInvokedHandler(IUICommand command)
{
// Display message showing the label of the command that was invoked
switch (command.Label)
{
case "Yes":
MainPage rootPage = this;
if (rootPage.EnsureUnsnapped())
{
// Yes was chosen. Save the file.
SaveNewFileAs();
}
break;
case "No":
RESET(); // Done.
break;
default:
// Not sure what to do, here.
break;
}
}
async public void SaveNewFileAs()
{
try
{
FileSavePicker saver = new FileSavePicker();
saver.SuggestedStartLocation = PickerLocationId.Desktop;
saver.CommitButtonText = "Save";
saver.DefaultFileExtension = ".txt";
saver.FileTypeChoices.Add("Plain Text", new List<String>() { ".txt" });
saver.SuggestedFileName = noteTitle.Text;
StorageFile file = await saver.PickSaveFileAsync();
thisFile = file;
if (file != null)
{
CachedFileManager.DeferUpdates(thisFile);
await FileIO.WriteTextAsync(thisFile, theNote.Text);
FileUpdateStatus fus = await CachedFileManager.CompleteUpdatesAsync(thisFile);
//if (fus == FileUpdateStatus.Complete)
// value = true;
//else
// value = false;
}
else
{
// Operation cancelled.
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.InnerException);
}
}
Any progress on this issue? I currently have the same problem. I have also found that the same problem occurs if a second MessageDialog is shown in the IUICommand event.
My solution is to cancel the first operation (that shows the first message dialog). Here some code I’m using (it’s accessible in a global object):
private IAsyncInfo mActiveDialogOperation = null;
private object mOperationMutex = new object();
private void ClearActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation == operation)
mActiveDialogOperation = null;
}
}
private void SetActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation != null)
{
mActiveDialogOperation.Cancel();
}
mActiveDialogOperation = operation;
}
}
public void StopActiveOperations()
{
SetActiveOperation(null);
}
public async void ShowDialog(MessageDialog dialog)
{
StopActiveOperations();
try
{
IAsyncOperation<IUICommand> newOperation = dialog.ShowAsync();
SetActiveOperation(newOperation);
await newOperation;
ClearActiveOperation(newOperation);
}
catch (System.Threading.Tasks.TaskCanceledException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
So every time I want to show a MessageDialog I call ShowDialog. This will cancel the current dialog if any (then a TaskCanceledException occurs).
In the case when I will use a FileSavePicker, I call StopActiveOperations before PickSaveFileAsync is called.
This works but I can’t say I like it. It feels like I’m doing something wrong.
OK, now I have figured it out :-). The documentation says explicit that you shouldn’t show new popups/file pickers in the UICommand:
http://msdn.microsoft.com/en-US/library/windows/apps/windows.ui.popups.messagedialog.showasync
This is an example of a bad way to do it:
private async void Button_Click(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog (the application will crash).");
dialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(OnDialogOkTest1)));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
private async void OnDialogOkTest1(IUICommand command)
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
This is the correct way to do it:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog");
UICommand okCommand = new UICommand("OK");
UICommand cancelCommand = new UICommand("Cancel");
dialog.Commands.Add(okCommand);
dialog.Commands.Add(cancelCommand);
IUICommand response = await dialog.ShowAsync();
if( response == okCommand )
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
}
Quite simple actually, I should have get this earlier...

C# WebBrowser Button Click, then go to another page

I need to click an html button and navigate to another page. After click I need to wait for page loading, and go to the new page only when the old page loaded.
Here is the code, that click a button:
element = webBrowser1.Document.GetElementById("LoginButton");
element.InvokeMember("click");
webBrowser has got a IsBusy property, but it don`t works after button click:
element = webBrowser1.Document.GetElementById("LoginButton");
element.InvokeMember("click");
if(webBrowser1.IsBusy)
{
MessageBox.Show("Busy"); // Nothing happens, but page is not full loaded.
}
If I add System.Threading.Thread.Sleep(1000) the page loads and I can go to next page, but page loading time on other computers can be more.
What can I do to load another page only after the previous page has loaded?
P.S: I am from Russia, so sorry for bad English.
If your webpage has any javascript blocks, you won't be able to solve the problem using the WebBrowser control itself. You should wait for a document.ready event using javascript code and let know your C# program about it.
Previously, I made a javascript block that provides the webpage state. It looks like this:
var isBusy = true;
function getIsScriptBusy () {
return isBusy;
}
// when loading is complete:
// isBusy = false;
// document.ready event, for example
and a C# code that waits for it to return true:
void WaitForCallback(int timeout) {
Stopwatch w = new Stopwatch();
w.Start();
Wait(delegate() {
return (string)Document.InvokeScript("getIsScriptBusy") != "false"
&& (w.ElapsedMilliseconds < timeout || Debugger.IsAttached);
});
if(w.ElapsedMilliseconds >= timeout && !Debugger.IsAttached)
throw new Exception("Operation timed out.");
}
void Wait(WaitDelegate waitCondition) {
int bRet;
MSG msg = new MSG();
while(waitCondition() && (bRet = GetMessage(ref msg, new HandleRef(null, IntPtr.Zero), 0, 0)) != 0) {
if(bRet == -1) {
// handle the error and possibly exit
} else {
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
Thread.Sleep(0);
}
}
There are lots of events exposed by the WebBrowser control. You might try Navigated or DocumentCompleted.
Nick
WebBrowser.Navigated is the browser event you are seeking.
Use this ,
You might just be able to use this once
br1.DocumentCompleted += br1_DocumentCompleted;
Application.Run();
Call
void br1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var br1 = sender as WebBrowser;
if (br1.Url == e.Url)
{
Console.WriteLine("Natigated to {0}", e.Url);
Application.ExitThread(); // Stops the thread
}
}
Replace br1 with your webbrowser name
Hope this helps

Categories