Расширение функциональности элементов управления 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}

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

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/254305/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *