Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Creating a small downloader tool for Windows Phone 7

08.16.2010
| 9919 views |
  • submit to reddit

Downloading data from the Internet became a necessity since, let’s say, the Internet was established. People want to store pictures, music, text files and what not. You can do this very thing on a Windows Phone 7 device as well. Although in this article I am not covering the possible ways to read the downloaded contents, I will show how to download specific files to isolated storage.

Know the limits

The first thing you should know is that there will be some restrictions. First and foremost, it is the allocated storage space. And you cannot go beyond that. Therefore, you might want to include specific download limits for the application, so that the users won’t be trying to download a 4GB archive when the application only is allowed to store 1GB of data.

Another restriction would be the possible bandwidth. Keep in mind that most users will be downloading via a cellular data network and not via WiFi (although this is also possible). Therefore you might want to check what type of connection is present before starting large downloads. This can be checked via NetworkInterface.NetworkInterfaceType.

Also keep in mind the limited amount of resources, so you certainly don’t want to overload your phone because of a large download.

Basic app structure

So what do you need for the basic UI structure? There will be a box, where the user can input the URL. At this point, it is reasonable to save the file in a location that is defined by the developer, so it is easier to manage. Also, there should be a button to start the download, another one to cancel it, a progress bar and a box that will show the current download status.

NOTE: Once you implement this simple application, you can easily make it a download manager, creating multiple downloads at once, since you will have to reuse the existing item template for each downloading item.

Overall, I came up with this UI:

Simple enough to get the job done. If you are interested in using the same structure, you can use this XAML:

<phone:PhoneApplicationPage
x:Class="WP7_Sandbox.MainPage"
x:Name="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="PortraitDown"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
shell:SystemTray.IsVisible="True">

<Grid x:Name="LayoutRoot" Background="Transparent">
<ProgressBar Maximum="100" Height="90" Name="progressBar1" Value="{Binding ElementName=MainWindow,Path=Progress}" Margin="25,216,23,462" />
<Button IsEnabled="False" Content="Cancel" Height="72" Name="btnCancel" Click="button1_Click" Margin="273,88,10,608" />
<Button Content="Start" Height="72" Name="btnStart" Margin="71,88,213,608" Click="btnStart_Click" />
<TextBlock Text="URL:" Height="30" HorizontalAlignment="Left" Margin="25,33,0,0" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="71,10,0,0" Name="txtUrl" VerticalAlignment="Top" Width="399" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="25,180,0,0" Text="Progress:" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="39,276,0,0" Name="txtQuantity" Text="0 / 0" VerticalAlignment="Top" />
<TextBlock Foreground="Yellow" Height="30" HorizontalAlignment="Left" Margin="25,330,0,0" Name="txtState" Text="Download State: Unknown" VerticalAlignment="Top" Width="432" />
</Grid>
</phone:PhoneApplicationPage>

Now, when the user enters a URL, I might want to check whether the entered URL is valid first. To do this, I am creating a custom method that will return a Boolean value, that will tell me whether the URL string is well-formed or not:

bool CheckUrl(string URL)
{
if (Uri.IsWellFormedUriString(URL,UriKind.Absolute))
return true;
else
return false;
}

Now I can write the event handler for the Start button. First of all, I am going to have a call to CheckUrl:

private void btnStart_Click(object sender, RoutedEventArgs e)
{
if (CheckUrl(txtUrl.Text))
{
txtState.Foreground = new SolidColorBrush(Colors.Green);
txtState.Text = "Download State: URL Validated";
}
else
{
txtState.Foreground = new SolidColorBrush(Colors.Red);
txtState.Text = "Download State: Invalid URL entered";
}
}

This will only check whether the URL is well-formatted and will set the state to the one that corresponds to the valid or invalid URL. But it does nothing to download the file.

To do this, I am creating a new instance of WebClient, that will actually handle the download process. In the class body, I am declaring the instance:

WebClient client;

When the application is launched, the class is initialized and I am referencing two event handlers – DownloadProgressChanged (to check the current progress) and OpenReadCompleted (triggered when the download completed):

client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);

Before going any further, I must mention that to track the progress, I have a DependencyProperty ready that will be bound to the ProgressBar, so I can dynamically trace the changes in download steps:

public double Progress
{
get
{
return (double)GetValue(ProgressState);
}
set
{
SetValue(ProgressState, value);
}
}

public static DependencyProperty ProgressState = DependencyProperty.Register("Progress",typeof(double),typeof(MainPage),new PropertyMetadata(0.0));

So speaking about these event handlers – here is what I have for DownloadProgressChanged:

void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
if (e.TotalBytesToReceive != -1)
{
txtQuantity.Text = string.Format("{0}/{1}", e.BytesReceived.ToString(), e.TotalBytesToReceive.ToString());
Progress = e.BytesReceived * 100 / e.TotalBytesToReceive;
}
}

For small files, sometimes the TotalBytesToReceive property might become -1, so if that is set for Progress, the ProgressBar will pretty much fail and you won’t see any progress. That’s not something you want here, so that is the reason I am avoiding this situation.

The progress is simply calculated by using the standard proportion rule. And I am also displaying the byte quantity in txtQuantity.

To save the actual file, I will need an instance of IsolatedStorageFile, so I am creating one in the main class body:

IsolatedStorageFile file;

So now, I just need to initialize this instance so that it is set to the current storage quota for the application.

file = IsolatedStorageFile.GetUserStoreForApplication();

Now I am ready to work on OpenReadCompleted:

void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
try
{
var resInfo = new StreamResourceInfo(e.Result, null);
var reader = new StreamReader(resInfo.Stream);

byte[] contents;
using (BinaryReader bReader = new BinaryReader(reader.BaseStream))
{
contents = bReader.ReadBytes((int)reader.BaseStream.Length);
}

using (IsolatedStorageFileStream stream = file.CreateFile(System.IO.Path.GetFileName(txtUrl.Text)))
{
stream.Write(contents, 0, contents.Length);
}

btnCancel.IsEnabled = false;
btnStart.IsEnabled = true;
txtState.Text = "Download State: Downloaded. Stored as " + System.IO.Path.GetFileName(txtUrl.Text);
}
catch (Exception ex)
{
btnCancel.IsEnabled = false;
btnStart.IsEnabled = true;
txtQuantity.Text = "0/0";
progressBar1.Value = 0;

txtState.Foreground = new SolidColorBrush(Colors.Red);
txtState.Text = "Download State: Canceled";
}
}

I am storing the file under the same name as the one used on the server, from which the file is downloaded. I am also showing the download state. As you can see, I am also changing the states of the two buttons used for starting and cancelling the download. This happens only when the download completes.

I am also making sure that any exception that might appear won’t crash the application, so all I do is just restore the UI to its initial state in case one occurred.
From now on, the Start button gets a bit more work for it. When the download is started, it should be disabled and the Cancel button should become enabled. At the same time, it will actually start the download. So my new event handler for it looks like this:

private void btnStart_Click(object sender, RoutedEventArgs e)
{
if (CheckUrl(txtUrl.Text))
{
txtState.Foreground = new SolidColorBrush(Colors.Green);
txtState.Text = "Download State: URL Validated";

client.OpenReadAsync(new Uri(txtUrl.Text));

btnStart.IsEnabled = false;
btnCancel.IsEnabled = true;
}
else
{
txtState.Foreground = new SolidColorBrush(Colors.Red);
txtState.Text = "Download State: Invalid URL entered";
}
}

For the cancelling button, all that is needed is this:

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
client.CancelAsync();
}

Basically it cancels the download and it will trigger a WebException in OpenReadCompleted, therefore the UI will be restored to its initial state.