2021/07/10

ブシナビ

6/27(日)、函館蔦屋書店でのヴァンガードの非公認大会に出たとき、店員さんから参加者に以下の連絡がありました。
「来週以降の公認大会はブシナビを使うので、準備しておいてください」
で、少しだけ使ってみたのでこのブシナビを取り上げたいと思います。
「取り上げてみたいと思います」とは言いますが、正直不満が重なっていますので、マイナス意見が多めとなっています。

まずはブシナビの公式ページを見てみます。
少しスクロールするとAppleのApp StoreとGoogle Playへのリンクがあります。
大会の最中も結果の報告で使うとのことですから、スマホ前提なのでしょう。
ずっと下まで行くと「ブシナビWEBサイトはこちら」というリンクもあるので、WEBアプリとしても使えるようです。

さっそくアプリをインストールして見たところ、ログインを求められます。
アカウントを持っていなかったので登録しようとしたところ、いきなり不満点にぶち当たりました。

不満1:アカウント作成時に求められる情報が多すぎる
TCGの大会に参加するためのアプリですので、アカウントを作るにしても、メールアドレスと名前くらいあれば事足りると思います。
ところが、ブシナビでは以下を入力するように求められます。
・メールアドレス
・氏名とフリガナ
・HNとフリガナ
・住所
・電話番号
・性別
・生年月日
HNで参加したい人もいるでしょうから、本名とHNがあるのはいいでしょう。
性別と生年月日も、商品のユーザー層についての調査と考えると、あったほうが都合がいいのでしょう。
住所と郵便番号、必要ですか?
もしかすると公式大会などでエントリー書類やらを届ける宛先として使うのかもしれませんが、FAQにもこれらが必要な理由が説明されていません。
利用規約にはブシロードの個人情報保護方針に従って個人情報を取り扱うとあるので、そこまで疑わなくてもいいのではないかとも思うのですが、未だに納得していません。

「なんでこんなに必要やねん」と思いつつもアカウントを作って、大会にエントリーできるようになりました。
画面の左下に「イベント検索」というメニューがありますので、ここから検索して登録できそうです。
先程(2021/07/10 16時頃)のスクリーンショットがこちらです。
いくつかつっこみたいですね。

不満2:なぜいきなり検索結果が出る
「イベント検索」というくらいですから、タップしたら検索条件を入力するのが当然では?
検索条件を入力するには、右上の虫眼鏡マークをタップしてから進まねばなりません。
もしかしたら、俺はすでに「タイトル:ヴァンガード」で検索したことがあるからこのようになっているのかもしれませんが、それでもこの動作は納得できません。
「1回検索したら、次も同じ条件で検索するだろう」という考えで作ったのかもしれませんが、この考えは正しいのでしょうか?
「前はヴァンガードの大会を検索したけど、今回はヴァイスシュヴァルツの大会を検索したい」とか、検索条件を変更するのはそんなにレアな操作ですか?
たかが1タップでも手間は手間です。多くの大会に出るヘビーユーザーに不便な仕様というのは、営利企業のアプリとしてどうなのかね、と思います。

不満3:絞り込みが甘い
大会の情報はどこかのDBに格納されていて、検索条件に合致するものを表示していることと思います。
先程のスクショには7/10の10:00開始、つまり閲覧時点でもう開始されている(そしておそらくもう終了している)大会まで表示されています。
その他の大会日程を見ると毎週土曜日10:00から大会を行っているようですので、7/3の大会は検索条件に合致しないとして表示していないのでしょう。
以上から、表示条件のうち日程は年月日までしかチェックしていないと推測します。
開始時刻までチェックして、開始時刻を過ぎたイベントは表示しないほうがいいのではないでしょうか?
イベントを検索するということは、エントリーしようと考えて検索しているパターンが多いでしょう。
エントリーした人が結果報告のために大会のページを開くというのはありますが、その場合は「マイイベント」から開けるので、検索結果に表示されなくても問題ないはずです。
エントリーするために検索したのにエントリーできないイベントが表示されるのはノイズであり、利便性を下げているのではないかと思います。

目的の大会にたどり着いたのでエントリーを済ませます。
「マイイベント」からエントリーした大会を開いてみます。
無事にエントリーできているのはいいですが、利用ガイドにないものが表示されています。

不満4:アンケート入力って何
FAQを見ても「アンケート入力が押せません」というのはあるものの、アンケートが何なのかは記載されていません。
アンケートに答えないと参加できないのかと思ったら、7/4の大会ではアンケートなしで参加できました。
スマホの画面も大型化しているとはいえ、パソコンのディスプレイと比べれば圧倒的に小さいです。
その貴重なスペースを意味不明な項目で占めるのは不便です。意味のないものであれば撤去するべきではないでしょうか。

エントリーを確認できたのでアプリを終了しましょう。
では戻るボタンを押して・・・

不満5:戻るボタンの挙動がWEBページそのまま
戻るボタンを押すと、これまでアクセスしてきたページの履歴を遡っていきます。まるでブラウザの戻るボタンを押したかのようです。
そういえば、ブラウザで公式ページを開くと、「ブシナビWEBサイトはこちら」というリンクもありましたね。ちょっと見比べてみましょう。
1枚目がアプリ版、2枚目がWEB版のものです。アプリ版はログイン状態、WEB版は非ログイン状態という違いはありますが、さほど変わらないように見えます。
つまり、アプリ版も結局WEB版にアクセスしているのではないかと思います。
AndroidにはWebViewというクラスが用意されていて、これを使うとアプリ内でWEBページを表示できます。
メニューの表示の仕方が違うので、WEB版をそのまま表示しているわけではなさそうですが、HTMLを解析して必要なところを書き換えているとか、サーバー側でユーザーエージェントを見て返すHTMLを切り替えているとか、そういう細工を入れているのかもしれません。
使う側からすると内部の実装は大した問題ではないのですが、じゃあネイティブアプリ版は必要なの?と疑問が浮かんできます。

なんとかメインらしい画面に帰ってきました。
では戻るボタンを押して終了しましょう。もしかしたら終了確認のダイアログが出るかもしれないですね。

不満6:戻るボタンでアプリを終了できない
何も起きません。
最初に触ったときには思わず「は???」と声が出ました。
アプリのメイン画面で戻るボタンを押してアプリを終了する。Androidユーザーには当たり前の操作ではないでしょうか?
それができないとはどういうこと?
そもそも、普通にアプリを作ったらメイン画面で戻るボタンを押したら終了します。
俺が昔作ったAndroidアプリでも、特に設定を変えるとか処理を書くとかなしで、メイン画面で戻るボタンを押したらアプリが終了しました。
ということは、わざわざ戻るボタンのイベント処理を拾って握りつぶしているのでしょう。
一体何のために?
「誤ってアプリを終了しないように」ということなのかもしれませんが、それなら再起動すればいいだけですよね?
iOSにはOS単位での戻るボタンがないので大した問題にはならなさそうですが、Androidでは他アプリとの決定的な操作の違いになっています。
アプリを終了するというのは、アプリの使用に必ずついて回る最も基礎的な操作です。
そこは他のアプリを足並み合わせても良かった、というか合わせてほしかったところです。

では大会に出かけましょう。
結果の報告または承認のためにアプリの操作が必要なので、一応起動しておきます。

不満7:謎のスプラッシュ画面
今の俺がブシナビを立ち上げると以下の画面になります。
「START」を押すと不満5で貼ったスクショの画面に遷移します。
で、この「START」って何?
規約により、ブシナビのアカウントは1人で1つしか持つことができませんし、複数人で1つのアカウントを共有することもできません。
したがって、ログインアカウントを切り替えるという機能はこのサービスにおいて不要であると言えます。
だったらスプラッシュ画面を起動して、バックグラウンドでログインを済ませ、ログインとページ取得が完了したらメイン画面に遷移する、というのがスマートではないでしょうか?
今の実装ではアプリを起動するたびに意味なくボタンを1回押させられていることになります。
ユーザーはモンキーテストのための人員ではないのです。
不満2でも書きましたが、たかが1タップでもユーザーの負担を減らそうという設計思想はないのでしょうか?

ここまでずらずらと不満をぶちまけてきましたが、これを改善するためになにかできないか、と考えてみます。

改善案1:WEBアプリに一本化する
俺の不満は多いですが、特に強いのは戻るボタンなどの操作感です。
ネイティブアプリの感覚で触ってWEBアプリのように動くから違和感があるのです。
じゃあWEBアプリに一本化すればいいんじゃないか、ということです。
これならAndroid版/iOS版を作る必要がなくなりますから、WEB版の改善に集中することができます。
また、公式ページからのリンクもGoogle Play/App Storeが不要になり、スペースを少しだけ節約できます。

改善案2:もっとネイティブアプリに寄せる
ある意味案1と真逆です。
まず、必要な画面は決まっているのでテンプレートは用意できるはずです。
テンプレートに入れる情報は(たぶん)今と同じくWEB版にアクセスして、HTML解析でもすればよいでしょう。
検索やエントリーなどで情報を送信する必要が出てきますが、これもWEB版と同じ内容を同じ宛先に送ればいいはずです。
HTML解析も、HTTPリクエスト/レスポンスの生成も、探せばライブラリが転がっていることでしょうから、それを使えば実装の手間も少し減るでしょう。
iOS版も同じようにネイティブアプリに寄せると考えると、それでもリソースはかかってしまいます。
それでも今よりはマシになる、と思います。

ちなみに、現時点(2021/07/10 17:53)でのGoogle Play/App Storeの評価を見てみると・・・
1枚目がGoogle Play、2枚目がApp Storeです。
どっちも平均2点台、そして☆1が最多です。
Google Playでは「大会でこれを強制されるくらいならブシロードのゲーム全部引退する」というレビューもあります。
本気なのかはさておき、ここまでボロクソ言われているということは、使いにくいと感じているユーザーが多いことの表れではないかと思います。

ブシロードによると、本格的な利用開始は2021年8月を予定しているとのことです。
ブシナビのアプリリリースが3月ですので、テスト期間として5ヶ月用意している、ということですね。
しかし、緊急事態宣言などで大会開催を見送った店舗も多いことでしょう。
それで本当に必要なテストは実施できたのか?と疑いたくなります。

現在のアプリバージョンはGoogle Playで1.2.0、App Storeで1.2.1となっています。
メジャーバージョンが上がっていないということは、ブシロードとしてはこのアプリに重大な欠点はないと考えているとみなしてよいのでしょう。更新履歴も「軽微な不具合の修正」だけですからね。

俺個人としては、ビジネスの視点を度外視して言えば、改善案で出したように抜本的な見直しをしたほうがいいと思います。
もちろんすべての決定権はブシロードにあるわけですが、「こんなはずじゃなかった」と後で泣き言言うのだけは避けていただきたいです。

今日の1曲:

2021/07/03

レジェンズ Blu-ray BOX

6/25(金)夜、以下のTweetがいろいろな方のRTから俺のタイムラインに流れ込んできました。

本放送が2004年4月~2005年3月なので、16~17年の時を経てのBD化ですね。
即予約、とまではいかないものの、翌日まで考えた末に予約しました。

実家には放送時に録画したものが残っているのですが、年代が年代だけにVHSなんです。
録画したものなので、災害情報とかニュース速報のテロップが入っていたりもします。
普通はDVDを買うところですが、当時DVDプレイヤーを持っていなかった上、付近に取り扱う店もなかったので、DVDは買いませんでした。
当時は高校生だったし、中古CD漁りに時間とお金をつぎ込んで余裕もありませんでしたからね・・・
そんな状況でのBD-BOX化は、全く予想していない朗報でした。
しかも描き下ろしイラストのボックス+ブックレット付きとのこと。
届くのが今から楽しみです。

当時の思い出とかを書こうとすると、何度書いて推敲しても「あれもこれもある、まだまだある」が尽きないと思うので、それは置いておきましょう。
代わりに「BD-BOX化を期にこんな事があるといいな」をいくつか挙げてみようと思います。

・小説版の続編刊行
当時高校に持っていって仲の良いクラスメイトに布教しようとしていたこともあって、小説版にも思い入れがあるんです。
本来小説版はアニメ版の前日譚となるはずだったのですが、その後アニメ版は大幅な路線変更(では済まないレベルだった様子)があり、小説版は全く孤立したストーリーとなってしまいました。
小説としての続きを刊行して、小説版の完結を見たいのです。

・「リボーンだよ! シロンくん」単行本刊行
レジェンズの漫画版は月刊ジャンプで連載されていたのですが、それと並行してアニメ版に基づくギャグ時空の4コマ漫画「リボーンだよ! シロンくん」も連載されていました。
月ジャン連載の全14話+月ジャン増刊号の1話があったのですが、連載当時から今日に至るまで単行本化されたことがありません。
数えてみたところ120ページほどだったので、ジャンプコミックスで単行本とするにはページが足りなかったのかもしれません。
ですが、A5判の4コマ漫画単行本とかだと150ページに満たないものもあるので、その枠で刊行してもらえないでしょうか・・・?
雑誌のスクラップでしか読めないというのはあまりにも寂しいです。

・「龍Q伝説」の収録
これはBD-BOXについての願望です。
首都圏外郭放水路の施設である龍Q館でだけ見ることができる(できた?)「甦る龍Q伝説」、見たいんです。
すでに内容、仕様、特典が明記されている以上可能性はほぼゼロですが、実現したら嬉しいです。

結構前から思っていた願望を3つ挙げてみました。
同じ願望をお持ちの方、違った願望をお持ちの方、いろいろいると思います。
1つでも現実になってくれると嬉しいですよねぇ。

いつもなら何かの曲を貼って〆るのですが、今日はレジェンズBD-BOXのCM(「風のレジェンズ」ver.)で〆たいと思います。

2021/06/19

FAC製作記 その3 JsonSerializer.SerializeAsync()で何も出力されない

本題の前に少しだけ、これまでの反省といいますか、ちょっとした注意書きを。
前回が前提となる事柄に分量や気力などを吸い取られまくったので、これからはそういう事柄は適当にカットします。

ここから本題。
これまでの2回はダイアログでしたが、今回はJSONの処理についてです。
FACでは設定した内容をsettings.jsonというファイルにJSON形式で保存しています。
起動したときにファイルから設定を読み込んで、画面の「設定を保存」ボタンをクリックするとファイルに設定を書き込むようになっています。 (画面についてはFACについての最初の投稿にスクショを貼っています)

【FACでのJSONの扱い方】
.NET 5対応の前後でJSONのシリアライズ/デシリアライズに使っているものが異なっています。
.NET 5対応前:Jil
.NET 5対応後:JsonSerializer
Jilは個人が作っているライブラリ、JsonSerializerは.NET Core 3.0/C# 8.0から追加された標準機能です。

今回の課題と解決策は以下の通りです。

【課題】
JsonSerializerの非同期シリアライズ&書き込みを使うと何も出力されない。

【解決策】
JsonSerializerはJSONのシリアライズ/デシリアライズにだけ使う。
ファイルの読み書きはStreamReader/StreamWriterに任せる。

今回は.NET 5対応のときに作ったテスト用アプリのソースコードを見ていただいたほうが早いと思います。

まずは読み込みから。


読み込みのメソッド全部でこれ。
コメントアウトしているDeserializeAsync()がストリームを使った非同期デシリアライズ&読み込みですが、こちらはうまくいきました。

一方の書き込みが以下になります。


try-catchのtry部分しか収まりませんでした。
コメントアウトしたコードにあるSerializeAsync()が非同期のシリアライズ&書き込みメソッドです。
例外は発生しなかったのですが、「課題」に書いたとおり、書き込み完了後にファイルを開いても何も出力されていない、という現象が発生しました。

【試行錯誤】
まずはFileStreamを作るときに使ったメソッドが良くないのではないか?と疑いました。
スクショの77~79行目で、Open()を使ったり、オプション指定するコンストラクターを使ったりと試しています。
これらはいずれも効果なく、ファイルには何も出力されない状態が続きました。

次にそもそもシリアライズは成功しているのか?ということで、Serialize()でシリアライズした結果をデバッグ出力させてみることにしました。
スクショの82と83行目です。
こちらは成功して、JSON文字列が出力されました。

以上の2つから、JsonSerializerはシリアライズだけ担当してもらい、ファイルへの書き込みは別にすればうまくいくのでは?という発想が出てきます。
それを試したのが90~92行目です。
コメントにある通りで、ファイルにはJSON文字列が出力されていました。成功です。

さて、これで読み込みはすべてJsonSerializerで、書き込みはJsonSerializer+StreamWriterで処理している状態になりました。
これでもちろん動くのですが、書き込みがJsonSerializer+StreamWriterなら、読み込みはJsonSerializer+StreamReaderにしたほうが、対照というか見栄えというか、きれいに見えませんか?

ということで今度は読み込み側に手を加えてみます。
ファイルからの読み込みをStreamReaderに受け持ってもらい、JsonSerializerはデシリアライズだけ担当してもらいます。
読み込みの52と53行目ですね。
実行した結果は変更前と変わらず、こちらは特に引っかかりなく書き換えに成功しました。

なので、今回はJsonSerializerを使う範囲をJSONとオブジェクトの変換に限定し、ファイルの読み書きはStreamReader/StreamWriterを使うことで解決としました。

【考察】
最初はファイルへの書き込みが終わる前にファイルを開いてしまったのが原因かもしれないと思ったのですが、それなら書き込めずに例外が発生するはずですので、この可能性はないでしょう。
次にGitHubでJsonSerializerのソースコードを見てみたのですが、わかりませんでした。
処理をたどっていくと、どうやらストリームに直接書き込まずにバッファリングしているようですが、バッファリングしようがしまいが最終的に出力先に書き込むのは変わらないはずなので、これのせいだとも思えません。
なんだったんでしょうね・・・?

【参考資料】

今日の1曲:


2021/06/06

FAC製作記 その2 ダイアログの表示をどう実装するか

前回に続きダイアログがテーマです。
前回はそもそもダイアログを表示できないところからスタートし、それを解決して表示できるようになりました。
今回はダイアログを表示できるところからスタートしますが、どこでどのように表示するかがポイントになります。
「いや、必要なタイミングで表示させるだけでしょ?」となりそうですが、その表示させる「だけ」にも考えなければならないことはあるわけです。

今回の課題と解法は以下のとおりです。

【課題】
MVVMの役割分担を壊さずにダイアログを表示したい。
.NET Framework時代はMVVMフレームワークの機能を使って実現していたが、.NET移行でライブラリを乗り換えたので、別の方法を探さなければいけない。

【解法】
ヘルパークラスを使う。
ヘルパークラスはM/V/VMいずれでもない名前空間に作る。

本題の前にMVVMについて。
MVVMは「Model」「View」「ViewModel」の頭文字を取ったもので、この3つでアプリケーションを構成します。
それぞれの役割は以下の通り。
  • Model:アプリケーションの本体、UIに関わらない内部の処理
  • View:ユーザーに見える部分、画面とか
  • ViewModel:ModelとViewの橋渡し
以下はWikipediaの図ですが、MとVM、VとVMがやり取りしてアプリの機能を実現します。
(作者:Ugaya40, CC BY-SA 3.0)

例えばFACの「ファイルを分類する」という機能の場合は以下になります。
1. ユーザーがVの実行ボタンをクリックする
2. VからVMに実行ボタンが押されたことが伝わる
3. VMはMのファイル分類を実行する
4. MからVMにファイル分類の結果が返される
5. VMからVにファイルの分類が完了したことが伝わる
6. Vがユーザーにファイルの分類が完了したことを見せる

MVVMは必ず従わなければいけない規則というわけではありません。
しかし、1箇所に全部まとめてしまうとメンテナンスも大変なので、FACでもMVVMパターンを採用しています。

ではMVVM的にダイアログはどう扱うべきでしょうか?
ダイアログはユーザーに見える部分ですから、Vが扱うべきである、と考えることができます。
しかしVは表示とユーザーからの入力を受け付けることが役割ですから、「いつ」「何を」表示するのか決めるのは越権行為です。
「いつ」「何を」表示するか決めるのはM、またはMとVMの役目と言えます。
ではMとVMで扱うのが正しいのかというと、今度はMやVMが直接画面を触るという越権行為が発生します。

もう1点、MVVMの解説図に「Data Binding」というのがあります。
日本語でもそのまま「データバインディング」と言われます。
Vでイベント(例:ボタンのクリック)が発生したときにVMのどの処理を実行するか、Vにどんなものを表示するのか、など、VとVMを紐付ける仕組みです。
MVVMパターンを使う上で重要なポイントではあるのですが、.NETや.NET Framework標準ではこの機能が用意されていません。
ですので、MVVMパターンを使うときは、データバインディングを含めてフレームワークのライブラリを使うことが一般的です。

ではFACでは.NET Framework時代にどうしていたのか、.NETではどうするのか、1つずつ書いていきます。

【.NET Framework時代】
MVVMフレームワーク:MVVM Light Toolkit
MVVM Light Toolkitはその名の通り薄い軽いフレームワークでした。
シンプルですが、データバインディングを始め必要なものは用意されています。

この時代はダイアログの表示をMVVM + Messengerで実現していました。
Messengerはその名の通り、VMからのメッセージをVに渡す役割をします。
VはMessengerに「xxというメッセージが来たらこの処理を呼んでくれ」と登録します。
VMは必要なときにMessengerにxxというメッセージを送ります。
これでVM→(メッセージ)→Messenger→(登録された処理の呼び出し)→Vという流れができます。
なお、本来はMessengerも自分で作らなければならないのですが、MVVM Light ToolkitにはMessengerも用意されています。

今回の場合、VがMessengerに登録する処理でダイアログを表示しています。
こうすることでVMから直接Vを触ることなく、つまりMVVMの役割分担を壊さずにダイアログを表示することができます。

【.NET時代】
MVVMフレームワーク:Prism
Prismはある意味MVVM Light Toolkitとは逆で、盛り沢山なフレームワークです。
機能的にはMVVM Light Toolkitで十分なのですが、最新版のリリースが2018年9月と古く、.NETに対応する様子もないため、.NETへの移行を期にPrismに乗り換えることにしました。

PrismにもMessengerに使えるEventAggregatorが用意されていますが、調べてみるとMVVM Light ToolkitのMessengerと全く同じというわけにはいかないようです。
人によっては「EventAggregatorはVM間の通信を実現する」と言い切っているくらいで、VとVM間の通信に使うのはちょっと怖くなってきました。

ではどうするのか。ここで先に書いた解法です。
FACのフォルダ/名前空間構成は以下のようになっています。
「Models」「ViewModels」「Views」にMVVMのそれぞれにあたるクラスが入ります。
そして、選択して色が変わっている「Helpers」というフォルダがあります。
ここにダイアログを表示するDialogHelperというクラスを入れてあります。(IDialogHelperはDialogHelperで実装する内容を定めたインターフェースです)

ダイアログを表示するときはVMがDialogHelperを呼び出します。
VM→(処理の呼び出し)→Helperという流れですね。
この方式でも、VMから直接Vを触ることなくダイアログを表示することができます。

【残った課題】
Helperクラスを使うことで、「VMが直接ダイアログを表示したりVを触ったりしない」という条件はクリアできました。
しかし「表示はVが受け持つ」という条件はクリアできていません。
.NETに移行するときにはライブラリの乗り換えなど大きな問題が積み重なっていたため、「VMに表示処理が入っていなければヨシ!」と現場猫化しました。
ということで、.NET Framework時代と同じく、ダイアログの表示はVに戻せないか、後日再チャレンジして見るかもしれません。

【参考資料】

2021/05/30

FAC製作記 その1 ダイアログを表示するときに例外発生

先日の通り、FACを作るときに遭遇したあれやこれやを早速書いていきます。

本日のお題はダイアログです。
GUIアプリケーションであれば、ダイアログを使わないもののほうが少ないのではないでしょうか。
FACでも結果の通知とフォルダーの選択にダイアログを使っています。

今回の現象と解決策だけ書くと以下のとおりです。

【何があったのか】
.NET Framework 4.7.2から.NET 5に移行するとき、ダイアログの表示で例外が発生した。

【どうしたのか】
ダイアログのライブラリを乗り換えた。

それぞれについて詳しく書く前に、.NET Framework 4.7.2時代のダイアログについて説明します。

【コトが起きる前】
.NET Framework 4.7.2時代、FACはダイアログとしてWindows API Code Packを使っていました。
これはもともとMicrosoftが提供していたもののようですが、俺が知ったときにはすでにMicrosoft直々の公開は終了していて、いろいろなユーザーがかつて公開されていたものをそのままor独自のモディファイを加えて公開している状況でした。
上記のリンクはそれらのうち俺が使っていたものです。

たかだかダイアログでなぜライブラリを使っているのかというと、標準では足りなかったからです。
FACはGUIフレームワークとしてWPFを使っています。
WPFにもダイアログは用意されているのですが、問題があります。
用意されているのは通常の通知用ダイアログのみで、フォルダーを選択するダイアログはないのです。(FACでは使っていませんが、ファイル選択ダイアログもない)

WPFでフォルダー選択ダイアログを使いたい場合、Windows API Code Packは定番と言っていいでしょう。
それについて解説しているブログもすぐ見つかるくらいなので、王道と言ってもいいかもしれません。
Windows API Code Packには通常のダイアログ(※)も用意されているので、ダイアログはすべてWindows API Code Packを使っている、というのが.NET Framework時代のFACでした。
※正確には機能が強化されたTaskDialogなのですが、通知用であれば同じ感覚で使えます。

【起きたこと】
FACを.NET 5に移行するため、事前検証としてテスト用アプリを作って、色々と動作を確認していたとき、事件は起こりました。
Windows API Code PackのGitHubのreadmeにも「.NET 5をサポートしてるよ!」と書いてあるので、同じようにコード書けば動くだろー、と楽観視していました。

まずは通常のダイアログの動作を確認します。


例外が発生しました。
サポートしてるんじゃなかったんかい。

次にフォルダー選択ダイアログの動作を確認します。


こちらは.NET Framework 4.7.2のときと同じように表示されます。
なんでやねん。

【例外のメッセージから調べる】
気を取り直して、例外のメッセージを確認してみます。

System.NotSupportedException: 'TaskDialog feature needs to load version 6 of comctl32.dll but a different version is current loaded in memory.'
EntryPointNotFoundException: Unable to find an entry point named 'TaskDialogIndirect' in DLL 'Comctl32.dll'.

「TaskDialogはcomctl32.dllのバージョン6以上が必要だが、そうでないバージョンがロードされている。」「comctl32.dllに'TaskDialogIndirect'というエントリーポイントがない。」とあります。
調べてみると、dllファイルのバージョンをマニフェストファイルで指定すれば良い、とのこと。

しかし、ここで俺は以下のように思いました。
「.NET Frameworkではマニフェストファイルなんか不要だった。.NETになって必要になるのは納得いかない」
今思い返すと逆ギレですが、「じゃあ別のダイアログを探そう」という発想に行き着きました。

【乗り換え先のダイアログを探す】
「.NET 5対応」「NuGetでインストール可能」を条件に探してみると、Ookii.Dialogsというものが見つかりました。
余談ですが、こちらの作者の方は東大に留学経験があり、サイトのトップにも「Ookii -発音は/oːkiː/(だいたい"oh-key")- は日本語でbigという意味で、私のラストネームと同じ意味です」とあるので、「大きい」を意識した命名です。
さっそくインストールして、Windows API Code Packのダイアログ類を使っていたところをOokii.Dialogsに差し替えてみます。

まずは通常のダイアログから。


問題なく表示されますね。
続いてファイル選択ダイアログも。


こちらも問題なく表示されました。

ということで、この件はWindows API Code PackからOokii.Dialogsへの乗り換えという形で解決しました。

【原因と解決策の考察】
精査したわけではないので、俺の想像であることをご承知おきください。
すでに書いたように、Windows API Code PackはもともとMicrosoft謹製です。
名前から想像するに、WindowsのAPIをごく薄くラップしたライブラリ類なのでしょう。
したがって、.NET Frameworkと .NET 5の差分(標準で参照するcomctl32.dllのバージョンが違う?)を隠蔽しきれなかったのではないかと思います。

Ookii.DialogsはTaskDialogクラスを独自に実装しています。
そのためにcomctl32.dllを参照する必要がなく、Windows API Code Packで地雷になった箇所の影響を受けなかったのではないでしょうか。

【参考資料】

今日の1曲:


2021/05/22

作ってるもの

ブログの記事としてはお久しぶりです。
前回の記事が2016年の10月ですので、約4年半ぶりです。

この間特に何かあったとかではないのですが、
「1週間空いてしまった、来週は書こう」
「そうこうしてるうちに1ヶ月空いてしまった、来月こそ書こう」
「1年空いたし、もうよくね?」
という感じで、ずるずると4年半以上何も更新がないという事態になっていました。
これからは不定期でも年に1回くらいは更新したいな、と思っています。

今回のタイトルの「作っているもの」ですが、パソコン上で動かす簡単なツールを作っています。
名前を「Fur Affinity Classifier」としました。以下では頭文字取って「FAC」と記載します。
ソースコードや実行ファイルはGitHubにおいてあります。
READMEは英語で書いてありますが、今回は日本語での説明兼宣伝をさせてください。

【何これ?】
Fur Affinity(以下FA)からダウンロードしたファイルを、ユーザーのIDごとにフォルダーに分類するツールです。
FAについてはご存じの方が多いと思うので、詳しい説明は省略しますが、ケモノに特化したpixivと考えていただいてよいと思います。
classify (分類) に -er を付けて「分類機」という意味合いで名付けました。

【なんで作ったの?】
俺はFur Affinityで気に入ったイラストなどがあると自分のパソコンに保存するのですが、ファイルの分類が手間でした。
ファイルを分類するためには
1. ファイル名を見て
2. ユーザーIDを確認して
3. ユーザーIDに応じたフォルダーを開いて
4. ファイルを移動する
という手順が必要でした。
これがファイル2~3個とかならまだなんとかなりますが、5個10個となると面倒、20を超えると分類に割く時間がもったいないくらいになります。
じゃあ自分の勉強も兼ねて、自動でやってくれるツールを作ってしまえ、というのがスタートラインになっています。

【何ができるの?】
「何これ?」の通り、FAからダウンロードしたファイルを、フォルダーに分類することができます。
FAからダウンロードしたファイルは所定の形式になっていますので、後述する対象のフォルダー内のファイルを走査して、合致するファイルを移動します。
また、「このユーザーはIDと違うフォルダーに振り分ける」という設定もできます。
日本のような非ラテン文字圏だとこういう事態は起きやすいのではないかと思います。

【どうすれば使えるの?】
FACの実行には.NET 5が必要になります。
また、FAC内でも設定が必要です。
実際に俺が使っているときのスクリーンショットを以下に貼ります。
フォルダー名は黒で潰してありますがご了承ください。


スクショのうち「フォルダー設定」、つまり「ダウンロードしたファイルが入っているフォルダー」「IDごとのフォルダーが入っているフォルダー」の設定が必須になります。
IDと違うフォルダーに振り分けたい場合は、スクショで「IDと異なるフォルダーに分類する」となっている一覧に入力することで、「ID」列のIDは「フォルダー」列のフォルダーに振り分けるようになります。

【注意することとかある?】
あくまでも「俺が使うためのツール」を公開したものであるので、要望をいただいた場合は検討しますが、対応をお約束することはできません。
ライセンス(MIT)のとおり、保証とかは一切ありません。

今後はFACを作る上で困ったこととその解決法とか、そういうことも書いていきたいなと思っています。
「え、こんな簡単なモンでもそんなんあるの?」と思われるかもしれませんが、あるんだなこれが。

今日の1曲:


2016/10/08

ニコニコ町会議函館

ニコニコ町会議、過去、北海道では札幌や室蘭などで開催したことがあるようですが、今年は函館開催なので、遊びに行ってきました。
と言っても、開催を知ったのは、開催まで1週間を切った9/25でした。
ヴァンガードの対戦動画を投稿しており、蔦屋書店で毎週のように会っているガランドウさんが「次の土曜町会議あるから~」という感じで言っていたのです。

で、家に帰ってから町会議のページを見てみると、コスプレ用の更衣スペースも用意されるとのこと。
これは着ぐるみ持参で突撃するしかねえ、と計画を立てたは良いのですが、問題が2つあったのです。

1.6月に静岡に行ったとき、スーツケースのキャスターが壊れた
2.開催日の午後一で予定を入れていた

2.については11:30会場、18:00閉会なので、まあそこそこ時間はできるだろうと判断しました。
深刻なのが1.で、キャスターの壊れたスーツケースを引きずって移動なんかしたくないです。
ということで、開催前日の退勤時に、寄り道して新しいスーツケースを買ってきました。

前日はスーツケースの宣伝タグなどを外して使えるようにして、どうやって収納するかのシミュレーションをやってました。
このときに気づいたんですが、新しい(2代目)スーツケースは初代よりも大きくて、初代のときは斜めに入れていた尻尾が、垂直に入れられるようになりました。
万が一同じスーツケースを使っている人がいたときのために、識別用にチェーン付きの小さいぬいぐるみをぶら下げて、前日準備は完了としました。

明けて町会議当日。
買い出しを済ませたあと、用事が済んだらすぐに出られるように、荷物を整えました。
14時までには用事が完了する予定だったんですが、完了したのが13:40過ぎ。
町会議会場に行けるバスに間に合うかの瀬戸際で、走ってなんとか間に合ったものの、息を整えられたのが降りる数分前という体たらく。

それでも会場には迷うことなく到着しました。
しかし、それからコスプレ更衣室を探すのに5分くらいかかりました。
メインの会場から道路を挟んだところ、というのは会場マップでわかっていたんですが、その道路がどこかわからなかった。
自分が住んでいる町とはいえ、普段通らないところだとこうなるというダメなパターンですね。

バスを降りたところで、今回参加する友人に連絡したところ、一旦会場を離れており、また会場入りするとのこと。
で、更衣室に入りまして、余裕を持って獣化。
獣化完了して更衣室を出たのが、15時ちょっと前だったと思います。
ぶっちゃけたところ特に目的もなくほっつき歩いていたところ、親子連れの方に声をおかけいただいて、お子さんと一緒に写真撮りました。
さらに、地図にある「町シンボルカー」の係の方に「よかったら撮っていきませんか?」と声をかけられて、こんな写真を撮っていただきました。


耳と髪隠れちゃってるやん。

その後、会場に戻ってきた友人たちと合流。
で、こんな写真撮りました。
差し出されてるのはラムのザンギです。
このあと美味しくいただきました。

その後も時折「写真いいですか?」と声をかけていただいて、女子中学生(たぶん)とか、お子さんとかと写真を撮りました。
握手して、手の肉球をぷにぷにされたりとかもありましたね。

コスプレ更衣室があるということは、当然レイヤーさんもおられるわけで、レイヤーさんにも「一緒に写真いいですか?」と何度かお声掛けいただきました。
で、写真をトリミングしてまとめてアップロードしていた方がいたので、「うちの狐とのツーショット単体でいただけませんか?」とお願いして、ありがたく頂戴しました。
今回の町会議は、俺にとっては初めてのことのオンパレードでした。
初めての「着ぐるみオフ以外での獣化」「屋外での獣化」「レイヤーさんがいる中での行動」というのがとても大きかったです。
さらに、2時間ぶっ続けで獣化してたのも、最長記録を更新したと思います。
オンパレードというか、初でないものがなかったという方が正しいかも。
そんな状況であっても楽しめたのは、たぶんニコニコという、ある意味何でもありな場だったからだと思います。
スタッフの皆様、参加された皆様、お疲れさまでした&ありがとうございました!

今日の1曲: