しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で数値が含まれる範囲に応じて任意の値に切り替える

数値を配色や文字列に変換する Converter を作りましたが、必要な値に応じて Converter を用意するのは少し面倒です

というわけで、変換後の値を任意の型にできるような汎用的な Converter にしてみたいと思います

まずは Converter を作成

/// <summary>
/// 数値から任意の値に変換する Converter
/// </summary>
[ContentProperty("Ranges")]
public class RangeToValueConverter<T> : IValueConverter 
{
    /// <summary>
    /// ColorTypeConverter
    /// </summary>
    private static readonly ColorTypeConverter ColorConverter = new ColorTypeConverter();

    /// <summary>
    /// ThicknessTypeConverter
    /// </summary>
    private static readonly ThicknessTypeConverter ThicknessConverter = new ThicknessTypeConverter();

    /// <summary>
    /// 数値範囲定義
    /// </summary>
    public List<RangeToValue> Ranges { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public RangeToValueConverter()
    {
        this.Ranges = new List<RangeToValue>();
    }

    /// <summary>
    /// 数値から任意の値に変換します
    /// </summary>
    /// <param name="value">数値</param>
    /// <param name="targetType">対象の型</param>
    /// <param name="parameter">パラメータ</param>
    /// <param name="culture">カルチャ</param>
    /// <returns>任意の値</returns>
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var converted = default(T);
        var number = default(double);

        if (value is double)
        {
            number = (double)value;
        }
        else if (value != null)
        {
            double.TryParse(value.ToString(), out number);
        }

        var range = (from r in this.Ranges
                        where r.From <= number && r.To >= number
                        select r).FirstOrDefault();

        if (range == null)
        {
            return converted;
        }

        try
        {
            switch (typeof(T).Name.ToLower())
            {
                case "color":
                    if (ColorConverter.CanConvertFrom(range.Value.GetType()))
                    {
                        converted = (T)ColorConverter.ConvertFrom(culture, range.Value);
                    }
                    break;

                case "thickness":
                    if (ThicknessConverter.CanConvertFrom(range.Value.GetType()))
                    {
                        converted = (T)ThicknessConverter.ConvertFrom(culture, range.Value);
                    }
                    break;

                default:
                    converted = (T)range.Value;
                    break;
            }
        }
        catch (InvalidCastException)
        {
        }

        return converted;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

/// <summary>
/// 数値範囲の記述クラス
/// </summary>
public class RangeToValue : BindableObject
{
    /// <summary>
    /// 数値範囲の最小値
    /// </summary>
    public double From { get; set; }

    /// <summary>
    /// 数値範囲の最大値
    /// </summary>
    public double To { get; set; }

    /// <summary>
    /// 変換後の値
    /// </summary>
    public object Value { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public RangeToValue()
    {
        this.From = 0d;
        this.To = 0d;
        this.Value = null;
    }
}

クラスをジェネリック型にして任意の値を XAML 側から指定できるようにしています

とりあえず文字列から変換しなければならなさそうな Color と Thickness のみ特別に分岐して変換します

これを前回の XAML を少し変更して利用します

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:b="clr-namespace:XamarinControl.Behaviors;assembly=XamarinControl"
             xmlns:c="clr-namespace:XamarinControl.Controls;assembly=XamarinControl"
             xmlns:cv="clr-namespace:XamarinControl.Converters;assembly=XamarinControl"
             xmlns:s="clr-namespace:XamarinControl.Selectors;assembly=XamarinControl"
             xmlns:vm="clr-namespace:XamarinControl.ViewModels;assembly=XamarinControl"
             xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
             x:Class="XamarinControl.Views.TopPage">

  <ContentPage.Resources>
    <ResourceDictionary>
      <cv:RangeToValueConverter x:Key="RangeToColorConverter" x:TypeArguments="Color">
        <cv:RangeToValue From="0" To="60" Value="Red"/>
        <cv:RangeToValue From="60" To="80" Value="Green"/>
        <cv:RangeToValue From="80" To="100" Value="Blue"/>
      </cv:RangeToValueConverter>
      <cv:RangeToValueConverter x:Key="RangeToTextConverter" x:TypeArguments="x:String">
        <cv:RangeToValue From="0" To="60" Value="赤点です!"/>
        <cv:RangeToValue From="80" To="100" Value="合格!"/>
      </cv:RangeToValueConverter>
    </ResourceDictionary>
  </ContentPage.Resources>
  
  <ListView >
    <ListView.ItemsSource>
      <scg:List x:TypeArguments="x:Int32">
        <x:Int32>50</x:Int32>
        <x:Int32>70</x:Int32>
        <x:Int32>80</x:Int32>
        <x:Int32>100</x:Int32>
      </scg:List>
    </ListView.ItemsSource>
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout Orientation="Horizontal" 
                       Padding="20,10" 
                       Spacing="10">
            <Label Text="{Binding StringFormat='{0} 点'}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center"
                   TextColor="{Binding Converter={StaticResource RangeToColorConverter}}"
                   FontSize="50"/>
            <Label Text="{Binding Converter={StaticResource RangeToTextConverter}}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center"
                   FontSize="40"/>
          </StackLayout>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

</ContentPage>

XAML 側からジェネリックの型(T の部分)を指定するには x:TypeArguments の添付プロパティを使います

Converter の定義以外は前回と全く同じ!

f:id:matatabi_ux:20150219001212p:plain

結果も全く同じ!

これで 2つ用意していた Converter が1つで済みましたね!