Monday, October 17, 2011 3:14 PM
lukaashek
WPF - Markup Extensions
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)

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