Markup Extensions umožňují rozšířit funkcionalitu XAMLu. WPF již obsahuje vestavěné Markup Extensions jako je např. NullExtension, StaticExtension, Binding apod. Tyto rozšíření poznáte jednoduše, vyskytují se v xamlu mezi složenými závorkami. Např.

Obr. Markup Extensions (Windows Presentation Foundation Unleashed)

image

Vytvoření vlastního rozšíření je velice jednoduché, stačí pouze podědit ze třídy MarkupExtension (namespace System.Windows.Markup) a implementovat metodu ProvideValue, která se postará o vrácení správné hodnoty. Pro ukázku možného použití se podíváme na následující jednoduché ukázky.

StringToBoolExtension – převádí řetězec ano na true či false

namespace BoolMarkupExtension
{
    using System;
    using System.Windows.Markup;
    /// <summary>
    ///Implementace vlastniho MarkupExtension. Retezec prevede na bool objekt
    /// </summary>
    [MarkupExtensionReturnType(typeof(bool))]
    public class StringToBoolExtension : MarkupExtension
    {
        public string Value { get; set; }
        /// <summary>
        /// Vraci objekt, ktery se nastavi jako hodnota atributu na nehoz je toto rozsireni aplikovano
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            bool isEnabled = false;
            if (string.Equals("ano", Value, StringComparison.OrdinalIgnoreCase))
                isEnabled = true;
            return isEnabled;
        }
    }
}

Použití v XAMLu:

<Window x:Class="BoolMarkupExtension.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ex="clr-namespace:BoolMarkupExtension"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button IsEnabled="{ex:StringToBool Value=ano}" Content="StringToBool" />      
    </StackPanel>
</Window>

Poznámka: Název třídy by měl mít suffix Extension. Není to povinné, spíše se jedná o doporučení. I když např. třída Binding tento suffix nemá! Při použití v XAMLu lze ovšem tento suffix ignorovat a použít pouze název třídy bez suffixu např. Static – i když se třída jmenuje StaticExtension

CurrentTimeExtension – umožní zobrazit aktuální čas

namespace BoolMarkupExtension
{
    using System.Windows.Markup;
    using System.Windows;
    using System.Windows.Threading;
    using System;
    [MarkupExtensionReturnType(typeof(string))]
    public class CurrentTimeExtension : MarkupExtension
    {
        DispatcherTimer _timer;
        UIElement _uiElement;
        DependencyProperty _dependencyProperty;
        /// <summary>
        /// Vytvori instanci tridy <see cref="CurrentTimeExtension"/>
        /// </summary>
        public CurrentTimeExtension()
        {
            _timer = new DispatcherTimer(TimeSpan.FromSeconds(1),
                                         DispatcherPriority.Background,
                                         (sender, args) =>
                                             {
                                                 _uiElement.SetValue(_dependencyProperty,DateTime.Now.ToLongTimeString());
                                             }, Dispatcher.CurrentDispatcher);
        }
        public override object ProvideValue(System.IServiceProvider serviceProvider)
        {
            IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
            _uiElement = (UIElement)target.TargetObject;
            _dependencyProperty = (DependencyProperty)target.TargetProperty;
            return DateTime.Now.ToLongTimeString();
        }
    }
}

Použití v XAMLu:

<Window x:Class="BoolMarkupExtension.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ex="clr-namespace:BoolMarkupExtension"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <TextBlock Text="{ex:CurrentTime}"/>
    </StackPanel>
</Window>

BoolToVisibilityExtension – na základě bool hodnoty vrátí hodnotu z enumu Visibility – Visible/Hidden. Toto rozšíření je vhodné v situaci, že máme nabindovaný nějaký ViewModel na View. Do vlastnosti Path nastavím název property ve ViewModelu, která může nabývat hodnot true/false. V metodě ProvideValue jsem pak schopný z DataContextu získat tento ViewModel a vytvořit Binding mezi cílovou vlastností (Path) a DependencyProperty, která bude hodnotu zobrazovat (v našem případě Button). Využívá se zde již existující konvertor BooleanToVisibilityConverter. Pokud hodnota vlastnosti ve ViewModelu bude true, tlačítko bude zobrazené, jinak bude schované.

namespace BoolMarkupExtension
{
    using System.Windows.Controls;
    using System.Windows.Markup;
    using System.Windows;
    using System.Windows.Data;
    [MarkupExtensionReturnType(typeof(Visibility))]
    public class BoolToVisibilityExtension : MarkupExtension
    {
        private object _dataContext;
        private FrameworkElement _uiElement;
        public string Path { get; set; }
        public override object ProvideValue(System.IServiceProvider serviceProvider)
        {
            IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
            _uiElement = target.TargetObject as FrameworkElement;
            _dataContext = _uiElement.DataContext;
            var dp = target.TargetProperty as DependencyProperty;
            
            Binding binding = new Binding();
            binding.Path = new PropertyPath(Path);
            binding.Source = _dataContext;
            binding.Mode = BindingMode.TwoWay;
            binding.Converter = new BooleanToVisibilityConverter(); // built-in konverter
            _uiElement.SetBinding(dp, binding);
            return _uiElement.GetValue(dp);
        }
    }
}

Použití v XAMLu:

<Window x:Class="BoolMarkupExtension.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ex="clr-namespace:BoolMarkupExtension"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <CheckBox x:Name="chb" IsChecked="{Binding Visible, Mode=TwoWay}"/>
        <Button Visibility="{ex:BoolToVisibility Path=Visible}" Content="test"/>
    </StackPanel>
</Window>

Závěr

I když dělám z WPF již nějakou dobu, byla pro mě tvorba vlastních Markup Extension docela novinkou. Třeba to bude něco nového i pro některé z Vás. Ukázkové solution si můžete stáhnout na http://lukaskubis.net/demos/BoolMarkupExtensionSample.zip