プログラムの事とか

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

WPFのDynamicなSolidColorBrushとDynamicなColor

WPFネタです。半日くらい悩んでいました

準備

App.xaml抜粋

<Application.Resources>
    <Color x:Key="FillColor">#00ffff</Color>
    <SolidColorBrush x:Key="FillBrush" Color="{DynamicResource FillColor}"/>
</Application.Resources>

MainWindow.xaml

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Rectangle Fill="{DynamicResource FillBrush}"/>
    <Rectangle Grid.Column="1">
        <Rectangle.Fill>
            <SolidColorBrush Color="{DynamicResource FillColor}"/>
        </Rectangle.Fill>
    </Rectangle>
    <Button Grid.Row="1" Content="Red" Click="RedButton_Click"/>
    <Button Grid.Row="1" Grid.Column="1" Content="Green" Click="GreenButton_Click"/>
</Grid>

MainWindow.xaml.cs

private void RedButton_Click(object sender, RoutedEventArgs e)
{
    Application.Current.Resources["FillColor"] = Colors.Red;
}

private void GreenButton_Click(object sender, RoutedEventArgs e)
{
    Application.Current.Resources["FillColor"] = Colors.Green;
}

やっていること

  • アプリケーションのリソースにFillColorというキーのColorとそのColorをDynamicResourceで参照しているFillBrushを用意します
  • Windowの左側のRectangle.FillにはFillBrushをDynamicResourceで入れておきます
  • 右側のRectangle.FillにはXAMLでSolidColorBrushを書いて、そのColorを{DynamicResource FillColor}とします
  • ボタン二つは押したときにFillColorの色を変えるようにしています

動作結果

これを動かすと

f:id:puni-o:20201215182204g:plain

MainWindow.xaml{DynamicResource FillColor}とした方だけが色が変わります。気持ち的には両方変わってほしいんですけどねぇ

ググるとStackOverflowに

stackoverflow.com

というのが出てきて、SolidColorBrushはFrameworkElementでもFrameworkContentElementでもないからダメ、みたいな解答が書いてあります。サンプルのRectangleが両方色が変わらなかったら納得なんですけどね。ちなみに英語力皆無なので本当にそういうことが書いてあるのかは知りません

おまけ

App.xamlの定義をMainWindow.xamlに持ってきて同じようなことをしてみます

MainWindow.xaml

<Window.Resources>
    <Color x:Key="FillColor">#00ffff</Color>
    <SolidColorBrush x:Key="FillBrush" Color="{DynamicResource FillColor}"/>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Rectangle Fill="{DynamicResource FillBrush}"/>
    <Rectangle Grid.Column="1">
        <Rectangle.Fill>
            <SolidColorBrush Color="{DynamicResource FillColor}"/>
        </Rectangle.Fill>
    </Rectangle>
    <Button Grid.Row="1" Content="Red" Click="RedButton_Click"/>
    <Button Grid.Row="1" Grid.Column="1" Content="Green" Click="GreenButton_Click"/>
</Grid>

MainWindow.xaml.cs

private void RedButton_Click(object sender, RoutedEventArgs e)
{
    Resources["FillColor"] = Colors.Red;
}

private void GreenButton_Click(object sender, RoutedEventArgs e)
{
    Resources["FillColor"] = Colors.Green;
}

結果

f:id:puni-o:20201215183219g:plain

両方変わるよぉぉぉ

UWPのスタートアップ起動をあきらめた話

あきらめました

f:id:puni-o:20200924091632j:plain

私は「twitterで流れている緊急地震速報tweetを見張って地図上にプロットする」という俺得アプリを公開しています

www.microsoft.com

多分アクティブユーザーは私一人でしょう。ログインするたびにアプリを起動するなんて面倒なので当然ですがスタートアップに登録できるようにしました、アクティブユーザーの100%が求める機能なので実装しないわけにはいきません。スタートアップ登録はStartupTaskクラスを使いました

OS起動時にちゃんと起動しない

いつのタイミングからか(あるいは実装初期からか)、ログイン後にアプリがちゃんと起動しなくなりました

最小化+スプラッシュスクリーンが出たままでメイン画面に遷移してくれません。これは自動起動ではなく手動で起動した場合には起きない現象です。そして適当に作った俺得アプリはメイン画面に遷移しないと機能が動かなかったのです

うれしい(?)ことにレビューにも同様の報告が上がりました

調査

手動起動ではちゃんと動いていたので初めはタイミングの問題(OS起動直後でネットワークが不安定とか)だと思っていたのですが、何度か再起動しても必ず同じ症状になりました

ということでログを仕掛けて実験してみました

起動時のログを確認したところ私の想定していた起動順(ライフサイクル)とは明らかに動作が異なっていました

UWPのライフサイクルは下記リンクにある通り、だと私もUWP世に出た当時に見た記憶があります

docs.microsoft.com

ですがStartupTaskで登録して再起動した場合このライフサイクル通りにはならないようです

ログを消してしまったので詳細は覚えていませんが、OnLaunchedの途中でOnSuspendingが呼ばれるような感じで、すぐにサスペンドされます。猶予はありませんでした

Microsoftのどこかのページで見たのですがStartupTaskで登録したアプリは起動時に必ず最小化されるらしいです。(最小化だけじゃなくサスペンドまでされるとは書いていなかったと思うけど)

この動作がOSの想定通りなのか仕様と違うのかはわかりませんが、そんな感じだったので私は考えることをやめました(俺得アプリだし調べるの面倒になったし)

最新版

ということで昨日リリースした最新版からはこの機能を消しました。すでにStartupTaskで登録している場合には起動時に解除するようにもしてあります(ただし解除処理は正常に起動しないとできない)

おまけ

アプリでのスタートアップ起動はあきらめましたが毎回手動起動なんて面倒なことは私にはできません。ということで手動で自動起動するようにします

以下は私の備忘録的なものですね。方法はショートカットファイルをOSのスタートアップフォルダに入れておく、という昔ながらの方法です

ショートカットを作成

ストアアプリはショートカットファイルを作るのが面倒でこまります

Win + R でファイル名を指定して実行を開いて、shell:appsfolderを実行します

f:id:puni-o:20200924095917j:plain

インストールしているアプリが出てくるので該当アプリ(今回はQuakeInfo)を探して、Shift + 右クリック -> ショートカットの作成

f:id:puni-o:20200924100219j:plain

作成先はデスクトップになると思います

ショートカットをスタートアップフォルダに移動

Win + R でファイル名を指定して実行を開いて、shell:startupを実行します

f:id:puni-o:20200924100339j:plain

先ほど作成したショートカットファイルを出てきたフォルダに移動すれば出来上がり

Azure Table Storage のContinuationTokenを盛大に勘違いしていた件

現在進行形でやらかしています。これから修正しなきゃいけないのでざっくりやらかしたことをメモ(調査も検証もなし)

コード

async Task<TableEntity[]> Get(long id1,long id2){
    var tableClient = _storageAccount.CreateCloudTableClient();
    var table1 = tableClient.GetTableReference("Table1");
    var table1Query = new TableQuery<TableEntity>();
    table1Query.FilterString = TableQuery.CombineFilters(
        TableQuery.GenerateFilterConditionForLong("Id1", QueryComparisons.Equal, id1),
        TableOperators.And,
        TableQuery.GenerateFilterConditionForLong("Id2", QueryComparisons.Equal, Id2));
    var entities = await table1.ExecuteQuerySegmentedAsync(table1Query, null);
    return entities.ToArray();
}

こんな感じの関数つくります

  • Table1というテーブルからId1,Id2を条件に全件取得しようとしています (PartitionKey指定していないのがそもそも・・・とかそういうのは無視で)
  • Id1,Id2を条件にした場合データの件数は多くても10件を超えません

勘違い点

docs.microsoft.com

上記ページに単一の EGT で最大 100 個のエンティティを処理できますと書いてあります。ほかにもTable Strorage関連のドキュメントなどでは100という数字(上限)がよく出てくるような気がします (気がしているだけ)

そこで、考えることを常にやめている私は「100件超える可能性があるときはContinuationToken使ってループ書こう」という結論になってしまいました

現象

上記関数はTable1のレコード数が100件(くらい)までは問題なく動きます。しかしデータ数が増えていくと後半の該当データは1回目のクエリでは帰ってこなくなります (そしてentities.ContinuationTokenがnullではなくなります)

今回は4件のデータを取ってくるのに2~3回のループが必要でした

どーいうこと?

面倒なので調べていません。予想で書きます

100件(たぶん)の制限は返ってくるデータ数ではなくAPIが読み込むデータ数なんだと思います。PartitionKeyを指定しない場合は全件検索(?)になるとかどこかに書いてあった気もしますし、今回は指定していなかったのでAPIは全件読み込むつもりでいるのでしょう 。そして最初の100件(くらい)から条件に合った数件のデータのみを返してきたんだと思います。PartitionKeyを指定した場合はPartitionKey内のデータのみを見るんでしょうね (きっとどこかにそういうドキュメントがあるんだろうな~)

私は同じような処理(件数が少ないことが分かっているのでループを書かない)を結構書いてしまっていますが、問題は今まで起きませんでした。それはほかの処理では必ずPartitionKeyを指定していたからで、いずれほかでも同じ問題が出てくるんじゃないかなー、と遠い目をしています

結論

  • PartitionKeyは指定しておけ
  • 手抜きダメ絶対