KatsuYuzuのブログ

.NET の開発をメインとした日記です。

Windows PhoneのLumia 520を買ったよ #wpjp

ついにスマートフォン買いました(日本WP待機ガラケー勢
f:id:KatsuYuzu:20140607182350p:plain
1万円台と、なんともお財布にやさしいローエンドモデルのLumia 520。

Nokia Lumia 520 (Black 黒) SIMフリー 海外携帯

Nokia Lumia 520 (Black 黒) SIMフリー 海外携帯

WPあーち行って購買意欲高まってはいたんですが「1分1秒の状態だ」とか「de:codeがあるからまだ買う時期じゃない」とか我慢していて、そんなことなかったので。*1
6月入って最新のEXPANSYSにLumia 630が並び始めたからどっち買うか迷ったんですけど、Lumia 630も同じくローエンドモデルで大差ないので、お財布にやさしい方を選びました。

Windows Phone 8.1への更新

お皿洗いとかハワイ行ったりしながら3時間くらい。

SDカードの認識

更新後にmicro SDを挿したんですが「見つかりません」と認識されなくて焦った。
冷静にぐぐ……bingって下記を参考に[設定] > [情報] > [電話のリセット]でリセットすると認識されました。

f:id:KatsuYuzu:20140607183113p:plain f:id:KatsuYuzu:20140607183416p:plain
アプリの保存先などをSDカードに設定して一安心。

感想

めっちゃぬるさく。きもちい。容量問題はmicro SDでクリアできるし、感度も速度も問題ないし文句なし!

*1:参加者への教材は素直に羨ましい

DependencyObjectから子要素をたどってBehavior、Actionをみつける

それで何が嬉しいかっていうとActionからサスペンドした時に、Actionを探して復帰することで、簡単に処理を継続できるとかとか。具体的にはFileOpenPicker用のアクションを作った時とか。

Windows ストアアプリは直接コマンドへ、Windows Phoneは復帰後にページからActionをみつけてコマンドへ。

子要素をたどる

まず、子要素をたどるにはVisualTreeHelperを使います。しかし、低級すぎて使いにくいので拡張メソッドを用意するといいです。

public static IEnumerable<DependencyObject> Children(this DependencyObject obj)
{
    if (obj == null) throw new ArgumentNullException("obj");

    var childrenCount = VisualTreeHelper.GetChildrenCount(obj);

    for (int i = 0; i < childrenCount; i++)
    {
        yield return VisualTreeHelper.GetChild(obj, i);
    }
}

public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj)
{
    if (obj == null) throw new ArgumentNullException("obj");

    foreach (var child in obj.Children())
    {
        yield return child;

        foreach (var grandChild in child.Descendants())
        {
            yield return grandChild;
        }
    }
}

これでpage.Descendants()なんてやると子要素をごっそり取れます。

Behavior、Actionをみつける

Behavior、Actionは表示要素ではないので子要素の中に入っていません。ここではInteraction.GetBehaviorsを使います。

要素を引数にして関連づけられたBehaviorCollectionが取得できます。ここまでくれば何とかなる!こちらも拡張メソッドを定義します。

public static T FindAction<T>(this DependencyObject obj)
    where T: IAction
{
    return obj
        .Descendants()
        .SelectMany(Interaction.GetBehaviors)
        .SelectMany(x =>
        {
            var eventTrigger = x as EventTriggerBehavior;
            if (eventTrigger != null)
            {
                return eventTrigger.Actions;
            }

            var dataTrigger = x as DataTriggerBehavior;
            if (dataTrigger != null)
            {
                return dataTrigger.Actions;
            }

            return null;
        })
        .OfType<T>()
        .SingleOrDefault();
}

Behaviorをみつける場合はSelectMany(Interaction.GetBehaviors).OfType()でいいです。Actionをみつける場合はActionsをもつBehaviorから展開する必要があるので、ここではSDKに含まれているBehaviorを対象としました。

Windows Phone 8.1 でピクチャーハブから写真を選択する #wpdev_jp

Windows Phone 8.1ではメモリが少ないデバイス用にいくつかのAPIで、アプリがサスペンドして、ユーザー操作後に復帰するようになっています。開発者的には気を遣うことが増えたわけですが、ユーザーフレンドリー。
そして、Windows ストアアプリとコードを共有するユニバーサル アプリでは、それらのAPIに対してWindows ストアアプリ用のHogeAsyncとWindows Phone用のHogeAndContinueが提供されています。HogeAndContinueを呼ぶとサスペンドするので、アプリの復帰処理を適切に行う必要があります。

ピクチャーハブから写真を選択する

従来のWindows Phoneアプリ(ここではWindows Phone 8以前)ではPhotoChooserTaskを使用しますが、ここではWindows ストアアプリと同様にFileOpenPickerを使用します。

ピクチャーハブの呼び出し

まず、拡張子や参照場所などの準備をします。

var picker = new FileOpenPicker
{
    .ViewMode = viewMode,
    .SuggestedStartLocation = PickerLocationId.PicturesLibrary
};

foreach (var extension in SupportedExtensions)
{
    // .jpgとか.pngとか
    picker.FileTypeFilter.Add(extension);
}

次に、対象とするプロジェクトに合わせた#ifディレクティブでAPIを呼びます。

#if WINDOWS_APP
    await picker.PickSingleFileAsync();
#else
    picker.PickSingleFileAndContinue();
#endif

サスペンドからの復帰

App.xaml.csOnActivatedで引数がIContinuationActivatedEventArgsか調べて処理します。

protected override void OnActivated(IActivatedEventArgs args)
{
    // 中略: ページの復帰処理

    var e = args as IContinuationActivatedEventArgs;
    if (e != null)
    {
        // 記事末の補助ライブラリのクラス
        new ContinuationManager().Continue(e);
    }
}

ライブラリのContinue内では、復帰したページがどのAPIからの復帰に対応しているか判別しています。

var fileOpenPickerPage = rootFrame.Content as IFileOpenPickerContinuable;
if (fileOpenPickerPage != null)
{
    fileOpenPickerPage.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
}

これでPageに引数として選んだファイルが渡されるのでアプリの用途で処理できます。

サンプル

サンプルは下記で公開しています。

サンプルアプリはPrismベースで、@okazukiさんのPrismAdapterを使用しています。

補助ライブラリ

サンプルの一部は開発中のライブラリ(Citrus.Interactions)を使用したものです。
ライブラリを使用すると、ピクチャーハブの呼び出しはPickPhotoActionというトリガーアクションを配置するだけでいいです。

<Button Content="PickPhotoAction">
    <Interactivity:Interaction.Behaviors>
        <Core:EventTriggerBehavior EventName="Click">
            <Interactions:PickPhotoAction CallbackCommand="{Binding PickPhotoCommand}" />
        </Core:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
</Button>