Главная » Разработка для Windows Phone 7 » WriteableBitmap и UIElement

0

Класс WriteableBitmap может получить визуальные элементы объекта UIElement двумя способами. В первом случае используется один из конструкторов:

WriteableBitmap writeableBitmap = new WriteableBitmap(element, transform);

Аргумент element (элемент) типа UIElement, и аргумент transform типа Transform. Этот конструктор создает растровое изображение на основании размера аргумента element, с учетом изменений, обусловливаемых аргументом transform (для которого можно задать значение null).

На растровой матрице формируется визуальное представление элемента и всех его дочерних визуальных элементов. Но любой RenderTransform, применяемый к элементу, игнорируется. Предоставление возможности применения этой трансформации по желанию является причиной введения второго аргумента. Результирующее растровое изображение берет за основу максимальные значения координат трансформированного элемента по вертикали и по горизонтали. Любая часть элемента, которая в результате трансформации оказалась в отрицательной области (слева или выше исходного элемента), отсекается.

Рассмотрим простой пример. В качестве фона сетки для содержимого используется текущий контрастный цвет. Сетка включает TextBlock и элемент Image:

Проект Silverlight: RecursivePageCaptures Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Background="{StaticResource PhoneAccentBrush}">

<TextBlock Text="Tap anywhere to capture page" HorizontalAlignment="Center" VerticalAlignment="Center" />

<Image Name="img"

Stretch="Fill" />

</Grid>

В элементе Image пока нет растрового изображения для отображения, но когда оно появится, соотношение его размеров будет проигнорировано, и изображение заполнит всю сетку для содержимого, перекрывая TextBlock.

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

Проект Silverlight: RecursivePageCaptures Файл: MainPage.xaml.cs (фрагмент)

public partial class MainPage : PhoneApplicationPage {

public MainPage() {

InitializeComponent();

}

protected override void OnManipulationStarted(ManipulationStartedEventArgs args) {

img.Source = new WriteableBitmap(this, null);

args.Complete();

args.Handled = true;

base.OnManipulationStarted(args);

}

}

При первом запуске приложения экран выглядит следующим образом:

После первого касания вся страница превращается в растровое изображение, отображаемое элементом Image:

Не забываем, что свойству Background перехваченного объекта PhoneApplicationPage задано значение по умолчанию null. Именно поэтому мы видим исходный фон панели содержимого за перехваченными заголовками. При последующих касаниях экрана происходит повторное сохранение содержимого страницы, включая сохраненный ранее элемент Image:

Эти элементы могут быть «сохранены» растровым изображением, только став его частью.

У класса WriteableBitmap также имеется метод Render с такими же двумя аргументами, как и в только что продемонстрированном конструкторе:

writeableBitmap.Render(element, transform);

Чтобы получить фактическое растровое изображение, содержащее все визуальные элементы, переданные в аргументе element, за вызовом Render должен следовать вызов Invalidate:

writeableBitmap.Invalidate();

Очевидно что на момент вызова этих методов WriteableBitmap уже создан, т.е. имеет фиксированный размер. На основании размера элемента и трансформации могут быть обрезаны некоторые (или все) элементы.

Если вызвать метод Render для вновь созданного элемента Button, к примеру, обнаружится, что он не работает. У этого нового Button нулевой размер. Чтобы задать элементу отличный от нулевого размер, необходимо вызвать методы Measure и Arrange. Однако в большинстве случаев мне не удавалось задать некоторый размер элементу даже путем вызова этих методов. Кажется, эта техника обеспечивает намного лучшие результаты, если элемент уже является частью дерева визуальных элементов. Лучше всего она работает с элементами Image и производными от Shape.

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

Область содержимого в приложении SubdivideBitmap (Разделение растрового изображения) включает TextBlock и Grid с двумя строками и двумя столбцами одинакового размера. В каждой из четырех ячеек Grid располагается элемент Image, имя которого соответствует его местоположению в сетке. Например, изображение с именем imgUL располагается в верхнем левом углу, а изображение с именем imgLR – в нижнем правом[18].

Проект Silverlight: SubdivideBitmap Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="txtblk"

Text="Touch to choose image" HorizontalAlignment="Center" VerticalAlignment="Center" />

<Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid.RowDefinitions>

<RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions>

<Image Name="imgUL" Grid.Row="0" Grid.Column="0" Margin="2" /> <Image Name="imgUR" Grid.Row="0" Grid.Column="1" Margin="2" /> <Image Name="imgLL" Grid.Row="1" Grid.Column="0" Margin="2" /> <Image Name="imgLR" Grid.Row="1" Grid.Column="1" Margin="2" /> </Grid> </Grid>

Файл выделенного кода класса MainPage настроен для реализации задачи PhotoChooserTask: объект PhotoChooserTask определен как поле, и в конце конструктора прикреплен обработчик событий Completed.

Проект Silverlight: SubdivideBitmap Файл: MainPage.xaml.cs (фрагмент)

public partial class MainPage : PhoneApplicationPage {

PhotoChooserTask photoChooser = new PhotoChooserTask();

public MainPage() {

InitializeComponent();

photoChooser.Completed += OnPhotoChooserCompleted;

}

protected override void OnManipulationStarted(ManipulationStartedEventArgs args) {

int dimension = (int)Math.Min(ContentPanel.ActualWidth,

ContentPanel.ActualHeight) – 8;

photoChooser.PixelHeight = dimension; photoChooser.PixelWidth = dimension; photoChooser.Show();

args.Complete();

args.Handled = true;

base.OnManipulationStarted(args);

}

}

После этого перегруженный OnManipulationStarted вызывает метод Show объекта PhotoChooserTask, запрашивая квадратную растровую матрицу, размеры которой определяются размером панели для содержимого. Из этого размера вычитаются 8 пикселов в пользу свойства Margin, заданного для каждого элемента Image в XAML-файле.

Когда PhotoChooserTask формирует событие Completed, вызывается обработчик этого события. Он начинает выполнение с создания объекта BitmapImage из потока, который ссылается на выбранное растровое изображение. Затем создается элемент Image (под именем imgBase) для отображения этого растрового изображения. Заметьте, элемент Image не является частью дерева визуальных элементов. Он существует исключительно для исполнения роли объекта-источника для вызовов Render.

Проект Silverlight: SubdivideBitmap Файл: MainPage.xaml.cs (фрагмент)

void OnPhotoChooserCompleted(object sender, PhotoResult args) {

if (args.Error != null || args.ChosenPhoto == null) return;

BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(args.ChosenPhoto);

Image imgBase = new Image(); imgBase.Source = bitmapImage; imgBase.Stretch = Stretch.None;

// Верхний левый

WriteableBitmap writeableBitmap = new WriteableBitmap(bitmapImage.PixelWidth /

bitmapImage.PixelHeight /

writeableBitmap.Render(imgBase, null); writeableBitmap.Invalidate(); imgUL.Source = writeableBitmap;

// Верхний правый

writeableBitmap = new WriteableBitmap(bitmapImage.PixelWidth / 2,

bitmapImage.PixelHeight / 2); TranslateTransform translate = new TranslateTransform(); translate.X = -bitmapImage.PixelWidth / 2; writeableBitmap.Render(imgBase, translate); writeableBitmap.Invalidate(); imgUR.Source = writeableBitmap;

// Нижний левый

writeableBitmap = new WriteableBitmap(bitmapImage.PixelWidth / 2,

bitmapImage.PixelHeight / 2);

translate.X = 0;

translate.Y = -bitmapImage.PixelHeight / 2; writeableBitmap.Render(imgBase, translate); writeableBitmap.Invalidate(); imgLL.Source = writeableBitmap;

// Нижний правый

writeableBitmap = new WriteableBitmap(bitmapImage.PixelWidth / 2,

bitmapImage.PixelHeight / 2); translate.X = -bitmapImage.PixelWidth / 2; writeableBitmap.Render(imgBase, translate); writeableBitmap.Invalidate(); imgLR.Source = writeableBitmap;

txtblk.Visibility = Visibility.Collapsed;

}

Далее обработчик события Completed создает четыре объекта WriteableBitmap, размеры каждого из которых соответствуют половине размеров исходного изображения. (Вычисления выполняются на основании размеров BitmapImage, а не размеров Image, которые на данный момент равны нулю.)

Также для всех вызовов Render, кроме первого, задан TranslateTransform, который обеспечивает смещение влево или вверх (или в обоих направлениях) на половину размера растрового изображения. За каждым вызовом Render следует вызов Invalidate. После этого каждый WriteableBitmap задается как значение свойства Source соответствующего элемента Image из XAML-файла. Задание свойства Margin для этих элементов Image обеспечивает их визуальное разделение, позволяя отчетливо видеть, что теперь мы работаем с четырьмя отдельными элементами Image:

Обратите внимание, что в коде используется всего один объект TranslateTransform. Как правило, для каждого элемента задается собственная трансформация, но если вы хотите применить одну и ту же трансформацию ко всем элементам, можно организовать ее совместное использование. В данном случае TranslateTransform используется временно исключительно в целях формирования визуального представления.

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

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии