Расширение функциональности элементов управления Windows с помощью AttachedProperty
Краеугольным камнем разработки приложений для 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