Статьи

Создание небольшого загрузчика для Windows Phone 7

Загрузка данных из Интернета стала необходимостью, так как, скажем, Интернет был создан. Люди хотят хранить картинки, музыку, текстовые файлы, а что нет. Это можно сделать и на устройстве с Windows Phone 7. Хотя в этой статье я не рассматриваю возможные способы чтения загруженного содержимого, я покажу, как загружать определенные файлы в изолированное хранилище.

Знайте пределы

Первое, что вы должны знать, это то, что будут некоторые ограничения. В первую очередь это выделенное место для хранения. И вы не можете выйти за пределы этого. Поэтому вы можете захотеть включить в приложение определенные ограничения на загрузку, чтобы пользователи не пытались загрузить архив объемом 4 ГБ, если приложению разрешено хранить только 1 ГБ данных.

Другим ограничением будет возможная пропускная способность. Имейте в виду, что большинство пользователей будут загружать данные через сотовую сеть передачи данных, а не через WiFi (хотя это также возможно). Поэтому вы можете проверить, какой тип соединения присутствует, прежде чем начинать большие загрузки. Это можно проверить через NetworkInterface.NetworkInterfaceType.

Также имейте в виду ограниченное количество ресурсов, поэтому вы, конечно, не хотите перегружать свой телефон из-за большой загрузки.

Базовая структура приложения

Итак, что вам нужно для базовой структуры пользовательского интерфейса? Там будет поле, где пользователь может ввести URL. На этом этапе целесообразно сохранить файл в месте, определенном разработчиком, чтобы им было проще управлять. Кроме того, должна быть кнопка, чтобы начать загрузку, еще одна, чтобы отменить ее, индикатор выполнения и поле, которое покажет текущее состояние загрузки.

ПРИМЕЧАНИЕ. После реализации этого простого приложения вы можете легко сделать его менеджером загрузок, создавая несколько загрузок одновременно, поскольку вам придется повторно использовать существующий шаблон элемента для каждого элемента загрузки.

В целом, я придумал этот интерфейс:

Достаточно просто, чтобы сделать работу. Если вы заинтересованы в использовании той же структуры, вы можете использовать этот 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>

Теперь, когда пользователь вводит URL-адрес, я могу проверить, является ли введенный URL-адрес допустимым в первую очередь. Для этого я создаю собственный метод, который будет возвращать логическое значение, которое сообщит мне, правильно ли сформирована строка URL:

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

Теперь я могу написать обработчик событий для кнопки « Пуск» . Прежде всего, я собираюсь позвонить в 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";
}
}

Это только проверит, правильно ли отформатирован URL, и установит состояние, соответствующее действительному или недействительному URL. Но это ничего не делает для загрузки файла.

Для этого я создаю новый экземпляр WebClient , который фактически будет обрабатывать процесс загрузки. В теле класса я объявляю экземпляр:

WebClient client;

Когда приложение запускается, класс инициализируется, и я ссылаюсь на два обработчика событий — DownloadProgressChanged (для проверки текущего прогресса) и OpenReadCompleted (срабатывает после завершения загрузки):

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

Прежде чем идти дальше, я должен упомянуть, что для отслеживания прогресса у меня есть готовый DependencyProperty, который будет привязан к ProgressBar , поэтому я могу динамически отслеживать изменения в шагах загрузки:

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));

Итак, говоря об этих обработчиках событий — вот что у меня есть для 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;
}
}

Для небольших файлов иногда свойство TotalBytesToReceive может принимать значение -1, поэтому, если оно установлено для Progress, ProgressBar в значительной степени завершится ошибкой, и вы не увидите никакого прогресса. Это не то, что вы хотите здесь, поэтому я избегаю этой ситуации.

Прогресс просто рассчитывается с использованием стандартного правила пропорции. И я также отображаю количество байтов в txtQuantity .

Чтобы сохранить реальный файл, мне понадобится экземпляр IsolatedStorageFile , поэтому я создаю его в теле основного класса:

IsolatedStorageFile file;

Так что теперь мне просто нужно инициализировать этот экземпляр, чтобы он был установлен на текущую квоту хранилища для приложения.

file = IsolatedStorageFile.GetUserStoreForApplication();

Теперь я готов работать над 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";
}
}

Я храню файл под тем же именем, которое используется на сервере, с которого он загружается. Я также показываю состояние загрузки. Как видите, я также меняю состояния двух кнопок, используемых для запуска и отмены загрузки. Это происходит только после завершения загрузки.

Я также проверяю, что любое исключение, которое может появиться, не приведет к сбою приложения, поэтому все, что я делаю, — это просто возвращаю пользовательский интерфейс в его первоначальное состояние в случае возникновения.
Отныне кнопка « Пуск» получает немного больше работы для нее. Когда загрузка начнется, она должна быть отключена, а кнопка « Отмена» станет активной. В то же время он фактически начнет загрузку. Так что мой новый обработчик событий выглядит так:

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";
}
}

Для кнопки отмены все, что нужно, это:

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

В основном это отменяет загрузку и это вызовет WebException в OpenReadCompleted , поэтому интерфейс будет восстановлен в исходное состояние.