読者です 読者をやめる 読者になる 読者になる

しっぽを追いかけて

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

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

Xamarin.Forms の Image に string の画像ファイルのローカルパスをデータバインディングする

C# Prism Windows ランタイムアプリ Xamarin XAML

WIndows ストアアプリなどの XAML では、下記のような感じで Image コントロールに対して直接画像ファイルのローカルパスをデータバインディングすることができます

<Image Source="{Binding ImageUri}"/> 

この場合の ImageUri は次のような string による指定でも大丈夫です

public class TopPageViewModel : ViewModelBase
{
    /// <summary>
    /// 画像のローカルファイルパス
    /// </summary>
    private string imageUri;

    /// <summary>
    /// 画像のローカルファイルパス
    /// </summary>
    public string ImageUri 
    {
        get { return this.imageUri; }
        set { this.SetProperty<string>(ref this.imageUri, value); }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPageViewModel()
    {
        this.imageUri = @"ms-appx:///Assets/cat01.png";
    }
}

これを Xamarin.Forms で同様にやろうとすると・・・表示されなくなります;

Xamarin.Forms の場合、共有プロジェクトに画像ファイルを配置する場合は、直接 string を指定するのは無理で、原則画像ファイルのビルドアクションをプロパティウィンドウから「埋め込みリソース」にした上で、次のように ImageSource に変換して指定しなければいけないようです

new Image().Source = ImageSource.FromResource(@"ImageSourceSample.Assets.cat01.png");

上記は ImageSourceSample というプロジェクト名の場合で、パスの指定の仕方が Winows ストアアプリなどと異なるのは仕方ないとしても、ViewModel に ImageSource の形式のプロパティを持たせるのはちょっと好ましくないですね

Xamarin の公式には IMarkupExtention を利用した方法 が紹介されていますが、今回は ValueConverter を利用して同様のことをやってみます

まずは ImageSourceConverter という IValueConverter を実装するクラスを共有プロジェクトに追加します

/// <summary>
/// 画像パスの文字列を ImageSource に変換する Converter
/// </summary>
public class ImageSourceConverter : IValueConverter
{
    #region IValueConverter

    /// <summary>
    /// 画像パスの文字列を ImageSource に変換します
    /// </summary>
    /// <param name="value">画像パスの文字列</param>
    /// <param name="targetType">変換後の型</param>
    /// <param name="parameter">パラメータ</param>
    /// <param name="culture">対象カルチャ</param>
    /// <returns>ImageSource</returns>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is string))
        {
            return default(ImageSource);
        }
        return ImageSource.FromResource(value.ToString());
    }

    /// <summary>
    /// ImageSource を画像パスの文字列に変換します
    /// </summary>
    /// <param name="value">画像パスの文字列</param>
    /// <param name="targetType">変換後の型</param>
    /// <param name="parameter">パラメータ</param>
    /// <param name="culture">対象カルチャ</param>
    /// <returns>画像パスの文字列</returns>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion //IValueConverter
}

あとは 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:cv="clr-namespace:ImageSourceSample.Converters;assembly=ImageSourceSample"
             xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=Prism.Mvvm.Xamarin"
             prismmvvm:ViewModelLocator.AutoWireViewModel="true"
             x:Class="ImageSourceSample.Views.TopPage">
  <ContentPage.Resources>
    <ResourceDictionary>
      <cv:ImageSourceConverter x:Key="ImageSourceConverter"/>
    </ResourceDictionary>
  </ContentPage.Resources>
  <Image Source="{Binding ImageUri, Converter={StaticResource ImageSourceConverter}}">
</ContentPage>

こうすれば ViewModel には ImageSource という View 依存のプロパティを排除して、プリミティブな string の画像パスだけ持てばよくなるわけです

ViewModel のデータを一時的にファイルに退避したりする場合にもこうしておくと楽なのでおすすめです