views:

439

answers:

3

I'd like to download a picture and afterwards show it in a picturebox.

at first I did like this:

WebClient client = new WebClient();
client.DownloadFile(url, localFile);
pictureBox2.Picture = localFile;

But that wasn't perfect because for the time while the download is performed the app is kinda freezing.

Then I changed to this:

public class ParamForDownload
{
    public string Url { get; set; }
    public string LocalFile { get; set; }
}
ParamForDownload param = new ParamForDownload()
        {
            Url = url,
            LocalFile = localFile
        };

      ThreadStart starter = delegate { DownloadMap (param); };
        new Thread(starter).Start();

        pictureBox2.Picture = localFile;



    private static void DownloadMap(ParamForDownload p) 
    {
        WebClient client = new WebClient();
       client.DownloadFile(p.Url, p.LocalFile);
    }

But now I have to do something like a "wait for thread ending" because the file is accessed in the thread and to same time there's downloaded something to the file by the DownloadMap method.

What would be the best wait to solve that problem?

+11  A: 

Basically, What was happening originally, was the UI Thread was completing the download, and because it was working away on that, it couldn't be refreshed or painted (working synchronously). Now what is happening is that you're starting the thread then the UI thread is continuing, then trying to assign the local file (which hasn't finished downloading) to the picture box. You should try either of the following:

You should use a background worker to complete your download task.

It has events that will be very handy. DoWork, where you can start the download.

There is also a RunWorkerCompleted event that is fired when the Work has completed. Where you can set the image there (pictureBox2.Picture = localFile;).

It's definitely worth checking out, I think it's the most appropriate way to complete what you are trying to achieve.

Or

If you want to stick with using a Thread. You could take out the Image assignment after you've done the Thread.Start(), and put this in to your Worker Thread function:

private delegate void MyFunctionCaller();

private static void DownloadMap(ParamForDownload p) 
{
    WebClient client = new WebClient();
   client.DownloadFile(p.Url, p.LocalFile);
    DownloadMapComplete(p);
}

private void DownloadMapComplete(ParamForDownload p)
{
if (InvokeRequired == true)
  {
  MyFunctionCaller InvokeCall = delegate { DownloadMapComplete(p); };
  Invoke(InvokeCall);
  }
else
  {
  pictureBox2.Picture = p.LocalFile;
  }
}
ThePower
The BackgroundWorker is definitely the thing you're searching. I just used it and it's so easy to use. Just add something to the DoWork event, put the result into e.Result and use this value in the RunWorkerCompleted event. Or if your long running functions are more complex, call them within do work by ((MethodInvoker)e.Argument).Invoke(); and update your GUI elements by BeginInvoke within your long running function. To determine from the GUI thread if there is something going on you can check backgroundWorker.IsBusy;
Oliver
For further informations take a look at: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Oliver
@Oliver: true, they're pretty developer friendly, they do a lot of our job for us.
ThePower
+1  A: 

When the user initiates the process what you need to do is:

1) Update the UI to indicate something is happening. IE: Disable all the fields and put up a message saying "I am downloading the file, please wait...". Preferentially with some kind of progress indicator (sorry I am not sure if the WebClient supports progress etc but you need to update the UI as the download make take a while).

2) Attach an event handler to the WebClient's 'DownloadFileCompleted'.

3) Use the WebClient's DownloadFileAsync method to start the download on another thread. You don't need to spin threads up yourself.

4) When the 'DownloadFileCompleted' event is fired route the call back to the UI thread by using the form's invoke method. THIS IS VERY IMPORTANT.

See: http://weblogs.asp.net/justin%5Frogers/pages/126345.aspx

and: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

5) Once the event has been routed back onto the UI thread open the file and update the UI as required (IE: Re-enable fields etc).

Leather.

Leather
Yes, the WebClient has a DownloadProgressChanged, and DownloadCompleted events
Jason Miesionczek
A: 

Not related to the threading issue, but if you don't have any other requirements for saving the photo to the disk you can do this:

WebClient client = new WebClient();
byte[] data = client.DownloadData(item.Url);
MemoryStream ms = new MemoryStream(data);
Bitmap img = new Bitmap(ms);
pictureBox.Image = img;
Jason Miesionczek
what's the advantage?
Kai
Doesn't clutter your disk with files you don't need. Especially if the files are large.
Jason Miesionczek
This is only useful if you won't be downloading the same photo(s) over and over again, in that case, saving them to disk and checking if they exist before downloading them again would be more efficient.
Jason Miesionczek