Статьи

Пользовательская обрезка изображений в Windows Phone 7 — часть 1 из 2

Если вы попытались использовать PhotoChooserTask по умолчанию для обрезки некоторых изображений, вы, возможно, уже заметили, что он не работает должным образом — приложение никогда не получает обрезанный поток. Мое решение этого? Моя собственная реализация механизма обрезки. В этих двух статьях я не собираюсь разрабатывать пользовательский элемент управления, а скорее покажу, как этот механизм может быть реализован в контексте страницы приложения.

Прежде всего, я создал базовый макет страницы:

<phone:PhoneApplicationPage 
x:Class="WP7Sandbox.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="696"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">

<Grid x:Name="LayoutRoot" Background="Transparent">
<Image Height="309" Margin="0" Name="image1" Stretch="Fill" Width="320" />
</Grid>

<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.download.rest.png" Text="Resize" IsEnabled="False"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.upload.rest.png" Text="Move" IsEnabled="True"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.check.rest.png" Text="Accept" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.close.rest.png" Text="Cancel" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

Наиболее важной частью здесь является управление изображением — оно в основном определяет область обрезки. Панель приложения предназначена для переключения между режимами (например, между изменением размера области обрезки и перемещением области обрезки). Обратите внимание, что значки, которые я использую для кнопок значков на панели приложения, — это те, которые поставляются в комплекте с SDK. Если вы хотите добавить свои собственные значки, вы можете сделать это, просто заменив URL (убедитесь, что вы используете правильную папку).

Обратите внимание, что по умолчанию кнопка « Переместить» по умолчанию отключена. Это происходит потому, что когда приложение запускается (или когда пользователь переходит на страницу), режим по умолчанию устанавливается на Изменение размера — считайте его будущим переключателем, который позволит пользователю переключаться между этими двумя режимами.

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

Полученное изображение фактически является фактическим изображением из галереи Windows Phone 7 по умолчанию. Он доступен в эмуляторе через PhotoChooserTask. Фактически, я запускаю эту задачу только тогда, когда страница загружается:

// Constructor
public MainPage()
{
InitializeComponent();

PhotoChooserTask task = new PhotoChooserTask();
task.Show();
task.Completed += new EventHandler<PhotoResult>(task_Completed);
}

Событие завершения задачи связано с task_Completed :

void task_Completed(object sender, PhotoResult e)
{
BitmapImage image = new BitmapImage();
image.SetSource(e.ChosenPhoto);
image1.Source = image;

SetPicture();
}

Из этого обработчика событий становится очевидным, что возвращенное изображение фактически передается в виде потока. Один метод, о котором вы, вероятно, еще ничего не знаете, это SetPicture, и именно здесь я определяю область обрезки.

void SetPicture()
{
Rectangle rect = new Rectangle();
rect.Opacity = .5;
rect.Fill = new SolidColorBrush(Colors.White);
rect.Height = image1.Height;
rect.MaxHeight = image1.Height;
rect.MaxWidth = image1.Width;
rect.Width = image1.Width;
rect.Stroke = new SolidColorBrush(Colors.Red);
rect.StrokeThickness = 2;
rect.Margin = image1.Margin;
rect.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(rect_ManipulationDelta);

LayoutRoot.Children.Add(rect);
}

Определенный прямоугольник является полупрозрачным и размещается точно над выбранным изображением.

Теперь вы можете видеть, что я подключаю обработчик событий ManipulationDelta — это обработчик событий, который отслеживает изменение состояния объекта, в частности его размер и размещение в контейнере. Когда пользователь касается прямоугольника, вам нужно дать ему возможность изменить его размер. Вот именно это и сделано:

void rect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
Rectangle r = (Rectangle)sender;
r.Width -= e.DeltaManipulation.Translation.X;
r.Height -= e.DeltaManipulation.Translation.Y;
}

E.DeltaManipulation.Translation.X и e.DeltaManipulation.Translation.Y изменяются в зависимости от скорости выполнения манипуляции и могут быть положительными (если выполняется минимизация) и отрицательными (если выполняется максимизация), поэтому имеет смысл вычитать перевод по конкретной оси от ширины или высоты прямоугольника.

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

Теперь пришло время реализовать метод переключения. Для этого прежде всего необходимо изменить панель приложения, чтобы кнопка « Изменить размер» и кнопка « Переместить» были привязаны к обработчику событий, который будет срабатывать при нажатии на них.

Итак, вот что я придумал:

<shell:ApplicationBarIconButton IconUri="/Images/appbar.download.rest.png" Text="Resize" Click="btn_Click"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.upload.rest.png" Text="Move" Click="btn_Click" IsEnabled="False"/>

Обратите внимание, что обе кнопки подключены к одному и тому же обработчику событий — в конечном итоге состояние текущей кнопки определяется общей логической переменной. Обратите внимание, что кнопки не названы — из-за природы содержимого ApplicationBar вы не добьетесь многого, назвав кнопки в выделенном коде по их именам — необходим подход на основе индекса.

Итак, вот что у меня есть для обработчика событий:

private void btn_Click(object sender, EventArgs e)
{
IApplicationBarIconButton button;
if (isMove)
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = false;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = true;
isMove = false;
}
else
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = true;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = false;
isMove = true;
}
}

Обратите внимание, что я получаю кнопки в соответствии с их положением на панели приложения. Не забывайте, что подсчет начинается с индекса ноль (0). Когда кнопка Resize нажата, Move активируется, а Resize деактивируется. То же самое происходит и наоборот — как тумблер.

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