2016年6月28日火曜日

Bloggerのhttps強化によるSyntaxHighlighter周りの修正

(2016/9/22追記)Google Driveのホスティングサービス終了により、この記事の方法では正常に動作しなくなりました。対策として、Firebaseのホスティングサービスを使用する方法があります。詳しく?はこちらへ。


どうも久しぶりです。

最近、自分のブログにアクセスしようとするとやたら重たいなということに気づき、いろいろ探ってみるとSyntaxHighlighterが怪しいということになりました。

SyntaxHighlighterはソースコードの予約語などをハイライトするツールで、多くの言語にも対応していたためこのブログでも使用していたのですが、どうやら最近そのソースコードのハイライトがされなくなっているようでした。

なぜだかわからないまま試行錯誤していたのですが、ようやく原因がわかりました。
Bringing HTTPS to all blogspot domain blogs 
どうも今年の5月の頭にblogspotドメインからありとあらゆるドメインへのhttpsリダイレクトがデフォルトでONになったようで、当ブログも例外ではありませんでした。
このブログではSyntaxHighlighter公式のホスティングサービスを用いて必要なjsファイルなどを読み込んでいましたが、そのホスティング サービスはhttpsに対応しておらず、そのリダイレクトで読み込みに時間がかかった挙句失敗するという非常に残念な状態になっていたようです。

上記のブログにはこのようなセンテンスもありました。
Please be aware that mixed content may cause some of your blog's functionality not to work in the HTTPS version. Mixed content is often caused by incompatible templates, gadgets, or post content. While we're proactively fixing most of these errors, some of them can only be fixed by you, the blog authors.
(筆者拙訳)混在コンテンツ(HTTPSとHTTPが混在したコンテンツ)はブログの一部機能が正常に動作しなくなる原因となりうる点に注意して下さい。テンプレートやガジェット、投稿コンテンツがHTTPSに対応していない場合、混在コンテンツになりえます。我々はそれらの問題の多くを積極的に修正しているところですが、一部はブログ著者のあなたでなければ修正できないものもあります。
ということで、今回の問題はその「ブログ著者じゃないと修正できない問題」に該当するようです。


問題がわかれば対処はどうにかなります。

まずは、SyntaxHighlighterをDLし、自分のGoogle Driveに保存して公開します。
そして、下記のコードをテンプレートのHTMLのヘッダー要素の一番最後にコピペします。

<!-- Begin SyntaxHighlighter-->
<link href='https://googledrive.com/host/[ID]/styles/shCore.css' rel='stylesheet' type='text/css'/> 
<link href='https://googledrive.com/host/[ID]/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shCore.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushCpp.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushCSharp.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushCss.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushJava.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushJScript.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushPowerShell.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushPython.js' type='text/javascript'/> 
<script src='https://googledrive.com/host/[ID]/scripts/shBrushXml.js' type='text/javascript'/> 
<script type='text/javascript'>
window.setTimeout(function() {
    SyntaxHighlighter.config.bloggerMode = true;
    SyntaxHighlighter.defaults['toolbar'] = false;
    SyntaxHighlighter.all();
}, 20);
</script>

[ID]の部分にはGoogle Driveの共有フォルダのアクセスIDを入力します。
IDとはGoogle Driveで共有設定したときに出てくる画面のid=の文字列です。上の画像のモザイクを掛けた部分です。
もちろん、テーマや読み込むスクリプトなどは必要に応じて変更すると良いでしょう。

また、最後に
SyntaxHighlighter.defaults['toolbar'] = false;
の一文を設けていますが、これを置くことでソースコード画面の右上の「?」マークが消えます。最初は消さなくてもいいかな~って思っていましたが、1行目に横長のコードを書くと鬱陶しくて仕方ないんですよね、これ。


というわけで、無事SyntaxHighlighterの問題を解決し、ブログの読み込みも早くなりましたとさ。めでたしめでたし。

P.S. ついでにブログのデザインも変えてみました。自分で目がチカチカするな~とか思ったらまた別のデザインにするかもしれません。

2016年2月9日火曜日

expressionとstatement

たまにはプログラミング言語についての話をしてみようかと思います。

最近、Twitterで中括弧戦争みたいなのが起きているらしいです。ぶっちゃけ、プログラミング言語なんてコンパイルできれば正義ですし、なおかつ保守性が高いコードを書けるのならば二重丸です。中括弧を付ける場合も付けない場合も、どこかに改行を入れる場合も、それが見やすいと思えるのならばそれで良いでしょう。実際、その辺はコーディング規約とかコーディングスタイルと言った言葉で、各宗派がある程度出来上がっていますし、たいてい、この手の宗派は入門に使った書籍に準じてしまうでしょうから、一緒のプロジェクトをやっているのでもなければ過干渉する必要はないと私は思っています。

しかし、これを機にググってみると中括弧に関する理解の甘い記述が見受けられます。プログラミング言語は自然言語ではないので、「言語ありきで、それについて文法などの説明を後付けする言語」ではなく、「文法ありきでそれをもとに記述する言語」です。
と言うわけで、私のC言語のこのあたりに関する理解について、ここにメモしておこうと思います。

中括弧の省略記法ではない

まず、if文などの制御文についてです。
if(hoge) {
    foo();
}

C言語ではおよそこのような書き方をします。ちなみに、中身が一文のみの場合、中括弧は省略できるとの記述があるものがしばしば見られます。
if(hoge)
    foo();

結果論としては正しいというか、そう見えます。しかし、中括弧が存在するのが原則で、省略記法としてその中身が一文だけなら中括弧が省略できるという理解なのならば、それは不十分です。


この手のプログラミング言語には「式(expression)」と「文(statement)」の概念を持っています。

まず、式とは
  • 値の計算を指定する
  • オブジェクトもしくは関数を指し示す
  • 副作用を引き起こす
の3種類の動作、またはその組み合わせを行うもののことを言います。副作用(side effect)という言葉は関数型言語マン以外には馴染みの薄い表現で、かつマイナスのイメージを持つ言葉かもしれませんが、ここでは「実行環境の状態を変化させる」程度の意味しか持ちません。例えば、変数の値を変更する(ことによって実行環境のメモリの値を一部書き換える)などといったことです。
式は評価した値を持ちます。

一方で、文とは実行すべき動作を規定したものです。JIS規格では
  • ラベル付き文
  • 複合文
  • 式文
  • 選択文
  • 繰返し文
  • 分岐文
の6種類が文として定義されています。詳しくは割愛しますが、ラベル付き文、式文、分岐文はセミコロンで終わる文法になっていて、選択文や繰返し文はいわゆるif文やwhile文など(分岐や繰り返しの終了地点まで含んだもの)で、複合文は中括弧で囲まれたブロックとなっています。式文はその名の通り式が入る文で、ここに空文も含まれています。分岐文はbreakとかcontinueとかreturnとかですね。


これらを踏まえた上で、if文の構文がどう定義されているかと言うと、
if ( 式 ) 文
です。 いたってシンプルです。

ここから導き出されるのは、中括弧無しは一文の場合の省略記法ではなく、式文で書くか複合文で書くかの違いに過ぎないということです。並列していくつもある「文」のうち1つを書けるという話なので、「原則が中括弧」みたいな発想はその人の固定観念ということになってしまいますね。


カンマ演算子

複合文は0個以上のをまとめて1つのとして扱うものですが、C言語には複数のを1つのとしてまとめる「カンマ演算子」と言うものがあったりします。ほぼほぼ出番が無い演算子ですので、知らない人も多いかもしれません。

カンマ演算子は次のような構文を持ちます。
式, 式, 式, ... , 式
2個以上の式をカンマでつなげるだけです。なお、この式の評価した値は最後の式(一番右側の式)となります。
int i, j, k;

k = (i = 0, j = 1); 
printf("i = %d, j = %d, k = %d", i, j, k);

このコードはコンパイルでき、実行すると
i = 0, j = 1, k = 1
という出力が得られます。 これを上手く使うことで、複合文のような記述を式文を使って作ることができます。
if(hoge)
    puts("hoge"),
    puts("foo");
else
    puts("bar");

ifの1つ下の行のputsは、末尾がセミコロンではなくカンマになっています。すなわち、2行目と3行目の2つのputsを合わせて1つの式にしているわけです。そのため、ifの直下の文は1つの式文になるため、これはコンパイルが通ります。もしもifに与えた条件式hogeが真ならば、hogeとfooが両方とも画面に出力されます。

この記法は中括弧で複数文をくくるより果てしなく見にくいです。と言うより、カンマかセミコロンかなんてパッと見で区別できる人なんてなかなかいません。そのような観点から、使われることはまずないと思われますが、たまに関数マクロなんかで上手く使えば問題の起きにくいマクロが作れたりしてそのような場に出番があると言えるでしょう。

else if文は存在しない

さて、C言語の選択文の項目には、実際には3種類の構文が規定されています。
  • if ( 式 ) 文
  • if ( 式 ) 文 else 文
  • switch ( 式 ) 文
見出しでネタバレしちゃっていますが、そうです、C言語に「else if文」と言うのは存在しません。しかしよく「else if」と記述する人はいますし、実際それでもコンパイルは通ります。
実はこれ、if文全体が1つの文と見なされることを利用し、else以下の文としてif文を置いただけということになります。すなわち、else ifはこのように書くのと同値です。
if(hoge) {
    foo();
} else {
    if(hogehoge) {
        foobar();
    } else {
        fooooo();
    }
}

一般的にこのような記法は無駄にインデントが増えるだけで好まれません。「if文の条件が成り立たなかったときに次の条件が成り立つかを判定する」という概念を1つ頭の中に入れるだけでelse ifという記述は読みやすくなりますから、その記法に反対する人はいないでしょう。しかし、言語仕様ではあくまでも「else以下の文をif文とした記法」となっているわけです。

それを踏まえると、else whileやelse switchなどの記法も同様に文法上書けることになってしまいますし、
if(hoge)
    while(hogehoge) {
        foobar();
        fooooo();
    }

といった記法も許されます。
読みやすいかどうかは別として、どこまでが文で、どこまでが式かと言うことをしっかりと理解しておけば、プログラムを見たときの脳内コンパイルは捗るでしょう。

C#のusing文

C#におけるusing文は、上記のif文同様に
using ( リソース取得式 ) 文
という構文になっています。もちろんusing文自体も文の1つにカウントされるため、using以下にusingを書くことができます。
using(StreamWriter sw = new StreamWriter("dst.txt"))
using(StreamReader sr = new StreamReader("src.txt"))
{
    while((line = sr.ReadLine()) != null)
        sw.WriteLine(line);
}

このように書くことによって、複数のIDisposableインスタンスを作って最後にすべて破棄するという動作を見た目上やっているようになります。本来ならば2つ目のStreamReaderのところはインデントを1段下げるべきなのでしょうが、特に並列的に書いて問題ないところですし、逆に下げると見にくくなるので敢えて下げていません。
下手したら、「usingは並列にいくつも書ける」みたいな説明をしだすところもあるかもしれませんが、特段文法上そういった定めをしているわけではなく、これはC言語系統の文法の体裁として、むしろ一般的な記述であるわけです。

まとめと私見

ここまで読んで、C言語系の言語のソースコードを見る目が変わった方もいるかもしれません。今まではなんとなく書いていたけど、今となればどれが文でどれが式かを意識しながら書けるようになっているかもしれません。

そもそも、プログラミング言語は、0と1のみで表されるコンピューターへの命令を人間にわかりやすい方法で書けるようにしようとして発展してきたものです。今は高度に抽象化され、さらに様々な記法やパラダイムで多種多様な形態を持っていますが、最終的に動作するコンピューターの仕組みは原則として同じです。そして、モダンなプログラミング言語はそのコンピューターの仕組みを知らなくてもできるだけ簡単に書けるように敷居を下げた一方で、やはりハイレベルなプログラミングにはどうしてもそういった知識は付いて回ります。
おそらくプログラミング言語に関してもそれは同じで、一般的な書き方、一般に可読性が良く保守性が高いと言われる記法はありますが、それより一歩進んでコンパイラーがどのように構文を解析し理解するかを頭に置いてプログラムを書くのはとても大事なことだと言えるでしょう。

私は、たまたまC言語を始めるにあたって巡り合った入門書に比較的そういった記述が多く、このような考え方を非常に強く植えつけられながらC言語を理解していきました。そのため、if以下の文などに中括弧を付けるかどうかの議論で「省略したほうがスマート」とか「省略したら可読性が下がる」みたいな意見を見るたびに「そもそも省略じゃないよねそれ」という気持ちになってしまいます。


私は、ifの下を必ず複合文にするかと言うと、そうではありません。むしろ、1つしか式を書かない場合は積極的に式文を使います。
「if文の中身を複数文にしたくなった時に中括弧を付け忘れたらバグになるじゃないか」という意見をたまに見ますが、私はそう思いません。インデントはIDEがちゃんと整理してくれますから、インデントだけ付けて中括弧を付け忘れる状態に気づかないということもまず無いですし、そもそも変更箇所の前後の中括弧が目に留まらないということもまずありません。むしろ、中括弧で1行を使ってしまうとプログラムの縦方向の密度が無駄に下がり、可読性が下がると考えています。
ただし、ifの直下にwhile文だけが入る場合など、一文でも複数行にまたがる場合は基本的に中括弧を入れています。すなわち、ほぼほぼ行数で決めているといったところでしょうか。

一方で「if(式)」とその「中身の文」の間に改行を入れない記法はまずしません。
if(input < 0)  return ERROR;

このような記法は、まあこれくらいなら良いのかもしれませんが、条件式が少しでも長くなると可読性が一気に下がるので私は好みません。たとえ空文でも次の行を1行使います。


結局、この辺りは好みだと思います。自分がC言語を習得したときの入門書、もしくはこれまでに使っていたプログラミング言語の風習、自分の感性、そういったものが合わさって「自分はこう書く」みたいな話が出てくるものでしょう。

大事なのは、言語の仕様を理解し、それに即し、また可読性と保守性が良くなるように自分で工夫を重ねることだと思います。そういったところから、技術力の高さをアピールしていきたいですね。

2016年1月22日金曜日

ポケモン「サファイア」の時計を復活させる

さて、巷ではもっぱらリメイク版である「アルファサファイア」「オメガルビー」が話題ですが(とは言っても発売は1年以上前ですが)、ここではリメイクされる前の「サファイア」を対象にしています。
検証はしていませんが、「ルビー」「エメラルド」も同様に復活させられる可能性もありますし、また、サファイアでもロットが違う製品だと違うチップが載っていて同様にいかない可能性もあります。

流石にゲームボーイアドバンスはもうレトロゲーム認定してもいいのではないかと思います。とても懐かしいですね。
久しぶりにサファイアをプレイしたくなったので、起動してみました。


おお、時計が電池切れだと。
電池切れで時計が動かなくなったから時間絡みのイベントは起きなくなるけど、それ以外のイベントは正常に動くからプレイ可能だよとのメッセージが出てきました。
ゲームボーイカラーまではセーブデータがRAM上に保存されていて、バックアップ用電池が切れるとセーブデータも吹き飛んでいましたが、ゲームボーイアドバンスからはセーブデータ自体は不揮発性メモリに書き込まれるようになったようです。しかし、時計管理はあくまでもカートリッジの責務なんですね。 DSからは時計もゲーム機本体のほうの責務になったみたいですが。

まあとりあえず、ふむふむと思いながらプレイを開始してみましたが、案外時間関係のイベントが多いことに気が付きます。
例えば、
  • きのみが育たなくなる
  • 毎日きのみをくれる人がくれなくなる
  • レコードを混ぜると出現する友達の秘密基地で、1日1回のバトルができなくなる
  • ミナモデパートで1日1回のくじが引けなくなる
  • 1週間に1回わざマシンをくれる人がくれなくなる
  • あさせのほらあなであさせのかいがらやあさせのしおが採れなくなる
  • カイナシティのがんばりやさんが二度と頑張らなくなる
  • マボロシじまが出現しなくなる(時計が動いていても出現したの見たことありませんが)
などなど、ゲームの楽しみが一気に消え去ります。こんなポケモンで一体何をしろと言うのだ!ひたすら殿堂入りか?殿堂入りをすればいいのか!?


電池交換は、実はまだゲームボーイアドバンスのソフトならば任天堂がサポートしてくれています(※注)

バックアップ電池の交換について

ちゃんと正常にゲームをプレイしたい人はまずこっちに頼んだほうが手っ取り早く確実で安いです。ですが、こういうのを分解して自分で修理するのを楽しみたい私は、自分で修理に挑戦してみることにしました。
※注:記事投稿時点。現在はバックアップ電池の交換サービスは終了しているようです。

なお、当たり前ですが、自分で分解、修理をするとその製品について二度と任天堂のサポートを受けられなくなる可能性があります。また、ゲームバランスを大幅に崩すようなゲームの改造も民事訴訟の対象になる可能性が無いことはないようです。もちろん、この記事を見て改造をして失敗した人とかがいたとしても私は責任を負えませんので、自己責任でお願いいたします。
今回の私の記事は、純粋に修理をするためというのと、あとは、まあゲームボーイアドバンスはもう枯れ果てたゲーム市場ですから、メーカーに目くじらを立てられるようなことも無いでしょうということで書くことにしました。


分解、そして電池交換

まずはカートリッジを分解します。分解はY字の特殊ドライバーを使いますが、ゲームボーイカラーまでのカートリッジに比べたら格段と入手性の高い汎用特殊ドライバーで何とかなります。
ねじを外したら、筐体を少しスライドするとロックが外れてパカッと開きます。


こんな感じになります。
ボタン電池が付いていますね。これが切れた電池で、CR1616のタブ付き電池になっています。

まずはタブの根元をはんだごてで温めて古い電池を外します。


CR1616はリチウム電池なので公称3Vですが、もうすでに0.15Vまで下がっていました。完全放電状態です。

ネットをいろいろ探すと、ここまではたどり着いている方も多く、多くの紹介記事では、このタブと電池の接合部を剥がして、新しい電池をテープ等で固定している方が多いように見受けられました。

し かし、それではタブの金属疲労の問題は元より、電池との接触も悪くなることが多いのではないかと思い、私は電池に直接はんだ付けすることにしました。電池は精巧に作られた化学製品で、電池をはんだごてで熱すると中の電解液が気化するなどの化学的変化を起こし、最悪、電池が爆発することがあるので非常に危険です。なので、良い子は真似しないようにしましょう((((((

新しい電池を百円ショップで買ってきて取り付けた画像がこちらです。


マイナス電極側が上になるように取り付けていますが、プラス電極が横に回り込んできているので、接触してショートさせることがないように配線を一部ビニールで覆っています。

これで起動すると、冒頭に出てきていた電池切れのメッセージが出なくなりました。
よしこれで時間関係のイベントが復活だ!よっしゃああああああ!!!!!

まだ時間関係のイベントが発生しない!

起動時の電池切れメッセージこそ出なくなったものの、きのみを植えて1日程度待っても芽が生えてきません。きのみをくれる人もきのみをくれません。おかしいです。電池が復活しても時間関係のイベントが発生しないのです。なぜだ、なぜなのだ!

ヒントは、キナギタウンにいる、1週間に1回わざマシンをくれる人のところにありました。


あと2105日!?およそ5年と9か月です。
正常ならば、ここで「あと6にち」など、1週間以内の日数を案内してくれます。

ここで僕は気が付きました。
ゲームの内部では、次にわざマシンをあげられるようになる日時を記録していたとしたらどうなるでしょう。現在の日時との差分を取って、その日数を「あと○にちたてば~」と答えることになるはずです。
現在の日時はというと、先ほど交換した電池で駆動しているRTCが管理しています。電池を交換したのでRTCのカウントはリセットされ、0日目になったはずです。

そ れ だ

すなわち、ゲームプレイ開始から2098日目に、次にわざマシンがあげられるようになる日を2105日目と記録していたため、電池を交換後でもお構いなしにRTCの日数カウンタがその日になるのを待つ必要が生まれた、ということになります。電池切れなんてそうそう起こることでもなければ、ユーザー自身での修理を想定していたわけでもないので、このような適当な実装になっているのでしょうね。
おそらく、1日に1回起きる系のイベントも同様に記録されていて正常に発生しないということなのでしょう。

このことからも、GBAソフトをメーカーに送って電池交換をしてもらった場合は、電池交換後にRTCのカウンタをずらすか、専用のツールでセーブデータを書き換えてイベントが正常に発生するようにしているものと思われます。
今回は自分で交換したためそのような書き換えができませんので、2105日待つ必要があります。

えっ

ネットで調べたところ、同じような症状になっている人もちらほら見かけました。
そして、ルビー・サファイアではRTCが1年を刻むときのみが育たなくなるバグがあったのですが、その修正プログラムを使えば1度に限ってこの状態を修正できるとの情報がありました。しかし、すでにこのパッチは当てており、リーフグリーン・ファイアレッドからのファームウェア更新を利用して修正することはできませんでした。

つまり、いよいよもって本当に2105日待つ必要があるということになります。

待てるかい!

そして、RTCのハードウェアハックへ

さて、ここまで自分でいろいろやってしまって引くに引けなくなった僕が次に考えたことは、「RTCの値を上書きして2105日分進めればいいんじゃね?」ということでした。
ネットでは電池交換まではしたけど同様に時間関係のイベントが発生しない状態になっている人までは見かけましたが、さすがにRTCをいじろうとしている人までは見かけませんでした。
ですが、カートリッジの基板を見ると、ICチップの脇に丁寧に「1M/512K FLASH U2」とか「U3 RTC」などとICの説明が書いてあります。そうです、どれがRTCかも明確です。もっと言えば、脇に水晶発振子があることからも明白でした。そこで、このRTCの仕様さえわかればRTCの書き換えもできるのではないか、という発想で型番をググってみました。

S-3511A
2018年2月28日追記:リンク切れになっております。通販サイトのタイロテックのこちらからダウンロード可能です。

見事にヒットしました。当時はセイコーインスツルメンツでしたが、今は半導体部門は子会社化されてエスアイアイ・セミコンダクタになっているみたいですね。まあ何にせよSEIKOです。SEIKOが供給していたRTCモジュールが僕のサファイアには搭載されているみたいです。すでにディスコンになっているようですが、データシートは問題なく読めます。よっしゃああああああ!!!!!

まずはプロトコルの確認です。まあRTCだとI2Cかなと思って眺めましたが、ACKとかスレーブアドレスとか無さそうだな。うんうん、SPIかな…いやデータ線が1本しか無いぞこれは…

独自規格だー┗(^o^)┛WwwwWWww┏(^o^)┓ドコドコドコドコwwwwwwwww

なんてこった。独自規格で通信プロトコルを定義しています。
なので、書き換えをする場合はI2CやSPIなどマイコンに載っているハードウェアを使わずに、このプロトコルに則ったIOの制御を自前でしてや らなければなりません。


また、当たり前ですが、データを書き換えるためにはICに何かしら電気的に接続する必要があります。そして、バックアップ用電池で動作している状態(=通電した状態)で書き換えをしたうえで接続を外し、元のカートリッジの形に戻してあげなければなりません。はんだ付けとかでやるのはちょっとリスクが高すぎますよね(実は自分もやってみたりしたのですが、結構スリルがありました)

そこで登場するのがICクリップです。
ICテストクリップ(SOP-8) [TCLIP-SOP8]
これは、基板に実装されたICを挟むことで電気的に接続し、外部に配線を回してくれるツールです。ちょっと値段は高めですが、通電中に書き換えるのならば通電中はんだ付けよりもこういうツールを使ったほうが安心でしょう。


実際に買ってきてRTCを挟んだものがこれです。こんな感じで簡単に接続できます。これはいいです。

マイコンは何でもいいのですが、まあ、安さと小ささ、そしてプログラムメモリの容量からPIC16F1705を選びました。
PIC16F1705
PIC16F1705は特にアナログ機能が強化されたマイコンですが、残念ながら今回はその機能は使いません。

適当に表示用のI2C液晶と操作ボタンを付けた回路を構成し、完成です。


めんどくさいので電源はCR2032を使いました。液晶バックライトを使ったらそんなに持たないでしょうが、まあ長時間運用するものでもないですし大丈夫でしょう。

I2C液晶は適当に制御プログラムを移植してきて動作を確認します。今回aitendoで買ってきた液晶は、液晶の表示向きを反転させるピンが出ていて、それに気づかずにだいぶ苦労しました。適当にそのピンをGNDかVDDに接続することで向きを固定できます。

さて、本題のRTC通信プログラムに入っていきましょう。
タイミングチャートとにらめっこしながらプログラムを書きます。各パルス幅は最低0.1us程度なので、まあゆとりをもって1usくらい取ってあげることにします。

#define sequenceDelay() __delay_us(1);

void rtc_Init()
{
    RTC_CS = 0;
    RTC_SCK = 1;
    RTC_SIO = 0;
    TRIS_RTC_CS = 0;
    TRIS_RTC_SCK = 0;
    TRIS_RTC_SIO = 1;
}

void rtc_WriteBit(bool_t data)
{
    RTC_SCK = 0;
    sequenceDelay();
    RTC_SIO = data ? 1 : 0;  
    sequenceDelay();
    RTC_SCK = 1;
    sequenceDelay();
}

void rtc_WriteByte(uint8_t data)
{
    for(uint8_t i = 0; i < 8; i++) {
        rtc_WriteBit(data & 0x01);  //Send from LSB to MSB
        data >>= 1;
    }
}

void rtc_WriteSequence(uint8_t command, const uint8_t *pData, uint8_t length)
{
    RTC_SCK = 1;
    RTC_SIO = 0;
    
    sequenceDelay();
    RTC_CS = 1;
    sequenceDelay();
    TRIS_RTC_SIO = 0;
    rtc_WriteByte(command);
    for(uint8_t i = 0; i < length; i++)
        rtc_WriteByte(pData[i]);
    RTC_CS = 0;
    sequenceDelay();    
    TRIS_RTC_SIO = 1;
}

bool_t rtc_ReadBit()
{
    bool_t ret;
    
    RTC_SCK = 0;
    sequenceDelay();
    RTC_SCK = 1;
    sequenceDelay();
    ret = PORT_RTC_SIO;
    
    return ret;
}

uint8_t rtc_ReadByte()
{
    uint8_t data = 0;
    for(uint8_t i = 0; i < 8; i++) {
        data >>= 1;
        if(rtc_ReadBit())  //Read from LSB to MSB
            data |= 0x80;
    }
    return data;
}

void rtc_ReadSequence(uint8_t command, uint8_t *pData, uint8_t length)
{    
    RTC_SCK = 1;
    RTC_SIO = 0;
    
    sequenceDelay();
    RTC_CS = 1;
    sequenceDelay();    
    TRIS_RTC_SIO = 0;
    rtc_WriteByte(command);    
    TRIS_RTC_SIO = 1;
    sequenceDelay();
    for(uint8_t i = 0; i < length; i++)
        pData[i] = rtc_ReadByte();
    RTC_CS = 0;
}

ビットを読み書きする関数し、それをバイトデータを読み書きする関数から呼び出します。

書き込みシーケンスと読み込みシーケンスは、まずはそのデータ内容を示すコマンドを送ってからデータの読み書きをしますので、そういったことをやりやすくした関数を用意します。ここでとても重要なのが、データはLSBからMSBに向かって送りますが、コマンドはMSBからLSBに向かって送る点です。なんでこんなクソ仕様にしたのかわかりませんが、コマンドとデータで送る順序が違うので、ここでは、「コマンドの値をデータシートに書いてある順序と逆順にしておく」という方法で対応し、プログラム自体はすべてLSBからMSBに向かって送るようにしました。

実際のコマンドは全部で7種類あり、リセット以外の6種類は読み書き両方ができるため、実質13種類あります。
ですが、RTCを制御するうえでは、年データからの時刻データの読み書きと、ステータスレジスタの読み書きと、リセットコマンドの送信さえできれば問題ないので、その分だけ実装しました。

#define RTC_COMMAND_FULL_READ       0b10100110
#define RTC_COMMAND_FULL_WRITE      0b00100110
#define RTC_COMMAND_STATUS_READ     0b11000110
#define RTC_COMMAND_STATUS_WRITE    0b01000110
#define RTC_COMMAND_RESET           0b10000110

#define RTC_DATETIME_LENGTH 7


typedef union _tagRTC_STATUS {
    uint8_t value;
    struct _tagBits {
        unsigned bit0  : 1;
        unsigned INTFE : 1;
        unsigned bit2  : 1;
        unsigned INTME : 1;
        unsigned bit4  : 1;
        unsigned INTAE : 1;
        unsigned Is24  : 1;
        unsigned POWER : 1;
    } bits;
} RTC_STATUS;



void rtc_WriteDateTime(const DATE_TIME *pdt, bool_t Is24)
{
    uint8_t data[RTC_DATETIME_LENGTH];
    bool_t IsPM = pdt->hour >= 12;
    uint8_t hour = pdt->hour;
    
    if(IsPM && !Is24)
        hour -= 12;    

    data[0] = ByteToBCD(pdt->year);
    data[1] = ByteToBCD(pdt->month);
    data[2] = ByteToBCD(pdt->day);
    data[3] = pdt->weekday;
    data[4] = ByteToBCD(hour) | (IsPM ? 0x80 : 0x00);
    data[5] = ByteToBCD(pdt->min);
    data[6] = ByteToBCD(pdt->sec);
        
    rtc_WriteSequence(RTC_COMMAND_FULL_WRITE, data, RTC_DATETIME_LENGTH);
}

void rtc_ReadDateTime(DATE_TIME *pdt)
{
    uint8_t data[RTC_DATETIME_LENGTH];

    rtc_ReadSequence(RTC_COMMAND_FULL_READ, data, RTC_DATETIME_LENGTH);

    bool_t isPM = data[4] & 0x80;
    
    pdt->year    = BCDToByte(data[0]);
    pdt->month   = BCDToByte(data[1]);
    pdt->day     = BCDToByte(data[2]);
    pdt->weekday = data[3];
    pdt->hour    = BCDToByte(data[4] & 0x3F);
    pdt->min     = BCDToByte(data[5]);
    pdt->sec     = BCDToByte(data[6] & 0x7F);
    pdt->msec    = 0;

    if(isPM && pdt->hour < 12)
        pdt->hour += 12;
}

void rtc_WriteStatus(RTC_STATUS status)
{
    rtc_WriteSequence(RTC_COMMAND_STATUS_WRITE, &status.value, 1);
}

RTC_STATUS rtc_ReadStatus()
{
    RTC_STATUS status;
    rtc_ReadSequence(RTC_COMMAND_STATUS_READ, &status.value, 1);
    return status;
}

void rtc_Reset()
{
    rtc_WriteSequence(RTC_COMMAND_RESET, NULL, 0);
}

こんな感じになりました。

このRTCモジュールは12時間表示モードと24時間表示モードがあり、時刻データの読み書きをするときは現在どっちのモードなのかを意識する必要があります。時刻データには現在が午後か午前かを表すフラグがあるので、データ読み込み後にはそのフラグを見てデータを処理してからBCDをbyteに変換しなければいけませんし、書き込み前にはSTATUSレジスタを読み込んで現在のモードに合わせて12時間表示化してデータを送信する必要があります。

あとは、これをいい感じに制御するインターフェースを作れば完成です。
PICのタイマー2はコンパレーターとセットで動いていて、コンパレーターで指定した値に達したら割り込みを掛けるとともに自動でタイマーをゼロにリセットしてくれます。なので、時計用途でとても使いやすいです(割り込みルーチン内でタイマー値を再設定する必要が無いため)。
時計と時計合わせ機能、そして上記の5つのコマンドの送受信機能を実装すれば、あとはRTCの時刻データを読み込み、修正し、書き込むことができるようになります。これで、ゲームの時間を今回は2105日分+α早送りしてやれば見事ゲームが正常な状態になります。


ちなみにですが、このRTCは電池を交換した後にSTATUSレジスタの読み込みと、適宜リセットコマンドの送信をしてやらなければなりません。ですが、その機能はGBAカートリッジ側でゲーム起動時に行ってくれるようなので、時刻データを読み書きする分には特に気を使う必要はなさそうです。

あと、RTCに設定する時刻は、リアルの時刻ではありません。まず、年月日データが大きな意味を持っていないことは誰にでも想像がつくでしょう(製造時に時計合わせしているだなんて考えにくいですからね)。時刻データは、ゲーム初期化時に聞かれる時刻設定がそのままオフセットになっているのか、私の環境ではRTCが0時00分00秒を示しているときにゲーム内では夕方5時過ぎになっているようでした。なので、その分を意識して時計合わせをしなければなりません。

まあ、なんにせよ、何回か「ゲームを起動して時刻を確認してから終了し、値を更新する」という作業を繰り返さなければならないことは間違いありません。少し面倒ですが、これで完全に時計を合わせられるようになります。


書き換え風景です。
このICクリップですが、やはり足元での接触が悪いことがあり、その場合はRTCのデータが上手く読み書きできなかったり、最悪RTCの中身が吹き飛びます。ですがまあ、根気よくやっていれば大丈夫でしょう…。

時刻データを書き換えるということ

ここまで読んで気づいた方もいるかもしれませんが、今回の記事は「ゲームボーイアドバンス用ソフトの時計をずらす」ということに帰着します。今回やったのは修理のための行為ですが、チートに転用しうることも容易に想像がつくでしょう。
今回はRTCにアクセスし時刻を動かすツールですから、ゲームプログラムのROMの読み書きはできません。ROMの読み書きは特に海賊版のデータが出回る可能性を大いに含みますから、著作権の観点からも、利益の観点からも、メーカーも総力を挙げてそういったツールの撲滅にかかるようです。
ですが、今回はせいぜい時計をずらす程度のチートツールとしてしか使えません。著作権の侵害にあたる可能性はまず無く、せいぜい著作人格権で問題になることがあるかもしれないという程度でしょう(チートツールは作者の意図したゲーム性を破壊することで著作人格権の侵害とみなされることがあるらしいです)。しかし、ポケモンは時刻をずらしたところでそんなにゲーム性を著しく欠くほどのチートにはならないでしょうし、上にも書いた通り、ゲームボーイアドバンスはもう何世代も前のゲーム機で市場は枯れ果てていますから、メーカーも目くじらを立てて怒ってくることも無いと思っています。
というわけで、現在私はこの記事に重大な問題があるとは認識しておりません。関係者の方で、万が一何か問題があるというのならばぜひご一報ください。

改造、修理も男のロマンですからね。怒られない程度の範囲内ならばやって楽しいでしょう。というわけで、私は自分のやったことを公開することにしました。

この記事を参考に改造する方がいるのならば、くれぐれも分をわきまえて行うようにしてくださいね。


さて、いかがだったでしょうか。

ゲームソフトにハード的な寿命がある最後の世代だと言えるゲームボーイアドバンス、その過渡期だからこそ発生した「セーブデータと時計データの錯誤」が生み出した問題ですが、何とか自力で解決することができました。
組み込み制御の知識がある程度あって、かつこの時代のソフトを復活させたいのならば、ぜひやってみてはいかがでしょうか。

あ、あくまでも自己責任でお願いしますよ。

2016年1月14日木曜日

PICマイコンでBCD変換

さて、今回は車輪の再発明というか、別に世の中的に新しいことはありませんが、BCD変換アルゴリズムについて知ったことがあったので備忘録的にまとめておこうと思います。

二進化十進表現(BCD)は、何かと組み込みプログラミングに付いて回ります。値を液晶に表示するときなんかは、内部的にBCD変換をすることになるはずです(C言語ならばprintfとかを使えばプログラマーが意識することはなくなるかもしれませんが)。
他にも、RTCなんかはカレンダー機能を内蔵していて、データのやり取りにはBCD形式を用いているものも結構ありますし、PIC24Fシリーズが持っているRTCCもBCDです。

さて、このBCDですが、このように組み込みプログラミング上で使おうとすると、マイコン内部の値、すなわちただの2進数との変換が必要になってきます。私の中ではこれは案外コストがかかるものであるという認識がありました。
というのも、BCD変換は要するに10進数の桁の抽出ですから、変数を10で除したり、その剰余を計算する必要があります。マイコンのような貧弱なCPUでは除算命令を持っていないことが多く、内部的にはループ等で除算が実装されていると思われます。また、C言語では除算演算子と剰余演算子が別個なので、同じ除数、被除数の場合はループならば本来は同時に商と剰余が産出されますが、C言語で記述する場合は、コンパイラが最適化してくれない限り、商と剰余で2回除算が必要になります。
もちろん自分でループを実装すればその手間は省けますが、それでもあまりスマートな気はしませんよね。

というわけで、こういうのって絶対すごいアルゴリズムあるよね~ってググっていたら見つけました。
BCD変換ルーチンについて
2020/5/4追記: 上記サイトリンク切れにつき、当ブログにてアルゴリズムを詳しく紹介する記事を書きました。アルゴリズムについてはこちらを参照ください。)
1度読んだだけではどういう意味かよく分かりませんでしたが(ついでに<SUB>タグなどが上手く仕事していなくてとても読みにくいですが)、何度か読むと意味がわかってきました。シフト演算で値をコピーするときにBCD変換しようという発想で、基本的にはコピー先の各桁が10以上になったら6を足して繰り上げしてあげるってことみたいです。ただ、シフト演算は要するに2倍するということですので、2倍後の各桁は最大で18になりえます。ですが、それは4ビット値の最大値(15)を超えて不都合なので、シフト演算で2倍する前に5以上かを判別し、繰り上げ処理(2倍する前なので6の半分の3を足す)をやってあげるということのようです。これによって、BCD変換前の値のビット数だけのループ回数と、あとはシフト演算や簡単な比較、足し算だけでBCD変換ができてしまいます。なんと低コストなのでしょう。PICにはディジットキャリービットがありますから、下位4bitが桁あふれをしたかを判別することができるので、4bitごとの演算はとてもやりやすく、さらに計算の低コスト化ができるでしょう。コンパイラの頭の良さを試しどころです。

というわけで、PICを意識したC言語でこのアルゴリズムを実装してみました。
typedef union {
    uint24_t value;
    struct _tagField {
        unsigned binary  : 8;
        unsigned BCD_1   : 4;
        unsigned BCD_10  : 4;
        unsigned BCD_100 : 4;
    } field;
} BYTE_BCD_CONVERTER_UNION;

uint16_t ByteToBCD(uint8_t val)
{
    BYTE_BCD_CONVERTER_UNION conv;
    conv.value = 0;
    
    conv.field.binary = val;
    
    for(uint8_t i = 0; i < 8; i++) {
        if(conv.field.BCD_1 >= 5)
            conv.field.BCD_1 += 3;
        if(conv.field.BCD_10 >= 5)
            conv.field.BCD_10 += 3;
        if(conv.field.BCD_100 >= 5)
            conv.field.BCD_100 += 3;
        conv.value <<= 1;
    }
    
    return (uint16_t)(conv.value >> 8);
}
8bitの値(byte)から3桁のBCD値を作り出す関数です。変にシフト演算とかを使いまくって「桁の値が5以上だったら」などという処理をするよりか、うまいことビットフィールド構造体を使うことで、コンパイラができる限り低コストなコードを生成してくれます。と同時にリトルエンディアンの処理系依存になってしまいますが、まあ、大体身の回りはリトルエンディアンなので良いでしょう(ヘラヘラ
ためしに、BCDの1の位と10の位が5以上かを見て処理する部分の逆アセンブル結果を見てみましょう。ちなみに、コンパイラはXC8 v1.35の無料版で、ターゲットはPIC16F1823です。
22:                    if(conv.field.BCD_1 >= 5)
0279  087A     MOVF i, W
027A  390F     ANDLW 0xF
027B  00F4     MOVWF multiplicand
027C  3005     MOVLW 0x5
027D  0274     SUBWF multiplicand, W
027E  1C03     BTFSS STATUS, 0x0
027F  2A8A     GOTO 0x28A
23:                        conv.field.BCD_1 += 3;
0280  087A     MOVF i, W
0281  390F     ANDLW 0xF
0282  00F4     MOVWF multiplicand
0283  3003     MOVLW 0x3
0284  07F4     ADDWF multiplicand, F
0285  087A     MOVF i, W
0286  0674     XORWF multiplicand, W
0287  39F0     ANDLW 0xF0
0288  0674     XORWF multiplicand, W
0289  00FA     MOVWF i
24:                    if(conv.field.BCD_10 >= 5)
028A  0E7A     SWAPF i, W
028B  390F     ANDLW 0xF
028C  00F4     MOVWF multiplicand
028D  3005     MOVLW 0x5
028E  0274     SUBWF multiplicand, W
028F  1C03     BTFSS STATUS, 0x0
0290  2A9C     GOTO 0x29C
25:                        conv.field.BCD_10 += 3;
0291  0E7A     SWAPF i, W
0292  390F     ANDLW 0xF
0293  00F4     MOVWF multiplicand
0294  3003     MOVLW 0x3
0295  07F4     ADDWF multiplicand, F
0296  0EF4     SWAPF multiplicand, F
0297  087A     MOVF i, W
0298  0674     XORWF multiplicand, W
0299  390F     ANDLW 0xF
029A  0674     XORWF multiplicand, W
029B  00FA     MOVWF i
こんな感じになっています。1の位側はそのままANDで0x0Fでマスクしてから比較処理をし、10の位側はSWAPFで上4bitを下と入れ替えて、から同様にマスクして処理をしています。シフト演算ではなくSWAPFを使って4bit一気に動かしたという処理は、まあ及第点でしょう。
実際、自分でアセンブリで書くのならば、1の位はそのままマスクせずに0x05を引いてからSTATUSレジスタのディジットボロービットを見ることになりそうですし、0x03を足してもその4bit値としては桁あふれは絶対起こさないということはわかっているので、XOR→AND→XORなんていう回りくどい処理は省くでしょう。また、10の位はSWAPFなんてせずに、0x50を引いて上位4bitが5以上かを見ればよいですし、ここで足し算をするときもSWAP→マスク→3を足す→SWAP→XOR→AND→XORなんていう回りくどいことをせず、0x30を足せば良いでしょう。
…と考えると、このバイナリもまだまだ相当ステップ省略できそうですね。「桁あふれしないことはわかっている」っていうのはかなりハードな最適化なのでコンパイラができなくても仕方ないかなと思いますが、SWAPをして3を足す代わりにそのまま0x30を足す、くらいの芸当はやってほしかったですね。まあ、無料版だから仕方ないか。C言語でそう書けばいいんでしょうけどね…。

2015年12月19日土曜日

WPF用縦書きテキストブロック Tategaki ver.2.1.1

(2025/06/26追記)
Google等からこのページに直接来られる方が多いようですが、最新版はver.3.2.3になっていますので、こちらからどうぞ。

先日Nugetデビューしたので、ついでにTategakiもNugetにアップロードしました。
バージョンを少し進めていますが、これはまあコードの一部をnameofにしてC#6.0化したり、XAML名前空間をURLで指定できるようにしたためです。

Glyphs特有の癖と言いますか、どうしても横書きのTextBlock等と同じように使えないところをどうにかしたいなとは思ってはいるのですが、なかなか上手くいかないですね…。例えば、フォント名→フォントファイルの変換なんかは独自に私が実装していますので、どうしてもTextBlockの抽象化ほどきれいにいっていなかったりします。早くWPF側で縦書きに対応してほしいのですが、なんていうか、もうMicrosoft内ではWPFを時代遅れとみている風潮すらありそうな気がして、あまり期待はしていません…。


Tategakiの使い方

そういえば、Tategakiをいかにして作るかみたいな話はしましたが、使い方はサンプルコードに頼りっぱなしで全然解説していなかったなーって思いましたので、ここで少し解説しておきます。

Nugetからダウンロード

まずは適当なWPFプロジェクトを作りNugetからTategakiを落としてきます。



これで必要な参照は追加されます。

XAMLでの配置

まずはXAMLでTategakiに使う名前空間を設定します。
xmlns:tg="http://schemas.eh500-kintarou.com/Tategaki"
そしたら、コントロールを適当に配置します。
<Grid>
    <tg:TategakiText Text="羅生門" VerticalAlignment="Center" FontSize="36" FontWeight="Bold" FontFamily="MS ゴシック" />
</Grid>
これで完成です。どうです?簡単でしょう。
ちなみに、自動的に改行される複数行に渡ったテキストはTategakiMultilineを使います。
<tg:TategakiMultiline Text="{Binding ElementName=textBox1, Path=Text, UpdateSourceTrigger=PropertyChanged}" />
どうです?簡単でしょう。

ちなみに、縦書きでテキストを作る場合、右から左へスタックしていくかと思いますが、WPFのStackPanelではそういうことはできません(たぶん)。なので、私は左右反転させたテキストStackPanelで左から右へスタックし、 そのStackPanel全体をもう一度左右反転させる、なんてことをやってそれっぽいコードを書いています。初めから内容がわかってるんだったら後ろの行から順に左からスタックしていくのとかもぜんぜんありでしょうが…。

てな感じで、適当にコントロールを配置するとこんな感じのアプリケーションが出来上がります。



ちゃんとフォントファイルから縦書き用の文字のインデックスを取ってきているので、カッコや句読点など横書きと縦書きで位置が変わるようなものでも正確に表示できていることがわかるかと思います。

ダウンロード


2015年12月17日木曜日

ListView Extensions ver.1.0.0-beta1

WPFのListViewって結構使うのに根性いりません?

ちょっとしたデータの可視化程度ならば適当な実装でもさほど困らないかもしれませんが、ListViewらしくヘッダーをクリックしてソートしたり、右クリックやダブルクリック、選択したアイテムの取得などの実装をしようとし始めるととたんに面倒になってくるのがWPFのListViewです。
そこで、ListViewでよくやることについて、手が届きそうで届かない痒い所を中心にView、ViewModel、Modelのすべてにおいてサポートをするライブラリを作ったら格段とListViewの使い勝手が上がるのではないか?という発想になりました。

というわけで、こんなクラスを実装し、ライブラリ化しました。下記の図の青色のクラスです。


順を追って説明していきます。

Model

Modelの中心的存在となるのはSortableObservableCollection<T>です。
ここで重要なのは「Sortable」です。Sortedじゃないんですね。ListViewに表示するデータはソートされていなくてもいいですし、ソートされていてもいいです。必要に応じて並び替えることができるということでSortableという名前にしました。
このコレクションは、ISortableObservableCollection<T>インターフェースを継承しています。
/// <summary>
/// ソート可能な変更通知コレクションのインターフェースです。
/// </summary>
/// <typeparam name="T">要素の型</typeparam>
public interface ISortableObservableCollection<T> : IList<T>, INotifyPropertyChanged, INotifyCollectionChanged
{
    /// <summary>
    /// 自身のソート状態からして適切な位置にアイテムを追加します。
    /// ソートされていなかったら末尾に追加されます。
    /// もしも追加するアイテムと同じ大きさのアイテムがあった場合、すでにあるものの最後に挿入されます。
    /// </summary>
    /// <param name="item">追加するアイテム</param>
    void AddAsSorted(T item);

    /// <summary>
    /// 指定したインデックスが示す位置にある項目を、コレクション内の新しい場所に移動します。
    /// </summary>
    /// <param name="oldIndex">移動する項目の場所を指定する、0から始まるインデックス。</param>
    /// <param name="newIndex">項目の新しい場所を指定する、0から始まるインデックス。</param>
    void Move(int oldIndex, int newIndex);

    /// <summary>
    /// 自身をソートします。
    /// </summary>
    /// <param name="propertyName">ソートするプロパティ名</param>
    /// <param name="direction">ソートする方向</param>
    void Sort(string propertyName, SortingDirection direction);

    /// <summary>
    /// 現在のソート条件
    /// </summary>
    SortingCondition SortingCondition { get; }
}
IList<T>とINotifyCollectionChangedを継承しているのは当然ですね。あとは、SortingConditionというプロパティを持つのでINotifyPropertyChangedも継承しています。

さて、このインターフェースが独自に実装を支持しているのは、見ての通り3つのメソッドと1つのプロパティです。
AddAsSortedは見ての通り、ソート状態を保ったままアイテムを追加するものです。もしもコレクションがソートされていなければ末尾に追加されます。
MoveはObservableCollectionなどと同じくアイテムを移動させるものです。
Sortはソートを実行するメソッドです。T型のプロパティ名をstringで指定して、SortingDirectionで指定した向きでソートを実行します。
public enum SortingDirection
{
    None,
    Ascending,
    Descending,
}
いたってシンプルです。Ascendingは昇順、Descendingは降順です。SortメソッドにNoneを渡すと例外を吐きます。

SortingConditionですが、これは現在のソート条件を示すプロパティです。
public class SortingCondition : IEquatable<SortingCondition>
{
    /// <summary>
    /// ソート条件なしとして初期化します
    /// </summary>
    public SortingCondition()
    {
        PropertyName = string.Empty;
        Direction = SortingDirection.None;
    }

    /// <summary>
    /// ソート条件なしとして初期化します
    /// </summary>
    public SortingCondition(string propertyName, SortingDirection direction)
    {
        if(direction == SortingDirection.None) {
            PropertyName = string.Empty;
            Direction = SortingDirection.None;
        } else {
            if(string.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException(nameof(propertyName));

            PropertyName = propertyName;
            Direction = direction;
        }
    }

    /// <summary>
    /// プロパティ名
    /// </summary>
    public string PropertyName { get; }

    /// <summary>
    /// ソート方向
    /// </summary>
    public SortingDirection Direction { get; }

    /// <summary>
    /// ソート条件がなしかを取得します。
    /// </summary>
    public bool IsNone => Direction == SortingDirection.None;

    #region IEquatable

    public override bool Equals(object obj)
    {
        return base.Equals(obj as SortingCondition);
    }

    public override int GetHashCode()
    {
        return PropertyName.GetHashCode();
    }

    public bool Equals(SortingCondition other)
    {
        if(other != null)
            return this.PropertyName == other.PropertyName && this.Direction == other.Direction;
        else
            return false;
    }

    #endregion

    public override string ToString()
    {
        if(IsNone)
            return "None";
        else
            return $"{PropertyName}, {Direction}";
    }

    /// <summary>
    /// ソート条件なし
    /// </summary>
    public readonly static SortingCondition None = new SortingCondition();
}
平たく言えばプロパティ名とソート方向を保持するクラスです。等価評価やソート条件なしを示すstaticフィールドなどが用意されており、ちょっとコードがごちゃごちゃしていますが、基本的にはソート条件を示すだけです。Sortメソッドを呼び出すことでソート条件が変化し、SortingConditionプロパティが変化します。また、アイテムの追加、移動、変更等でソート条件を満たさなくなった時も同様にこのプロパティは変化します。

概ねこれがModelの中身です。
SortableObservableCollectionは中でObservableCollectionのフィールドを持っており、コレクションの変更通知自体はObservableCollectionに任せています。しかし、ObservableCollectionはスレッドセーフではないので、そのあたりのニーズがある場合は適当なスレッドセーフな変更通知コレクションを実装したライブラリなどと組み合わせて独自のISortableObservableCollectionを実装するとよいです。このライブラリではSortableCollection抽象クラスを提供しており、これはソートにかかわるメソッドやプロパティのみが実装されたものなので、これを上手く継承するとそういったものが比較的楽に作れるかと思います。

ViewModel

ViewModelは基本的にListViewViewModel<TViewModel,TModel>クラスを使うことになります。
コンストラクタを見れば使い方が想像つきやすいかと思います。
public ListViewViewModel(ISortableObservableCollection<TModel> source, Func<TModel, TViewModel> converter, Dispatcher dispatcher)
まずはsourceです。これはModelで用意したSortableObservableCollectionを指定すれば良いです。そのコレクションの変更を監視し、それに追従して自身のコレクション(読み取り専用)を変更します。
ところで、ViewModelはViewの都合を吸収するものですから、Modelで使ってたコレクションの要素の型とは違うものになることが多いです。なので、その変換をするメソッドをconverterに渡します。そうすることで、ModelをViewModelに変換した状態でListViewViewModelに保持することができるようになります。
最後に、ListViewコントロールに対してコレクションの変更通知をするときはUIスレッドで行う必要があるため、この引数でUIのDispatcherを引き渡してあげる必要があります。これにより、ListViewViewModelではたとえUIスレッド外でSortableObservableCollectionが変更されたとしてもちゃんとUIスレッド上でCollectionChangedイベントを発生させてくれます。

ちなみに、コンストラクタはもう1つあり、そちらではそのDispatcherの実行優先度を指定することができます。指定しなかった場合(上記のコンストラクタを使った場合)はDispatcherPriority.Normalになります。

最後に、このコンストラクタを呼び出す例を示しましょう。
People = new ListViewViewModel<PersonViewModel, Person>(model.People, m => new PersonViewModel(m), DispatcherHelper.UIDispatcher);
modelのPeopleプロパティ(SortableObsevableCollection<Person>型)に対して、それに対応するListViewViewModelを作っています。変換はPersonViewModelのコンストラクタにPerson型を与えることで実現しています。逆に言えば、コレクションの要素のViewModelはこのような実装をすることをお勧めします。


また、ViewModelにはSortableObservableCollectionに対応したコレクション操作以外に、ListViewの他の機能に対応するプロパティもいくつか持っています。

コレクションの操作

例えば、ListViewを使う場面として、何か適当なデータを複数セットする場合などがあると思います。その場合、データの追加、削除、移動などといった作業が付きまといます。


この画像を見ると、上から3つの項目が選択されています。選択されている項目があるのでRemoveボタンは有効です。また、一番上の項目が選択されているためこれをさらに上へ移動することはできないのでUpボタンは無効化されていますが、下への移動はできるのでDownボタンは有効になっています。
このようなデータの削除、移動は選択状態さえわかれば実行の可否がわかりますし、各要素のデータの内容知っている必要もありません。項目を消し去ったりそのまま動かせばいいだけです。なので、ListViewViewModelでは、その動作を実現するコマンドを用意してあります。
/// <summary>
/// 選択中の項目を削除するコマンド
/// </summary>
public ICommand RemoveSelectedItemCommand { get; }

/// <summary>
/// 選択中の項目を上へ移動するコマンド
/// </summary>
public ICommand MoveUpSelectedItemCommand { get; }

/// <summary>
/// 選択中の項目を上へ移動するコマンド
/// </summary>
public ICommand MoveDownSelectedItemCommand { get; }
ちなみにですが、見ての通り追加はコマンドでは用意されていません。追加されるデータの内容がわかりませんからね。また、追加は基本的にいつでも可能なものです。もしでないときがあるのならば、それはListViewの状態から決まるものではないと考えられます。なのでこのライブラリではそれに対応するコマンドはサポート外です。適当にコマンドを作るなりメソッドをバインディングするなりしてSortableObservableCollectionに直接Addしてください。

ソート

ソートは流れが面倒なので詳しくはまとめて後述します。
ViewModelとしては、ソート指示を受け取るSortByPropertyCommandと、現在のソート条件を示すSortingConditionプロパティを持っています。

選択

ListViewで複数選択する場合はSelectedItemsプロパティをバインディングする必要があります。
実はこのプロパティは読み取り専用依存関係プロパティなので普通にバインディングできないのですが、まあとりあえずそれについては後述します。
SelectedItemsをバインディングする先として、ListViewViewModelではSelectedItemsSetterプロパティを用意しています。ですが、これは非ジェネリックスのIList型なのでとても使いにくいです。なので、この内容をミラーリングしたジェネリックのコレクション、しかも変更通知までできるようにしたものとして、SelectedItemsプロパティを用意してあります。もしも選択状態を知りたければこれを読み込めば大丈夫です。

ちなみに、選択をViewModel側から変更したいときのためにSelectItem、 UnselectItem、ToggleItemSelectionなどのメソッドも用意されています。また選択されているか同化を判別するためにIsSelectedItemなどのメソッドもあります。また、選択を反転するコマンドとしてToggleSelectionCommandも用意されています。

View

Viewのサポートは上記のようなクラスの提供とは少し異なります。なんといっても使うのはListViewですから、例えばListViewViewModelをバインディングするだけですべてが実現できるようなコントロールは用意していません。必要に応じて、必要なものをバインディングしてください。

まずは下準備として、XAMLに名前空間の定義をします。
xmlns:lv="http://schemas.eh500-kintarou.com/ListViewExtensions"
名前空間はURLでまとめてあります。
XAMLの書き方は機能別に記述します。

ソート


ListViewのヘッダーをクリックしたらソートがされるべきです。ですが、残念ながらListViewには勝手にソートしてくれる機能はありません。なので、そのような機能をこのライブラリがサポートすることで解決しています。

全体の流れてとしては下記の図のようになります。



View:ヘッダーがクリックされる
↓コマンド呼び出し
ViewModel
↓Sortメソッド呼び出し
Model::自身をソートし、ソート条件を更新する
↓コレクション・プロパティ監視
ViewModel:自身のコレクションとソート条件をModelと同期
↓バインディング
View:コレクションの反映、ソート済みのヘッダーの▲▼印の表示

てな具合ですかね。ViewからModelまでを往復します。
ViewModel→Model→ViewModelの流れはListViewViewModelクラスとSortableObservableCollectionクラスがいい感じに勝手に処理をしてくれますが、ViewとViewModelのつながりはXAMLに記述しなければなりません。例えば、こんな感じになります。
<ListView ItemsSource="{Binding People}" >
    <ListView.Resources>
        <lv:SortingConditionConverter x:Key="ConditionToDirectionConverter" />
    </ListView.Resources>
    <ListView.View>
        <GridView>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding Name}">
                <GridViewColumnHeader Command="{Binding People.SortByPropertyCommand}" CommandParameter="Name" >
                    <lv:SortedHeader Content="Name" SortingDirection="{Binding People.SortingCondition, Mode=OneWay, Converter={StaticResource ConditionToDirectionConverter}, ConverterParameter='Name'}" />
                </GridViewColumnHeader>
            </GridViewColumn>
            <GridViewColumn Width="150" DisplayMemberBinding="{Binding Pronunciation}" >
                <GridViewColumnHeader Command="{Binding People.SortByPropertyCommand}" CommandParameter="Pronunciation" >
                    <lv:SortedHeader Content="Pronunciation" SortingDirection="{Binding People.SortingCondition, Mode=OneWay, Converter={StaticResource ConditionToDirectionConverter}, ConverterParameter='Pronunciation'}" />
                </GridViewColumnHeader>
            </GridViewColumn>
            <GridViewColumn Width="70" DisplayMemberBinding="{Binding Age}" >
                <GridViewColumnHeader Command="{Binding People.SortByPropertyCommand}" CommandParameter="Age" >
                    <lv:SortedHeader Content="Age" SortingDirection="{Binding People.SortingCondition, Mode=OneWay, Converter={StaticResource ConditionToDirectionConverter}, ConverterParameter='Age'}" />
                </GridViewColumnHeader>
            </GridViewColumn>
            <GridViewColumn Width="120" DisplayMemberBinding="{Binding Birthday}" >
                <GridViewColumnHeader Command="{Binding People.SortByPropertyCommand}" CommandParameter="Birthday" >
                    <lv:SortedHeader Content="Birthday" SortingDirection="{Binding People.SortingCondition, Mode=OneWay, Converter={StaticResource ConditionToDirectionConverter}, ConverterParameter='Birthday'}" />
                </GridViewColumnHeader>
            </GridViewColumn>
            <GridViewColumn Width="100" DisplayMemberBinding="{Binding Height}" >
                <GridViewColumnHeader Command="{Binding People.SortByPropertyCommand}" CommandParameter="Height_cm" >
                    <lv:SortedHeader Content="Height" SortingDirection="{Binding People.SortingCondition, Mode=OneWay, Converter={StaticResource ConditionToDirectionConverter}, ConverterParameter='Height_cm'}" />
                </GridViewColumnHeader>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>
まずはGridViewColumnでバインディングするプロパティ名を指定します。この辺まではListViewの一般的な使い方かと思います。ですが、これはあくまでも"列"の設定ですので、列に対応したヘッダーの設定を別口でする必要があります。ということで、GridViewColumnHeaderを指定してあげます。これが列のヘッダーに対応する要素ですので、クリック時のコマンドをListViewViewModelのSortByPropertyCommandにバインディングしてあげることで、ヘッダーのクリックを伝えることができるようになります。CommandParameterにはソートするときに使うプロパティ名を指定する必要があるので注意してください。

また、GridViewColumnHeaderはそのContentが表示内容になるわけですが(すなわち任意のコントロールを表示内容として配置できます)、その表示内容にはこのライブラリが提供している SortedHeaderコントロールを使うといいです。これは、Contentに加えてソートを示す▲や▼などの図形を同時に表示することができるコントロールです。SortingDirectionプロパティにNoneを指定すれば非表示、Ascending/Descendingで▲/▼になります。
この状態をListViewから反映させるには、SortingConditionをバインディングするといいです。ただし、SortingConditionはソートに使われたプロパティ名とその方向を保持しているクラスですので、適当なValueConverterを介してそれを各ヘッダーのSortingDirectionに変換してやる必要があります。それがSortingConditionConverterになります。パラメーターにプロパティ名を与えれば、そのプロパティ名に一致するSortingConditionの場合はそのSortingDirection、そうでない場合はSortingDirection.Noneを返すようになっています。

これで、ヘッダーを押すとソートされ▲▼が付くというListViewが実現できるようになりました。

選択のバインディング

さて、上でListViewのSelectedItemsプロパティは読み取り専用なので直接バインディングはできないということを紹介しました。
じゃあどうするか。トリガーアクションを使います。

このライブラリではListViewSelectedItemsActionというアクションを持っており、このアクションが実行されると、SelectedItemsをSourceにコピーしてくれます。イベントトリガーを使って、SelectionChangedイベントが発生するたびにこのアクションを実行しても良いですが、選択項目が変更されるとどうもSelectedItemsにアイテムが追加されたり削除されたりするだけで、SelectedItems自体の参照は変わらないようです。むしろ、ViewModel側から選択操作をする場合は、このSelectedItemsの参照が無ければできませんから(これのAddメソッドを呼び出すことで実現している)、インスタンスが出来上がったらいち早く呼び出してやる必要があります。

というわけで、あなたが普段使っているMVVMライブラリのメッセンジャー機能を使ってこのようにListViewViewModelをインスタンス化した直後にSelectedItemsをミラーリングしてあげてください。私が普段使っているLivetではこうなります。
People = new ListViewViewModel<PersonViewModel, Person>(model.People, m => new PersonViewModel(m), DispatcherHelper.UIDispatcher);
Messenger.Raise(new InteractionMessage("SelectedItemsMirroring"));
XAMLではこのようにします。
<i:Interaction.Triggers>
    <l:InteractionMessageTrigger Messenger="{Binding Messenger}" MessageKey="SelectedItemsMirroring" >
        <lv:ListViewSelectedItemsAction Source="{Binding People.SelectedItemsSetter}" />
    </l:InteractionMessageTrigger>
</i:Interaction.Triggers>
こうすることで、ListViewViewModelが作られた直後にSelectedItemsがミラーリングされ、選択項目の取得や設定が可能になります。図にするとこんな感じになります。



ちなみにですが、 ContentRenderedとかのイベントを拾ってあげればわざわざViewModelからメッセージ送らなくていいんじゃないの?との考えもあるかもしれませんが、タイミングによってはContentRendered発生後にListViewViewModelをインスタンス化することがありますので、上手く拾えない可能性が結構あります。変なところで悩まないためにもこの方法が今のところベストかなーと思っています。何かいい対策があればいいのですが…。

ListViewの項目のダブルクリック

さて、ファイルビュアーみたいなものを想定した場合、項目がダブルクリックされたときにその要素のViewModelに対してコマンドを発火させるなりメソッドを呼び出すなりしてやりたくなることがあるかと思います。
これが案外めんどくさいんですね。詳細については以前の記事で紹介しましたが、この記事の機能もこのライブラリに組み込まれています。さらに、前回の記事ではコマンドをバインディングするだけでしたが、メソッドも直接バインディングできるようになっています。
<ListView ItemsSource="{Binding People}" >
    <!-- 中略 -->
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <!--<Setter Property="lvap:DoubleClickBehavior.Command" Value="{Binding DoubleClickCommand}" />-->
            <Setter Property="lv:DoubleClickBehavior.MethodTarget" Value="{Binding}" />
            <Setter Property="lv:DoubleClickBehavior.MethodName" Value="DoubleClicked" />
        </Style>
    </ListView.ItemContainerStyle>
</ListView>
こんな感じですね。コメントアウトしてあるのはコマンドをバインディングする方法で、コメントアウトしていないほうがメソッドを直接バインディングするほうです。

ライセンス

以下の各項目をお守りください
  • このライブラリを利用する方は自己責任でお願いします。いかなる問題が起きても作者は責任を負いません。
  • このソフトを悪用しないでください。
  • このソフトウェアを無断で単体での転載、再配布しないでください。ただし、このライブラリを参照しているソフトウェアと一緒に配布する場合を除きます。
  • 作者は使用方法やバグに関するサポートをする義務を負いません。
  • 有償アプリケーションには使用してはならない。
  • 完成したソフトウェアのどこか(ヘルプ、バージョン情報など)と、ReadMeなどのドキュメンテーションに私のライブラリを使用したことを明記すること。ただし、作者(私)がこのライブラリを自分のソフトで使用するときはその限りではない。

公開

Nugetに初挑戦してみました。どうぞ。
https://www.nuget.org/packages/ListViewExtensions/
なお、 プレリリース版扱いですので、Nugetでは検索時にプレリリース版もヒットするように設定する必要があるので注意してください。

また、こちらにサンプルプログラムを用意しています。どうぞ。
ListViewExtensionsSample ver.1.0.0-beta1

2015年12月8日火曜日

ListViewItemのイベントをMVVMスタイルのプログラムで受信する - 添付プロパティ編

さて、以前にReactive Propertyを使って苦し紛れにListViewItemのイベントを受信する方法を紹介しました。しかし、その方法は本当にかなり苦し紛れで、それらしいスタイルは曲がりなりにも美しいとは言えませんでした。

しかし、今回は別の方法でついにそれっぽい書き方をすることに成功しました。 その方法はずばり、添付プロパティを使う方法です。
WPFには添付プロパティという機構があり、本来そのコントロールが持っているプロパティではない任意のプロパティを作成し、コントロールに添付することができます。Grid.Rowなどのプロパティがその一つで、例えばTextBlockは当然Grid以外にも配置されますからGridの何行目何列目に位置するなんていうプロパティは持っていませんが、添付プロパティを使うことでそういう情報を付加できるようになるものです。

というわけで、添付プロパティで適当なコマンドをListViewItemに添付してしまえば、そこで上手くやることでそのコマンドを発火させることができるのではないかというアプローチになります。

public static class DoubleClickBehavior
{
    public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
        Regex.Replace(nameof(CommandProperty), "Property$", ""),    //末尾のPropertyを消す
        typeof(ICommand),
        typeof(DoubleClickBehavior),
        new FrameworkPropertyMetadata(null, (sender, e)=> {
            Control ctrl = sender as Control;

            if(ctrl != null) {
                ICommand oldCommand = (ICommand)e.OldValue;
                ICommand newCommand = (ICommand)e.NewValue;

                if((oldCommand != null) && (newCommand == null))    //購読を停止
                    ctrl.MouseDoubleClick -= Control_MouseDoubleClick_ForCommand;
                if((oldCommand == null) && (newCommand != null))    //購読を開始
                    ctrl.MouseDoubleClick += Control_MouseDoubleClick_ForCommand; ;
            }
        }));

    public static void SetCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    private static void Control_MouseDoubleClick_ForCommand(object sender, MouseButtonEventArgs e)
    {
        ICommand com = GetCommand((Control)sender);

        if(com.CanExecute(e))
            com.Execute(e);
    }
}

こんな感じです。添付プロパティはDependencyProperty.RegisterAttached()メソッドを使って作った****Propertyという名前のプロパティで、セッターやゲッターはGet****()、Set****()という名前で作るというお作法になっています。そしていずれもstaticです。

ここでミソなのは、添付プロパティのコンストラクタに与えるFrameworkPropertyMetadataクラスは、コンストラクタに値の変更時にその通知を受け取るハンドラを渡すオーバーロードがあるということです。もちろんその通知のsenderは添付プロパティが取り付けられたコントロールになるので、それに対してイベントの購読を行えば、イベントを拾って添付プロパティに投げることが可能になります。

XAMLではこんな感じになります。

<ListView ItemsSource="{Binding Items}" >
    <ListView.View>
        <GridView>
            <!-- 中略 -->
        </GridView>
    </ListView.View>
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="b:DoubleClickBehavior.Command" Value="{Binding DoubleClickCommand}" />
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

いたって普通にItemsContainerStyleでプロパティを設定しています。しかしこれが添付プロパティで、上記の通りこれに値をバインディングすることでイベントを購読できるようになります。

それにしてもこのアイディアを知ったときは目から鱗だったなあ。