Pro datovou konverzi nabízí WPF několik hotových konvertorů, mezi které patří např. BooleanToVisibilityConverter, který převádí hodnoty Boolean na výčtový typ Visibility, ZoomPercentageConverter, konvertující typ Double, a JournalEntryListConverter, který však nemůžeme použít přímo.
Mnoho jich není, a tak s velkou pravděpodobností nastane situace, že budeme potřebovat konvertovat data z jiných a do jiných formátu, než umožňují již hotové nabízené konvertory. Pro tyto případy si dnes zkusíme několik vlastních konvertorů vytvořit.
Teorii o tom jak konvertor vypadá z hlediska kódu necháme na později, až budeme programovat samotný konvertor, a nejprve si vytvoříme základ našeho projektu.
1. Přípravná fáze
Ve Visual Studiu necháme vytvořit nový projekt v jazyce C# a .NET Frameworku 3.0 nebo 3.5. Pojmenujeme jej jako "databinding" a po vytvoření projektu se přepneme do editace XAML kódu, kde nahradíme původní element Grid StackPanelem.
Váš kód by měl vypadat takto:
<Window x:Class="databinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
</StackPanel>
</Window>
|
Nyní si vytvoříme data, která budeme navazovat. Půjde o data ve formátu XML, reprezentující dopravní prostředky, kde každý má nastaven název, barvu a atribut zda-li létá či nikoliv.
<Window.Resources>
<XmlDataProvider x:Key="data" XPath="/DopravniProstredky/DopravniProstredek">
<x:XData>
<DopravniProstredky xmlns="">
<DopravniProstredek Nazev="Auto" Barva="červená" Leta="false" />
<DopravniProstredek Nazev="Loď" Barva="bílá" Leta="false" />
<DopravniProstredek Nazev="Vlak" Barva="modrá" Leta="false" />
<DopravniProstredek Nazev="Letadlo" Barva="zelená" Leta="true" />
</DopravniProstredky>
</x:XData>
</XmlDataProvider>
</Window.Resources>
|
Data budeme navazovat na ListBox, ale ještě než to uděláme, vytvoříme si v sekci zdrojů datovou šablonu.
<DataTemplate x:Key="sablona">
<StackPanel Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding XPath=@Nazev}"/>
<TextBlock Width="100" Text="{Binding XPath=@Barva}" />
<TextBlock Width="100" Text="{Binding XPath=@Leta}"/>
</StackPanel>
</DataTemplate>
|
Poslední části, před vytvářením konvertoru, je přidání vlastního ListBoxu.
<ListBox x:Name="pole" Height="150"
ItemTemplate="{StaticResource sablona}"
ItemsSource="{Binding Source={StaticResource data}}" />
|
2. Vytváření vlastního konvertoru
Teď když spustíte dosud napsanou aplikaci tak zjistíte, že jsou v Listboxu např. u dopravních prostředků zobrazené hodnoty true a false, což není zrovna moc uživatelsky přívětivé. Mnohem lepší by bylo, kdyby se zde zobrazovaly hodnoty ano a ne. A právě teď je ta správná chvíle na datový konvertor.
Datové konvertory jsou vlastně třídy odvozené od rozhraní IValueConverter, které obsahuje dvě metody Convert a ConvertBack.
Přidejme si do našeho projektu novou třídu, kterou pojmenujme např. "DataConverters.cs".
Celou třídu upravíme do této podoby:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
namespace databinding.Converters
{
[ValueConversion(typeof(string), typeof(string))]
public class AnoNeConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
}
}
}
|
Protože je rozhraní IValueConverter obsaženo v prostoru názvů System.Windows.Data, přidáme jej k ostatním použitím direktivy using a nezapomeneme na System.Windows.Media, který budeme potřebovat později. Dále upravíme namespace naší třídy na např. "databinding.Converters". To budeme potřebovat z důvodu přístupu k této třídě z XAML. Konvertor bude převádět string na string, přestože vstup vypadá jako bool, v datech je reprezentován řetězcem. Nakonec potřebujeme implementovat metody Convert a ConvertBack, které si vynucuje rozhraní.
Upravíme metodu Convert:
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool hodnota = System.Convert.ToBoolean(value.ToString());
string odpoved;
if (hodnota)
{
odpoved = "Ano";
}
else
{
odpoved = "Ne";
}
return odpoved;
}
|
Ve velké vetšině případů je možné si vystačit pouze s touto metodou a proto ConvertBack postačí implementovat takto:
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Tato konverze není implementována!");
}
|
Tím máme vytvořen celý konvertor a pojďme jej použít.
Nejdříve upravíme element Window tak, že do něj přidáme připojení prostoru názvů s konvertorem:
xmlns:src="clr-namespace:databinding.Converters"
|
V sekci zdrojů vytvoříme objekt s konvertorem:
<src:AnoNeConverter x:Key="prevod"/>
|
který teď použijeme. Datovou šablonu upravíme následujícím způsobem:
<DataTemplate x:Key="sablona">
<StackPanel x:Name="pozadi" Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding XPath=@Nazev}"/>
<TextBlock Width="100" Text="{Binding XPath=@Barva}" />
<TextBlock Width="100"
Text="{Binding Converter={StaticResource prevod},
XPath=@Leta}"/>
</StackPanel>
</DataTemplate>
|
A to je vše.
Nyní ještě uděláme, aby barva pozadí TextBlocku, zobrazující název barvy, byla stejná jako jméno barvy. Vytvoříme si druhý konvertor, který bude převádět string na SolidColorBrush. Teď již víte jak na vytváření konvertoru a tak to bude snadné. Kód konvertoru bude vypadat takto:
[ValueConversion(typeof(string), typeof(SolidColorBrush))]
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Color barva;
switch (value.ToString())
{
case ("červená"):
barva = Colors.Red;
break;
case ("modrá"):
barva = Colors.Blue;
break;
case ("zelená"):
barva = Colors.Green;
break;
default:
barva = Colors.White;
break;
}
return new SolidColorBrush(barva);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Tato konverze není implementována!");
}
}
|
V XAML vytvoříme objekt konvertoru:
<src:StringToColorConverter x:Key="nabarvu"/>
|
a upravíme datovou šablonu:
<DataTemplate x:Key="sablona">
<StackPanel x:Name="pozadi" Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding XPath=@Nazev}" />
<TextBlock Width="100"
Background="{Binding Converter={StaticResource nabarvu},
XPath=@Barva}"
Text="{Binding XPath=@Barva}" />
<TextBlock Width="100"
Text="{Binding Converter={StaticResource prevod},
XPath=@Leta}" />
</StackPanel>
</DataTemplate>
|
3. A ještě jeden na závěr
Dosud jsme vytvářeli konvertory u nichž jsme zanedbávali metodu ConvertBack. Pojďme se na závěr podívat i na zpětnou konverzi. Na ploše aplikace máme ještě trochu místa a tak si vytvoříme jednoduchý překladač třeba z češtiny do angličtiny a zpět.
Pod ListBox z minulého příkladu umístěme následující kód:
<Label>Česky:</Label>
<TextBox x:Name="cz" Width="100"/>
<Label>Anglicky:</Label>
<TextBox x:Name="en" Width="100"
Text="{Binding Mode=TwoWay, ElementName=cz, Path=Text}" />
|
Vlastnost Text druhého TextBoxu je dvoucestně navázána na vlastnost Text prvního TextBoxu, který funguje jako zdroj. Píšete-li něco v prvním, je to zobrazeno zároveň i ve druhém. Naopak to funguje také, ale musíme dát zdroji nějak vědět, že došlo k úpravě na straně "klienta". To uděláme třeba tak, že si vytvoříme handler metody TextChanged. Ten může vypadat takto:
private void en_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox pole = sender as TextBox;
BindingExpression binding =
pole.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
|
Pojďme na konvertor:
[ValueConversion(typeof(string), typeof(string))]
public class Prekladac : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string preklad;
switch (value.ToString())
{
case ("červená"):
preklad = "red";
break;
case ("modrá"):
preklad = "blue";
break;
case ("zelená"):
preklad = "green";
break;
default:
preklad = "";
break;
}
return preklad;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string preklad;
switch (value.ToString())
{
case ("red"):
preklad = "červená";
break;
case ("blue"):
preklad = "modrá";
break;
case ("green"):
preklad = "zelená";
break;
default:
preklad = "";
break;
}
return preklad;
}
}
|
V XAML vytvoříme objekt konvertoru:
<src:Prekladac x:Key="prekladac"/>
|
A upravíme TextBox:
<TextBox x:Name="en" TextChanged="en_TextChanged" Width="100"
Text="{Binding Converter={StaticResource prekladac},
Mode=TwoWay, ElementName=cz, Path=Text}"/>
|
A to je již opravdu vše.
Datové konvertory jsou zajímavým mechanismem, který má své využití ve spoustě případů. Mají také spoustu možností, dokážou přijímat parametry a následně je zpracovávat, dokážou pracovat s CultureInfo apod. Doufám, že jsem vám tímto článkem srozumitelnou formou alespoň trochu přiblížil problematiku jejich vytváření a aplikace na elementárních příkladech.
Příště se můžeme podívat třeba na datové validátory.
Kompetní výpisy zdrojového kódu:
Window1.xaml
<Window x:Class="databinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:databinding.Converters"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<src:AnoNeConverter x:Key="prevod"/>
<src:StringToColorConverter x:Key="nabarvu"/>
<src:Prekladac x:Key="prekladac"/>
<XmlDataProvider x:Key="data" XPath="/DopravniProstredky/DopravniProstredek">
<x:XData>
<DopravniProstredky xmlns="">
<DopravniProstredek Nazev="Auto" Barva="červená" Leta="false" />
<DopravniProstredek Nazev="Loď" Barva="bílá" Leta="false" />
<DopravniProstredek Nazev="Vlak" Barva="modrá" Leta="false" />
<DopravniProstredek Nazev="Letadlo" Barva="zelená" Leta="true" />
</DopravniProstredky>
</x:XData>
</XmlDataProvider>
<DataTemplate x:Key="sablona">
<StackPanel x:Name="pozadi" Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding XPath=@Nazev}" />
<TextBlock Width="100"
Background="{Binding Converter={StaticResource nabarvu},
XPath=@Barva}"
Text="{Binding XPath=@Barva}" />
<TextBlock Width="100"
Text="{Binding Converter={StaticResource prevod},
XPath=@Leta}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox x:Name="pole" Height="150"
ItemTemplate="{StaticResource sablona}"
ItemsSource="{Binding Source={StaticResource data}}" />
<Label>Česky:</Label>
<TextBox x:Name="cz" Width="100"/>
<Label>Anglicky:</Label>
<TextBox x:Name="en" TextChanged="en_TextChanged" Width="100"
Text="{Binding Converter={StaticResource prekladac},
Mode=TwoWay, ElementName=cz, Path=Text}"/>
</StackPanel>
</Window>
|
Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace databinding
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void en_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox pole = sender as TextBox;
BindingExpression binding =
pole.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
}
|
DataConverters.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
namespace databinding.Converters
{
[ValueConversion(typeof(string), typeof(string))]
public class AnoNeConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool hodnota = System.Convert.ToBoolean(value.ToString());
string odpoved;
if (hodnota)
{
odpoved = "Ano";
}
else
{
odpoved = "Ne";
}
return odpoved;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Tato konverze není implementována!");
}
}
[ValueConversion(typeof(string), typeof(SolidColorBrush))]
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Color barva;
switch (value.ToString())
{
case ("červená"):
barva = Colors.Red;
break;
case ("modrá"):
barva = Colors.Blue;
break;
case ("zelená"):
barva = Colors.Green;
break;
default:
barva = Colors.White;
break;
}
return new SolidColorBrush(barva);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Tato konverze není implementována!");
}
}
[ValueConversion(typeof(string), typeof(string))]
public class Prekladac : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string preklad;
switch (value.ToString())
{
case ("červená"):
preklad = "red";
break;
case ("modrá"):
preklad = "blue";
break;
case ("zelená"):
preklad = "green";
break;
default:
preklad = "";
break;
}
return preklad;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string preklad;
switch (value.ToString())
{
case ("red"):
preklad = "červená";
break;
case ("blue"):
preklad = "modrá";
break;
case ("green"):
preklad = "zelená";
break;
default:
preklad = "";
break;
}
return preklad;
}
}
}
|