Visual Studio 2017 で Xamarin する(2)簡単なレイアウト

プロジェクトを作成する

Visual Studio を起動して、メニューから ファイル>新規作成>プロジェクト を選択する。 左側のツリーから、 VisualC#>Cross-Platform>クロスプラットフォームアプリを選択。

f:id:foohogehoge:20170408130328p:plain:w600

プロジェクト名はサンプルに倣って「TicTacToe」とする。

テンプレートなどの選択

自動で作成されても扱いきれないので、テンプレートは空のアプリを選択。 UIテクノロジは当然Xamarin.Forms。 コード共有方法はPCLにしてみた。理由はない。ググった結果を眺めて勘で決めた。

f:id:foohogehoge:20170408141336p:plain:w600

UWPはターゲットプラットフォームの選択ダイアログが出る。デフォルトでいく。

f:id:foohogehoge:20170408141436p:plain:w600

ほかにも「Macが見つからない」的なメッセージが出るがMacはないので無視。

自動生成後

ソリューションエクスプローラーはこんな感じ。

f:id:foohogehoge:20170408141810p:plain:h600

1つ気になったのが、UWPプロジェクトの下にあるMainPage.xaml

f:id:foohogehoge:20170408142043p:plain:w600

編集しても画面に反映されないので????な感じ。 呟いてみたら助けていただいた。ありがとうございます。

メイン画面をつくる

(移植可能)の下にある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"
             xmlns:local="clr-namespace:TicTacToe"
             x:Class="TicTacToe.MainPage">

    <Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

</ContentPage>

Controls Reference - Xamarinで調べると、Xamarin.Formsでは一番外側の要素はPageになるらしい。 初めてのAndroid 第4版 のサンプルでは、activity_main.xmlの内側にfragent_main.xmlを配置をしている。 Xamarin.Formsにはフラグメントの概念がなさそうだが、とりあえず見た目だけ真似してみる。

fragent_main.xmlの代わりを作る

メイン画面の子要素として使うビューを作成する。

TicTacTue(移植可能)プロジェクトの下に新規Xamlファイルを作成する。 テンプレートがいろいろあるが、先ほどの調査結果からPage的なものは除外して考える。 今回は「Forms ContentView Xaml」を選択した。ファイル名は FragmentMain.xaml とする。作成直後はこんな感じになる。

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TicTacToe.FragmentMain">
  <ContentView.Content>
  </ContentView.Content>
</ContentView>

さて、初めてのAndroid 第4版ではLinerLayoutで定義されているが、Xamarin.Formsではどうなるのか調べてみる。 Controls Reference - Xamarinを眺めると、StackLayoutが良さそう。 下のように書き換えてみる。

<?xml version="1.0" encoding="UTF-8"?>
<StackLayout 
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="TicTacToe.FragmentMain">
</StackLayout>

と、ここでエラーが発生。

‘FragmentMain’ の partial 宣言では、異なる基底クラスを指定してはいけません。

FragmentMain.xaml.cs の基底クラスも変更しなければならないようだ。基底クラスを ContentView から StackLayout へ変更する。

namespace TicTacToe
{
    public partial class FragmentMain : StackLayout
    {
        public FragmentMain()
        {
            InitializeComponent();
        }
    }
}

あとは、テキストラベルとボタンを配置すればよい。

<?xml version="1.0" encoding="UTF-8"?>
<StackLayout 
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="TicTacToe.FragmentMain">
    <Label
       Text="Tic Tac Toe"
       VerticalOptions="Start"
       HorizontalOptions="Center" />
    <Button
        Text="CONTINUE" />
    <Button
        Text="NEW GAME" />
    <Button
        Text="ABOUT"/>
</StackLayout>

文字列部分はリソースファイルを使う予定だが、とりあえずレイアウトを確認したいので後回しにする。 見た目に関する細かいプロパティもとりあえず置いておく。

メイン画面へ組み込んでみる

いきなりでアレだが、こんな感じになる。

<?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:local="clr-namespace:TicTacToe"
             x:Class="TicTacToe.MainPage">

    <Frame VerticalOptions="Center"
           HorizontalOptions="Center">
        <local:FragmentMain />
    </Frame>
</ContentPage>

入れ子にする方法を調べたわけではないが、補完が効いたのでたぶんあってる。

結果確認

Android

とりあえずここまでをAndroidで確認してみる。写経の時点で作成済みの Android Emulator を選択して実行する。 Android Emurator がなければ、ツール>AndroidAndroidエミュレーターマネージャーから作成する。

f:id:foohogehoge:20170408155943p:plain:w600

たまに実行できない場合があるが、ビルドをキャンセルしてリビルドしたりすればOK。

f:id:foohogehoge:20170408160543p:plain:h600

UWP

UWPは既定の構成ではAnyCPUでビルドされないのでx64を選んで実行する。

f:id:foohogehoge:20170408160800p:plain:w600

実行するとこんな感じになる。

f:id:foohogehoge:20170408160858p:plain:w600

見た目がよろしくないが、とりあえずレイアウトは確認できた。

参考

Xamarin.Formsを使った感想と困ったことなど(Android中心) - Qiita

Xamarin.Forms XAML の基本 | Xamarin : XLsoft エクセルソフト

User Interface - Xamarin

Visual Studio 2017 で Xamarin する(1)前段

前段

仕事は相変わらずWindowsFormメイン*1。 しかし、Windows10を触っているとUWPみたいなレスポンシブな感じに乗り遅れている気がしてならないので、Android開発でもしてみようと考えた。 ↓の本を買って写経してみたが、理解度がイマイチ。

初めてのAndroid 第4版

初めてのAndroid 第4版

理解度を深める+新しい技術に触る ために、この本の内容をXamarinで実装してみることにした。

開発準備

まず、大前提として写経のためにAndroidSDKToolsは導入済み。 XamarinのためにVisual Studio 2017 Community をインストールする。 適当にそれっぽいオプションを選べば大丈夫だが、下記を参考にすればベターな感じ。

ytabuchi.hatenablog.com

HelloWorld

さすがにいきなりコンバートするのも勇気がいるので肩慣らし……と思ったら去年やってた。

Visual Studio Community 2015 でAndroidアプリを作成する(Xamarin) - foohogehoge's blog

多分大丈夫なのでいきなり始めてみる。

*1:Gitを導入したのでシェルスクリプトは書くようになった

遅まきながらLINQ関連を勉強してみた

実際、仕事でもちょくちょくLINQを使ってはいるのだが
真面目に触っていないのでちょっとおさらいして自分の理解をまとめてみた。

LINQってなに?

IEnumerableに対する操作を行うExtentionメソッド。
引数に動作を決めるデリゲートをとるため柔軟な操作が可能。
IEnumerableから抽出する要素をフィルタするWhere、
抽出順序を決めるOrderBy、
抽出する要素そのものを決定するSelect などがある。
var query = from x in array select x;
のように書く「クエリ構文」と
var query = array.Select((x) => x);
のように書く「メソッド構文」がある。
メソッド構文の方が理解しやすかった。

業務基幹系は今日もVBであるのでVBで書くと
Dim query As IEnumerable(Of Something) = From x In array Select x が「クエリ構文」
Dim query As IEnumerable(Of Something) = array.Select(Function(x) x) が「メソッド構文」

雑感

要するに、シェルスクリプトのパイプ処理のように
「forを書かずに配列を処理する」
のに便利。
ループ処理が思考を妨げて本来の問題へフォーカスしにくくなるのを防ぐ効果はあると思う。
やりすぎると「シェル芸」ならぬ「LINQ芸」になりそうな予感。

MSYS2(zsh)+Git for Windowsを快適に使う

どうも補完が遅い気がしていたが、これで解決。
1.ホームディレクトリに.zshディレクトリを作成
2.Gitのインストールディレクトリ以下 mingw32\share\git\completion から git-completion.bash を .zsh へコピー
3.Gitのインストールディレクトリ以下 mingw32\share\git\completion から git-completion.zsh を _git にリネームして .zsh へコピー
4..zshrcに 以下を追記
  fpath=(~/.zsh $fpath)

Visual Studio Community 2015 でAndroidアプリを作成する(Xamarin)

普段はモバイル開発とは全く無縁だが、Xamarinが無償提供になったようなのでとりあえずどんなものか試してみる。
ドキュメントは、Creating Mobile Apps with Xamarin.Forms Book First Edition - Xamarin からPDFがダウンロードできる。これがあれば大体のことはできそうだが、1000ページ超でサクッと試すには不向き。

とっかかりやすいチュートリアルとして、Getting Started with Xamarin.Forms - Xamarinを試してみる。
「Hello, Xamarin.Forms」ー「Quickstart」から始める。


  1. 新規プロジェクトの作成
    VisualStudioを起動して、新規プロジェクトを作成する。
    f:id:foohogehoge:20160427214454p:plain
  2. テンプレートの選択
    新しいプロジェクト ダイアログで、「Blank App (Xamarin.Forms Portable)」を選択する。
    プロジェクト名は、チュートリアルに従って「Phoneword」とした。
    f:id:foohogehoge:20160427214715p:plain
  3. Windowsのプラットフォーム
    f:id:foohogehoge:20160427215004p:plain
    チュートリアルには記載されていないが、Windowsのプラットフォームを選択するダイアログがある。2つしかバージョンが選択できなかったので、Target Versionを新しいほう/Minimum Version を古いほうにした。
  4. 空のプロジェクトが作成された
    f:id:foohogehoge:20160427215641p:plain
    なぜか無数のエラーが……ほとんどUWPから発生しているようだ。。。
  5. エラーを解消していく
    以前にニュースリーダーか何かで「テンプレートから作成した直後はエラーの嵐」だとチラ見した記憶があったので適当にググる。下記サイトがヒットしたので参考にさせていただく。
    ytabuchi.hatenablog.com

  6. ソリューションを右クリックして「NuGetパッケージの復元」
    f:id:foohogehoge:20160427221514p:plain

  7. PCLプロジェクトのリビルド

    PCL プロジェクトを[リビルド]

    だそうだが、PCL???と思ったので調べてみると下記が見つかった。
    PCL (Portable Class Library) - Xamarin 3 の新しいコード共有テクニック : XLsoft エクセルソフト
    PCLとは全プラットフォームで共通して使えるライブラリのことで、”移植可能”と書いてあるプロジェクトのようだ。PCL+固有プラットフォームのプロジェクト の組み合わせでアプリケーションを構築する……のかな。。。
    リビルドすると問題なく成功する。


  8. UWPをリビルドする。これも問題なく成功した。
    警告が10個残っているが、とりあえず無視して先へ進む。

  9. PCLプロジェクトを右クリックして新規項目を追加を選択

    f:id:foohogehoge:20160427223500p:plain


  10. Visual C# > Cross-Platform から Forms XML Page を選択。 名前はチュートリアルに従って ”MainPage” とする。

    f:id:foohogehoge:20160427224034p:plain


  11. 追加した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="Phoneword.MainPage">
    	<ContentPage.Padding>
    		<OnPlatform x:TypeArguments="Thickness"
                        iOS="20, 40, 20, 20"
                        Android="20, 20, 20, 20"
                        WinPhone="20, 20, 20, 20" />
    	</ContentPage.Padding>
    	<ContentPage.Content>
    		<StackLayout VerticalOptions="FillAndExpand"
                         HorizontalOptions="FillAndExpand"
                         Orientation="Vertical"
                         Spacing="15">
    			<Label Text="Enter a Phoneword:" />
    			<Entry x:Name="phoneNumberText" Text="1-855-XAMARIN" />
    			<Button x:Name="translateButon" Text="Translate" Clicked="OnTranslate" />
    			<Button x:Name="callButton" Text="Call" IsEnabled="false" Clicked="OnCall" />
    		</StackLayout>
    	</ContentPage.Content>
    </ContentPage>
    


  12. ソリューションエクスプローラーでMainPage.xamlを展開して、MainPage.xaml.csを開く

    f:id:foohogehoge:20160427230019p:plain


  13. MainPage.xaml.csの内容を下記で置き換える

    using System;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    
    namespace Phoneword {
        public partial class MainPage : ContentPage {
            string translatedNumber;
    
            public MainPage() {
                InitializeComponent();
            }
    
            void OnTranslate(object sender, EventArgs e) {
                translatedNumber = Core.PhonewordTranslator.ToNumber(phoneNumberText.Text);
                if (!string.IsNullOrWhiteSpace(translatedNumber)) {
                    callButton.IsEnabled = true;
                    callButton.Text = "Call " + translatedNumber;
                }
                else {
                    callButton.IsEnabled = false;
                    callButton.Text = "Call";
                }
            }
    
            async void OnCall(object sender, EventArgs e) {
                if (await this.DisplayAlert(
                        "Dial a Number",
                        "Would you like to call " + translatedNumber + "?",
                        "Yes",
                        "No")) {
                    var dialer = DependencyService.Get<IDialer>();
                    if (dialer != null)
                        dialer.Dial(translatedNumber);
                }
            }
        }
    }
    

    エラーが出るが、この後のステップで解消されそうなので無視して次へ進む。


  14. ソリューションエクスプローラーからApp.csを開いて下記の内容で置き換える。

    using System;
    using Xamarin.Forms;
    
    namespace Phoneword {
        public class App : Application {
            public App() {
                MainPage = new Phoneword.MainPage();
            }
    
            protected override void OnStart() {
                // Handle when your app starts
            }
    
            protected override void OnSleep() {
                // Handle when your app sleeps
            }
    
            protected override void OnResume() {
                // Handle when your app resumes
            }
        }
    }
    


  15. PCLプロジェクトにC#のクラスを新規追加する。名前は”PhoneTranslator”として内容を下記で置き換える。

    using System.Text;
    
    namespace Core {
        public static class PhonewordTranslator {
            public static string ToNumber(string raw) {
                if (string.IsNullOrWhiteSpace(raw))
                    return null;
    
                raw = raw.ToUpperInvariant();
    
                var newNumber = new StringBuilder();
                foreach (var c in raw) {
                    if (" -0123456789".Contains(c))
                        newNumber.Append(c);
                    else {
                        var result = TranslateToNumber(c);
                        if (result != null)
                            newNumber.Append(result);
                        // Bad character?
                        else
                            return null;
                    }
                }
                return newNumber.ToString();
            }
    
            static bool Contains(this string keyString, char c) {
                return keyString.IndexOf(c) >= 0;
            }
    
            static readonly string[] digits = {
                "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"
            };
    
            static int? TranslateToNumber(char c) {
                for (int i = 0; i < digits.Length; i++) {
                    if (digits[i].Contains(c))
                        return 2 + i;
                }
                return null;
            }
        }
    }
    


  16. PCLプロジェクトに IDialer インターフェイスを追加して下記の内容とする。

    using System;
    
    namespace Phoneword
    {
        public interface IDialer
        {
            bool Dial(string number);
        }
    }
    


  17. 次のステップはiPhon用のコーディングだが、実機がないのでパスしてAndroidの項へ飛ぶ。と、その前にMainPage.xaml.csでコンパイルエラーが出ていて気になるので解決しておく。
    チュートリアルはここから各プラットフォームのコーディングを行って終了のようだ。
    つまり、現時点で共通部分であるPCLプロジェクトにコンパイルエラーが出ているのはマズいはずである。
    「InitializeComponentメソッドがない」と怒られている。WindowsForm開発でもなじみのあるメソッドだが、どこに実装されているのか?おそらくMainPage.xamlあたりにあるはずだが……探してもない。コピペミスを疑ってチュートリアルからMainPage.xamlを再度貼り付けなおしたらエラーが消えた。
    InitializeComponentの実体を探すとobj\Debugの下に「Phoneword.MainPage.xaml.g.cs」が作成されておりそこに定義してあった。
    ……このエラーはこの後のステップでも度々発生するが、xamlを編集して保存するとOKな様子。

  18. Androidプロジェクトに PhoneDialer クラスを追加して以下の内容で置き換える。

    using Android.Content;
    using Android.Telephony;
    using Phoneword.Droid;
    using System.Linq;
    using Xamarin.Forms;
    
    using Uri = Android.Net.Uri;
    
    [assembly: Dependency(typeof(PhoneDialer))]
    
    namespace Phoneword.Droid {
        public class PhoneDialer : IDialer {
            public bool Dial(string number) {
                var context = Forms.Context;
                if (context == null)
                    return false;
    
                var intent = new Intent(Intent.ActionCall);
                intent.SetData(Uri.Parse("tel:" + number));
    
                if (IsIntentAvailable(context, intent)) {
                    context.StartActivity(intent);
                    return true;
                }
    
                return false;
            }
    
            public static bool IsIntentAvailable(Context context, Intent intent) {
                var packageManager = context.PackageManager;
    
                var list = packageManager.QueryIntentServices(intent, 0)
                    .Union(packageManager.QueryIntentActivities(intent, 0));
    
                if (list.Any())
                    return true;
    
                var manager = TelephonyManager.FromContext(context);
                return manager.PhoneType != PhoneType.None;
            }
        }
    }
    


  19. ソリューションエクスプローラーでAndroidプロジェクトのプロパティをダブルクリック
    f:id:foohogehoge:20160428004216p:plain

  20. Android Manifest を選択し、Required permissionsから「CALL_PHONE」にチェックする。
    f:id:foohogehoge:20160428004622p:plain
    また、Package name が空だとエラーダイアログが表示されるので、Package name に "Phoneword" と入力する。

  21. 他のプラットフォームは追々試すとして、とりあえずAndroidだけビルドしてみる。
    「"ConvertResourcesCases"タスクが予期せずに失敗しました」

  22. ググったところ、ソリューションのパスに日本語が含まれているとNGらしいのでソリューションを移動してリトライしたところうまくビルドできた。

  23. 普段使用しているNexsus5を開発者モードにし、USBデバッグ可にしてPCと接続する。
    開発者モードは、Nexsus5の設定>端末情報 で表示されるビルド番号を連打すると使用できるようになる。
    設定>開発者向けオプションからUSBデバッグをONにする。

  24. VisualStudioでAndroidプロジェクトをスタートアッププロジェクトに設定し、デバッグ実行する。
    しばらくすると、Nexsus側に画面が表示された。

    f:id:foohogehoge:20160428013902j:plain


msys2を更新したらPATH設定が消えた??

msys2からupdate-coreコマンドが消えたあとあたりのタイミングで、
msys2を起動してもWindows環境変数のPATHを認識しなくなった。

gitはmsys2でインストールしたものではなく別途Git for Windowsをインストールしているのでちょっと困った。

msys2の起動に使用していたバッチファイル(msys2_shell.bat)を確認すると
「start_shell.cmd」を呼び出すだけのシンプルなものに変更されていた。
「start_shell.cmd」の中身を確認してみると、

rem To export full current PATH from environment into MSYS2 use '-use-full-path' parameter
rem or uncomment next line
rem set MSYS2_PATH_TYPE=inherit

 と書かれていた。

つまり、msys2_shell.batでstart_shell.cmdを呼び出す際に -use-full-path を指定するか
start_shell.cmdを編集して

rem set MSYS2_PATH_TYPE=inherit 

 を

set MSYS2_PATH_TYPE=inherit 

 にすればOKらしい。
start_shell.cmdを呼び出す際に -use-full-path を指定したところGit for Windowsが使えるようになった。

Windows版SourceTreeが遅いときのチェックポイント

Windows版SourceTreeが遅いときは、ブックマーク数を減らすのが有効。
自動でリモートの更新を取得しない設定にしておいてもブックマーク一覧に表示される更新情報を表示するためにリモートをチェックしているっぽい。