Статьи

Разработка SevenDrops — загрузчик фотографий на основе Dropbox для Windows Phone 7 [6/6]

Это последний фрагмент моего учебного пособия, в котором описан способ загрузки файлов в Dropbox из приложения Windows Phone. Ядро в значительной степени завершено прямо сейчас, и все, что нужно сделать, это правильно обработать фактический процесс загрузки (читай: убедитесь, что пользователь знает, что происходит). Вот некоторые подробности о дополнениях, которые я сделал в существующем каркасе приложения. 

Прежде всего, я добавил новый класс, названный UploadCounter. Это специально предназначено для подсчета количества ожидающих загрузок.

public class UploadCounter : INotifyPropertyChanged
{
    private int counter = 0;
    public int Counter
    {
        get
        {
            return counter;
        }
        set
        {
            if (value != counter)
            {
                counter = value;
                RaisePropertyChange("Counter");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChange(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Обратите внимание, что класс реализует интерфейс INotifyPropertyChanged — таким образом, я могу связать его в любом месте приложения, и всякий раз, когда я изменяю значение свойства Counter, он автоматически связывается и результат обновляется в представлении (UI).

Мне нужно будет получить доступ к счетчику глобально (из любой точки приложения), поэтому я добавляю экземпляр UploadCounter к самому классу приложения:

Обратите внимание, что я также инициализирую этот класс при запуске приложения (посмотрите на конструктор). Одна вещь, которую вы, возможно, также заметили, это Диспетчер. Это не инициализируется здесь, а скорее на главной странице. Я собираюсь использовать его для обновления UploadCounter.Counter из вторичного потока позже, но я буду использовать диспетчер, связанный с потоком пользовательского интерфейса.

Тем не менее, в файле MainPage.cs, в конструкторе страниц у меня есть назначение Dispatcher:

Вернемся, однако, к самому свойству Counter. Как только я начинаю процесс загрузки, я устанавливаю для свойства количество элементов в списке изображений:

С каждой загрузкой, которая будет завершена, значение будет уменьшаться. Для этого мне нужно вернуться к классу OAuthClient и уменьшить счетчик при вызове GetResponse (метод, который обрабатывает конечный ответ при загрузке изображения):

Вот где в игру вступает диспетчер, о котором я говорил ранее. Если я не уменьшаю свойство с помощью диспетчера, будет вызван недопустимый межпотоковый вызов, и это вызовет исключение, поэтому, как правило, здесь рекомендуется использовать основной экземпляр Dispatcher.

Теперь, когда сам счетчик определил основной рабочий процесс, пришло время показать его где-нибудь. На момент загрузки я решил отобразить всплывающее окно и показать, сколько элементов ожидают в очереди. Я создал пользовательский элемент управления, который будет добавлен в качестве дочернего элемента в базовое всплывающее окно:

<UserControl x:Class="SevenDrops.Helpers.UploadingFiles"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    Loaded="UserControl_Loaded"
    d:DesignHeight="221" d:DesignWidth="256">
    
    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}" Height="219" Width="261">
        <TextBlock Height="30" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="Uploading files..." VerticalAlignment="Top" Width="239" />
        <TextBlock Height="30" HorizontalAlignment="Left" Margin="12,177,0,0" Name="textBlock2" Text="Remaining files:" VerticalAlignment="Top" />
        <TextBlock Height="30" HorizontalAlignment="Left" Margin="157,177,0,0" x:Name="RemainingText" VerticalAlignment="Top" />
    </Grid>
</UserControl>

RemainingText — это экземпляр TextBlock, где я буду отображать количество оставшихся элементов. Я устанавливаю его привязку в коде после загрузки элемента управления:

Binding binding = new Binding();
binding.Source = App.uploadCounter;
binding.Path = new PropertyPath("Counter");
binding.Converter = new IntToStringConverter();

RemainingText.SetBinding(TextBlock.TextProperty, binding);

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

public class IntToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return System.Convert.ToString(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return System.Convert.ToInt32(value);
    }
}

Когда начинается загрузка, я вручную создаю экземпляр Popup:

Popup uploadPopup = new Popup();
uploadPopup.Child = new UploadingFiles();
uploadPopup.IsOpen = true;
uploadPopup.VerticalOffset = 100;
uploadPopup.HorizontalOffset = 25;

Binding b = new Binding();
b.Source = App.uploadCounter;
b.Converter = new CounterToVisibilityConverter();
b.Path = new PropertyPath("Counter");
uploadPopup.SetBinding(Popup.IsOpenProperty, b);

Здесь следует заметить, что его свойство IsOpen привязано к Counter. Вы можете задаться вопросом — как целочисленное свойство может быть связано с логическим. Здесь используется конвертер, который всегда будет возвращать истину (всплывающее окно видно), если значение счетчика больше нуля (файлы все еще загружаются), и ложь, если значение счетчика равно нулю.

Конечный результат выглядит так:

В этом проекте еще предстоит проделать большую работу. Например, мне нужно проверить ответ, полученный от сервера после завершения загрузки — если это не {«result»: «winner!»}, Значит, что-то пошло не так. Кроме того, мне нужно будет реализовать функцию отмены, которая позволит пользователю отступать после того, как загрузка уже началась.

Проект с открытым исходным кодом и будет доступен в ближайшее время на его странице CodePlex .