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

0

Из приведенных ранее в данной книге примеров можно догадаться, что полосы прокрутки и ползунки не имеют такого широкого применения в приложениях для устройств с мультисенсорным вводом, как в средах, где основной ввод осуществляется посредством мыши. В примере предыдущей главы ScrollViewer отвечает непосредственно на касание, а не на какие-либо манипуляции с его полосами прокрутки, которые существуют практически исключительно как концепции.

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

Control (абстрактный)

RangeBase (абстрактный) ProgressBar

ScrollBar (запечатанный) Slider

Класс RangeBase (Диапазон) описывает свойства Minimum (Минимум), Maximum (Максимум), SmallChange (Небольшое изменение) и LargeChange (Большое изменение) для определения параметров прокрутки, а также свойство Value для хранения выбора пользователя и событие ValueChanged (Значение изменилось), которое сигнализирует об изменении значения Value. (Обратите внимание, что ProgressBar (Индикатор выполнения) также наследуется от RangeBase, но его свойство Value всегда управляется программно, а не задается пользователем.)

Я остановил свой выбор на Slider в качестве примера, потому что его версия в Windows Phone 7 кажется более соответствующей приложению для телефона, чем ScrollBar. Наша задача – с помощью трех элементов управления Slider создать приложение ColorScroll, имеющее следующий интерфейс:

Самый простой способ создать такой интерфейс – с помощью вложенных сеток. Возьмем сетку с тремя строками и тремя столбцами, в которых располагаются три элемента управления Slider и шесть элементов TextBlock. Эту сетку поместим в другую сетку, включающую всего две ячейки. В ее второй ячейке (не занятой первым Grid) располагается элемент Rectangle, и значением его свойства Fill является SolidColorBrush, цвет которого определяется значениями, выбранными с помощью ползунков.

Больший Grid с двумя ячейками является обычным Grid под именем ContentPanel. Являются ли эти две ячейки двумя строками или двумя столбцами, определяется в файле выделенного кода на основании текущего значения свойства Orientation.

XAML-файл включает коллекцию Resources с описаниями Style для TextBlock и Slider:

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

<phone:PhoneApplicationPage.Resources>

<Style x:Key="textStyle" TargetType="TextBlock">

<Setter Property="HorizontalAlignment" Value="Center" /> </Style>

<Style x:Key="sliderStyle" TargetType="Slider"> <Setter Property="Minimum" Value="0" /> <Setter Property="Maximum" Value="255" /> <Setter Property="Orientation" Value="Vertical" /> </Style>

</phone:PhoneApplicationPage.Resources>

Style всего с одним Setter кажется несколько излишним, но он не помешает, если в будущем предполагается добавить другой Setter для Margin или FontSize. Диапазон допустимых значений по умолчанию для Slider – от 0 до 10. Я изменил его, чтобы привести в соответствие однобайтовому значению.

У ScrollBar и Slider есть собственные свойства Orientation, совершенно не связанные со свойством Orientation класса PhoneApplicationPage, но имеющие некоторое отношение к свойству Orientation класса StackPanel, поскольку совместно используют одно и то же перечисление Orientation со значениями Horizontal и Vertical.

По умолчанию свойство Orientation класса Slider имеет значение Horizontal. (Для ScrollBar это значение Vertical; в чем разница, я никогда не понимал.)

По умолчанию предельно верхнее положение вертикального Slider ассоциировано со значением Maximum. Это вполне подходит для данного приложения, но положение вещей можно изменить, задав свойству IsDirectionReversed (Изменить направление) значение true.

Рассмотрим панель содержимого полностью:

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

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

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

<Rectangle Name="rect" Grid.Row="0" Grid.Column="0" />

<Grid Name="controlGrid" Grid.Row="1" Grid.Column="0"> <Grid.RowDefinitions>

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

<Grid.ColumnDefinitions>

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

<!– Красный столбец –> <TextBlock Grid.Column="0" Grid.Row="0" Text="Red" Foreground="Red"

Style="{StaticResource textStyle}" />

<Slider Name="redSlider" Grid.Column="0" Grid.Row="1"

Foreground="Red"

Style="{StaticResource sliderStyle}" ValueChanged="0nSliderValueChanged" />

<TextBlock Name="redText" Grid.Column="0" Grid.Row="2" Text="0"

Foreground="Red"

Style="{StaticResource textStyle}" />

<!– Зеленый столбец –> <TextBlock Grid.Column="1" Grid.Row="0" Text="Green" Foreground="Green"

Style="{StaticResource textStyle}" />

<Slider Name="greenSlider" Grid.Column="1" Grid.Row="1" Foreground="Green"

Style="{StaticResource sliderStyle}" ValueChanged="0nSliderValueChanged" />

<TextBlock Name="greenText" Grid.Column="1" Grid.Row="2" Text="0"

Foreground="Green"

Style="{StaticResource textStyle}" />

<!– Синий столбец — > <TextBlock Grid.Column="2" Grid.Row="0" Text="Blue" Foreground="Blue"

Style="{StaticResource textStyle}" />

<Slider Name="blueSlider" Grid.Column="2" Grid.Row="1" Foreground="Blue"

Style="{StaticResource sliderStyle}" ValueChanged="0nSliderValueChanged" />

<TextBlock Name="blueText" Grid.Column="2" Grid.Row="2" Text="0"

Foreground="Blue"

Style="{StaticResource textStyle}" />

</Grid> </Grid>

Для всех событий ValueChanged всех элементов управления Slider задан один и тот же обработчик. Этот обработчик использует очень простой подход и не утруждает себя определять, какой именно из Slider сформировал событие:

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

void 0nSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double>

args) {

Color clr = Color.FromArgb(255, (byte)redSlider.Value,

(byte)greenSlider.Value, (byte)blueSlider.Value);

rect.Fill = new SolidColorBrush(clr);

redText.Text = clr.R.ToString("X2"); greenText.Text = clr.G.ToString("X2"); blueText.Text = clr.B.ToString("X2");

}

Как можно заметить, XAML-файл не инициализирует свойство Value ни одного из Slider, и вот почему.

При создании страницы создаются различные элементы и элементы управления, обработчики событий подключаются к событиям и задаются значения свойств. Когда при создании этой страницы свойству Value любого из Slider задается какое-либо значение, этот Slider формирует событие ValueChanged. Весьма вероятно, что это приведет к вызову метода OnSliderValueChanged (При изменении значения ползунка) класса MainPage до того, как страница будет полностью создана. Но метод OnSliderValueChanged ссылается и на другие элементы дерева визуальных элементов. Если эти элементы не будут существовать на этот момент, возникнет ошибка времени выполнения.

Хотите увидеть это в действии? Попробуйте задать

<Setter Property="Value" Value="128" />

в описании Style для Slider.

Формирование события до того, как дерево визуальных элементов полностью построено, является общей проблемой. Чтобы избежать этого, можно либо повысить надежность обработчиков событий проверкой элементов и элементов управления на эквивалентность null, либо можно сделать то, что сделал я в ColorScroll: все свойства, формирующие события на странице, задаются в конструкторе класса после вызова метода InitializeComponent, когда дерево визуальных элементов уже полностью сформировано:

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

public MainPage() {

InitializeComponent();

redSlider.Value = 128; greenSlider.Value = 128; blueSlider.Value = 128;

}

Для обработки изменения ориентации телефона MainPage перегружает свой метод OnOrientationChanged. Среди аргументов этого события имеется свойство Orientation типа PageOrientation.

Полезно знать, что значениями перечисления PageOrientation являются битовые флаги со следующими значениями:

None

0000-0000

Portrait

0000-0001

Landscape

0000-0010

PortraitUp

0000-0101

PortraitDown

0000-1001

LandscapeLeft

0001-0010

LandscapeRight

0010-0010

Проверка заданной ориентации осуществляется посредством побитовой операции ИЛИ между свойством Orientation и членами Portrait или Landscape с последующей проверкой на ненулевой результат. Это несколько упрощает код:

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

protected override void OnOrientationChanged(OrientationChangedEventArgs args) {

ContentPanel.RowDefinitions.Clear(); ContentPanel.ColumnDefinitions.Clear();

// Альбомный

if ((args.Orientation & PageOrientation.Landscape) != 0) {

ColumnDefinition coldef = new ColumnDefinition(); coldef.Width = new GridLength(1, GridUnitType.Star); ContentPanel.ColumnDefinitions.Add(coldef);

coldef = new ColumnDefinition();

coldef.Width = new GridLength(1, GridUnitType.Star); ContentPanel.ColumnDefinitions.Add(coldef);

Grid.SetRow(controlGrid, 0); Grid.SetColumn(controlGrid, 1);

}

// Портретный

else {

RowDefinition rowdef = new RowDefinition(); rowdef.Height = new GridLength(1, GridUnitType.Star); ContentPanel.RowDefinitions.Add(rowdef);

rowdef = new RowDefinition();

rowdef.Height = new GridLength(1, GridUnitType.Star); ContentPanel.RowDefinitions.Add(rowdef);

Grid.SetRow(controlGrid, 1); Grid.SetColumn(controlGrid, 0);

}

base.OnOrientationChanged(args);

}

Объект ContentPanel должен переключаться между двумя строками для портретного и двумя столбцами для альбомного режима отображения, поэтому он создает объекты GridDefinition (Описание сетки) и ColumnDefinition для новой ориентации. (Или он мог бы создать эти коллекции заранее и просто переключаться между ними. Или он мог бы создать объект Grid 2 х 2 в XAML-файле и задать неиспользуемой строке или столбцу нулевую высоту или ширину.)

Элемент Rectangle всегда располагается в ячейке с параметрами Grid.Row и Grid.Column равными нулю. Но присоединенные свойства Grid.Row и Grid.Column сетки controlGrid (Сетка элементов управления) должны быть заданы с использованием синтаксиса, который мы обсуждали в предыдущей главе.

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

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

По теме:

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