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

熊小屋日誌

Windows 10 UWPやXamarin, Python、mbed/NetMF/Arduino/Edison, Azureなどぼちぼちと。たまにPCや勉強会、セミナーなどの話題も

Windows 10 MobileデバイスへのUWPアプリの配置エラーDEP6400

Windows 10 MobileデバイスにUWPアプリを配置しようとしたら、次のエラーが出たことがあります。

DEP6400 : 配置できませんでした。Visual Studio の別のインスタンスから同じエミュレーターまたはデバイスのために別の配置またはデバッグ セッションが実行中ではないことを確認してください。0x8973190E: ファイル '%FOLDERID_SharedData%\PhoneTools\11.0\Debugger\bin\RemoteDebugger\msvsmon.exe' の書き込み中にエラーが発生しました。

DEP6400や0x8973190EでWeb検索しても解決できなかったのですが、'%FOLDERID_SharedData%\PhoneTools\11.0\Debugger\bin\RemoteDebugger\msvsmon.exe'で検索したら以下を発見。

stackoverflow.com

Usually, this error means there's a pending Windows Phone OS update. Can you see if you have pending updates on the phone and restart?

確かにOSの更新が保留されていて、再起動がスケジュールされた状態でした。端末を再起動して、更新を適用したら上記のエラーは出なくなりました。

Xamarin.Forms入門中~ソリューション新規作成が終わらない問題の解決~

昨日の記事で「新しいソリューションも作成が終わらないトラブル」に見舞われている、と書いたのですが、それを解決できました。 具体的には、Xamarin.Formsの新規ソリューション作成を実行したとき、Droid→iOSとプロジェクトが作成されて、UWPプロジェクトの作成で処理が終わらない、という現象です。

blog.strawhat.net

別のUWPアプリを開いたときに、C:\Windows\TempにアクセスできずXAMLファイルなどを開けないことに気づきました。

そこで、C:\Windows\Tempフォルダをエクスプローラで表示しようとすると、アクセス権がなく表示できないとのエラーダイアログが出たので権限を取得すると、UWPプロジェクトの作成もすんなりと終わりました。何かの拍子に権限設定が変わって、UWPプロジェクトの作成に支障が出たようです(権限が変わったのはおかしい話ですが)。

ご参考までに。

Xamarin.Forms入門中~Androidホームスクリーンウィジェットの実装~

これは、[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016の24日目の記事です。

先日は Xamarin.Forms上でHello World - SDD(Sleep-Driven Development) でした。

Windows 10 UWPアプリのライブタイルをXamarin.FormsのAndroidアプリ側で実装する案をまとめてみます。これまでに自力で調べられた範囲での案なので、これ以外にもっとよい方法があるかもしれませんし、既知の方法かもしれませんがその点はご了承ください。

お詫び

記事を書いてる途中で、Visual Studio 2015でサンプルコードのソリューションを開けなくなり、さらに新しいソリューションも作成が終わらないトラブルに見舞われています。そのため、サンプルコードとその実行結果をおみせできない状況です。トラブルが直ったら、サンプルコードをGitHubに上げて、実行結果のスクリーンショットを掲載したいと思います。

また、記事中のコードの断片もビルドが通ってないので、誤りがあるかもしれません。これも後日直したいと思います。

Xamarin.Formsを窓から放り投げたい気分ですが、気を取り直して記事を進めます。

背景

これまで、KumalicaなどWindows 10 MobileのUWPアプリを作ってきましたが、Android版も同時にリリースしてもっと多くの人に使ってほしいと思い、Xamarin.Formsへの移行を進めています。そこで、Xamarin.Formsの機能、特にUWPアプリで使った機能を置き換えるための方法を調べて、記事にまとめています。

前回の記事、Xamarin.Forms入門中~UWPアプリのライブタイルの置き換え①~では、Xamarin.Androidでのウィジェットのサンプルコードを調べた結果を書いてます。

blog.strawhat.net

この時点では、Xamarin.Formsの場合の構成方法が見えてなかったのと、UWPアプリのライブタイルのようにアプリ側からウィジェットを更新する方針が見えてなかったので、さらに調べてみました。

Xamarin.FormsでのAndroidホームスクリーンウィジェットの実装方法

Xamarin.FormsアプリのAndroidプロジェクトで、ホームスクリーンのウィジェットを実装する方法ですが、VSソリューションの構成を見せられないので、概要だけ書くと、

  1. Xamarin.FormsのXamarin.AndroidプロジェクトにResources/xml/widget.xmlファイルを作成して、appwidget-provider要素でウィジェットを定義する。レイアウトの定義方法は、Androidでの一般的な方法なので公式資料かXamarin.AndroidのSimpleWidgetサンプルを参考にしてください。

  2. AppWidgetProviderを実装したクラスを作成して、BroadcastReceiver・IntentFilter・MetaDataの属性を設定する。ここもXamarin.AndroidのSimpleWidgetサンプルと同じです。

[BroadcastReceiver (Label = "@string/widget_name")]
[IntentFilter(new [] { AppWidgetManager.ActionAppwidgetUpdate, })]
[MetaData("android.appwidget.provider", Resource = "@xml/widget")]
public class WordWidget : AppWidgetProvider {
(略)

と、Xamarin.AndroidのSimpleWidgetサンプルで実装されていたコードを、Xamarin.FormsのDroidプロジェクトに持ってきただけです。

(後日、ソリューションのファイル構成を載せます)

このDroidプロジェクトをビルドして配置すると、アプリ本体がインストールされるとともに、ウィジェットもインストールされて画面に配置できます。

(後日、スクリーンショットを載せます)

アプリからホームスクリーンウィジェットの更新

アプリからホームスクリーンウィジェットを更新する方針として、以下の2案を考えていました。

  1. アプリからインテントをブロードキャストして、BroadcastReceiverを実装したクラスがそれを受信してウィジェットを更新する。

  2. ローカルデータベースか何かに更新情報を記録して、ウィジェットから参照する(更新を知る手段は未検討)

まず案1で実装したところ、思ったように動作しているので、これで進めようと思います。実装内容の概要は以下の通りです。

  • DroidプロジェクトにBroadcastReceiverを実装したクラスMyBroadcastReceiver を作成する。カスタムインテントに対して、ウィジェットの更新メソッドを呼び出す。
    [BroadcastReceiver]
    [IntentFilter(new[] { MyBroadcastReceiver.CUSTOM_INTENT })]
    public class MyBroadcastReceiver : BroadcastReceiver
    {
        public const string CUSTOM_INTENT = "homescreenwidgetsample1.droid.widget.update";

        public override void OnReceive(Context context, Intent intent)
        {
            if (intent.Action == CUSTOM_INTENT)
            {
                string text = intent.GetStringExtra("text");
                SampleWidget.Update(context, text);
            }
        }
    }
  • MyBroadcastReceiverから呼び出されるウィジェット更新のメソッドを作成する。このメソッドはウィジェットのOnUpdateメソッドからも呼ばれます。
        public static void Update(Context context, string text)
        {
            RemoteViews updateViews = new RemoteViews(context.PackageName, Resource.Layout.widget_word);
            updateViews.SetTextViewText(Resource.Id.blog_title, text);

            ComponentName thisWidget = new ComponentName(
                context.PackageName, Java.Lang.Class.FromType(typeof(SampleWidget)).Name);
            AppWidgetManager manager = AppWidgetManager.GetInstance(context);
            manager.UpdateAppWidget(thisWidget, updateViews);
        }
  • ウィジェット更新のインターフェースILiveTileServiceをPCLプロジェクトに作成する。
    public interface ILiveTileService
    {
        void Update(string text);
    }
  • ILiveTileServiceの実装クラスLiveTileServiceをDroidプロジェクトに作成する。更新の実装では、更新データを載せたExtraを設定したカスタムインテントをブロードキャストします。
    public class LiveTileService : ILiveTileService
    {
        public void Update(string text)
        {
            Intent updateIntent = new Intent();
            updateIntent.SetAction(Widgets.MyBroadcastReceiver.CUSTOM_INTENT);
            updateIntent.PutExtra("text", text);
            Xamarin.Forms.Forms.Context.SendBroadcast(updateIntent);
            return;
        }
    }
  • IPlatformInitializerのAndroidの実装クラス(MainActivity.csファイル)で同インターフェースに対する同実装クラスのインジェクションを登録する。
    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IUnityContainer container)
        {
            container.RegisterType(typeof(ILiveTileService), typeof(LiveTileService));
        }
    }
  • PCLプロジェクトのViewModelのコンストラクタで、ILiveTileServiceを実装したインスタンスを受け取り、ウィジェットの更新時にそのメソッドを呼び出す。
public class MainPageViewModel : BindableBase, INavigationAware
    {
        private ILiveTileService liveTileService;

        public DelegateCommand UpdateButtonClickedCommand { get; private set; }

        public MainPageViewModel(ILiveTileService liveTileService)
        {
            this.liveTileService = liveTileService;
            this.UpdateButtonClickedCommand = new DelegateCommand(() => UpdateButtonClicked());
        }

        private void UpdateButtonClicked()
        {
            this.liveTileService.Update(DateTime.Now.ToString());
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }

(後日、ソリューションのファイル構成を載せます)

ソリューションのビルド・配置の後、ウィジェットをホームスクリーンに置いてからアプリを起動してボタンを押すと、ウィジェットが更新されています。

(後日、スクリーンショットを載せます)

まとめ

Windows 10 UWPアプリのライブタイルを、Xamarin.FormsのDroidプロジェクトでもホームスクリーンウィジェットを使って同様の機能を実装できることが分かりました。

なお、Xamarin.FormsのUWPアプリ側でも、Android側と同様にIPlatformInitializerの実装クラスを作成して、 TileUpdateManagerを使ってライブタイルを更新できます。今後は、この方法によるUWP版とAndroid版でライブタイルの共通化を実装していきたいと思います。

明日はfumiya-kumeさんです。よろしくお願いします。

MicroPythonで寿司を回してみた

これは、Python Advent Calendar 2016の22日目の記事です。

MicroPythonスクリプトをマイコンボードに配置してPCから切り離した状態で実行させる方法と、その応用例として寿司を回してみた例を紹介します。

MicroPythonでのマイコンアプリ開発

Web、機械学習、数値計算をはじめ、様々な分野で利用されているPythonですが、ken5owataさんのPython Advent Calendar 2016の10日目の記事「micropythonを始めよう(ESP8266編)」で紹介されているように、MicroPythonを使ってマイコンのアプリを開発することもできます。

micropython.org

MicroPythonの詳細は先ほどの記事で説明されているので割愛しますが、国内で安価に入手できるESP8266に対応しているメリットがあると思います。今回もESPr Developer(ESP-WROOM-02開発ボード)を使って説明をします。なお、先ほどの記事でも書かれていますが、MicroPythonファームウェアヘの更新で工事設計認証に影響を及ぼす可能性がある点は注意が必要です。この点がクリアされるといいのですが。

MicroPythonボードへのスクリプトの配置

MicroPythonはPCとシリアル接続して、REPL(Read Evaluate Print Loopの略)を使ってPCから対話的にPythonスクリプトを評価・実行できる特徴があります。MicroPythonを学習したり、コードを検証しているときはとても便利な機能なのですが、マイコンをPCから切り離してガジェットとして動作させるときには使えない方法です。

そこで、スクリプトをESP8266に配置して、PCと接続されていない状態でも実行できる方法がないか調べてみて、見つけたツールがadafrui-ampyです。

github.com

(参考)Adafruitによる解説記事

ampyはシリアル通信を用いて、MicroPythonボードのファイルを操作したり、指定したPC上のPythonスクリプトをMicroPythonボードで実行できるツールです。ボード上のファイル操作をできるツールは他にありますが、PC上のファイルを指定してボードで実行できる機能はampyの特徴かと思います。

インストール方法

ampyはpipでインストールできます。

C:\temp>pip install adafruit-ampy
Collecting adafruit-ampy
  Downloading adafruit_ampy-0.6.3-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): click in c:\python\winpython-64bit-3.5.2.3qt5\python-3.5.2.amd64\lib\site-packages (from adafruit-ampy)
Requirement already satisfied (use --upgrade to upgrade): pyserial in c:\python\winpython-64bit-3.5.2.3qt5\python-3.5.2.amd64\lib\site-packages (from adafruit-ampy)
Installing collected packages: adafruit-ampy
Successfully installed adafruit-ampy-0.6.3

ampyでの操作方法

まずampyのヘルプを見てみます。

C:\temp>ampy --help
Usage: ampy [OPTIONS] COMMAND [ARGS]...

  ampy - Adafruit MicroPython Tool

  Ampy is a tool to control MicroPython boards over a serial connection.
  Using ampy you can manipulate files on the board's internal filesystem and
  even run scripts.

Options:
  -p, --port PORT  Name of serial port for connected board.  Can optionally
                   specify with AMPY_PORT environemnt variable.  [required]
  -b, --baud BAUD  Baud rate for the serial connection (default 115200).  Can
                   optionally specify with AMPY_BAUD environment variable.
  --version        Show the version and exit.
  --help           Show this message and exit.

Commands:
  get    Retrieve a file from the board.
  ls     List contents of a directory on the board.
  mkdir  Create a directory on the board.
  put    Put a file on the board.
  reset  Perform soft reset/reboot of the board.
  rm     Remove a file from the board.
  run    Run a script and print its output.

ヘルプからわかるように、ampyでできることは以下の4種類です。

  • ボード上のファイル操作(PCへのファイル取得、一覧表示、PCからのファイル配置、削除)
  • ボードでのディレクトリ作成
  • ボードのソフトリセットとリブート
  • (引数に指定したPC上の)Pythonスクリプトの実行と結果の表示

操作するときはMicroPythonボードをシリアル接続して、そのCOMポート番号を-pオプションに指定します。ボードレートは指定しなくても問題ないと思います。

ファイル操作の例

ampy get/ls/put/rmコマンドでMicroPythonボード上のファイルを取得・一覧表示・配置・削除できます。

ちなみに、putコマンドでのファイルの配置では、ボード上のファイル名を指定することもできて、コマンドの最後にフルパスで記述します。

C:\temp>ampy -p COM5 ls
boot.py
webrepl_cfg.py
data.py
main.py

C:\temp>ampy -p COM5 put hello.py /main.py

C:\temp>ampy -p COM5 get main.py

C:\temp>ampy -p COM5 rm main.py
Pythonスクリプトの実行例

ampy runコマンドを使うと、PCにあるPythonスクリプトをMicroPythonボードで実行できます。例外が発生した場合のスタックトレースは、ampyコマンドを実行しているターミナル画面に表示されます。Pythonスクリプトの開発中は、このコマンドでデバッグできます。

C:\temp>ampy -p COM5 run hello.py
(出力は省略)

PCから切り離したMicroPythonボードでスクリプトを実行する方法

MicroPythonボードは、電源が投入されたときに実行される2種類のファイルがあります。

  • boot.py: 電源が投入されるとはじめに実行されるスクリプト

  • main.py: boot.pyの後に実行されるスクリプト

(参考)3. The internal filesystem — MicroPython 1.8.6 documentation

そこで、MicroPythonボードで実行したスクリプトをampy putコマンドでmain.pyとしてボードに配置すれば、 PCと接続していなくても実行することができます。

応用例:ESP-WROOM-02+AQM1248Aで寿司を回す

MicroPythonスクリプトをマイコンボード単体で実行できることが分かったら、次は寿司を回したくなるものです。 さっそくESPr DeveloperにSPI接続の小型グラフィック液晶ボード AQM1248Aをつないで、寿司を回してみました。

ソースコードはこちらで公開しています。コード自体の説明は別の記事にまとめることにします。

github.com

これを実行するためには、まずESPr DeveloperとAQM1248Aを接続します。

f:id:kumar:20161221230850p:plain:w350 f:id:kumar:20161221062702j:plain:w350

次に、ampy putコマンドでdata.pyファイルとSushiRotator_ESP8266_AQM1248A_SPI.pyファイル(ボード上の名前をmain.pyとする)をESP-WROOM-02に配置します。

C:\temp>ampy -p COM5 put data.py

C:\temp>ampy -p COM5 put SushiRotator_ESP8266_AQM1248A_SPI.py /main.py

この状態でESPr DeveloperのマイクロUSB端子に電源をつなぐと、液晶ボードに寿司が回り始めます。これで、どこでも寿司を回すことができますね。実際にモバイルバッテリーで動作させている様子を動画で見てください。


MicroPythonSushiRotatorの実行例

遅い…

寿司の移動がだいぶ遅いのと、寿司が1組だけで寂しいので、もっと回転寿司らしく動くように改善したいと思います。

まとめ

手になじんだPythonでマイコンのアプリを開発できるMicroPythonはとても魅力的です。 開発環境も、シリアル接続したマイコンでREPLを使って対話的にコードを実行できるだけでなく、ampyコマンドでスクリプトファイルを実行したり、マイコンボードに配置して単独で実行できたりします。

現時点ではMicroPythonをサポートしたIDEを知らないのですが、ampyのようなツールもあるので、いずれはIDEでサポートされるのではないかと期待しています。

明日の担当はJoJeongMinさんです。よろしくお願いします。

StateTriggersでの画面制御(画面の回転、Continuum・PC)

これは、Windows 10 Mobile / Windows Phone Advent Calendar 2016の7日目の記事です。

StateTriggersでの画面制御

Windows 10 UWPでは、VisualStateManagerでのVisualStateの定義にStateTriggersが追加されて、コントロールがVisualStateの状態になるトリガーを記述することができるようになりました。

StateTriggersのサンプルコードは、画面の幅・高さを扱うAdaptiveTriggerを使った例が多いのですが、他のStateTriggersを作成して組み合わせることで、それ以外の条件に応じてレイアウト変更などの画面制御をXAMLで行うことができます。

既に広く知られた手法かとも思ったのですが、Webを検索しても詳しく書いた例が少ないため、参考として、画面を回転した場合と、ContinuumやPCで表示した場合のそれぞれについて、StateTriggersを使ったXAMLベースでの画面レイアウト変更を行う方法を紹介します。

サンプルアプリの画面仕様

今回、例として作成するアプリの画面仕様は以下の通りです。画面が広いContinuumやPC画面では、2画面を1つにまとめて表示する仕様を想定しました。

  • リストのあるマスター画面と、選択された項目の情報を表示する詳細画面から構成される。
  • マスター画面は、ロゴの画像とリストから構成される。ポートレート表示では縦に並べて、ランドスケープ表示では横に並べる。
  • 詳細画面はテキスト欄から構成される。
  • マスター画面と詳細画面は、電話ではリストの選択と戻るボタンで相互に画面遷移する別画面とする。一方、ContinuumとPCでは1画面に並べて表示する。

f:id:kumar:20161206220648p:plain:w400

画面の実装方針

電話のポートレート表示とランドスケープ表示、そしてContinuum・PC画面の表示を統一的に扱うために、2行×3列のGridを作成して、マスター画面のロゴ画像・リスト、詳細画面のテキスト欄を各セルに並べます。

マスター画面は、電話のポートレート表示では、1列目の1行目・2行目にロゴ画像とリストを配置して、残りは非表示にします。

f:id:kumar:20161206221721p:plain:w300

また、電話のランドスケープ表示では、1行目の1列目・2列目にロゴ画像とリストを配置して、残りは非表示にします。

f:id:kumar:20161206221810p:plain:w300

電話の詳細画面は、3列目の1行目にテキスト欄を表示して、残りは非表示にします。

f:id:kumar:20161206230821p:plain:w300

最後に、Continuum・PC画面では、1列目の1行目・2行目にロゴ画像とリストを配置して、3列目の1行目・2行目にまたがってテキスト欄を表示します。2列目は非表示にします。

f:id:kumar:20161206222559p:plain:w300

これらの画面レイアウトの変更は、電話のポートレート表示・ランドスケープ表示、Continuum・PC画面であることをStateTriggersで判定して、各状態のVisualState.SettersでGridの表示状態を変更することで実現します。

画面制御の実装

サンプルアプリを実装したコードを以下で公開しています。本記事では、ポイントとなる箇所だけ説明します。

github.com

電話のマスター画面(ポートレート表示)

この画面が表示されるのは、以下の条件を満たした場合です。

  • 画面の向きがポートレートである
  • 電話かつ非Continuumで実行されている(*)
    • Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamilyがMobileである
    • UIViewSettings.GetForCurrentView().UserInteractionModeがTouchである
  • リストが選択されていない

(*)電話・PCおよびContinuumの判定方法は高橋さんのブログ記事「UWPをPCとMobileで動かしたときの環境チェック方法 – 高橋 忍のブログ」で紹介されている通り、DeviceFamilyとUserInteractionModeで判定できます。

f:id:kumar:20161207035836p:plain:w300

(高橋さんのブログ記事から引用)

この複合的な条件をVisualState.StateTriggersで実装するのですが、そのときに便利なのが WindowsStateTriggersと呼ばれるパッケージです。

WindowsStateTriggersには、デバイスの状態を判定するトリガーだけでなく、CompositeStateTriggerというAND条件を判定するトリガーも含まれます。これは子要素にトリガーをとり、それらがすべてTrueのときにTrueと判定されます。

これらを使うと、電話のマスター画面(ポートレート表示)を判定するStateTriggersは以下のように書けます。

<VisualState.StateTriggers>
    <wintrigger:CompositeStateTrigger Operator="And">
        <wintrigger:CompositeStateTrigger.StateTriggers>
            <wintrigger:OrientationStateTrigger Orientation="Portrait"/>
            <wintrigger:DeviceFamilyStateTrigger DeviceFamily="Mobile"/>
            <wintrigger:UserInteractionModeTrigger InteractionMode="Touch"/>
            <wintrigger:IsFalseStateTrigger Value="{x:Bind ViewModel.IsItemsListSelected, Mode=OneWay}"/>
        </wintrigger:CompositeStateTrigger.StateTriggers>
    </wintrigger:CompositeStateTrigger>
</VisualState.StateTriggers>

リストが選択されていないことは、ViewModelのIsItemsListSelectedプロパティ(リストの選択状態に連動)がFalseであることをIsFalseStateTriggerを使って判定します。

このトリガーが満たされた時に、Gridの行・列の表示・非表示を設定して、画面要素の行列位置を変更します。電話のマスター画面(ポートレート表示)の場合、2列目・3列目を非表示にしますが、これはColumnDefinitionにx:Name属性を設定して、

<Grid.RowDefinitions>
    <RowDefinition x:Name="Row0" Height="Auto"/>
    <RowDefinition x:Name="Row1" Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
    <ColumnDefinition x:Name="Column0" Width="1*"/>
    <ColumnDefinition x:Name="Column1" Width="1*"/>
    <ColumnDefinition x:Name="Column2" Width="1*"/>
</Grid.ColumnDefinitions>

この名前を使って、行・列のWidthを0に設定することで実現できます。

    <VisualState.Setters>
        <Setter Target="Row0.Height" Value="Auto"/>
        <Setter Target="Row1.Height" Value="*"/>

        <Setter Target="Column0.Width" Value="*"/>
        <Setter Target="Column1.Width" Value="0"/>
        <Setter Target="Column2.Width" Value="0"/>
                        
        <Setter Target="IconImage.(Grid.Column)" Value="0" />
        <Setter Target="IconImage.(Grid.Row)" Value="0" />
        <Setter Target="IconImage.(Grid.ColumnSpan)" Value="1" />
        <Setter Target="IconImage.(Grid.RowSpan)" Value="1" />

        <Setter Target="ItemsList.(Grid.Column)" Value="0" />
        <Setter Target="ItemsList.(Grid.Row)" Value="1" />
        <Setter Target="ItemsList.(Grid.ColumnSpan)" Value="1" />
        <Setter Target="ItemsList.(Grid.RowSpan)" Value="1" />
                        
        <Setter Target="ItemInfo.(Grid.Column)" Value="1"/>
        <Setter Target="ItemInfo.(Grid.Row)" Value="0"/>
        <Setter Target="ItemInfo.(Grid.ColumnSpan)" Value="1"/>
        <Setter Target="ItemInfo.(Grid.RowSpan)" Value="2"/>
    </VisualState.Setters>
</VisualState>   

電話のマスター画面(ランドスケープ表示)

この画面が表示されるのは、以下の条件を満たした場合です。

  • 画面の向きがランドスケープである
  • 電話かつ非Continuumで実行されている
    • Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamilyがMobileである
    • UIViewSettings.GetForCurrentView().UserInteractionModeがTouchである
  • リストが選択されていない

これを記述するStateTriggersは以下の通りです。

<VisualState.StateTriggers>
    <wintrigger:CompositeStateTrigger Operator="And">
        <wintrigger:CompositeStateTrigger.StateTriggers>
            <wintrigger:OrientationStateTrigger Orientation="Landscape"/>
            <wintrigger:DeviceFamilyStateTrigger DeviceFamily="Mobile"/>
            <wintrigger:UserInteractionModeTrigger InteractionMode="Touch"/>
            <wintrigger:IsFalseStateTrigger Value="{x:Bind ViewModel.IsItemsListSelected, Mode=OneWay}"/>
        </wintrigger:CompositeStateTrigger.StateTriggers>
    </wintrigger:CompositeStateTrigger>
</VisualState.StateTriggers>

このトリガーが満たされたときのVisualState.Settersは省略します。

電話の詳細画面

この画面が表示されるのは、以下の条件を満たした場合です。

  • 電話かつ非Continuumで実行されている
    • Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamilyがMobileである
    • UIViewSettings.GetForCurrentView().UserInteractionModeがTouchである
  • リストが選択されている

これを記述するStateTriggersは以下の通りです。リストが選択されていることの判定に使ったIsTrueStateTriggerは、WindowsStateTriggersのIsFalseStateTriggerを修正して作成しています。

<VisualState.StateTriggers>
    <wintrigger:CompositeStateTrigger Operator="And">
        <wintrigger:DeviceFamilyStateTrigger DeviceFamily="Mobile"/>
        <wintrigger:UserInteractionModeTrigger InteractionMode="Touch"/>
        <trigger:IsTrueStateTrigger  Value="{x:Bind ViewModel.IsItemsListSelected, Mode=OneWay}"/>
    </wintrigger:CompositeStateTrigger>
</VisualState.StateTriggers>

このトリガーが満たされたときのVisualState.Settersは省略します。

Continuum・PC画面

電話のContinuum表示、もしくはPC画面であることは、UIViewSettings.GetForCurrentView().UserInteractionModeがMouseであることで判定できます。

<VisualState.StateTriggers>
    <wintrigger:UserInteractionModeTrigger InteractionMode="Mouse"/>
</VisualState.StateTriggers>

このトリガーが満たされたときのVisualState.Settersは省略します。

実行結果

ポートレート表示

f:id:kumar:20161207034155p:plain:w250 f:id:kumar:20161207034202p:plain:w250

ランドスケープ表示

f:id:kumar:20161207034214p:plain:w250 f:id:kumar:20161207034216p:plain:w250

Continuum

f:id:kumar:20161207034314p:plain:w300

PC

f:id:kumar:20161207034452p:plain:w300

注意点

1つのXAMLに全てのコントロールを配置して、動的にその位置と表示・非表示を変更して画面制御を実装したため、戻るキーでの画面遷移に注意する必要があります。

電話で実行した場合、詳細画面で戻るキーが押されたらマスター画面に戻る必要があります。そのため、DeviceGestureServiceのGoBackRequestedイベントハンドラを実装して、画面の状態に応じた処理を行います。

このイベントハンドラを実装するためには、ViewModelでVisualStateGroupのVisualStateを取得する必要があるのですが、ViewModelでViewの状態をどのように取得すればよいのかまだ分かっていません。そのため、UIViewSettings.GetForCurrentView().UserInteractionModeがTouchかMouseか調べることで暫定的に対応しています。ここは今後の改善点としたいと思います。

private void DeviceGestureService_GoBackRequested(object sender, DeviceGestureEventArgs e)
{
    var view = Windows.UI.ViewManagement.UIViewSettings.GetForCurrentView();
    if (view.UserInteractionMode == Windows.UI.ViewManagement.UserInteractionMode.Mouse)
    {
        // PCもしくはContinuumの場合、アプリを終了する
        e.Handled = false;
        e.Cancel = false;
    }
    else
    {
        // 電話で、アイテムが選択されている画面の場合、リスト画面へと戻る
        if (this.IsItemsListSelected)
        {
            this.SelectedIndex = -1;
            e.Handled = true;
            e.Cancel = true;
        }
        else
        {
            e.Handled = false;
            e.Cancel = false;
        }
    }
}

まとめ

VisualStateManagerのStateTriggersを使って、画面の各状態に遷移するトリガーと、各状態でのコントロールの表示を定義することで、XAML上でレイアウト変更などの画面制御を行うこと、そしてこのStateTriggersの記述には、WindowsStateTriggersを利用することで、複合的な条件にも対応できることを見ました。

このように、XAML上で画面制御を定義することで、ViewModelやViewのコードビハインドで画面遷移に関する処理(状態の判定や画面遷移)を書く必要がなくなり、それぞれに書かれるべきコードの記述に専念できます。

Windows 10 Mobile / Windows Phone Advent Calendar 2016

以上がWindows 10 Mobile / Windows Phone Advent Calendar 2016の7日目の記事でした。

明日はpikepikeidさんです。よろしくお願いします。

Xamarin.Forms入門中~UWPアプリのライブタイルの置き換え①~

Windows 10 UWPアプリのタイル通知を、Androidではウィジェットに置き換える方法を調べてたので、そのメモ。

XamarinでのAndroidウィジェット作成

作成方法

調べた範囲では、プラットフォーム固有機能のAndroidウィジェットは Xamarin.Formsで作成できなくて、公式サンプルのSimple Widgetのように、Xamarin.Androidで作成できる模様。

サンプルの構成

重要なファイルは以下の模様。

  • Resources/xml/widget_word.xml

ウィジェットの設定をするファイル。更新間隔はandroid:updatePeriodMillisで指定する。

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/widget_message" />
  • Resources/layout/widget_word.xml

ウィジェットのレイアウトを定義するファイル。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    style="@style/WidgetBackground">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:src="@drawable/star_logo" />
   ...
</RelativeLayout>
  • WordWidget.cs

Android.Appwidget.AppWidgetProviderを継承したクラスで、更新時に呼ばれるOnUpdateを定義。更新処理はAndroid.App.Serviceを継承したUpdateServiceで実装している。

using System;
using Android.App;
using Android.Appwidget;
using Android.Content;

namespace SimpleWidget
{
    [BroadcastReceiver (Label = "@string/widget_name")]
    [IntentFilter (new string [] { "android.appwidget.action.APPWIDGET_UPDATE" })]
    [MetaData ("android.appwidget.provider", Resource = "@xml/widget_word")]
    public class WordWidget : AppWidgetProvider
    {
        public override void OnUpdate (Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
        {
            // To prevent any ANR timeouts, we perform the update in a service
            context.StartService (new Intent (context, typeof (UpdateService)));
        }
    }
}
  • UpdateService.cs

Android.App.Serviceを継承している。OnStartで更新処理を実装。

f:id:kumar:20161201072941p:plain:w300

Xamarin.Formsとの共存

新しいプロジェクトの追加>Visual C#>Windowsから"クラスライブラリ(iOS、Android、Windowsのポータブル)"を選択して追加したPCLを、Xamarin.Androidから参照して利用できるので、Xamarin.Formsで作成するアプリ本体とXamarin.Androidのウィジェットで共通利用するライブラリの実装は問題なさそう。

f:id:kumar:20161130073630p:plain:w300

詳しい検証は週末にコードを書いて検証してみます。

データの更新方法

Resources/xml/widget_word.xmlで指定した時間間隔で定期的に呼ばれてウィジェットを更新するのですが、UWPのライブタイルのようにアプリからデータを更新したいのですが、それは調査中です。

Androidアプリの一般的な話になると思うので、程なく情報を見つけられそうです。

Xamarin.Forms入門中~ソリューション作成~

Windows 10 MobileとAndroidの両方にアプリをリリースできるよう、Xamarin.Formsを学習してます。 その学習で得たことを書いていきます。

ソリューションの作成

新しいプロジェクト>テンプレート>Visual C#>Prismから"Prism Unity App (Xamarin.Forms)"を選択する。

f:id:kumar:20161130073233p:plain:w300

表示されない場合、Xamarin.Forms Templateの拡張機能が必要かも。

f:id:kumar:20161130074116p:plain:w300

ライブラリの追加

アプリから共通に使うライブラリは、PCLとして追加する。新しいプロジェクトの追加>Visual C#>Windowsから"クラスライブラリ(iOS、Android、Windowsのポータブル)"を選択する。

f:id:kumar:20161130073630p:plain:w300

これをXamarin.Formsアプリの共通ライブラリプロジェクトから参照すればよいが、このままではエラーが出るので、ターゲットを合わせる必要がある(Silverlightを追加)

f:id:kumar:20161130073444p:plain:w300