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

Browse sample. Обзор примера

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

В статье о форматировании строк вы узнали, как использовать StringFormat свойство привязки данных для преобразования любого типа в строку. Для других типов преобразований необходимо написать специальный код в классе, реализующем интерфейс 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;
    }
}

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

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

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

Примечание.

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

В следующем примере показано, как использовать этот преобразователь значений в привязке данных:

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

    <StackLayout Padding="10, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="enter search term"
               VerticalOptions="Center" />
        <Button Text="Search"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                IsEnabled="{Binding Source={x:Reference entry1},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
        <Entry x:Name="entry2"
               Text=""
               Placeholder="enter destination"
               VerticalOptions="Center" />
        <Button Text="Submit"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                IsEnabled="{Binding Source={x:Reference entry2},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
    </StackLayout>
</ContentPage>

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

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

Enable buttons.

Примечание.

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

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

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

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

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

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

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

    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://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SwitchIndicatorsPage"
             Title="Switch Indicators">
    <ContentPage.Resources>
        <Style TargetType="Label">
            <Setter Property="FontSize" Value="18" />
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>

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

    <StackLayout Padding="10, 0">
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="Center">
            <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="Center">
            <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="Center">
            <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.

Примечание.

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

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

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

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

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

    public event PropertyChangedEventHandler PropertyChanged;

    public float Red
    {
        get { return color.Red; }
        set
        {
            if (color.Red != value)
            {
                Color = new Color(value, color.Green, color.Blue);
            }
        }
    }

    public float Green
    {
        get { return color.Green; }
        set
        {
            if (color.Green != value)
            {
                Color = new Color(color.Red, value, color.Blue);
            }
        }
    }

    public float Blue
    {
        get { return color.Blue; }
        set
        {
            if (color.Blue != value)
            {
                Color = new Color(color.Red, color.Green, value);
            }
        }
    }

    public Color Color
    {
        get { return 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);
            }
        }
    }

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

Значения Redсвойств и Blue свойств Greenмогут варьироваться от 0 до 1. При этом требуется, чтобы компоненты отображались как шестнадцатеричные значения из двух цифр. Чтобы отобразить их в виде шестнадцатеричных значений в XAML, они должны быть умножены на 255, преобразованы в целое число, а затем отформатированы со спецификацией X2 в свойстве StringFormat. Умножение на 255 и преобразование в целое число может выполняться преобразователем значений. Чтобы сделать преобразователь величин максимально универсальным, можно указать множитель с помощью свойства ConverterParameter, которое означает, что он вводит методы Convert и ConvertBack как аргумент parameter:

public class FloatToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)Math.Round((float)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 float)
            return (float)parameter;
        else if (parameter is int)
            return (int)parameter;
        else if (parameter is string)
            return float.Parse((string)parameter);

        return 1;
    }
}

В этом примере Convert метод преобразуется из a float в int то время, как умножается на parameter значение. Метод ConvertBack делит целый value аргумент на parameter и возвращает float результат.

Тип аргумента 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 как строку. По этой причине преобразователь значений floatвключает отдельный GetParameter метод, который обрабатывает варианты для parameter типа , intили string.

Следующий пример XAML создает FloatToIntConverter экземпляры в словаре ресурсов:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.RgbColorSelectorPage"
             Title="RGB Color Selector">
    <ContentPage.BindingContext>
        <local:RgbColorViewModel Color="Gray" />
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <Style TargetType="Slider">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>

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

        <local:FloatToIntConverter x:Key="floatToInt" />
    </ContentPage.Resources>

    <StackLayout Margin="20">
        <BoxView Color="{Binding Color}"
                 HeightRequest="100"
                 WidthRequest="100"
                 HorizontalOptions="Center" />
        <StackLayout Margin="10, 0">
            <Label Text="{Binding Name}" />
            <Slider Value="{Binding Red}" />
            <Label Text="{Binding Red,
                                  Converter={StaticResource floatToInt},
                                  ConverterParameter=255,
                                  StringFormat='Red = {0:X2}'}" />
            <Slider Value="{Binding Green}" />
            <Label Text="{Binding Green,
                                  Converter={StaticResource floatToInt},
                                  ConverterParameter=255,
                                  StringFormat='Green = {0:X2}'}" />
            <Slider Value="{Binding Blue}" />
            <Label>
                <Label.Text>
                    <Binding Path="Blue"
                             StringFormat="Blue = {0:X2}"
                             Converter="{StaticResource floatToInt}">
                        <Binding.ConverterParameter>
                            <x:Single>255</x:Single>
                        </Binding.ConverterParameter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>

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

RGB color selector.