XAML.cz Magazín moderních technologií založených na XAML

Konvertor - nejlepší přítel člověka 2

Napsáno pro WPF od Tomáš Pastorek [14.04.2010]

V minulém díle tohoto miniseriálu o konvertování dat při DataBindingu byl uveden příklad použití interface IValueConverter, který umožňuje upravit data na cestě z jednoho zdroje do cíle. Dnes se podíváme blíž na konvertor založený na IMultiValueConverter, kterým lze konvertovat data z několika zdrojů do jednoho cíle. Celý princip konverze ukazuje následující obrázek:

Princip IMultiValueConverteru

Pro konverzi ze zdroje do cíle se používá metoda:

object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

Pro konverzi z cíle do zdroje:

object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

Jak parametr values metody Convert, tak i návratová hodnota metody ConvertBack je pole typu object – tedy pole libovolných hodnot.

Kdy MultiValueConverter použít?

Důvody, proč sáhnout po konvertoru více hodnot implementujícím IMultiValueConverter narozdíl od IValueConverter, jsou v podstatě dva:

  • Potřebujeme z několika zdrojů použít jeden výsledek. 
    Pokud bychom měli několik zdrojů, např. údaje o zákazníkovi, a chtěli bychom z nich sestavit jeden řetězec; Pokud bychom chtěli nad více hodnotami provést nějaký výpočet (součet, apod.)...
  • Potřebujeme konvertovat pouze jednu hodnotu přes IValueConverter, ale jako parametr konvertoru bychom potřebovali použít hodnotu svázanou DataBindingem.
    K použití konvertoru můžeme připojit ConverterParameter (příklad), kterým můžeme ke konverzi přidat nějaké další parametry. Bohužel, ConverterParameter není DependencyProperty, a tím pádem nemůžeme použít DataBinding.  Řešením je místo jednoduchého konvertoru (IValueConverter) použít vícenásobný (IMultiValueConverter) a parametr nepředávat pomocí ConverterParameter, ale pomocí dalšího datového zdroje. Toto použití si ukážeme v příkladu.

Příklad

Tento příklad představuje trochu “tricky” řešení, jak automaticky doplňovat čísla řádků k položkám v kontrolních prvcích založených na ItemsControl (ComboBox, ListBox, ...). Začal bych motivačním obrázkem výsledku :) ...

Ukázka výsledku konvertoru pro přidání čísel  

Do ListBoxu se předává pouze seznam jmen a číslování zajišťuje konvertor pro číslování řádků. Nejprve se podívejme na metodu Convert konvertoru, který jsem pojmenoval RowNumberConverter. Jedná se o zjednodušenou verzi pro prezentační účely. Celý kód včetně projektu si můžete stáhnout v závěru článku.

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (values.Count() != 2)
        throw new ArgumentException("RowNumberConverter needs 2 parameters! First: Binding to item, Second: Binding to parents ItemsControl.");

    parent = values[1] as System.Windows.Controls.ItemsControl;

    if (parent == null)
        throw new ArgumentException("Second parameter must be inherited from ItemsControl.");

    // IList -  Muzeme pouzit IndexOf
    System.Collections.IList source = parent.ItemsSource as System.Collections.IList;
    if (source != null)
        return FormatNumber((source.IndexOf(values[0])+1).ToString(culture),parameter);
    else
    { 
        // Jinak musime pouzit enumerator
        int index = 1;
        foreach (object obj in parent.ItemsSource)
        {
            if (obj == values[0])
            {
                return FormatNumber(index.ToString(culture),parameter);
            }
            index++;
        }
    }
    // a kdyz se nic nenaslo, tak vratime X
    return FormatNumber("X", parameter); 
}
private string FormatNumber(string value, object parameter)
{
    string format = parameter as string;
    if (format != null && value!=null)
    {
        return String.Format(format, value);
    }
    return value;
}

Princip konverze je velmi jednoduchý. Konvertor očekává jako první parametr celou položku seznamu a jako druhý parametr nadřazený prvek odvozený od ItemsControl, který tuto položku obsahuje v ItemsSource. Pokud je kolekce v ItemsSource odvozená od IList, použije se výraz (IndexOf(položka) + 1) k určení čísla řádku. Pokud kolekce není odvozená od IList, najde se položka postupným procházením s použitím enumerátoru. Metoda FormatNumber slouží k formátování čísla na základě parametru zadaného přes ConverterParameter.

Konvertor pro číslování řádků najde své uplatnění v šablonách položek (DataTemplate). O DataTemplate se můžete dočíst podrobnější informace v článku DataBinding a DataTemplate.

Následující ukázka XAMLu ukazuje použití konvertoru pro číslování řádku tak, jak to ukazoval motivační obrázek:

<Window x:Class="ConverterTest2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ConverterTest2"
    xmlns:collections="clr-namespace:System.Collections.Specialized;assembly=System"  
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="Xaml.cz - Ukázka IMultiValueConverter" Height="150" Width="300">
    <Window.Resources>    
        
        <collections:StringCollection x:Key="data">
            <sys:String>Jaroslav Novák</sys:String>
            <sys:String>František Jedlička</sys:String>
            <sys:String>Jana Smutná</sys:String>
            <sys:String>Josef Novotný</sys:String>
            <sys:String>Lucie Veselá</sys:String>
        </collections:StringCollection>
        
        <local:RowNumberConverter x:Key="rownumbers" />
</Window.Resources> <Grid> <ListBox Height="auto" Margin="0,5,0,5" ItemsSource="{Binding Source={StaticResource data}}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Margin="0,0,10,0"> <TextBlock.Text> <MultiBinding Converter="{StaticResource rownumbers}" ConverterParameter=" {0}."> <Binding /> <Binding RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type ItemsControl}}" /> </MultiBinding> </TextBlock.Text> </TextBlock> <TextBlock Text="{Binding}"></TextBlock> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>

Pro demonstrační účely jsem vytvořil jednoduchý příklad dat – kolekci jmen, kterou jsem nadefinoval přímo do elementu Windows.Resources a pojmenoval ji “data”. Ve stejném elementu je připravena instance RowNumberConverter pod názvem “rownumbers”. ListBoxu přiřadím zdroj dat – kolekci “data”. Dále je nadefinovaná DataTemplate pro položku ListBoxu:

<DataTemplate>
    <StackPanel Orientation="Horizontal">
        <TextBlock Margin="0,0,10,0">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource rownumbers}" ConverterParameter=" {0}.">
                        <Binding />
                        <Binding RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type ItemsControl}}" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        <TextBlock Text="{Binding}"></TextBlock>
    </StackPanel>
</DataTemplate>

Řádek ListBoxu je tedy zobrazen jako StackPanel s vodorovným zarovnáváním položek (řadí se vedle sebe na řádek) a obsahuje TextBlocky pro zobrazení textu. V prvním TextBlocku je použitý MultiBinding, který je obdobou DataBindigu a je určený právě pro použití s konvertorem založeným na IMultiValueConverter, který konvertuje více zdrojů na jednu výslednou hodnotu. Prvním elemtent MultiBindingu<Binding />” řiká, že se jako první zdroj dostane celá položka ListBoxu. Druhý zdroj “<Binding RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type ItemsControl}}" />” je navázání instance nadřazeného kontrolního prvku typu ItemsControl. Pro MultiBinding je použitý konvertor “rownumbering”, který je deklarován v elementu Windows.Resources. Jako parametr je použitý řetězec "{0}.", který bude výstup formátovat (bude za číslo přidávat tečku).

Druhý TextBlock slouží pouze pro zobrazení hodnoty položky jako ve standardním ListBoxu.

Závěr

Dnešní příklad demonstruje, jaká kouzla se dají dělat za použití konvertorů. Slibované zdrojové kódy k příkladu jsou samozřejmě také k mání. V příštím díle o konvertorech se podíváme na další zajímavá řešení, které nám použití konvertorů nabízí.

Komentáře

ukládám komentář, vyčkejte prosím..
  1. Buďte první, kdo napíše komentář.

@xamlcz

  • RT @jvanrhyn: XAML, It's a bit like olives. Takes a while to get used to. But once you're used to it. It is actually pretty good. <3 XAML
  • RT @moser_christian: WPF Inspector 0.9.7 is released. It supports .NET 3.5 and 4.0 The project is now open source and available on CodeP ...
  • Jeff Handley oznámil vydání WCF RIA Services v.1.0 SP1 RTM http://bit.ly/gOgckn ke stažení na http://bit.ly/gVAXdK
  • jedna výzva pro Brno. Byl někdo z vás na přednášce o RIA v MS Akvárku? Dejte o sobě vědět. Děkuji
  • také jste uvažovali o tom, že zkusíte na projekt použít Caliburn Micro nebo naopak Prism 4? A co tak obojí, šlo by to nebo ne? Již brzy