I was hoping someone could point me in the right direction. I want to make a simple WPF application that has a button and a textbox. I click the button, and it starts to loop downloading a bunch of files. I can't seem to figure out how to not let the downloading stop the UI from updating. From what I can gather I'm probably going to have to use some threading code; but so far all the examples I've found and tried don't work for me. Any help or direction on where I should look and learn would be great. I can't seem to figure out how I can output those textbox.text messages around each file download.
foreach (var ticker in tickers)
{
var url = string.Format(urlPrototype, ticker, startMonth, startDay, startYear, finishMonth, finishDay, finishYear, "d");
var csvfile = directory + "\\" + ticker.ToUpper() + ".csv";
tbOutput.Text += "Starting Download of : " + ticker + "\n";
webClient.DownloadFile(url, csvfile);
tbOutput.Text += "End Download of : " + ticker + "\n";
numStocks++;
}
tbOutput.Text += "Total stocks downloaded = " + numStocks + "\n";
If you mark your method as async, you can use the DownloadFileTaskAsync method
await webClient.DownloadFileTaskAsync(url, csvfile)
If you choose to use the BackgroundWorker, it allows you to output those messages into the TextBox around each file download. Here is a crude example adapted for your requirement.
1) At the class level, create an instance of the BackgroundWorker class and add event handlers to the BackgroundWorker instance's events:
BackgroundWorker workerDownload = new BackgroundWorker();
workerDownload.WorkerReportsProgress = true;
workerDownload.DoWork += workerDownload_DoWork;
workerDownload.ProgressChanged += workerDownload_ProgressChanged;
workerDownload.RunWorkerCompleted += workerDownload_RunWorkerCompleted;
2) Create an event handler for the background worker's DoWork event:
The DoWork event handler is where you run the time-consuming operation
on the background thread. Any values that are passed to the background
operation are passed in the Argument property of the DoWorkEventArgs
object that is passed to the event handler.
private void workerDownload_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var ticker in tickers)
{
// you can pass the required info as argument:
string[] arrArg = (string[])e.Argument;
string theUrl = arrArg[0];
string directory = arrArg[1];
var url = string.Format(theUrl, ticker);
var csvfile = directory + "\\" + ticker.ToUpper() + ".csv";
// perform the download operation and report progress:
workerDownload.ReportProgress(0, "Starting Download of : " + ticker + "\n");
webClient.DownloadFile(url, csvfile);
workerDownload.ReportProgress(100, "End Download of : " + ticker + "\n");
numStocks++;
}
}
3) Create an event handler for the background worker's ProgressChanged event:
In the ProgressChanged event handler, add code to indicate the
progress, such as updating the user interface.
private void workerDownload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
tbOutput.Text += e.UserState.ToString();
}
4) Create an event handler for the RunWorkerCompleted event:
The RunWorkerCompleted event is raised when the background worker has
completed.
private void workerDownload_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
tbOutput.Text += "Total stocks downloaded = " + numStocks + "\n";
}
5) Start running the background operation by calling the RunWorkerAsync method:
int numStocks = 0;
string strDirectory = "<a_directory>";
string strUrl = string.Format(urlPrototype, startMonth, startDay, startYear, finishMonth, finishDay, finishYear, "d");
string[] args = new string[2] { strUrl, strDirectory };
workerDownload.RunWorkerAsync(args);
There are a lot ways to implement it. For example:
1) Using async/await if you programming in .Net Framework 4.5. It is simpler than BackgroundWorker
https://msdn.microsoft.com/en-us/library/hh191443.aspx
private async void button_Click(object sender, RoutedEventArgs e)
{
Uri someUrl=new Uri(#"http://dotnetperls.com");
WebClient webClient=new WebClient();
await webClient.DownloadFileTaskAsync(someUrl, csvFile);
}
2) BackgroundWorker. This class is really intended to make asynchronous operations to avoid freezing UI.
See http://www.wpf-tutorial.com/misc/multi-threading-with-the-backgroundworker
public partial class MainWindow : Window
{
BackgroundWorker bw;
public MainWindow()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.DoWork += bw_DoWok;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
}
}
void bw_RunWorkerComleted(object sender, RunWorkerCompletedEventAgs e)
{
MessageBox.Show("The result is " + e.Result.ToString());
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var ticker in tickers)
{
var url = string.Format(urlPrototype, ticker, startMonth, startDay, startYear, finishMonth, finishDay, finishYear, "d");
var csvfile = directory + "\\" + ticker.ToUpper() + ".csv";
webClient.DownloadFile(url, csvfile);
numStocks++;
}
e.Result = "End Of Download ";
}
private void button_Click(object sender, RoutedEventArgs e)
{
bw.RunWorkerAsync();
tbOutput.Text += "Starting Download of : " + ticker + "\n";
}
3) Use Thread class and update using Dispatcher class:
ThreadStart job = new ThreadStart(() =>
{
foreach (var ticker in tickers)
{
var url = string.Format(urlPrototype, ticker, startMonth, startDay, startYear, finishMonth, finishDay, finishYear, "d");
var csvfile = directory + "\\" + ticker.ToUpper() + ".csv";
webClient.DownloadFile(url, csvfile);
numStocks++;
}
Dispatcher.BeginInvoke((Action)(()=> tbOutput.Text += "End Download of : " + ticker + "\n";}));
});
Thread thread = new Thread(job);
thread.Start();
http://www.beingdeveloper.com/use-dispatcher-in-wpf-to-build-responsive-applications
Related
So every 5 seconds the myTimer2 function gets executed. I'm trying to display the date and time when the function gets executed. Unfortunately, I'm getting System.InvalidOperationException error
"Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on"
public UpdateForm2()
{
InitializeComponent();
Timer x = new Timer(5000);
x.AutoReset = true;
x.Elapsed += new System.Timers.ElapsedEventHandler(myTimer2);
x.Start();
}
public void myTimer2(object sender, System.Timers.ElapsedEventArgs e)
{
// Getting error on this line.
textBox1.Text = "The textbox has been updated on " + DateTime.Now.ToString("HH:mm:ss tt") + Environment.NewLine;
}
You can use invoke.
public UpdateForm2()
{
InitializeComponent();
Timer x = new Timer(5000);
x.AutoReset = true;
x.Elapsed += new System.Timers.ElapsedEventHandler(myTimer2);
x.Start();
}
public void myTimer2(object sender, System.Timers.ElapsedEventArgs e)
{
this.Invoke(new MethodInvoker(delegate {
textBox1.Text = "The textbox has been updated on " + DateTime.Now.ToString("HH:mm:ss tt") + Environment.NewLine;
}));
}
I am in need of a solution to trigger code when an external application is closing / closes.
I am unable to use System.Diagnostics Process.GetProcessByName to detect if the process is running since it might conflict with an anticheat system. I would need trigger the snippet of code only when the program closes and only then.
I made a good, event-based implementation.
class Monitor
{
public event EventHandler ProgramStarted;
public event EventHandler ProgramClosed;
public Monitor(string process)
{
string pol = "2";
if (!process.EndsWith(".exe")) process += ".exe";
var queryString =
"SELECT *" +
" FROM __InstanceOperationEvent " +
"WITHIN " + pol +
" WHERE TargetInstance ISA 'Win32_Process' " +
" AND TargetInstance.Name = '" + process + "'";
var s = #"\\.\root\CIMV2";
ManagementEventWatcher watcher = new ManagementEventWatcher(s, queryString);
watcher.EventArrived += new EventArrivedEventHandler(OnEventArrived);
watcher.Start();
}
private void OnEventArrived(object sender, EventArrivedEventArgs e)
{
if (e.NewEvent.ClassPath.ClassName.Contains("InstanceDeletionEvent"))
{
EventHandler handler = ProgramClosed;
handler?.Invoke(this, e);
}
else if (e.NewEvent.ClassPath.ClassName.Contains("InstanceCreationEvent"))
{
EventHandler handler = ProgramStarted;
handler?.Invoke(this, e);
}
}
}
To use it, you just create an instance of the class and set up the events. For example:
static void Main(string[] args)
{
var mon = new Monitor("chrome");
mon.ProgramClosed += Mon_ProgramClosed;
mon.ProgramStarted += Mon_ProgramStarted;
Console.ReadKey(true);
}
private static void Mon_ProgramStarted(object sender, EventArgs e)
{
MessageBox.Show("Program started.");
}
private static void Mon_ProgramClosed(object sender, EventArgs e)
{
MessageBox.Show("Program closed.");
}
Make sure to add reference to System.Drawing if you're using a console app, and ,for winforms, adjust the modifiers.
I'm working on a WPF application in Visual Studio, I need to download a large file and extract it in my code. Someone recommended that I use background workers, but now when I try to increase the value on my progress bar it doesn't work... Can anyone help?
public void InstallVersion(string version)
{
string location = File.ReadAllText(#"C:\Users\" + Environment.UserName + #"\AppData\Roaming\MidnightFallsLauncher\data\locator.txt");
location = location + #"\Versions\" + version;
if (File.Exists(location + ".zip"))
File.Delete(location + ".zip");
if (Directory.Exists(location))
{
DirectoryInfo di = new DirectoryInfo(location);
foreach (FileInfo file in di.GetFiles())
{
file.Delete();
}
foreach (DirectoryInfo dir in di.GetDirectories())
{
dir.Delete(true);
}
}
if (!myWorker.IsBusy)
{
myWorker.RunWorkerAsync();
}
}
And here is my worker code
public MainWindow()
{
InitializeComponent();
myWorker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
myWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
myWorker.ProgressChanged += new ProgressChangedEventHandler(myWorker_ProgressChanged);
myWorker.WorkerReportsProgress = true;
myWorker.WorkerSupportsCancellation = true;
}
protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
string location = File.ReadAllText(#"C:\Users\" + Environment.UserName + #"\AppData\Roaming\MidnightFallsLauncher\data\locator.txt");
location = location + #"\Versions\" + Version;
WebClient Client = new WebClient();
string url = "";
string content = "";
string downloadlink = "";
List<string> availibleVersions = new List<string>();
List<string> versionDownload = new List<string>();
url = "https://midnightfalls.glitch.me/versions.html";
content = Client.DownloadString(url);
foreach (string line in content.Split(new string[] { "<br>", "<br />" }, StringSplitOptions.None))
{
if (line.Contains("0"))
{
availibleVersions.Add(line);
}
}
url = "https://midnightfalls.glitch.me/versionslink.html";
content = Client.DownloadString(url);
foreach (string line in content.Split(new string[] { "<br>", "<br />" }, StringSplitOptions.None))
{
if (line.Contains("https"))
{
versionDownload.Add(line);
}
}
for (var i = 0; i < availibleVersions.Count; i++)
{
if (availibleVersions[i] == Version)
{
downloadlink = versionDownload[i];
}
}
Client.DownloadFile(downloadlink, location + ".zip");
ZipFile.ExtractToDirectory(location + ".zip", location);
File.Delete(location + ".zip");
RunGame(Version);
}
protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
Also, while my worker is running, the UI freezes... I'm pretty sure that's not meant to happen.
EDIT:
The UI is now updating but the progress bar still doesnt work...
It looks like the problem is that the main thread is constantly working while the background worker is doing its work:
while (this.myWorker.IsBusy)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
means that your main thread is constantly doing stuff while the background job is working, which is why the UI doesn't update.
You need to move the progress update to the background worker (where you could also set a value that actually makes some sense, e.g. to indicate how many of the 'availableVersion's you have downloaded).
Hope that makes sense.
EDIT:
Suppose we'll put all the code directly in the view, so assume we have a progress bar named 'progressBar' and a button named 'btnStart' (which kicks of the backgroundworker).
Here's the codebehind:
private BackgroundWorker worker;
public MainWindow()
{
InitializeComponent();
this.worker = new BackgroundWorker();
this.worker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
this.worker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
this.worker.ProgressChanged += new
ProgressChangedEventHandler(myWorker_ProgressChanged);
this.worker.WorkerReportsProgress = true;
this.worker.WorkerSupportsCancellation = true;
}
private void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
private void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Whatever you need to do when finished here (alert, update a label, etc.)
}
private void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Just loop and report new progress. Sleep a little in between each
// progress update so that it isn't over before we have a chance to see it.
for(int i=0;i<100;i++)
{
Thread.Sleep(200);
this.worker.ReportProgress(i);
}
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
this.worker.RunWorkerAsync();
}
What happens is that the background worker fires an event notifying that progress has changed.
The main thread has a handle on that event, and updates the progress bar.
Since it's a background worker you don't need to use Dispatcher.Invoke - this is already taken care of.
Hope the example clarifies things for you.
I'm pretty sure you're blocking right here:
while (this.myWorker.IsBusy)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
You should be calling ReportProgress on your BackgroundWorker instance within your myWorker_DoWork method.
Also, if you're using .NET 4.5 or later, you can dump the BackgroundWorker entirely and rewrite this code using the async/await pattern.
I have a button click event where I make once some Lists and start the backgroundworker1:
private void btnDownload_Click(object sender, EventArgs e)
{
btnDownload.Enabled = false;
label7.Text = "Downloading...";
ei.Init();
if (countryList.Count() == 0)
{
foreach (ExtractImages.Continent continent in ei.world.continents)
{
foreach (ExtractImages.Country country in continent.countries)
{
if (country.name == "Israel")
{
foreach (string imageUri in country.imageUrls)
{
countryList.Add(imageUri);
}
}
else
{
foreach (string imageUri in country.imageUrls)
{
newList.Add(imageUri);
}
}
}
}
}
backgroundWorker1.RunWorkerAsync();
}
In the dowork event:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (downloaded == false)
{
getTotalBytes(countryList);
CreateCountryDateTimeDirectories(countryList);
downloadFile(countryList);
}
else
{
getTotalBytes(newList);
CreateCountryDateTimeDirectories(newList);
downloadFile(newList);
}
}
In the dowork event I have two phases first it will make calculations and will download the links in the List countryList.
What I want to do is once it finish to download all the link in countryLinks start backgroundworker1 over again and this time download the links in the List newList.
This is how l'm downloading the links in the List.
private Queue<string> _downloadUrls = new Queue<string>();
private int urlCount = 0; // keep track of how many urls are processed
private async void downloadFile(IEnumerable<string> urls)
{
urlCount = 0;
foreach (var url in urls)
{
_downloadUrls.Enqueue(url);
urlCount++;
}
// urlCount is now set
await DownloadFile();
}
private async Task DownloadFile()
{
if (_downloadUrls.Any())
{
WebClient client = new WebClient();
client.DownloadProgressChanged += ProgressChanged;
client.DownloadFileCompleted += Completed;
var url = _downloadUrls.Dequeue();
sw = Stopwatch.StartNew();
if (url.Contains("true"))
{
await client.DownloadFileTaskAsync(new Uri(url), #"c:\temp\TempSatFiles\" + urlCount + "Infrared.jpg");
//await client.DownloadFileTaskAsync(new Uri(url), countriesMainPath + "\\" + currentDownloadCountry + "\\" + urlCount + "Infrared.jpg");
}
else
{
await client.DownloadFileTaskAsync(new Uri(url), #"c:\temp\TempSatFiles\" + urlCount + "Invisible.jpg");
//await client.DownloadFileTaskAsync(new Uri(url), countriesMainPath + "\\" + currentDownloadCountry + "\\" + urlCount + "Invisible.jpg");
}
return;
}
}
double percentageTotalDownload = 0;
double totalBytesDownloaded = 0;
private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
// Calculate download speed and output it to labelSpeed.
label3.Text = string.Format("{0} kb/s", (e.BytesReceived / 1024d / sw.Elapsed.TotalSeconds).ToString("0.00"));
// Update the progressbar percentage only when the value is not the same.
double bytesInCurrentDownload = (double)e.BytesReceived;
double totalBytesCurrentDownload = double.Parse(e.TotalBytesToReceive.ToString());
double percentageCurrentDownload = bytesInCurrentDownload / totalBytesCurrentDownload * 100;
ProgressBar1.Value = int.Parse(Math.Truncate(percentageCurrentDownload).ToString());//e.ProgressPercentage;
// Show the percentage on our label.
Label4.Text = e.ProgressPercentage.ToString() + "%";
// Update the label with how much data have been downloaded so far and the total size of the file we are currently downloading
label10.Text = string.Format("{0} MB's / {1} MB's",
(e.BytesReceived / 1024d / 1024d).ToString("0.00"),
(e.TotalBytesToReceive / 1024d / 1024d).ToString("0.00"));
//Let's update ProgressBar2
totalBytesDownloaded = e.BytesReceived + bytesFromCompletedFiles;
percentageTotalDownload = totalBytesDownloaded / totalBytesToDownload * 100;
progressBar2.Value = (int)percentageTotalDownload;
label6.Text = progressBar2.Value.ToString() + "%";
}
long bytesFromCompletedFiles = 0;
private async void Completed(object sender, AsyncCompletedEventArgs e)
{
var cnt = System.Threading.Interlocked.Decrement(ref urlCount);
if (cnt > 0)
{
await DownloadFile();
label9.Text = urlCount.ToString();
}
else
{
label7.Text = "Download completed";
downloaded = true;
btnDownload.Enabled = true;
sw.Stop();
}
}
Now it will download the links in countryList.
When it finish download all the files in the List it will get to the else part:
label7.Text = "Download completed";
downloaded = true;
btnDownload.Enabled = true;
sw.Stop();
Here i want to restart the backgroundworker1 and this time in the dowork event it will download the links in the newList.
The problem is how do I know that the backgroundworker1 is not busy ? There might be a situation that it downloaded the all the files and the backgroundworker1 is still busy ?
Or maybe I should start the backgroundworker in the completed event of the backgroundworker ? If it finished all the downloads it will then get to the backgroundworker completed event ? or first it will get to the webclient completed event ?
You can check if the worker is busy or not by using...
if(!backgroundworker1 .IsBusy)
backgroundworker1 .RunWorkerAsync();
No, if you've properly checked when it finishes then the worker will finish on when the downloading completes, it can be busy when it will wait for response and the response doesn't come soon.
Yes, when worker completes it work, it is recommended(from my side) to start it again to perform some other tasks.
Yes whenever it will finish, the completed event will be fired.
the event of webclient is inside the backgroundworker event, webclient completed event will be first. You've to debug your code first to see the sequential flow of your code.
I have a program that gets the app name, place them on a listbox and sends them to the other window if you click the send button.
What I wanted to know is, is it possible for it to automatically send every 10 seconds after a single click on the send button? If yes, how can I possibly do that?
There's the codes, in case if it brings of any help.
private void cmd_send_Click_1(object sender, EventArgs e)
{
String processID = "";
String processName = "";
String processFileName = "";
String processPath = "";
string hostName = System.Net.Dns.GetHostName();
listBox1.BeginUpdate();
try
{
for (int i = 0; i < listBox1.Items.Count; i++)
{
piis = GetAllProcessInfos();
try
{
// String pno = textBox4.Text.ToString();
// String path = textBox5.Text.ToString();
// String name = textBox6.Text.ToString();
// String user = textBox7.Text.ToString();
// output.Text += "\n Sent data : " + pno + " " + user + " " + name + " " + path ;
processID = piis[i].Id.ToString();
processName = piis[i].Name.ToString();
processFileName = piis[i].FileName.ToString();
processPath = piis[i].Path.ToString();
output.Text += "\n\nSENT DATA : \n\t" + processID + "\n\t" + processName + "\n\t" + processFileName + "\n\t" + processPath + "\n";
}
catch (Exception ex)
{
wait.Abort();
output.Text += "Error..... " + ex.StackTrace;
}
NetworkStream ns = tcpclnt.GetStream();
String data = "";
//data = "--++" + " " + textBox4.Text + " " + textBox5.Text + " " + textBox6.Text + " " + textBox7.Text;
data = "--++" + " " + processID + " " + processPath + " " + processFileName + " " + hostName;
if (ns.CanWrite)
{
byte[] bf = new ASCIIEncoding().GetBytes(data);
ns.Write(bf, 0, bf.Length);
ns.Flush();
}
}
}
finally
{
listBox1.EndUpdate();
}
}
Any help would be greatly appreciated.
You could place your code inside a single method, call that method initially on button click and start/stop your timer depending on it's current state.
private Timer _timer;
public Form() // Initialize timer in your form constructor
{
InitializeComponent();
_timer = new Timer();
_timer.Interval = 10000; // miliseconds
_timer.Tick += _timer_Tick; // Subscribe timer to it's tick event
}
private void _timer_Tick(object sender, EventArgs e)
{
SendData();
}
private void cmd_send_Click_1(object sender, EventArgs e)
{
if (!_timer.Enabled) // If timer is not running send data and start refresh interval
{
SendData();
_timer.Enabled = true;
}
else // Stop timer to prevent further refreshing
{
_timer.Enabled = false;
}
}
private void SendData()
{
// Your code here
}
EDIT:
If you're using .NET framework 4.5 or above you can do the same thing in using async/await.
private bool keepRefreshing;
private async void cmd_send_Click_1(object sender, EventArgs e)
{
if (keepRefreshing)
{
keepRefreshing = false;
return;
}
keepRefreshing = true;
while (keepRefreshing)
{
// Your code here
await Task.Delay(10000);
}
}
On button click it will send data and it will keep sending with delay of 10 seconds. When you press the button second time it will stop refreshing interval, third time it will start again and so on..
// Declare a timer
Timer tmr = new Timer();
tmr.Interval = 10000; // 10 second
tmr.Tick += timerHandler; // We'll write it in a bit
tmr.Start(); // The countdown is launched!
private void timerHandler(object sender, EventArgs e) {
// Here the code what you need each 10 seconds
tmr.Stop(); // Manually stop timer, or let run indefinitely
}
Their are many ways one is follow.
private void cmd_send_Click_1(object sender, EventArgs e)
{
bool isResend=true;
while (isResend==true)
{
// Put all your here
System.Threading.Thread.Sleep(10000);
}
}
Other ways are using Timer, etc...
Everyone's answer is cool, but as for me if you really need that "click" as start, i'll do it this way.
Initiate events for timer & background worker inside form load.
set timer.start(); inside click.
Once ticking, if backgroundworker is not busy, execute background worker.
Ensure that you don't directly set label1.text = "send some works here." inside the background worker, it will cause error.
Hope this helps.