One part of my application works loading of Images from the predefined folder. At this time when loading the images it takes more time. Now I figured out that the progress Bar which can let me to tell the progress of loading.
The Problem I faced is:
I can't able to Integrate the BackgroudWorker, Progress Bar with my function.
For Instance the following is the Background worker and Progress bar code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Load file list here
int totalImageCount = 10000;
// Set maximum value of the progress bar
progressBar1.Invoke(new Action(() => { progressBar1.Maximum = totalImageCount; }));
for (int i = 0; i < totalImageCount; i++)
{
// Load a single image here
Thread.Sleep(10);
// User cancelled loading (form shut down)
if (e.Cancel) return;
// Set the progress
progressBar1.Invoke(new Action(() => { progressBar1.Value = i; }));
}
// Cleanup here
}
// Starts the loading
private void button1_Click(object sender, EventArgs e)
{
// Start loading images
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.RunWorkerAsync();
}
// Stops the loading
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// Stop the loading when the user closes the form
if (backgroundWorker1.IsBusy) backgroundWorker1.CancelAsync();
}
The following is The function which needs to be for Progress Bar
private void LoadImages()
{
string imagesPath = (Application.StartupPath + "/UnknownFaces/");
string[] extensions = new[] { ".jpg", ".jpeg", ".png" };
var allfiles = Directory.GetFiles(imagesPath);
this.imageList1.ImageSize = new Size(256, 250);
this.imageList1.ColorDepth = ColorDepth.Depth32Bit;
foreach (FileInfo fileInfo in filesSorted)
{
try
{
this.imageList1.Images.Add(fileInfo.Name,
Image.FromFile(fileInfo.FullName));
}
catch
{
Console.WriteLine(fileInfo.FullName + " is is not a valid image.");
}
}
this.lstView_un.View = View.LargeIcon;
lstView_un.LargeImageList = this.imageList1;
lstView_un.Items.Clear();
for (int j = 0; j < this.imageList1.Images.Count; j++)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = j;
item.Text = imageList1.Images.Keys[j].ToString();
this.lstView_un.Items.Add(item);
}
}
catch (Exception ex)
{
MessageBox.Show("Something Wrong happen! "+ex.Message);
}
}
I think the main routine works are there:
this.lstView_un.View = View.LargeIcon;
lstView_un.LargeImageList = this.imageList1;
lstView_un.Items.Clear();
for (int j = 0; j < this.imageList1.Images.Count; j++)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = j;
item.Text = imageList1.Images.Keys[j].ToString();
this.lstView_un.Items.Add(item);
}
The slow part of your code is actually the loop that reads the files, not the loop that populates your ListView.
The best way to report or otherwise present progress state in the UI from BackgroundWorker is to use the ProgressChanged event. However, the code example you are working from will work as well. That is, just update the ProgressBar object directly from your worker code. It fails to take full advantage of the features that BackgroundWorker provides (and indeed, raises the question of why bother with BackgroundWorker if you're not going to use its features), but it will work.
For example:
var allfiles = Directory.GetFiles(imagesPath);
this.imageList1.ImageSize = new Size(256, 250);
this.imageList1.ColorDepth = ColorDepth.Depth32Bit;
// Set the maximum value based on the number of files you get
progressBar1.Invoke((MethodInvoker)(() => { progressBar1.Maximum = filesSorted.Count(); }));
foreach (FileInfo fileInfo in filesSorted)
{
try
{
this.imageList1.Images.Add(fileInfo.Name,
Image.FromFile(fileInfo.FullName));
}
catch
{
Console.WriteLine(fileInfo.FullName + " is is not a valid image.");
}
// Update the ProgressBar, incrementing by 1 for each iteration of the loop
progressBar1.Invoke((MethodInvoker)(() => progressBar1.Increment(1)));
}
Note: your code example is incomplete, and doesn't make sense even as a snippet. One problem in particular is that you retrieve the file names into the array allfiles, but you are iterating on a completely different collection, filesSorted. I did my best with the code you provided to work with, but since the code you posted wouldn't work as-is, you may well have to make some minor adjustments to the example I've provided to get it to do what you want.
If you are unable to figure this out with the above, please provide a good, minimal, complete code example that reliably illustrates your scenario and what precisely you're having trouble figuring out.
Related
In my application i have two Forms (that's my 1st quite big app)
After clicking start button in parent form i want loading panel to appear, and some logic to be done.
Loading panel (it is just another widowss form) contains bunifu loading circle animation (and some text).
Logic part is responsible for collecting names from directory tree, then replacing some text in Ms.Word files on the tree.
When i open loading panel without executing the logic, loading panel is animated properly and everything works fine.
private void bunifuFlatButton1_Click(object sender, EventArgs e)
{
int x = this.Location.X+this.Width/2-75;
int y = this.Location.Y +this.Height/2-175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show();
//System.Threading.Thread.Sleep(5000); \\this doesn't help animation to start
if (FormLogic._dataList.Count > 0) \\Here Logic part starts
{
for (int i = 0; i < FormLogic._dataList.Count; i++)
GetDir.GetTarget(FormLogic._dataList[i]);
/*foreach (var directory in FormLogic._dataList)
GetDir.GetTarget(directory);*/
LogList.Items.Add(DateTime.Now + "List isn't empty");// for testing
FormLogic.ClearData();
}
LP.Close();
}
After enabling logic loading panel appears (appearance isn't smooth), but animation doesn't work (it starts to work only when logic part did the work - i tested it by disabling LP.Close(). What can be reason of this problem ?
Additinal question. In .NET environment the code is compiled to work with multiple processor threads or i have to do it manually ?
EDIT 06/08/2018 7:21 CEST
I cant access LogList from GetDir Method (due to processing it by other thread).
I tried multiple Invoke construction, but none of it seemed to work ;/
I am just too rookie to figure it out. I specified more details in code below:
namespace Docr
{
public partial class DocrForm : Form
{
.....
private async void Button1_Click(object sender, EventArgs e)
{
int x = this.Location.X + this.Width / 2 - 75;
int y = this.Location.Y + this.Height / 2 - 175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show(); //animation
int count = FormLogic._dataList.Count;
var list = FormLogic._dataList;
await Task.Run(() =>// processing logic during showing animation
{
if (count > 0)
{
for (int i = 0; i < count; i++)
{
GetDir.GetTarget(list[i],LogList); // Passing LogList as and argument
}
Invoke((Action)(() => { LogList.Items.Add(DateTime.Now + "Hi LogList"); }));\\ works fine
}
});
FormLogic.ClearData();
LP.Close();
}
....
}
namespace DocrLogic
{
class GetDir
{
.....
static public void GetTarget(string UserDirectory, ListBox List)// passing ListBox as an Argument
{
var path = UserDirectory;
var TargetDir = new DirectoryInfo(path);
var AllDocs1 = TargetDir.GetFiles("*.doc*", SearchOption.AllDirectories);
var ProperPrefixes = new List<string> { };
Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); })); // THIS DOESN'T WORK
....
}
.....
}
}
You need to make the method async, then use await to wait for the logic to complete. This way the LP form won't be interrupted by the heavy logic.
private async void bunifuFlatButton1_Click(object sender, EventArgs e)
{
int x = this.Location.X+this.Width/2-75;
int y = this.Location.Y +this.Height/2-175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show();
int count = FormLogic._dataList.Count;
var list = FormLogic._dataList;
await Task.Run(()=>
{
if(count > 0)
{
for (int i = 0; i < count; i++)
{
GetDir.GetTarget(list[i]);
}
this.Invoke(() => { LogList.Items.Add(DateTime.Now + "List isn't empty"); });
}
});
FormLogic.ClearData();
LP.Close();
}
You have to make your code thread-safe by using Invoke to access not-thread-safe objects such as UI objects, otherwise, it will throw a System.Threading.ThreadAbortException or a System.InvalidOperationException.
Invoke syntax may differ based on your project but you may
see this post to understand the proper ways of using Invoke()
update
You must never try to access a UI object outside invoke. invoke is provided by System.Windows.Forms.Control and depends on the thread which originally created that control. therefore having Invoke on some other random class simply does not work.
In the second part, you need to change
public static void GetTarget(string UserDirectory, ListBox List)// passing ListBox as an Argument
{
...
Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); })); // THIS DOESN'T WORK
}
to
(you need to send the whole invoke line as the action parameter)
public static void GetTarget(string UserDirectory, Action action)// passing the action as an Argument
{
...
action();
}
or
(you need to set Dispatcher to LogList before starting the Task)
public static Control Dispather;
public static void GetTarget(string UserDirectory)// passing the action as an Argument
{
...
Dispather.Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); }));
}
I have the code below to copy some folders over to a different location if the user has the checkbox checked for that folder.
I have a backgroundworker and a progresbar. I see that people on this site and even on MSDN gives the same example to update the progressbar with
for (int i = 0; i <= 100; i++)
{
// Report progress to 'UI' thread
backgroundWorker1.ReportProgress(i);
// Simulate long task
System.Threading.Thread.Sleep(100);
}
This is all fine and I get the idea on how it works. But what I can't figure out is to implement my checkboxes and copy the folder if it's checked and then update the progress bar depending on how many checkboxes I have. I count the checked boxes and assigned it to prgbarmax.
This is what I have so far:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < prgbarmax; i++)
{
int step = (i / prgbarmax) * 100;
if (test1)
{
//do the copy here
backgroundWorker1.ReportProgress(i);
}
if (tes2)
{
//do the copy here
backgroundWorker1.ReportProgress(i);
}
if (test3)
{
//do the copy here
backgroundWorker1.ReportProgress(i);
}
.... so on
}
}
You need to construct a list of folders to be copied, and copy only one folder at each iteration of the for loop. The problem of your code is you tried to copy all the folders in one iteration.
To illustrate the idea in code sample.
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
//construct the list of folder to be copied
List<DirectoryInfo> listOfFolders = new List<DirectoryInfo>();
if (test1)
listOfFolders.Add(folder1);
if (test2)
listOfFolders.Add(folder2);
if (test3)
listOfFolders.Add(folder3);
//begin to copy
for (int i = 0; i < listOfFolders.Count; i++)
{
listOfFolders[i].Copy(...); //copy only one folder in the list
int step = ((i + 1) / listOfFolders.Count) * 100; //calculate the progress
backgroundWorker1.ReportProgress(step);
}
}
First of all, My code is written in Windows Form Application - C#.
I need to execute a method (which is very modular and it's runtime is depends on how much physical memory you have used in your system), and while this method is running, I want to present to the user a progressbar. I don't know how to sync the progressbar, with the function's runtime.
EDIT: HERE IS MY CODE:
public SystemProp()
{
// Getting information about the volumes in the system.
this.volumes = getVolumes();
for (int i = 0; i <volumes.Length; i++)
{
// Create a txt file for each volume.
if (!System.IO.File.Exists(dirPath + volumes[i].Name.Remove(1) + #".txt"))
{
using (FileStream fs = File.Create(dirPath + volumes[i].Name.Remove(1) + #".txt"))
{
}
}
// Treescan function for each Volume.
TreeScan(volumes[i].Name);
}
}
private bool isSafe()
{ return true; }
private DriveInfo[] getVolumes()
{
DriveInfo[] drives = DriveInfo.GetDrives();
return drives;
}
private void TreeScan(string sDir)
{
try
{
foreach (string f in Directory.GetFiles(sDir))
{
using (FileStream aFile = new FileStream(dirPath + sDir.Remove(1) + #".txt", FileMode.Append, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(aFile)) { sw.WriteLine(f); }
}
foreach (string d in Directory.GetDirectories(sDir))
{
TreeScan(d);
}
}
catch (Exception)
{ }
}
The function is the treescan.
I would appriciate any kind of help,
Thank You Very Much!!
You should calculate progress and set ProgressBar.Value inside the method.
For example you have a for loop from 1 to 100.
for (int i = 0; i < 100; i ++)
{
//...
progressBar.Value = i;
}
You can also set a maximum value of progress using Maximum property.
So for a for loop from 1 to 10 you can set Maximum to 10 and don't calculate a progress.
progressBar.Maximum = 10;
for (int i = 0; i < 10; i ++)
{
//...
progressBar.Value = i;
}
If you can't split you method in different stages where you can change progress value, you can create a timer that ticks every one second and change the progress value in Tick event handler.
In order to set progress value based on runtime you can use Stopwatch.
Timer and Stopwatch should be started in the beginning of the method.
Timer timer = new Timer();
Stopwatch stopwatch = new Stopwatch();
void Method()
{
timer.Start();
stopwatch.Start();
//...
}
private void Timer_Tick(object sender, EventArgs e)
{
var progress = CalculateProgress (); // calculate progress
progressBar.Value = progress;
// or
progressBar.Value = stopwatch.Elapsed.Seconds;
}
well i am new to C#, and implementing a code, in which i have two buttons, with one acting as starting of data acquisition and storing it in a csv file and other button to stop it.
well codes for all these are as follows:
//button for start DAQ
private void stdaq_Click(object sender, EventArgs e)
{
stopped = false;
process();
}
//button for stoping DAQ
private void spdaq_Click(object sender, EventArgs e)
{
stopped = true;
}
// process function
private process()
{
int iAvail = 0;
int iRead = 0;
string filename = #textBox3.Text;// taking csv file name from user
// jit:
//a function calculating the total number of values and storing it in iAvail
int[] iRawData = new Int32[iAvail];
double[] dScaledData = new Double[iAvail];
//a function transferring the data from buffer and storing it in dscaledData array
List<double> data = new List<double>();
for (int i = 0; i < iAvail; i++)
{
data.Add(dScaledData[i]);
}
Task myFirstTask = Task.Factory.StartNew(()
=>
{
while (stopped == false)
{
Write(data.ToArray(), filename);
// goto jit;
}
});
}
// csv creater and data writer
public static void Write(double[] data, string outputPath)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.GetLength(0); i++)
{
if (stopped) break;
sb.AppendLine(string.Join(",", data[i]));
}
if (File.Exists(outputPath))
{
File.AppendAllText(outputPath, sb.ToString());
}
else
{
File.WriteAllText(outputPath, sb.ToString());
}
}
this is what i am implementing, and the problem with this code is that when the data is first transferred and written to the file, then again the same data is written again and again irrespective of new data and i tried implementing that Goto statement(can be seen in comments) but it is giving error - " Control cannot leave the body of an anonymous method or lambda expression ", and if i don't use the While loop the data is not written at all.
So i want to call my process function and to transfer data to csv starting on press of a start button, take fresh data everytime and write it to csv or can say call the process method again from it's start point and to stop it on click of the stop button, but i am unable to do it irrespective of various tries with different loops and some threading functions also.
please help with this.
Assuming you only need to Write once, you should remove this or change it from while to if:
while (stopped == false)
The loop will cause Write to be called infinitely until stopped becomes true.
Also, you might want to change Write to return rather than break if stopped is true, so that you don't write anything if you are supposed to be stopping:
if (stopped) break;
to
if (stopped) return;
If you want to generate data again and really do want to loop forever, just move that code into the loop:
Task myFirstTask = Task.Factory.StartNew(()
=>
{
while (stopped == false)
{
List<double> data = new List<double>();
// TODO: Generate data here - move all relevant code here
Write(data.ToArray(), filename);
}
});
I think this is a job for the BackgroundWorker.
This code will start you up:
public partial class Form1 : Form
{
int loopCounter = 0; // variable just used for illustration
private static BackgroundWorker bw = new BackgroundWorker(); // The worker object
// This function does your task
public void doSomeStuff(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 1000; i++)
{
loopCounter = i; // Pass the loop count to this variable just to report later how far the loop was when the worker got cancelled.
Thread.Sleep(100); // Slow down the loop
// During your loop check if the user wants to cancel
if (bw.CancellationPending)
{
e.Cancel = true;
return; // quit loop
}
}
}
// This button starts your task when pressed
private void button1_Click(object sender, EventArgs e)
{
bw.WorkerSupportsCancellation = true; // Set the worker to support cancellation
bw.DoWork += doSomeStuff; // initialize the event
if (!bw.IsBusy) // Only proceed to start the worker if it is not already running.
{
bw.RunWorkerAsync(); // Start the worker
}
}
// This button stops your task when pressed
private void button2_Click(object sender, EventArgs e)
{
// Request cancellation
bw.CancelAsync();
textBox1.Text = "The bw was cancelled when 'loopCounter' was at: " + loopCounter.ToString();
}
}
I am working on a WinForm project where I have a label in a for loop. I want to show the label each time after executing the label.text statement. But it doesn't show for every time, rather it shows after for loop is finished.
I tried to achieve this by using Thread.Sleep(). But I can't. Please help me.
NOTE :- lblProgress is a Label
Here's my coding.
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Thread.Sleep(10000);
}
Whenever you create a WinForm application, it is spun up into a new process and a new thread is created. Any updates to the User Interface are all done on the same thread as your process. This means when your application is doing "busy work", your UI will be blocked because they are on the same thread. What this means is that, in order to achieve what it is you're trying to achieve, you have to do a little extra work.
First step we need to do is create a function for your work routine (we could use an anonymous function, but since you are new to C#, I think it'll be easier to understand if we break it out), like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
Next, we need to create a new thread that executes our DoWork() function. Its unclear what the "trigger" is for doing your work, but I'm going to assume its a button click:
private void button1_click(object sender, EventArgs e)
{
var work = new Thread(DoWork);
work.Start();
}
So now, whenever someone click the button, we will start a new thread that executes our DoWork function in that thread. The new thread spawns, then execution is immediate returned and our GUI will now update in real time as our thread is executing in the background.
But wait! We still have one more problem to take care of. The problem is that Window's form controls are not thread safe and if we try to update a control from another thread, other then the GUI's thread, we will get a cross-thread operation error. The key to fixing this is to use InvokeRequired and Invoke.
First, we need to make another function that does just the label update:
private void SetProgressLabel(int progress)
{
lblProgress.Text = "Hello World" + progress;
}
In your form class, we also need to create a new delegate:
public partial class Form1 : Form
{
private delegate void ProgressCallback(int progress);
// ..
// The rest of your code
// ..
}
Finally, change your DoWork() method to something like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
if (lblProgress.InvokeRequired)
{
lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i });
}
else
{
SetProgressLabel(i);
}
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
This uses the label's (derived from Control) InvokeRequired property to determine if an Invoke is required. It returns true or false. If its false, we can just call our SetProgressLabel() function like we'd normally do. If its true, we must use Invoke to call our function instead.
Congratulations! You just made your first thread safe application.
Now, just as an aside note, you are not properly releasing and disposing of your objects. I recommend you change your DoWork() code to something like this:
private void DoWork()
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout = sourceTable.Rows[i].Field<string>(0);
using (dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString))
{
dest.Open();
using (destcmd = new SqlCommand(checkout, dest))
{
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
if (lblProgress.InvokeRequired)
{
lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i });
}
else
{
SetProgressLabel(i);
}
Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second)
}
}
}
}
Because I wrapped your IDisposable's into using blocks, the resources will automatically be disposed of once it goes out of scope.
Although threading would be the more ideal solution another solution is:
Application.DoEvents()
this will give the UI thread time to update.
Example
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout= sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
lblProgress.Text = "Hello World"+i;
Application.DoEvents();
}
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{
string checkout;
checkout = sourceTable.Rows[i].Field<string>(0);
dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString);
dest.Open();
destcmd = new SqlCommand(checkout, dest);
destcmd.ExecuteNonQuery();
dest.Close();
prcmail();
prcmessagecheck();
var task = Task.Factory.StartNew(() =>
{
//Thread.Sleep(1000);
lblProgress.Text = "Hello World" + i;
}, CancellationToken.None, TaskCreationOptions.None, ui);
task.Wait();
}
});
If you are executing the mentioned code on the UI thread, UI will be refreshed only after entire for loop is executed. Based on your needs, progress bar/background worker kind of set up looks suitable.