vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Xamarin.FormsでXAMLと仲良くしたい話 その1

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の14日目の記事です。

qiita.com

とりあえず起動はするし、ボタンを押すとHello Worldと出力するとか、
簡単な動きくらいは作れる気がしてきました。

しかしながら、見た目がどうにもなのは悲しい。

ということで、今回は UI の部分について調べてみることにしました。

なお、 Xamarin.Forms ではボタンなどをコードで追加することもできますが、
今回は XAML を中心に進めてみます。

デフォルトで生成されるXAMLを見てみる

まず Blank App でプロジェクトを作成して、デフォルトの XAML を見てみます。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSample"
             x:Class="XamlSample.MainPage">

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

< /ContentPage>
  • 「xmlns:local="clr-namespace:XamlSample"」の行は、(少なくとも)デフォルトでは使用していないようで、
    削除してしまっても特にエラーは起きませんでした。
  • この記事を書く少し前のバージョンまで Label は ContentPage.Content と StackLayout の中に入れられていましたが、
    仕様の変更があったようです。( PCL から .Net Standard に変更したことには関係がなさそうなので)

ContentPage に対して コードの表示 を行うと、 コードビハインド (MainPage.xaml.cs) が開きます。

要は XAML ファイルのルート要素とコードビハインドのクラスとが紐づいているので( x:Class="XamlSample.MainPage" で指定)、
コードから XAML 要素にアクセスすることが可能になるのですね。

Xamarin.Forms で使用できる Layout は下記の通り。

ボタンなどのコントロール類は、 WPF などと(多分)ほぼ同じです。
自動で各プラットフォームらしい見た目にしてくれるのはすごいですね!

xmlnsとxmlns:xについて

「xmlns="http://xamarin.com/schemas/2014/forms"」で XAML の使用を宣言しています。

「xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"」で「x:Class」や「x:Name」などのプロパティが使用可能になります。

x は変更可能で、「xmlns:x= ~ 」を「xmlns:xx= ~ 」のように変更すると「xx:Class」と書くことができるようになります。

また xmlns:x では Name など XML でも定義されているプロパティがありますが、
これはそのプロパティをどの要素に対しても使用可能にするために定義されているようです。

ContentPage直下のLayout

適当にいじっていた時におや?と思ったところですが、 ContentPage 直下におけるLayoutは一つだけのようです。
そのため、下記のように書いてしまうと、上に書いた StackLayout の内容が表示されませんでした。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < StackLayout>
        < Label Text="Welcome to Xamarin.Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />
    < /StackLayout>
    < AbsoluteLayout>
            < BoxView WidthRequest="100" HeightRequest="100" BackgroundColor="YellowGreen"/>
    < /AbsoluteLayout>
< /ContentPage>

コードで要素を追加する

例えば Layout 内の要素は決まっていて、中のデータだけが変化する場合は DataBinding を使うのが良さそうです。

ただ、条件によって要素ごと差し替えたい場合だと難しそうです。

ということで、コードから要素を追加してみます。

追加対象の親要素を取得する

まずは要素を追加する対象となる、親の要素を取得してみます。

前述の通り、 XAML ファイルの要素とコードビハインドのクラスは紐付けられており、
コードビハインドで this.Content とすれば、 XAML ファイルの ContentPage.Content にアクセスできます。

ということで、あとは対象の要素を指定して、そこに子となる要素を追加するだけです。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.MainPage">
    < StackLayout x:Name="Outline">
        < Label Text="Welcome to Xamarin.Forms!"
            VerticalOptions="CenterAndExpand" 
            HorizontalOptions="CenterAndExpand" />
    < /StackLayout>
< /ContentPage>

※2017.12.14 21:30更新

親要素を取得するのに FindByName を使用していましたが、
XAML で 親要素に x:Name で名前を付けている場合、
そのまま x:Name でつけた名前でコードビハインドでもアクセスできるそうです。

処理も減ってシンプルになりますね(*'▽')

田淵さん、ありがとうございますm(__)m

Before

// 親要素となる StackLayout.
var layout = this.FindByName< StackLayout>("Outline");
layout.Children.Add(addContent);

After

// 親要素となる StackLayout.
Outline.Children.Add(addContent);

MainPage.xaml.cs

using Xamarin.Forms;

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

            // 追加する要素.
            var addContent = new Label
            {
                Text = "Label from code",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };
            // 親要素となる StackLayoutに要素を追加.
            Outline.Children.Add(addContent);
        }
    }
}
  • これで「Welcome to Xamarin.Forms!」というラベルの下に、
    「Label from code」というラベルが追加されます。

レイアウトしてみる StackLayout

それでは、いくつか Layout を試してみます。

StackLayout

  • 縦方向または横方向に要素を並べます (Orientation で切り替え)。
  • 何も指定しない場合、縦方向に並べる場合は横幅いっぱい、横方向の場合は縦幅いっぱいに要素が表示されます。
  • StackLayout だけを使って縦横組み合わせたい場合は、 StackLayout を入れ子構造にします。
  • ただし縦方向に並べる StackLayout と横方向に並べる StackLayout を同階層に置いてしまうとエラーになります。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < StackLayout BackgroundColor="Azure" Orientation="Vertical" VerticalOptions="Start">
        < StackLayout Orientation="Horizontal" Spacing="0">
            < BoxView BackgroundColor="Yellow" WidthRequest="100" />
            < StackLayout Orientation="Vertical" BackgroundColor="Brown" HorizontalOptions="FillAndExpand">
                < Label Text="世界さん" TextColor="White"/>
                < Label Text="ちーっす!" TextColor="White" />
            < /StackLayout>
        < /StackLayout>
        < StackLayout Orientation="Horizontal" Spacing="0">
            < BoxView BackgroundColor="Yellow" WidthRequest="100" />
            < StackLayout Orientation="Vertical" BackgroundColor="Brown" HorizontalOptions="FillAndExpand" Spacing="0">
                < Label Text="Hello" TextColor="White" Margin="3, 3, 3, 3"/>
                < Label Text="World!" TextColor="White" Margin="3" />
            < /StackLayout>
        < /StackLayout>
    < /StackLayout>
< /ContentPage>
  • 要素同士はデフォルトでスペースが含まれるので、なくしたい場合は Spacing を "0" にする必要があります。

結果はこんな感じになります。

f:id:mslGt:20171214000906j:plain

レイアウトしてみる GridView

次は格子状にレイアウトできる GridView を試してみます。

GridView では行数・列数とサイズを指定し、要素と配置する場所を指定します。

< ?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="XamlSample.View.MainPage">
    < Grid VerticalOptions="FillAndExpand">
        
        < !-- 行の指定. 今回は一番下の行が30固定、残りが半分ずつ使うことになります -->
        < Grid.RowDefinitions >
            < RowDefinition Height="1*"/>
            < RowDefinition Height="Auto"/>
            < RowDefinition Height="30"/>
        < /Grid.RowDefinitions >
        < !-- 列の指定. Widthを指定していないので3分割 -->
        < Grid.ColumnDefinitions>
            < ColumnDefinition />
            < ColumnDefinition />
        < /Grid.ColumnDefinitions>
        < !-- StackLayoutを入れたり-->
        < StackLayout VerticalOptions="FillAndExpand"
                     HorizontalOptions="FillAndExpand"
                     BackgroundColor="Yellow">
            < Label Text="世界さんちーっす" />
        < /StackLayout>
        < !-- GridViewを入れたり-->
        < Grid VerticalOptions="FillAndExpand"
                  Grid.Row="0"
                  Grid.Column="1" >
            < Grid.RowDefinitions >
                < RowDefinition Height="Auto"/>
                < RowDefinition Height="Auto"/>
            < /Grid.RowDefinitions >
            < Grid.ColumnDefinitions>
                < ColumnDefinition />
                < ColumnDefinition />
            < /Grid.ColumnDefinitions>
            < BoxView Color="Aqua" 
                     Grid.Row="0"
                     Grid.Column="0" />
            < BoxView Color="Maroon" 
                     Grid.Row="1"
                     Grid.Column="0" />
            < BoxView Color="Navy" 
                     Grid.Row="1"
                     Grid.Column="1" />
            < BoxView Color="Silver" 
                     Grid.Row="0"
                     Grid.Column="1" />
        < /Grid>
        < BoxView Color="Maroon" 
                 Grid.Row="1"
                 Grid.Column="0" />
        < BoxView Color="Navy" 
                 Grid.Row="1"
                 Grid.Column="1" />
        < BoxView Color="Purple" 
                 Grid.Row="2"
                 Grid.Column="0" />
        < BoxView Color="Lime" 
                 Grid.Row="2"
                 Grid.Column="1" />
    < /Grid>  
< /ContentPage>
  • Grid.Row 、Grid.Column を指定しない場合、デフォルトで Grid.Row: 0 、Grid.Column: 0 が設定されるため、
    複数あると最初の要素が隠れてしまいます。

上記のコードを実行するとこのようになります。

f:id:mslGt:20171214001007j:plain

複雑なレイアウトを行う場合は、行番号・列番号の指定がややこしそうですね。。。

BoxViewについて

これまでも登場している BoxView ですが、これは四角形を表示するものです。

GridView などと同じように、中に Label などの要素を入れられるのかな?と思いきやエラーになります。

エラーになる

< BoxView Color="Silver">
    < Label Text="世界さん、ちーっす" />
< /BoxView>

なのであくまでも四角形を表示するためのもの、ということのようです。

装飾などに利用するのでしょうか。

おわりに

とりあえず StackLayout と GridView 、 BoxView に触れてみましたが、
まだ「なんでこんな大きさになるの。。。(´・ω・`)」となることも多いです。

まぁこういうのは慣れが重要だと思いますので、実際にアプリを作りながら慣れていきたいと思います。

次回は入りきらなかった RelativeLayout の予定。。。のはず。

参照

Xamarin.Forms

XAML

iPhoneでAndroid Wearと連携してみたメモ

はじめに

いきなりですが、メイン端末を iPhone にしました。

とはいってもまだ新しいものを買ったわけではなく、以前使っていた iPhone5s です。

iPhone にしようかなと思った理由は Google 先生が日本で Pixel2 が出てくれないし・・・というのもありますが、
iPhoneX で Prime sense の技術がとかいう話が、今更ながら気になってきたためです。

ただ、今まで Android 端末で過ごしてきただけに、
急に切り替えるとあれもできないこれもできない/(^o^)\とならないかと心配になったので、
とりあえず iPhone5s で試してみることにしました。

で、特に気になっていた Android Wear (Zenwatch2) が使えるのか問題を試してみたので、メモっておきます。

Zenwatch2との連携

Zenwatch2 には限りませんが、 Android Wear との連携は Android Wear アプリを使うことで可能です。

ただ、Android でやるように、ただ Bluetooth を On にすれば良いのではなく、
アプリを立ち上げて明示的に連携してやる必要があります。

Appleのアプリとの連携

それではまず iPhone に標準で入っているアプリとの連携について。

電話

電話がかかってきたことが表示され、受ける・または切ることができます。

SMS

メッセージをみることはできますが、返信をすることはできません。

Music、Podcast

曲名が表示され、再生・停止・スキップを行うことができます。

Clock

アラームが鳴っても通知されず、止めることもできません。

他のアプリとの連携

基本的にプッシュ通知は受けることができ、内容を見ることができます。

Hangout

テキストメッセージを見ることはできますが、画像のプレビューはできません。

Google Home(というか OK google)

反応しませんでした。

これができれば iOS アプリの Cortana と合わせて混とんとした感じになって面白かったのですが。。。
(どんな願望や。。)

Google Fit、Zenfit

iOS アプリがないので無理。。。かと思いきや、
実はこれらのアプリは Watch 単体で動作するため記録を取ることができます。
(GPS 情報は iPhone から取得)

iPhone は標準でトラッキングアプリが入っていないようだったのでどうしようかなと思っていたのですが、
これは助かりました。

おわりに

というわけで、絶望的かなと思われた Android Wear との連携ですが、
特に Android Wear の OS が 2.0 になったこともあって結構問題ありませんでした。

ただ他はともかくアラームが Watch でコントロールできないのは、
二度寝防止に朝大量にセットしている私としては結構痛い。。。

ま、今すぐ無いと困るってわけでもないので、 Xamarin の学習がてら自作するのも良いかもなぁ~などと思ってもいます。

あと、4年も前の端末が曲がりなりにもちゃんと使えるってのはすごいですねぇ。 開発者としてはさっさと替えて!って気もしなくもないですが苦笑。

コードを書く時間を確保したい話

はじめに

この記事は 子育てエンジニア・クリエイター Advent Calendar 2017 の11日目の記事です。

adventar.org

このブログに書き残している通り、プライベートの時間を使ってあれこれコードを書いてみたり試してみています。

が、子どもが生まれ、大きくなってくるにつれて時間を確保するのが難しくなってきました。
遊んで~と言ってくることももちろんですが、パソコン自体にも興味があるようで、 特に電源がついている状態だとキーボードを触りまくったりラップトップをパカパカ開いたり閉じたりし始めます/(^o^)\

そんな中で私がどのように時間確保しているかを書き残しておきます。

本当は最新のサービスとか、すごい技術とかで時間短縮してます!とかの方が良いのでしょうが、
いたって普通の方法に終止しています。期待した人残念でした。

子どもを寝かしつけたあとにする

前まではこの方法でした。

子どもが寝た後ならゆっくり時間もとれるし( ´∀`)bグッ!

と思っていたのですが、高確率で寝落ちしちゃうんですよね。。。orz

また、寝たばかりだとまだ眠りが浅く、結局起きてしまうことも。。。

ということで、(たまにはやりますが)基本的には夜遅くにやる、というのは諦めました。

早起きする

じゃあ逆に早起きすりゃいいんじゃね?というのが現在の方法です。

夜は子どもと一緒にさっさと寝る。

で、朝5時ごろに起きて、洗濯物を洗ったり出発の準備をしつつコードを書く、と。

最近は布団のやつがなかなか私を放してくれないので、6時ごろになってきてしまっていますが。。。

これだと出発の時間が決まっているので、ある程度集中してコードを書くことができる(気がする)
というメリットもあります。

大学卒業くらいまでは夜中まで起きてて昼まで寝てるみたいな生活していたのに、
変わるものだなぁ、と大学生当時の自分が見たら思うかもしれませんね。

その他

その他便利だなぁ~と思っているものとしては、電子書籍ですね。
それもスマホで見られるもの。

どうしても子どもと出かけるとなれば荷物は多いわけで、
そこに大きな本を追加するのはツラい。。。

紙の本の方が見やすかったりするメリットもわかるのですが。

電子書籍アプリの中でのお気に入りとしては、 Google Play Books です。

特に良いところが2つあって、1つ目が英語などを翻訳するとき、
単語だけではなく2,3単語組み合わせた熟語も翻訳ができることです。

そういうのは翻訳アプリに任せる、というのも手ですが、行ったり来たりしなくて済むならそちらの方が助かるので。

2つ目が、クレジットカードなどを登録しなくてもマルチプラットフォームに対応してくれているところです。
外ではスマホで良くても、家などではもっと大きな画面で見たい。

Google Play Books だと Web ブラウザでも見られるので助かります。

以上ステマでしたw

あとは勉強会に行く時間がとりづらいというところで、
Podcast や Channel 9 などの動画配信にも助けられています。

日本の勉強会でも、ストリーミング配信していたり、
VR 空間上や Twitter 上で行われるものがあったりしてすごいです。

直接会ったり聴いたりできる勉強会の良さはもちろんあるのですが、
地方在住だったり自由な時間が取りづらかったりする身としては大変助かります。

あと時間確保の上記以外の方法としては、
仕事帰りにもくもくする、というのがあります。

和歌山にも少ないながらもカフェがあります。

幸か不幸か残業代は見込み時間で支払われ、残業自体も多いので1時間くらいなら妻にバレることもありません(白目)

。。。嫌なオチになってしまった(´・ω・`)

おわりに

子どもがもう少し大きくなったら、あまり私とも遊んではくれなくなるんだろうなぁ、
と思うと今一緒に遊べる時間を大事にしたい。

とは思いつつ、やっぱり自分自身の興味に任せてあれこれ試したり学んだりする時間も欲しい。

ということで、両者を上手くバランス取れるようになれたらなぁ、と思っています。

妻との今年、そして来年の話

はじめに

この記事は 妻・夫を #愛してる ITエンジニア Advent Calendar 2017 - ADVENTAR の四日目の記事です。

adventar.org

妻とのこれまでや日常生活については去年も書いたので、これまでの話はしません。

mslgt.hatenablog.com

ここ一年から来年に向けての話をだらだらと書いてみたいと思います。

子どもとの生活

やっぱり良くも悪くも子どもが生活の中心になっちゃいますね。

休日など、どこへ行くにも3人。

もしくはたまには一人でゆっくり過ごしてねってことで、私と子どもの2人でとか。

私自身はそれでも良いかな、と思うのですが、妻としては私と2人でたまには、
とか思ってたりしないかな?と少し不安には思っています。

まぁもう少し子どもが大きくなってきたらそういう時間もとれるかな?とは思っているのですが。

正直子どもがわんぱくになってきたこともあり、
恋人同士というよりは戦友、といった意識になってきているところもありますw

恋愛感情云々はともかく、お互いを助け合って問題を乗り越えていく、
という点では共通項もあるのかなと。

おわりに

もう少しあれこれ書こうかな?と思っていたはずなのですが、
実際に書き始めるとあまり出てこなかったのでいったん締めます。
(後日書き足したりするかも)

最後に妻へ。

いつも帰りが遅くなってしまったりして、
大変な仕事を任せきりにしてしまってごめん。

来年は家族がもう一人増えてさらに大変になると思うけど、
僕もできるだけ時間を取るようにするので、一緒に頑張りましょう。

愛してます。

Xamarin.FormsとMVVMに触れてみたい話

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiita の四日目の記事です。

qiita.com

前回に引き続き Xamarin.Forms に触ってみたお話ですが、今回は MVVM にも入門してみた話を中心にまとめます。

MVVMについて

Xamarin について調べると必ずと言っていいほど登場する MVVM について。

詳しくは参照サイトを見ていただくとして超おおざっぱにまとめると、
プログラムを下記の3つに分けて設計しましょう、という話ですね。

  1. View: 画面に表示する部分を担当する。ボタンクリックなどのイベントを検知して ViewModel に伝える。
  2. ViewModel: 1.View と、3.Model とをつなぎ合わせる。通常 1.View と一対の関係となる。
  3. Model: 計算処理などを行う。処理の内容によってクラス数は増減する。

  4. 1.View -> 2.ViewModel -> 3.Model の順に呼び出され、矢印の先にあるクラスのことを知ることはできるが、
    逆はできない(例: 1.View は 2.ViewModel を知っていて呼び出すことができるが、逆は×)

なるほどね。完全に理解した(わかってない)。という感じなのですが、特に気になったことが2つありました。

Viewをコントロールするものは誰か

実はこの MVVM を Unity でも試してみたのですが、その時に迷ったのがコレ。

  1. View: uGUI の Canvas
  2. ViewModel: ViewModel の機能を担当するクラス
  3. Model: Model の機能を担当するクラス

とすると、誰が 1.View の表示・非表示を切り替えるの?また、ページを表示した直後の処理って誰がするの?という話です。

よくよく見てみると、 Xamarin.Forms で View を担当するのは、 (例えば) MainPage.xaml だけではなく、
MainPage.xaml.cs がいます。

Android でみると Activity ですね。

またページ遷移を担うのはまた別のクラス(例えば NavigationPage に関連するクラス)です。

これらをすべて View としてまとめてしまって良いのか疑問はありますが、
CanvasXaml の表示・非表示やページを開いた時の処理を実行するクラスは別にある、ということですね。

状態は誰が持つのか

表示だけを担う View はともかく、 ViewModel と Model の内、誰が状態を持つのか、
というのが2つ目の疑問でした。

結論としては、 View に関連する状態は ViewModel が、 Model の各処理に関連する状態はそれぞれのクラスが持つ、
ということのようです。

考えてみれば処理の中心となるクラスが情報を持ち、それ以外のクラスへは必要な情報だけを渡す、
というシンプルな考え方と言えそうです。

INotifyPropertyChangedによる通知

さて、 2.ViewModel は 1.View を知ることはできない、と書きましたが、
例えば クリックイベント発火 -> 処理 -> 完了後に表示を切り替え としたい場合は、
View に処理が完了したことを伝えたくなります。

View で Obervable を使うなどなど方法は色々ありますが、
INotifyPropertyChanged を使う方法も便利だと思いました。

呼ぶ側 (View)

private SubPageOneViewModel viewModel;

public partial class SubPageOne : ContentPage
{
    viewModel = new SubPageOneViewModel();

    // ViewModelからの通知を購読.
    viewModel.PropertyChanged += (_, e) => {
        // 処理完了後に何かする.
    };
}
publci void OnDoSomethingButtonClicked(object sender, EventArgs e)
{
    viewModel.DoSomething();
}

呼ばれる側 (ViewModel)

private string calcResult;
public string CalcResult
{
    get => calcResult;
    set
    {
        // 値をセットするときに購読者に通知を送る.
        calcResult = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CalcResult)));
    }
}
public void DoSomething(){
    // 何か計算処理.

    // CalcResult の set が実行される.
    CalcResult = "計算結果";
}
  • 上記では CalcResult に全く同じ値を代入した場合も通知が送られるため、
    set で必要に応じて不要な通知が送られないようにします。
  • ViewModel 側では プロパティではなく変数 calcResult に値を入れることもできますが、
    その場合通知が送られないので注意が必要です。

DataBindingってみる

MVVM によるクラス同士の疎結合化を助けてくれる機能の一つに DataBinding があります。

xaml.cs クラスでボタンなどのインスタンスを持つことなく、
Model で変更された値をそのまま View (xaml) に反映したり、クリックなどのイベントを直接取得することができます。

SubPageOne.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.View.SubPageOne">
    < ContentPage.Content>
        < StackLayout BackgroundColor="#0078d7">
            < Label Text="{Binding CalcResult}" />
            < Button BackgroundColor="Yellow" Command="{Binding CalcCommand}">< /Button>
        < /StackLayout>
    < /ContentPage.Content>
< /ContentPage>
  • クリックイベントとして、 Clicked= に DataBinding を設定することはできず、
    ICommand を使ってコマンドとして登録する必要があります(コマンドについては後述)。

SubPageOne.xaml.cs

using Xamarin.Forms;
using XamarinSample.ViewModel;

namespace XamarinSample.View
{
    public partial class SubPageOne : ContentPage
    {
        public SubPageOne()
        {
            InitializeComponent();
            // ViewModel クラスを DataBinding として設定.
            BindingContext = new SubPageOneViewModel();
        }
    }
}

と、ここまでは良かったのですが。。。

失敗

最初、 DataBinding する値を下記のように書いていました。

SubPageOneViewModel.cs

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

namespace XamarinSample.ViewModel
{
    public class SubPageOneViewModel
    {
        public ICommand CalcCommand { get; private set; }
        public string CalcResult{ get; set; }
        
        public SubPageOneViewModel()
        {
            CalcResult = "Starts";
            // ボタンに Binding しているコマンド.
            CalcCommand = new Command(() =>
            {
                var subtraction = DependencyService.Get();
                CalcResult = subtraction.Calc(0, 1).ToString();
            });
            CalcResult = "Start2";
        }
    }
}

実行してみるとエラーは発生せず、ラベルには「Start2」と表示され、
ボタンを押すと CalcCommand の中の処理が実行されます。

しかし、なぜか CalcCommand の中にある「CalcResult = subtraction.Calc(0, 1).ToString();」がラベルに反映されないorz..
(なお「CalcResult = "Start2";」は実行されているのもよくわからず。。。)

Android などではイベントが別スレッドで発火するため、
下記のようにメインスレッドで実行してみては...?と思いましたが、うまくいかず。

// これでも反映されず.
CalcCommand = new Command(() =>
{
    Device.BeginInvokeOnMainThread(() =>
    {
        var subtraction = DependencyService.Get();
        CalcResult = subtraction.Calc(0, 1).ToString();
    });    
});

実は、 DataBinding で変更された値を View に反映するためには、
INotifyPropertyChanged で変更を通知する必要があるのでした。

SubPageOneViewModel.cs

using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamarinSample.ViewModel
{
    public class SubPageOneViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ICommand CalcCommand { get; private set; }

        private string calcResult;

        public string CalcResult
        {
            get => calcResult;
            set
            {
                calcResult = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CalcResult)));
            }
        }

        public SubPageOneViewModel()
        {
            CalcResult = "Starts";

            CalcCommand = new Command(() =>
            {   
                var subtraction = DependencyService.Get();
                CalcResult = subtraction.Calc(0, 1).ToString();
            });

            CalcResult = "Start2";
        }
    }
}

また、 PropertyChanged を実行するときの引数「PropertyChangedEventArgs(nameof(CalcResult))」は Binding しているプロパティ名を渡す必要があり、
例えばローカル変数である「PropertyChangedEventArgs(nameof(calcResult))」などでは反映されないようなので注意が必要です。

DataBinding を使うことで、 xaml.cs クラスからほとんど処理を省くことができ、
表示する部分と処理を実行する部分とがより簡単に分離できるようになりました。

おわりに

まだ慣れないせいか、 xamlxaml.cs と実行される処理が(自分で書いたコード上では)切り離されているのは、
どこかふわふわして不安な感じもあります。

ただ、だからこそ仕様の変更に強いコードになる、ということだとは思うので、
ゆっくり内容を理解していきつつ、使いどころや効果的な使い方を模索していきたいと思います。

参照

Xamarin

MVVM

DataBinding

【C#】処理の委譲で迷った話

はじめに

この記事は C# Advent Calendar 2017 の一日目の記事です。

qiita.com

「継承より委譲を」という言葉を、Java開発者の方々を中心に(と思う)よく目にします。

  • クラスの継承をすると、親クラスの変更に子クラスが大きな影響を受けるので変更がしづらくなる
  • 委譲の場合、外部から呼び出しできるメソッド・そうでないメソッドの区別をつけやすい
  • 一般的には子クラスが親クラスの特別な種類である場合(is-a関係)に継承し、
    親クラスが子クラスを含んでいる(例えば子クラスが持つ機能を親クラスが持っている状態。 has-a関係)場合は委譲を用いる

といったところがその理由のようです。

OK。じゃあ処理を委譲しましょう。

と思ったのですが、どのように設計すれば委譲したことになるの?というところで迷ったので、
その辺りをまとめます。

delegateについて

C# 委譲」といったキーワードで検索すると、 delegate についての記事が見つかります。

delegate はごく簡単にまとめるとメソッドを引数として渡せるものです。

// 引数、戻り値が同じメソッドを変数として扱うことができる.
public delegate void CallNoArgMethod();

public class MainController
{
    public MainController()
    {
        var noArgMethod1 = new CallNoArgMethod(NoArgMethod);
        var noArgMethod2 = new CallNoArgMethod(StaticNoArgMethod);

        // NoArgMethod()が実行される.
        noArgMethod1();
        // StaticNoArgMethod()が実行される.
        noArgMethod2();

        // delegateメソッドをひとまとめにすることもできる.
        var mixedMethod = noArgMethod1;
        mixedMethod += noArgMethod2;

        // NoArgMethod()とStaticNoArgMethod()が実行される.
        mixedMethod();

        // delegateを自分で定義せずに Action 、Func (戻り値あり)を使うこともできる.
        var action = new Action(NoArgMethod);
        // 戻り値の型を指定する必要がある.
        var funcWuLongTea = new Func(CallHasReturnValueMethod);
    }
    // delegateを作るメソッド
    public void NoArgMethod()
    {
        Debug.WriteLine("世界さん、ちーっす");
    }
    // staticメソッドでも同じように扱うことができる.
    public static void StaticNoArgMethod()
    {
        Debug.WriteLine("staticだよ");
    }
    public string CallHasReturnValueMethod()
    {
        return "Hello";
    }
}

一般的には処理が完了したあと呼び出し元に通知するためのコールバック、ボタン押下などのイベント、
Linqなどで用いられる無名関数(通常ラムダ式が使われますが)などで使用されます。

var nums = new List {1, 2, 3, 4, 5};
// delegateを使って書く.
var evenDelegate = nums.Select(delegate(int num) { return num % 2 == 0; });
// ラムダ式を使って書く.
var evenLambda = nums.Select(num => num % 2 == 0);

で、これを使ってどうやって委譲するんです?というか、処理を委譲するのにdelegateを使う必要ってあるんです?

はい。ここで迷いました...orz

前述の通り、委譲というのは(例えば)そのクラスが持つ機能を別のクラスに分割する(委譲する)ことらしい。

ただ、これって別に delegate を使う必要はなくて、
処理を別クラスに分けて、メソッドを呼び出してあげれば良いのでは......?

と思ったのですが、あまりに自明すぎるのか、誰も気にしていないのか、この辺りに言及する資料は見つけられませんでした
(日本語で検索した限りでは)。

元の言葉を調べてみよう

ところで、Java界隈で継承と委譲の話が良く出てくる理由に、 Effective Java があるようです。

では、英語でも検索するにあたって、英語版の Effective Java ではどう表現されているかを見てみることにしました。

「Favor composition over inheritance」

ふむふむ。「inheritance」が「継承」なので、委譲にあたる言葉は「composition」のはずですね。

「composition」の意味を調べると「組み立て、合成」など(compositionの意味 - 英和辞典 Weblio辞書)と出てきます。

ほうほう。

......。

「継承より委譲を」の時の「委譲」って「委譲」じゃないんじゃないですか???

......少々取り乱しました。

結局、やるべきことは一つのクラスに対象物が持っている性質や機能をひとまとめにするのではなく、
複数クラスに切り分けて親となるクラスから呼び出すようにしましょう、ということで、
その方法についてはメソッド呼び出しでも delegate でも良い、という理解で良さそうです。

delegateで遊んでみる

せっかくなのでここからは delegate を使ってもう少し遊んでみることにします。

コールバック

何かの処理を行った後、それが完了したことを呼び出し元に伝えてほしい場合があります。
delegateを使うことで、呼び出された側が呼び出し元のクラスインスタンスを持たなくても通知できるようになります。

public void StartControlling()
{
    ExecuteSomeOperationWithCallback(() =>
    {
        // 完了後の処理.
    });
}
private void ExecuteSomeOperationWithCallback(Action callback)
{
    // 何か処理して終わったらdelegateメソッド呼び出し..
    callback();
}

呼び出せる処理を限定する

delegate でメソッドを変数として扱うには、クラスのインスタンスが必要になります。
ただ、クラスのインスタンス自身は、delegate メソッドの変数(メンバー変数として作成)を生成するときにローカル変数として生成しても、
その後で delegate メソッドがGCで空になる、といったことは無いようです。

そのため、呼び出し元のクラスで呼び出すメソッドを限定することができます。

public class MainController
{
    private CallStringArgMethod callMethod;
    public MainController()
    {
        var delegateMethodManager = new DelegateMethodManager();
        callMethod = new CallStringArgMethod(delegateMethodManager.PrintMessage);
    }
    public void StartControlling()
    {
        // このクラスで呼べるのはPrintMessageだけ.
        callMethod("Hello");
    }
}
public delegate void CallStringArgMethod(string message);
public class DelegateMethodManager
{
    public void NoArgMethod()
    {
        Debug.WriteLine("Hello No Arg");
    }
    public void PrintMessage(string message)
    {
        Debug.WriteLine(message);
    }
}

その他

例えばUnityで3Dモデルを動かす、という処理を複数クラスで共通化させた場合。
そのままの名前で呼び出すのではなく、 moveCube 、 carModel など対象物に特化させた名前を付けると、
意図がよりわかりやすくなるかもしれません。

また、メソッド自体は private にしておきたいが、特定の条件でのみ操作したい、といった場合、
intなどの変数でやるように Getter / Setter を使うことができます。

おわりに

一般的に浸透している(と思う)言葉でも、語源に近い言語で調べてみるの大事ですね(苦笑)。

まぁそれはそれとして、 delegate もコールバック関数やLinqで使う以外にも、
使い方によってはなかなか便利そうです。

もちろん乱用は厳禁ですが(;'∀')

明日は neuecc さんです。よろしくお願いいたします(..)_

参照

Xamarin.Formsに触れてみた話

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiita の一日目の記事です。

qiita.com

なんとなく気になりつつも触れていなかった Xamarin に触れてみた、という内容です。

なお Xamarin には AndroidiOS の薄いラッパーである Xamarin.Android / Xamarin.iOS もありますが、
今回は Xamarin.Forms を試してみることにしました。

理由としては、同じように C# で書けてマルチプラットフォームに対応する Unity との使い分けができると良いな、と思ったためです。

Xamarinプログラミング入門をベースに自分の興味に任せて試した内容をまとめてみたいと思います。

準備

まずは準備から。 Visual Studio は 2017.15.4.4 Community Edition を使っています。

インストールは Visual Studio 本体のインストール時か、メニューの ツール > ツールと機能を取得 から、
.Netによるモバイル開発 にチェックを入れると可能です。

簡単ですね。

Xamarin Live Playerが有効にならない

Xamarin には Xamarin Live Player という機能があり、
AndroidiPhone に同名のアプリをインストールして Visual Studio とリンクしておくと、
同一ネットワークにつながっている場合はUSBケーブルでPCに接続しなくても実機でのデバッグを行うことができます。
(iPhoneMac が必要)

使い方は例えば Android なら、プロジェクトを Android に切り替えて、
バイスを選択するところから Xamarin Live Player を選択・・・

できませんでした/(^o^)\

Visual Studio のバージョンは問題ないし、 Xamarin も最新といっているし・・・

と思っていたら、公式サイトに載っていました。

どうやらデフォルトでは有効になっていないらしく、
ツール > オプション > Xamarin > その他 から、 Xamarin Live Playerを有効にする にチェックを入れる必要があるそうです。

f:id:mslGt:20171130233638j:plain

f:id:mslGt:20171130233822j:plain

ちゃんと説明は読みましょう、というお話でしたorz

なお有効にしたあと、テザリング環境でも試してみましたが、
Androidについてはテザリング環境でも問題なく接続できました。

確認したい端末が複数台ある場合など、結構便利なのではないでしょうか。

デフォルトのプロジェクトを見てみる

テンプレートを Blank App 、 Code Sharing Strategy を PCL にして Xamarin プロジェクトを作成すると、
以下の4つのプロジェクトを持つソリューションが生成されます。
(プロジェクト名は XamarinSample としました)

  • XamarinSample (移植可能)
  • XamarinSample.Android
  • XamarinSample.iOS
  • XamarinSample.UWP

View は XamarinSample (移植可能) のものを使います。

が、ほかのプロジェクトを見てみると、それぞれ MainPage.xaml(UWP) や MainActivity.cs(Android) など、
スタートアップやメインページの表示に関わりそうなファイルが見つかります。

とりわけ気になったのが UWP 。
MainPage.xaml と App.xaml って、 XamarinSample (移植可能) と一緒じゃないのという。

試しに UWP の MainPage.xaml にボタンなどを追加してみましたが、
少なくともデフォルトでは反映されませんでした。

また、 MainPage.xaml を削除するとエラーが発生しました。

基本的に各プロジェクトのコードはスタートアップのために存在するもののようで、
UWP については MainPage.xaml.cs や App.xaml.cs のコードビハインドが必要なので空の Xaml ファイルがある、
ということのようです。

xamlxaml.csについて

XamarinSample (移植可能) の MainPage.xaml と MainPage.xaml.cs ですが、
デフォルトではプロジェクト直下にあります。

これを View というディレクトリを作ってその中に移動し、
MainPage.xaml.cs の namespace を合わせて XamarinSample.View のように変更してやると、エラーになります。

これは MainPage.xaml や App.xaml.cs で MainPageクラスを呼んでいるためで、
合わせて変更してあげる必要があります。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.View.MainPage"> 
~省略~
< /ContentPage>

App.xaml.cs

~省略~
public App()
{
    InitializeComponent();
    MainPage = new XamarinSample.View.MainPage(); 
}
~省略~

イベントとナビゲーションの追加

それでは、画面にボタンを追加して別のページに遷移する、というのを試してみたいと思います。

まずは SubPageOne.xaml というページを追加しておきます。

ナビゲーション

Xamarin.Forms では、 NavigationPage を使用することで、
比較的簡単にページ遷移が実装できるようになります。

NavigationPage を使用するためには、
XamarinSample (移植可能) の App.xaml.cs を変更する必要があります。

Before

~省略~
public App()
{
    InitializeComponent();
    MainPage = new XamarinSample.View.MainPage();
}
~省略~

After

~省略~
public App()
{
    InitializeComponent();
    MainPage = new NavigationPage(new XamarinSample.View.MainPage());
}
~省略~

あとは下記のように Navigation.PushAsync を使えばOKです。 (なお遷移後のページには戻るボタンが自動で表示されます)

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace XamarinSample.View
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
        async void OpenSubPageOneButtonClicked(object sender, EventArgs e)
        {
            // 遷移後のページ(SubPageOne.xaml.cs を指定する).
            await Navigation.PushAsync(new SubPageOne());
        }
    }
}

遷移前

f:id:mslGt:20171130234051j:plain

遷移後

f:id:mslGt:20171130234115j:plain

イベント

MainPage.xaml にボタンを追加して、 MainPage.xaml.cs の OpenSubPageOneButtonClicked をイベント関数としてセットします。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.View.MainPage">
    < ContentPage.Content>
        < AbsoluteLayout BackgroundColor="Yellow">
            < Button AbsoluteLayout.LayoutBounds="20,20,200,30" Clicked="OpenSubPageOneButtonClicked" BackgroundColor="#0078d7">SubPage1< /Button>
        < /AbsoluteLayout>
    < /ContentPage.Content>
< /ContentPage>

プラットフォーム固有のクラスを呼ぶ

さて、基本的には各プラットフォーム共通の処理を XamarinSample (移植可能) に追加していくわけですが、
中にはプラットフォームごとに処理を分ける必要がある場合もあります。

その場合の方法はいくつかあるようですが、
今回は DependencyService を使うことにしました。

XamarinSample (移植可能) にインターフェースを作っておき、
各プラットフォームのプロジェクトにそのインターフェースを継承したクラスを作り、
そこで固有の処理を書きます。

で、それを DependencyService を使って実行時に該当プラットフォームのクラスを呼ぶ、
という流れのようです。

ICalc.cs

namespace XamarinSample
{
    public interface ICalc
    {
        float Calc(float currentValue, float calcValue);
    }
}

呼ばれる側

Subtraction.cs (Androidプロジェクトに作成)

using Xamarin.Forms;
using XamarinSample.Droid;

// DependencyServiceで呼べるようにする.
[assembly: Dependency(typeof(Subtraction))]
namespace XamarinSample.Droid
{
    public class Subtraction : ICalc
    {
        public float Calc(float currentValue, float calcValue)
        {
            return 4;
        }
    }
}

Subtraction.cs (UWPプロジェクトに作成)

using Xamarin.Forms;
using XamarinSample.UWP;

// DependencyServiceで呼べるようにする.
[assembly: Dependency(typeof(Subtraction))]
namespace XamarinSample.UWP
{
    public class Subtraction : ICalc
    {
        public float Calc(float currentValue, float calcValue)
        {
            return 2;
        }
    }
}

呼び出す側

SubPageOne.xaml.cs

using System.Diagnostics;
using Xamarin.Forms;
using XamarinSample.ViewModel;

namespace XamarinSample.View
{
    public partial class SubPageOne : ContentPage
    {
        public SubPageOne()
        {
            InitializeComponent();
            var subtraction = DependencyService.Get();
            Debug.WriteLine(subtraction.Calc(0, 1).ToString());
        }
    }
}

これで、Android で実行した場合は 4 が、Windows で実行した場合は 2 が返ってきます。 #if ~ で切り分けるよりシンプルで良いですね。

出力する

apk ファイル (Android) や ipa ファイルとしてアプリを書き出したい場合、 ソリューションエクスプローラーのそれぞれのプロジェクト上で 右クリック -> アーカイブ をクリックすればOKのようです。

UWP は HockeyApp または ストア > アプリパッケージの作成 から。
また WindowsiOS を出力する場合は、 MacXcode に接続されている必要があります。

なおストア配信時の設定は下記のような情報を参考に。

おわりに

同じように C# でコードを書き、マルチプラットフォームにアプリを作成できる Unity とはずいぶん違うのだなぁ、というのが率直な感想です。

C#7が使えたり (Unity は Experimental な機能を On にしても C#6 までの対応 )、
独自のお作法ももちろんあるのでしょうが、 WPF など普通の? C# に近い印象を受けました。

もちろん Unity が悪いって話ではなく、ただ違うという話ですよ。念のため。

Xamarin.Forms の XamlGUI エディタがない(っぽい)、
何かいじるとエラーが (大抵実行には問題がなく、エラーの出たプロジェクトをクリーン・リビルドすると直るのですが)、
とまだ発展途上なところも見受けられます。

が、すぐ改善されるだろうと思いますし、できれば自分もそれに寄与できればなぁ、とも思いました。

最後に、今回作ったサンプルは、(ボタン位置などめちゃくちゃですが)黄色をベースに作っていました。

その理由は。。。

♪ We all live in a yellow Xamarin, yellow Xamarin, yellow Xamarin ♫

https://www.youtube.com/watch?v=vefJAtG-ZKI

・・・おあとがよろしいようで。

明日は gnk263 さんです。よろしくお願いいたします(..)_

参照