プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

Creators Updateで標準がコマンドプロンプトからPowerShellに変わったけどなんの問題もなかった件

Windows 10 Creators Updateが出てから四か月以上たって今更ではありますが、標準のCUI(スタート右クリックやエクスプローラーでShift+右クリックで選べるやつ)がコマンドプロンプトからPowerShellに変わりましたね

CUIなんてiisreset ping ipconfig しか使わないからコマンドプロンプトでいいんだよ!と思って一度はコマンドプロンプトに変えたんですが、PowerShellで全部できるんですよね

f:id:puni-o:20170824094144p:plain

ということですぐにPowerShellに戻しました

おしまい

Xamarin.iOSで時計とやり取りするあれこれ

以前

puni-o.hatenablog.com

を書きましたが、あれから時が経ちました

久しぶりにいじったらいろいろ変わっていてはまったのでそんなことを

大前提

古いバージョンはストアに出さない

これさえなければはまることもなく古い奴を出しっぱなしで済んだんですけどね

watchOS 1のアプリはストアに出ません*1

ということでOS 2.0以降のプロジェクトを新規に作りました watchOSのプロジェクトは比較的小さいはずなので無理やりプロジェクトを変換するより作り直した方が速いと思います

ということで以下に書くのはwatchOS 3.2(phone側は10.3.1)を前提とします

通信方式

1.0のころ使っていた

WKInterfaceController.OpenParentApplication

は使えなくなりました

Watch Connectivityってやつを使うらしいっす

この辺を簡単にラップしたのがXamarinのページにあります

Watch Connectivity - Xamarin

上のサンプルではUpdateApplicationContextメソッドだけがラップされています

今回はこのメソッドではないものを使いました*2

SendMessageを使う

上記サンプルに追記して使います

送る側

public void SendMessage(NSDictionary<NSString, NSObject> sendData)
{
    Console.WriteLine($"SendMessage on {Device}");
    if (validSession == null || session.Delegate == null) return;
    try
    {
        validSession.SendMessage(sendData, 
                      (replyMessage) => 
        {
            //応答
        }, 
                      (obj) => 
        {
            Console.WriteLine("Send error." + obj);
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception SendMessage: {ex.Message}");
    }
}

一つ目の引数(NSDictionary)が送信データ、二つ目が受信側からのコールバック、三つ目がエラーのハンドラです

受ける側

public override void DidReceiveMessage(WCSession session, NSDictionary<NSString, NSObject> message)
{
    Console.WriteLine($"Receiving Message on {Device}");
}

public override void DidReceiveMessage(WCSession session, NSDictionary<NSString, NSObject> message, WCSessionReplyHandler replyHandler)
{
    Console.WriteLine($"Receiving Message on {Device}");
    replyHandler(new NSDictionary<NSString, NSObject>(
                new[] { (NSString)"" },
                new[] { (NSObject)null })
    );
}

二種類のDidReceiveMessageをoverrideする必要があります

両方実装しないと送信側にエラーが出ます(なぜ!)

replyHandlerを呼ぶと送信側の//応答部分が呼ばれるみたいです

制限

一度に送信できるデータサイズには制限があります

出典がどこかわかりませんがstackoverflow先生が教えてくれました

stackoverflow.com

SendMessageの場合は65536byteだそうです*3

このサイズを超えたデータを送信しようとした場合・・・何も起こりません

相手に届かないだけでエラーにはなりません(はまるよ!)*4

約65KBというサイズですが、watch全体に表示する画像をPNGで送ろうとした場合、少し複雑になると超えるくらいのサイズです

透過PNGでもなければJPEGで圧縮して送りましょう(送りました)

最後に

今回の内容もちゃんと調べてませんがそもそもXamarinでwatchアプリ作っている人なんていないと思うのでてきとうでいいですよね

*1:イツノマニ

*2:メソッドの種類についてはググれば出てくるです

*3:キリがいいのか悪いのか

*4:どこかのメソッドが呼ばれているのかもしれませんがそこまで追ってなかったので

WPFのItemsControlで仮想化が効かない時に確認すること

なんで仮想化しないんだろー、と悩んでいたら当たり前だったこと

準備

<DataTemplate>
    <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/>
</DataTemplate>

DataTemplateをこんな感じで書いて

public class TextConverter : IValueConverter
{
    private int _counter;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        System.Diagnostics.Debug.WriteLine($"Convert {_counter++}");
        return value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

こんなコンバーターを作っておきました

TextBlockが作られるたびにTextConverter.Convertが呼ばれて、何回呼ばれたかが_converterで分かります

あとはコードビハインドで

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    Items.ItemsSource = Enumerable.Range(0, 100);
}

って書いておけばいいですね (ItemsはItemsControlの名前)

_converterの数で何個TextBlockが作られたかわかるはずです

仮想化が効くとき

<ItemsControl x:Name="Items" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

TextBlockは画面に表示している分+1個くらいしか作られません

仮想化が効かないとき

<ScrollViewer>
    <ItemsControl x:Name="Items" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Converter={StaticResource TextConverter}}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

TextBlockは100個作られます

外のScrollViewerで隠れてはいますがItemsControlの高さが全項目分表示できる高さになっているので当たり前ですよね

こぴぺヨクナイ

なんでこんな書き方をしていたかというと、ItemsControlをスクロール可能にする方法をStackOverflowなどで調べると下の書き方が結構出てくるからなんですね

コピペはしませんが「こー書けばいいのねー」と何も考えずに書いたせいです

スミマセンデシタ