shuhelohelo’s blog

Xamarin.Forms多めです.

Xamarin.FormsでEmbedded Resourceの画像を使う

Xamarin.formsで組み込みの画像を使う方法

Xamarin.Formsで使う画像は画像リソースはプラットフォームのプロジェクトごとに持たせるようになっている.

しかし,同じ画像を各プロジェクトにコピーしなければならないし,その管理をする必要があるので面倒.

これを共通コード側に持たせることで,上記の手間をなくすことができる.

こちらのブログのおまけのところも参考に. rksoftware.hatenablog.com

画像をEmbedded Resourceとしてプロジェクトに追加する

f:id:shuhelohelo:20200105114048p:plain

追加自体は特に難しくない. プロジェクトにドラッグアンドドロップするだけ.

次に画像ファイルのプロパティを開いて,

f:id:shuhelohelo:20200105114257p:plain

Build ActionEmbedded Resourceにする.

f:id:shuhelohelo:20200105114342p:plain

マークアップ拡張を作成する

リソースを表す文字列からImageSourceを返すマークアップ拡張を作成する.

[ContentProperty(nameof(Source))]属性をつけることを忘れないこと.

namespace XFEmbeddedImages.Extensions
{
    [ContentProperty(nameof(Source))]
    class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null)
            {
                return null;
            }

            var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);

            return imageSource;
        }
    }
}

XAML側ではこのマークアップ拡張を利用するために,上記のクラスの名前空間を追加します.

xmlns:local="clr-namespace:XFEmbeddedImages.Extensions"

そしてEmbedded Resourceにした画像ファイルを以下の形で指定します.

名前空間.フォルダ名.ファイル名

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="XFEmbeddedImages.MainPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:d="http://xamarin.com/schemas/2014/forms/design"
    xmlns:local="clr-namespace:XFEmbeddedImages.Extensions"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackLayout>
        <Image Source="{local:ImageResource XFEmbeddedImages.Images.avatar_men_2.jpg}" />
    </StackLayout>

</ContentPage>

実行すると,画像が表示される. f:id:shuhelohelo:20200105120330p:plain

バインディングのときはどうするの?

上で書いたとおり,埋め込みリソースの画像を使用するには文字列で埋め込みリソースを指定して,それを使ってImageSourceを作成している.

ということで,ImageのSourceプロパティに文字列をバインディングして,それをConverterでImageSourceに変換すればいいということかと.

以下のようなConverterを定義する. IMarkupExtensionを実装することでXAML側で使いやすくしておく.

    public class ImageSourceConverter : IMarkupExtension, IValueConverter
    {
        //Source→View
        //ViewModelやコードビハインドからXaml側へ
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string embeddedResourcePath = (string)value;

            var imageSource = ImageSource.FromResource(embeddedResourcePath, typeof(ImageResourceExtension).GetTypeInfo().Assembly);

            return imageSource;
        }

        //View→Source
        //Xaml側からViewModelやコードビハインドへ
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }

コードビハインド側は以下のようにバインディング元のプロパティを用意し,埋め込みリソースを指定する文字列(名前空間.フォルダ名.ファイル名)を設定しておく.

    public partial class MainPage : ContentPage
    {
        public string ResourcePath { get; set; } = "XFEmbeddedImages.Images.avatar_men_2.jpg";
        public MainPage()
        {
            InitializeComponent();

            this.BindingContext = this;
        }
    }

XAML側では以下のように通常のバインディングコンバーターの使い方をすればよい.

(前提:このコンバーター名前空間を追加しておく)

        <Image Source="{Binding ResourcePath, Converter={local:ImageSourceConverter}}" />

実行すると以下のように画像が表示され,バインディングで画像を表示できることが確認できる.

f:id:shuhelohelo:20200105130858p:plain

ソースコード:

github.com

メモ

    [ContentProperty(nameof(Source))]
    public sealed class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null) return null;

            return ImageSource.FromResource(Source);
        }
    }