views:

230

answers:

4

I've got a c# windows form app I threw together. It's fairly simple:\

inputs:

  • text string
  • source folder path
  • destination folder path
  • integer count

The app searches through text files in the source folder for the entered text string; if it finds the string then it copies that file and an image file with the same name to the destination folder. It does this however many times based on the integer input.

So I have a button, and in the button click event I call

ProcessImages(tbDID.Text, tbSource.Text, tbDest.Text, comboBoxNumberImages.SelectedItem.ToString());

which is:

private void ProcessImages(string DID, string SourceFolder, string DestFolder, string strNumImages)
        {         
            int ImageCounter = 0;
            int MaxImages = Convert.ToInt32(strNumImages);

            DirectoryInfo di = new DirectoryInfo(SourceFolder);

            foreach (FileInfo fi in di.GetFiles("*.txt"))
            {
                if (fi.OpenText().ReadToEnd().Contains(DID))
                {
                    //found one!
                    FileInfo fi2 = new FileInfo(fi.FullName.Replace(".txt", ".tif"));
                    if (fi2.Exists)
                    {
                        try
                        {
                            tbOutput.Text += "Copying " + fi2.FullName + " to " + tbDest.Text + "\r\n";
                            fi2.CopyTo(tbDest.Text + @"\" + fi2.Name, true);
                            tbOutput.Text += "Copying " + fi.FullName + " to " + tbDest.Text + "\r\n";
                            fi.CopyTo(tbDest.Text + @"\" + fi.Name, true);

                            ImageCounter++;
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message);
                        }
                    }
                }

                if (ImageCounter >= MaxImages)
                    break;

            }

        }

What happens is that the process runs fine, but I want to update a textbox on the form with progress as the files are copied. Basically the form blanks out while it's running, and after it's finished the output is in the textbox. I'd like to implement a BackgroundWorker get it to update the UI while it's running.

I've looked through the examples but am not really following them. I don't have a percentage complete value, I just want to update .Text changes each iteration and have it display. I don't even think I necessarily need to put the actual copying action in different threads, it just sounds like that needs to be run separately from the main UI thread. Maybe I'm over complicating this altogether...can someone push me in the right direction? Thanks!

A: 

The UI doesn't update because you're not allowing any window messages to be processed in your long-running file processing loop. WinForms apps redraw in response to WM_PAINT messages which are processed in the message queue in the main thread.

The simplest solution is to force a UI update: Try calling Update() on your form after modifying the textbox inside your loop.

Your app will still be UI frozen (non responsive to mouse clicks, etc) but this should at least get the progress messages drawn on the screen. If updating the display is all you really need, then stop here.

The next level of solution would be to allow your application to process pending window messages in your file processing loop. Call Application.DoEvents() in your loop (Instead of form.Update). This will allow the form to redraw itself with your text output updates and will eliminate your UI freeze - the app can respond to mouse and keyboard activity.

Be careful here, though - the user could click the button that started the current activity while the current activity is in progress - reentrancy. You should at a minimum disable the menu or button that kicks off your long-running file processing to prevent reentrancy.

A third level of solution would be to use a background thread for the file processing. This introduces a whole host of new issues you need to be aware of, and in many cases threads are overkill. There's not much point in pushing the file processing off into a background thread if you're not going to allow the user to do anything else with your app while the file processing is happening.

dthorpe
The OP is already on the right track, which is to use BackgroundWorker, which both creates the background thread (well, it uses a pool thread), and deals with some of the issue you allude to in your last paragraph.
Will Dean
The OP doesn't indicate that the user has anything else to do in the UI while file processing is taking place, therefore a background thread may be overkill IMO.
dthorpe
A: 

If you use a background worker you can use the ReportProgress method to return any integer, such as the number of records processed. It doesn't have to be a percentage. Then, in the ProgressChanged handler you can update your textbox. E.g.

int count = e.ProgressPercentage;
textBox1.Text = string.Format("{0} images processed.", count);

If you don't want to use a background worker you can call Application.DoEvents() inside your loop. This will give the UI an opportunity to refresh itself and respond to user actions. But beware - it will slow your program a lot so you may want to call it only on every 100th iteration.

Sisiutl
+1  A: 

You are on the right track with the background worker. Here is an example I put together to show you how to do this. Create a new windows app with Form1. Add 4 controls to it: label1, backgroundWorker1, button1, and button2. Then use this code-behind. Then you can use the ReportProgress userState to report back to the main thread whatever you want. In this example, I am passing a string. The ProgressChanged event handler is then on the UI thread, and updates the textbox.

    public partial class Form1 : Form
{
    int backgroundInt;
    public Form1()
    {
        InitializeComponent();
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState as string;
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        backgroundInt = 1;
        while (backgroundWorker1.CancellationPending == false)
        {
            System.Threading.Thread.Sleep(500);
            backgroundWorker1.ReportProgress(0, 
                String.Format("I found file # {0}!", backgroundInt));
            backgroundInt++;
        }
    }


    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        backgroundWorker1.CancelAsync();
    }
}
Ryan from Denver
And the significant point here in terms of the OP's original 'I don't have a percentage' complaint is that ReportProgress can take not only percentage, but *also* a general-purpose object - which can (like in this example) be a string.
Will Dean
A: 

Just create a (1) a delegate to wrap your ProcessImages method call, (2) fire off the call using a delegate, and (3) when you wish to update the textbox from your ProcessImages method, check for cross-thead operation and make sure you do the update from the main thread:

delegate void ProcessImagesDelegate(string did, string sourceFolder, string destFolder, string strNumImages);

private void ProcessImages(string DID, string SourceFolder, string DestFolder, string strNumImages)
{
    // do work

    // update textbox in form
    if (this.textBox1.InvokeRequired)
    {
        this.textBox1.Invoke(new MethodInvoker(delegate() { this.textBox1.Text = "delegate update"; }));
    }
    else
    {
        this.textBox1.Text = "regular update";
    }

    // do some more work
}

public void MyMethod()
{
    new ProcessImagesDelegate(ProcessImages).BeginInvoke(tbDID.Text, tbSource.Text, tbDest.Text, comboBoxNumberImages.SelectedItem.ToString(), null, null);
}
Jason
Why's this better than using BackgroundWorker?
Will Dean
Since they both use the thread pool and none gives me better performance over the other, I tend to like delegates better as they generally give me cleaner/simpler code (imho), plus this is just another way of accomplishing what (s)he wants
Jason