Xamarin.Forms+F# で Navigation.PushAsync を使うときの注意

Xamarin.Forms で、次のページに遷移させるときには NavigationPage クラスを使って置いて、遷移先のページに対して、

await this.Navigation.PushAsync(new SubPage());

なことをやります。NavigationPage を使うと複数ページを簡単に扱えるので、超簡単なスマートフォンアプリを作る分には楽ですよね。テスト的に作るときにいいです。

これを F# でやります。

■サンプルコード

FSharpNavigationPushAsync
http://github.com/moonmile/XamarinSamples/tree/master/FSharpNavigationPushAsync

簡単な遷移だけのサンプルコードです。
iOS/Android/Windows Phone で動くようにしてあります。フロントは面倒なので C# で書いてありますが、中身の PCL のところは F# になっています。フロント部分は、基本そのままなので C# でも良いかと。

以下、F# で Xamarin.Forms を扱う時の手順も含めて書いておきます。

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

残念ながら F# には、Mobile Apps がないので、C# で作ります。

Xamarin.Forms を使う PCL は Visual Studio 2013 でしか作れません。Xamarin Studio では作れません。これを、元の C# の PCL とすり替えます。

NuGet で Xamarin.Forms を更新しておきます。

各プロジェクトのバージョンが違うと、実行エラーになります。Android/iOS の場合は、特に問題がでないのですが、Windows Phone の場合は実行時にコケルので曲者です。

■F# PCL を standalone でビルドする

F# 単体で動かす場合には、FSharp.Core を気にしなくていいのですが、Xamarin を使って Android/iOS/Windows Phone で共通のライブラリを作るときは、standalone スイッチをつけてビルドをします。

standalone をつけなくても、Android/iOS の場合は大丈夫なのですが、何故か Windows Phone のときだけうまくいきません。FSharp.Core のバージョン違いのようで、Windows Phone SL なので、これは仕方がないっぽいです。
まあ、standalone の注意は、以下を見てください。

恐ろしい standalone オプション – 2つのアンコール
http://hafuu.hatenablog.com/entry/20121214

複雑に絡みあいはじめると、変なことになりそうですが、F# で作った PCL を C# のフロントで使う場合にはこれで十分でしょう。

■Windows Phone の ProductID を直す

Xamarin.Forms のプロジェクトテンプレートのバグです。Windows Phone のプロジェクトの ProductID のところを手作業で直しておきます。Properties/WMAppManifest.xml を Visual Studio で開こうとすると、「マニフェストを読む込むことができません」というエラーがでます。

このまま、XML エディタを開いて、

  <App xmlns=&quot;&quot; ProductID=&quot;f653f4bf-b71d-4572-b073-bc2c48395070&quot; Title=&quot;FSharpNavigationPushAsync.WinPhone&quot; RuntimeType=&quot;Silverlight&quot; Version=&quot;1.0.0.0&quot; Genre=&quot;apps.normal&quot;  Author=&quot;FSharpNavigationPushAsync.WinPhone author&quot; Description=&quot;Sample description&quot; Publisher=&quot;FSharpNavigationPushAsync.WinPhone&quot; PublisherID=&quot;f1f3a5b4-b6e0-4488-9fde-ac0b70a9f79e&quot;>

これを、以下のように {} が付くように直します。プロジェクトテンプレートの {} 忘れです。

  <App xmlns=&quot;&quot; ProductID=&quot;{f653f4bf-b71d-4572-b073-bc2c48395070}&quot; Title=&quot;FSharpNavigationPushAsync.WinPhone&quot; RuntimeType=&quot;Silverlight&quot; Version=&quot;1.0.0.0&quot; Genre=&quot;apps.normal&quot;  Author=&quot;FSharpNavigationPushAsync.WinPhone author&quot; Description=&quot;Sample description&quot; Publisher=&quot;FSharpNavigationPushAsync.WinPhone&quot; PublisherID=&quot;{f1f3a5b4-b6e0-4488-9fde-ac0b70a9f79e}&quot;>

Visual Studio Mobile Apps – Blank App (Portable/Shared) – Xamarin Forums
http://forums.xamarin.com/discussion/18694/visual-studio-mobile-apps-blank-app-portable-shared

あたりを参考にしてください。

■F#版の App.fs

C# で書いてある、Hello Forms をそのまま F# で書き直すと、こんな感じになります。

type App() =
    static member GetMainPage() =
        new ContentPage(
            Content = new Label(
                Text = "Hello, Forms !",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand ))

この状態で、Android/iOS/Windows Phoneが動作することを確認してください。

■F#で PushAsync する

遷移先のページを作っておきます。

type SubPage() as this =
    inherit ContentPage()
    do
        this.Content <-
            new Label(
                Text=&quot;This is SubPage&quot;,
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand )

遷移元(メインページ)は、こんな感じです。StackLayout.Children のところが入れ子にできないので、C# のように書けないのが残念なのですが(今度、拡張メソッドを作りましょう)、ひとつひとつ組み立てていきます。

type App() =
(*
    static member GetMainPage() =
        new ContentPage(
            Content = new Label(
                Text = &quot;Hello, Forms !&quot;,
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand ))
*)

(*
    let pushNextPage(next) =
            let AwaitTaskVoid : (Task -> Async<unit>) =
                Async.AwaitIAsyncResult >> Async.Ignore
            page.Navigation.PushAsync(next) |> AwaitTaskVoid |> ignore
*)
    static member GetMainPage() =
        let button = new Button( Text = &quot;Go Next Page&quot;)
        let layout = new StackLayout()
        layout.Children.Add( new Label( Text = &quot;Navigation.PushAsync&quot;))
        layout.Children.Add( button )
        let page = new ContentPage ( Content = layout )
        button.Clicked.Add( fun(_) ->

                // C# の場合は
                // await page.Navigation.PushAsync(new SubPage())
                // これで動作する

                /// 1.これで良さそうな気もするのだがこれは動かない
                // let t = button.Navigation.PushAsync( page )
                // t.Start()
                // t.Wait()
                ///

                /// 2.スレッドを変えてみたが、Android/iOS だけ動く
                // 何故か Windows Phone では動かず
                // let tk = new Task(fun() ->
                //    let t = page.Navigation.PushAsync(new SubPage())
                //    t.Start()
                // )
                // tk.RunSynchronously()

                /// 3. async すると戻り値がvoidなので、ややこしいのだが、
                /// この方式だと3プラットフォームで動く
                /// 参考 http://stackoverflow.com/questions/8022909/how-to-async-awaittask-on-plain-task-not-taskt
                /// async {} の場合、戻り値を持たない Task が使えないのでこうやるらしい。
                /// Page.Navigation.PushAsync が戻り値を持たないのでややこしいだけ。
                let AwaitTaskVoid : (Task -> Async<unit>) =
                    Async.AwaitIAsyncResult >> Async.Ignore
                page.Navigation.PushAsync(new SubPage()) |> AwaitTaskVoid |> ignore

            )
        new NavigationPage(page)

結論から言うと、PushAsync するときに、async の結果を捨てるための補助関数が必要になります。
F# では async/await を書けないので、async {} を使うわけですが、動きが微妙に違うのが曲者です。いや、どちらがいいという訳ではないのですが、一対一にコンバートできません。

/// 1.これで良さそうな気もするのだがこれは動かない
let t = button.Navigation.PushAsync( page )
t.Start()
t.Wait()

一見、これで動きそうな気もするのですが、iOS/Android の実装が異なるらしく動きません。Windows Phoneのほうも駄目なので、PushAsync が返している Task は別ものっぽいです。

/// 2.スレッドを変えてみたが、Android/iOS だけ動く
// 何故か Windows Phone では動かず
let tk = new Task(fun() ->
   let t = page.Navigation.PushAsync(new SubPage())
   t.Start()
)
tk.RunSynchronously()

描画スレッドの違いがおかしいらしいので、別に Task を作ると動くようになります。最初、Xamarin.Forms.XamlProvider は、この方式でやっていたのですが、昨晩 Windows Phone で動かしてみると実行エラーがでることがわかりました。

/// 3. async すると戻り値がvoidなので、ややこしいのだが、
/// この方式だと3プラットフォームで動く
/// 参考 http://stackoverflow.com/questions/8022909/how-to-async-awaittask-on-plain-task-not-taskt
/// async {} の場合、戻り値を持たない Task が使えないのでこうやるらしい。
/// Page.Navigation.PushAsync が戻り値を持たないのでややこしいだけ。
let AwaitTaskVoid : (Task -> Async<unit>) =
    Async.AwaitIAsyncResult >> Async.Ignore
page.Navigation.PushAsync(new SubPage()) |> AwaitTaskVoid |> ignore

結果的に async {} に沿う形に直してやると Windows Phone でも動くようになります。async {} の場合は、戻り値を持たない Task を扱うことができないので、AwaitTaskVoid のような補助メソッドを用意してやります。

Async を拡張して、Async.AwaitTaskVoid にすると、それっぽくなります。

module AsyncExtentions =
    type Async with
        static member AwaitTaskVoid : (Task -> Async<unit>) =
                        Async.AwaitIAsyncResult >> Async.Ignore
open AsyncExtentions
...
page.Navigation.PushAsync(new SubPage()) |> Async.AwaitTaskVoid |> ignore

調子に乗って、コンピュテーション式でやろうとして async do! を使うと動かないんですよねー。これは3機種とも動きません。

async {
    do! page.Navigation.PushAsync(new SubPage()) |> Async.AwaitTaskVoid
} |> ignore

というわけで、F# で PushAsync するときは、Async.AwaitIAsyncResult 拡張を作ってから呼び出すといい、というか注意書きということです。

カテゴリー: F#, Xamarin | Xamarin.Forms+F# で Navigation.PushAsync を使うときの注意 はコメントを受け付けていません

小4の夏休みの宿題をチケット駆動で乗り切る方法を模索

先日の日曜日、日割りでスケジュールを立てた後、1日のタイムスケジュールを立てたのですが、初日からうまくいかないことが判明…いや、途中にプールが挟まったり、学校の短時間の補習がはさまったりして、うまくいきませんでした。初日から、午後6時過ぎまで夏休みの宿題をしている有様に。

まずは、前提から書きますので、参考にしてください。

■夏休みの宿題の全体を把握する

先週の日曜日にやったことは、まず、夏休みの宿題の量を全体として把握しておくことです。

  • 漢字ドリル
  • 計算ドリル
  • 公文算数
  • 一言日記
  • 自由研究(かたつむりの観察日記)
  • 読書感想文

この中で、一言日記は毎日やることなので別な計画にします。自由研究はかたつむりの観察日記だそうで、これも毎日やります。読書感想文は、適当な締切を決めて(祖母の家に遊びに行く前とか、海に遊びに行く前とか)自由にしておきました。

公文算数は週5ペースでやるのですが、週2回教室に行くことになります。持って帰ってきた宿題は週のうちにやればよいので、土日を含んでもよいし、含めなくても良い状態です。

漢字ドリルと計算ドリルは全体のページ数を把握した後、「何日で終わらせるのか?」の計画を立てます。夏休み自体は40日あるのですが、これには土日が含まれています。また、お盆に祖母に家に行ったり、海に行ったりする日も含めてしまっています。これを取り除くと、だいたい20日になります。祖母の家や土日はプロジェクトバッファとして扱います。

■日割り計算する

計算ドリルと漢字ドリルの全体像を把握して、宿題をする日数が20日間と決まったら、これを日割りします。小数点になってしまうので、小4には難しい(分数や小数点は小5からですよね)。あまりを出して、それを上回れば計画通りに終わる、ということを子供に印象付けます。

ここでは、20日間で、2ページずつやれば終わることがわかりました。

そのままでもいいのですが、20日間ずっと宿題が続いているという状態がいいのか、「早めに宿題を終わらせたいのか?」という判断を子供にさせます。これは子供の性質にもよるので、どちらともいえないのですが「自発」を重んじます。

その後、再計画を立てて、計算ドリルと漢字ドリルを10日間で終わらせることにしました。

  • 計算ドリルを毎日4ページずつ
  • 漢字ドリルを毎日3ページずつ
  • 公文算数を平日にやる

という計画が立てられました。これに従って進めばいいのですが、さて、いろいろ「誘惑」なりがあり現実はままならないものです。

■ひとまず、計画を実行してみる

計画がスムースにいくのかどうかは、実際やってみないとわかりません。そのためにプロトタイプとして宿題をやるスピードを確かめるのが第一です。で、初日(火曜日)にやってみたところ、結構、散々な結果でした。

  • 午前中に学校のプールがあった。
  • プールから帰ってきて、宿題をやったかどうかは不明(ワタクシは家で仕事中)
  • お昼を食べた
  • 午後に、漢字と計算ドリルの宿題をやった…模様
  • 午後4時頃のテレビを見ていた。
  • 夕方に弟を迎えに行った。
  • 保育園から帰ってくると、公文が終わっていなかった。
  • 夕飯前(7時半)に公文を無理やり終わらせる。
  • 8時半ごろにカタツムリの観察日記を書いている。
  • 寝る直前に一言日記を書いている。

というわけで、ダメダメですね。これらは一見、学習習慣の問題のように見えますが、違います。まあ「習慣」ができると、早めにやる「習慣」ができるのですが、小4の段階ではまだ「習慣」以前なので、もうちょっと違う問題です。

基本、私は家で仕事なので、娘を監視できる立場にはいるのですが、それじゃ面倒ですよね。面倒というか、監視されないと宿題ができないのでは駄目ですし、これからが大変です。なので、面倒な監視作業はしません。が、「習慣」づけは必要なので、この夏はやる予定です。

■夏休みの宿題にも割り込みがたくさんある

毎日の課題(漢字ドリル、計算ドリル)をやるための障害として、「割り込み」があります。日常ルーチンをこなそうとしても、電話が入ったり、メールに答えないと駄目だったり、そういう問題ですね。私の場合、宅配便が一番集中力がそがれるのですが…まあ、これは仕方がない愚痴です。

プールなり学校の補習なりがあれば、一日の中で夏休みの宿題の時間が削られます。計画通り、むりやり夏休みの宿題をする時間をとればいいのですが(実際、毎日のルーチンワークとしての宿題は1時間以内でおさまるでしょう)、ルーチンワークができない小4の娘には、計画がずれても、計画通りに進むスタイルが必要になります。

■チケットを切る

宿題の日割りはできたものの、それを「毎日やる」ことにすると負担が出てしまうので、毎日やる必要がない状態にします。夏休みの宿題の場合は、

  • 全体の量が決まっている(途中でボリュームが増えたりしない)
  • 手戻りがほとんどない(やった宿題が無駄にならない)
  • 締切が決まっている(エンドが決まっている)
  • 分割が可能

という特徴があります。プロジェクトとしての組み立て方も WBS に落とすよりも、日割りで計算する簡単な方法がとれます。これは、読書感想文のような場合には、WBS な落とし込みをするか、別なスタイルが必要です。

というわけで、「毎日、計画通りに漢字ドリルと計算ドリルを数ページずつやる」という計画が破たんしてしまったので、「夏休みの宿題チケットを消費する」というスタイルに変更します。漢字ドリル3ページ、計算ドリル4ページのチケットをそれぞれ作って、これを10枚の付箋にします。全部で20枚になるわけですね。

■チケットを消費する

チケット駆動にすると、あまりに遅いペースでチケットを消費すると、エンドがずれるという現象がおこります。プロジェクトの場合は、締切を動かすことも可能ですが、夏休みの場合はエンドが絶対なので、締切を動かすことができません。このため、チケットが消費されているスピードは適宜「監視」が必要です。

本来ならば、バーンダウンチャートを使ってグラフ化するとわかりやすいのですが、ちょっとそのあたりは実際に娘にとってチケット駆動がうまく動くかどうか確かめてからやってみましょう。

カテゴリー: 雑談 | 小4の夏休みの宿題をチケット駆動で乗り切る方法を模索 はコメントを受け付けていません

Xamarin.Forms の XAML を F# から使うために XamlTypeProvider を使う

XFormsPreviewer では、動的にXAMLファイルをロードしてレンダリングする(内部的にはXamarin.Formsのコントロールを生成する)してプレビューを実現していますが、このコアライブラリに XamlTypeProvider があります。最初は Xamarin.Forms のプレビューだけを作るつもりだったけど、作っているうちに F# の TypeProvider と似ていることがわかり、じゃあ TypeProvider 的にしようとしたのが発端です。

■Xamrin.Forms と XAML の関係

要は、WPF や Windows Store の XAML と同じように、フロントエンドを記述する XAML と、ビルド時に自動生成される partial class とで成り立っています。この partial class のほうで、コントロールの名前による参照や、ボタンコントロールのクリックイベントなどを記述するわけで、事実上のコードビハイドですね。Silverlight の XAML の場合には、XAML 自体に C# のコードを埋め込めるので(今でもそうだと思う)XAML 単体で動かすことも可能なわけですが…Windows Store/Windows Phone 8.1 の XAML はできたかどうかわかりません。たぶん、Xamarin.Forms の XAML もできません。

で、コードビハイドを使うと、XAML で記述した View に内部動作のコードが埋め込まれるわけで、(それがコードビハイドだとしても)View と Logic が分離していないのではないか。という話なのですが、まあ、実装的には MVVM の INotifyPropertyChanged/ICommand にしてしまうか、別な方法をにしてしまうか、という具合です。ワタクシ的にはコードビハイドでもいいと思うんですけどね。うまく分離されていれば。

「うまく分離する」という点で、MVVM パターンは、View と Modelが分離できていますが、複数の View を扱うときにちょっと困ります。これは Xamarin.Forms の問題かもしれないけど、とあるビューから別のビューに移るときに、PushAsync を使いますが、この Navigation プロパティは Page にくっついて引数もページなんですよね。

page.Navigation.PushAsync(nextPage)

当たり前といえば当たり前だけど、画面遷移自体は、ページからページに移る。ViewModel の場合は Page に依存したくないので、View から本格的に ViewModel を分離させたときに Command パターン内では他のページに遷移させることができない、ってことになります。まあ、現実的な解としては ViewModel に対応する View を持たせてしまえばいいのですが。

そんな依存関係から、Xamarin.Forms も Windows Store の XAML も View=XAML に対応するコードを出力して、各種イベントやデータを保持する方法をとっています。
実は、F# の TypeProvider も同じことが言えて、実行時にビルドして Type を決めるので FsXaml のように WPF の XAML をコンバートできるけど、これは動作時にはできないのです。というのも、未知の XAML に対して、内部プロパティやイベントの判定を、先にコードビハイドに記述することはできませんよね。。まあ、方法としては、

  • XAML ダウンロード時に TypeProvider でビルドする。
  • Page に付属するプロパティは、dynamic で受けて参照させる。

ということで、実行にデータの整合性のチェックをすればよいのですが、完全にダイナミックに、というわけにはいきません。

また、Xamrin.Forms の XAML から出力するコードは C# のために、F# から使おうとするビルドエラーになります。これは Windows Store の XAML の場合も同じで、ここのコードビハイドのコードが言語依存になってしまって邪魔です。XAML 自体はコード依存していないのに。
逆に言えば、コードビハイドに全く依存しない形で、書けばバックエンドが C# であろうと F# であろうと、場合によっては Visual Basic であろうと関係なく書ける可能性があります。

■View と ViewModel を動的ににリンクさせる

通常の MVVM モデルの場合には、先の画面遷移の関係もあって、View と ViewModel は1対1の関係になっています。View(XAML)に対して、ひとつひとつ ViewModel を作ることが多いのですが、ViewModel から View への依存を完全に切り離してしまえば、ひとつの ViewModel を複数の View に対応させることができます。

コンパイル時にこれを実現するのは非常に簡単で、同じ ViewModel を使っているけど状況に応じて複数の View を切り替えることができます。ただし、「状況に応じて」切り替えられるのは、ViewModel の中ではなく、先の Page のようなコードビハイドの中になります。

ここで、View と ViewModel を完全に分離させようとすると矛盾がでます。

– XAML は、コードビハイドを出力する
– コードビハイドでのみ、画面遷移/Viewの切り替えが可能
– View は ViewModel と分離させたい
– ViewModel から画面遷移くを直接アクセスできない。

この矛盾は、XAML の仕組みがコードビハイドに依存している(partial class)ためであって、XAML 自体が徹底して View に徹していれば、うまく矛盾が解消できるような気がします。もちろん、ビヘイビアのような View 内部に存在するコード(アニメーションのようなコード)は、XAML にあるほうがよいのですが、ViewModel でもまかなえるものは、切り離してしまったほうがよいでしょう。

…という発想のもとに、XAML コード自体を完全に動的にロードさせます。動的にロードさせるわけですから、未知の XAML ファイルでも大丈夫です。いままで、リソースとして持つか、文字列としてプロジェクト内に埋め込むかしかなかったものが、インターネット上に XAML ファイルを置いて、それをダウンロードして使うことができます。まあ、作り始めて分かったのですが、ちょうど HTML がブラウザ上で動くのと同じ仕組みですね。

HTML の場合が

  • HTML でデザインコードを書く。
  • Javascript で動きのあるコードを書く。
  • Javascript はブラウザのインタープリタで動く

というように、コード部分がインタープリタにしかならないのがスピードではネックだったりします。

Xamarin.Forms 用の XAML ファイルを直接ダウンロードできるようにすることで、

  • XAML でデザインコードを書く。
  • 動作は、C# で ViewModel 内に書く。あるいは、動的リンクされるコードビハイドもどきに記述する。
  • ViewModel内 のコードは事前コンパイルで、Xamarin によりネイティブコードで動く。

ViewModel のアセンブリを動的ダウンロードできるかどうかは分からないのですが(ActiveXみたいな感じ?)、ひとまず、この方式をとることで Javascript などによるインタープリタではないネイティブの動作が可能になります(もちろん、Javascript等であっても、実行時ビルドされていればスピードは同じなんですけど)。

Xaml のパース自体が車輪の再発明っぽいので、あまりやりたくないのですが、ざっと F# で書いてみたところ 400 行程度なのでなんとか許容範囲でしょう。

■実際どう使うのか?

moonmile/XamarinXamlPreview
https://github.com/moonmile/XamarinXamlPreview

Xamarin.Forms for F# のコンテスト用に作ったので F# になっていますが、当然ながら C# でも使えます。page.Navigation.PushAsync あたりが、変なことになっているのは F# だからであって、C# だと結構素直に書けます。

type MainPage(path:string) =
    inherit ContentPage()
    let mutable page:Page = null
    let mutable buttonNextPage:Button = null
    let mutable buttonStopWatch:Button = null
    let mutable buttonStopWatchDownload:Button = null
    let mutable label:Label = null
    
    let pushPageVM(rn:string, vm:ViewModelBase) =
        let xaml = ResourceLoader.GetString(rn)
        let next = ParseXaml.LoadXaml(xaml) 
        next.BindingContext <- vm
        let t = new Task( fun() -> 
            let task = page.Navigation.PushAsync(next)
            task.Start()
        )
        t.RunSynchronously()
    
    let pushPage(rn:string) =
        let xaml = ResourceLoader.GetString(rn)
        let next = ParseXaml.LoadXaml(xaml) 
        let t = new Task( fun() -> 
            let task = page.Navigation.PushAsync(next)
            task.Start()
        )
        t.RunSynchronously()

    let pushPageDL(url:string) =
        
        try 
            let hc = new HttpClient()
            let xaml = hc.GetStringAsync(url).Result
            let next = ParseXaml.LoadXaml(xaml) 
            let t = new Task( fun() -> 
                let task = page.Navigation.PushAsync(next)
                task.Start()
            )
            t.RunSynchronously()
        with
        | _ -> 
            label.Text <- String.Format(&quot;Error: cannot open {0}&quot;, url )

    do 
        let xaml = ResourceLoader.GetString(path)

        page <- ParseXaml.LoadXaml(xaml)

        buttonNextPage <- page.FindByName<Button>(&quot;buttonNextPage&quot;)
        buttonStopWatch <- page.FindByName<Button>(&quot;buttonStopWatch&quot;)
        buttonStopWatchDownload <- page.FindByName<Button>(&quot;buttonStopWatchDownload&quot;)
        label <- page.FindByName<Label>(&quot;label&quot;)
        
        buttonNextPage.Clicked.Add( fun(e) -> 
                pushPageVM(&quot;NextPage.xml&quot;, new ViewModelNextPage())
            )
        buttonStopWatch.Clicked.Add( 
            fun(e) -> pushPage(&quot;StopWatchPage.xml&quot;))

        buttonStopWatchDownload.Clicked.Add( 
            fun(e) -> pushPageDL(&quot;http://moonmile.net/up/DownLoadPage.xml&quot;))

    member this.Page 
        with get() = page

Xaml ファイルをそのままプロジェクトに埋め込むと、C# のコードビハイドを吐き出すので、拡張子を xml にしています。ViewModel 自体は、XAML を動的に生成して結びつけます。内部的にはリフレクションの嵐になっていますが気にしてはいけません。外側から使う時、ViewModel から使う時はリフレクションなどは意識しない作りになっています。普通の ViewModel を使うときと同じ要領ですね。

F# なので Command パターンのところがややこしくなっていますが、C# でも同じように書けます。データバインディングを使うので、View に依存しないように書けます。ここは MVVM と同じです。

type ViewModelStopWatch() =
    inherit ViewModelBase()

    let mutable startTime = DateTime.Now
    let mutable endTime  = DateTime.Now
    let mutable time:TimeSpan = new TimeSpan()
    let mutable task:Task = null
    let mutable taskflag = false


    /// リフレクションで iOS/Android の System.Timers.Timer を持ってくる
    /// 現在調節中
    let makeTimer (interval:int, hdr:ElapsedEventHandler) =
        let asm = Assembly.Load(AssemblyName(&quot;System&quot;))
        let t = asm.GetType( &quot;System.Timers.Timer&quot; )
        let item = System.Activator.CreateInstance(t)

        t.GetRuntimeProperty(&quot;Enabled&quot;).SetValue( item, true )
        t.GetRuntimeProperty(&quot;AutoReset&quot;).SetValue( item, true )
        t.GetRuntimeProperty(&quot;Interval&quot;).SetValue( item, interval )
        t.GetRuntimeEvent(&quot;Elapsed&quot;).AddEventHandler( item, hdr )
        t.GetRuntimeMethod(&quot;Start&quot;, null).Invoke(item, null) |> ignore
        item

    member this.Time 
        with get() =  
            time <- endTime - startTime
            String.Format(&quot;{0:00}min {1:00}sec {2:000}&quot;, time.Minutes, time.Seconds, time.Milliseconds )
    member this.StartTime
        with get() = startTime.ToString()

    member this.StartCommand 
        with get() =
            let cmd = 
                new DelegateCommand(
                    (fun (o) -> true ),
                    (fun (o) -> 
                        startTime <- DateTime.Now 
                        endTime <- startTime
                        this.OnPropertyChanged(&quot;StartTime&quot;)
                        this.OnPropertyChanged(&quot;Time&quot;)
                        // makeTimer(2000, new ElapsedEventHandler(fun(s,e)-> 
                        //     System.Diagnostics.Debug.WriteLine(&quot;time {0}&quot;, endTime ))) |> ignore
                        // )))
                        ))
            cmd

    member this.StopCommand 
        with get() =
            let cmd = 
                new DelegateCommand(
                    (fun (o) -> true ),
                    (fun (o) -> 
                        taskflag <- false
                        endTime <- DateTime.Now 
                        this.OnPropertyChanged(&quot;Time&quot;)
                    ))    
            cmd

    member this.ResetCommand 
        with get() =
            let cmd = 
                new DelegateCommand(
                    (fun (o) -> true ),
                    (fun (o) -> 
                        taskflag <- false
                        startTime <- DateTime.Now
                        endTime <- DateTime.Now 
                        this.OnPropertyChanged(&quot;StartTime&quot;)
                        this.OnPropertyChanged(&quot;Time&quot;)
                    ))    
            cmd

ここでストップウォッチを作ろうと思ったのですが、Xamarin.Android の場合、Task.Delay の扱いが Windows Forms と異なるので苦戦中です。実は WinRT も同じ現象になるので、定期的なメソッド起動はタイマーを使わないと駄目なんですよね。このあたりは、別なサンプルアプリとして実装します。

Xamarin.Forms や MvvmCross 的には Service を使うのでしょうが、今回はプラットフォーム依存のアセンブリを動的ロードして、タイマークラスをリフレクションを使って操作する、ということを試しています。結構乱暴ですが、こうすると PCL からプラットフォームへのインターフェースがいらなくなって(タイマーを起動するインターフェースが必要なくなる)依存関係が「疎」になります。実は、タイマーの扱いも、Android/iOS/WinStore とそれぞれ異なるので、工夫が必要なのですが、Device を使って回避する予定です。

ライブラリとしての XamlTypeProvider は NuGet を使って取得できます。
https://www.nuget.org/packages/Xamarin.Forms.XamlProvider/

■実行する

「Download View from Internet」ボタンをクリックすると、インターネット上から XAML コードをダウンロードしています。こうすることで、View だけを定期的に更新することが可能です。

XFormsPerviwer もそうなのですが、Xamarin.Forms の XAML を外だしにしていつでも更新できるようにしておくと、ちょこちょこと XAML を手作業で修正して、エミュレータ上で動作を確認できます。たぶん、開発効率が上がるかな、ってのと、ちょっとしたパズルゲームとか設定画面をダイナミックにロードさせると、画面の雰囲気ががらりと変わってよいかと。アプリ自体はそのままなので、手軽に変えらえるのがミソです。そう、ややこしい「審査」がいらなくなりますからね。

この仕組み自体が「審査」が通るからどうかは、近々試してみる予定です。しばらくは、プレビュー的に使うのと、XamlTypeProvider の整備ってところです。

カテゴリー: 開発 | Xamarin.Forms の XAML を F# から使うために XamlTypeProvider を使う はコメントを受け付けていません

NuGetでXamarin.Forms.XamlProvilderを公開しました

動的にXamarin.FormsのXAMLファイルをロードするときのライブラリとして使っている、Xamarin.Forms.XamlProvilder を NuGet で公開しました。

NuGet Gallery | Xamrin.Forms Xaml Provider 0.1.1
https://www.nuget.org/packages/Xamarin.Forms.XamlProvider/0.1.1

XAMLの文字列から ContentPage 等を作るので、こんな風にXAML形式のデータをC#のコード内に埋め込んで記述ができます。

public void LoadSample()
{
	string xml = @&quot;<?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='XFormsPreview.NewPage'>
		<StackLayout>
			<Label Text='New Page'  />
			<Button Text='Click me'  />
		</StackLayout>
		</ContentPage>&quot;;

	var page = PageXaml.LoadXaml<ContentPage>(xml);
	Assert.IsNotNull(page);
	var layout = page.Content as StackLayout;
	Assert.IsNotNull(layout);
	Assert.AreEqual(2, layout.Children.Count);

文字列から動的に変換するので、ファイルリソースをプロジェクトに埋め込ませて読み込むこともできます。

[TestMethod]
public void GridDemoPage()
{
    var fs = File.OpenText(@&quot;XamlGridDemoPage.xaml&quot;);
    var xaml = fs.ReadToEnd();
    var page = PageXaml.LoadXaml<ContentPage>(xaml);

    Assert.IsNotNull(page);
    Assert.AreEqual(&quot;Grid Demo Page&quot;, page.Title );
    Assert.AreEqual(new Thickness(0, 20, 0, 0), page.Padding);

これを HTTP 経由で拾ってくるようにしたのが XFormsPreviewer です。

async void OnClickSample0(object sender, EventArgs e)
{
    var hc = new HttpClient();
    try
    {
        var res = await hc.GetStringAsync("http://localhost:10150/get/0");
        var page = PageXaml.LoadXaml(res);
        await this.Navigation.PushAsync(page);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
    }
}

当然、HTTP 経由で拾ってくるので、インターネット上に XAML ファイルを配置しておけば、アプリケーションの更新なしに、動的に Xamarin製 XAML ファイルをダウンロードして表示を変えることが可能です…が、いまのところ、プレビューの機能しかないので静的ページしか表示できません。この部分はちょうど Javascript 無しの WebView みたいなものです。

■名前を取得する

ver.0.1.1 では、x:Name で Element を参照できるようにしました。

public void TestName()
{
    string xml = @&quot;<?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='XFormsPreview.NewPage'>
	<StackLayout>
		<Label x:Name='label1' Text='New Page'  />
		<Button  x:Name='button1' Text='Click me'  />
	</StackLayout>
</ContentPage>
&quot;;
         
    var page = PageXaml.LoadXaml<ContentPage>(xml);
    Assert.IsNotNull(page);
    var layout = page.Content as StackLayout;

    var label1 = page.FindByName<Label>(&quot;label1&quot;);
    var button1 = page.FindByName<Button>(&quot;button1&quot;);
    Assert.IsNotNull( label1 );
    Assert.AreEqual(&quot;New Page&quot;, label1.Text);
    Assert.AreEqual(&quot;Click me&quot;, button1.Text);
}

XAML内に x:Name で名前を付けておくと、Xamarin.Forms や WPFなどでは、それぞれのプロパティにしてくれますが、動的にXAMLファイルをロードする場合は直接プロパティを参照しづらいので、FindByName<Label>(“name”) のように参照します。FindByName 自体は Xamarin.Forms にもあるし、Silverlight でよく使うパターンです。
こうすると、固定で Xamarin.Forms を作ったときと同じように、各種のプロパティが設定できます。

var page = PageXaml.LoadXaml<ContentPage>(xml);
var label1 = page.FindByName<Label>(&quot;label1&quot;);
label1.Text = &quot;change display&quot;;

■BindingContent を実装中

ただし、x:Name でコントロールのオブジェクトを取ってくると、動的にロードされた XAML との接点が多くなってしまうので、もっと沿結合にするため BindingContext の部分を実装中です。MVVM パターンにしたがって、
INotifyPropertyChanged インターフェースと ICommand インターフェースが実装されていれば、ViewModel がそのまま利用できる感じになる予定です。

カテゴリー: C#, Xamarin | NuGetでXamarin.Forms.XamlProvilderを公開しました はコメントを受け付けていません

Xamarin.Forms 用のプレビューアをアルファ版で公開

1週間ほど掛けて、おおまかなところが動いてきたのでアルファ版で公開します。
何ができるかというと、Xamarin.Forms の XAML ファイルを動的にロードして、エミュレータ上に表示させます。現在のところ、Xamarin Studio にも Visual Studio にも GUI ベースのデザイナがないので、XAML コードを手打ちしています。まあ、手打ち自体は構わないのですが、画面の表示を確認するのに、いちいちビルドしてエミュレータなどにデプロイしないと画面が分かりません。勢い、StatckLayout などの汎用的な画面を作ることが多いのですが、それにしても色とか微妙な文字の配置とかが面倒です。そこで、XAML 記述を HTTP 経由で持ってきて、エミュレータ内で再構築するプレビューツールを作りました。エミュレータ上で画面をリロード(HTTP 経由で XAML ファイルを再びロード)することで、画面の状態が更新できます。これにより、ひとまず、XAMLのコードを書く→デプロイ→デザインの確認→XAMLのコード直し、というサイクルがなくなり、XAMLのコードを書く→画面をリロードする→XAMLの手直し、の手順だけで良くなります。ちょっと、使ってみた感じでは、表示している文字変更とか色変更が手軽にできるので、Xamarin Studio で本格的なデザイナが作られるまでのツナギになるとは思います。

■ダウンロード

moonmile/XFormsPreviewer
https://github.com/moonmile/XFormsPreviewer

まだ、NuGet を作っていないので、github から直接取ってきてください

■構成

image

  • Host/XFormsPreviewerHost ? XAML データを提供する簡易ホスト
  • Sample/XFormsSampleApp — サンプルプロジェクト(XAMLファイルが含まれる)
  • Viewer/XFormsPreviewer — 簡易プレビューア
  • XFormsProvider ? Xamarin製XAMLのパーサ
  • XFormsProvider.Test ? テストプロジェクト

プレビューアが、簡易ホストを経由してサンプルプロジェクト内の XAML ファイルを表示させます。プレビューア自体は、iOS/Android/Windws Phoneエミュレータなので、適当にカスタマイズしてリロードしてください。今後はもうちょっとプレビューア/簡易ホストをもう少しリッチにしていきましょう。

■使い方

応答する XAML ファイルを指定する。
簡易ホストを書き換えて、応答する XAML ファイルを指定します。/get/item のような形式で、プレビューアから呼び出されます。

 
public PreviewHost()
{
    this.Port = 10150;
    this.LoadPaths = new Dictionary<string, string>();
    // TODO: change load xaml file
    // ここで、レスポンスを返す XAML ファイルを指定。
    // 作成中のプロジェクトフォルダを指定すればok.
    // 後で WPF あたりで作成してドラッグ&ドロップ.
    // mac のためにコマンドラインで使えるようにしておく。
    var basefolder = @&quot;..\..\..\Sample\XFormsSampleApp\XFormsSampleApp\&quot;;
    this.LoadPaths.Add(&quot;0&quot;, basefolder + &quot;MainPage.xaml&quot;);
}

簡易クライアントを管理者モードで立ち上げる。
Windows 8.1 の場合は、ローカルサーバーを立ち上げるときに指定ポートを明示的に開ける必要があります。面倒な場合は、コマンドプロンプトを管理者モードで立ち上げてください。

image

プレビューアで要求ボタンを書き換える。
新しくボタンを作ってもよいし、既存のボタンを書き換えてもよいです。

/// <summary>
/// HTTP クライアント経由で XAML ファイルをロードする
/// http://hostname:10150/get/[num] 形式で取得する
/// [num] 部分は XFormsPreviewHost と合わせる
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
async void OnClickSample0(object sender, EventArgs e)
{
    var hc = new HttpClient();
    try
    {
        var res = await hc.GetStringAsync(this.Uri + &quot;0&quot;);
        var page = PageXaml.LoadXaml(res);
        await this.Navigation.PushAsync(page);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
    }
}

呼び出しは /get/item 形式です。XAML データのローディングは非常に簡単ですPageXaml.LoadXaml(res) のように LoadXaml メソッドを呼び出すことで Page オブジェクト(内部で設定している ConentPage オブジェクトなど)を受け取ることができます。ここでは Navigation を使って PushAsync メソッドで画面遷移をさせます。これにより、ボタンを押すごとに XAML データのリロードができます。

ホスト名を書き換える。通常、エミュレータからは名前解決ができないと思うので、IP アドレスを直書きしています。このあたりは、別途設定ファイルから読み込ませる予定です。

public MainPage()
{
    InitializeComponent();

    this.Port = 10150;
    // TODO: Change IP address of XFormsPreviewHost.exe on machine.
    // XAML ファイルをホスティングしている PC の IP アドレスを書く。
    // エミュレータから名前解決ができれば、マシン名でもOK.
    this.Hostname = "172.16.0.9";
}

imageimage

一度作ったプレビューアは何度も再利用ができます。/get/item コマンドだけ変更しなければ、簡易ホストのほうで、自由に応答 XAML ファイルを変更できます。

Visual Studio のほうでは、XAML のインテリセンスが効かないので、Xamarin Studio で XAML ファイルを編集します。属性などをコード補間してくれるので、多少は間違いがすくないかも。

image

■Type Provider 風に提供

XFormsPreviewer/XFormsProvider/PageXaml.fs at master ・ moonmile/XFormsPreviewer
https://github.com/moonmile/XFormsPreviewer/blob/master/XFormsProvider/PageXaml.fs

パーサ自体は F# で書いています。PCL の場合 FSharp.Core の競合があって懸念点が多いのですが、各種のワーニングは無視です。警告がいやだったので、最初は C# で書いていたのですが、今後のメンテを考えると F# のほうが良いかなと(単なる趣味…かもしれません)。

すごい大ざっぱに書いているので、タグ名とプロパティ名の対応を取ってない(DTDを考慮していない…というか、そもそも Xamarin.Forms の XAML に DTD が用意されていないのが問題では?…とも思ったけど、MS製XAML http://schemas.microsoft.com/winfx/2006/xaml/presentation のほうの記述もないので、そういうものかもしれない)のですが、まあツナギなので動けばよいという考えて作っています。が、どうせならば、F# の Type Provider と互換をとりたいですよね。FsXaml な感じで、WPF 製 XAML と互換を取るぐらいまでは作る予定です。最終的には、Windows Store 製 XAML や Android の axml, Xcode の storyboard の互換までいきたい。

■できないこと

本格的にデザイナ/プレビューアとして使うには、足りない機能が満載なのですが、

  • static resource の参照ができない。
  • 外部 UI モジュールを呼び出せない。
  • x:Name を参照できない。
  • ICommand が動かない。
  • MVVM の Binding が動かない。
  • デザイン時の Data Binding がないので、ListView が表示できない。

の項目ができません。このあたりは、おいおい実装していく予定です。これを実装すると、動的に XAML をロードするだけで、本体に View がいらないという状態になって、本格的に MVVM での View と ViewModel の疎結合が実現できそうです。

カテゴリー: F#, Xamarin | Xamarin.Forms 用のプレビューアをアルファ版で公開 はコメントを受け付けていません

Xamarin.Formsへ動的にXAMLデータをロードする…の手始め

第1回 Xamarin User Group は無事終了。プレゼン資料については後ほど解説を入れるとして、なにかと気になったので、ちょっと手を付けてみました。

Xamarin.Forms を使うとき、Phone の場合は画面が狭いので手打ちで XAML を書いてもなんとかなるのですが、タブレットのように広い画面になった時や、Xcode の autolayout みたいな感じでレイアウトしようとすると手打ち XAML だけだと結構大変です。実行しないとうまくいかないし、懇親会でもレイアウトに苦労している場面もあったりして、

  • エミュレータで実行してみないと、タグの typo が分からない。
  • エミュレータで実行してみないと、レイアウト崩れがわからない。

のは、いかんともしがたいところ。Xamarin.Forms 用のデザイナが出ればいいんですが、暫くは出る様子もないので、なんとかならんかなと。そこに、「プレビューでもあれば便利なのに」とのツイートを見つけました。

なるほど、デザイン自体は大変そうだけど、プレビューだけならなんとかなりそうです。

  1. Xamarin.Froms の XAML を手打ちする。
  2. 更新されたファイルを読み取って、デザイナに描画する。

程度の繰り返しができればよいわけで、そのサイクルが早く回れば try & error も進みそう。で、なにか方法はないかと思って、ひとつ思いついたのが、

  1. デザイン用の XAML を手打ちする。
  2. なんらかの形で iOS/Android のエミュレータにアップ。あるいは、エミュレータからプル。
  3. なんらかの形で、エミュレータ上で XAML を解析して、レイアウトを作成する。

というパターンです。プレビュー自体をエミュレータ内で動かしてしまえばよいのです。

image

幸いにして、Xamarin.Forms の場合は、プラットフォーム共通のレイアウトページとして Xamarin.Forms の ContentPage が用意されています。大抵は PCL で作るんですが、この new ContentPage したものを、iOS/Android のプラットフォーム毎に再構成しています。ならば、この new ContentPage のところをすり替えてしまえば、良いわけで実際、XAML のほうではなくて、コードでちまちまと ContentPage を作っているところを、XAML から作り込めるようにすれば良いわけです。

namespace XFormsPreview
{
    public partial class MainPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        void OnClickPreview(object sender, EventArgs e)
        {
            // var page = new NewPage();
            string xml = @"<?xml version='1.0' encoding='utf-8' ?>
<Contentpage x:class="XFormsPreview.NewPage" 
xmlns:x=http://schemas.microsoft.com/winfx/2009/xaml
xmlns="http://xamarin.com/schemas/2014/forms"> <Stacklayout> <Label Text="New Page"> <Button Text="click me"> </Stacklayout> </Contentpage> "; var page = ContentPageXaml.LoadXaml(xml); this.Navigation.PushAsync(page); } } }

こんな風に文字列で XAML を設定して LoadXml すると ContentPage ができるようにします。ここは F# の Type Provider と似た感じですね。

まだテスト中なので、XAML の文字を埋め込んでいますが、これをエミュレータの動いている PC から XAML ファイルをダウンロードできるようにすれば、XAML の修正とプレビューのサイクルが早くなります。大まかの動きの目途は立ったので、あとはちまちまと Xmarin.Forms のコントロールを移し替えていきます。車輪の再発明ような感じもするし、既にあるような気もするし、本格的なデザイナが出るまでの短い間という訳でもあるけど、ひとまず、一通り動くところまで作ってみる予定。

カテゴリー: C#, Xamarin | Xamarin.Formsへ動的にXAMLデータをロードする…の手始め はコメントを受け付けていません

Xamarin.iOS/Android から F# PCL プロジェクトを使う方法

ちょっとトリッキーなので、後から Xamarin Studio 側が修正するような気もしますが、現状の回避策として。

■Xamarin.Forms を F# で使う

Xamarin.Forms は、PCL版とShared版があります。C# では “Mobile Apps” から どちらかのプロジェクトを選びますが、F# のプロジェクトにはこれがありません。じゃあ、F# では作れないのかというとそうではなくて、単純に、

– F# で PCL プロジェクトを作成する
– F# で iOS/Android プロジェクトを作成する
– iOS/Android プロジェクトから PCL プロジェクトを参照させる

という手順で Ok なハズです。PCL プロジェクト自体は、Xamarin Studio では作れないので、Visual Studio のほうで作りますが、後で述べるようにどっち作っても構わなそうです。
それぞれのプロジェクトに NuGet から Xamarin.Formss を入れてビルドが通る状態にしておきます。

iOS/Android プロジェクトから、PLC プロジェクトを参照設定したいところですが、参照設定ができません。この現象は、Visual Studio でも同じで…というか、Visual Studio では F# の iOS/Android プロジェクトが開けないので、Xamarin Studio 上でしかできません。

ひとつの方法としては、

F#のPCLプロジェクトをC#から参照するときにunable to add a reference to project… – omanuke-ekunamoの日記
http://d.hatena.ne.jp/omanuke-ekunamo/20140108/1389171418

のように、アセンブリの方を参照します。が、Xamarin.Forms の場合は、PCL 自体にロジックが入ることが多く、デバッグが結構手間なので、プロジェクト参照をさせます。

ちなみに、
– F# で作った PCL を C# プロジェクトから参照する。
– C# で作った PCL を F# プロジェクトから参照する。

ということは可能なのですが、なぜか F# から F# PCL だけが参照できません。

■ *.fsproj を開いて直接編集する

参照元(iOS/Android プロジェクト)の *.fsproj を開いて以下を追加します。

  <ItemGroup>
    <ProjectReference Include=&quot;../../WebBrickClientFs/WebBrickClientFs/WebBrickClientFs.fsproj&quot;>
      <Project>571E0A3B-E069-4B46-BBAC-B0F060351904</Project>
      <Name>WebBrickClientFs</Name>
    </ProjectReference>
  </ItemGroup>

ProjectReference に参照先のプロジェクト(PCLプロジェクト)fsprojを設定して、Project にプロジェクトGUIDを設定します。この部分は、C#からF# PCLを参照したときと同じものです。

そうして、Xamarin Studio で開くと、警告が出た状態ではありますが、参照ができるようになります。この状態でビルドも通ります。

■ビルドの警告はなぜでるのか?

ビルドをすると、こんな風な警告が出ます。

C:/Program Files (x86)/MSBuild/12.0/bin/Microsoft.Common.CurrentVersion.targets(5,5): Warning MSB3277: 同じ依存アセンブリの異なるバージョン間で、解決できない競合が見つかりました。 これらの参照上の競合は、ログの詳細度が詳細に設定されている場合にビルド ログにリストされます。 (MSB3277) (WebBrickClientFs.Android)

これは、それぞれのプロジェクトに入っている FSharp.Core のバージョンが異なるからなんですね。

Visual Stuido で作った F# PCL では、Reference Assemblies/Microsoft/FSharp/.NETCore/$(TargetFSharpCoreVersion)/FSharp.Core.dll を参照していますが、Android のほうでは、Reference Assemblies/Microsoft/Framework/MonoAndroid/v1.0/FSharp.Core.dll を参照しています。
.NETCoreのほうは、3.3.1 なのですが、MonoAndroid のほうは 2.3.1 なんですよね。これがずれてしまっているようです。ちなみに MonoTouch にあるのは 2.9.9 なのでこれもバージョンが違っています。おそらく、このバージョンが揃わない限り、先の警告が出てしまうようです。

■バージョンを揃えることができるのか?

試しに Xamarin.Forms.Core のバージョンを見ると PCL/Android/iOS ともに 1.1.1.0 になっています。おそらく、これを揃えればいいのでしょうが、現状の配布状況(Nuget にもいくつかの Fsharp.Core があります)をみると、揃えるのは難しそうですね。自前でビルドをすると、バージョンが揃うのかなと。このあたりは別途調べてみます。せめて、F# 3.0 なのか F# 3.1 なのかがわかるといいんだけど。

カテゴリー: F#, Xamarin | Xamarin.iOS/Android から F# PCL プロジェクトを使う方法 はコメントを受け付けていません

BrickPi + LOGO Mindstorms EV3 をゲームコントローラーでコントロールする(後編)

手っ取り早く動かすには、先の あしがらがらくた研究所 by いしわたむねお さんのところから、RPi_DualShock3.c を取得してビルドをします。動作確認ができたら、BrickPiNet をインストールして試します。.NET版なので、apt-get install Mono をしないとダメなんですよね…というか、RasPi のレポジトリに 4.5 の Mono が入っていたか確認してないので、これはあとで確認。手元のものは 12時間かけてビルドをしたものなので、ちょっとお試しという訳にはいかないので。そのうち、パッケージを作りましょう(つーか、パッケージの作り方から学ばないとダメなんだけど)。

■C# からジョイススティックを操作する

C言語でIOCTLしないと駄目かと思ったんですが、Linuxでは /dev/input/js0 にアクセスさえすれば、ボタンやジョイスティックの状態が取れることを知りました linux/joystick.h に詳しい情報があります。
このデバイスは、そのまま C# のファイルストリームでも使えるので、こんな感じに書けます。

Raspberry Pi Series ? Mono C# USB Joystick Handlermpolaczyk | software, robotics, engineering & geeky stuff… | mpolaczyk | software, robotics, engineering & geeky stuff…
http://mpolaczyk.pl/raspberry-pi-mono-c-joystick-handler/
C# で joystick を利用する
https://gist.github.com/moonmile/7c44e5130617c7a76a87

自前のほうは、Dualshock3 専用になっています。どうやら各社のジョイスティック/ゲームパッド事に番号が異なっていて、実地で検証しないとだめなそうです。アプリケーションのほうが対応するというパターンらしいです。

public struct js_event
{
    public UInt32 time;    /* event timestamp in milliseconds */
    public Int16 value;    /* value */
    public byte type;      /* event type */
    public byte number;    /* axis/button number */
    public override string ToString()
    {
        return string.Format("time:{0} val:{1} type:{2} num:{3}", time, value, type, number);
    }
}

type でボタン(0x01)かジョイスティック(0x02)をチェックして、number でボタンの位置を調べます。ジョイステックの場合は0がニュートラル(中央)で、スティックを倒すとそれぞれ +/- に振れます。
ここでは、まだ作っていないのですが、Dualshuck3 ではコントローラ自体の傾き検知が入っています。23,24,25,26番にデータが入って来るのですが、どの軸なんだろう、ってのは後で調べましょう。

public bool SELECT { get { return Button0; } }
public bool LeftStick { get { return Button1; } }
public bool RightStick { get { return Button2; } }
public bool START { get { return Button3; } }
public bool Up { get { return Button4; } }
public bool Right { get { return Button5; } }
public bool Down { get { return Button6; } }
public bool Left { get { return Button7; } }
public bool L2 { get { return Button8; } }
public bool R2 { get { return Button9; } }
public bool L1 { get { return Button10; } }
public bool R1 { get { return Button11; } }
public bool Triangle { get { return Button12; } }
public bool Circle { get { return Button13; } }
public bool Cross { get { return Button14; } }
public bool Square { get { return Button15; } }

public int LeftAxisX { get { return Axis0; } }
public int LeftAxisY { get { return Axis1; } }
public int RightAxisX { get { return Axis2; } }
public int RightAxisY { get { return Axis3; } }

■BrickPiNet.dll を使う

そんな訳で、BrickPiNet https://github.com/moonmile/BrickPiNet を作成中です。

/dev/input/js0 を read して、ちまちまと調べていけばジョイスティックの操作はできるのですが、ボタンを離したタイミングとか、同時押しの状態とかが面倒ですよね。
そのあたり、状態が変かしたときにイベントを発生するのが、BPiJoystick です。

using System;
using Moonmile.BrickPiNet;

namespace Sample
{
    class simplebot_joystick
    {
        BPiMotor motor1, motor2;
        BPiJoystick js;

        public void main()
        {
            BPi.Setup();
            BPi.AutoUpdate = true;
            this.motor1 = new BPiMotor() { Port = BrickPi.PORT_B, Enabled = true };
            this.motor2 = new BPiMotor() { Port = BrickPi.PORT_C, Enabled = true };

            js = new BPiJoystick();
            js.OnJoystickChanged += js_OnJoystickChanged;
            js.Setup();

            BPi.Timeout = 5000;
            this.Go();
        }

        void js_OnJoystickChanged(object sender, JoystickEventArgs e)
        {
            int ly = e.Joystick.LeftAxisY;
            int ry = e.Joystick.RightAxisY;
            Console.WriteLine("joystick {0} {1}", ly, ry);
	    int sp1 = ly/(32767/200);
	    int sp2 = ry/(32767/200);
	    move_bot( sp1, sp2 );

        }

        void move_bot(int sp1, int sp2)
        {
            this.motor1.Speed = sp1;
            this.motor2.Speed = sp2;
        }
        void Go()
        {
            var k = Console.ReadKey();
        }
    }
}

BPiJoystick を new して、OnJoystickChanged を登録すれば、ジョイスティックが変化したときだけイベントが発生します。現在のところ、ジョイスティックとボタンの状態変化が一緒になって飛んでくるので、ボタンだけ使いたいときに不便なのですが…まあ、ひとまず、これでジョイスティックを使ったロボットが簡単に作れます。

■さらに、もうひとつのモーターを使うために

これは次の課題ですが、BrickPi/LEGO mindstorms EV3 には、モーター用のポートが4つついています。現在、車輪用に2つ使っていますが、他にもモーターを使えばクレーン用とかに使えます。これはあとで実験してみましょう。他に接触センサーとかカラーセンサーもついているので、それも確認して実装していきます。

また、GPIO を直接操作することでサーボも動かせます。このあたりは、wiringPi http://wiringpi.com/ を利用して BrickPiNet から制御できるようにしていきます。

カテゴリー: RaspberryPi | BrickPi + LOGO Mindstorms EV3 をゲームコントローラーでコントロールする(後編) はコメントを受け付けていません

BrickPi + LOGO Mindstorms EV3 をゲームコントローラーでコントロールする(前編)

Raspberry Pi + BrickPi + LOGO Mindstorms EV3 をジョイスティックコントローラで遠隔制御します。まあ、単純に Raspberry Pi に Bluethooth を接続して、これを Dualshock3 で使えるようにするだけなのですが。

image

工作と小物のがらくた部屋: Raspberry Pi で Dualshock3 (Bluetooth接続ゲームコントローラ)
http://junkroom2cyberrobotics.blogspot.jp/2013/03/raspberry-pi-dualshock3-bluetooth.html
RapiroをPS3のDUALSHOCK3で動かしてみる | Makuake(マクアケ)NOTE クラウドファンディングブログ
http://www.makuake.com/blog/crowdfunding/rapiro-dualshock/

USB 接続すれば簡単なのですが、遠隔操作をするとなると2つの方法があります。Bluetooth を使う方法と WiFi を使う方法ですね。ワイヤレスコントローラ (DUALSHOCK3) は 4千円ちょっとと結構な値段がするのですが(RasPi 以上という噂もw)、まあ初回の実験だし確実につなげておきたいのでこれにしました。SBDBTのPSコントローラ用ファーム更新!凄いですよ!いろいろなゲームパッドで無線・有線による操作が可能になりました: ROBOMIC(ブログ) を見ると SBDBT というのを使えば、普通のコントローラも無線化できそうなのでが、これは今度の課題で。

Bluethooth での接続手順は、先の2つのブログに書いてあるのですが、自分用に書き写しておきます。Bluetooth は PLANEX Bluetooth USBアダプター Ver.4.0+EDR/LE(省エネ設計)対応 BT-Micro4 を使っています。最初は他の人が RasPi で動作確認できたものを使うのが無難ですね。たぶん、大抵のものは動くとは思うのですが、無線LAN で失敗したので、先のブログの人が使っているものと同じものを購入しました。

■Bluethooth の準備

apt-get で bluethooth パッケージをインストールします。

sudo apt-get install bluetooth bluez-utils bluez-compat bluez-hcidump
sudo apt-get install libusb-dev libbluetooth-dev

うまく認識ができていると

/etc/init.d/bluetooth status

で確認ができます。

image

usb の状態は lsusb で確認が可能です。

lsusb 

image

Bus 001 Device 005: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode) ってのが、Bluetooth の USB ドングルです。 Bus 001 Device 004 のほうは WiFi の USB ドングルです。ゲームコントローラで遠隔制御はするのですが、ログ出力は初期制御のために WiFi をつなげています。電流的にどうなんだろうなぁ、という心配もありますが。一応、問題なく動いています。

■QtSixA のインストール

QtSixA is the Sixaxis Joystick Manager をインストールします。Sixaxis/DualShock3 を Linux に割り当てられるそうなので、RasPi でも OK です。SourceForge からダウンロードするのですが、http://sourceforge.net/projects/qtsixa/files/QtSixA%201.5.1/ にある QtSixA-1.5.1-src.tar.gz が最新です(といえ、2011年10月に開発が止まっていますが)。

tar -xvf QtSixA-1.5.1-src.tar.gz
cd QtSixA-1.5.1/sixad
make
sudo make install

すると、Dualshock3 を受け入れる sixad ってのがビルド&インストールされます。QtSixA-* 直下で make をすると pyuic4 コマンドがないというエラーが出るので、sixad フォルダでビルドします。Raspberry Pi 用の Debian に qgis, python-qgis がないからみたいですね。これは特に不要で、sixad とペアリング用の sixpair のみが必要です。

cd QtSixA-1.5.1/utils
make
sudo make install

■Dualshock3 とペアリングする

Daulshock3 と Raspberry Pi を USB コードで接続して、sixpair を動かします。

sudo sixpair

image

うまくいくと Bluethooth の MAC IDが取れます(たぶん USB のほう)。初回だけ USB ケーブルをつないで、あとからは Dualshock3 コントローラの「PS」ボタンでペアリングができます。

■動作させる

sixad で bluetooth の待ち受けをして、Dualshock3 で PS ボタンを押します。

sixad -start

image

(たぶん)Dualshock3 の MAC が表示されて接続状態になります。

動作確認自体は、/dev/input/js0 を表示させることで、確認ができます。

sudo cat /dev/input/js0

image

ボタンを押したり、ジョイスティックを動かしたりすると信号が流れていくのがわかります。

動作確認自体は、意外に簡単! LinuxでジョイスティックをC++から使う方法 – akihiko’s tech note からサンプルコードをビルドして動かしています。/dev/input/js0 をオープンして中身を表示させます。

image

■sixad の終了

sixad 自体は Ctrl+C で終わらせるのですが、そのままでは bluetooth が解放されないようです。

image

sixad -stop で終了させると、Bluetooth が解放されます。

ただし、cat /dev/input/js0 を見ると、いつまでもデータが送られてくる(Dualshock3を傾けるとデータが入っているのがわかる)のでペアリング自体は続いてしまっているようです。仕方がないので、Bluetooth の USB ドングルを抜いて止めるのですが、なんかいい方法はないですかね?

で、お次は、/dev/input/js0 を読み込んで、EV3 のモーターを動かすプログラムを書きます。

カテゴリー: RaspberryPi | BrickPi + LOGO Mindstorms EV3 をゲームコントローラーでコントロールする(前編) はコメントを受け付けていません

QEMU で Raspberry Pi をエミュレートする

Raspberry Pi は CPU が ARM なので、クロスコンパイラ環境が必須なのですが結構手間なので、エミュレータ環境を使って、その中で Mono や OpenCV をビルドすると良い。

QEMU – Emulating Raspberry Pi the easy way (Linux or Windows!)
http://xecdesign.com/qemu-emulating-raspberry-pi-the-easy-way/

そんな訳で、上記からエミュレータをダウンロードして解凍すると、あっさりと動きます。「QEMU Raspberry Pi」で検索するとぼろぼろと出てくるので、これが定番だったんだ。

■初期環境を作る

Raspberry PiをQEMUでエミュレートする方法 | 意識低い開発者のBlog
http://blog.ymyzk.com/2013/12/raspberry-pi-qemu/
Raspberry Pi のイメージファイルを拡張する | 意識低い開発者のBlog
http://blog.ymyzk.com/2013/12/raspbian-image-resize/

にある手順に従って、

  • /etc/ld.so.preload を開いて #/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so のようにコメントアウトする。
qemu-system-arm -kernel kernel-qemu ^
    -cpu arm1176 -m 256 -M versatilepb -no-reboot ^
    -serial stdio ^
    -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw init=/bin/bash" ^
    -hda D:isoRaspberryPi2014-06-20-wheezy-raspbian.img
vi /etc/ld.so.preload
  • RasPi のイメージファイルが 2GB 程度なので +16G 程度追加しておく。
qemu-img resize 2014-06-20-wheezy-raspbian.img +16GB

こうすると、ひとまず QEMU 上で Raspberry Pi が動作します。

■ネットワークを構築する

単純にコンパイルするだけならば上記の設定で十分なのですが、今回は apt-get をして Mono や OpenCV の環境を構築するので、外部ネットワークの接続が必須です。これは、TAP 型で外部接続させます。

オープンギャラリー:QEMUゲスト間ネットワーク接続環境
http://www.os-museum.com/qemutapnet/qemutapnet.htm

Windows環境の場合は、

OpenVPN をインストール http://www.openvpn.jp/

  • ネットワーク接続の名前を「TAP32」のように設定しやすいものに変更。
  • 「ネットワークと共有センター」→「アダプターの設定の変更」で「ブリッジ接続する」
    私の環境では Hyper-V が入っているため vEthernet とブリッジしていますが、普通はイーサネット(PCが接続しているLANポート)を使うと思われます。

image

起動時に -net tap.iframe=TAP32 の様に設定

qemu-system-arm -kernel kernel-qemu ^
	-cpu arm1176 -m 1012 -M versatilepb -no-reboot ^
	-serial stdio ^
	-append "root=/dev/sda2 panic=1 rootfstype=ext4 rw" ^
	-net nic -net tap,ifname=TAP32 ^
	-hda 2014-06-20-wheezy-raspbian.img

■制限

  • メモリが -m 256 で 256MB しか設定できません。それ以上を設定しても無視される。QEMU の制限かも
  • OpenVPN の接続スピードが 10Mpbs になっている。今となっては相当遅いので、何か別のローカルネットワークの手段を考えたほうがいいかも(方法が分からん)。git などからソースダウンロードすると非常に遅いので、別途 Debian でダウンロードしたものをコピーすると良い。
  • 実行速度は、実機とほとんど変わらない。Mono をビルドすると10時間程度かかりました。まあ、ビルドしている間に実機がふさがれるのを防ぐのとバックアップがとりやすい(*.img ファイルそのまま保存すればOK)ので、これはこれで使い勝手がよいかと。
カテゴリー: RaspberryPi | 2件のコメント