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

かずきのBlog@hatena

XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Visual Studio Codeの拡張機能で外部コマンドを実行したい

Visual Studio Code

そんなプラグイン作りたい時もありますよね? ということでやり方。

importでchild_processを読み込んで、それのexecを呼ぶだけです。yo codeで作成したTypeScriptのプロジェクトだと以下のような感じ。java -versionを出力ウィンドウに出しています。

'use strict';
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as child_process from 'child_process';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

    var outputChannel = vscode.window.createOutputChannel("java");
    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "helloworldextensions" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
        // The code you place here will be executed every time your command is executed
        child_process.exec('java -version', (error, stdout, stderror) => {
            outputChannel.appendLine(stdout);
            outputChannel.appendLine(stderror);
        });
    });

    context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {
}

ふむふむ。

ASP.NET CoreがVisual Studio Codeで動いたよ on Windows 10

ASP.NET C#

簡単にメモっておきます。まずはdotnetコマンドがないと話しになりません。私の場合はいつの間にか(たぶんVS2015のASP.NET Coreの入れたときだと思う)入ってたのでこの手順は踏んでませんが、以下のページからSDKあたりを入れておく必要があると思います。

github.com

インストールしてdotnetコマンドが通るようにしておきましょう。

次にNode.jsをインストールします。

Node.js

そして、当然ながらVisual Studio Codeもインストールしておきます。

Microsoft Virtual Academy のご紹介

次にコマンドプロンプトで必要なツール類をインストールします。

npm install -g yo bower gulp

そして、asp.netのプロジェクトのジェネレータをインストールします。

npm install -g generator-aspnet

次に、Visual Studio CodeでC# Extensionsの拡張機能を入れておきます。

marketplace.visualstudio.com

長い下準備は完了です。コマンドプロンプトで適当なフォルダを作ります。そして、以下のコマンドをたたきます。

yo aspnet

以下のような選択肢が現れるので、Web Application Basic (without Membership and Authorization)を選びます。

C:\Users\Kazuki\Documents\Visual Studio Code\Projects>yo aspnet

     _-----_     ╭──────────────────────────╮
    |       |    │      Welcome to the      │
    |--(o)--|    │  marvellous ASP.NET Core │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? What type of application do you want to create?
  Empty Web Application
  Console Application
  Web Application
> Web Application Basic [without Membership and Authorization]
  Web API Application
  Class Library
  Unit test project (xUnit.net)

Bootstrapを選んで、適当に名前を付けてEnterします。つけた名前のフォルダが作成されるので、そのフォルダに移動してcode .と打ち込んでVisual Studio Codeを立ち上げます。VS Codeを立ち上げると色々リストアするかとか聞かれるのでYesマンになりましょう。

暫く待つと、こんな感じでインテリセンスとかきくようになります。

f:id:okazuki:20160829082711p:plain

Ctrl + Shift + Bでビルドしたり、F5でデバッグ実行が出来ます。

f:id:okazuki:20160829083031p:plain

意外に簡単にASP.NET Coreの開発がVisual Studio Codeでできそうです。

まとめ

本家Visual Studioがある環境で、Visual Studio Codeを使ってあえて開発する理由 #とは

Visual Studio Codeでbeta版のTypeScriptを使う方法

TypeScript

以下のコマンドでbetaのTypeScriptを入れたとします。

npm install -g typescript@beta

でもVisual Studio Codeは、通常の設定だと安定板のTypeScriptをターゲットにしています。 そんなときは、ファイル→基本設定→ユーザー設定を開いてTypeScriptのlib.*.d.tsとtsserver.jsのあるフォルダを設定してやります。

私の環境だとこんな感じになりました。

// 既定の設定を上書きするには、このファイル内に設定を挿入します
{
    "typescript.tsdk": "C:\\Users\\Kazuki\\AppData\\Roaming\\npm\\node_modules\\typescript\\lib"
}

これで、めでたくリリース前のTypeScriptを試せます!

Xamarin AndroidでRelativeLayoutを見てみよう

Xamarin Android

過去記事

RelativeLayout

他のコントロールや親のパネルから見て相対的にどういう位置に表示するかといった指定方法で並べるレイアウトになります。親からの相対位置の指定方法は以下のものがあります。

  • layout_alignParentLeft:親の左側
  • layout_alignParentRight:親の右側
  • layout_alignParentTop:親の上側
  • layout_alignParentBottom:親の下側
  • layout_centerInParent:親の中央

以下のようなaxmlを記述すると

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Center" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Top|Left"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="Bottom|Right"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
</RelativeLayout>

以下のように表示されます。

f:id:okazuki:20160821193737p:plain

他の要素から相対的に位置を設定するには以下の属性を指定します。属性の値は相対的に配置したい元になるコントロールのID(@id/XXXX)になります。

  • layout_toLeftOf:左に配置する
  • layout_toRightOf:右に配置する
  • layout_above:上に配置する
  • layout_below:下に配置する
  • layout_alignLeft:左端に位置を合わせる
  • layout_alignRight:右端に位置を合わせる
  • layout_alignTop:上端に位置を合わせる
  • layout_alignBottom:下端に位置を合わせる

全組み合わせは試しませんが、以下のようなaxmlで

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Center" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Right|above"
        android:layout_toRightOf="@id/MyButton"
        android:layout_above="@id/MyButton" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="Left|below"
        android:layout_toLeftOf="@id/MyButton"
        android:layout_below="@id/MyButton" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:text="alignLeft|below"
        android:layout_alignLeft="@id/MyButton"
        android:layout_below="@id/MyButton" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button4"
        android:text="alignRight|above"
        android:layout_alignRight="@id/MyButton"
        android:layout_above="@id/MyButton" />
</RelativeLayout>

以下のように表示されます。

f:id:okazuki:20160821193844p:plain

Xamarin AndroidでLinearLayoutを見てみよう

Xamarin Android

過去記事

LinearLayout

要素を縦と横に並べることができるレイアウトです。android:layout_width、android:layout_height、andorid:layout_gravity、android:layout_weightを使ってレイアウト内のコントロールの表示を制御できます。 android:layout_widthとandroid:layout_heightは10dpのように数値で指定することもできますし、wrap_contentかmatch_parentと指定することが出来ます。wrap_contentでは、表示サイズは、コントロール内のコンテンツの大きさによって決まります。match_parentは、親要素いっぱいにひろがります。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
</LinearLayout>

上記のようなaxmlではボタンの横幅が親要素いっぱいに表示され、縦幅がコンテンツの内容の高さになります。LinearLayoutのandroid:orientationでvertical、horizontalで縦並びか横並びを指定できます。つまり、以下のようにもう1つボタンを置いたとすると縦に2個並びます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <Button
      android:id="@+id/MyButton"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/Hello" />
  <Button
      android:id="@+id/MyButton2"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/Hello" />
</LinearLayout>

以下のように表示されます。

f:id:okazuki:20160821154833p:plain

android:layout_gravity属性を指定すると、上下左右どちらに寄せるのか中央寄せにするのかなどが指定できます。例えば、以下のようにボタンの幅をwrap_contentにしてandroid:layout_gravityをleftとrightに指定すると左寄せ(デフォルト)と右寄せになります。axmlを以下に示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/Hello"
        android:gravity="left" />
    <Button
        android:id="@+id/MyButton2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/Hello"
        android:layout_gravity="right" />
</LinearLayout>

表示は以下のようになります。

f:id:okazuki:20160821154909p:plain

android:layout_weightを使うと、レイアウトで余った余白部分を、どのような比率で分け合うかということが指定できます。例えば、ボタンが3つあって2つ目のボタンを画面の余白いっぱいに表示したい場合は以下のようなaxmlになります。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <Button
      android:id="@+id/MyButton"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/Hello"/>
  <Button
      android:id="@+id/MyButton2"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/Hello"
      android:layout_weight="1"/>
  <Button
      android:id="@+id/MyButton3"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/Hello" />
</LinearLayout>

表示は以下のようになります。

f:id:okazuki:20160821154945p:plain

例えば、2つ目と3つ目を、残りの余白を均等に分け合いたいといった場合には、android:layout_heightを0dpに指定して、android:layout_weightに同じ値を指定することで実現できます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
    <Button
        android:id="@+id/MyButton2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:text="@string/Hello"
        android:layout_weight="1" />
    <Button
        android:id="@+id/MyButton3"
        android:layout_width="match_parent"
        android:layout_height="0od"
        android:text="@string/Hello"
        android:layout_weight="1"/>
</LinearLayout>

表示は以下のようになります。

f:id:okazuki:20160821155033p:plain

Xamarin AndroidでActivityにライフサイクルを確認してみた

Xamarin Android

過去記事

Activityのライフサイクル

Activityのライフサイクルについて説明します。Activityのライフサイクルで呼び出されるメソッドは以下の7つがあります。があります。

  • OnCreateメソッド: Activityが作成されたときに呼び出される。
  • OnStartメソッド: アクティビティがユーザーに表示される直前に呼び出される。
  • OnResumeメソッド: アクティビティがユーザーとの操作が開始される直前に呼び出される。
  • OnPauseメソッド: 別のアクティビティを表示する直前に呼び出される
  • OnStopメソッド: アクティビティがユーザーから見えなくなると表示される
  • OnDestroyメソッド: アクティビティが破棄される前に呼び出される
  • OnRestartメソッド: アクティビティが停止したあと再開する直前に呼び出される

通常は、OnCreateメソッドで初期化処理を行い、OnPauseメソッドで未保存の永続化データを保存すると良いでしょう。別のActivityが表示されると、もともと表示されていたActivityは、通常OnPause→OnStopが呼び出されて次の表示が来るまで待ちますが、メモリが圧迫されたりするとActivityが破棄されたりします。そうなると、次に戻ってきたときはOnCreateからやり直しになります。その時、EditTextなどの入力途中のデータなどは、IDが振られていると、自動的に復元されます。その他のユーザーがActivityのフィールドなどに保持していたデータは何もしないとクリアされてしまいます。これに対応するためには、OnSaveInstanceStateメソッドをオーバーライドして、Bundleにデータを保存します。OnCreateの引数で渡されるBundleがnullじゃないときは、保存されたデータがあるということなので、データの復元をBundleから行います。 この動作を確認するためのプログラムを以下に示します。MainActivityとNextActivityを持っただけのシンプルなプロジェクトで、以下のようなコードを記述します。

using Android.App;
using Android.Content;
using Android.OS;
using Android.Util;
using Android.Views;
using Java.Interop;
using System;

namespace ActivityLifecycle
{
    [Activity(Label = "ActivityLifecycle", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        private string Id { get; set; }

        protected override void OnSaveInstanceState(Bundle outState)
        {
            base.OnSaveInstanceState(outState);
            Log.Debug(nameof(MainActivity), $"{nameof(OnSaveInstanceState)}: {this.Id}");
            outState.PutString(nameof(Id), this.Id);
        }

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            this.SetContentView(Resource.Layout.Main);

            if (bundle == null)
            {
                this.Id = Guid.NewGuid().ToString();
            }
            else
            {
                this.Id = bundle.GetString(nameof(Id));
            }
            Log.Debug(nameof(MainActivity), $"{nameof(OnCreate)}: {this.Id}");
        }

        protected override void OnStart()
        {
            base.OnStart();
            Log.Debug(nameof(MainActivity), $"{nameof(OnStart)}: {this.Id}");
        }

        protected override void OnResume()
        {
            base.OnResume();
            Log.Debug(nameof(MainActivity), $"{nameof(OnResume)}: {this.Id}");
        }

        protected override void OnRestart()
        {
            base.OnRestart();
            Log.Debug(nameof(MainActivity), $"{nameof(OnRestart)}: {this.Id}");
        }

        protected override void OnPause()
        {
            base.OnPause();
            Log.Debug(nameof(MainActivity), $"{nameof(OnPause)}: {this.Id}");
        }

        protected override void OnStop()
        {
            base.OnStop();
            Log.Debug(nameof(MainActivity), $"{nameof(OnStop)}: {this.Id}");
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            Log.Debug(nameof(MainActivity), $"{nameof(OnDestroy)}: {this.Id}");
        }

        [Export(nameof(MyButtonClick))]
        public void MyButtonClick(View v)
        {
            this.StartActivity(new Intent(this, typeof(NextActivity)));
        }
    }
}

ポイントは、OnSaveInstanceStateメソッドとOnCreateメソッドになります。それ以外はボタンが押されたときの画面遷移の処理と、各ライフサイクルメソッドが呼び出されたかログを出力するためのものになります。アプリケーションを実行してMainActivityを表示するとログは以下のようなものが表示されます。

08-20 15:36:59.111 D/MainActivity( 2702): OnCreate: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:36:59.116 D/MainActivity( 2702): OnStart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:36:59.117 D/MainActivity( 2702): OnResume: 6986edf6-5759-46a9-98be-4f9185ad23de

そして、NextActivityへ遷移すると以下のように表示されます。

08-20 15:37:51.231 D/MainActivity( 2702): OnPause: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:37:52.000 D/MainActivity( 2702): OnSaveInstanceState: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:37:52.001 D/MainActivity( 2702): OnStop: 6986edf6-5759-46a9-98be-4f9185ad23de

そして、戻るボタンでMainActivityに戻ると以下のように表示されます。

08-20 15:39:41.493 D/MainActivity( 2702): OnRestart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:39:41.493 D/MainActivity( 2702): OnStart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:39:41.493 D/MainActivity( 2702): OnResume: 6986edf6-5759-46a9-98be-4f9185ad23de

OnDestroyメソッドが呼び出されていないので、Idは保持されたままなのは当然ですよね。次に、実機の開発者オプションで「アクティビティを保持しない」を選択して、疑似的にメモリ不足などでActivityが破棄された時の動作をエミュレートしてみたいと思います。実行してMainActivityが表示されると以下のようなログが出ます。

08-20 15:42:38.221 D/MainActivity(31579): OnCreate: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:42:38.223 D/MainActivity(31579): OnStart: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:42:38.225 D/MainActivity(31579): OnResume: dbc9cedb-a67a-4e61-b6b1-911925fbd001

そして、NextActivityへ遷移を行います。

08-20 15:43:10.217 D/MainActivity(31579): OnPause: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.667 D/MainActivity(31579): OnSaveInstanceState: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.673 D/MainActivity(31579): OnStop: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.689 D/MainActivity(31579): OnDestroy: dbc9cedb-a67a-4e61-b6b1-911925fbd001

OnDestroyメソッドが呼び出されてMainActivityが破棄されたことが確認できます。この状態で戻るボタンを押してMainActivityに戻ると以下のように表示されます。

08-20 15:44:12.607 D/MainActivity(31579): OnCreate: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:44:12.609 D/MainActivity(31579): OnStart: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:44:12.624 D/MainActivity(31579): OnResume: dbc9cedb-a67a-4e61-b6b1-911925fbd001

一度MainActivityが破棄されたにも関わらず、Idの値が保持されていることが確認できます。繰り返しますが、画面に置かれた部品でIDが振られたものは自動的に値が保持されるため気にする必要はありませんが、今回のように自分でActivityに一時的に保持しているものは自前で保存と復元を行う必要があります。

Xamarin.Androidで画面遷移してみよう

Xamarin Android

過去記事

画面遷移してみよう

ここでは、Andoridの画面遷移について説明します。Andoridでは、画面遷移にIntentというものを使います。このIntentは、とても汎用的なメッセージング機構でサービスとよばれるバックグラウンドで実行される処理の起動や、ここで説明する別画面(Activity)の起動などができます。さらには、別アプリのActivityやサービスなども起動することもできます。Intentは、簡単に言うと宛先とデータを持った入れ物です。それを投げつけると、それに応答するように設定されたものが応答します。 では、画面遷移するアプリケーションを作ってIntentの簡単な使い方を見てみたいと思います。NavigationAppという名前でAndroidのBlank Appを作成します。そして、新規作成から、ActivityをNextActivityという名前で作成します。NextActivityに対応する見た目を定義するaxmlをResources/layoutフォルダにNext.axmlという名前で作成します。 Main.axmlにEditTextを追加します。idは@+id/GreetMessageにしました。axmlを以下に示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minWidth="25px"
    android:minHeight="25px">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/GreetMessage" />
</LinearLayout>

メニューの定義

ここで、画面遷移のほかに新しいことをやってみたいと思います。メニューをアクションバーに追加してみたいと思います。メニューは、ActivityのOnCreateOptionsMenuメソッドをオーバーライドしてMenuInflaterのInflateメソッドを使うことで作成します。メニューは、Resources/menuフォルダにXMLで定義します。ここでは、MainMenu.xmlという名前で以下のようなXMLを定義しました。

<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/NavigateMenu"
        android:title="@string/NavigateMenuText" 
        android:onClick="NavigateMenuClick"
        android:showAsAction="always"
        android:icon="@drawable/Icon"/>
</menu>

android:idに識別用のIDを定義し、android:titleにメニューを長押ししたときに表示されるテキストを指定し、android:onClickに選択時に実行されるメソッドを指定し、android:showAsActionにアクションバーへの表示方法を指定し、android:iconにアイコンを指定します。android:showAsActionには、バーには表示しないnever、余裕があればバーに表示するifRoom、常に表示するalways、android:titleのテキストを表示するwithTextなどがあります。その他の完全な属性の定義については、以下のページを参照してください。

itemは複数定義することが出来ます。また、menu/menu/itemのようにメニューにメニューを入れ子にすることもできます。 上記メニューを画面に表示します。MainActivityのOnCreateOptionsMenuでMenuInflaterのInflateメソッドでメニューのリソースと引数で渡されたIMenuを結びつけます。そして、NavigateMenuClickメソッドを定義して選択時のアクションを定義します。NavigateMenuClickは、Java側にメソッドを教えるためにMono.Android.dllアセンブリ(追加で参照が必要)に定義されているJava.Interop.Export属性で名前を指定します。コードは以下のようになります。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Util;
using Java.Interop;

namespace NavigationApp
{
    [Activity(Label = "NavigationApp", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            this.SetContentView(Resource.Layout.Main);
        }

        public override bool OnCreateOptionsMenu(IMenu menu)
        {
            this.MenuInflater.Inflate(Resource.Menu.MainMenu, menu);
            return true;
        }

        [Export(nameof(NavigateMenuClick))]
        public void NavigateMenuClick(IMenuItem menuItem)
        {
            Log.Debug("MainActivity", $"Clicked: {menuItem.TitleFormatted}");
        }
    }
}

これで、実行すると以下のような画面が表示され、アクションバーのメニューを選択することで出力ウィンドウにメッセージが表示されます。

f:id:okazuki:20160819220438p:plain

画面遷移

メニューを選択したときに画面遷移を行うようにします。以下のようにNextActivityを対象としたIntentを作成して、PutExtraメソッドでIntentにデータを設定してStartActivityメソッドで次の画面を起動します。

[Export(nameof(NavigateMenuClick))]
public void NavigateMenuClick(IMenuItem menuItem)
{
    // NextActivityへのIntentを作成して
    var intent = new Intent(this, typeof(NextActivity));
    // データを詰めて
    intent.PutExtra("Message", this.FindViewById<EditText>(Resource.Id.GreetMessage).Text);

    this.StartActivity(intent);
}

NextActivity用の画面であるNext.axmlではTextViewを1つおいて受け取ったメッセージを表示したいと思います。axmlの内容を以下に示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minWidth="25px"
    android:minHeight="25px">
    <TextView
        android:text="Text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/TextViewGreetMessage" />
</LinearLayout>

画面遷移先のNextActivityでは、Intentプロパティを通じて渡されたIntentが参照できます。ここからGetStringExtraメソッドで渡された値を参照してTextViewに設定しています。

using Android.App;
using Android.OS;
using Android.Widget;

namespace NavigationApp
{
    [Activity(Label = "NextActivity")]
    public class NextActivity : Activity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            this.SetContentView(Resource.Layout.Next);

            var textView = this.FindViewById<TextView>(Resource.Id.TextViewGreetMessage);
            textView.Text = this.Intent.GetStringExtra("Message");
        }
    }
}

実行すると、以下のようになります。

f:id:okazuki:20160819220537p:plain

f:id:okazuki:20160819220547p:plain

画面遷移が行われて、値が渡されていることが確認できます。

Xamarin.AndroidでHello world

Xamarin Android

ハローワールドを通じて、簡単なアプリケーションの開発の流れを見てみようと思います。Visual Studioのプロジェクトの新規作成から「Android」→「Blank App(Android)」を選択します。

f:id:okazuki:20160817211930p:plain

「HelloWorld」とプロジェクト名をつけてプロジェクトを作成します。そうすると、以下のような構造をもったプロジェクトが作成されます。

f:id:okazuki:20160817211948p:plain

MainActivity.csがメインの画面を表すクラスになります。Main.axmlが、MainActivityで使用されている画面のレイアウトを定義したファイルになります。Strings.xmlが、アプリ内で使用する文字列を定義したファイルになります。Resource.Designer.csファイルはMain.axmlやStrings.xmlなどから自動生成される、プログラム内から各種リソースにアクセスするためのIDが定義されたクラスになります。MainActivityクラスは以下のようになっています。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace HelloWorld
{
    [Activity(Label = "HelloWorld", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.MyButton);

            button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
        }
    }
}

まず、ポイントとなるのがActivityクラスを継承している点です。このクラスを継承したものがAndroidでは画面として使えます。次に、このクラスが初期起動時に呼び出されるActivityであることを定義するために属性が定義されています。 [Activity(Label = "HelloWorld", MainLauncher = true, Icon = "@drawable/icon")] MainLauncher = trueの記述がそれになります。LabelはラベルでIconはアイコンです。Activityが生成されたときにフレームワークから呼び出されるOnCreateメソッドで初期化処理が行われています。SetContentViewメソッドが画面定義ファイルとActivityの紐づけを行うメソッドになります。Resourceクラスが、先ほど説明したResource.Designer.csファイルで自動生成されたファイルでMain.axmlを指し示すためのIDにResource.Layout.Mainでアクセス出来るようになっています。これでMain.axmlで定義された見た目が使用されるようになります。Main.axmlは、以下のように定義されています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
</LinearLayout>

xmlns:android=”http://schemas.android.com/apk/res/android”名前空間が定義されたXMLファイルになります。LinearLayoutという要素を縦や横に並べるレイアウトの中に、Buttonが置かれているというくらいの理解で今は大丈夫(というかこれを書いてる時点で、その程度の理解です)だと思います。ポイントとして、押さえておきたいのが、Buttonに対して定義されているandroid:id=”@+id/MyButton”という属性です。これはMyButtonという名前のIDをボタンに割り当ててることになります。 ではMainActivityに戻ります。MainActivityでは、FindViewByIdメソッドを使って画面内のコントロールにアクセス出来ます。ここでは、MyButtonというIDのコントロール(先ほど確認したMain.axml内で定義されてるやつですね)を取得してイベントを登録しています。処理自体はcount変数をカウントアップしているだけのシンプルなものですね。では、実行してみましょう。

f:id:okazuki:20160817212051p:plain

ボタンを押すとカウントアップされた値が表示されます。

f:id:okazuki:20160817212112p:plain

BMIを計算してみよう

これを少しいじってBMIを計算するアプリを作ってみようと思います。身長と体重を入力するボックスを作ります。Main.axmlを以下のように変更します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
    <EditText
        android:id="@+id/EditTextHeight"
        android:hint="@string/Height"
        android:inputType="numberDecimal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <EditText
        android:id="@+id/EditTextWeight"
        android:hint="@string/Weight"
        android:inputType="numberDecimal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

EditTextがAndroidのテキスト入力用コントロールになります。android:hint属性は、ウォーターマークをだすための属性になります。@string/Heightや@string/Weightは、strings.xmlで以下のように定義している文字列を参照する書き方です。BmiMessageは、プログラム内から使用する予定の文字列になります。

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="Hello">Hello World, Click Me!</string>
  <string name="ApplicationName">HelloWorld</string>
  <string name="Weight">体重</string>
  <string name="Height">身長</string>
  <string name="BmiMessage">BMIは%fです</string>
</resources>

android:inputType=”numberDecimal”は、数値を入力するための設定になります。android:layout_widthとandorid:layout_heightは、幅と高さをどうするかを指定します。今回は横幅は親に合わせて、縦幅はコンテンツに合わせるように指定しています。(つまり横長) 実行すると、以下のように表示されます。

f:id:okazuki:20160817212205p:plain

身長、体重のウォーターマークが出ている点と、入力欄にフォーカスを合わせたときのソフトウェアキーボードが数字になっているので、先ほどの設定が効いていることがわかります。 最後に、処理を書いていきます。BMIの計算式は「体重(kg) ÷ {身長(m) X 身長(m)}」なので、ボタンが押されたときに、そう表示されるようにしています。アプリの仕様的にわかりにくいですが、今回は体重はkgで、身長はmで入力されてるものとして処理しています。

using Android.App;
using Android.OS;
using Android.Widget;

namespace HelloWorld
{
    [Activity(Label = "HelloWorld", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            this.SetContentView(Resource.Layout.Main);

            var button = this.FindViewById<Button>(Resource.Id.MyButton);
            var height = this.FindViewById<EditText>(Resource.Id.EditTextHeight);
            var weight = this.FindViewById<EditText>(Resource.Id.EditTextWeight);

            button.Click += (_, __) =>
            {
                double h;
                if (!double.TryParse(height.Text, out h))
                {
                    return;
                }

                double w;
                if (!double.TryParse(weight.Text, out w))
                {
                    return;
                }

                // 体重(kg) ÷ {身長(m) X 身長(m)}
                var bmi = w / (h * h);
                button.Text = string.Format(this.Resources.GetString(Resource.String.BmiMessage ,bmi));
            };
        }
    }
}

ActivityにResourcesというプロパティがあり、これのGetStringメソッドを使うことでStrings.xmlに定義した文字列をプログラム中から使うことが出来ます。今回はBMIの計算結果を表示する文字列として使っています。 実行して入力してボタンを押すと以下のようになります。

f:id:okazuki:20160817212237p:plain

Androidで定期的に処理を実行したい

Android

そんなときはAlarmManagerを使います。 使い方はPendingIntentを作ってAlarmManagerを取得してsetRepeatingする感じ。 一回こっきりでいい場合は、setメソッドでもいいみたいですね。

詳細はAlarmManagerのドキュメントを見よう。

AlarmManager | Android Developers

ということで、こんなコードを書いてやれば現在時間から1分間隔でIntentが投げられるようになります。今回の場合はサービスをキックしています。

Intent intent = new Intent(this, LogService.class);
intent.setAction("LogServiceAction");
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis(), 60 * 1000, pendingIntent);

Activityでやれば、アプリを起動したあとから有効になります。端末再起動時や、アプリアップデート時にリセットされるらしいので、それがいやならしかるべきIntentを受け取るReceiverを作ってそこで上記処理を書けばいいっぽいです。

ふむふむ。