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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.