Статьи

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

Если вы еще этого не знали, Dropbox — это сервис облачного хранилища, который позволяет хранить файлы на своих серверах, а затем получать к ним доступ из любого места. Этот сервис предоставляет публичный API, который я решил интегрировать в приложение Windows Phone 7 для резервного копирования моих фотографий. Интеграция с SkyDrive великолепна, но мне просто интересно узнать о реализации Dropbox, так что этот набор статей описывает, как я создавал приложение.

Достаточно интро. Где код? Итак, вот XAML для главного окна:

<phone:PhoneApplicationPage 
    x:Class="SevenDrops.MainPage"
    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"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    x:Name="Main">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="60" />
            <RowDefinition Height="*"/>
            <RowDefinition Height="80"></RowDefinition>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="1" Text="Current Account:" Style="{StaticResource PhoneTextTitle3Style}" Margin="20,15,0,0" HorizontalAlignment="Left" Width="171"></TextBlock>
        <TextBlock Grid.Row="1" Text="NAME" Margin="201,15,102,0" Style="{StaticResource PhoneTextTitle3Style}"></TextBlock>
        <Button Grid.Row="1" Height="80" Width="80" Margin="380,-10,0,0">
            <Image Height="48" Width="48" Source="/Images/pick.png" Margin="-8,0,0,0"></Image>
        </Button>
        
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <Image Source="/Images/drops.png" Height="164" Width="164" Margin="339,0,0,0"></Image>
            <TextBlock x:Name="ApplicationTitle" Margin="0,-150,0,0" Text="SevenDrops" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="2" Margin="12,29,12,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="309*" />
                <ColumnDefinition Width="147*" />
            </Grid.ColumnDefinitions>
            <ListBox ItemsSource="{Binding ElementName=Main,Path=ImageCollection}" Grid.ColumnSpan="2">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="460" Height="80">
                            <Image Height="128" Width="128" Source="{Binding Path=Source}" Margin="-310,0,0,0"></Image>
                            <TextBlock Text="{Binding Path=ImageName}" Margin="70,0,0,0" Width="220" TextWrapping="Wrap"></TextBlock>
                            <Button Width="80" Height="80" Margin="380,0,0,0">
                                <Image Source="/Images/delete.png" Height="48" Width="48" Margin="-8,0,0,0"></Image>
                            </Button>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
        
        <Button Grid.Row="3" Content="Add Image" Click="Button_Click"></Button>
    </Grid>
</phone:PhoneApplicationPage>

Если вам интересно, как выглядит настоящий пользовательский интерфейс, вот скриншот:

Сам список на скриншоте сейчас отсутствует, поскольку в коллекции нет элементов, хотя он привязан к ImageCollection (объяснено ниже) — его целью является перечисление выбранных изображений, которые впоследствии будут переданы методу, который будет хранить изображения внешне. Для каждого элемента списка определен пользовательский шаблон ItemTemplate, показывающий значок изображения (также известный как миниатюра) и имя, а также кнопку «Удалить», которая исключит изображение из очереди.

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

ObservableCollection<ImageUnit> Images;

Коллекция инициализируется при инициализации самого окна:

// Constructor
public MainPage()
{
InitializeComponent();
Images = new ObservableCollection<ImageUnit>();
}

Но это было бы неприменимо непосредственно из кода приложения — мне нужно иметь свойство DependencyProperty для привязки. Поэтому я регистрирую один:

DependencyProperty _collection = DependencyProperty.Register("ImageCollection", typeof(ObservableCollection<ImageUnit>), typeof(MainPage), null);

Впоследствии мне нужно изменить коллекцию, чтобы она была привязана к свойству DependencyProperty:

public ObservableCollection<ImageUnit> Images
{
get
{
return (ObservableCollection<ImageUnit>)GetValue(_collection);
}
set
{
SetValue(_collection, value);
}
}

Так что есть класс ImageUnit, который немного сбивает с толку — в конце концов, я не упомянул, что это такое. Вместо того, чтобы просто временно сохранить изображение, я также определю имя изображения (которое, кстати, будет GUID). Итак, сам класс выглядит так:

public class ImageUnit
{
public byte[] ImageContents { get; set; }
public string ImageName { get; set; }
public BitmapImage Source
{
get
{
BitmapImage image = new BitmapImage();
image.SetSource(new MemoryStream(ImageContents));
return image;
}
set;
}
}

На данный момент это действительно простой и в то же время оставит место для настройки при необходимости. Свойство source будет возвращать пользовательский BitmapImage на основе ImageContents. Вы можете спросить — почему я не могу просто иметь свойство на основе BitmapImage, чтобы у меня не было дополнительного свойства? Причина проста — при загрузке изображения мне нужно будет передать байтовый массив, и я не могу получить его из BitmapImage. В то же время, я не могу установить источник изображения локально, чтобы быть байтовым массивом.

Для записи, этот класс находится в пользовательском подпространстве имен, называемом Доменом (определенным, конечно, в пользовательской папке):

Поэтому в вашем главном классе окна убедитесь, что в заголовке класса присутствуют следующие дополнительные операторы using:

using System.Collections.ObjectModel;
using SevenDrops.Domain;

Первый предназначен для ObservableCollection, а второй — для класса ImageUnit . Причина, по которой я использую ObservableCollection вместо любого другого общего списка, заключается в очень хороших возможностях автоматической привязки. Каждый связанный объект будет автоматически обновляться при обновлении списка (спасибо за INotifyPropertyChanged).

Теперь вы можете видеть, как DataTemplate для шаблона элемента списка привязан к экземплярам ImageUnit, хранящимся в коллекции Images. Проблема в том, что у меня там нет изображений. Чтобы исправить это, вы можете видеть, что есть обработчик события Button_Click, объявленный для кнопки, которая обрабатывает добавление изображения. Вот что у меня есть в коде:

private void Button_Click(object sender, RoutedEventArgs e)
{
PhotoChooserTask task = new PhotoChooserTask();
task.Completed += new EventHandler<PhotoResult>(task_Completed);
task.Show();
}

Это просто вызовет программу выбора фотографий. Не забудьте добавить соответствующую ссылку:

using Microsoft.Phone.Tasks;

И обработчик события task_Completed выглядит так:

void task_Completed(object sender, PhotoResult e)
{
    ImageUnit unit = new ImageUnit();
    unit.ImageName = Guid.NewGuid().ToString() + ".jpg";
    byte[] b = new byte[e.ChosenPhoto.Length];
    e.ChosenPhoto.Read(b, 0, Convert.ToInt32(e.ChosenPhoto.Length));
    unit.ImageContents = b;

    Images.Add(unit);
}

Таким образом, новый экземпляр ImageUnit создается для каждого выбранного изображения. Затем полученный поток изображений преобразуется в байтовый массив и сохраняется. Кроме того, новый GUID генерируется для представления уникального имени изображения (используется для его загрузки). Позже я могу добавить собственное именование, но чтобы показать идею приложения, автоматически назначается имя.

Запустив приложение, я могу добавить пару изображений, и вот что я получаю: