суббота, 30 апреля 2011 г.

MarkupExtension

 

Иногда бывает такая ситуация, когда встроенные функциональные возможности XAML не вполне соответствуют вашим требованиям, но вы не хотите перемещать функциональность в code-behind каждый раз, когда вам это может понадобиться. В такой ситуации можно написать свой собственный MarkupExtension. MarkupExtension предоставляет базовый класс для реализаций расширений разметки XAML, который может поддерживаться службами XAML платформы .NET Framework и ее средствами чтения и записи XAML. Примерами расширения разметки являются BindingExtension, StaticResourceExtension, и RelativeSourceExtension. Все, что вы используете в XAML в таких скобках {} является MarkupExtension. Одним из примеров такой разметки является StaticResourceExtention, который ищет ресурсы. Ниже показан ресурс кисти линейного градиента с ключом gradientBrush.

<Window.Resources>

<LinearGradientBrush x:Key="gradientBrush1" StartPoint="0.5,0.0" EndPoint="0.5, 1.0">

<GradientStop Offset="0" Color="LightBlue" />

<GradientStop Offset="0.3" Color="Blue" />

<GradientStop Offset="0.7" Color="Lime" />

<GradientStop Offset="1" Color="Aqua" />

</LinearGradientBrush>

</Window.Resources>

Использование gradientBrush:

<TextBlock Text="Ivanov Alex" Background="{StaticResource gradientBrush1}" Foreground="White" FontStyle="Italic" FontWeight="Bold" FontSize="15"/>

Вы можете заметить, что StaticResourceExtension заканчивается на "Extension", но когда вы используете его в XAML "Extension" часть отсутствует (т. е. {} Binding). Такое именование это соглашение WPF, подобное Dependency Property, которые заканчиваются на Property, но используются без этого окончания. Ниже представлен другой способ использования расширений разметки.

<TextBlock Text="Sergeev Petr" Foreground="White" FontStyle="Italic" FontWeight="Bold" FontSize="15">

<TextBlock.Background>

<StaticResourceExtension ResourceKey="gradientBrush1" />

</TextBlock.Background>

</TextBlock>

Создание своего MarkupExtension

Чтобы создать новое Расширение разметки, вам нужно создать новый класс, производный от MarkupExtension абстрактный базовый класс. Постарайтесь выбрать название, которое носит описательный характер, но не слишком длинное (помните, что оно будет использоваться в XAML и Intellisense вам не поможет писать название своего расширения). Не забудьте дописать в конец имени “Extension”. Единственное, что вам необходимо сделать после этого, переопределить ProvideValue абстрактный метод. Описание метода из MarkupExtension представлено ниже.

public abstract object ProvideValue(IServiceProvider serviceProvider);

Метод MarkupExtension.ProvideValue – при реализации в производном классе возвращает объект, заданный как значение целевого свойства данного расширения разметки.

Параметр serviceProvider - вспомогательный объект поставщика служб, который может предоставить службы для расширения разметки.

Когда процессор XAML обрабатывает значение типа узла и элементов, являющихся расширением разметки, он вызывает метод ProvideValue этого расширения разметки и записывает результат в поток графа или сериализации объекта. Средство записи объекта XAML передает контекст службы для каждой такой реализации через параметр serviceProvider.

Метод ProvideValue предоставляет пользовательскому расширению разметки доступ к контексту службы, сообщающему среде, где фактически обработчик XAML вызывает расширение разметки. В пути загрузки это обычно XamlObjectWriter. В пути сохранения это обычно XamlXmlWriter. Каждый контекст службы передается как внутренний класс контекста поставщика службы XAML, реализующий шаблон поставщика службы. For more information about the available services and what they represent, see Преобразователи типов или расширения разметки для XAML.

Созданный класс расширения разметки должен использовать открытый уровень доступа — у обработчиков XAML всегда должна быть возможность создать экземпляр класса поддержки расширения разметки для использования его служб.

Теперь, когда вы переопределили ProvideValue, вы уже почти всё сделали!  Чтобы указать тип вывода есть MarkupExtensionReturnType атрибут, который можно применить к классу и Type аргументу (но не к методу). Это возвращение типа будет проверен анализатор XAML во время компиляции, чтобы убедиться, что вы правильно присвоения типов свойств.

[MarkupExtensionReturnType (TypeOf (ImageSource))]

Следующая вещь, которая вам вероятно понадобится это некоторые входные. Они имеют нормальный вид get/set свойств. Они могут быть любого типа, которого вы хотите. В большинстве случаев анализатор XAML будет в состоянии выяснить правильный тип из любой строки аргументов. При использовании расширения в XAML, синтаксис снова заимствован из атрибутов в том, что свойства передаются через запятую имя = значение ". Также можно указать один параметр конструктора, который отображает одно свойство без имени аргумента. Вы можете увидеть это в действии с Binding, передавая только значение путь без "Path =".

Теперь, когда структура создана и у вас есть аргументы вам нужно просто написать логику. Чтобы помочь вам в этом, есть несколько сервисов, которые могут дать вам более подробную информацию о вызове XAML. Параметр IServiceProvider к ProvideValue позволяет получить доступ к этим услугам. IXamlTypeResolver и IProvideValueTarget две службы, которые могут быть получены от IServiceProvider. Существует очень мало документации на эти интерфейсы, но они перечислены на MSDN. Доступ к этим службам можно осуществить с помощью следующего кода.

 
IXamlTypeResolver resolver = (IXamlTypeResolver)serviceProvider.GetService
(typeof(IXamlTypeResolver));   IProvideValueTarget target =
 (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

После получения информации, не забудьте проверить на NULL. 

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

· IProvideValueTarget сообщает ссылку на объект и идентификатор свойства из контекста, где используется расширение разметки

· IXamlTypeResolver предоставляет службу, синхронизирующую общее поведение записи объектов XAML, и обеспечивает Type на основе имени типа XAML. Имя при необходимости может включать префикс для сопоставленного пространства имен XAML.

· IXamlSchemaContextProvider напрямую предоставляет активный контекст схемы XAML. Из отчета XamlSchemaContext можно получить сведения о контексте схемы XAML, такие как способ отображения сборки для поддержки типа, предпочтительный префикс в сборках, списки каждого XamlType в сборке и т. д..

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

· IUriContext

· IAmbientProvider

· IDestinationTypeProvider

· IRootObjectProvider

· IXamlNameResolver

· IXamlNamespaceResolver

Потенциально реализации ProvideValue могут игнорировать параметр serviceProvider. Это применяется для некоторых основных сценариев, где контекст для возврата значения вообще не требуется.

Использование расширения разметки точно так же как с помощью любого пользовательского объекта в XAML. Добавить объявление пространства имен XML, как XMLNS: местные = "CLR-пространство имен: MyCustomNamespace" для текущей сборки или XMLNS: контроль = "CLR-пространство имен: CustomControlNamespace; сборки = CustomControlLibrary" за сборку. Затем с помощью пользовательского MarkupExtension вместе с XMLNS префикс и расширение исключить из названия.

Пример создания MarkupExtension


Ниже описан класс спецального расширения разметки CalculatorExtension, в котором определены свойства X и Y типа double и свойство Operation типа enum. В зависимости от значения свойства Operation, над входными свойствами X и Y производятся различные вычисления и возвращается строка.





using System;

using System.Windows;

using System.Windows.Markup;

namespace Wrox.ProCSharp.XAML

{

public enum Operation

{

Add,

Subtract,

Multiply,

Divide

}

[MarkupExtensionReturnType(typeof(string))]

public class CalculatorExtension : MarkupExtension

{

public CalculatorExtension()

{

}

public double X { get; set; }

public double Y { get; set; }

public Operation Operation { get; set; }

public override object ProvideValue(IServiceProvider serviceProvider)

{

IProvideValueTarget provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

if (provideValue != null)

{

var host = provideValue.TargetObject as FrameworkElement;

var prop = provideValue.TargetProperty as DependencyProperty;

}

IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

if (xamlTypeResolver != null)

{

}

double result = 0;

switch (Operation)

{

case Operation.Add:

result = X + Y;

break;

case Operation.Subtract:

result = X - Y;

break;

case Operation.Multiply:

result = X * Y;

break;

case Operation.Divide:

result = X / Y;

break;

default:

throw new ArgumentException("invalid operation");

}

return result.ToString();

}

}

}


Так будет выглядеть разметка XAML.





<Window x:Class="Wrox.ProCSharp.XAML.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:Wrox.ProCSharp.XAML"

Title="MainWindow" Height="150" Width="225">

<Window.Resources>

<LinearGradientBrush x:Key="gradientBrush1" StartPoint="0.5,0.0" EndPoint="0.5, 1.0">

<GradientStop Offset="0" Color="LightBlue" />

<GradientStop Offset="0.3" Color="Blue" />

<GradientStop Offset="0.7" Color="Lime" />

<GradientStop Offset="1" Color="Aqua" />

</LinearGradientBrush>

</Window.Resources>

<StackPanel>

<TextBlock Text="Ivanov Alex" Background="{StaticResource gradientBrush1}" Foreground="White" FontStyle="Italic" FontWeight="Bold" FontSize="15"/>

<TextBlock Text="Sergeev Petr" Foreground="White" FontStyle="Italic" FontWeight="Bold" FontSize="15">

<TextBlock.Background>

<StaticResourceExtension ResourceKey="gradientBrush1" />

</TextBlock.Background>

</TextBlock>

<TextBlock Text="{local:Calculator Operation=Add, X=3, Y=4}" />

<TextBlock>

<TextBlock.Text>

<local:CalculatorExtension>

<local:CalculatorExtension.Operation>

<local:Operation>Multiply</local:Operation>

</local:CalculatorExtension.Operation>

<local:CalculatorExtension.X>7</local:CalculatorExtension.X>

<local:CalculatorExtension.Y>11</local:CalculatorExtension.Y>

</local:CalculatorExtension>

</TextBlock.Text>

</TextBlock>

</StackPanel>

</Window>


И мы можем убедится, что в code-behind нет никакого кода.





using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

namespace Wrox.ProCSharp.XAML

{

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

}

}

Вот что у нас получилось при выполнении данной программы.

image

Резюме


В данной статье была продемонстрирована расширяемость XAML, которая служит базой для таких технологий как WPF и Silverlight.

Комментариев нет:

Отправить комментарий