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
の色を変えるようにしています
動作結果
これを動かすと
MainWindow.xamlで{DynamicResource FillColor}
とした方だけが色が変わります。気持ち的には両方変わってほしいんですけどねぇ
ググるとStackOverflowに
というのが出てきて、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; }
結果
両方変わるよぉぉぉ
UWPのスタートアップ起動をあきらめた話
あきらめました
私は「twitterで流れている緊急地震速報のtweetを見張って地図上にプロットする」という俺得アプリを公開しています
多分アクティブユーザーは私一人でしょう。ログインするたびにアプリを起動するなんて面倒なので当然ですがスタートアップに登録できるようにしました、アクティブユーザーの100%が求める機能なので実装しないわけにはいきません。スタートアップ登録はStartupTask
クラスを使いました
OS起動時にちゃんと起動しない
いつのタイミングからか(あるいは実装初期からか)、ログイン後にアプリがちゃんと起動しなくなりました
最小化+スプラッシュスクリーンが出たままでメイン画面に遷移してくれません。これは自動起動ではなく手動で起動した場合には起きない現象です。そして適当に作った俺得アプリはメイン画面に遷移しないと機能が動かなかったのです
うれしい(?)ことにレビューにも同様の報告が上がりました
調査
手動起動ではちゃんと動いていたので初めはタイミングの問題(OS起動直後でネットワークが不安定とか)だと思っていたのですが、何度か再起動しても必ず同じ症状になりました
ということでログを仕掛けて実験してみました
起動時のログを確認したところ私の想定していた起動順(ライフサイクル)とは明らかに動作が異なっていました
UWPのライフサイクルは下記リンクにある通り、だと私もUWP世に出た当時に見た記憶があります
ですがStartupTask
で登録して再起動した場合このライフサイクル通りにはならないようです
ログを消してしまったので詳細は覚えていませんが、OnLaunched
の途中でOnSuspending
が呼ばれるような感じで、すぐにサスペンドされます。猶予はありませんでした
Microsoftのどこかのページで見たのですがStartupTask
で登録したアプリは起動時に必ず最小化されるらしいです。(最小化だけじゃなくサスペンドまでされるとは書いていなかったと思うけど)
この動作がOSの想定通りなのか仕様と違うのかはわかりませんが、そんな感じだったので私は考えることをやめました(俺得アプリだし調べるの面倒になったし)
最新版
ということで昨日リリースした最新版からはこの機能を消しました。すでにStartupTask
で登録している場合には起動時に解除するようにもしてあります(ただし解除処理は正常に起動しないとできない)
おまけ
アプリでのスタートアップ起動はあきらめましたが毎回手動起動なんて面倒なことは私にはできません。ということで手動で自動起動するようにします
以下は私の備忘録的なものですね。方法はショートカットファイルをOSのスタートアップフォルダに入れておく、という昔ながらの方法です
ショートカットを作成
ストアアプリはショートカットファイルを作るのが面倒でこまります
Win + R でファイル名を指定して実行を開いて、shell:appsfolder
を実行します
インストールしているアプリが出てくるので該当アプリ(今回はQuakeInfo)を探して、Shift + 右クリック -> ショートカットの作成
作成先はデスクトップになると思います
ショートカットをスタートアップフォルダに移動
Win + R でファイル名を指定して実行を開いて、shell:startup
を実行します
先ほど作成したショートカットファイルを出てきたフォルダに移動すれば出来上がり
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件を超えません
勘違い点
上記ページに単一の 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
は指定しておけ- 手抜きダメ絶対