Главная » Разработка для Windows Phone 7 » Конвертеры привязок Windows Phone 7

0

Экспериментируя с приложением SliderBindings (или увидев снимок экрана выше), можно заметить, что TextBlock по-разному отображает значения Slider: то это целое число, то десятичная дробь с одним или несколькими знаками после запятой, но чаще всего это числа с полной выкладкой 15 разрядов после запятой, предусмотренных для значений с плавающей точкой двойной точности.

Можно ли каким-то образом исправить это?

Да, безусловно. В классе Binding есть свойство Converter (Конвертер). Это свойство ссылается на класс, который преобразовывает данные по пути от источника к цели и (если необходимо) в обратном направлении. Очевидно, что некоторое неявное преобразование данных

выполняется в любом случае, поскольку числа преобразовываются в строки и строки преобразовываются в числа. Но у нас есть возможность обеспечить более явную поддержку этим преобразованиям.

Свойство Converter класса Binding типа IValueConverter (Конвертер значений). Это интерфейс, которому необходимы лишь два метода: Convert (Преобразовать) и ConvertBack (Преобразовать в обратном направлении). Метод Convert обрабатывает данные на пути от источника к целевому объекту, и метод ConvertBack обеспечивает преобразование в обратном направлении для двунаправленных привязок.

Если класс конвертера не предполагается использовать с двунаправленными привязками, просто возвращайте null из ConvertBack.

Чтобы добавить простой конвертер в SliderBindings, введем в проект новый класс и назовем его TruncationConverter (Конвертер для усечения разрядов). На самом деле этот класс уже есть в проекте, рассмотрим его:

Проект Silverlight: SliderBindings Файл: TruncationConverter.cs

using System;

using System.Globalization; using System.Windows.Data;

namespace SliderBindings {

public class TruncationConverter : IValueConverter {

public object Convert(object value, Type targetType,

object parameter, CultureInfo culture)

{

if (value is double)

return Math.Round((double)value);

return value;

}

public object ConvertBack(object value, Type targetType,

object parameter, CultureInfo culture)

{

return value;

}

}

}

Аргумент value метода Convert – это объект, передаваемый от источника к цели. Этот метод просто проверяет, является ли он типа double. Если да, он явно приводит его к double для метода Math.Round.

MainPage.xaml должен ссылаться на этот класс, т.е. мы должны объявить пространство имен XML:

xmlns:local="clr-namespace:SliderBindings"

После этого класс TruncationConverter задается как ресурс:

<phone:PhoneApplicationPage.Resources>

<local:TruncationConverter x:Key="truncate" />

</phone:PhoneApplicationPage.Resources>

Все эти дополнения уже сделаны в файле MainPage.xaml проекта SliderBindings.

После этого расширение разметки Binding ссылается на этот ресурс:

<TextBlock Name="txtblk"

Text="{Binding ElementName=slider, Path=Value,

Converter={StaticResource truncate}}" … />

Я разнес расширение разметки на три строки, чтобы все три компонента было отчетливо видно. Обратите внимание, что StaticResource, другое расширение разметки, встроено в первое расширение разметки, поэтому в конце этого выражения стоят две фигурные скобки.

Теперь число отображаемых знаков при выводе значений в TextBlock ограничено:

Не забывайте указывать конвертер как StaticResource. Часто так и хочется просто присвоить имя ключа в качестве значения свойства Converter привязки:

<!– Это неверно! –> <TextBlock Name="txtblk"

Text="{Binding ElementName=slider, Path=Value,

Converter=truncate}" … />

Я до сих пор нередко допускаю такую ошибку, а потом выискивать причины возникшей проблемы очень сложно.

Описание конвертера как ресурса является, несомненно, самым распространенным подходом при использовании конвертеров, но не единственным. Применяя синтаксис свойство-элемент для Binding, класс TrunctionConverter можно встроить непосредственно в разметку:

<TextBlock . >

<TextBlock.Text>

<Binding ElementName="slider" Path="Value"> <Binding.Converter>

<local:TruncationConverter /> </Binding.Converter> </Binding> </TextBlock.Text> </TextBlock>

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

На самом деле TrucationConverter – ужасный конвертер. Несомненно, он выполняет свою задачу, но при этом не отличается гибкостью. Вы собираетесь вызывать метод Math.Round в классе конвертера, но не было бы лучше иметь опцию для округления значений до определенного количества знаков после запятой? А не имело бы больше смысла обеспечить все виды форматирования: не только для чисел, но и для других типов данных?

Такие чудеса обеспечивает класс из библиотеки Petzold.Phone.Silverlight под именем StringFormatConverter (Конвертер с форматированием строк):

Проект Silverlight: Petzold.Phone.Silverlight Файл: StringFormatConverter.cs

using System;

using System.Globalization; using System.Windows.Data;

namespace Petzold.Phone.Silverlight {

public class StringFormatConverter : IValueConverter {

public object Convert(object value, Type targetType,

object parameter, CultureInfo culture)

{

if (targetType == typeof(string) && parameter is string) return String.Format(parameter as string, value);

return value;

}

public object ConvertBack(object value, Type targetType,

object parameter, CultureInfo culture)

{

return value;

}

}

}

Кроме свойства Converter, в классе Binding также имеется свойство ConverterParameter (Параметр преобразования). Значение этого свойства передается в вызов метода Convert как аргумент parameter (параметр). Здесь метод Convert принимает аргумент parameter как стандартную строку форматирования .NET, которая может использоваться в вызове String.Format.

Чтобы использовать этот конвертер в приложении SliderBindings, необходимо указать ссылку на библиотеку Petzold.Phone.Silverlight. (Это уже сделано.) Также в наш файл уже добавлено объявление пространства имен XML:

xmlns:petzold="clr-

namespace:Petzold.Phone.Silverlight;assembly=Petzold.Phone.Silverlight"

Создадим экземпляр класса StringFormatConverter в коллекции Resources страницы:

<phone:PhoneApplicationPage.Resources>

<petzold:StringFormatConverter x:Key="stringFormat" /> </phone:PhoneApplicationPage.Resources>

Теперь этот конвертер можно использовать в расширении разметки Binding. В качестве значения ConverterParameter зададим строку форматирования .NET с одним полем подстановки:

Text="{Binding ElementName=slider, Path=Value,

Converter={StaticResource stringFormat}, ConverterParameter=…}"

Но при вводе строки форматирования .NET сразу же выявляется проблема. В стандартной строке форматирования .NET используются фигурные скобки. Можно с уверенностью сказать, что синтаксический анализатор XAML при разборе расширения разметки Binding не будет рад встроенным фигурным скобкам.

Простейшее решение – заключить значение ConverterParameter в одинарные кавычки:

Text="{Binding ElementName=slider, Path=Value,

Converter={StaticResource stringFormat}, ConverterParameter='{0:F2}’}"

Средство синтаксического анализа XAML и визуальный дизайнер в Visual Studio тоже не распознают этот синтаксис, но он не создает проблем во время выполнения. Чтобы дизайнер принял такую запись, вставьте пробел (или какой-то другой символ) после первой одинарной кавычки.

Мы знаем, что ConverterParameter используется как первый аргумент при вызове String.Format, поэтому можем немного приукрасить его:

Text="{Binding ElementName=slider, Path=Value,

Converter={StaticResource stringFormat}, ConverterParameter=’The slider is {0:F2}’}"

И получаем вот такой результат:

Относительный источник

Silverlight for Windows Phone поддерживает три основных типа привязок, систематизированных по источнику данных. До сих пор в данной главе мы рассматривали привязки ElementName, в которых привязка ссылается на именованный элемент. Но далее для подключения источника данных мы будем использовать преимущественно свойство Source, а не ElementName.

Третий тип привязок называют RelativeSource (Относительный источник). В Windows Presentation Foundation RelativeSource намного более гибок, чем его версия в Silverlight, которая может не произвести должного впечатления. Одно из применений RelativeSource связано с шаблонами и будет рассмотрено в главе 16. Кроме этого, он позволяет определять привязку, ссылающуюся на свойство того же элемента, обозначаемого как Self (Сам объект). Синтаксис продемонстрирован в следующем приложении:

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

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

HorizontalAlignment="Center" VerticalAlignment="Center">

<TextBlock Text="{Binding RelativeSource={RelativeSource Self},

Path=FontFamily}" />

<TextBlock Text=" – " />

<TextBlock Text="{Binding RelativeSource={RelativeSource Self},

Path=FontSize}" />

<TextBlock Text=" pixels" /> </StackPanel> </Grid>

В качестве значения свойства RelativeSource задается другое расширение разметки, включающее RelativeSource и Self. Path ссылается на другое свойство того же элемента. Таким образом, элементы TextBlock отображают собственные значения FontFamily и FontSize.

Источник «this»

Предположим, поставлена задача вывести на экран множество коротких строк текста, каждую из которых должна быть заключена в рамку. Принято решение создать для этого элемент управления BorderedText (Текст в рамке), унаследовав его от UserControl, и описать его следующим образом:

<petzold:BorderedText Text="Ta Da!"

FontFamily="Times New Roman"

FontSize="9 6"

FontStyle="Italic"

FontWeight="Bold"

TextDecorations="Underline"

Foreground="Red"

Background="Lime"

BorderBrush="Blue"

BorderThickness="8"

CornerRadius="3 6"

Padding="16 4"

HorizontalAlignment="Center" VerticalAlignment="Center" />

Как видно из префикса пространства имен XML, этот класс уже входит в состав библиотеки Petzold.Phone.Silverlight.

BorderedText наследуется от UserControl, и UserControl наследуется от Control. Таким образом, нам известно, что BorderedText уже будет иметь некоторые из этих свойств посредством наследования классов. В самом BorderedText должны быть описаны свойства Text, TextDecorations, CornerRadius и, возможно, еще пара других, что обеспечит классу большую гибкость.

Весьма вероятно, что дерево визуальных элементов файла BorderedText.xaml будет состоять из TextBlock, расположенного в Border. Все многочисленные свойства TextBlock и этого Border должны задаваться из свойств BorderedText.

Один из способов сделать это был представлен в предыдущей главе. В том случае в классе ColorColumn описывались свойства Label и Value. Задание новых значений этим свойствам в элементах дерева визуальных элементов осуществлялось с помощью обработчиков событий изменения значения свойства. Намного проще сделать это с помощью привязки данных.

В файле выделенного кода для BorderedText просто описываются все свойства, которые недоступны посредством наследования от Control:

Проект Silverlight: Petzold.Phone.Silverlight Файл: BorderedText.xaml.cs

using System;

using System.Windows;

using System.Windows.Controls;

namespace Petzold.Phone.Silverlight {

public partial class BorderedText : UserControl {

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(BorderedText), new PropertyMetadata(null));

public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register("TextAlignment", typeof(TextAlignment), typeof(BorderedText),

new PropertyMetadata(TextAlignment.Left));

public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register("TextDecorations", typeof(TextDecorationCollection), typeof(BorderedText), new PropertyMetadata(null));

public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(BorderedText),

new PropertyMetadata(TextWrapping.NoWrap));

public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(BorderedText),

new PropertyMetadata(new CornerRadius()));

public BorderedText() {

InitializeComponent();

public string Text {

set { SetValue(TextProperty, value); }

get { return (string)GetValue(TextProperty); }

}

public TextAlignment TextAlignment {

set { SetValue(TextAlignmentProperty, value); }

get { return (TextAlignment)GetValue(TextAlignmentProperty); }

}

public TextDecorationCollection TextDecorations {

set { SetValue(TextDecorationsProperty, value); } get { return

(TextDecorationCollection)GetValue(TextDecorationsProperty); } }

public TextWrapping TextWrapping {

set { SetValue(TextWrappingProperty, value); }

get { return (TextWrapping)GetValue(TextWrappingProperty); }

}

public CornerRadius CornerRadius {

set { SetValue(CornerRadiusProperty, value); }

get { return (CornerRadius)GetValue(CornerRadiusProperty); }

}

}

}

Много кода, но ничего сложного, ведь это всего лишь описания свойств. Нет никаких обработчиков событий изменения значений свойств. Рассмотрим XAML-файл с Border и TextBlock:

Проект Silverlight: Petzold.Phone.Silverlight Файл: BorderedText.xaml

<UserControl x:Class="Petzold.Phone.Silverlight.BorderedText"

xmlns="http://schemas.microsoft.com/winfx/2 0 0 6/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2 0 0 6/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2 0 0 8" Name="this">

<Border Background="{Binding ElementName=this, Path=Background}"

BorderBrush="{Binding ElementName=this, Path=BorderBrush}" BorderThickness="{Binding ElementName=this, Path=BorderThickness}" CornerRadius="{Binding ElementName=this, Path=CornerRadius}" Padding="{Binding ElementName=this, Path=Padding}">

<TextBlock Text="{Binding ElementName=this, Path=Text}"

TextAlignment="{Binding ElementName=this, Path=TextAlignment}" TextDecorations="{Binding ElementName=this, Path=TextDecorations}"

TextWrapping="{Binding ElementName=this, Path=TextWrapping}" />

</Border> </UserControl>

Обратите внимание, что у корневого элемента есть имя:

Name="this"

Этому корневому элементу можно задать любое имя, но традиционно используется ключевое слово C# this, потому что в контексте XAML-файла this ссылается на текущий экземпляр класса BorderedText. Это хорошо знакомая концепция. Наличие этого имени означает, что есть возможность связывать свойства BorderedText и свойства элементов, образующих дерево визуальных элементов.

В данном файле для свойства Foreground или остальных свойств для задания шрифта не требуется использовать привязки данных, потому что они наследуются по дереву визуальных элементов. Единственное свойство TextBlock, об утрате которого в этом элементе управления я пожалел – Inlines, но TextBlock описывает это свойство как свойство только для чтения, поэтому мы не можем задавать привязку для него.

Приложение BorderedTextDemo тестирует наш новый элемент управления: Проект Silverlight: BorderedTextDemo Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <petzold:BorderedText Text="Ta Da!"

FontFamily="Times New Roman"

FontSize="9 6"

FontStyle="Italic"

FontWeight="Bold"

TextDecorations="Underline"

Foreground="Red"

Background="Lime"

BorderBrush="Blue"

BorderThickness="8"

CornerRadius="3 6"

Padding="16 4"

HorizontalAlignment="Center" VerticalAlignment="Center" />

</Grid>

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

По теме:

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