かずきのBlog@hatena

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

かずきのXamarin.Forms入門を更新しました

5か月ぶりくらいに内容をアップデートしました。

SlideShareにサインインするとダウンロードボタンが表示されるので、そこからダウンロードしてご利用ください。

www.slideshare.net

変更内容

  • Xamarin.Forms 2.3.4ベースの内容に更新しました
  • Prism.Forms 6.3ベースの内容に更新しました

今後

内容のアップデートだけなので、これから前にもらったフィードバックなどを反映させたあとSlideShareのPDFを再更新かけてAmazonにも放流しようと思います。

Bot FrameworkのDirect Line APIをXamarin.Formsから使う場合のサンプルプログラム

を書きました。 .NET Standardにしないといけなかったりとちょっと手間取りましたが、なんとか動く感じになりました。リポジトリは以下になります。

github.com

Bot Frameworkを使えば簡単にBotを作れるので、作ったBotを自分のアプリに組み込む際の参考にしてください!ではでは。

ReactiveProperty v4系をXamarin.Formsで使う方法

Reactive Extensions 3系は正直Xamarinで使うのは辛かった。なんかパッケージ追加しただけで動かなくなるし。

Reactive Extensions 4系は、そんなことがない!素敵!!

ということでReactive Extensions 4系に依存しているReactiveProperty 4系もXamarin.Formsから割とするっと使えます。ということで手順をアップデートしました。

github.com

まだpreview版なので本番に適用するかどうかは慎重に!

ReactiveProperty v4.0.0 プレリリース版をリリースしました

表題の通りReactiveProperty v4.0.0のプレリリース版を公開しました。NuGetのページは以下になります。

www.nuget.org

主な変更点は以下の通りです。

更新

  • System.Reactive v4.0.0-preview00001 に更新

変更

  • ReactiveProperty の internal なコンストラクタを public に変更しました
  • ReadOnlyReactiveProperty の internal なコンストラクタを public に変更しました

破壊的変更

  • SerializeHelper の削除
  • .NET Standard 1.1 から .NET Standard 1.3 に変更
    • .NET Framework 4.5 を対応プラットフォームから外しました
    • Windows store app を対応プラットフォームから外しました

Prism.Forms でプラットフォーム固有処理を呼び分ける方法

Xamarin.Forms にも標準で DependencyService という機能があるんですが、こいつよりも柔軟な素敵な機能が Prism.Forms には提供されています。

IPlatformInitializer

それは IPlatformInitializer です!これは、Prism.Forms のDIコンテナへのインスタンスの登録処理をプラットフォーム固有プロジェクトで実行する箇所を提供するという仕組みになります。

これによって、インターフェースをPCLプロジェクトに定義して、プラットフォーム固有処理をDroidとiOSとUWPプロジェクトに実装したものをDIコンテナに登録するといったことが出来るようになってます。

使ってみよう

ということで使ってみようと思います。Visual Studio 2017でPrism Template Packを入れてプロジェクトを新規作成するとIPlatformInitializerが使われた状態のコードが生成されるので楽です。

今回は Autofac を使ったプロジェクトテンプレートを前提にお話ししますが、他のDIコンテナでも大体同じになります。

プロジェクトを新規作成すると、Droid, iOS, UWPプロジェクトに以下のIPlatformInitializerを実装したクラスが生成されています。

  • Androidプロジェクト
    • MainActivity.csの中のAndroidInitializerクラス
  • iOSプロジェクト
    • AppDelegate.csの中のiOSInitializerクラス
  • UWPプロジェクト
    • MainPage.xaml.csの中のUwpInitializerクラス

こんな感じのクラスになっていると思います。(Androidのものを抜粋してます)

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {

    }
}

このRegisterTypesメソッドがPrismのアプリが開始するタイミングで呼び出されるので、ここにコンテナへの登録処理を行います。

本来はOSごとに違う処理をしたいときとかに使うのですが、今回はOSごとに違う文字列を返すという処理を例に作ってみようと思います。(OSごとに違う値を返したいというケースでは本来OnPlatformを使いましょう)

まず、PCLのプロジェクトにプラットフォーム固有の処理を抽象化したインターフェースを定義します。今回の例の場合は、文字列を返すだけのメソッドになります。こんな感じのコードをPCLのプロジェクトに追加します。

namespace PrismAutofacApp3.Services
{
    public interface IGreetingService
    {
        string GetText();
    }
}

そして、プラットフォーム固有のプロジェクトに以下のようにインターフェースを実装したクラスを作成します。

Droidプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.Droid.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello Droid project.";
        }
    }
}

iOSプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.iOS.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello iOS project.";
        }
    }
}

UWPプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.UWP.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello UWP project.";
        }
    }
}

PlatformInitializer へのインスタンス登録処理の追加

Droidプロジェクト

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

iOSプロジェクト

public class iOSInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

UWPプロジェクト

public class UwpInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

使ってみよう

使い方は、普通にDIしたクラスと同じです。Prism Template Packで生成さらたプロジェクトをちょっといじってみましょう。MainPageViewModelにIGreetingServiceをDIして使っています。

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using PrismAutofacApp3.Services;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PrismAutofacApp3.ViewModels
{
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        private IGreetingService GreetingService { get; }

        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        public MainPageViewModel(IGreetingService greetingService)
        {
            this.GreetingService = greetingService;
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {
            this.Title = this.GreetingService.GetText();
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }
}

実行して確認してみましょう。今Macがない環境なのでAndroidとUWPだけですが以下のように表示されるテキストが変わっていることが確認できます。

f:id:okazuki:20170615203023p:plain

f:id:okazuki:20170615203204p:plain

まとめ

ということでIPlatformInitializerについて紹介しました。こいつのメリットとして、プラットフォーム固有のクラスに対して、さらに別のクラスをDIしたりといったこともできるという柔軟性は、DependencyServiceに無いですし、このようにインターフェースをDIするようにしておくと、後で単体テストするときでもいい感じにモックに差し替えたりできるので便利です。

ということで良いXamarin.Froms + Prism.Forms生活を!!

Xamarin.Forms と Azure の組み合わせサンプル書いてみたよ

Microsoft Cognitive Services を使ったサンプルを ABC 2017 Spring で作ってみたら動かなくて悲しい目にあったので、全部スマホでやってた処理を、きちんとサーバーサイドとクライアントサイドにわけてデータも1度処理したものは永続化してというのをやってサンプルプログラムにしてみました。

どんなプログラムなの?

Twitter から #microsoft のハッシュタグを検索して画像を収集するアプリケーションになります。収集した画像は、Microsoft Cognitive Services の Vision API を使ってカテゴリ分けを行い、画像がどんなものかという説明文も Vision API を使って生成しています。 Vision API の結果は英語でかえってくるので Microsoft Cognitive Services の Translator Text API を使って日本語に翻訳しています。

カテゴリ分けされた画像のデータは Microsoft Azure の Cosmos DB に保存しています。画像の収集と Vision API による認識処理は Azure Functions を使って定周期で実行しています。

f:id:okazuki:20170605174226p:plain

クライアントアプリケーションは Xamarin.Forms で作ってるので Android / iOS / UWP で動かすことが出来ますが作業環境の都合上 iOS で動作確認が出来てません!後でやらないと…。クライアントアプリケーションの実行結果は以下のような感じになります。

f:id:okazuki:20170605174345p:plain

f:id:okazuki:20170605174357p:plain

f:id:okazuki:20170605174409p:plain

ポイント

何を見たらいいのだろうか?

Cognitive Services API の呼び出し方

Cognitive Services の API を呼び出すコード例として見ることが出来ると思います。具体的には VisionService クラスあたりがそれになります。

github.com

Cosmos DB に C# からアクセスする方法

Cosmos DB に対して C# からアクセスする際のコード例になります。DBの作成からデータの追加検索を行っています。また、データ件数が多くなったときのページング処理も実装しています。

github.com

Prism.Forms を使ったコード例として

Prism.Forms を使ったコード例としても見てください。具体的には CognitiveServicesSample.Client プロジェクトになります。

github.com

Xamarin.Forms の ListView を使ったインクリメンタルローディング

データは基本的に50件ずつ取得してListViewが下までスクロールされたらデータを追加で読み込むようにしています。 Xamarin.Forms の ListView には、このようなインクリメンタルローディングの処理は組み込みでサポートされていないため ListView を拡張して IncrementalLoadingListView を作成して使っています。

github.com

実際に使っているのは CategorizedImagePage になります。

github.com

CategorizedImagePageViewModel に追加読み込みのロジックがあります。

github.com

作ってて諦めたことや、やらないといけないこと

.NET Standard

.NET Standard で出来るところは .NET Standard のプロジェクトにして WebAPI は ASP.NET Core で実装しようと思ってたのですが Cosmos DB の SDK の要求する .NET Standard のバージョンが 1.6 だったりした関係で UWP のプロジェクトから参照できなかったりして泣いて諦めました。

自動ビルド

リポジトリに Push したら自動でビルドしてテスト流して配備とかやるとかっこいいなぁと思ってるけど、そもそも単体テストが無い!!!一応、外部リソースに依存する部分とかは interface 切ってたりするので単体テスト書いたりできる土壌は出来てるのでそのうちやらないといけないと思いつつやってない…。

おまけとして

ここで作ってる Controller 2つですが

github.com

http://cogsample.azurewebsites.net にデプロイしてあるので API を叩くことでデータがとってこれます。

http://cogsample.azurewebsites.net/api/Category で現在収集してる画像のカテゴリ一覧が取れます。

http://cogsample.azurewebsites.net/api/CategorizedImage?category=people_ で指定した名前のカテゴリの画像を50件とってくることが出来ます。続きのデータを読み込みたいときは結果の JSON の Continuation の値を continuation パラメータとしてくっつけて送ることで取得できます。

結果の JSON の形式は以下のクラスを使えば JSON.NET とかでデシリアライズできます。

github.com

github.com

実際に URL を叩いてるコードは以下のクラスになります。

github.com

まとめ

突っ込みかなんかあったら PullRequest や Issue 待ってます。日本語でおk。

#decode17 に参加してきました(登壇側で)(ほぼ技術的なエントリではないです)

5/23 - 24 で開催された Microsoft の主催する開発系イベントの de:code 2017 に登壇者側で参加してきました。de:code は、初回から前回まで一般参加者として参加してきたので割と思い入れのあるイベントでした。今年は Microsoft に転職して最初の de:code でしたが、登壇側で参加できるとは思ってませんでした。

https://www.microsoft.com/ja-jp/events/decode/2017/

de:code の会議に勉強のため参加させていただいたら、その場で登壇が決まってしまいました。担当する内容は Xamarin + Visual Studio Mobile Center という内容でした。タイトルを考えないといけなかったので、同じく Xamarin のセッションを1つ担当する @chomado さんと相談して Xamarin入門 と Xamarin実践 という感じでセッションをするという話しになりました。

ということでセッションタイトルは[Xamarin 実践] モバイル アプリの開発、テスト、テスト配布を全て実践 - Xamarin & Visual Studio Mobile Center -になりました。Xamarinは、軽く触ってるので、まぁなんとかなるかなと思ってたのですがセッションタイトルが決まった段階で Visual Studio Mobile Center は、アカウント作ってちょっと触ったくらいだったのでちょっと焦ってました。

ということで、Visual Studio Mobile Centerの動作を確認するためのサンプルプログラムを作りました!

github.com

といっても、Visual Studio 2017のXamarinのプログラムを新規作成するときに生成されるマスター詳細のプログラムです!これにMobile CenterのmBaaSの機能をちょろっと追加したのと、押したら絶対クラッシュするボタンを追加した感じです。(クラッシュレポート機能のために)

あと、自動テストのためのXamarin.UITestも追加してあるので、このリポジトリをフォークしてもらえればMobile Centerに対してリポジトリを指定してもらえば自動ビルドとかが動くようになってるので、もしMobile Centerを試したいけどプログラムを用意したくないんだよなぁって人は使ってみてもいいかもしれません。

Visual Studio Mobile Center について感想

さて、Visual Studio Mobile Centerについては近々セッション資料と動画があわせて公開されると思うのでそちらを参照していただくのと、前に書いたBlog記事があるので見ていただければ大体ばっちりになると思います!

blog.okazuki.jp

現時点で Preview なので無料で試せるので試さない手はないです。

Mobile Center

自動ビルド・テスターへの配布・実機テスト

Visual Studio Mobile Center の機能についてですが大部分が非常にシンプルに使えるようになっています。例えば自動ビルドについては、リポジトリを選んでブランチを選ぶだけで、ほぼすべての設定が終わりみたいな感じです。とてもお手軽!これだけで、リポジトリにプッシュされたタイミングでAndroidやiOSやUWPのアプリがビルドされるようになるなんて素晴らしいですね。ただ、素晴らしい反面、Visual Studio Mobile Centerが想定していない構成のプロジェクトとかだとおそらくビルドできないですし、ちょっとビルドの時にカスタマイズした処理を入れたいんだよねぇっていうありがちな要望には現時点で対応できません。

なので、本格的な開発で使用するには、現時点ではちょっと弱いかもしれません。そんな時は、Visual Studio Team Servicesなどの本格的な自動ビルドの機能をもったプロダクトを使うといいでしょう。個人的にVisual Studio Mobile Centerは、簡単に開発のベストプラクティスを試してメリットを感じることが出来る環境を提供していることだと思います。自動ビルドから自動でのテスターへのパッケージの配布の便利さを実感してもらえれば、恐らくVisual Studio Mobile Centerが対応していないものでも自動ビルドなどのプラクティスを取り入れるというモチベーションになると思います。

Visual Studio Mobile Centerの自動ビルド機能について、割と辛口(シンプルすぎて使えないともとれるような内容)に書きましたが、実機テストの機能とテスト配布の機能は割と普通に使えます。これはVisual Studio Mobile Centerが次世代のXamarin Test CloudやHockey Appだと言われていることからも力の入れ具合がわかります。(Visual Stduio Mobile Centerは決して次世代のVisual Studio Team Servicesではないという認識) ということで、実はVisual Studio Mobile Centerの実機テスト機能とテスト配布の機能についてはVisual Studio Team Servicesから叩くことができるタスクが用意されています。これを使えば自動ビルドは本格的なVisual Studio Team Servicesを使いつつ実機テストとテスターへの配布はVisual Studio Mobile Centerを使うといったことが出来ます。 また、実機テストはコマンドラインからキックする感じなので、他のCIツールからも恐らく簡単に叩けるように仕込めると思います。

f:id:okazuki:20170601200043p:plain

Tables と Identity

mBaaSの機能を提供する Tables と Identity ですが、こいつは実はVisual Studio Mobile Centerのプロジェクトを新規作成した段階では使えません。Microsoft Azureのアカウントと紐づけることで裏でMobile Appsが作られて、それをVisual Studio Mobile Centerの画面から管理できるといった感じです。なので実質 Mobile Apps の EasyTable と 認証機能 になります。

なので実際に Mobile Apps を使ったことがある方はおわかりの通り、非常にシンプルなテーブルへのデータのCRUDと、そのデータへのアクセス時に認証情報をのせたりとかいったことが出来るだけになります。それで十分な程度の用途の場合にはサーバーサイドを開発しなくていいので最強ですが、提供機能以上のことをしようとすると破綻します。はい。

例えば、複数テーブルのデータをサーバーサイドでJOINしてもってきたいといったケースや、複数テーブルに跨るトランザクションをはりたいとかいったことは出来ないので死にます。死ぬので、Xamarin.Formsなどから使うときは、きちんとデータアクセス部分は抽象化して interface を切っておいて、その実装クラスでVisual Studio Mobile Centerのテーブルにアクセスするといった作りにしておきましょう。いざとなったらMicrosoft AzureのWeb App上に構築した独自のWeb APIにアクセスするような実装と差し替えれる余地を残しておきましょう。

もしくは、最初からEasyTableには荷が重いといったことが明らかな場合は、最初から使うのはあきらめましょう。ハマらない場所に、無理やりプロダクトを当てはめようとするのは個人的には辛いので嫌いです。

Crashes と Analytics

これは、個人的には使えるなら使ったらいいと思うVisual Studio Mobile Centerの機能の1つです。クライアント側で起きたクラッシュ時のログの吸い上げや、アプリの使用状況などを確認するためのログを吸い上げる機能などがあり、それを割といい雰囲気の画面で確認できるのでお勧めです。Visual Studio Mobile Centerを使わないにしても本格的なモバイルアプリケーションを作成する場合には、似たような機能を作る or サービスを使うことは検討しておいたほうがいいでしょう。検討の結果使わないという判断をしたなら、それはそれでいいと思います。

f:id:okazuki:20170601201125p:plain

まとめ

ということで、de:code 2017に登壇側で参加してきました。 de:code 2017全体としてはAIや、MR(HoloLensいいよね)などに注力しているので Xamarin については無償化された直後に開催された昨年のde:codeと違って大盛り上がりというわけではないといった印象を受けました。といっても、ちゃんと Xamarin については5つも枠がとられているので相変わらず重要な位置づけのテクノロジとして扱われてるのかなといった印象を受けます。

MRについては、HoloLensなどがアプリの実行のプラットフォーム(UWP!!UWP!!)になります。3Dにきちんと対応したアプリはXamarin.Formsでは作れませんが、UWPアプリは動くのでHoloLensで動くアプリ自体は作ることができます。(あえてXamarin.Formsで作るメリットがどれくらいあるかは…)

ただ、AIについてはAIを使ったアプリケーションを実行するプラットフォームとしてスマートフォンは、まだまだ重要な立ち位置にあると思っています。そして、複数プラットフォームに対応したアプリを開発するうえではXamarinはイケてると個人的に思っています。コンシューマー向けのようながっつりと画面を作りこむようなアプリはXamarin.Nativeで作ったり、業務アプリみたいに画面はコンシューマー向けほど作りこむというより複数プラットフォーム対応時の生産性が重要!といったケースではXamarin.Formsを選んだり選択肢があるのも素晴らしいと思ってます。

ということで、XamarinとVisual Studio Mobile Centerについてよろしくお願いします!

感想は以上です。ここまで読んでくれてありがとうございました!

最近公開されているMSの中の人が書いたAzureブログ記事(独断と偏見で)

なんかがっつりしたBlog記事が結構書かれてるのを見つけたのでまとめておきます。 結構がっつりした記事もありますね…。私のBlogのライトな雰囲気とは違って新鮮。

blogs.msdn.microsoft.com

blogs.msdn.microsoft.com

blogs.msdn.microsoft.com

blogs.technet.microsoft.com

blogs.msdn.microsoft.com

blogs.technet.microsoft.com

blogs.technet.microsoft.com

Visual Studio 2017をアップデートした直後にASP.NET CoreをAzure WebAppにデプロイしたら502エラーになった

2時間くらいはまった…。

結果としては、以下のStackoverflowにある通り、Microsoft.NETCore.Appのバージョンを明示的にしてあげればよかった。VSのアップデートともにローカルのバージョンは上がったけどAzure側はまだ追いついてないってことなのかな?

stackoverflow.com

UWP Community Toolkitを使って今風?なListViewのUIを実現する

UWP Community Toolkitが、ちょっと目をはなしてる隙に1.4.1にまでバージョンアップしてました。 色々なコントロールとかが提供されているのですが、ListViewに絡むコントロールをいくつか紹介したいと思います。

インクリメンタルローディング

最近のアプリって下の方までスクロールすると続きを読み込むっていうUI多いですよね? ということで、そういう機能を実現する方法がUWPに提供されています。

ISupportIncrementlLoadingインターフェースがそれになります。

docs.microsoft.com

ただ、こいつの実装だるいんですよね。 ということで、UWP Community Toolkitでは、いい感じに扱ってくれる機能を提供していたりします。

ということでさっそく作ってみましょう。

まず、何でもいいので表示用のデータの入れ物クラスを準備します。

public class Person
{
    public string Name { get; set; }

    public override string ToString() => this.Name;
}

このクラスのデータをインクリメンタルローディングしてみようと思います。UWP Community ToolkitにはIIncrementalSource<T>というインターフェースがあります。こいつを実装することでインクリメンタルローディングが出来るようになります。

インターフェースは単純でページ数とデータ取得件数が渡ってくるので、それの部分に対応したデータを返すだけです。

ということで、NuGetでUWP Community Toolkitで検索してパッケージを追加しましょう。Microsoft.Toolkit.Uwp.UI.Controlsが今回のお目当てのパッケージになります。

IIncrementalSource<T>インターフェースの実装は以下のような感じになります。

public class PersonSource : IIncrementalSource<Person>
{
    public Task<IEnumerable<Person>> GetPagedItemsAsync(
        int pageIndex, 
        int pageSize, 
        CancellationToken cancellationToken = default(CancellationToken))
    {
        Debug.WriteLine($"GetPagedItemsAsync: {pageIndex}, {pageSize}");
        var results = Enumerable.Range(pageIndex * pageSize, pageSize)
            .Select(x => new Person { Name = $"tanaka {x}" });
        return Task.FromResult(results);
    }
}

今回は、ログを吐いて適当にデータを生成して返しています。本当は、ここでREST APIとか叩いてデータを取ってくる感じになります。

IIncremantlSource<T>を実装したら、それを扱うためのIncrementalLoadingCollection<TSource, T>クラスを作ります。今回はMainPageにプロパティとして持たせました。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public MainPage()
    {
        this.InitializeComponent();
    }
}

あとは、適当にXAMLでこれをバインドします。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind People}">
        </ListView>
    </Grid>
</Page>

実行すると、以下のような結果になります。(といっても普通にデータが表示されるだけです)下までスクロールすると延々とデータが表示されるのがわかります。

f:id:okazuki:20170417111313p:plain

ひっぱって更新(PullToRefresh)

UWPのListViewに何故標準でないのか?という引っ張って更新機能ですが、UWP Community ToolkitにはPullToRefreshListViewというコントロールがあります。ListViewのかわりに使うことで、引っ張って更新が簡単に実現できます。

PullToRefreshContentプロパティで、引っ張ったときに表示されるメッセージやコンテンツを編集できます。ReleaseToRefreshContentプロパティで更新されるところまで引っ張ったときに表示されるメッセージやコンテンツを編集できます。

そして、RefreshRequestedイベントかRefreshCommandを処理することで引っ張って更新時の処理を実行できます。コード例は以下のような感じになります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested">
        </controls:PullToRefreshListView>
    </Grid>
</Page>

イベントハンドラでは、今回の例ではIncrementalLoadingCollection<TSource, T>RefreshAsyncメソッドをたたいています。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public MainPage()
    {
        this.InitializeComponent();
    }

    private async void PullToRefreshListView_RefreshRequested(object sender, EventArgs e)
    {
        await this.People.RefreshAsync();
    }
}

実行すると以下のような感じで引っ張って更新が出来るようになります。

f:id:okazuki:20170417112248p:plain

f:id:okazuki:20170417112258p:plain

左右にスワイプ可能なListView

最近よくあるUIでListViewの項目を左右にスワイプすることでアクションを実行するというものがあります。これもUWP Community Toolkitで提供されています。

SlidableListItemListViewItemTemplateに設定することで実現可能です。注意点としては、SlidableListItemListViewItemいっぱいに表示するためにHorizontalContentAlignmentVertlcalContentAlignmentStretchに設定しておくという点があります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem>
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

これで実行すると以下のように左右にスワイプするとアイコンが表示されるようになります。

f:id:okazuki:20170417112834p:plain

アイコンのカスタマイズは、LeftIconプロパティとRightIconプロパティで行います。

<controls:SlidableListItem LeftIcon="Accept"
                           RightIcon="AddFriend">
    <TextBlock Text="{x:Bind Name}" />
</controls:SlidableListItem>

左右にスワイプされたときの処理はLeft/RightCommandRequestedイベントかLeft/RightCommandを処理することで出来ます。Left/RightCommandParameterを活用することでスワイプされた項目のデータを渡したりもできます。

ICommandインターフェースの実装がだるいのでPrism.CoreをNuGetから参照に追加して以下のようなコードを書きます。

public sealed partial class MainPage : Page
{
    public IncrementalLoadingCollection<PersonSource, Person> People { get; } = new IncrementalLoadingCollection<PersonSource, Person>();

    public DelegateCommand<Person> LeftCommand { get; }

    public DelegateCommand<Person> RightCommand { get; }

    public MainPage()
    {
        this.InitializeComponent();
        this.LeftCommand = new DelegateCommand<Person>(x => Debug.WriteLine($"LeftCommand: {x}"));
        this.RightCommand = new DelegateCommand<Person>(x => Debug.WriteLine($"RightCommand: {x}"));
    }

    private async void PullToRefreshListView_RefreshRequested(object sender, EventArgs e)
    {
        await this.People.RefreshAsync();
    }
}

XAMLは以下のような感じになります。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d"
      x:Name="page">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem LeftIcon="Accept"
                                               RightIcon="AddFriend"
                                               LeftCommand="{Binding LeftCommand, ElementName=page}"
                                               LeftCommandParameter="{Binding}"
                                               RightCommand="{Binding RightCommand, ElementName=page}"
                                               RightCommandParameter="{Binding}">
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

実行すると以下のように、左右にスワイプしたらログが出るようになります。

f:id:okazuki:20170417113611p:plain

逆方向にスクロールするとすぐ表示されるヘッダー

これ名前なんて言うんですかね。Facebookアプリとか使ってるとコンテンツを見るために下にスクロールするとヘッダー(ボタンとか操作項目が並んでる)が非表示になって、コンテンツが画面いっぱいに表示されるけど、ちょっと逆方向にスクロールすると、すぐにヘッダーが表示されるっていうやつです。

これは、ScrollHeaderコントロールをListViewHeaderに設定することで実現できます。このとき、ScrollHeaderコントロールのModeプロパティをQuickReturnにしてホストするListViewコントロールをTargetListViewBaseに設定するのがポイントです。

<Page x:Class="App13.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App13"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      mc:Ignorable="d"
      x:Name="page">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:PullToRefreshListView x:Name="listView" 
                                        ItemsSource="{x:Bind People}"
                                        PullToRefreshContent="引っ張って更新"
                                        ReleaseToRefreshContent="離すと更新します"
                                        RefreshRequested="PullToRefreshListView_RefreshRequested"
                                        SelectionMode="None">
            <ListView.Header>
                <controls:ScrollHeader Mode="QuickReturn"
                                       TargetListViewBase="{x:Bind listView}">
                    <Border Background="Beige">
                        <StackPanel Padding="10"
                                    Orientation="Horizontal">
                            <Button Content="Foo"
                                    Margin="5" />
                            <Button Content="Bar"
                                    Margin="5" />
                            <Button Content="Baz"
                                    Margin="5" />
                        </StackPanel>
                    </Border>
                </controls:ScrollHeader>
            </ListView.Header>
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Person">
                    <controls:SlidableListItem LeftIcon="Accept"
                                               RightIcon="AddFriend"
                                               LeftCommand="{Binding LeftCommand, ElementName=page}"
                                               LeftCommandParameter="{Binding}"
                                               RightCommand="{Binding RightCommand, ElementName=page}"
                                               RightCommandParameter="{Binding}">
                        <TextBlock Text="{x:Bind Name}" />
                    </controls:SlidableListItem>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment"
                            Value="Stretch" />
                    <Setter Property="VerticalContentAlignment"
                            Value="Stretch" />
                </Style>
            </ListView.ItemContainerStyle>
        </controls:PullToRefreshListView>
    </Grid>
</Page>

実行すると、以下のようにヘッダーが表示されて、スクロールすると消えるけど、逆方向にスクロールすると、すぐにょきっと生えてくるようになります。

f:id:okazuki:20170417114323p:plain

f:id:okazuki:20170417114358p:plain

f:id:okazuki:20170417114428p:plain

まとめ

ということで、UWP Community ToolkitでListViewまわりの機能を紹介しました。これ以外にも色々なコントロールや機能があるので、興味のある方は見てみるといいかも?より良いUIが簡単に手に入るかもしれませんしね!

github.com