Краеугольным камнем разработки приложений для 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
Автор: Viacheslav01