On a Windows Form, I am using a Webbrowser control in C#. It's job is to upload a file and then press the submit button. My only problem is that my code tries to press the submit button before the file is finished uploading. I tried using:
System.Threading.Thread.Sleep(1000);
In between the two tasks (commented out below). This seems to pause the entire process so that didn't work. Can anyone tell me what the best way to do this is?
private void imageBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
this.imageBrowser.DocumentCompleted -= imageBrowser_DocumentCompleted;
Populate().ContinueWith((_) =>
{
//MessageBox.Show("Form populated!");
}, TaskScheduler.FromCurrentSynchronizationContext());
//System.Threading.Thread.Sleep(10000);
try
{
var buttons = imageBrowser.Document.GetElementsByTagName("button");
foreach (HtmlElement button in buttons)
{
if (button.InnerText == "done")
{
button.InvokeMember("click");
}
}
}
catch
{
//debug
}
}
async Task Populate()
{
var elements = imageBrowser.Document.GetElementsByTagName("input");
foreach (HtmlElement file in elements)
{
if (file.GetAttribute("name") == "file")
{
file.Focus();
await PopulateInputFile(file);
}
}
}
async Task PopulateInputFile(HtmlElement file)
{
file.Focus();
// delay the execution of SendKey to let the Choose File dialog show up
var sendKeyTask = Task.Delay(500).ContinueWith((_) =>
{
// this gets executed when the dialog is visible
SendKeys.Send("C:\\Users\\00I0I_c0OlVXtE6FO_600x450.jpg" + "{ENTER}");
System.Threading.Thread.Sleep(1000);
SendKeys.Send("{ENTER}");
}, TaskScheduler.FromCurrentSynchronizationContext());
file.InvokeMember("Click"); // this shows up the dialog
await sendKeyTask;
// delay continuation to let the Choose File dialog hide
await Task.Delay(500);
//SendKeys.Send("{ENTER}");
}
Is the WebBrowser loading a local file? Can you post the html code as well?
I came across such a situation when I was working with google-maps-api-3. I was setting some markers on the form in the WebBrowser_DocumentCompleted but was getting a null object exception. So I moved the code for set marker to a .NET Button control. I noticed that the exception was not thrown when I set the marker after the map tiles completed loading. DocumentCompleted was firing before the tiles got loaded and I was getting a null object exception.
So what I did was to use a tilesLoaded event in my javascript. In this event, I set a property back in C# code which set the markers in the OnPropertyChanged event.
I know what I am posting here is not a solution. But if you post your html code, I can give you answer with some code.
I solved this. The code I was using to click the button was in the wrong spot. The code now looks like so:
private void imageBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
this.imageBrowser.DocumentCompleted -= imageBrowser_DocumentCompleted;
try
{
Populate().ContinueWith((_) =>
{
var buttons = imageBrowser.Document.GetElementsByTagName("button");
foreach (HtmlElement button in buttons)
{
if (button.InnerText == "done")
{
button.InvokeMember("click");
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch
{
//debug
}
}
My mistake was thinking in terms of having a certain amount of seconds pass before executed the next line of code, when I should have been thinking in terms of having the next line of code execute when the previous task was complete.
Related
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.
I have a GridView (RadGridView by Telerik) with a CheckBox column for multiple row selection. Each row represents a document and I need to be able to select multiple documents to apply them a digital signature.
Actually if only one document is selected, I open a new aspx page inside a popup and that page calls the DigitalSignature service (a web application listening on specific port) that open a Wacom screen wich waits for the signature on the Wacom tablet. When the user sign or close the window, the popup close and the GridView is updated.
I need to do this inside a foreach loop for every selected row.
The problem is that now I call ScriptManager.RegisterStartupScript with unique name but they're opened all togheter at the same time but I want to open one by one.
Possibly without using jQuery or 3rd party libraries.
I'm using .NET Framework v3.5.
Actual code:
foreach (SelectedDocument selectedDoc in SelectedDocuments)
{
Documento doc;
try
{
// Open document
doc = session.Load<Document>(Convert.ToInt64(selectedDoc.ID));
if (doc != null)
{
// Get the file...
// Do some stuff...
// Set query string
string queryString = string.Format("Sign.aspx?fileNameToSign={0}&reader={1}&view={2}&docid={3}&userid={4}",
pathQueryString, certificate, "0", selectedDoc.ID, UserId);
string urlDownload = Page.ResolveClientUrl(string.Format("~/Path/To/Folder/{0}", queryString));
string script = string.Format("loadDownload('{0}','_blank',600,600);", urlDownload);
// Open Wacom window
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "DocID" + selectedDoc.ID, script, true);
}
}
}
Why don't you set an on off check to see that the document has been signed, before the loop gets to the next document, it should wait (Use await to go on with the loop)
See example:
Start by creating a helper method to generate a task that will be completed when a button is clicked:
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<bool>();
EventHandler handler = null;
handler = (s, e) =>
{
tcs.TrySetResult(true);
button.Click -= handler;
};
button.Click += handler;
return tcs.Task;
}
After that, you can easily wait until either 2 seconds (or more) have passed, or the button is clicked:
public async void Bar()
{
this.button.Focus();
this.button.BackColor = Color.Green;
await Task.WhenAny(Task.Delay(2000), button.WhenClicked());
// now the loop continues and the button changes its color
this.button.BackColor = Color.White;
}
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;
};
}
I use a WebBrowser control as a "preview" for a special type of markup. I process the markup, and then set the browser's DocumentText property to display it. This works okay.
However, sometimes the control takes upwards of 5-10 seconds for this operation to complete. Since I do this at application start up (blanking the window with DocumentText = ""), and it still takes forever to do this, I want to somehow Thread this or something so that the rest of the application can start up while the control... does whatever it does.
For reference, the function in question is:
private void btnRefresh_Click(object sender, EventArgs e) {
try {
scrolltop = html.Document.Body.ScrollTop;
scrollleft = html.Document.Body.ScrollLeft;
} catch (NullReferenceException) { }
html.DocumentText = HtmlProcessing.ProcessCode(txtCode.Text); //takes 5-10 seconds
}
I know I can't just fire up a thread to handle this, since I assume the WebBrowser control will puke if I try to access it from the other thread. Are there any other techniques I could use to do this?
Edit:
Turns out, my delay was not being caused by html.DocumentText, nor by HtmlProcessing.ProcessCode, but by a bit of code that I omitted from the sample, because I thought it was irrelevant:
private void btnRefresh_Click(object sender, EventArgs e) {
try {
scrolltop = html.Document.Body.ScrollTop;
scrollleft = html.Document.Body.ScrollLeft;
} catch (NullReferenceException) { }
html.DocumentText = HtmlProcessing.ProcessCode(txtCode.Text, GetImageList());
}
That call to GetImageList() was taking forever. The contents of the function looks like:
List<string> GetImageList() {
List<string> ret = new List<string>();
ret.AddRange(Directory.GetFiles(settings.LocalImageFolder).Where((f) => { return extensions.Contains(Path.GetExtension(f)); }));
ret.AddRange(Directory.GetFiles(settings.RemoteImageFolder).Where((f) => { return extensions.Contains(Path.GetExtension(f)); }));
return ret;
}
And, settings.RemoteImageFolder is, in fact, a network path that was taking forever. I ended up turning this into an IEnumerable<string>. That way, I only hit the network when ProcessCode actually needs files from there.
The problem was a slow network access that was hidden behind a property accessor, and had nothing to do with the web browser control.
I am writing a toolbar for IE(6+). I have used the various sample bars from
codeproject.com (http://www.codeproject.com/KB/dotnet/IE_toolbar.aspx), and have a toolbar that works, registers unregisters etc. What I want the toolbar to do is to highlight divs within an html page as the users' mouse moves over that div. So far the highlighting code works, but I would like to display the name of the div (if it exists) in a label on the toolbar (changing as the mouse moves etc).
I cannot for the life of me get this to happen and trying to debug it is a nightmare. As the assembly is hosted in IE, I suspect that I am causing an exception (in IE) by trying to update the text on the label from a thread that didn't create that control, but because that exception is happening in IE, I don't see it.
Is the solution to try to update the control in a thread-safe way using Invoke? If so how?
Here is the event code:
private void Explorer_MouseOverEvent(mshtml.IHTMLEventObj e)
{
mshtml.IHTMLDocument2 doc = this.Explorer.Document as IHTMLDocument2;
element = doc.elementFromPoint(e.clientX, e.clientY);
if (element.tagName.Equals("DIV", StringComparison.InvariantCultureIgnoreCase))
{
element.style.border = "thin solid blue;";
if (element.className != null)
{
UpdateToolstrip(element.className);
}
}
e.returnValue = false;
}
and here is an attempt at thread-safe update of the toolbar:
delegate void UpdateToolstripDelegate(string text);
public void UpdateToolstrip(string text)
{
if (this.toolStripLabel1.InvokeRequired == false)
{
this.toolStripLabel1.Text = text;
}
else
{
this.Invoke(new UpdateToolstripDelegate(UpdateToolstrip), new object[] { text });
}
}
Any suggestions much appreciated.
I can't really reproduce the issue (creating a test project for an IE toolbar is a tad too much work), but you can try this:
Add the following routine to a public static (extensions methods) class:
public static void Invoke(this Control control, MethodInvoker methodInvoker)
{
if (control.InvokeRequired)
control.Invoke(methodInvoker);
else
methodInvoker();
}
And then replace the section of similar code in the first block with this:
if (element.className != null)
{
this.Invoke(() => toolStripLabel1.Text = element.className);
}
This is a sure-fire way of avoiding thread-safe issues in UI applications.