wtorek, 19 czerwca 2012

Moje boje z "Binding" - konwertery

Właściwość "Converter" podczas umożliwia nam przekształcenie powiązanych elementów na oczekiwaną wartość zwracaną przez "Binding".
Przykładowo "bindujemy" wartość a=2 a chcemy wyświetlić a*a, to jest właśnie zadanie dla konwertera!
Poniżej przedstawiony jest kod wykonujący takie działanie, opiszmy więc go po kolei :)
Atrybut "ValueConversion" określa nam typy danych biorące udział w konwersji. Jego napisanie nie jest wymagane dla poprawnego działania konwertera, aczkolwiek uważane jest za dobrą praktykę programistyczną.
[ValueConversion(typeof(double), typeof(double))]
Konwerter może dziedziczyć po interfejsie "IValueConverter" lub "IMultiValueConverter". Obydwa interfejsy mają dwie metody "Convert" oraz "ConvertBack".
Metoda "Convert" wywoływana jest w momencie "przybywania" danych ze źródła do celu (np. zmiana danych w modelu  wywołuje zmianę na widoku), metoda "ConvertBack" działa analogicznie w drugą stronę.
Parametry tych metod to odpowiednio obiekt lub tablica obiektów do skonwertowania, typ obiektu wynikowego, parametr konwertera ("ConverterParameter") oraz obiekt "CultureInfo" pozwalający nam uzależnić konwersję od wersji językowej programu.
Odnosząc się do poniższego kodu metoda "Convert" po otrzymaniu w "value" wartości 5 zwróci nam 25, zaś metoda "ConvertBack" po otrzymaniu 25 zwróci nam 5.
Jeśli parametr "value" bedzie innego typu niż "double" każda z metod zwróci "DependencyProperty.UnsetValue".
    [ValueConversion(typeof(double), typeof(double))]
    public class SquareConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double)
                return (double)value * (double)value;
            return DependencyProperty.UnsetValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double)
                return Math.Sqrt((double)value);
            else if(value is string)
            {
                double result;
                if (double.TryParse((string)value, out result))
                    return Math.Sqrt(result);
            }
            return DependencyProperty.UnsetValue;
        }
    }
Gdy konwerter zwróci parametr "DependencyProperty.UnsetValue" wskazuje na to, że nie zwróci nam żadnej wartości. W takim przypadku "Binding" użyje wartości zdefiniowanej jako parametr "FallbackValue" lub wartości domyślnej dla typu wynikowego konwersji.

Definiowanie takiego konwertera w kodzie XAML.
    <Window.Resources>
        <converters:SquareConverter x:Key="squareConverter" />
    </Window.Resources>
Następnie używamy konwerter w "bindingu".
<TextBox Text="{Binding Path=Val, Converter={StaticResource squareConverter}, Mode=TwoWay}" Height="23" Width="200"/>
Do konwertera możemy przekazać dodatkowy parametr poprzez "ConverterParameter", niestety wadą tego rozwiązania jest to, że do "ConverterParameter" nie możemy "bindować" danych ponieważ nie jest on typu "DependencyObject".
Za jego pomocą możemy przekazać tekst czy referencję do obiektu widoku.

Poniższy konwerter jeżeli dostanie wartość typu "string" oraz parametr będzie typu "string" sklei te dwa teksty i wyświetli w kontrolce, w drugim przypadku jeżeli otrzymany parametr będzie typu "Button" to konwerter do parametru "value" doda zrzutowaną na "string" wartość parametru "Content" z "Buttona" i wyświetli to w kontrolce.
    [ValueConversion(typeof(string), typeof(string))]
    public class AppendTextConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string)
            {
                StringBuilder sb = new StringBuilder(value.ToString());
                if (parameter is string)
                    sb.Append(parameter.ToString());
                else if (parameter is Button)
                    sb.Append(((Button)parameter).Content.ToString());
                return sb.ToString();
            }
            return DependencyProperty.UnsetValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }
W klasie "MainWindowViewModel" dodaję właściwość:
        private string _text = "Written by ";
        public string Text
        {
            get { return _text; }
            set
            {
                _text = value;
                FirePropertyChanged(() => Text);
            }
        }
W pliku ".xaml" dodaję odwołania do "AppendTextConverter"
        <Button Name ="btn2ConverterParameter" Content="Radosław Dąbrowicz" Height="23" HorizontalAlignment="Left" Width="200" />
        <TextBox Text="{Binding Path=Text, Converter={StaticResource appendTextConverter}, ConverterParameter=Author}" Height="23" HorizontalAlignment="Left" Width="200"/>
        <TextBox Text="{Binding Path=Text, Converter={StaticResource appendTextConverter}, ConverterParameter={x:Reference btn2ConverterParameter}}" Height="23" HorizontalAlignment="Left" Width="200"/>
otrzymamy na pierwszym "TextBox'ie" napis "Written by Author" zaś na drugim "Written by Radosław Dąbrowicz".

Cóż jednak mamy zrobić w przypadku gdy chcemy "zbindować" i konwertować wiele parametrów z modelu?
Rozwiązaniem jest "MultiBinding".
W klasie "MainWindowViewModel" dodaję dwie właściwości, będą one sumowane w konwerterze:
        private double _AValue = 5;
        public double AValue
        {
            get { return _AValue; }
            set
            {
                _AValue = value;
                FirePropertyChanged(() => AValue);
            }
        }
        private double _BValue = 5;
        public double BValue
        {
            get { return _BValue; }
            set
            {
                _BValue = value;
                FirePropertyChanged(() => BValue);
            }
        }
Następnie "multi" konwerter sumujący otrzymane wartości.
    public class AdditionConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values == null)
                return 0;
            return values.Sum(obj => obj is double ? (double)obj : 0).ToString();
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
Po "zbindowaniu" wartości do kontrolek
        <TextBox Text="{Binding Path=AValue}" Height="23" HorizontalAlignment="Left" Width="200"/>
        <TextBox Text="{Binding Path=BValue}" Height="23" HorizontalAlignment="Left" Width="200"/>
        <TextBlock>
            <TextBlock.Text>
                   <MultiBinding Converter="{StaticResource additionConverter}">
                        <Binding Path="AValue"/>
                        <Binding Path="BValue"/>
                   </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
otrzymujemy prosty kalkulator obliczający sumę dwóch cyfr.

Na koniec chciałbym jeszcze wspomnieć użytecznym o konwerterze "BooleanToVisibility" dostarczonym przez Microsoft.
Konwertuje on z wartości "bool" do typu "Visibility", za pomocą którego określana jest widoczność kontrolek.
Jego użycie jest analogiczne do "naszych" konwerterów opisanych powyżej więc nie będę się już dalej rozpisywał.
Tak więc do następnego razu.

Brak komentarzy:

Prześlij komentarz