I am using VS2010 on a WPF app. I cannot use the async feature.
Basically I need to load two images, and after images are downloaded a need to print them.
I need to manage the downloading of both images. How to achieve this?
var bitmap = new BitmapImage(new Uri("http://abcd.com/logo.png"));
var bitmapB = new BitmapImage(new Uri("http://abcd.com/logoB.png"));
if (!bitmap.IsDownloading)
{
// print immediately code here
}
else
{
bitmap.DownloadCompleted += (o, e) =>
{
// print when download completed
};
}
if (!bitmapB.IsDownloading)
{
// print immediately
}
else
{
bitmapB.DownloadCompleted += (o, e) =>
{
// print when download completed
};
}
It sounds like what you really want is the WebClient Class. You can use a number of its methods to download either the image as a file, or as a byte collection. The best part is that you can also do this asynchronously, so it will all happen on a background thread automatically.
When using these asynchronous methods, you need to handle the relevant Download...Completed event to retrieve the results. From the DownloadStringCompletedEventHandler Event page on MSDN:
public static void DownLoadFileInBackground2 (string address)
{
WebClient client = new WebClient ();
Uri uri = new Uri(address);
// Specify that the DownloadFileCallback method gets called
// when the download completes.
client.DownloadFileCompleted += new AsyncCompletedEventHandler(
DownloadFileCallback2);
// Specify a progress notification handler.
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(
DownloadProgressCallback);
client.DownloadFileAsync (uri, "serverdata.txt");
}
Take a look at the WebClient.DownloadDataAsync Method and WebClient.DownloadFileAsync Method pages on the MSDN website for more information.
Something like the following code should do the job for a variable number of images. It downloads all images sequentially in a ThreadPool thread before invoking a PrintImages method in the UI thread. Note the after downloading each image is frozen (by image.Freeze()) in order to make it cross-thread accessible.
private void DownloadAndPrintImagesAsync(IEnumerable<string> urls)
{
ThreadPool.QueueUserWorkItem(o =>
{
var images = urls.Select(url => DownloadImage(url));
Dispatcher.Invoke(new Action(() => PrintImages(images)));
});
}
private BitmapImage DownloadImage(string url)
{
var buffer = new WebClient().DownloadData(url);
var image = new BitmapImage();
using (var stream = new MemoryStream(buffer))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
image.Freeze();
return image;
}
private void PrintImages(IEnumerable<BitmapImage> images)
{
// print here
}
You might improve this by issuing multiple downloads in parallel, but that would complicate the code. You would have to wait for all asynchronous downloads to be finished before printing.
Update: Based on the proposal given by Sheridan, you may modify the DownloadAndPrintImagesAsync method like this:
private List<BitmapImage> images = new List<BitmapImage>();
private void DownloadAndPrintImagesAsync(IEnumerable<string> urls)
{
foreach (var url in urls)
{
var webClient = new WebClient();
webClient.DownloadDataCompleted += ImageDownloadCompleted;
webClient.DownloadDataAsync(new Uri(url));
}
}
private void ImageDownloadCompleted(object sender, DownloadDataCompletedEventArgs e)
{
if (!e.Cancelled && e.Error == null)
{
var image = new BitmapImage();
using (var stream = new MemoryStream(e.Result))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
images.Add(image);
if (images.Count == 2) // or whatever
{
// print images
}
}
}
Related
So, I want to send the image from my WPF app to the Web API controller. When I get the image as BitmapImage and try to send it, I get the error "the calling thread cannot access this object because a different thread owns it". I don't see how am I modifying UI and why do I get the error. Here's the code:
WPF code:
private void BtnSendImage_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = Environment.SpecialFolder.MyDocuments.ToString();
BitmapImage slika = null;
if (ofd.ShowDialog() == true)
{
odabranaSlika = ofd.FileName;
Uri adresa = new Uri(odabranaSlika, UriKind.Absolute);
slika = new BitmapImage(adresa);
ItemDAL idal = new ItemDAL();
if (idal.SendImage(slika))
{
MessageBox.Show("Yay!");
}
else
{
MessageBox.Show("Awwww");
}
}
}
Method SendImage from the ItemDAL class (the code stops at postResult.Wait() part):
public bool SendImage(BitmapImage bmp)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:52407/");
var postResult = client.PostAsJsonAsync<BitmapImage>("api/values/postimage", bmp);
postResult.Wait();
var result = postResult.Result;
if (result.IsSuccessStatusCode)
{
return true;
}
else
{
return false;
}
}
}
What am I doing wrong? I know I'm not suppose to modify UI, unless it's from the main thread, bot how is this doing anything to the UI?
my ActionResult provides a StreamContent file in the HttpResponseMessage result.Content. Now I would like to track the state of the download, to delete the file immediately after it was downloaded.
I found solutions that use the ByteStream, which split the file into chunks but that lacks the possibilities to provide a HttpStatusCode and other information in case some authorization tests deny the request.
This is my current controller:
[HttpGet]
public HttpResponseMessage GetZipDownload(string token, string guid)
{
if (!dOps.ValidateToken(token))
{
return Request.CreateResponse(HttpStatusCode.Unauthorized,
new HttpError("Unauthorized"));
}
Device device = dOps.GetDeviceFromToken(auth);
AssetOperations assetOps = new AssetOperations();
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
FileStream file = assetOps.GetZip(guid);
var content = new StreamContent(file);
result.Content = content;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
result.Content.Headers.Add("Connection", "Keep-Alive");
result.Content.Headers.Add("Content-Length", (file.Length).ToString());
return result;
}
Before I dig deeper into the ByteStream solution, I would like to ask if anybody probably knows about a reliable solution for ASP.NET MVC 5.
You can register EndRequest event handler to achieve it.
Override Init() method inside MvcApplication class, and register to EndRequest event handler.
Save into HttpContext.Current.Items the file path with a constant key.
Global.asax file
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// some startup code, removed for clearity
}
public override void Init()
{
base.Init();
EndRequest += MvcApplication_EndRequest;
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
if (
HttpContext.Current.Request.Url.AbsolutePath == "/DownloadFileEndedWebsite/Home/Contact" // This should be adjusted.
&& HttpContext.Current.Items.Contains("DownloadFilePath"))
{
var filePath = HttpContext.Current.Items["DownloadFilePath"];
// Delete file here..
}
}
}
HomeController.cs
public ActionResult Contact()
{
HttpContext.Items.Add("DownloadFilePath", "DownloadFilePathValue");
return View();
}
You should create a method like: startDownload() from the UI thread. The idea of WebClient.DownloadFileAsync() is that it will spawn a worker thread for you automatically without blocking the calling thread.
As this thread explains, the Trhead will help you to have control of the download,
private void startDownload()
{
Thread thread = new Thread(() => {
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri("http://joshua-ferrara.com/luahelper/lua.syn"), #"C:\LUAHelper\Syntax Files\lua.syn");
});
thread.Start();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate {
var bytesReceived = e.BytesReceived;
//double bytesIn = double.Parse(e.BytesReceived.ToString());
//double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
//double percentage = bytesIn / totalBytes * 100;
//label2.Text = "Downloaded " + e.BytesReceived + " of " + e.TotalBytesToReceive;
//progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString()); // YOU CAN HAVE CONTROL OF THE PERCENTAGE OF THE DOWNLOAD, BUT, FOR MVC MAYBE IS BETTER NOT USE IT
});
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate {
//TODO: EXECUTE AN EVENT HERE, **THE DOWNLOAD ALREADY IS COMPLETED**!!!
});
}
My app downloads several images from the internet and shows progress of the overall download in one single progress bar. The problems is that the progress isn't being updated correctly when using a BackgroundTransferService.
This is what I have.
From my PhoneApplicationPage_Loaded I call download a list of the images urls that i want to download.
WebClient wc = new WebClient();
wc.DownloadStringCompleted += wc_DownloadStringCompleted;
wc.DownloadStringAsync(new Uri(MyDownloadList), null);
Then in the wc_DownloadStringCompleted I register a custom event DownloadProgress in my DownloadManagerClass which updated the progress bar in the UI pbDownloadProgress
Then I call the code which adds a new BackgroundTransferRequest and register the TransferStatusChanged and TransferProgressChanged (that resides in the DownloadManagerClass).
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var up = e.Result;
Dispatcher.BeginInvoke(() => txtDownloadProgressText.Text = "Downloading");
downloadManager.DownloadProgress += (src, args) =>
{
Debug.WriteLine("Updating progress to " + args.Progress ` 100);
Dispatcher.BeginInvoke(() =>
{
if (pbDownloadProgress.IsIndeterminate)
{
pbDownloadProgress.IsIndeterminate = false;
pbDownloadProgress.Minimum = 0;
pbDownloadProgress.Maximum = 100;
}
pbDownloadProgress.Value = args.Progress * 100;
});
};
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += (s, ev) =>
{
downloadManager.DownloadImages(up);
};
bg.RunWorkerAsync();
}
}
And my DownloadImages method
internal void DownloadImages(String result)
{
List<String> URLs = ConvertStringToListUrls(result);
var localFile = IsolatedStorageFile.GetUserStoreForApplication();
if (!localFile.DirectoryExists(TRANSFERS_DIRECTORY))
{
localFile.CreateDirectory(TRANSFERS_DIRECTORY);
}
numberDownloadedItems = 0;
totalDownloadingItems = URLs.Count;
foreach (String item in URLs)
{
Debug.WriteLine("Adding Uri:" + uri);
Uri transferUri = new Uri(Uri.EscapeUriString(item), UriKind.RelativeOrAbsolute);
BackgroundTransferRequest transferRequest = new BackgroundTransferRequest(transferUri);
transferRequest.Method = "GET";
string downloadFile = transferFileName.Substring(transferFileName.LastIndexOf("/") + 1);
Uri downloadUri = new Uri(TRANSFERS_DIRECTORY + "/" + downloadFile, UriKind.RelativeOrAbsolute);
transferRequest.DownloadLocation = downloadUri;
transferRequest.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferStatusChanged);
transferRequest.Tag = downloadFile;
transferRequest.TransferPreferences = TransferPreferences.AllowCellularAndBattery;
BackgroundTransferService.Add(transferRequest);
Debug.WriteLine("Adding transferID: " + transferRequest.RequestId);
}
}
Finally my transfer_TransferStatusChanged method which mainly calls the DownloadProgress event when one file is completed.
void transfer_TransferProgressChanged(object sender, BackgroundTransferEventArgs e)
{
if (transfer.TransferStatus == TransferStatus.Completed && (transfer.StatusCode == 200 || transfer.StatusCode == 206)) {
if (RemoveTransferRequest(transfer.RequestId))
if (DownloadProgress != null) DownloadProgress(this, new ProgressEventArgs(++numberDownloadedItems, totalDownloadingItems));
}
}
Every step seems to be working fine but the problem is that on the output I get the debug messages indicating the progress that should be updated (like the ones below) but the progressbar itself remains unchanged.
Removing transferID: {05ea9c10-025f-4e90-bc55-e9d36424b5f0}
10:37:27 AM:200: Updating progress to 14.2857142857143
Removing transferID: {e92f79fe-dc99-48b3-b0b1-76d3f5cedd51}
10:37:27 AM:651: Updating progress to 21.4285714285714
Removing transferID: {f61c8c9e-aa41-4e2a-8906-fca06961e69e}
10:37:28 AM:30: Updating progress to 28.5714285714286
Removing transferID: {46abc1df-37e6-432a-9b55-0adb3a8a2235}
I already tried to remove the dispatcher use, to reorganize the code but I can't seem to get it to work.
PS: I removed some exception catching from the code to keep it simpler
I am writing a C# application for uploading and downloading file. The download uses WebClient object and its DownloadAsycDownload method. The download works fine for multiple files. It downloads as much files as I want.
My problem is I am not able to show the progress of all file in different progress bars which are dynamically added to the form's flowlayout control.
Here is my code:
public ProgressBar[] bar;
public int countBar=0;
...
bar[countBar] = new ProgressBar();
flowLayoutPanel1.Controls.Add(bar[countBar]);
countBar++;
request.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownoadInProgress);
request.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
request.DownloadFileAsync(new Uri(this.uri), localPath);
byte[] fileData = request.DownloadData(this.uri);
FileStream file = File.Create(localPath);
file.Write(fileData, 0, fileData.Length);
file.Close();
}
public void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
flowLayoutPanel1.Controls.Remove(bar[countBar]);
countBar--;
MessageBox.Show("Download Completed");
}
public void DownoadInProgress(object sender, DownloadProgressChangedEventArgs e)
{
bar[countBar].Maximum = total_bytes;
bar[countBar].Value = (int)e.BytesReceived;
}
You're using a count to index into progress bars, but once one is complete - you remove the last one, where you really should remove the one associated with the file.
I suggest, in this case, using a Dictionary<WebClient, ProgressBar> (might not be WebCliet - should be the type of sender in the events).
...
var progBar = new ProgressBar();
progBar.Maximum = 100;
flowLayoutPanel1.Controls.Add(progBar);
request.DownloadProgressChanged += DownoadInProgress;
request.DownloadFileCompleted += DownloadFileCompleted;
request.DownloadFileAsync(new Uri(this.uri), localPath);
dic.Add(request, progBar);
// You shouldn't download the file synchronously as well!
// You're already downloading it asynchronously.
// byte[] fileData = request.DownloadData(this.uri);
// FileStream file = File.Create(localPath);
// file.Write(fileData, 0, fileData.Length);
// file.Close();
Then, you can remove countBar altogether, and have the new methods:
public void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
// Can remove (WebClient) cast, if dictionary is <object, ProgressBar>
var request = (WebClient)sender;
flowLayoutPanel1.Controls.Remove(dic[request]);
dic.Remove(request);
MessageBox.Show("Download Completed");
}
public void DownoadInProgress(object sender, DownloadProgressChangedEventArgs e)
{
var progBar = dic[(WebClient)sender];
progBar.Value = e.ProgressPercentage;
}
I would use something like this with lambdas, a bit more concise, no need in dictionary:
var webClient = new WebClient();
var pb = new ProgressBar();
pb.Maximum = 100;
flowLayoutPanel1.Controls.Add(pb);
webClient.DownloadProgressChanged += (o, args) =>
{
pb.Value = args.ProgressPercentage;
};
webClient.DownloadFileCompleted += (o, args) =>
{
flowLayoutPanel1.Controls.Remove(pb);
};
webClient.DownloadFileAsync(new Uri(this.uri), localPath);
I am trying to use webclient check download a stream before it is handled by an ExtendedImage, because my app is showing a bug when the uri is not found.
So my solution is to load the image first and then read the webclient result into the extended image.
This is what I am trying to do.
WebClient wc = new WebClient();
wc.OpenReadAsync(Uri);
wc.OpenReadCompleted += delegate(object Sender, OpenReadCompletedEventArgs e){
Logo = new BitmapImage();
ExtendedImage hExtendedImage = new ExtendedImage();
try
{
hExtendedImage.SetSource(e.Result);
Logo.SetSource(hExtendedImage.ToStream());
}
catch (WebException)
{
}
};
but now I am getting an "image is not loaded" error from hExtendedImage on this line
Logo.SetSource(hExtendedImage.ToStream());
I'm obviously loading the image from e.Result into the hExtendedImage wrong.
var client = new WebClient();
// Always define event handlers,
// BEFORE calling any method that could invoke them.
client.OpenReadCompleted += (s1, e1)
{
Logo = new BitmapImage();
var extendedImage = new ExtendedImage();
extendedImage.OnLoadingCompleted += (s2, e2)
{
// Invoke the dispatcher, so we're sure it's set on the UI thread.
Dispatcher.BeginInvoke(new Action
(
() => Logo.SetSource(extendedImage.ToStream()))
);
};
extendedImage.SetSource(e1.Result);
};
client.OpenReadAsync(Uri);
Unfortunately SetSource is ansyc. Use the event LoadingCompleted of hExtendedImage to set Logo source.
Be careful: LoadingCompleted callback is not in ui thread! You must invoke dispatcher if you want to change UI controls like Image.
From ExtendedBitmap source on CodePlex:
public void SetSource(Stream stream)
{
Contract.Requires<ArgumentNullException>(stream != null, "Stream cannot be null.");
if (_uriSource == null)
{
LoadAsync(stream);
}
}