Pull to refresh

WPF Markup Extensions

Reading time 4 min
Views 8K
С выходом .Net 3.0 у нас появилась возможность дополнять базовые классы без их переопределения собственными методами. Данная технология получила название Code Extensions Methods
Но как оказалось, что таким же простым и очень гибким методом можно расширить возможности и XAML разметки окон и компонентов.

Простым примером использования extension можно привести всем хорошо знакомую конструкцию
<Button Content="{Binding Path=Title}"/>

* This source code was highlighted with Source Code Highlighter.

Где Binding и есть ни что иное как расширения языка.
А теперь конкретный пример, исправляющий недостаток вышеупомянутой конструкции, когда сделать привязку к самому себе можно только через длинную строчку кода
<Button Content="{Binding Path=Height,
   RelativeSource={RelativeSource Self}}"
/>


* This source code was highlighted with Source Code Highlighter.

Создадим собственное расширение SelfBinding:
1. Добавим в проект класс и обзовем его SelfBinding
2. Унаследуем данный класс от MarkupExtension
3. Добавим свойство Path типа string
4. Создадим пустой конструктор
5. Создадим конструктор с одним параметром string Path
public SelfBinding(string Path)
{
    this.Path = Path
}


* This source code was highlighted with Source Code Highlighter.

6. Переопределим его метод ProvideValue
public override object ProvideValue(IServiceProvider serviceProvider)
    {
      try
      {

        if (string.IsNullOrEmpty(Path))
          throw new ArgumentNullException("Path", "The Path can not be null");

        //Получим провайдер, с информацией об объектре и првязках
        var providerValuetarget = (IProvideValueTarget)serviceProvider
          .GetService(typeof(IProvideValueTarget));

        //Получим объект, вызвавший привязку
        DependencyObject _targetObject = (DependencyObject)providerValuetarget.TargetObject;

        //Получим свойство для возвращения значения привязки
        PropertyInfo _sourceProperty = _targetObject.GetType().GetProperty(Path);

        //Вернем значение свойства
        return _sourceProperty.GetValue(_targetObject,null);

      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex);
        return null;
      }
    }


* This source code was highlighted with Source Code Highlighter.


Теперь можно и проверить
<Window x:Class="for7raid.wpfExtension.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:for7raid.wpfExtension"
  Title="Window1" Height="300" Width="300">
  
  <Grid>
    <Button Content="{local:SelfBinding Foreground}" />
  </Grid>
</Window>


* This source code was highlighted with Source Code Highlighter.

Все получилось, теперь следует добавить возможность использовать конвертеры, как это сделано в стандартном Binding.
7. Добавим проперти Converter типа IValueConverter и проверим его при возращении результата
//Если у нас задан конвертер, прогоним значение через него
if (Converter != null)
   return Converter.Convert(value, _targetProperty.PropertyType, null,
       Thread.CurrentThread.CurrentCulture);
else
   return value;


* This source code was highlighted with Source Code Highlighter.


Все!

Ну и на закуску: Импортируем в стандартное пространство имен наш Namespase чтобы каждый раз нам не приходилось писать префикс перед каждым расширением. Добавьте в файл Assembly.cs следующую строчку и отбилдите проект
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "for7raid.wpfExtension")]

* This source code was highlighted with Source Code Highlighter.


TODO:
1. Добавить триггер на изменение свойства, чтобы была живая привязка
2. Возможно реализовать задание пути в следующем виде: Path=Foreground.Name

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

<Button Content="{Title btnKey, Default=Hello button}",
     Visability="{AllowedBy All sp1 sp2 sp3}"/>


* This source code was highlighted with Source Code Highlighter.


Искодный код

Почитать по теме: Markup Extensions and XAML
WPF Multilanguage Markup Extension
Tags:
Hubs:
+19
Comments 18
Comments Comments 18

Articles