To collect information on a webpage, I can use the WebBrowser.Navigated event.
First, navigate to the url:
WebBrowser wbCourseOverview = new WebBrowser();
wbCourseOverview.ScriptErrorsSuppressed = true;
wbCourseOverview.Navigate(url);
wbCourseOverview.Navigated += wbCourseOverview_Navigated;
Then process the webpage when Navigated is called:
void wbCourseOverview_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
//Find the control and invoke "Click" event...
}
The difficult part comes when I try to go through a string array of urls.
foreach (var u in courseUrls)
{
WebBrowser wbCourseOverview = new WebBrowser();
wbCourseOverview.ScriptErrorsSuppressed = true;
wbCourseOverview.Navigate(u);
wbCourseOverview.Navigated += wbCourseOverview_Navigated;
}
Here, because the page load takes time, wbCourseOverview_Navigated is never reached.
I tried to use the async await in C#5. Tasks and the Event-based Asynchronous Pattern (EAP) is found in here. Another example can be found in The Task-based Asynchronous Pattern.
The problem is WebClient has async method like DownloadDataAsync and DownloadStringAsync. But there is no NavigateAsync in WebBrowser.
Can any expert give me some advice? Thank you.
There is a post in StackOverflow (here). But, does anyone know how to implement that strut in its answer?
Update again.
Suggested in another post here in StackOverflow,
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
return tcs.Task;
}
So I have:
foreach (var c in courseBriefs)
{
wbCourseOverview.Navigate(c.Url);
await wbCourseOverview.WhenDocumentCompleted();
}
It looks good until my web browser visits the second url.
An attempt was made to transition a task to a final state when it had already completed.
I know I must have made a mistake inside the foreach loop. Because the DocumentCompleted event has not been raised when it loops to the second round. What is the correct way to write this await in a foreach loop?
There is a post in StackOverflow (here). But, does anyone know how to implement that strut in its answer?
Ok, so you want some code with awaiter. I've made two pieces of code.
The first one uses TPL's built-in awaiter:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ProcessUrlsAsync(new[] { "http://google.com", "http://microsoft.com", "http://yahoo.com" })
.Start();
}
private Task ProcessUrlsAsync(string[] urls)
{
return new Task(() =>
{
foreach (string url in urls)
{
TaskAwaiter<string> awaiter = ProcessUrlAsync(url);
// or the next line, in case we use method *
// TaskAwaiter<string> awaiter = ProcessUrlAsync(url).GetAwaiter();
string result = awaiter.GetResult();
MessageBox.Show(result);
}
});
}
// Awaiter inside
private TaskAwaiter<string> ProcessUrlAsync(string url)
{
TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
{
// TODO: put custom processing of document right here
taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
});
webBrowser1.DocumentCompleted += handler;
taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });
webBrowser1.Navigate(url);
return taskCompletionSource.Task.GetAwaiter();
}
// (*) Task<string> instead of Awaiter
//private Task<string> ProcessUrlAsync(string url)
//{
// TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
// var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
// {
// taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
// });
// webBrowser1.DocumentCompleted += handler;
// taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });
// webBrowser1.Navigate(url);
// return taskCompletionSource.Task;
//}
And the next sample contains the sample implementation of awaiter struct Eric Lippert was talking about here.
public partial class Form1 : Form
{
public struct WebBrowserAwaiter
{
private readonly WebBrowser _webBrowser;
private readonly string _url;
private readonly TaskAwaiter<string> _innerAwaiter;
public bool IsCompleted
{
get
{
return _innerAwaiter.IsCompleted;
}
}
public WebBrowserAwaiter(WebBrowser webBrowser, string url)
{
_url = url;
_webBrowser = webBrowser;
_innerAwaiter = ProcessUrlAwaitable(_webBrowser, url);
}
public string GetResult()
{
return _innerAwaiter.GetResult();
}
public void OnCompleted(Action continuation)
{
_innerAwaiter.OnCompleted(continuation);
}
private TaskAwaiter<string> ProcessUrlAwaitable(WebBrowser webBrowser, string url)
{
TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
{
// TODO: put custom processing of document here
taskCompletionSource.SetResult(e.Url + ": " + webBrowser.Document.Title);
});
webBrowser.DocumentCompleted += handler;
taskCompletionSource.Task.ContinueWith(s => { webBrowser.DocumentCompleted -= handler; });
webBrowser.Navigate(url);
return taskCompletionSource.Task.GetAwaiter();
}
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ProcessUrlsAsync(new[] { "http://google.com", "http://microsoft.com", "http://yahoo.com" })
.Start();
}
private Task ProcessUrlsAsync(string[] urls)
{
return new Task(() =>
{
foreach (string url in urls)
{
var awaiter = new WebBrowserAwaiter(webBrowser1, url);
string result = awaiter.GetResult();
MessageBox.Show(result);
}
});
}
}
}
Hope this helps.
Instead of using wbCourseOverview_Navigated use webBrowser1_DocumentCompleted when fist URL load completed done your job and go to next url
List<string> urls = new List<string>();
int count = 0;
public Form1()
{
InitializeComponent();
webBrowser1.DocumentCompleted+=new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
}
private void Form1_Load(object sender, EventArgs e)
{
webBrowser1.Navigate(urls[count++]);
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//Do something
webBrowser1.Navigate(urls[count++]);
}
Related
Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)
I want to have the returned value from the send Operation which is a string and use it in the public MainPage() section. I tried this way, bot doesn´t work. Any idea how to get this value out of the send() Method?
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
string stringData = "";
stringData = "aktion=getBenutzer&name=" + Login.getBenutzername();
//send(stringData);
//textBlock1.Text = getContentOfSendOperation();
button.Foreground = Einstellungen.getBrush();
button1.Foreground = Einstellungen.getBrush();
button2.Foreground = Einstellungen.getBrush();
stringData = "aktion=getMitarbeiterListe";
//string mitarbeiterListe = getContentOfSendOperation();
var task = send(stringData);
string mitarbeiterListe = task.Result;
textBlock1.Text = mitarbeiterListe;
//comboBox.Items.Add()
}
public Frame globalFrame { get { return _mainFrame; } }
private void button_Click(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Datenbank));
}
public async Task<String> send(string stringData)
{
System.Net.Http.HttpClient oHttpClient = new System.Net.Http.HttpClient();
Uri uri = new Uri("*********");
oHttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("moralsKite/DesktopTestClient");
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, uri);
request.Content = new StringContent(stringData, Encoding.UTF8, "application/x-www-form-urlencoded");
var reponse = await oHttpClient.SendAsync(request);
if (reponse.IsSuccessStatusCode)
{
return "??";
//return await reponse.Content.ReadAsStringAsync();
}
return "!!";
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Einstellungen));
}
private void button2_Click_1(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Ueber));
}
}
}
You cannot run asynchronous operation and await it in constructor. In your example the task can run little longer (varying on signal and so on), the constructor of a class should be fast. Better subscribe to one of the page's events like Loaded and put your work there:
public MainPage()
{
this.InitializeComponent();
// rest of code
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// Following Peter Torr's comment - Loaded event can be fired multiple times
// for example once you navigate back to the page
this.Loaded -= MainPage_Loaded; // deregister from event if you want to run it once
stringData = "aktion=getMitarbeiterListe";
// in your send method uncomment the line:
// return await reponse.Content.ReadAsStringAsync();
// then it will return asynchronously the content as string and can be used like this:
string mitarbeiterListe = await send(stringData);
}
Events can be async then there shouldn't be problems, you may also implement an information for user that something is loading in the background.
Instead of :
var task = send(stringData);
string mitarbeiterListe = task.Result;
use
string mitarbeiterListe = await send(stringData);
Make the method where you call send data async.
I do not recommend to call gathering of data in constructor.
As title says. I want to activate this code with a button in FormsApp, but i really do not how to do it.
public static async Task<List<string>> GetProxiesFromSslProxies()
{
try
{
string rssContent = await new WebClient().DownloadStringTaskAsync("http://sslproxies24.blogspot.de/feeds/posts/default");
XDocument feed = XDocument.Parse(rssContent);
SyndicationFeed sf = SyndicationFeed.Load(feed.CreateReader());
List<string> allProxies = new List<string>();
foreach (SyndicationItem si in sf.Items)
{
var cont = (TextSyndicationContent) si.Content;
var matches = Regex.Matches(cont.Text, #"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{2,6}\b");
allProxies.AddRange(from Match match in matches select match.Value);
}
return allProxies.Distinct().ToList();
}
catch (Exception)
{
}
return null;
}
You have to add a button handler to your button. Then you invoke your method in the handler. You can attach the handler in the WinForms designer - just double click the button. Or you can add it in the constructor of your form:
public Form1()
{
InitializeComponent();
this.button1.Click += new System.EventHandler(this.button1_Click);
}
And the handler:
private async void button1_Click(object sender, EventArgs e)
{
var results = await GetProxiesFromSslProxies();
}
private async void Button_Click( ... )
{
List<string> proxies = await GetProxiesFromSslProxies();
//do something with 'proxies'.
}
I am trying to get my form to wait until a particular part of my _Load method is finished before continuing. I have a few methods that are async, but I cannot figure out why I am not able to get the code to wait until fakeClickCheckUpdate is finished before continuing. Here are the main methods involved:
public myForm(string args)
{
InitializeComponent();
Load += myForm_Load;
}
private void myForm_Load(object s, EventArgs e)
{
this.fakeClickCheckUpdate();
loadFinished = true;
if (this.needsUpdate == true)
{
Console.WriteLine("Needs update...");
}
else
{
Console.WriteLine("update is false");
}
}
public void fakeClickCheckUpdate()
{
this.checkUpdateButton.PerformClick();
}
private async void checkUpdateButton_Click(object sender, EventArgs e)
{
await startDownload(versionLink, versionSaveTo);
await checkVersion();
Console.WriteLine(needsUpdate);
}
private async Task checkVersion()
{
string currVersion;
string newVersion;
using (StreamReader sr = new StreamReader(currVersionTxt))
{
currVersion = sr.ReadToEnd();
}
using (StreamReader nr = new StreamReader(versionSaveTo))
{
newVersion = nr.ReadToEnd();
}
if (!newVersion.Equals(currVersion, StringComparison.InvariantCultureIgnoreCase))
{
this.BeginInvoke((MethodInvoker)delegate
{
progressLabel.Text = "New version available! Please select 'Force Download'";
});
this.needsUpdate = true;
}
else
{
this.BeginInvoke((MethodInvoker)delegate
{
progressLabel.Text = "Your version is up-to-date. No need to update.";
});
this.needsUpdate = false;
}
}
Basically, I want it to check the current version with checkVersion and finish that before it tries to continue past loadFinished = true inside of myForm_Load. I have checkVersion set as an async Task so that the button click can use await on it. Is there any way to get the functionality I need with this code?
First, move your code out of your perform click action.
private async void checkUpdateButton_Click(object sender, EventArgs e)
{
await CheckForUpdate();
}
private async Task CheckForUpdate()
{
await startDownload(versionLink, versionSaveTo);
await checkVersion();
Console.WriteLine(needsUpdate);
}
You can then call that same function in your OnLoad.
private async void myForm_Load(object s, EventArgs e)
{
await CheckForUpdate();
loadFinished = true;
if (this.needsUpdate == true)
{
Console.WriteLine("Needs update...");
}
else
{
Console.WriteLine("update is false");
}
}
I'm using silverlight to access a webservice to request some data. This call is asynchronous. I (think I) have to put this data in a class member after doing some operations on it, so I can access it later.
public class CardPrinter
{
// The card to be printed
private UIElement printCard;
public void PrintStaffCard(string p_persoons)
{
Debug.WriteLine(p_persoons);
foreach (string persoon in p_persoons.Split(','))
{
int p_persoon = Convert.ToInt32(persoon.Trim());
this.GetStaffData(p_persoon);
}
}
private void GetStaffData(int p_persoon)
{
PictureServiceClient proxy = new PictureServiceClient();
proxy.GetPersonelCardInfoCompleted += this.Proxy_GetPersonelCardInfoCompleted;
proxy.GetPersonelCardInfoAsync(p_persoon);
}
private void Proxy_GetPersonelCardInfoCompleted(object sender, GetPersonelCardInfoCompletedEventArgs e)
{
if (e.Error != null)
{
Debug.WriteLine(e.Error.Message);
}
else
{
this.SendStaffCardToPrinter(e.Result);
}
}
private void SendStaffCardToPrinter(CardInfo.CardInfo card)
{
Canvas canvas = new Canvas()
//Do some stuff
this.printCard = canvas;
PrintDocument pd = new PrintDocument();
pd.PrintPage += new EventHandler<PrintPageEventArgs>(this.Pd_PrintPage);
pd.Print(card.accountNr, null, true);
}
private void Pd_PrintPage(object sender, PrintPageEventArgs e)
{
e.PageVisual = this.printCard;
}
}
The problem is in the printCard variable. Sometimes it still contains the data from a previous async call in the foreach.
If I could make sure that the call in the foreach is compeletely finished there would not be a problem, but not sure how to do this and if this is the correct way to handle this.
What is the best way to handle a situation like this?
You can make the code easier to use by using TaskCompletionSource to convert the asynchronous methods from event based to task based. Then you can get rid of the variable and usage of the methods becomes much like using a synchronous method.
I haven't tested this, but it should be close to what you need. You may also find the following article useful. And also the following post Nested Asynchronous function in Silverlight
public class CardPrinter
{
public void PrintStaffCard(string p_persoons)
{
Debug.WriteLine(p_persoons);
foreach (string persoon in p_persoons.Split(','))
{
int p_persoon = Convert.ToInt32(persoon.Trim());
var cardInfo = await this.GetStaffDataAsync(p_persoon);
await this.SendStaffCardToPrinterAsync(cardInfo);
}
}
private Task<CardInfo.CardInfo> GetStaffDataAsync(int p_persoon)
{
var tcs = new TaskCompletionSource<CardInfo.CardInfo>();
PictureServiceClient proxy = new PictureServiceClient();
proxy.GetPersonelCardInfoCompleted += (s, e) =>
{
if (e.Error != null)
{
Debug.WriteLine(e.Error.Message);
tcs.SetException(e.Error);
}
else
{
tcs.SetResult(e.Result);
}
};
proxy.GetPersonelCardInfoAsync(p_persoon);
return tcs.Task;
}
private Task SendStaffCardToPrinterAsync(CardInfo.CardInfo card)
{
Canvas canvas = new Canvas();
//Do some stuff
var tcs = new TaskCompletionSource<object>();
PrintDocument pd = new PrintDocument();
pd.PrintPage += (s, e) =>
{
e.PageVisual = canvas;
tcs.SetResult(null);
};
pd.Print(card.accountNr, null, true);
return tcs.Task;
}
}
The GetPersonalCardInfoAsync method should have an overload where you can pass a UserState argument. You can pass your printCard there when you're making the call and access it later in your Proxy_GetPersonelCardInfoCompleted.
private void GetStaffData(int p_persoon, UIElement printCard)
{
PictureServiceClient proxy = new PictureServiceClient();
proxy.GetPersonelCardInfoCompleted += this.Proxy_GetPersonelCardInfoCompleted;
proxy.GetPersonelCardInfoAsync(p_persoon, printCard);
}
private void Proxy_GetPersonelCardInfoCompleted(object sender, GetPersonelCardInfoCompletedEventArgs e)
{
UIElement printCard = (UIElement)e.UserState;
// do stuff
}