高見知英のかいはつにっし(β)

高見知英のアプリケーション開発日誌 のほか、地域活動などの活動報告ブログ。

月齢表示付き「日付」

今日はPocketPCのTodayプラグインを作ってみました。日付に加えて、現在の月齢を表示するだけという簡単なものですが、資料が少なくて大変でした(__;)
TodayプラグインはC++言語のネイティブで作ることになるのですが、なにしろC自体もよく知らなかったわけですし、どういう風にAPIを使えばいいかもよく分からない状況で大変大変。
とりあえずまあ、形になりましたので、またWikiに挙げておきます。いつも通りどんどん増えます、ごめんなさい。

さて、せっかくなので分かりにくかったところを数点。C言語プログラマじゃない観点から行きますので、参考になるところも多いと思います。
開発環境はVisualStudio2005で。

プロジェクトの作成〜最低限の骨組みまで

DLLとしてプロジェクトを作成してから、基本的な関数を準備するまでは、参考サイトの3番および4番が参考になります。基本的に3番のページのをほとんど丸写しでも問題ないかな というくらいです。
さて、3番ではアイコンリソースを読んでないようですが、アイコンリソースが必要な場合には注意が必要です。
LoadIcon APIでは32ピクセルのアイコンしか読めないとのことなので、LoadImageを使うようにしましょう。LoadImageの最後の引数に関する定数のほとんどはPocketPC用のヘッダファイルには定義されていないようなので、縦横には読み込みたいファイルの縦横の幅を、最後の引数には0を指定します。

g_hIcon = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_LUNA), IMAGE_ICON, 16, 16, 0);

という感じ。
リソースを読み込んだ場合は、ちゃんとDestroyしておきましょう(実はしなくても良いみたいですが、ものがDLLですしちゃんと処理しておきましょう)*1

DestroyIcon(g_hIcon);

LoadとDestroyを書く場所が一カ所になってバランスが良いので、DllMainの中に書くと良いかもしれませんね。

CustomItemOptionsDlgProc

わたしは使ってないので

return FALSE;

とだけ書きました。nakkaさんのソフトのソースを見ると、別に使わないようなら書かなくてもいいようですが。

WndProc

ここからは3番のページには書いていないので、4番のページか、nakkaさんのソフトのソースを参考にするのがいいでしょう。
わたしは4番のページのものを参考にしましたが、msiのインストールが面倒なので、nakkaさんのサイトに行って、ソフトのソースを見させてもらったほうが早いでしょうね。
さて、ここで最低限サポートしなければいけないのは

  • WM_TODAYCUSTOM_QUERYREFRESHCACHE
  • WM_ERASEBKGND
  • WM_DESTROY
  • WM_PAINT

の4つのメッセージです。WM_DESTROYはなんで要るのか分かりませんが、4番のソースに

  case WM_DESTROY:
    return 0;

と書いてあったので、それに従っておきました。


WndProcなら常識とも言えるものですが、処理しないメッセージはみんな

return DefWindowProc(hwnd, uMsg, wParam, lParam);

を使って流しましょう。どうなるか分かりません。

WM_TODAYCUSTOM_QUERYREFRESHCACHE

初期化時に呼び出されるメッセージのようです。
とりあえず、わたしはソースコードの内容をほぼコピーしました

  TODAYLISTITEM *ptliItem;
  BOOL    bReturn = FALSE;
  
  // get the pointer to the item from the Today screen
  ptliItem = (TODAYLISTITEM*)wParam;

  // make sure we have a TODAYLISTITEM and that the shell is ready to go
  if ((NULL == ptliItem) || (WaitForSingleObject(SHELL_API_READY_EVENT, 0) == WAIT_TIMEOUT))
  {
      return FALSE;
  }

  // if this is the first time this is called, we should set our height
  if (0 == ptliItem->cyp)
  {
    ptliItem->cyp = DRA::SCALEY(20); // 注1
    return TRUE;
  }
    
  return FALSE;

ただ、上の「注1」の部分は、Todayプラグインの縦幅を示すので、ちゃんと考えて入力しておきましょう。
DRA::SCALEYというのはよく分かりませんが、おまじないのようなものだと思っておきます。とりあえず20=一行と思っておけば問題ないかも(わたしのところでは一応問題ありませんでした)*2

WM_ERASEBKGND

背景消去です。これもまた、サンプルのとおりに

  TODAYDRAWWATERMARKINFO dwi;
  dwi.hdc = (HDC)wParam;
  GetClientRect(hwnd, &dwi.rc);
    
  dwi.hwnd = hwnd;
  SendMessage(GetParent(hwnd), TODAYM_DRAWWATERMARK, 0,(LPARAM)&dwi);
  return TRUE;

で、問題なし。
TODAYM_DRAWWATERMARKというのは、参考6番を見ると分かりますが、背景を残すというメッセージだそうです。

WM_DESTROY

先ほど書いたとおり、0を返します。

WM_PAINT

ペイントメソッドです。ここがメインです。4番のものの場合いろいろ処理が入ってるので、作りたいものに関係あるのかないのか、見極めていかなければいけません。
とはいえここでやることは基本的に他の言語とおなじなので、問題ないと思います。C言語に不慣れな人でもそうそう詰まることはないと思います。とりあえず、

 GetWindowRect( hwnd, &rcWindBounds); 
 hDC = BeginPaint(hwnd, &ps);
 SetBkMode(hDC, TRANSPARENT);

とだけしておけば、hDCに向かって描画関数を使えば絵や文字が書けます。ウィンドウ矩形はrcWindBoundsを見れば分かりますね。
文字列を描画するときは、Unicode文字列を使わなければいけないことに注意です。
文字型はWCHARを、書式化はswprintfを使います。
一応注意点としては、設定のテキストカラーを取得する方法。

SendMessage(GetParent(hwnd), TODAYM_GETCOLOR, (WPARAM) TODAYCOLOR_TEXT, NULL);

とすれば、戻り値にテキストのカラーを示す(DelphiユーザーならTColorでおなじみ)COLORREF型の数値が返ってきますので、これをSetTextColorでhDCにセットして描画 と。難しくないですね。

配置

ここもまた困った。リファレンスないんですもの。手探りで何とかしましたので、ちょっと不安が残ります。
.NET Compact Framework一般ソフトと違って、Cabが必須なようです。ソリューションに「スマートデバイスCABプロジェクト」を追加します。
内容はよく分かりませんが、レジストリに以下のキーが必要らしい。ソリューションビューのCabプロジェクトの右クリックメニューより、「表示>レジストリ」 として、レジストリツリーを表示させます。
使用方法はろくにヘルプにないんですが、たぶんエクスプローラと使用感がおなじなので分かる…はず。
なお、書き込むキーはすべて「HKLM\Software\Microsoft\Today\Items\%AppName%*3\」です。

  • DLL(文字列値)=DLLの絶対パス
  • Enabled(DWORD)=1
  • Selectability(DWORD)=0か1か2*4
  • Type(DWORD)=4

あとは、メニューの「表示>ファイルシステム」より、Windowsフォルダに、「プロジェクト名(アクティブ)のプライマリ出力」を置くようにすればOKです。


そのほかの注意点としては

  • Manufactureに日本語が使えない(わたしみたいに、普段日本語名を名乗ってる人は注意)
  • ProducrNameは変更しておくこと(初期値はCABプロジェクトの名前ですが、これがソフトの名前になります)
  • ついいじってしまいたくなりますが、PocketPC2003にインストールさせたい場合、Compressをfalseにしなければいけません(初期値のまま)。
  • CABプロジェクトの右クリックメニュー>プロパティより、出力ファイル名を見直しておきましょう(これまた初期値はCABプロジェクの名前なので)

テスト円滑化

さて、これをエミュレータで動かすには、開発PCのCABが作成されたフォルダを共有フォルダにしてエミュレータから見えるようにして、直接エミュレータからCABを開かせるのが楽です。
この方法だとデバッグできませんが、二回目からは別の方法を使えるのでデバッグができるはずです(わたしはデバッグしてないので。そんなのが必要なほど複雑なことやってないし)。


で、二回目以降は、「エミュレータ側でToday画面からプラグインを取り除く→VisualStudioでビルドして配置→再度エミュレータでTodayプラグインを使うように設定する」という流れが最速かも。もちろん配置はCABで設定したものとあわせておきましょう。設定値を誤ったりすると、Today画面全体に影響を与えてしまう(PocketPC2003のバグ?)ので注意です。
なお、試してみましたが、これならエミュレータを再起動する必要すらありません。
まあ、それでも面倒なものは面倒なので、Windows上でアルゴリズムのテストをしてからプラグイン化を行う というのがベストなのかも。


と、こんなことを繰り返しているうちに、なんとかTodayプラグインが完成したわけです。もっと凝ったことをしようと思うともっと大変なのかもしれませんが、とりあえず一段落っと。

おまけ 月齢を求めるには

これはわたしのソフトの実装ですが、月齢を求めるコードの方もとまどいました。JavaScriptのサンプルなら山ほどありましたが、CでJavaScriptのgetTimeに相当するものが思いつかず…(こんな調子じゃ、DelphiC#でやるときも困りそうだな… 何かあったと思うんですけどね)。
とりあえず簡易式でごまかしました。まあ、TODAYに貼るくらいならあれが適当かもしれません。
ちなみに、再描画時にはじめて日付等を更新しますので、TODAYのまま次の日になったりした場合、表示が更新されません。TIMERを使っても良かったんですが、電力を無駄遣いしそうで…。

*1:こうやって読み込めない場合は、アイコンファイルが間違ってるか、リソースIDが違うかです。確認しましょう

*2:文字サイズを設定で小さくしていると、若干不都合が出てくるようですね、支障はないですが、対応しておくとカッコいいかもしれません

*3:この変数はそのまま書き込んじゃって良いです。

*4:参考5番に載ってます。わたしは1にしましたが、書いてあるとおりコーディング無しで選択処理まではやってくれます、すごい