Преобразователи значений привязки Xamarin.Forms

Download Sample Скачайте пример

Привязки данных обычно передают данные из исходного свойства в целевое свойство (а иногда из целевого свойства в исходное). Эта передача проста в том случае, когда исходные и целевые свойства относятся к одному типу или когда один тип может быть преобразован в другой тип путем неявного преобразования. Если это не так, должно выполняться преобразование типов.

В статье, посвященной форматированию строк, было показано, как можно использовать свойство StringFormat привязки данных для преобразования любого типа в строку. Для других типов преобразований необходимо написать специальный код в классе, реализующем интерфейс IValueConverter. (Универсальная платформа Windows содержит аналогичный класс, названный IValueConverter в Windows.UI.Xaml.Data пространстве имен, но он IValueConverter находится в Xamarin.Forms пространстве имен.) Классы, реализующиеIValueConverter, называются преобразователями значений, но они также часто называются преобразователями привязки или преобразователями значений привязки.

Интерфейс IValueConverter

Предположим, что вы хотите определить привязку данных, в которой исходное свойство имеет тип int, а целевым свойством является bool. Требуется, чтобы эта привязка данных создавала значение false, если целочисленный источник равняется нулю, и значение true в противном случае.

Это можно сделать с помощью класса, реализующего интерфейс IValueConverter.

public class IntToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value != 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? 1 : 0;
    }
}

Вы можете задать экземпляр этого класса для свойства Converter класса Binding или свойства Converter расширения разметки Binding. Этот класс становится частью привязки данных.

Метод Convert вызывается при перемещении данных из источника в целевое свойство в привязках OneWay или TwoWay. Параметр value — это объект или значение из источника привязки данных. Метод должен возвращать значение типа целевого свойства привязки данных. Показанный здесь метод приводит параметр value к int, а затем сравнивает их с нулем для получения логического (bool) возвращаемого значения.

Метод ConvertBack вызывается, когда данные перемещаются из целевого в исходное свойство в привязках TwoWay или OneWayToSource. ConvertBack выполняет обратное преобразование: он предполагает, что параметр value является bool из целевого свойства и преобразует его в возвращаемое значение int для источника.

Если привязка данных также содержит параметр StringFormat, вызывается преобразователь величин, прежде чем результат форматируется как строка.

Страница Включение кнопок в примере Демонстрации привязки данных демонстрирует использование этого преобразователя в привязке данных. Экземпляр IntToBoolConverter создается в словаре ресурсов страницы. Затем он указывается с помощью расширения разметки StaticResource, чтобы задать свойство в двух привязках данных Converter. Совместное использование преобразователей данных несколькими привязками данных на странице широко распространено:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.EnableButtonsPage"
             Title="Enable Buttons">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:IntToBoolConverter x:Key="intToBool" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="enter search term"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Search"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry1},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />

        <Entry x:Name="entry2"
               Text=""
               Placeholder="enter destination"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Submit"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry2},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
    </StackLayout>
</ContentPage>

Если преобразователь величин используется на нескольких страницах приложения, можно создать его экземпляр в словаре ресурсов в файле App.xaml.

Страница Включение кнопок демонстрирует распространенную потребность, когда Button выполняет операцию на основе текста, который пользователь вводит в представлении Entry. Если никакие данные не были введены в Entry, Button должен быть отключен. Каждый элемент Button содержит привязку данных для его свойства IsEnabled. Источником привязки данных является свойство Length свойства Text соответствующего элемента Entry. Если это свойство Length не возвращает нуль, преобразователь величин возвращает true и включается элемент Button:

Enable Buttons

Обратите внимание, что свойство Text в каждом Entry инициализируется в пустую строку. Свойство Text по null умолчанию, и привязка данных не будет работать в этом случае.

Некоторые преобразователи величин созданы специально для конкретных приложений, тогда как другие являются общими. Если вы знаете, что преобразователь величин будет использоваться только в привязках OneWay, то метод ConvertBack может просто возвращать null.

Метод Convert в приведенном выше примере неявно предполагает, что аргумент value имеет тип int, а возвращаемое значение должно быть типа bool. Аналогичным образом метод ConvertBack предполагает, что аргумент value имеет тип bool, и возвращает значение int. Если это не так, возникнет исключение времени выполнения.

Вы можете написать более общие преобразователи величин, которые принимают несколько разных типов данных. Методы Convert и ConvertBack могут использовать операторы as или is с параметром value либо вызывать GetType для этого параметра, чтобы определить его тип, а затем выполнять соответствующие действия. Ожидаемый тип возвращаемого значения метода задается параметром targetType. В некоторых случаях преобразователи величин используются с привязками данных разных целевых типов: преобразователь величин может использовать аргумент targetType для выполнения преобразования для правильного типа.

Если выполняемое преобразование отличается для разных языков и региональных параметров, используйте для этой цели параметр culture. Аргумент parameter для Convert и ConvertBack рассматривается далее в этой статье.

Свойства преобразователя привязки

Классы преобразователей величин могут иметь свойства и универсальные параметры. Этот конкретный преобразователь величин преобразует bool из источника в объект типа T для целевого свойства:

public class BoolToObjectConverter<T> : IValueConverter
{
    public T TrueObject { set; get; }

    public T FalseObject { set; get; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? TrueObject : FalseObject;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((T)value).Equals(TrueObject);
    }
}

Страница Переключение индикаторов демонстрирует, как он может использоваться для отображения значения представления Switch. Хотя, как правило, экземпляры преобразователей величин создаются как ресурсы в словаре ресурсов, эта страница демонстрирует альтернативу: экземпляр каждого преобразователя величин создается между тегами элемента и свойства Binding.Converter. x:TypeArguments указывает универсальный аргумент, а для TrueObject и FalseObject задаются объекты этого типа:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SwitchIndicatorsPage"
             Title="Switch Indicators">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Label">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>

            <Style TargetType="Switch">
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="CenterAndExpand">
            <Label Text="Subscribe?" />
            <Switch x:Name="switch1" />
            <Label>
                <Label.Text>
                    <Binding Source="{x:Reference switch1}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="x:String"
                                                         TrueObject="Of course!"
                                                         FalseObject="No way!" />
                        </Binding.Converter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal"
                     VerticalOptions="CenterAndExpand">
            <Label Text="Allow popups?" />
            <Switch x:Name="switch2" />
            <Label>
                <Label.Text>
                    <Binding Source="{x:Reference switch2}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="x:String"
                                                         TrueObject="Yes"
                                                         FalseObject="No" />
                        </Binding.Converter>
                    </Binding>
                </Label.Text>
                <Label.TextColor>
                    <Binding Source="{x:Reference switch2}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="Color"
                                                         TrueObject="Green"
                                                         FalseObject="Red" />
                        </Binding.Converter>
                    </Binding>
                </Label.TextColor>
            </Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal"
                     VerticalOptions="CenterAndExpand">
            <Label Text="Learn more?" />
            <Switch x:Name="switch3" />
            <Label FontSize="18"
                   VerticalOptions="Center">
                <Label.Style>
                    <Binding Source="{x:Reference switch3}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="Style">
                                <local:BoolToObjectConverter.TrueObject>
                                    <Style TargetType="Label">
                                        <Setter Property="Text" Value="Indubitably!" />
                                        <Setter Property="FontAttributes" Value="Italic, Bold" />
                                        <Setter Property="TextColor" Value="Green" />
                                    </Style>                                    
                                </local:BoolToObjectConverter.TrueObject>

                                <local:BoolToObjectConverter.FalseObject>
                                    <Style TargetType="Label">
                                        <Setter Property="Text" Value="Maybe later" />
                                        <Setter Property="FontAttributes" Value="None" />
                                        <Setter Property="TextColor" Value="Red" />
                                    </Style>
                                </local:BoolToObjectConverter.FalseObject>
                            </local:BoolToObjectConverter>
                        </Binding.Converter>
                    </Binding>
                </Label.Style>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>

В последних трех парах Switch и Label универсальный аргумент имеет значение Style и все объекты Style предоставляются для значений TrueObject и FalseObject. Они переопределяют неявный стиль для Label, заданный в словаре ресурсов, поэтому свойства в этом стиле явно назначаются Label. Включение и выключение Switch приводит к тому, что изменения отражаются в соответствующем Label:

Switch Indicators

Triggers можно также использовать для реализации аналогичных изменений в интерфейсе пользователя на основе других представлений.

Параметры преобразователя привязки

Класс Binding определяет свойство ConverterParameter, и расширение разметки Binding также определяет свойство ConverterParameter. Если это свойство задано, то значение передается в методы Convert и ConvertBack как аргумент parameter. Даже если экземпляр преобразователя величин совместно используется несколькими привязками данных, параметр ConverterParameter может отличаться для выполнения немного отличающихся преобразований.

Использование ConverterParameter демонстрируется на примере программы выбора цветов. В этом случае RgbColorViewModel имеет три свойства типа double с именем Red, Green и Blue, которые используются для создания значения Color:

public class RgbColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public double Red
    {
        set
        {
            if (color.R != value)
            {
                Color = new Color(value, color.G, color.B);
            }
        }
        get
        {
            return color.R;
        }
    }

    public double Green
    {
        set
        {
            if (color.G != value)
            {
                Color = new Color(color.R, value, color.B);
            }
        }
        get
        {
            return color.G;
        }
    }

    public double Blue
    {
        set
        {
            if (color.B != value)
            {
                Color = new Color(color.R, color.G, value);
            }
        }
        get
        {
            return color.B;
        }
    }

    public Color Color
    {
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Red"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Green"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Blue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
        get
        {
            return color;
        }
    }

    public string Name
    {
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
        get
        {
            return name;
        }
    }
}

Значения свойств Red, Green и Blue задаются в диапазоне от 0 до 1. При этом требуется, чтобы компоненты отображались как шестнадцатеричные значения из двух цифр.

Чтобы отобразить их в виде шестнадцатеричных значений в XAML, они должны быть умножены на 255, преобразованы в целое число, а затем отформатированы со спецификацией X2 в свойстве StringFormat. Преобразователь величин может обработать первые две задачи (умножение на 255 и преобразование в целое число). Чтобы сделать преобразователь величин максимально универсальным, можно указать множитель с помощью свойства ConverterParameter, которое означает, что он вводит методы Convert и ConvertBack как аргумент parameter:

public class DoubleToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)Math.Round((double)value * GetParameter(parameter));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value / GetParameter(parameter);
    }

    double GetParameter(object parameter)
    {
        if (parameter is double)
            return (double)parameter;

        else if (parameter is int)
            return (int)parameter;

        else if (parameter is string)
            return double.Parse((string)parameter);

        return 1;
    }
}

Convert преобразует из double в int с умножением на значение parameter; ConvertBack делит аргумент value целого числа на parameter и возвращает результат double. (В программе, показанной ниже, преобразователь величин используется только в связи с форматированием строк, поэтому ConvertBack не используется.)

Тип аргумента parameter может отличаться в зависимости от того, определена ли привязка данных в коде или в XAML. Если свойство ConverterParameter объекта Binding задается в коде, вполне вероятно, ему будет присвоено числовое значение:

binding.ConverterParameter = 255;

Свойство ConverterParameter имеет тип Object, поэтому компилятор C# интерпретирует литерал 255 как целое число и присваивает это значение свойству.

При этом в XAML ConverterParameter, вероятнее всего, будет задано следующим образом:

<Label Text="{Binding Red,
                      Converter={StaticResource doubleToInt},
                      ConverterParameter=255,
                      StringFormat='Red = {0:X2}'}" />

255 выглядит как число, но, поскольку ConverterParameter имеет тип Object, средство синтаксического анализа XAML обрабатывает 255 как строку.

По этой причине преобразователь величин, показанный выше, включает отдельный метод GetParameter, который обрабатывает ситуации, когда parameter принадлежит к типу double, int или string.

Страница Выбор цвета RGB создает экземпляр DoubleToIntConverter в словаре ресурсов, за которым следует определение двух неявные стилей:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.RgbColorSelectorPage"
             Title="RGB Color Selector">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Slider">
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>

            <Style TargetType="Label">
                <Setter Property="HorizontalTextAlignment" Value="Center" />
            </Style>

            <local:DoubleToIntConverter x:Key="doubleToInt" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <StackLayout.BindingContext>
            <local:RgbColorViewModel Color="Gray" />
        </StackLayout.BindingContext>

        <BoxView Color="{Binding Color}"
                 VerticalOptions="FillAndExpand" />

        <StackLayout Margin="10, 0">
            <Label Text="{Binding Name}" />

            <Slider Value="{Binding Red}" />
            <Label Text="{Binding Red,
                                  Converter={StaticResource doubleToInt},
                                  ConverterParameter=255,
                                  StringFormat='Red = {0:X2}'}" />

            <Slider Value="{Binding Green}" />
            <Label Text="{Binding Green,
                                  Converter={StaticResource doubleToInt},
                                  ConverterParameter=255,
                                  StringFormat='Green = {0:X2}'}" />

            <Slider Value="{Binding Blue}" />
            <Label>
                <Label.Text>
                    <Binding Path="Blue"
                             StringFormat="Blue = {0:X2}"
                             Converter="{StaticResource doubleToInt}">
                        <Binding.ConverterParameter>
                            <x:Double>255</x:Double>
                        </Binding.ConverterParameter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>    

Значения свойств Red и Green отображаются с помощью расширения разметки Binding. Свойство Blue, тем не менее, создает экземпляр класса Binding для демонстрации способа задания явного значения double для свойства ConverterParameter.

Ниже приведен результат:

RGB Color Selector