Главная » Разработка для Windows Phone 7 » Пользовательские элементы управления UserControl

0

Как известно, элементы управления, которые предполагается использовать только для специальных целей в собственных приложениях, проще всего создавать, наследуясь от UserControl. Для этого просто в XAML-файле определяем дерево визуальных элементов для элемента управления.

Можно применить аналогичный подход с ContentControl, но в этом случае XAML-файл будет включать определения Style и ControlTemplate. Преимущество такого подхода в использовании свойства Content для собственных целей элемента управления.

Также можно наследоваться от Control. Такой подход имеет смысл, если производный класс располагается в библиотеке, и вы хотите, чтобы элемент управления имел заменяемый шаблон. Style и ControlTemplate для темы по умолчанию располагаются в файле generic.xaml библиотеки.

В библиотеке Petzold.Phone.Silverlight есть пример такого элемента управления: XYSlider. Этот элемент управления позволяет пользователю перемещать Thumb по двухмерной поверхности, сохраняя его местоположение в свойстве Value типа Point. Но координаты нормализованы в диапазоне от 0 до 1 относительно верхнего левого угла. Такая нормализация устраняет необходимость задания значений Minimum и Maximum, как в обычном Slider.

Кроме свойства Value, класс XYSlider также определяет свойство PlaneBackground (Плоский фон) типа Brush. Это поверхность, по которой перемещается Thumb, и вскоре мы увидим, почему ее надо выносить отдельно от обычного свойства Background объекта Control.

Как видно из его атрибутов, в шаблоне для этого класса должны присутствовать два элемента: Canvas под именем «PlanePart» (Часть-описание плоскости) и Thumb под именем «ThumbPart» (Часть-описание бегунка):

Проект Silverlight: Petzold.Phone.Silverlight Файл: XYSlider.cs (фрагмент)

[TemplatePartAttribute(Name = "PlanePart", Type = typeof(Canvas))] [TemplatePartAttribute(Name = "ThumbPart", Type = typeof(Thumb))]

public class XYSlider : Control {

Canvas planePart;

Thumb thumbPart;

Point absoluteThumbPoint;

public event RoutedPropertyChangedEventHandler<Point> ValueChanged;

public static readonly DependencyProperty PlaneBackgroundProperty = DependencyProperty.Register("PlaneBackground", typeof(Brush), typeof(XYSlider),

new PropertyMetadata(new SolidColorBrush(Colors.Gray)));

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Point), typeof(XYSlider),

new PropertyMetadata(new Point(0.5, 0.5), 0nValueChanged));

public XYSlider() {

this.DefaultStyleKey = typeof(XYSlider);

}

public Brush PlaneBackground {

set { SetValue(PlaneBackgroundProperty, value); }

get { return (Brush)GetValue(PlaneBackgroundProperty); }

}

public Point Value {

set { SetValue(ValueProperty, value); } get { return (Point)GetValue(ValueProperty); }

Производный от Control объект получает уведомление о том, что его шаблон готов, посредством вызова метода OnApplyTemplate (При применении шаблона). Это подходящий момент для класса вызвать метод GetTemplateChild, передав в него имена, указанные в атрибутах. Правильным поведением класса будет допускать возможность отсутствия некоторых частей, даже частей, играющих ключевую роль в правильном функционировании элемента управления:

Проект Silverlight: Petzold.Phone.Silverlight Файл: XYSlider.cs (фрагмент)

public override void OnApplyTemplate() {

if (planePart != null) {

planePart.SizeChanged -= OnPlaneSizeChanged;

}

if (thumbPart != null) {

thumbPart.DragDelta -= OnThumbDragDelta;

}

planePart = GetTemplateChild("PlanePart") as Canvas; thumbPart = GetTemplateChild("ThumbPart") as Thumb;

if (planePart != null && thumbPart != null) {

planePart.SizeChanged += OnPlaneSizeChanged; thumbPart.DragStarted += OnThumbDragStarted; thumbPart.DragDelta += OnThumbDragDelta; ScaleValueToPlane(this.Value);

}

base.OnApplyTemplate();

}

В случае наличия Canvas и Thumb определяется обработчик событий SizeChanged объекта Canvas и событий DragStarted и DragDelta объекта Thumb.

Обработчик событий SizeChanged обновляет местоположение Thumb относительно Canvas; обработчик DragDelta обновляет значение свойства Value элемента управления XYSlider:

Проект Silverlight: Petzold.Phone.Silverlight Файл: XYSlider.cs (фрагмент)

void OnPlaneSizeChanged(object sender, SizeChangedEventArgs args) {

ScaleValueToPlane(this.Value);

}

void OnThumbDragStarted(object sender, DragStartedEventArgs args) {

absoluteThumbPoint = new Point(Canvas.GetLeft(thumbPart),

Canvas.GetTop(thumbPart));

}

void OnThumbDragDelta(object sender, DragDeltaEventArgs args) {

absoluteThumbPoint.X += args.HorizontalChange; absoluteThumbPoint.Y += args.VerticalChange;

Value = new Point(Math.Max(0,

Math.Min(1, absoluteThumbPoint.X / planePart.ActualWidth)), Math .Max(0,

Math.Min(1, absoluteThumbPoint.Y / planePart.ActualHeight)));

}

void ScaleValueToPlane(Point point) {

if (planePart != null && thumbPart != null) {

Canvas.SetLeft(thumbPart, planePart.ActualWidth * point.X); Canvas.SetTop(thumbPart, planePart.ActualHeight * point.Y);

}

}

Обработчик событий изменения значения свойства Value также обновляет координаты Thumb и формирует событие ValueChanged:

Проект Silverlight: Petzold.Phone.Silverlight Файл: XYSlider.cs (фрагмент)

static void 0nValueChanged(Dependency0bject obj,

DependencyPropertyChangedEventArgs args)

{

(obj as XYSlider).0nValueChanged((Point)args.0ldValue, (Point)args.NewValue);

}

protected virtual void 0nValueChanged(Point oldValue, Point newValue) {

if (newValue.X < 0 || newValue.X > 1 || newValue.Y < 0 || newValue.Y > 1) throw new Argument0ut0fRangeException("Value",

"Value property must be Point with coordinates between 0 and 1");

ScaleValueToPlane(newValue);

if (ValueChanged != null) ValueChanged(this,

new RoutedPropertyChangedEventArgs<Point>(oldValue, newValue));

}

Style и ControlTemplate по умолчанию располагаются в файле generic.xaml:

Проект Silverlight: Petzold.Phone.Sivlerlight Файл: Themes/generic.xaml (фрагмент)

<Style TargetType="local:XYSlider"> <Setter Property="Template"> <Setter.Value>

<ControlTemplate TargetType="local:XYSlider">

<Border Background="{TemplateBinding Background}"

BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">

<Canvas Name="PlanePart"

Background="{TemplateBinding PlaneBackground}" Margin="4 8"> <Thumb Name="ThumbPart"> <Thumb.Style>

<Style TargetType="Thumb">

<Setter Property="Width" Value="96" /> <Setter Property="Height" Value="96" /> <Setter Property="Template"> <Setter.Value>

<ControlTemplate TargetType="Thumb"> <Path Name="path"

Stroke="{StaticResource PhoneForegroundBrush}" StrokeThickness="{StaticResource

PhoneStrokeThickness}"

Fill="Transparent"> <Path.Data>

<GeometryGroup FillRule="Nonzero">

<EllipseGeometry RadiusX="48" RadiusY="48" /> <EllipseGeometry RadiusX="6" RadiusY="6" /> <LineGeometry StartPoint="-4 8 0" EndPoint="-6 0" /> <LineGeometry StartPoint="4 8 0" EndPoint="6 0" /> <LineGeometry StartPoint="0 -48" EndPoint="0 -6" /> <LineGeometry StartPoint="0 48" EndPoint="0 6" /> </GeometryGroup> </Path.Data> </Path> </ControlTemplate> </Setter.Value> </Setter> </Style> </Thumb.Style> </Thumb> </Canvas> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>

Border окружает весь элемент управления. Canvas под именем «PlanePart» задано свойство Margin, величина которого соответствует половине размера Thumb. Это позволяет центрировать Thumb для обозначения точки на плоскости, оставаясь при этом полностью в рамках элемента управления. В ControlTemplate для Control имеется другой ControlTemplate для Thumb, который формирует своего рода шаблон «мишени».

Протестируем приложение в проекте WorldMap. Область содержимого включает XYSlider, значением свойства PlaneBackground которого задан ImageBrush, использующий карту мира:

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

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

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

<petzold:XYSlider Name="xySlider" Grid.Row="0"

ValueChanged="OnXYSliderValueChanged"> <petzold:XYSlider.PlaneBackground>

<!– Изображение любезно предоставлено NASA/JPL-Caltech (http://maps.jpl.nasa.gov). –>

<ImageBrush ImageSource="Images/ear0xuu2.jpg" /> </petzold:XYSlider.PlaneBackground> </petzold:XYSlider>

<TextBlock Name="txtblk" Grid.Row="1"

HorizontalAlignment="Center" />

</Grid>

Файл выделенного кода посвящен обработке события ValueChanged, формируемого XYSlider, и преобразованию нормализованных координат Point в широту и долготу:

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

public partial class MainPage : PhoneApplicationPage {

public MainPage() {

InitializeComponent(); DisplayCoordinates(xySlider.Value);

}

void 0nXYSliderValueChanged(object sender,

RoutedPropertyChangedEventArgs<Point> args)

{

DisplayCoordinates(args.NewValue);

}

void DisplayCoordinates(Point point) {

double longitude = 360 * point.X – 180; double latitude = 90 – 180 * point.Y;

txtblk.Text = String.Format("Longitude: {0:F0} Latitude: {1:F0}",

longitude, latitude);

}

}

И вот что мы имеем:

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

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

По теме:

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