C#やモバイルアプリ関係のログ

C#やXamarin、スマートフォンアプリのことを書いておきたいと思います

Xamarin.Forms XamlのGrid・BindingとDependencyServiceの説明

Xamarin.Formsの各OS毎の機能の呼び出し方の説明
WEBブラウザ起動を例として記載します。

-XamlのGridの説明
-XamlのBindingの説明
-DependencyServiceの説明

空のプロジェクト作成

空のプロジェクトを作成してください。
f:id:Jirobe_Katori:20190326224013j:plain

f:id:Jirobe_Katori:20190326224016j:plain

フォルダ"Views"を作成してMainPage.xamlを格納してください。
f:id:Jirobe_Katori:20190327061943j:plain

Gridについて

MainPage.xamlのUIを変更しましょう。xamlはザムルと読みます。下記の内容に書き換えてください。

MainPage.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"
             x:Class="Sample.MainPage">

    <!-- 行3列2のマトリクス -->
    <Grid>

        <!-- 数字が固定長、Autoが必要最低、*があまり全部 -->
        <!-- ここでは列2個-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <!-- 数字が固定長、Autoが必要最低、*があまり全部 -->
        <!-- ここでは行3個-->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="50" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- Grid.Column、Grid.Rowで座標指定 座標指定だから順番は自由-->
        <StackLayout Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center">
            <Entry Placeholder="URIを入力してください" />
        </StackLayout>
        <Button Grid.Column="1" Grid.Row="1" Text="URIを開く:Device.OpenUri" />
        <Button Grid.Column="0" Grid.Row="2" Text="URIを開く:DependencyService" />
        
    </Grid>
</ContentPage>

 

そうすると下記のようになります。 サンプルのGridは列が2個、行が3個です。インデックスは0スタート。1行目は2列の結合、2行目は2列目、3行目は1列目に描写としています。
f:id:Jirobe_Katori:20190327062852j:plain

Device.OpenUri(Uri) Method

28行目の要素に「Clicked=」を手打ちで書き加えてください。 すると「新しいイベントハンドラ」とガイドが出てきますので押下してください。

<Button Grid.Column="1" Grid.Row="1" Text="URIを開く:Device.OpenUri"  Clicked="Button_Clicked"/>

 

MainPage.xaml.csを開いてみましょう。
f:id:Jirobe_Katori:20190327063823j:plain

イベントハンドラ「Button_Clicked」が追加されているはずです。

private void Button_Clicked(object sender, EventArgs e)
{

 }

 

ここに処理を書いてみましょう。

private void Button_Clicked(object sender, EventArgs e)
{
    Device.OpenUri(new Uri(@"https://www.google.com/"));
}

 

これで実行して、"URIを開く:Device.OpenUri"のボタンを押下してください。WEBブラウザが起動するはずです。

AndroidとUWPなど異なる環境間で同じ動きであることを確認してください。

WEBブラウザを開くというOSの機能の呼び出しですが、URIを開くことだけは下記の関数が用意されています。 docs.microsoft.com

しかし、ほかの動作は現状共通ロジックがXamarin.Forms側で用意されているわけではありません。そこで、次は各OSの機能を各OS毎に定義して呼び出す方法に触れたいと思います。

DependencyService インターフェース部

DependencyService というものがあります。

docs.microsoft.com

実際に書いていきたいと思います。.Net StandardのプロジェクトにフォルダServicesを追加しましょう。

f:id:Jirobe_Katori:20190328072903j:plain

Servicesの中にインターフェースIOpenUriServiceを作成しましょう。

IOpenUriService.cs

namespace Sample.Services
{
    public interface IOpenUriService
    {
        void OpenUri(string uri);
    }
}

MainPage.xamlの29行目の要素に「Clicked=」を手打ちで書き加えてください。 すると「新しいイベントハンドラ」とガイドが出てきますので押下してください。

MainPage.xaml.csに新しいイベントハンドラが増えているのでそれを下記のように編集しましょう。

private void Button_Clicked_1(object sender, EventArgs e)
{
    var openUriService = DependencyService.Get<IOpenUriService>();
    openUriService.OpenUri(@"https://www.google.com/");
}

これで”ボタンを押すとインターフェイスIOpenUriServiceのOpenUri(string)を呼び出す”という指示をライブラリに与えることができました。

下図の青で囲った部分の実行プロジェクトのほうに実装を書きましょう。
f:id:Jirobe_Katori:20190329070610p:plain

DependencyService 実装部 Android

AndroidのプロジェクトにフォルダSevicesを追加しましょう。 f:id:Jirobe_Katori:20190329071024p:plain

Services配下にクラスOpenUriServiceを追加します。
OpenUriService.cs

using Android.Content;
using Android.Net;
using Sample.Droid.Services;
using Sample.Services;
using Xamarin.Forms;

[assembly: Dependency(typeof(OpenUriService))]
namespace Sample.Droid.Services
{
    class OpenUriService : IOpenUriService
    {
        public void OpenUri(string strUri)
        {
            Uri uri = Uri.Parse(strUri);
            Intent intent = new Intent(Intent.ActionView, uri);
            Android.App.Application.Context.StartActivity(intent);
        }
    }
}

ここまで出来たらAndroidで実行してみてください。 "URIを開く:DependencyService"ボタン押下でブラウザが起動すれば成功です。

DependencyService 実装部 UWP Windows

UWPのプロジェクトにフォルダSevicesを追加しましょう。 f:id:Jirobe_Katori:20190330072806p:plain

Services配下にクラスOpenUriServiceを追加します。
OpenUriService.cs

using Sample.Services;
using Sample.UWP.Services;
using System;
using Windows.System;
using Xamarin.Forms;

[assembly: Dependency(typeof(OpenUriService))]
namespace Sample.UWP.Services
{
    class OpenUriService : IOpenUriService
    {
        public void OpenUri(string strUri)
        {
            Uri uri = new Uri(strUri);
            var wb = Launcher.LaunchUriAsync(uri);
        }
    }
}

ここまで出来たらWindowsで実行してみてください。 "URIを開く:DependencyService"ボタン押下でブラウザが起動すれば成功です。

MVVM Bindig コマンド

MVVMというものがあります。Xamarin.FormsというかXamlを使うものはMVVMパターンで書くものであるそうです。

aa Model View ViewModel - Wikipedia

MVVMでいうと現時点のサンプルはViewであるMainPage.xaml.csに直接ロジックを書いていてパターンに沿っていません。

MVVMパターンに書き換えてみましょう。WEB上の記事を見ればModelでもINotifyPropertyChangedを実装してしまうのはOKは風潮を感じるのでここでは単純化のためにViewModelとModelを同じものとして扱います。

MainPage.xamlのボタンを書き換えます。ボタンのClicked属性をCommand属性に書き換えてください。

<Button Grid.Column="1" Grid.Row="1" Text="URIを開く:Device.OpenUri" Command="{Binding DeviceOpenUriCommand}"/>
<Button Grid.Column="0" Grid.Row="2" Text="URIを開く:DependencyService" Command="{Binding DependencyServiceCommand}"/>

いらなくなったイベントハンドラコメントアウトしておきましょう。
f:id:Jirobe_Katori:20190330081112p:plain

ライブラリのプロジェクトにフォルダViewModelsを作ってください。
f:id:Jirobe_Katori:20190330075531p:plain

作成したフォルダViewModelsの中にクラスBaseViewModelを作成してください。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Sample.ViewModels
{
    class BaseViewModel : INotifyPropertyChanged
    {
        protected bool SetProperty<T>(ref T backingStore, T value,
    [CallerMemberName]string propertyName = "",
    Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

 

フォルダViewModelsの中にクラスMainPageViewModelを作成してください。

using Sample.Services;
using System;
using System.Windows.Input;
using Xamarin.Forms;

namespace Sample.ViewModels
{
    class MainPageViewModel : BaseViewModel
    {
        public ICommand DeviceOpenUriCommand => new Command(() => Device.OpenUri(new Uri(@"https://www.google.com/")));
        public ICommand DependencyServiceCommand => new Command(() =>
        {
            var openUriService = DependencyService.Get<IOpenUriService>();
            openUriService.OpenUri(@"https://www.google.com/");
        });
    }
}

 
作成したVIewModelをViewで参照します。
MainPage.cs

using Sample.ViewModels;
using Xamarin.Forms;

namespace Sample
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            //ここでViewはViewModelを参照します
            BindingContext = new MainPageViewModel();
        }

        //private void Button_Clicked(object sender, EventArgs e)
        //{
        //    Device.OpenUri(new Uri(@"https://www.google.com/"));
        //}

        //private void Button_Clicked_1(object sender, EventArgs e)
        //{
        //    var openUriService = DependencyService.Get<IOpenUriService>();
        //    openUriService.OpenUri(@"https://www.google.com/");
        //}
    }
}

これでViewModelにロジックを記載できるようになりました。実際に動かしてみましょう。

MVVM Bindig コマンド以外

コントロールの状態(テキストやSwitchの状態など)をViewModel側で受け取ってみましょう。

Entry(テキストボックス)を画面に追加しましょう。そして、Entryに入力されたURIにブラウザで接続できるようにしましょう。

ADDとコメントで書かれている箇所が追記箇所です。

MainPage.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"
             x:Class="Sample.MainPage">

    <!-- 行3列2のマトリクス -->
    <Grid>

        <!-- 数字が固定長、Autoが必要最低、*があまり全部 -->
        <!-- ここでは列2個-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <!-- 数字が固定長、Autoが必要最低、*があまり全部 -->
        <!-- ここでは行3個-->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="50" />
            <RowDefinition Height="Auto" />
            <!-- ADD Start -->
            <RowDefinition Height="Auto" />
            <!-- ADD End -->
        </Grid.RowDefinitions>

        <!-- Grid.Column、Grid.Rowで座標指定 座標指定だから順番は自由-->
        <StackLayout Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center">
            <Entry Placeholder="URIを入力してください" />
        </StackLayout>
        <Button Grid.Column="1" Grid.Row="1" Text="URIを開く:Device.OpenUri" Command="DeviceOpenUriCommand"/>
        <Button Grid.Column="0" Grid.Row="2" Text="URIを開く:DependencyService" Command="DependencyServiceCommand"/>
        
        <!-- ADD Start -->
        <Entry Grid.Row="3" Grid.ColumnSpan="2" Placeholder="URLを入力してください" Text="{Binding URIText}" />
        <!-- ADD End -->
        
    </Grid>
</ContentPage>

Text="{Binding URIText}"と属性が書かれています。この"Binding"というのはTextに限定されず、様々な属性で利用できます。Bindingの対象はstringやintなどのプリミティブなものだけではなく、クラスなども利用できます。

次に、書き換えたXAMLに合わせてVIewModelを書き換えます。

MainPageViewModel.cs

using Sample.Services;
using System;
using System.Windows.Input;
using Xamarin.Forms;

namespace Sample.ViewModels
{
    class MainPageViewModel : BaseViewModel
    {
        //ADD Start
        private string uRIText;

        public string URIText
        {
            get { return uRIText; }
            set { SetProperty(ref uRIText, value); }
        }
        //ADD End

        //MOD Start
        //public ICommand DeviceOpenUriCommand => new Command(() => Device.OpenUri(new Uri(@"https://www.google.com/")));

        //public ICommand DependencyServiceCommand => new Command(() =>
        //{
        //    var openUriService = DependencyService.Get<IOpenUriService>();
        //    openUriService.OpenUri(@"https://www.google.com/");
        //});

        public ICommand DeviceOpenUriCommand => new Command(() => Device.OpenUri(new Uri(URIText)));

        public ICommand DependencyServiceCommand => new Command(() =>
        {
            var openUriService = DependencyService.Get<IOpenUriService>();
            openUriService.OpenUri(URIText);
        });
        //MOD End
    }
}

これで実行してみましょう。Viewの内容がViewModelに連携されることがわかります。これでVIewからロジックを取り除くことができました。

これがViewにViewModelがパラメータの形で保有している値をバィンディングしている例となります。

最後に

つたなくて申し訳ないのですが、一応これでXamarin.Formsの”異なるOSでの処理実装”と"データバィンディング"に触れられたと思います。

実際にアプリを作るとユーザーの操作関係なしに処理側でViewからViewModelやViewModelからViewにイベントを通知したい場面があると思います。そういう場面では下記のようなものを利用します。

docs.microsoft.com

そのうち、触れられたらと思います。では

Xamarin.Android JavaからC#への書き換え

Xamarin.AndroidにおいてJavaC#でラッピングされています。ですが、コードの命名JavaはキャメルでC#パスカルでそれを筆頭に書き方の作法の違いがあります。

そこで、それの雰囲気を伝えるために下記を書きました。

Xamarin.Androidのプロジェクトを作る

新規プロジェクトを作ってください。
gif

ボタンを増やす

ボタンを配置しましょう。
gif

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:text="Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minWidth="25px"
        android:minHeight="25px"
        android:id="@+id/button1" />
</RelativeLayout>[f:id:Jirobe_Katori:20190322062544g:plain]

ボタン押下にイベントを紐づける

ボタンにイベントハンドラを足しておきましょう。

    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            Button button1 = FindViewById<Button>(Resource.Id.button1);
            button1.Click += (s, e) => { };
        }
    }

javaを検索する

今回はカメラの起動をしてみましょう。Javaの記事を探します。
gif

今回はこちらの内容がよさそうです。

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, RESULT_CAMERA);

[Android] Camera で撮影、Intentで簡単にやりましょ (https://akira-watson.com/android/camera-intent.html)

JavaC#のソースに張り付け、そして考えずに感じてください。
gif

int RESULT_CAMERA = 1001;
Button button1 = FindViewById<Button>(Resource.Id.button1);
button1.Click += (s, e) => {
    Intent intent = new Intent(MediaStore.ActionImageCapture);
    StartActivityForResult(intent, RESULT_CAMERA);
};

アプリを実行

Android上でアプリを実行してみましょう。カメラが起動するはずです。

Xamarin 環境構築 手順メモ MacOS

身内向け

Visual Studio 2017 for Macのインストールから新規プロジェクト作成まで

Visual Studio 2017 for Mac

下記の手順でVisual Studioのダウンロードとインストール
Visual Studio 2017 for Mac をインストールする

Android実機の用意

Androidの実機をデバッグできるようにしておきます。

[Androidでの操作]

設定から端末情報を開き、ビルド番号を連打してください。トーストが表示されます。
f:id:Jirobe_Katori:20190312072252j:plain

そうしたら、設定から開発者向けオプションが選択できるようになっていますので画面遷移して、USBデバッグを有効化してください。
f:id:Jirobe_Katori:20190312072256j:plain

MacOSであればUSBケーブルとPCをつなげるだけで認識されます。

Xamarin.Android 新規プロジェクト作成

Xamarin.Forms 新規プロジェクト作成

下記手順で新規プロジェクトを作成します。 f:id:Jirobe_Katori:20190318072433p:plainf:id:Jirobe_Katori:20190318072427p:plainf:id:Jirobe_Katori:20190318072420p:plainf:id:Jirobe_Katori:20190318072415p:plain

この手順ではAndroidiOSが有効化されます。

アプリの実行対象を選択しましょう。
例:上の京セラが私の実機、下のAndroid_Accelerated_Oreoが仮想マシンです。私は仮想マシンを作り直しているのでデフォルトのマシンとは名前が違うかもしれません。

f:id:Jirobe_Katori:20190318072409p:plain

f:id:Jirobe_Katori:20190318072404p:plain

実機、あるいは仮想マシンで対象が選択できたら開始のボタンを押下しましょう。 f:id:Jirobe_Katori:20190318072446p:plain

アプリが起動します。
f:id:Jirobe_Katori:20190318072451p:plain

Xamarin 環境構築 手順メモ Windows

身内向け

Visual Studio 2017のインストールから新規プロジェクト作成まで

Visual Studio 2017 のインストール

下記の手順でVisual Studioのダウンロードとインストール
Visual Studio 2017 のインストール

インストール時、下記を選択してください。 f:id:Jirobe_Katori:20190308072345j:plain
f:id:Jirobe_Katori:20190308072340j:plain

Android実機の用意

Androidの実機をデバッグできるようにしておきます。

[Androidでの操作]

設定から端末情報を開き、ビルド番号を連打してください。トーストが表示されます。
f:id:Jirobe_Katori:20190320072848g:plain

そうしたら、設定から開発者向けオプションが選択できるようになっていますので画面遷移して、USBデバッグを有効化してください。
f:id:Jirobe_Katori:20190312072256j:plain

Windows10であればUSBケーブルとPCをつなげるだけで認識されます。

Xamarin.Android 新規プロジェクト作成

下記手順で新規プロジェクトを作成します。 f:id:Jirobe_Katori:20190308073246j:plain f:id:Jirobe_Katori:20190320071836j:plain

実機を利用する場合はAndroidのバージョンは実機のバージョン以下に設定してください。 f:id:Jirobe_Katori:20190320071839j:plain

プロジェクトができたらアプリの実行対象を選択しましょう。
例:上の京セラが私の実機、下のMy Deviceが仮想マシンです。私は仮想マシンを作り直しているのでデフォルトのマシンとは名前が違うかもしれません。
f:id:Jirobe_Katori:20190311073807j:plain

選択出来たら、開始ボタンを押下してみましょう。
実機Androidをつなげていなくても、初期インストールされたAndoroidの仮想マシンが起動します。 ※Visual Studioインストール時にデフォルトでAndroid仮想マシンが一つ作られているはずです。

緑色の三角の開始ボタンで下記のアプリが動けば成功です。
f:id:Jirobe_Katori:20190320071846g:plain

私もRyzenを使っているのですが、CPUがRyzenのPCはAndroid仮想マシンは動かないかもしれません。
解決方法もあるかと思いますが、特に調べていません。

IntelCPUのマシンでAndroidVMの起動に失敗した人はVisual Studioを管理者として起動してみてください。 これでうまくいった人もいます。
f:id:Jirobe_Katori:20190312070752j:plain

Xamarin.Forms 新規プロジェクト作成

下記手順で新規プロジェクトを作成します。 f:id:Jirobe_Katori:20190308073246j:plainf:id:Jirobe_Katori:20190308073249j:plainf:id:Jirobe_Katori:20190308073254j:plain

この手順ではAndroidとUWP(Windowsのデスクトップアプリ)が有効化されます。 下記のウィンドウが表示された場合は開発者モードを選択してください。 f:id:Jirobe_Katori:20190308073304j:plain

アプリの実行対象を選択しましょう。
例:上の京セラが私の実機、下のMy Deviceが仮想マシンです。私は仮想マシンを作り直しているのでデフォルトのマシンとは名前が違うかもしれません。
f:id:Jirobe_Katori:20190311073807j:plain

選択出来たら、開始ボタンを押下してみましょう。
実機Androidをつなげていなくても、初期インストールされたAndoroidの仮想マシンが起動します。 ※Visual Studioインストール時にデフォルトでAndroid仮想マシンが一つ作られているはずです。

私もRyzenを使っているのですが、CPUがRyzenのPCはAndroid仮想マシンは動かないかもしれません。
解決方法もあるかと思いますが、特に調べていません。

IntelCPUのマシンでAndroidVMの起動に失敗した人はVisual Studioを管理者として起動してみてください。 これでうまくいった人もいます。
f:id:Jirobe_Katori:20190312070752j:plain

次は下記のようにビルドのターゲットをAndroidからUWP(Windows)に切り替えて、リビルド、実行してみましょう。 f:id:Jirobe_Katori:20190312072926j:plain

ターゲットはローカルコンピュータとします。 f:id:Jirobe_Katori:20190312073157j:plain

Androidと同じアプリが起動するはずです。
f:id:Jirobe_Katori:20190312073541j:plain