I'm trying to write a C# application that updates a progress bar as multiple files have been copied through. I am using this method to do the copying which takes a Dictionary<string, string>:
public static async Task CopyFiles(Dictionary<string, string> files, IProgress<int> progressCallback)
{
for (var x = 0; x < files.Count; x++)
{
var item = files.ElementAt(x);
var from = item.Key;
var to = item.Value;
using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
{
await inStream.CopyToAsync(outStream);
}
}
var percentComplete = ((x + 1) / files.Count) * 100;
//progressCallback((int)((x + 1) / files.Count) * 100);
progressCallback.Report(percentComplete);
}
}
Inside my main class I am using the following code to try to update my progress bar (progressBar1) but this code doesn't appear to be working, it seems to complete the ProgressBar towards the END of the copying of both files and it takes like a second to complete, not sure why this is happening.
private async void button4_Click(object sender, EventArgs e)
{
button4.Enabled = false;
var progress = new Progress<int>(percent =>
{
progressBar1.Value = percent;
});
await Copier.CopyFiles(new Dictionary<string, string> {
{ Source1, Destination1 },
{ Source2, Destination2 }
},
progress);
button4.Enabled = true;
MessageBox.Show("Completed");
}
}
The problem is caused because the integer division x + 1 / files.Count returns zero for every x except the last. You can fix this by multiplying by 100 first (100*(x+1)/files.Count) but there are better alternatives.
There's no need to calculate and report a percentage in CopyFiles. You can set the progress bar's Maximum value :
private async void button4_Click(object sender, EventArgs e)
{
button4.Enabled = false;
var files = new Dictionary<string, string> {
{ Source1, Destination1 },
{ Source2, Destination2 }
};
progressBar1.Maximum=files.Count;
progressBar1.Value =0;
var progress = new Progress<int>(x =>
{
progressBar1.Value = x+1;
});
await Copier.CopyFiles(files,progress);
button4.Enabled = true;
MessageBox.Show("Completed");
}
}
And report just the current iteration :
public static async Task CopyFiles(Dictionary<string, string> files, IProgress<int> progressCallback)
{
for (var x = 0; x < files.Count; x++)
{
...
progressCallback.Report(x);
}
}
IProgress< T> can handle complex objects, not just primitive types. It's possible to report both the iteration and the file names for display in the status bar :
record CopyProgress(int Iteration, string From, string To);
...
var progress = new Progress<int>(x =>
{
progressBar1.Value = x.Iteration;
statusText.Text = $"Copied {x.From} to {x.To}";
});
...
progressCallback.Report(new CopyProgress(x,from,to);
Related
I have a class to load some data from a file and a progress bar form to show the process. My class uses a for loop to load data with a selected buffer size and sets the progress bar value in each loop.
I want to add a cancel and pause button to my form, but when my class starts loading data, the form buttons dont work.
I tried using different threads but they can't have access to same element.
How can I make it so that buttons work when data is loading?
Note: user can select the read type so there are different methods for each type(double,int,byte)
here is my load function:
for (int count = 0; count < (FileSize / LoadBufferSize); count++)
{
if (_check_click == 2)
{
return convertedData;
}
else if(_check_click==1)
{
return new Int16[1];
}
else
{
int j = 0;
for (int i = 0; i < fileContent.Length; i++)
{
try
{
fileContent[i] = br.ReadInt16();
}
catch (EndOfStreamException)
{
loadflag = 1;
contentSize = i;
break;
}
}
for (int k = 0; k < contentSize; k += ReSampleRate)
{
try
{
convertedData[(count * fileContent.Length / ReSampleRate) + j] = fileContent[k];
j++;
}
catch (IndexOutOfRangeException)
{
MessageBox.Show("could not load the file completely");
goto lable;
}
catch
{
MessageBox.Show("something went wrong");
}
}
progress = ((count + 1) * fileContent.Length ) / (FileSize / 100);
barForm.SetBar((int)progress);
}
}
lable:
{
return convertedData;
}
This might be helpful.
public class Form1 : Form
{
private Queue<string> _items = new Queue<string>(new [] { "A", "B", "C" });
private Button _startButton = new Button() { Text = "Start", Top = 8, Left = 4, Height = 24, Width = 100 };
private Button _pauseButton = new Button() { Text = "Pause", Top = 32, Left = 4, Height = 24, Width = 100 };
private bool _paused = false;
public Form1()
{
_startButton.Click += (s, e) => this.Process();
_pauseButton.Click += (s, e) => _paused = true;
this.Controls.Add(_startButton);
this.Controls.Add(_pauseButton);
}
private void Process()
{
if (!_paused && _items.TryDequeue(out string text))
{
Console.WriteLine(text);
this.Invoke(() => this.Process());
}
}
}
The key thing here is that the private void Process() method has a recursive call to itself through the .Invoke method. This keeps running Process so long as there are items in the queue, but it also lets other events occur in the meanwhile, so if someone clicks the Pause button then the Process method will stop running.
There is no for loop. Just a repeating Process method that responds to any changes in state.
I have two methods from two different third party libraries:
Task WriteToAsync(Stream stream);
Task LoadAsync(Stream stream);
I need to pipe data from source WriteTo method to Load method.
Currently next solution is used:
using (var stream = new MemoryStream()) {
await source.WriteToAsync(stream);
stream.Position = 0;
await destination.LoadAsync(stream);
}
Is there any better way?
As the code below demonstrates, you can use pipe streams to stream data from one to the other, and you should not use await on the writer until after you have started the reader.
class Program
{
static void Main(string[] args)
{
ReaderDemo rd = new ReaderDemo();
GenPrimes(rd).ContinueWith((t) => {
if (t.IsFaulted)
Console.WriteLine(t.Exception.ToString());
else
Console.WriteLine(rd.value);
}).Wait();
}
static async Task GenPrimes(ReaderDemo rd)
{
using (var pout = new System.IO.Pipes.AnonymousPipeServerStream(System.IO.Pipes.PipeDirection.Out))
using (var pin = new System.IO.Pipes.AnonymousPipeClientStream(System.IO.Pipes.PipeDirection.In, pout.ClientSafePipeHandle))
{
var writeTask = WriterDemo.WriteTo(pout);
await rd.LoadFrom(pin);
await writeTask;
}
}
}
class ReaderDemo
{
public string value;
public Task LoadFrom(System.IO.Stream input)
{
return Task.Run(() =>
{
using (var r = new System.IO.StreamReader(input))
{
value = r.ReadToEnd();
}
});
}
}
class WriterDemo
{
public static Task WriteTo(System.IO.Stream output)
{
return Task.Run(() => {
using (var writer = new System.IO.StreamWriter(output))
{
writer.WriteLine("2");
for (int i = 3; i < 10000; i+=2)
{
int sqrt = ((int)Math.Sqrt(i)) + 1;
int factor;
for (factor = 3; factor <= sqrt; factor++)
{
if (i % factor == 0)
break;
}
if (factor > sqrt)
{
writer.WriteLine("{0}", i);
}
}
}
});
}
}
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;
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
When I execute this code in command line, it's working fine:
class Program
{
private static List<Ping> pingers = new List<Ping>();
private static List<string> value = new List<string>();
private static int instances = 0;
private static object #lock = new object();
private static int result = 0;
private static int timeOut = 2500;
private static int ttl = 7;
public static void Main()
{
string baseIP = "192.168.1.";
Console.WriteLine("Pinging destinations of D-class in {0}*", baseIP);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
SpinWait wait = new SpinWait();
int cnt =1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeOut, data, po);
cnt += 1;
}
//while (instances > 0)
//{
// wait.SpinOnce();
//}
watch.Stop();
for (int i = 0; i < value.Count; i++)
{
Console.WriteLine(value[i]);
}
DestroyPingers();
Console.WriteLine("Finished in {0}. Found {1} active IP-addresses.", watch.Elapsed.ToString(), result);
Console.ReadKey();
}
public static void Ping_completed(object s, PingCompletedEventArgs e)
{
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string sa = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(sa);
//Console.WriteLine(sa);
String diachiip = e.Reply.Address.ToString();
result += 1;
}
else
{
//Console.WriteLine(String.Concat("Non-active IP: ", e.Reply.Address.ToString()))
}
}
private static void CreatePingers(int cnt)
{
for (int i = 1; i <= cnt; i++)
{
Ping p = new Ping();
p.PingCompleted += Ping_completed;
pingers.Add(p);
}
}
private static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed;
p.Dispose();
}
pingers.Clear();
}
}
But when I convert from it to window form, it doesn't work. I don't kwow why, I have tried many different ways...
Code is here:
public partial class Form1 : Form
{
public static List<Ping> pingers = new List<Ping>();
public static List<string> value = new List<string>();
public static int instances = 0;
public static object #lock = new object();
public static int result = 0;
public int timeout = 2500;
public static int ttl = 7;
public Form1()
{
InitializeComponent();
}
public void btnscan_Click(object sender, EventArgs e)
{
string baseIP = "192.168.1.";
//int kt = Int32.Parse(txtkt.Text);
//int start = Int32.Parse(txtstart.Text);
CreatePingers(254);
PingOptions po = new PingOptions(ttl, true);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] data = enc.GetBytes("");
int cnt = 1;
Stopwatch watch = Stopwatch.StartNew();
foreach (Ping p in pingers)
{
lock (#lock)
{
instances += 1;
}
p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data, po);
cnt += 1;
}
watch.Stop();
//Result alway return 0
lst1.Items.Add(result.ToString());
lst1.Items.Add(value.Count.ToString());
for (int i = 0; i < value.Count; i++)
{
lst1.Items.Add(value[i]);
lst1.Items.Add("\n");
}
DestroyPingers();
string a = "Finished in " + watch.Elapsed.ToString() + ". Found " + result + " active IP-addresses.";
lst1.Items.Add(a);
}
public static void CreatePingers(int kt)
{
for (int start = 1; start <= kt; start++)
{
// class System.Net.NetworkInformation.Ping
Ping p = new Ping();
p.PingCompleted += Ping_completed();
pingers.Add(p);
}
}
public static PingCompletedEventHandler Ping_completed()
{
PingCompletedEventHandler a = new PingCompletedEventHandler(abc);
return a;
}
static void abc(object s, PingCompletedEventArgs e)
{
value.Add("abc");
lock (#lock)
{
instances -= 1;
}
if (e.Reply.Status == IPStatus.Success)
{
string abcd = string.Concat("Active IP: ", e.Reply.Address.ToString());
value.Add(abcd);
result += 1;
}
}
public static void DestroyPingers()
{
foreach (Ping p in pingers)
{
p.PingCompleted -= Ping_completed();
p.Dispose();
}
pingers.Clear();
}
}
What is wrong in this code?
Method SendAsync returns 0 because you are not waiting for it to complete. You are missing await and async (see msdn):
async void btnscan_Click(object sender, EventArgs e)
{
...
await p.SendAsync(string.Concat(baseIP, cnt.ToString()), timeout, data,
...
}
SpinWait was making code to work in console application. In winforms you should not use SpinWait (nor Sleep) in UI thread. You can create another thread (e.g. by using Task) and then you can copy/paste code from console application 1-to-1. But then you will need to use Invoke each time when you want to access UI controls.
async/await is really better.. if it will work (I concluded that from method name, I've no idea what method does, nor how to use it).
Perhaps I miss one thing, if SendAsync returns value, then you can get it by (the requirement to mark method where you use await with async still):
var result = await p.SendAsync(...);
Basic Goal is that i have four progress bar and want to run them at once as button is pressed and i donot have to use background worker have to do by this.
var t = new Thread(() =>
{
try
{
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
I tried and codded
for (i = 0; i < 4; i++)
{
var t = new Thread(() =>
{
for (double x = 0; x < 10000; x = x + 0.5)
{
progressVal=(int)x;
this.Invoke(new EventHandler(ProgressBar));
Thread.Sleep(2);
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void ProgressBar(object sender, EventArgs e)
{
progressBar1.Value=progressVal;
}
but cannot think of idea how to manuplate other progress bars
I would put the progress bars into an array:
var pBars = new[] { progressBar1, progressBar2, progressBar3, progressBar4 };
foreach (var pBar in pBars)
{
new Thread(currentPBar =>
{
for (double x = 0; x < 10000; x = x + 0.5)
{
var progress = (int)x;
Action<ProgressBar, int> del = UpdateProgress;
Invoke(
del,
new object[] { (ProgressBar)currentPBar, progress }
);
Thread.Sleep(2);
}
}).Start(pBar);
}
and the UpdateProgress method:
private void UpdateProgress(ProgressBar pBar, int progress)
{
pBar.Value = progress;
}
This being said, using a BackgroundWorker is far more adapted to your scenario.