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

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

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

Написать данную статью меня побудила статья habrahabr.ru/company/edusty/blog/253635/. В статье найдено решение конкретной проблемы и предложено работающее решение. Однако для его использования необходимо в 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} Использование присоединяемых свойств ограничено только вашим воображением, используя их можно реализовать множество удобных расширений, которые значительно упростят разработку, повторное использование и поддержку вашего кода.

Код тестового приложения доступен на Github: https://github.com/Viacheslav01/RtbEx

© Habrahabr.ru