I download an image from an URL asynchronously using WebRequest this way:
public void Download(string url)
{
byte[] buffer = new byte[0x1000];
WebRequest request = HttpWebRequest.Create(url);
request.Method = "GET";
request.ContentType = "image/gif";
request.BeginGetResponse(result =>
{
WebRequest webRequest = result.AsyncState as WebRequest;
WebResponse response = webRequest.EndGetResponse(result);
ReadState readState = new ReadState()
{
Response = response.GetResponseStream(),
AccumulatedResponse = new MemoryStream(),
Buffer = buffer,
};
readState.Response.BeginRead(buffer, 0,
readState.Buffer.Length, ReadCallback, readState);
}, request);
}
public void ReadCallback(IAsyncResult result)
{
ReadState readState = result.AsyncState as ReadState;
int bytesRead = readState.Response.EndRead(result);
if(bytesRead > 0)
{
readState.AccumulatedResponse.BeginWrite(readState.Buffer, 0, bytesRead, writeResult =>
{
readState.AccumulatedResponse.EndWrite(writeResult);
readState.Response.BeginRead(readState.Buffer, 0, readState.Buffer.Length, ReadCallback, readState);
}, null);
}
else
{
readState.AccumulatedResponse.Flush();
readState.Response.Close();
pictureBox1.Image = Image.FromStream(readState.AccumulatedResponse);
}
}
public class ReadState
{
public Stream Response { get; set; }
public Stream AccumulatedResponse { get; set; }
public byte[] Buffer { get; set; }
}
and it works ok, but I would like to show the progress of the download as the browsers do, and not to show only when it finishes.
If I do
pictureBox1.Image = Image.FromStream(readState.AccumulatedResponse);
before it finishes I get an exception that the picture is not valid, even though it has some data.
Is there anyway to show partial data?
JPEG has a special encoding mode called "Progressive JPEG" in which data is compressed in multiple passes of progressively higher detail. Windows 7 has built-in support for this.
I would like to show the progress of
the download as the browsers do, and
not to show only when it finishes.
You have two options:
Use WebClient.DownloadDataAsync
. This will raise progress events via DownloadProgressChanged
and provide a final notification of when the data is available via the DownloadDataCompleted
event. At that point, you could assign the image to, say, the PictureBox
.
If you are downloading an image to eventually display in a PictureBox
control then it may even be easier to just go with PictureBox.LoadAsync
. This too will provide progress updates via its LoadProgressChanged
event and finally LoadCompleted
on completion.
You've problem with the PictureBox control, not with the partial download.
An hack is to use the WebBrowserControl, as IE is able to show partial images.
When you begin the request, check the WebResponse.ContentLength property, that should give you the total number of bytes to expect. Then keep track of the total number of bytes read in your ReadCallback method. The rest should be obvious. :)
[edit] Oops, I re-read the question, and that wasn't quite what you wanted. I'm still leaving this up though, in case somebody might benefit from it.
I don't think you will manage that using the built-in methods of the .NET framework, since they will first read the full image and then display it. You will need to either find some other library that supports displaying partially downloaded images or write a parser yourself.
You can probably do this, however, it will require some additional knowledge of the structure of gif files. Wotsit.org has several links to gif structure documents, to get you started.
The general approach would be to take your total accumulated data, and from the end, search back until you find the end of a block terminator. The data between this last valid block and the end of the stream is only partially complete, and must be considered junk data. You want to remove it, and add a file trailing block to the stream. Then, you should be able to load this edited stream into a PictureBox.
There's an event you can use on an asynchronous web download request that gets called periodically and allows you to directly update a ProgressBar control. Here's an example of how I've used this event:
delegate void SetProgressDelegate(int percentComplete);
/// <summary>
/// A thread-safe method for updating the file download progress bar.
/// </summary>
/// <param name="bytesReceived"></param>
/// <param name="totalBytes"></param>
void SetDownloadProgress(int percentComplete)
{
if (this.InvokeRequired)
{
SetProgressDelegate d = new SetProgressDelegate(SetDownloadProgress);
this.Invoke(d, new object[] { percentComplete });
}
else
{
this.progressFileDownload.Value = percentComplete;
}
}
/// <summary>
/// Event handler to update the progress bar during file download.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void web_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
SetDownloadProgress(e.ProgressPercentage);
}
And here's how I wire up the event and start the download:
WebClient web = new WebClient();
web.DownloadFileCompleted += new AsyncCompletedEventHandler(web_DownloadFileCompleted);
web.DownloadProgressChanged += new DownloadProgressChangedEventHandler(web_DownloadProgressChanged);
web.DownloadFileAsync(sourceUri, destinationFileName);
Your solution reads the file directly into memory, whereas mine saves it to a file on disk, so this solution may or may not work for you. If displaying a progress indication is important to you, this method does work and you can always download to a temporary file, then load that file into the image control once it's complete.