...

воскресенье, 29 марта 2015 г.

Расширение функциональности элементов управления Windows с помощью AttachedProperty


Краеугольным камнем разработки приложений для Windows (WPF, SilverLight, WP, WinRT) является паттерн MVVP. Который основан на концепции связывания данных модели представления и пользовательского интерфейса, что позволяет, используя декларативное описание UI посредством XAML избавится от codebegind (так я и не придумал/нашел русского перевода) и перенести всю логику работы с пользовательским интерфейсом в модель представления.


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


Написать данную статью меня побудила статья http://ift.tt/1a6B0kq. В статье найдено решение конкретной проблемы и предложено работающее решение. Однако для его использования необходимо в codebehind для каждого текстового блока вызывать код. Более того если данные предполагают изменение в процессе работы необходимо следить за их изменением. В процессе своей работы такие решения встречаю довольно часто, они отличаются реализацией, но их все отличает одно неизменное свойство, сложность поддержки и сопровождения кода.


Для решения подобных задач, необходимо использовать присоединяемые свойства (AttachedProperty), данная технология предоставляет три необходимые для решения задачи возможности:


1) Хранить любое значение в контексте элемента управления для которого оно было задано

2) Уведомлять об изменении данных свойства

3) Использоваться для декларативного связывания в XAML


Решим задачу из приведенного выше примера с помощью присоединяемого свойства, для этого создадим новый статический класс с именем RtbEx и добавим в него описание нового AttachedProperty:



public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(RtbEx), new PropertyMetadata(default(string)));

public static void SetText(DependencyObject element, string value)
{
element.SetValue(TextProperty, value);
}

public static string GetText(DependencyObject element)
{
return (string) element.GetValue(TextProperty);
}




Мы указали для свойства имя Text и тип значения string. Отдельно обращу внимание на методы [Set|Get]Text они добавлены для следования рекомендованному шаблону объявления свойств и предназначены для упрощения доступа к значению свойства. Теперь мы можем использовать это свойство для хранения связанных с элементом управления данных.

var someText = “Some Text”;
RtbEx.SetText(richTextBlock, someText);
someText = RtbEx.GetText(richTextBlock);




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

Указать обработчик события необходимо в описании присоединяемого свойства во втором параметре метаданных свойства:

public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(RtbEx), new PropertyMetadata(default(string), OnTextChanged));




Обработчик события изменения свойства должен иметь тип PropertyChangedCallback

private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var richTextBlock = d as RichTextBlock;
if (richTextBlock == null)
{
return;
}

richTextBlock.Blocks.Clear();

var text = e.NewValue as string;
if (string.IsNullOrWhiteSpace(text))
{
return;
}

richTextBlock.Blocks.Add(CreateParagraph(text));
}




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

private static Paragraph CreateParagraph(string text)
{
var paragraph = new Paragraph();

var splitResult = Regex.Split(text, @"(https?://\S+)");
foreach (var part in splitResult)
{
if (part.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var hyperLink = new Hyperlink {NavigateUri = new Uri(part)};
hyperLink.Inlines.Add(new Run {Text = part});

paragraph.Inlines.Add(hyperLink);
continue;
}

paragraph.Inlines.Add(new Run {Text = part});
}

return paragraph;
}




Код формирования наполнения RTB не важен в контексте задачи и далек от идеала, он просто делит строку на блоки с ссылками и простым текстом, после чего строит иерархию представления документа RichTextBlock.

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


Для этого в документ XAML добавим пространство имен содержащее расширение



xmlns:ex="using:RtbEx.Extensions"




И зададим связывание с помощью обычного {binding}

<RichTextBlock ex:RtbEx.Text="{Binding SomeText}" FontSize="20"/>


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


Код тестового приложения доступен на Github: http://ift.tt/1a6EdQS


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


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

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