ONKYO TW317A5 の活用

 国内初となる "スレート PC" ONKYO TW シリーズの TW317A5 に関する記事です。

 Delphi によるスレート PC プログラミングも並行してやりたいと思います。



諸元

 TW317 シリーズには初代の TW317A5、au の TW317A7、法人向けの TW317A7PH があります。

スペック

 TW317A5 及びそのバリエーションのスペックは以下の通りです。

機能 TW317A5 TW317A7 TW317A7H TW317A7PH
OS Windows® 7 Home Premium 32ビット 正規版
(日本語版)
Windows® 7 Professional 32ビット 正規版
(日本語版)
Office ソフト
CPU インテル® Atom™ プロセッサー N450
(1 コア / 1.66 Ghz / HT 対応 / 64bit 対応 / TDP 5.5W)
ディスプレイ タッチパネル付 11.6型 ワイド TFT カラー液晶 (光沢パネル / LEDバックライト搭載)
最大1,366×768ドット (約 1,619 万色)
マルチタッチ対応 (静電容量方式 / 最大接触点数: 2)
グラフィックシステム インテル® GMA 3150
Broadcom Crystal HD
サウンドシステム Realtek ALC269 オーディオコーデック (HD Audio 準拠)
システムメモリー PC2-5300/667MHz 1GB
(DDR2 SDRAM 200pin SO-DIMM)
PC2-5300/667MHz 2GB
(DDR2 SDRAM 200pin SO-DIMM)
ディスクドライブ SSD 32GB
(SanDisk SSD P4 32GB / mSATA)
光学ドライブ
無線 LAN IEEE802.11 b/g/n
(Atehros AR9002WB-1NG Wireless network Adapter)
Bluetooth® Bluetooth® Ver.2.1 + EDR
(Atheros AR9002WB-1NG Bluetooth Module)
Webカメラ 130万画素 CMOS センサー搭載 Web カメラ (前面)
スピーカー 内蔵ステレオスピーカー
マイク 内蔵モノラルマイク
メモリーカードスロット SD / SDHC / SDXC / MMC 共用スロット
センサー ・3 軸加速度センサー
・照度センサー (AC 時は無効)
インターフェース ・ヘッドホン端子 (ステレオ ミニジャック)
・USB 2.0 ポート×2
・デジタルディスプレイ出力端子 (HDMI ミニ Type C)
・SIM スロット
・ドック用コネクタ
バッテリー リチウムイオンポリマーバッテリー
(7.4V 4,800mAh)
動作時間 約 5 時間 約 4.4 時間
本体寸法 295×195×14mm (突起物は含まず)
液晶部: 256×144mm
重量 約 1.0 kg
価格 ¥69,800 (au ショップでご確認ください) ??? ¥79,800
付属品 ACアダプター,リカバリーディスクキット,各種マニュアル,他

 ネットブック的なスペックですが、Windwos 7 の機能は一通り備えています。

兄弟機

 TW317 シリーズには兄弟機があります。

 これらの機種のリソースを流用できる事を覚えておくといいと思います。

後継機

 実質的な後継機は TW3A-A1 シリーズです。

 TW317 シリーズとの大まかな違いは以下の通りです。

 Windows 8 のメトロ UI の実質的な最低画面解像度である 1366×768 もそのままです。

アクセサリ

 ...極端に少ないのですよね。

 画像でマット代わりに敷いてあるのは Embarcadero デベロッパーキャンプの際に頂いた資料入れです (非売品)。エンボスで滑り止めになって丁度いい感じです。

付属アプリケーション

 タスクトレイにある幾つかのアプリケーションはスレート PC を扱う上で重要なものとなっています。

レビュー

 個人的な感想を。

 ちなみに指紋は定番のメガネ拭き用クロスで拭き取るのがいいのですが、脂分が多い場合には "無水エタノール" で拭いてからメガネ拭き用クロスで拭くといいでしょう。ティッシュは物にもよりますが、細かい擦り傷が付く事があります。

分解

 TW317A5 のメモリ換装を行うには分解するしかありません。構造はシンプルですが、上部カバーのツメを折りやすいので注意が必要です。

  1. 矢印の割れ目の所に爪を掛け、上方に向かって水平にこじ開ける。

  2. 印のネジを左右 2 本外す。左側のネジ穴には封印のシールがある。

    そのまま背面を下方へ、写真で裏になっている液晶パネル側を上方へ並行にスライドさせる。

  3. 上部の方からゆっくりと 75°近くまで起こす。裏蓋と液晶パネル側はフィルムケーブルで繋がっているので、勢いよく開けない事。

    赤い四角で囲まれた箇所が SO-DIMM スロット。シールで保護してあるので、シールをはがして換装する必要がある。

  4. メモリを換装し終わったら、分解と逆の手順で組み立てる。

 ※画像はクリックすると拡大します。
 ※分解はあくまでも自己責任で行ってください。

さらに分解

 メモリ換装だけならシールドを外す必要はありませんが、SSD の換装やモジュールの追加を行う場合にはシールドを外す必要があります。シールドを外す際にはフィルムケーブルを取り外す必要があります。画像はシールドを外した状態の TW317A5 です。

 シールドを外すためにはタッチパネル側と繋がっている 3 つのケーブルを外してやらなければなりません。

 2 つのフィルムケーブルはコネクタを 90°跳ね上げてやると抜く事ができます。もう一つのコネクタはプルタブが付いていますのでそれを持って上に引き抜くと外れます。

 ※画像はクリックすると拡大します。
 ※分解はあくまでも自己責任で行ってください。


Windows 7

 1GB でも結構普通に動作します。決して速くはないですが、遅くもないです。

 なお、TW317 シリーズに使われている CPU、Atom N450 は 64bit OS 対応なので、Windows 7 x64 に入れ替える事も可能ですが、殆どメリットはありません。N450 のメモリ上限が 2GB となっているからです。

快適に動作させるための環境整備

 メインで使ってる PC がそれなりに非力なので、Windows 7 を快適に動作させるためのノウハウは (残念ながら) 若干持ち合わせています。危険を伴わない程度のライトチューニングですが効果は結構あります。

 まず、SSD を劣化させにくくしましょう。SSD は書き込み回数に制限があるので、同じ領域に何度も書き込むべきではありません。

 次に体感的なパフォーマンスを向上させます。

アップデータの入手

 アップデータは ONKYO のサポートサイトから入手できます。また、兄弟機である EXOPC のサイトのドライバも利用可能です。

 こちらには ONKYO サポートサイトには存在しない、  等もあります。もちろん、ご利用は自己責任でお願いします。なお、現行のものは既にタッチスクリーンファームウェアが 1.006h にアップデートされているようです。

Windows 7 SP1

 Windows 7 SP1 を適用する前に、必ず ONKYO のサポートサイト から Atheros の Bluetooth ドライバを DL してきて事前にインストールして下さい。これをやらずに SP1 を適用すると Bluetooth が利用不能になります

 Windows 7 SP1 を適用すると、Microsoft のドライバも更新されてしまい、そちらの方が新しいために専用のドライバがインストールできなくなってしまいます。詳しくは ONKYO サポートサイトの Q&A "Windows 7でWindows Update 時にドライバをインストールしない設定を教えてください。" を参照して下さい。

 アップデートドライバは、以下の手順で適用します。

  1. アップデータを TW317A5 へ保存します。
    Bluetooth は無線 LAN とコンボになっており、Bluetooth のドライバをアンインストールすると無線 LAN のドライバもアンインストールされてしまうため、アップデータを LAN からコピーする事が不可能となってしまいます。USB フラッシュメモリや SD カードをお持ちであれば話は別ですが。
  2. [コントロールパネル | プログラムと機能] を開きます。

  3. "Atheros WLAN and Bluetooth Client Installation Program" をアンインストールします。

  4. TW317A5シャットダウンします。
  5. TW317A5 の電源を入れます。
  6. DL してきたアップデータをインストールします。
 デバイスマネージャからではなく、必ずコントロールパネルからアンインストールして下さい。わざわざシャットダウンして電源を入れているのは、私の環境では再起動だと何度試してもうまくいかなかったからです。

Windows 7 KB2571011

 ONKYO のサポート情報にもありますが、Windows 7 はそのままだとスクリーンキーボードで特定のキーコンビネーションを受け付けません。

 これはキーボードレスなタッチパネル搭載機にはキツイ問題なので、KB2571011 の適用をオススメします。なお、この修正プログラムの入手にはメールアドレスが必要です。


ハードウェア

 TW317 シリーズは拡張性が乏しいので、2 つしかない USB ポートは有意義に使うべきでしょう。少なくとも 1 つは USB ポートの空きを作っておくべきかと。

Bluetooth キーボード (1)

 手持ちの "リュウド アールボードフォーケイタイ 2100BTJ (Bluetooth HID. JIS配列) RBK-2100BTJ" を接続してみましたが、至って普通に使えるみたいです。

 折り畳みの収納式スタンドが付属していますが、本来は PDA 用スタンドですから、とても TW317 シリーズを支えられる代物じゃありません。ペアリングの方法はマニュアルを参照して下さい。

 後継機や OEM 品もあります。

Bluetooth キーボード (2)

 手持ちの "バトル&ゲット ポケモンタイピングDS" のキーボードを接続してみましたが、こちらも普通に使えるみたいです。

 付属のスタンドでは TW317 を支えられないと思います。

Bluetooth マウス

 これまた、手持ちの "Bluetooth 光学マウス (3 ボタン / オプティカルマウス)" を接続してみましたが、こちらも普通に使えるみたいです。

 ペアリングの方法はマニュアルを参照して下さい。

USB キーボード & マウス

 普通に USB キーボードとマウスを接続してしまうと USB ポートをすべて使い切ってしまうし、コードが邪魔になります。かと言って、ワイヤレスタイプのものでも USB レシーバーが必要なタイプだと結局は USB ポートを消費してしまいます。

 USB 接続ならキーボードとタッチパッドが一体化している SANWA SUPPLY SKB-TP01SV が良さそうです。サイズも TW317 シリーズとほぼ同じとなっています。カラーバリエーションがシルバーしかないのが少々残念ですが...。

 USB キーボードだけでいいのなら、ケースと USB キーボードが一体となった Leather Folio Case With Integrated USB Keyboard が良さげです。但し英語キーボードになります。

メモリ

 TW317A5 の場合、2GB に交換する (増設ではなく交換) に越した事はないですが、分解は難しい部類だと思います (構造は簡単なのですが、ツメを折ったりフィルムケーブルを破損しやすいと思います)。SSD は書き込み回数に上限があるため、Windows を少ないメモリで稼働させるとスワップアウトした分は SSD に書かれてしまい、劣化は当然早くなってしまいますので、最初から TW317A7 / TW317A7PH を選択した方が良いでしょう。

 前者は規格に合致したメモリで、後者は上位規格となっています。

 当方もメモリを D2N800CQ-2GLZJ に換装していますが、Windows 7 の Windows エクスペリエンスインデックスのメモリのスコアは換装前の 4.5 から 4.6 へと微妙に上がっています。

SD / SDHC / SDXC カード

 SSD 容量の少なさを補うためには、SDHC / SDXC カードを使う事になると思います。大容量で安い SDHC / SDXC カード を使うのもいいですが、

 高速に読み書きできる SDHC を使うと、ファイルコピー等のストレスが低減できると思います。基本的には Class 10 規格の SDHC カードで充分 (UHS-I には対応していないハズ) だと思われますが、一口に Class 10 と言っても実測するとバラつきがありますので、SDHC カードのベンチマーク結果等を参考に購入される事をオススメします。

有線 LAN

 TW317 シリーズには有線 LAN ポートがありませんので、有線 LAN 接続をしたければ USB タイプの有線 LAN アダプタかドッキングステーションを使う事になります。

 100 BASE ですが、UE-200TX-G2 はコンパクトでいいですね。

3G & GPS

 TW317 シリーズには3G スロットこそありますが、ここに SIM を突っ込んでも何も起こりません。3G を使うには、"3G & GPS Sierra Wireless 3G Card (MC8781)" が別途必要となります (兄弟機 である EXOPC の 3G + WiFi モデルには "Huawei EM770W 3G/GPS" が搭載されているようです)。なお、JustSlate.com で 3G + GPS モジュールを購入する場合には PayPal のアカウントが必要です (日本在住の場合)。

 3G / GPS モジュールの取り付け方法や、動作状況については上記 Youtube の動画を参考にして下さい。上記動画は 兄弟機 である EXOPC のものです (3G & GPS Sierra Wireless 3G Card (MC8781) を購入する際は JustSlate.com の FAQ を必ずお読みください)。TW317 シリーズでの動作は未保証ですのでご了承ください。

 残念ながら、3G & GPS Sierra Wireless 3G Card (MC8781) は Windows 7 のロケーション API に対応していません。また、MC8781 を購入の際はアンロックされているかどうかを必ず確認するようにして下さい (自前でのアンロックには追加で 10 英ポンド 掛かります)。

3G & GPS Sierra Wireless 3G Card (MC8781) の GPS

 3G & GPS Sierra Wireless 3G Card (MC8781) の GPS はスタンドアロンで動作しますが、ファームウェアのバージョンによっては GPS 機能が使えません。私が JustSlate.com から購入した MC8781 のファームウェアは "F1_0_0_4CAP" で、そのままでは GPS 機能が使えませんでした。

  1. DC Unlocker を DLし、ファームウェアのバージョンを確認する。

    JustSlate.com の "3G Watcher" ではファームウェアのバージョンを確認できなかったような気がします。加えて GPS ツールも付いていなかった気がします。
  2. F1_2_3_15AP ファームウェアアップデータ (http://www.sierrawireless.com/resources/software/88x/AC881U_F1_2_3_15ap.exe) を DL し実行する。
  3. "3G Watcher" ではなく "AirCard Watcher" を使う。

    "AirCard Watcher" は DL したファイル名が "Watcher_Generic.msi" であれば、大抵どれでも動作します。例えば、2. でファームウェアを流用させてもらった AirCard 881U 用 のものが使えます。

 ちなみに、DC Unlocker は MC8781 のアンロックを行う事もできます。但しアンロックは有料かつ PayPal のアカウントが必要です。

 JustSlate.com のアンテナでは実用に耐えないかもしれません (GPS を有効にするので精一杯かも)...ですが、感度の良い内蔵できる GPS アンテナとなると入手が困難です。


ソフトウェア

 TW317A5 に必要なアプリケーションをインストールしてみます。ヒトそれぞれ、好みのあるアプリケーションについては言及しません。

Internet Explorer 9

 TW317A5 の CPU は Atom N450 で HT 対応とはいえシングルコアなので、描画をハードウェアに投げてしまう Internet Explorer 9 の方が快適に動作するように思います...しかしながら、あくまで CPU 負担の軽減が目的なので HTML5 アニメーションとかの過度な期待は禁物です。

Firefox (4 以降)

 IE9 と同じ理屈です。Firefox はメモリ喰いなイメージがありましたが、リソースが限られたマシンではそれなりの動作をするようです。

Adobe Reader X

 プリインストールされている Adobe Reader は 9.4.4 なので (TW317A7PH には "Adobe Reader X" がプリインストールされています)。但し、これを入れたらスタートアップから Adobe 関係の自動アップデートアラータを削除しておいた方が良いでしょう。自動アップデートのためだけに常駐アプリが走るというのはナンセンスですから。

Microsoft Office

 微妙ですが、ノート PC 代わりに使えない事もないのでインストールしてみてもいいでしょう。もちろん、その際には キーボードとマウスが必須となります。

 ただ、最近の MS-Office はサイズが大きいので、Office Personal 2010 (Word / Excel / Outlook) を最小インストールするか、無償の LibreOfficeMicrosoft Office ビューア を併用するといいと思います。

アンチウィルスソフト

 軽いのは AVG AntiVirus です。個人で使うなら Free Edition もあります。但し、インストール時にリンクスキャナは外しておいた方がいいです。簡単に AVG AntiVirus 2011 Free Edition のインストール方法を紹介します。

  1. この画面では "基本的な保護" にチェックを入れます。こちらが Free Edition で、もう片方は体験版になります。

  2. この画面では "カスタムインストール" にチェックを入れ、"AVG 2011 ガジェット" のチェックを外します。

  3. この画面では "リンクスキャナ" のチェックを外します。

  4. この画面では "AVG セキュリティツールバー" のチェックを外し、"AVG Secure Search を既定のプロバイダにする" のチェックを外します。

    後は任意です。

 これはアンチウィルスソフトウェア全般に言える事ですが、"%SystemRoot%\SoftwareDistribution\DataStore" 以下のファイルを監視対象外にすると Windows の起動や終了等が早くなる事があります。このフォルダ内にあるのは Windows Update のカタログで、起動時/終了時に Windows から頻繁にアクセスされるのですが、これにアンチウィルスソフトが反応してしまい動作が重くなってしまいます。

 フォルダそのものを除外すると、ここにウィルスが入っても感知しなくなるので面倒でも配下のファイルを対象外に設定するようにして下さい。

ExTOUCH

 TW317A7 に付属する ExTOUCH (PC Watch の記事) は TW317A5 / TW317A7PH に付属していませんが、ExTOUCH の facebook ページで 「いいね」をクリックした人を対象に、個人向けExTOUCHが無償提供されています。 (配布終了の模様です)

HotKey2WindowsKey

 Download: HotKey2WindowsKey_110.zip (2014/04/27)

 TW317 シリーズで動作する常駐型アプリケーションです。ホットキーを押すと Windows キーを押した事にします。これを使えば "タスクバーを隠す" 設定が可能になるため、画面を幾らか広く使えます。

 Windows 8 搭載スレート PC にはスマホの Home ボタンに相当するボタン (Windows ボタン) が大抵付いていますが、Windows 7 世代のスレート PC にはこれがないものが多いです。TW317 シリーズにも Home ボタンに相当するボタンがないのでこれをホットキーで代用するためのアプリケーションです。


スレート PC プログラミング

 お仕着せのアプリケーションばっかり触っても楽しくないので自分でプログラムを組んでみる事にします。TW317 シリーズ...ネットブックにも言える事ですが、非力な PC ではネイティブアプリケーションの出番です。このトピックでは Delphi を使ったスレート PC アプリケーション開発の Tips を掲載していきます。

 タッチアプリケーションの概要については

 これらを事前に読んでおく事をオススメします。なお、使用する Delphi は Delphi XE を想定していますが、基本的には Delphi 2010 でも動作すると思います。

マウスジェスチャ対応画像ビューア

 まずは肩慣らしに "マウスジェスチャ対応画像ビューア" を作ってみましょう。Delphi だと 10 分もあれば作れます (以下をコピペなら 5 分も掛らないハズです)。

 とりあえず仕様を。

 操作性や機能の向上は各自やって下さいね。では行ってみましょう。

  1. Delphi で新規プロジェクトを作成します。
  2. フォームに ActionManager を貼ります (ActionManager1)
  3. フォームに GestureManager を貼ります (GestureManager1)
  4. フォームに Button を貼ります (Button1)
  5. フォームに Button を貼ります (Button2)
  6. フォームに Panel を貼ります (Panel1)
  7. Panel1 に Image を貼ります
  8. ここまでの作業でこんな感じになると思います。

    では次に進みましょう。

  9. オブジェクトインスペクタでフォームのプロパティを変更します。

    ※(任意) の所は変更しなくても支障はありません。

  10. フィールド (クラス内プライベート変数) を追加します。

      private
        { Private 宣言 }
        FIndex: Integer;
        FPicList: TStringList;
        ...

    FIndex はページのインデックスを指すための変数で、FPicList は画像のフルパス名を保持するリストです。

  11. 上記変数の初期化処理/終了時処理を記述します。

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      FIndex := 1;
      FPicList := TStringList.Create;
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      FPicList.Free;
    end;

    FIndex の初期値が 1 になっている理由は後で解ります。

  12. フォーム上の ActionManager をダブルクリックし、設定ダイアログを開きます。
  13. "アクションの新規作成" でアクションを追加します。
  14. "アクションの新規作成" でアクションを追加します。
  15. 設定ダイアログの "アクション" にある "<<" をダブルクリックしてイベントを記述します。

    procedure TForm1.Action_LeftExecute(Sender: TObject);
    begin
      if FIndex > 0 then
        begin
          Dec(FIndex);
          Image1.Picture.LoadFromFile(FPicList[FIndex]);
        end;
      Panel1.SetFocus;
    end;

    ページを戻る処理になります。

  16. 同様に、">>" をダブルクリックしてイベントを記述します。

    procedure TForm1.Action_RightExecute(Sender: TObject);
    begin
      if FIndex < FPicList.Count-1 then
        begin
          Inc(FIndex);
          Image1.Picture.LoadFromFile(FPicList[FIndex]);
        end;
      Panel1.SetFocus;
    end;

    ページを進む処理になります。

  17. フォームの OnShow イベントを記述します。

    procedure TForm1.FormShow(Sender: TObject);
    var
      FilePath: string;
      FileName: string;
    begin
      OnShow := nil;
    
      FilePath := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'pic';
      if not DirectoryExists(FilePath) then
        ForceDirectories(FilePath);
    
      for FileName in TDirectory.GetFiles(FilePath, '*.jpg', TSearchOption.soAllDirectories) do
        FPicList.Add(FileName);
    
      if FPicList.Count > 0 then
        Action_LeftExecute(nil);
    end;

    ファイル一覧を取得してリストに突っ込んでいます。

  18. uses に IOUtils と Jpeg を追記します。

    uses
      ..., IOUtils, Jpeg;

    TDirectory が使えるようにするのと、TImage で Jpeg を使えるようにするためです。

  19. Button1 のプロパティを変更します。
  20. Button2 のプロパティを変更します。
  21. Panel1 のプロパティを変更します。
  22. Image1 のプロパティを変更します。

    上記設定で、"画像をフォームサイズに合わせてアスペクト比を保持しつつ拡大縮小し中央に置く" 事が可能になります。

  23. ここまでの作業でこんな感じになると思います。


  24. Image1 の Touch.GestureManager に GestureManager1 を指定します
  25. Touch.Gestures.Standard にある基本ジェスチャにアクションを割り当てます
  26. Touch.Gestures.Standard.Left にチェックを入れ、Action_Left を割り当てます
  27. Touch.Gestures.Standard.Right にチェックを入れ、Action_Right を割り当てます
  28. Touch の所は最終的にこうなります。


    "マウスでクリックしたまま左へ移動しマウスを離す操作" が基本ジェスチャの "Left" です。逆が "Right" になります。

 これにて完成です。一度実行してそのまま終了すると、EXE のあるフォルダの下に pic フォルダができますので、そこへ任意の Jpeg 画像を突っ込んでみて下さい。

  

 このように、画像が変わってもアスペクト比を保ったまま可能な限り拡大して中央に画像が表示されます。左右のボタンまたはカーソルキーの左右、またはジェスチャで画像を進めたり戻したりできます。要は各種イベントが ActionManager で管理されているのなら GestureManager を貼って、適当なジェスチャを選び、そこへアクションを設定してやるだけでジェスチャーアプリケーションが実現できるという事です。

 Jpeg 以外の画像ファイルを読み込む方法については、

 に詳細がありますので、そちらを参照して下さい。

リモートデバッガをインストールする

 TW317 シリーズに Delphi をインストールするのは容量や開発効率の面でオススメしません...とはいえ、マルチタッチやセンサー等、スレート PC 固有のデバッグを行う必要はあります。

 そこでリモートデバッガの出番となります。Delphi のリモートデバッガのインストール方法については、

 に詳細がありますので、そちらを参照して下さい。

タッチイベントを取得する

 タッチイベントを取得できなければ何も始まりません。まずはタッチイベントを取得する所から始めてみます。

マルチタッチ関連のイベントとプロパティ

 対話型ジェスチャと OnGesture イベントの EventInfo パラメータは以下のような関係になっています。

Action GestureID Flags Location Distance TapLocation Angle InertiaVector
TGestureID TInteractiveGestureFlags TPoint Integer TSmallPoint Double TSmallPoint
X Y X Y X Y
ズーム igiZoom gfBegin = 開始
gfInertia = 慣性
gfEnd = 終了
パン igiPan
回転 igiPan
二本指タップ igiTwoFingerTap
プレス & タップ igiPressAndTap

 EventInfo.DistanceEventInfo.TapLocation は共用体になっている事に注意して下さい。

 マルチタッチに関連するプロパティは以下の通りです。

 理屈上では確かにこのようになっていますが、実際にやってみるとうまくいかない事が多いのが悩みどころです。

フリックの挙動

 [コントロールパネル | ペンとタッチ] の "フリック" でフリックを有効にしていないと WM_TABLET_FLICK が飛んでこないようです。ですが、これを有効にしたままだとシステム既定のフリックのアイコンが出てしまいますよ...?

 先に述べたようにアプリケーションで独自にフリックを検出するには WM_TABLET_FLICK を処理すればいいのですが、フリックの方向や位置を調べるには wParam に FLICK_DATA、lParam にFLICK_POINT が入ってきますので、これを調べます...が、構造体の定義で嫌な予感がするでしょうが、これは 32bit のビットフィールドになっています。

TFLICK_DATA TFLICK_POINT
bit Field Type
31 ActionArgument SmallInt
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15 OnInkingSurface Boolean
14 Reserved SmallInt
13
12 ShiftModifier Boolean
11 WinModifier Boolean
10 AltGRModifier Boolean
9 MenuModifier Boolean
8 ControlModifier Boolean
7 FlickDirection Byte
6
5
4 FlickActionCommandCode Byte
3
2
1
0
bit Field Type
31 Y SmallInt
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15 X SmallInt
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0

 詳しくは述べませんが、Delphi はビットフィールドの扱いが苦手です。ビットフィールドなら...

type
  { TFLICK_DATA }
  TFLICK_DATA = 
    record
      FlickActionCommandCode: 0..31//  5bit
      FlickDirection: 0..7;          //  3bit
      ControlModifier: 0..1;         //  1bit
      MenuModifier: 0..1;            //  1bit
      AltGRModifier: 0..1;           //  1bit
      WinModifier: 0..1;             //  1bit
      ShiftModifier: 0..1;           //  1bit
      Reserved: 0..2;                //  2bit
      OnInkingSurface: 0..1;         //  1bit
      ActionArgument: -32768..32767// 16bit
    end;
  { PFLICK_DATA }
  PFLICK_DATA = ^TFLICK_DATA;

 これでいいような気がしますが、SizeOf(TFLICK_DATA) をやってみて下さい。4 を返さないと思います。packed{$A-} コンパイラ指令等の指定も無意味です。

 wParam / lParam を DWORD でそのまま受けると後々面倒なので、各構造体には以下のユニットで定義されているレコードを使うといいでしょう。

unit TabFlicks;

interface

uses
  Windows, Types;

const
  FLICK_WM_HANDLED_MASK               = 1;

  FLICKACTION_COMMANDCODE_NULL        = 0;
  FLICKACTION_COMMANDCODE_SCROLL      = 1;
  FLICKACTION_COMMANDCODE_APPCOMMAND  = 2;
  FLICKACTION_COMMANDCODE_CUSTOMKEY   = 3;
  FLICKACTION_COMMANDCODE_KEYMODIFIER = 4;

  FLICKDIRECTION_RIGHT                = 0;
  FLICKDIRECTION_UPRIGHT              = 1;
  FLICKDIRECTION_UP                   = 2;
  FLICKDIRECTION_UPLEFT               = 3;
  FLICKDIRECTION_LEFT                 = 4;
  FLICKDIRECTION_DOWNLEFT             = 5;
  FLICKDIRECTION_DOWN                 = 6;
  FLICKDIRECTION_DOWNRIGHT            = 7;
  FLICKDIRECTION_INVALID              = 8;

type
  { _FLICK_DATA }
  _FLICK_DATA =
  record
  strict private
    FBitField: DWORD;
    function GetFlickActionCommandCode: Byte;
    function GetFlickDirection: Byte;
    function GetControlModifier: Boolean;
    function GetMenuModifier: Boolean;
    function GetAltGRModifier: Boolean;
    function GetWinModifier: Boolean;
    function GetShiftModifier: Boolean;
    function GetReserved: SmallInt;
    function GetOnInkingSurface: Boolean;
    function GetActionArgument: SmallInt;
    function GetValue: DWORD;
    procedure SetFlickActionCommandCode(const Value: Byte);
    procedure SetFlickDirection(const Value: Byte);
    procedure SetControlModifier(const Value: Boolean);
    procedure SetMenuModifier(const Value: Boolean);
    procedure SetAltGRModifier(const Value: Boolean);
    procedure SetWinModifier(const Value: Boolean);
    procedure SetShiftModifier(const Value: Boolean);
    procedure SetReserved(const Value: SmallInt);
    procedure SetOnInkingSurface(const Value: Boolean);
    procedure SetActionArgument(const Value: SmallInt);
    procedure SetValue(const V: DWORD);
  public
    constructor Create(const InitValue: DWORD);
    property FlickActionCommandCode: Byte read GetFlickActionCommandCode write SetFlickActionCommandCode;
    property FlickDirection: Byte read GetFlickDirection write SetFlickDirection;
    property ControlModifier: Boolean read GetControlModifier write SetControlModifier;
    property MenuModifier: Boolean read GetMenuModifier write SetMenuModifier;
    property AltGRModifier: Boolean read GetAltGRModifier write SetAltGRModifier;
    property WinModifier: Boolean read GetWinModifier write SetWinModifier;
    property ShiftModifier: Boolean read GetShiftModifier write SetShiftModifier;
    property Reserved: SmallInt read GetReserved write SetReserved;
    property OnInkingSurface: Boolean read GetOnInkingSurface write SetOnInkingSurface;
    property ActionArgument: SmallInt read GetActionArgument write SetActionArgument;
    property Value: DWORD read GetValue write SetValue;
  end;
  { TFLICK_DATA }
  TFLICK_DATA = _FLICK_DATA;
  { PFLICK_DATA }
  PFLICK_DATA = ^TFLICK_DATA;

  { _FLICK_DATA }
  _FLICK_POINT =
  record
  strict private
    FBitField: DWORD;
    function GetX: SmallInt;
    function GetY: SmallInt;
    function GetValue: DWORD;
    procedure SetX(const Value: SmallInt);
    procedure SetY(const Value: SmallInt);
    procedure SetValue(const V: DWORD);
  public
    constructor Create(const InitValue: DWORD);
    property X: SmallInt read GetX write SetX;
    property Y: SmallInt read GetY write SetY;
    property Value: DWORD read GetValue write SetValue;
  end;
  { TFLICK_POINT }
  TFLICK_POINT = _FLICK_POINT;
  { PFLICK_POINT }
  PFLICK_POINT = ^TFLICK_POINT;

implementation

{ _FLICK_DATA }
constructor _FLICK_DATA.Create(const InitValue: DWORD);
begin
  ZeroMemory(@Self, SizeOf(Self));
  FBitField := InitValue;
end;

function _FLICK_DATA.GetActionArgument: SmallInt;
begin
  result := (FBitField and $FFFF0000shr 16;        // bit16 to bit31
end;

function _FLICK_DATA.GetAltGRModifier: Boolean;
begin
  result := ((FBitField and $00000400) = $00000400); // bit10
end;

function _FLICK_DATA.GetControlModifier: Boolean;
begin
  result := ((FBitField and $00000100) = $00000100); // bit8
end;

function _FLICK_DATA.GetFlickActionCommandCode: Byte;
begin
  result := (FBitField and $0000001F);               // bit0 to bit4
end;

function _FLICK_DATA.GetFlickDirection: Byte;
begin
  result := (FBitField and $000000E0shr 5;         // bit5 to bit7
end;

function _FLICK_DATA.GetMenuModifier: Boolean;
begin
  result := ((FBitField and $00000200) = $00000200); // bit9
end;

function _FLICK_DATA.GetOnInkingSurface: Boolean;
begin
  result := ((FBitField and $00008000) = $00008000); // bit15
end;

function _FLICK_DATA.GetReserved: SmallInt;
begin
  result := (FBitField and $00006000shr 13;        // bit13 to bit14
end;

function _FLICK_DATA.GetShiftModifier: Boolean;
begin
  result := ((FBitField and $00001000) = $00001000); // bit12
end;

function _FLICK_DATA.GetValue: DWORD;
begin
  result := FBitField;
end;

function _FLICK_DATA.GetWinModifier: Boolean;
begin
  result := ((FBitField and $00000800) = $00000800); // bit11
end;

procedure _FLICK_DATA.SetActionArgument(const Value: SmallInt);
begin
  // bit16 to bit31
  FBitField := FBitField and (not $FFFF0000);
  FBitField := FBitField or (DWORD(Value) shl 16);
end;

procedure _FLICK_DATA.SetAltGRModifier(const Value: Boolean);
begin
  // bit10
  if Value then
    FBitField := FBitField or $00000400
  else
    FBitField := FBitField and (not $00000400);
end;

procedure _FLICK_DATA.SetControlModifier(const Value: Boolean);
begin
  // bit8
  if Value then
    FBitField := FBitField or $00000100
  else
    FBitField := FBitField and (not $00000100);
end;

procedure _FLICK_DATA.SetFlickActionCommandCode(const Value: Byte);
begin
  // bit0 to bit4
  FBitField := FBitField and (not $0000001F);
  FBitField := FBitField or DWORD(Value);
end;

procedure _FLICK_DATA.SetFlickDirection(const Value: Byte);
begin
  // bit5 to bit7
  FBitField := FBitField and (not $000000E0);
  FBitField := FBitField or (DWORD(Value) shl 5);
end;

procedure _FLICK_DATA.SetMenuModifier(const Value: Boolean);
begin
  // bit9
  if Value then
    FBitField := FBitField or $00000200
  else
    FBitField := FBitField and (not $00000200);
end;

procedure _FLICK_DATA.SetOnInkingSurface(const Value: Boolean);
begin
  // bit15
  if Value then
    FBitField := FBitField or $00008000
  else
    FBitField := FBitField and (not $00008000);
end;

procedure _FLICK_DATA.SetReserved(const Value: SmallInt);
begin
  // bit13 to bit14
  FBitField := FBitField and (not $00006000);
  FBitField := FBitField or (DWORD(Value) shl 13);
end;

procedure _FLICK_DATA.SetShiftModifier(const Value: Boolean);
begin
  // bit12
  if Value then
    FBitField := FBitField or $00001000
  else
    FBitField := FBitField and (not $00001000);
end;

procedure _FLICK_DATA.SetValue(const V: DWORD);
begin
  FBitField := V;
end;

procedure _FLICK_DATA.SetWinModifier(const Value: Boolean);
begin
  // bit11
  if Value then
    FBitField := FBitField or $00000800
  else
    FBitField := FBitField and (not $00000800);
end;

{ _FLICK_POINT }
constructor _FLICK_POINT.Create(const InitValue: DWORD);
begin
  ZeroMemory(@Self, SizeOf(Self));
  FBitField := InitValue;
end;

function _FLICK_POINT.GetValue: DWORD;
begin
  result := FBitField;
end;

function _FLICK_POINT.GetX: SmallInt;
begin
  result := (FBitField and $0000FFFF);        // bit0 to bit15
end;

function _FLICK_POINT.GetY: SmallInt;
begin
  result := (FBitField and $FFFF0000shr 16// bit16 to bit31
end;

procedure _FLICK_POINT.SetValue(const V: DWORD);
begin
  FBitField := V;
end;

procedure _FLICK_POINT.SetX(const Value: SmallInt);
begin
  // bit0 to bit15
  FBitField := FBitField and (not $0000FFFF);
  FBitField := FBitField or DWORD(Value);
end;

procedure _FLICK_POINT.SetY(const Value: SmallInt);
begin
  // bit16 to bit31
  FBitField := FBitField and (not $FFFF0000);
  FBitField := FBitField or (DWORD(Value) shl 16);
end;

end.

 もっと簡単な方法をご存じの方がいらっしゃいましたら、ご教示下さい m(_ _)m

 使い方はこのようになります。

uses
  ..., TabFlicks;

type
  TForm1 = class(TForm)
    ...
  private
    { Private 宣言 }
   procedure TabletFlick(var msg: TMessage); message WM_TABLET_FLICK;
  public
    { Public 宣言 }
  end;

...  

procedure TForm1.TabletFlick(var msg: TMessage);
var
  s: String;
  FLICK_DATA: TFLICK_DATA;
  FLICK_POINT: TFLICK_POINT;
begin
   s := 'フリック';

   // フリックの方向を取得
   FLICK_DATA  := TFLICK_DATA.Create(msg.WParam);
   case FLICK_DATA.FlickDirection of
     FLICKDIRECTION_RIGHT:
       s := s + '(右)';
     FLICKDIRECTION_UPRIGHT:
       s := s + '(右上)';
     FLICKDIRECTION_UP:
       s := s + '(上)';
     FLICKDIRECTION_UPLEFT:
       s := s + '(左上)';
     FLICKDIRECTION_LEFT:
       s := s + '(左)';
     FLICKDIRECTION_DOWNLEFT:
       s := s + '(左下)';
     FLICKDIRECTION_DOWN:
       s := s + '(下)';
     FLICKDIRECTION_DOWNRIGHT:
       s := s + '(右下)';
     FLICKDIRECTION_INVALID:
       s := s + '(不正)';
  end;
  // フリックの位置を取得
  FLICK_POINT := TFLICK_POINT.Create(msg.lParam);
  s := s + Format(' X=%d Y=%d', [FLICK_POINT.X, FLICK_POINT.Y]);

  Memo1.Lines.Add(s);

  // システムデフォルトのフリックをキャンセルする
  msg.Result := FLICK_WM_HANDLED_MASK;
end;

 「FlickDirection は 3bit しかないんだから、FLICKDIRECTION_INVALID が発生する事はないよね?」 とかいうツッコミは Microsoft さんにお願いします。

 戻り値として FLICK_WM_HANDLED_MASK を指定しないと、システムデフォルトのフリックが実行されてしまいます。どうやってもアイコンの件が解決しないような気がしますが...アプリケーションで独自のフリックをやりたければ "パン" とイナーシアでやれって事なのでしょうか?

 Flicks Gestures (MSDN) に、

The flick feedback consists of two parts; an icon representing the action and a label containing the name of the action. The label is displayed below the icon. The feedback is displayed immediately after the flick is detected. Although applications can customize their behavior in response to flicks by handling the flick window message, the application cannot disable or modify the flick feedback.

 とありますね...うーん。"アプリで制御はできるけど、フリックフィードバックは無効にできないよ" 何故、このようなアホな仕様になっているのでしょうか?言い訳としては "フリックが成功したかどうかわかんないじゃん?" という事のようですが、それを言うのならジェスチャだって成功したかどうか判らないと思うのですが?フリックフィードバックを消す事が出来たら何の不都合があると言うのでしょう?

 WM_APPCOMMAND に頼らずに自力でフリックを制御したい理由ですが、フリックの左右に割り当てられている "進む/戻る" が Vista / 7 で逆になっているからです。Vista では右方向へのフリックが "進む" なのですが、7 では左方向へのフリックが "進む" になっています。Internet Explorer / Media Player 等の "進む" 方向は右ですよね?パンの操作に合わせて変更されたらしいのですが、これではキーボード等と併用する際に紛らわしくて仕方ありません。ナビゲーションフリックの設定をユーザが変更する可能性もあるため、フリックはアプリケーションで制御したいのです。

UI の最小サイズを試算する

 タッチ操作の場合、あまりにも細かい UI は操作できません。ユーザーインターフェイスのガイドラインは タッチ (MSDN) にありますが、ちょっと待って下さい。この情報は本当に正しいのでしょうか?

 ここに書かれているのはディスプレイが本当に 96dpi であると仮定した場合の事です...今時、ちゃんと 96dpi なディスプレイなんて存在しないのですし。嘘だとお思いなら、以下の表を元にお使いのディスプレイのサイズを測ってみて下さい。

モード ピクセル 横幅 (cm) 縦幅 (cm)
SVGA 800×600 21.17cm 15.87cm
XGA 1024×768 27.09cm 20.32cm
UWSVGA 1280×600 33.86cm 15.87cm
WXGA 1280×768 33.86cm 20.32cm
WXGA 1280×800 33.86cm 21.17cm
SXGA 1280×1024 33.86cm 27.09cm
HD 1366×768 36.14cm 20.32cm
SXGA+ 1400×1050 37.04cm 27.78cm
WSXGA 1600×1024 42.33cm 27.09cm
UXGA 1600×1200 42.33cm 31.75cm
FHD 1920×1080 50.80cm 28.57cm

 本来なら液晶パネルのサイズは上記の表の通りでなくてはなりません。TW317 シリーズの実測サイズは以下のようになっています (キリのいい数字になるように端数を調整しています)。

 TW317 シリーズは HD 液晶なので、リアル 96dpi だと横幅は 361.4mm になってしまいます。ドットピッチが 0.1875mm なので、実際の dpi は "25.4 (1 inch) / 0.1875 = 135.46666..." となります。これを 96 で割ればリアル 96dpi との比率が出ます。1.411111... ですね。

procedure TForm1.Button1Click(Sender: TObject);
var
  Value, Pixels, mmSize: Currency;
  s: string;
begin
  s := '';
  Pixels := StrToCurr(Edit1.Text); // 横ピクセル
  mmSize := StrToCurr(Edit2.Text); // 実測した液晶パネルの横幅 (mm)

  // 実 DPI を求める
  Value  := 25.4 / (mmSize / Pixels);
  s := s + '実 DPI: ' + FormatCurr('#,##0.00', Value) + #$0D#$0A;
  // 実 96dpi との比率を求める
  Value  := Value / 96;
  s := s + '比率: ' + FormatCurr('#,##0.00', Value);

  ShowMessage(s);
end;

 実 DPI と実 96dpi との比率を表示するソースコードを掲載しておきます。Edit1 に横解像度 (ピクセル数)、Edit2 に液晶パネルの実測横幅 (mm) を入力してボタンを押してください。

Javascript で計算
横解像度:px
横幅:mm
実 dpi:ppi
dpi 比:dpi/ppi
ドットピッチ:mm

 以下は実測せずにカタログスペックから実 DPI と実 96dpi との比率を表示するソースコードです。

uses
  ..., Math;

procedure TForm1.Button2Click(Sender: TObject);
var
  Value, Pixels, dWidth, dHeight, dInch, dDegree: Currency;
  s: string;
begin
  s := '';
  Pixels  := StrToCurr(Edit1.Text); // 横ピクセル
  dWidth  := Pixels;
  dHeight := StrToCurr(Edit2.Text); // 縦ピクセル
  dInch   := StrToCurr(Edit3.Text); // カタログでの液晶サイズ (Inch) = 斜辺

  // 角度を求める
  dDegree  := ArcTan(dWidth / dHeight);
  // 横幅を求める (Inch)
  dWidth   := dInch * Sin(dDegree);
  // 縦幅を求める (Inch) - Unused
  dHeight  := dInch * Cos(dDegree);
  // 実 DPI を求める
  Value  := Pixels / dWidth;
  s := s + '実 DPI: ' + FormatCurr('#,##0.00', Value) + #$0D#$0A;
  // 実 96dpi との比率を求める
  Value  := Value / 96;
  s := s + '比率: ' + FormatCurr('#,##0.00', Value);

  ShowMessage(s);
end;

 Edit1 に横解像度 (ピクセル数)、Edit2 に縦解像度 (ピクセル数)、Edit3 に液晶パネルのインチ数 (inch) を入力してボタンを押してください。カタログでの液晶パネルのインチ数は結構あいまいなので (11.6インチを 12インチと表記したりするので) 上の実測バージョンより精度は劣りますが、大体の目安にはなるでしょう。なお、TW317 シリーズのパネルのインチサイズは実測で 11.57 です。

Javascript で計算
横解像度:px
縦解像度:px
画面インチ数:inch
実 dpi:ppi
dpi 比:dpi/ppi
ドットピッチ:mm

 話を元に戻します。Microsoft のガイドラインでピクセル表記になっているものは、

コントロールの最小サイズは 23 × 23 ピクセル (13 × 13 DLU)、
最もよく使用されるコントロールは 40 × 40 ピクセル (23 × 22 DLU) 以上です。 

 TW317 シリーズの場合、1.4倍で読み替える必要があるという事になります。

コントロールの最小サイズは 32 × 32 ピクセル、
最もよく使用されるコントロールは 56 × 56 ピクセル以上です。 

 まぁ、そうなると TW317 シリーズでは殆どのコモンコントロールはデフォルトサイズだとアウトな訳ですが。なお、DLU については "レイアウト メトリック (MSDN)" を参照して下さい。Twips...思えば、そんなのもありましたねぇ (´ー`)y-~~

 iPhone や iPad の強みの一つはハードウェアが固定されている事です。このスレート PC や Andoroid 機は各メーカーが異なる仕様のものを発売しているため、描画一つにしたって決め打ちする事ができません。ラスター型の画像は拡大縮小すれば汚くなりますし、すべてをベクター画像でやろうとすると SVG や WMF / EMF を使うしかありません。画面サイズと解像度の問題を解決するには、異なる解像度用にリソース DLL を用意して対処するしかないのかもしれませんね。

マルチタッチが可能かどうかを調べる

 操作性が全くと言っていいほど異なるため、個人的にはマルチタッチ対応アプリケーションと普通のデスクトップ用アプリケーションとは分けて作った方がいいと思いますが、代替手段で両対応させなければならない事もあるでしょう。このような場合には、マルチタッチ機能を有しているかどうかを判定する必要があります。

 マルチタッチが可能かどうかは GetSystemMetrics(SM_DIGITIZER) で問い合わせる事ができます。

  CanMultiTouch := (GetSystemMetrics(SM_DIGITIZER) and NID_MULTI_INPUT) > 0;

 NID_MULTI_INPUT (0x40) が立っていればマルチタッチ可能です。

  CanMultiTouch := CheckWin32Version(61and ((GetSystemMetrics(SM_DIGITIZER) and NID_MULTI_INPUT) > 0);

 万全を期すならこのようなコードになりますが、マルチタッチではない機能...例えばフリック等は Vista でも可能です (ペンに対応していればいい) ので SM_DIGITIZER の他のフラグを調べるなどして、実情に合ったコードを記述して下さい。

タッチパネルが利用可能な接触点の最大数を得る

 Windows 7 は最大で 255 の接触点をサポートしていますが、ハードウェア毎に最大接触点数は異なります。最大接触点数は GetSystemMetrics() で SM_MAXIMUMTOUCHES を問い合わせる事によって取得できます。

var
  MaxTP: Byte;
begin
  MaxTP := GetSystemMetrics(SM_MAXIMUMTOUCHES);
end;

 TW317A5 の同時に処理できる接触点は 2 つですので、ピアノのようなアプリケーションを作るのは無理っぽいですね (ドミソが同時に押せない)。

 Microsot のデモでよくある 5 本指を使ったマルチタッチアプリを実現するためにはハードウェアサポートが必要です。アプリケーションの目的によってはマルチタッチディスプレイを購入する際に最大接触点数を確認する必要があります。

アクションをジェスチャマネージャに動的に割り当てる

 マルチタッチが使えない場合には、ジェスチャマネージャで代替する事も考えなくてはならないでしょう。以下はアクションをジェスチャマネージャに動的に割り当てたり解除するコードとなります。

// アクションを標準ジェスチャに動的に割り当てる
var
  GestureItem: TCustomGestureCollectionItem;
begin
  Form1.Touch.GestureManager   := GestureManager1;
  Form1.Touch.StandardGestures := [sgLeft, sgRight];
  for GestureItem in GestureManager1.GestureList[Form1] do
    begin
      if      GestureItem.Name = 'Left'  then
        GestureItem.Action := Action_Left
      else if GestureItem.Name = 'Right' then
        GestureItem.Action := Action_Right;
    end;
end;

// 標準ジェスチャを解除する
var
  GestureItem: TCustomGestureCollectionItem;
begin
  Form1.Touch.StandardGestures := [];
//Form1.Touch.GestureManager   := nil;
end;

 Form1 上のパネル (Panel1) に割り当てられたジェスチャの場合には上記コードの Form1 を Panel1 で置き換えて下さい。

 「何故ジェスチャマネージャを動的に割り当てたり解除したりしなくてはならないのか?」 と思われる事でしょう。もっともな疑問ですが、これは実際にマルチタッチと TGestureManager を同時に利用してみると解ります。例えば Standard Gesture の左右を有効にしたままマルチタッチが有効だと、フリックを始め殆どのマルチタッチ機能が誤動作してしまいます。要は機能がカブってしまうのです。

自動回転に応答させる

 TW317 シリーズは Millenium というアプリケーションにより、自動で画面を回転させる事ができます。

 自動回転を検出するには幾つか方法がありますが、最も簡単だと思われる方法は、

type
  TForm1 = class(TForm)
    ...
    FOWidth: Integer;
    FOHeight Integer;
    procedure WMSettingChange(var Msg: TWMSettingChange); message WM_SETTINGCHANGE;
    ...
  end;  
...

procedure TfrmMain.FormCreate(Sender: TObject);
begin
//FOWidth  := GetSystemMetrics(SM_CXSCREEN);
//FOHeight := GetSystemMetrics(SM_CYSCREEN);
  FOWidth  := Screen.Width;
  FOHeight := Screen.Height;
  ...
end;

procedure TForm1.WMSettingChange(var Msg: TWMSettingChange);
var
  dWidth, dHeight: Integer;
begin
  inherited;
//dWidth  := GetSystemMetrics(SM_CXSCREEN);
//dHeight := GetSystemMetrics(SM_CYSCREEN);
  dWidth  := Screen.Width;
  dHeight := Screen.Height;
  if (FOWidth <> dWidth) or (FOHeight <> dHeight) then
    begin
      FOWidth  := dWidth;
      FOHeight := dHeight;

      // 回転されたか解像度が変更された
      ...
    end;
end;

 WM_SETTINGCHANGE メッセージを捕捉し、そこで Scrren 変数の値または GetSystemMetrics(SM_CXSCREEN) / GetSystemMetrics(SM_CYSCREEN) を調べるやり方です。この方法だと画面の回転だけでなく解像度の変更にも対応します。

 いずれにせよ、回転を検知したら UI を変更する必要があります。縦持ちと横持ちで同じ UI というのは使いにくい事が多いからです。

 UI が変更される過程が見えてしまうのはみっともないので、

begin
  LockWindowUpdate(Form1.Handle);
  try

  // UI 変更処理

  finally
    LockWindowUpdate(0);
  end;  
end;

 LockWindowUpdate() で画面の描画を一旦停めてしまいましょう。

画面の向きを変更する

 Millenium や Intel GMA の機能 を使って画面を回転させる事は可能です。しかし、センサーによる自動回転はレスポンスが悪く、意図しない角度で自動回転してしまう事があります。Intel GMA のは (キーボードの) ホットキーを押下するか、またはタスクトレイを右クリックしなくてはなりません。スレート PC においてこの操作方法は致命的です。

 ...という訳でアプリケーションで画面の向きを制御してみましょう。画面の向きを変えるには "ChangeDisplaySettings() を使います。

 ChangeDisplaySettings()Delphi でも普通に使えるのですが、DEVMODE 構造体を書き換えないと少々面倒です。幸いな事に、Changing Screen Orientation Programmatically using Delphi (The Road to Delphi)" において、画面の向きを変更するサンプルが掲載されていますので、これをユニットにして使う事にしましょう。

// -----------------------------------------------------------------------------
// Changing Screen Orientation Programmatically using Delphi (The Road to Delphi)
// http://theroadtodelphi.wordpress.com/2011/02/11/changing-screen-orientation-programmatically-using-delphi/
// -----------------------------------------------------------------------------
unit ScreenRotation;

interface

uses
  Windows, SysUtils;

type
  _devicemode = record
    dmDeviceName: array [0..CCHDEVICENAME - 1of Char;
    dmSpecVersion: WORD;
    dmDriverVersion: WORD;
    dmSize: WORD;
    dmDriverExtra: WORD;
    dmFields: DWORD;
    union1: record
    case Integer of
      0: (
           dmOrientation: Smallint;
           dmPaperSize: Smallint;
           dmPaperLength: Smallint;
           dmPaperWidth: Smallint;
           dmScale: Smallint;
           dmCopies: Smallint;
           dmDefaultSource: Smallint;
           dmPrintQuality: Smallint
         );
      1: (
           dmPosition: TPointL;
           dmDisplayOrientation: DWORD;
           dmDisplayFixedOutput: DWORD
         );
    end;
    dmColor: Shortint;
    dmDuplex: Shortint;
    dmYResolution: Shortint;
    dmTTOption: Shortint;
    dmCollate: Shortint;
    dmFormName: array [0..CCHFORMNAME - 1of Char;
    dmLogPixels: WORD;
    dmBitsPerPel: DWORD;
    dmPelsWidth: DWORD;
    dmPelsHeight: DWORD;
    dmDiusplayFlags: DWORD;
    dmDisplayFrequency: DWORD;
    dmICMMethod: DWORD;
    dmICMIntent: DWORD;
    dmMediaType: DWORD;
    dmDitherType: DWORD;
    dmReserved1: DWORD;
    dmReserved2: DWORD;
    dmPanningWidth: DWORD;
    dmPanningHeight: DWORD;
  end;
  devicemode  = _devicemode;
  Pdevicemode = ^devicemode;

const
  DM_DISPLAYORIENTATION = $00800000;
  ENUM_CURRENT_SETTINGS = -1;
  DMDO_DEFAULT: DWORD   = 0;
  DMDO_90: DWORD        = 1;
  DMDO_180: DWORD       = 2;
  DMDO_270: DWORD       = 3;

procedure ChangeOrientation(NewOrientation: DWORD);
function ScreenOrientation: DWORD;

implementation

procedure ChangeOrientation(NewOrientation: DWORD);
var
  dm: TDeviceMode;
  dwTemp: DWORD;
begin
  ZeroMemory(@dm, SizeOf(dm));
  dm.dmSize := SizeOf(dm);
  if EnumDisplaySettings(nil, DWORD(ENUM_CURRENT_SETTINGS), dm) then
    begin
      if Odd(Pdevicemode(@dm)^.union1.dmDisplayOrientation) <> Odd(NewOrientation) then
        begin
         dwTemp := dm.dmPelsHeight;
         dm.dmPelsHeight := dm.dmPelsWidth;
         dm.dmPelsWidth  := dwTemp;
        end;
      if Pdevicemode(@dm)^.union1.dmDisplayOrientation <> NewOrientation then
        begin
          Pdevicemode(@dm)^.union1.dmDisplayOrientation := NewOrientation;
          if (ChangeDisplaySettings(dm, 0) <> DISP_CHANGE_SUCCESSFUL) then
            RaiseLastOSError;
        end;
    end;
end;

function ScreenOrientation: DWORD;
var
  dm: TDeviceMode;
  dwTemp: DWORD;
begin
  ZeroMemory(@dm, SizeOf(dm));
  dm.dmSize := SizeOf(dm);
  if EnumDisplaySettings(nil, DWORD(ENUM_CURRENT_SETTINGS), dm) then
    result := Pdevicemode(@dm)^.union1.dmDisplayOrientation
  else
    result := DMDO_DEFAULT;
end;
end.

 ScreenRotation を uses して ChangeOrientation() を呼び出すだけで簡単に画面の向きを変更する事ができます。このユニット内にある ScreenOrientation() という名のもう一つの関数は、現在の画面の向きを返すものです。

uses
  ..., ScreenRotation; 

procedure TForm1.Button1Click(Sender: TObject);
begin
  ChangeOrientation(DMDO_90);      // 90°回転
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ChangeOrientation(DMDO_DEFAULT); // 元に戻す
end;

 TW317A5 ではすべての方向に回転できる事を確認しており、Millenium による自動回転よりもレスポンスがいいようです。

 なお、言うまでもないことかもしれませんが...画面の向きを標準に戻す機能を作ってから実行するようにしましょう。さもないと画面を元に戻すのに難儀するハメになります (^^;A

 さて、回転ができるようになって喜んでいると、困った場面に遭遇する事になると思います。"自動回転に応答させる" の項で書いた WM_SETTINGCHANGE が飛んでこない事があるからです。これを回避するには、WM_DISPLAYCHANGE メッセージに応答するようにします。

type
  TForm1 = class(TForm)
    ...
    procedure WMDisplayChange(var Msg: TWMDisplayChange); message WM_DISPLAYCHANGE;
    ...
  end;  
...

procedure TForm1.WMDisplayChange(var Msg: TWMDisplayChange);
begin
  inherited;

  // ここに処理を書く
end;

 TW317A5 の場合には、WM_DISPLAYCHANGE にだけ対応すればいいようですね。Millenium で自動回転させると、WM_SETTINGCHANGEWM_DISPLAYCHANGE の両方が飛んでくるようです。

UI と画面の向き

 TW317 シリーズにはハードウェアボタンがない (ホットキーはありますが) ため、画面上に何らかのボタンが必要となります。マルチタッチのジェスチャだけでは足りませんし、ジェスチャより確実に動作するボタンがあった方がいい事もあるでしょう。

 一例として画像ビューアの UI を考えてみます。画像は 4:3 またはそれに近い比率のものが多く、ワイドディスプレイである TW317 シリーズ では両端がデッドスペースになる事はお解り頂けると思います。ここに操作ボタンを設置するとこんな感じになります。

 横長の画像がアスペクト比を壊す事無く最大限に表示されていますね...いい感じです。最下段のボタンは見た目操作しやすそうですが、実際にはとても押しにくく、下から 2 段目あるいは 3 段目あたりが最も押しやすい位置のボタンとなります。ここによく使うボタンを割り当てるとよいでしょう。

 今度は縦長の画像を用意してみましょう。

 この画像、実際にはスクエアなのですが、縦長の画像だと思って下さい。左右に余白ができてしまいますね。縦長ですから画像を横に回転 (270°回転) させればいいような気がします。

 ですが、これだと UI と画像の向きが一致しないため読みづらくて仕方ありません。では、左右の黒い操作パネルを Align = alLeft / alRight のまま、自動回転 (90°回転) に頼ってみる事にしましょう。

 おやおや、これではさっきよりも悪くなってしまいました。デッドスペースを全く活かせていません。では、画像を横に回転 (270°回転) させた上で回転機構を OFF にして本体を縦 (90°回転) にしたらどうでしょう?

 デッドスペースは活かせていますが、これだと持ち方によっては誤操作してしまいかねません。

 よって、回転した時にはデッドスペースを考えた上で UI を変化させる必要があります。忘れがちですが、スレート PC 用アプリケーションは本体を回転させる事を前提とした作りにしなくてはなりません。なお、本体を 90°回転させていたのは、270°回転だと ホットキー / 照度センサー / インジケータ を手で押さえてしまうからです。

 スレート PC には、

 平面軸だけで回転軸が 3 つある事になります。操作を簡略化させるには軸の数を減らさなくてはなりません。この画像ビューアでは画像の回転 (アプリケーション内での向き) をオミットし、  いずれかとする事で操作の簡略化を図っています。断っておきますが、この画像ビューアのデスクトップ PC 版を作る事になったとしたら、スクリーン回転ボタンは画像回転ボタンになるでしょう。デスクトップ用の回転できるディスプレイを所持している人はそう多くないハズですので、画像と UI の方向の不一致があったとしても幾らかは有用でしょう (画像の回転がなければ 0 軸ですから)。

 ここで紹介した画像ビューアは "マルチタッチアプリケーションのサンプル (Embarcadero)" から DL する事ができます。画像ビューアの操作方法は以下のようになっています。

マルチタッチ シングルタッチ
(タッチパネル / マウス)
マウス キーボード
Windows へ戻る 〔Ctrl〕+〔F12〕
前の画像へ 左フリック 〔←〕
次の画像へ 右フリック 〔→〕
先頭の画像へ 〔Home〕
最後の画像へ 〔End〕
画像を拡大する ズームジェスチャ 〔Ctrl〕+ マウスホイール(↑) 〔Ctrl〕+〔+〕
画像を縮小する 〔Ctrl〕+ マウスホイール(↓) 〔Ctrl〕+〔-〕
画像を元のサイズに戻す 二本指タップ
(ツーフィンガータップ)
中ボタン 〔Ctrl〕+〔Enter〕
拡大した画像を上へ移動 ドラッグ マウスホイール(↑) 〔Shift〕+〔↑〕
拡大した画像を下へ移動 マウスホイール(↓) 〔Shift〕+〔↓〕
拡大した画像を左へ移動 〔Shift〕+〔←〕
拡大した画像を右へ移動 〔Shift〕+〔→〕
画面を右へ 90° 回転させる
画面を左へ 90° 回転させる
画面を標準の位置に戻す

 スレート PC 用に UI を特化させたアプリケーションですが、普通のデスクトップ PC で試す事も可能です。デスクトップ PC ではフリックが使えないので、StandardGestures の Left / Right で代用しています。このため、拡大時にはジェスチャーによる画像送りはできません (ドラッグと同じ操作であるため)。

 マルチタッチのイベントを確認できるテスター (with ソースコード) も付属しています。左ペインのオプションを幾つか有効にして、水色の領域でタッチジェスチャーを行うと、右ペインのリストボックスに判定履歴が表示されます。

コンポーネントに Touch プロパティと OnGesture イベントを追加する

 例えば、古いコンポーネントとかをジェスチャ対応にしたいとします。ジェスチャを使いたいのなら、下位クラスで...

  ...
  published
    ...

    property Touch;
    ...
    property OnGesture;
    ...
  end;

 このように Published にて再定義すればいいです。

 しかし、上記をやったにも関わらず、"ズーム / 回転等は動作しても Touch.GestureManager に割り当てられた機能が動作しない" 場合があります。この際には、親コントロールを辿っていって...

    procedure CMGestureManagerChanged(var Message: TMessage); message CM_GESTUREMANAGERCHANGED;

 このようなメッセージ処理が存在しないのなら、StdCtrls.pas の TCustomEdit.CMGestureManagerChanged() のコードを丸パクリしてコンポーネントに実装するとうまく動作するようです。

生のタッチイベントを処理する

 ジェスチャやフリックを使わず、接触点 (タッチポイント) 情報を自前で処理するには WM_TOUCH に応答します。

 WM_TOUCH を処理するには、以下の API を使う必要があります。

 具体的な実装例は以下のようになります。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TRawTouchEvent = procedure (Sender: TObject; const TI: TTouchInput) of object;

  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormShow(Sender: TObject);
    procedure RawTouchDown(Sender: TObject; const TI: TTouchInput);
    procedure RawTouchUp(Sender: TObject; const TI: TTouchInput);
    procedure RawTouchMove(Sender: TObject; const TI: TTouchInput);
  private
    { Private 宣言 }
    FOnRawTouchDown: TRawTouchEvent;
    FOnRawTouchUp: TRawTouchEvent;
    FOnRawTouchMove: TRawTouchEvent;
    procedure WMTouch(var msg: TMessage); message WM_TOUCH;
  public
    { Public 宣言 }
  published
    property OnRawTouchDown: TRawTouchEvent read FOnRawTouchDown write FOnRawTouchDown;
    property OnRawTouchUp: TRawTouchEvent read FOnRawTouchUp write FOnRawTouchUp;
    property OnRawTouchMove: TRawTouchEvent read FOnRawTouchMove write FOnRawTouchMove;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// フォーム表示時
procedure TForm1.FormShow(Sender: TObject);
var
  CanMultiTouch: Boolean;
begin
  OnShow := nil;

  CanMultiTouch := (GetSystemMetrics(SM_DIGITIZER) and NID_MULTI_INPUT) > 0;
  if CanMultiTouch then
    begin
      // タッチを有効にするウィンドウを指定
      RegisterTouchWindow(Self.Handle, 0);
      // イベントを設定
      OnRawTouchDown := RawTouchDown; // 接触点が追加された (押された)
//    OnRawTouchMove := RawTouchMove; // 接触点が移動した
      OnRawTouchUp   := RawTouchUp;   // 接触点が削除された (離された)
    end;
end;

// タッチメッセージ (WM_TOUCH) 処理
procedure TForm1.WMTouch(var msg: TMessage);
var
  cInputs: DWORD;
  TouchInputs: array of TouchInput;
  TI: TTouchInput;
begin
  cInputs := LOWORD(msg.wParam); // 上位ワードは予約されている
  if cInputs > 0 then
    begin
      // 接触点構造体を確保
      SetLength(TouchInputs, cInputs);
      // 接触点情報を取得
      if GetTouchInputInfo(msg.lParam, cInputs, @TouchInputs[0], SizeOf(TouchInput)) then
        begin
          // 接触点毎にイベントを発生させる
          for TI in TouchInputs do
            begin
              // TOUCHEVENTF_DOWN
              if (TI.dwFlags and TOUCHEVENTF_DOWN) > 0 then
                if Assigned(FOnRawTouchDown) then
                  OnRawTouchDown(Self, TI);
              // TOUCHEVENTF_MOVE
              if (TI.dwFlags and TOUCHEVENTF_MOVE) > 0 then
                if Assigned(FOnRawTouchMove) then
                  OnRawTouchMove(Self, TI);
              // TOUCHEVENTF_UP
              if (TI.dwFlags and TOUCHEVENTF_UP  ) > 0 then
                if Assigned(FOnRawTouchUp) then
                  OnRawTouchUp(Self, TI);
            end;
          // タッチ入力ハンドルを閉じる
          CloseTouchInputHandle(msg.lParam);
          msg.Result := 0// WM_TOUCH を処理した
          Exit;
        end;
    end;
  DefWindowProc(Self.Handle, msg.Msg, msg.wParam, msg.lParam);
end;

// タッチイベントハンドラ(接触点毎) - TOUCHEVENTF_DOWN
procedure TForm1.RawTouchDown(Sender: TObject; const TI: TTouchInput);
var
  s: string;
  sPrimary: string;
begin
  if (TI.dwFlags and TOUCHEVENTF_PRIMARY) > 0 then
    sPrimary := '*'
  else
    sPrimary := ' ';
  s := Format('[DOWN] %s%.8x: (X=%d,Y=%d)', [sPrimary, TI.dwID, TI.x div 100, TI.y div 100]);
  Memo1.Lines.Add(s);
end;

// タッチイベントハンドラ(接触点毎) - TOUCHEVENTF_MOVE
procedure TForm1.RawTouchMove(Sender: TObject; const TI: TTouchInput);
var
  s: string;
  sPrimary: string;
begin
  if (TI.dwFlags and TOUCHEVENTF_PRIMARY) > 0 then
    sPrimary := '*'
  else
    sPrimary := ' ';
  s := Format('[MOVE] %s%.8x: (X=%d,Y=%d)', [sPrimary, TI.dwID, TI.x div 100, TI.y div 100]);
  Memo1.Lines.Add(s);
end;

// タッチイベントハンドラ(接触点毎) - TOUCHEVENTF_UP
procedure TForm1.RawTouchUp(Sender: TObject; const TI: TTouchInput);
var
  s: string;
  sPrimary: string;
begin
  if (TI.dwFlags and TOUCHEVENTF_PRIMARY) > 0 then
    sPrimary := '*'
  else
    sPrimary := ' ';
  s := Format('[UP]   %s%.8x: (X=%d,Y=%d)', [sPrimary, TI.dwID, TI.x div 100, TI.y div 100]);
  Memo1.Lines.Add(s);
end;
end.

 WM_TOUCH のメッセージの中ですべてを処理してもいいのですが、サンプルでは 3 つのイベントを発生させる事で OnMouseDown / OnMouseUp / OnMouseMove のような使い勝手を実現させています。各接触点の情報は TOUCHINPUT 構造体 に格納されます。

 生のタッチイベントを処理するので、WM_TOUCH はジェスチャやフリックとは排他利用となります。

タッチキーボード

 スレート PC での文字入力はあまりやりたくありませんが、それでも文字入力が必要な事があります。Delphi 2010 またはそれ以降では TTouchKeyborad というコンポーネントがあり、ソフトウェアキーボードを簡単に実現できます。

 TTouchKeyboard はツールパレットの [Touch] にあります。

 Layout プロパティを "NumPad" に変更するとテンキーにする事ができます。

 Layout = 'Standard' なキーボードのレイアウトは 101 / 102 / 106 キーボードが用意されており、現在のロケールに応じたキーボードが自動的に選択されます。強制的に英語キーボードにしたい場合には、

uses
  ..., Keyboard;

type
  TTempTouchKeyboard = class(TCustomTouchKeyboard);
  ...

 このようにしておいて、

procedure TForm1.FormCreate(Sender: TObject);
begin
  TTempTouchKeyboard(TouchKeyboard1).CreateKeyboard('en-US'); // 英語キーボード
  TouchKeyboard1.Redraw;
end;

 実行時に言語/国を指定します。

 漢字変換関連キーのない英語キーボードになりました。

カスタムタッチキーボード

 場合によってはキーを自前で描画したり、キー配列をカスタマイズしたい事もあると思います。キーの自前描画に関しては "TTouchKeyboardをいぢ(め)る。(全力わはー)" を参照して下さい。ここではキー配列のカスタマイズ方法を説明します。

 キー配列のカスタマイズ方法は以下の通りです。

  1. XML でキーボードレイアウトファイルを作成する
    XML の記述の仕様は "Hacking the TTouchKeyboard Part IV" にて解説されていますが、イチから作るのは面倒なので既存のキーボードレイアウトから XML ファイルを生成します。 XML の生成には "Save Touch Keyboard Layout (SaveLayout.exe)" をコンパイルして使います。そのままだとちょっとだけ使いにくいので、SaveKeyboard.pas に軽微な修正を施します。
     
    procedure SaveXmlFile(const FileName: string; KeyboardLayout: TVirtualKeyLayout);
    var
      ...
      Dmy: String// 追加
    begin
      ...
      { 出力 された XML を整形して読みやすくする }
      { ---------------------------------------- }
      Dmy := XMLDoc.FormatXMLData(Document.XML.Text);
      Document.XML.Text := Dmy;
      Document.Active := True;
      { ---------------------------------------- }
    
      Document.SaveToFile(FileName);
    end;

    XML を整形して、
     
    procedure TForm1.Button3Click(Sender: TObject);
    ...
    begin
      ...
      try
        SaveDialog1.FileName := LayoutName + '.xml'// ファイル名のデフォルトを生成
        if (Layout <> niland SaveDialog1.Execute then
          SaveXmlFile(SaveDialog1.FileName, Layout);
      finally
        Layout.Free;
      end;
    end;

    保存用のファイル名を自動的に付与します。SavaDialog1 の Filter プロパティと DefaultExt プロパティも設定しておくといいでしょう。 このツールを使って吐かれる XML は UTF-8N (BOM 無し UTF-8) で保存されますので、UTF-8N を編集できるテキストエディタがあると便利です。
     
    <keyboard keyboardname="CalcPad" keyboardtype="CalcPad" width="240" height="229" minwidth="180" minheight="150" rowheight="48">
            <row topmargin="0" bottommargin="2">
                    <key caption="R・CM" vk="120" width="48" height="48"                rightmargin="2"               />
                    <key caption="M-"   vk="121" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="M+"   vk="122" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="+/-"  vk="123" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="CA"   vk="128" width="48" height="48" leftmargin="2"                 stretch="true"/>
            </row>
            <row topmargin="2" bottommargin="2">
                    <key                vk="103" width="48" height="48"                rightmargin="2"               />
                    <key                vk="104" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key                vk="105" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="%"    vk="125" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="C・CE" vk="126" width="48" height="48" leftmargin="2"                 stretch="true"/>
            </row>
            <row topmargin="2" bottommargin="2">
                    <key                vk="100" width="48" height="48"                rightmargin="2"               />
                    <key                vk="101" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key                vk="102" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="×"   vk="106" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="÷"   vk="111" width="48" height="48" leftmargin="2"                 stretch="true"/>
            </row>
            <row topmargin="2" bottommargin="2">
                    <key                vk="97"  width="48" height="48"                rightmargin="2"               />
                    <key                vk="98"  width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key                vk="99"  width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key                vk="107" width="48" height="92" leftmargin="2" rightmargin="2"               />
                    <key                vk="109" width="48" height="48" leftmargin="2"                 stretch="true"/>
            </row>
            <row topmargin="2" bottommargin="0">
                    <key                vk="96"  width="96" height="48"                rightmargin="2"               />
                    <key                vk="110" width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key                vk="0"   width="48" height="48" leftmargin="2" rightmargin="2"               />
                    <key caption="="    vk="127" width="48" height="48" leftmargin="2"                 stretch="true"/>
            </row>
    </keyboard>

    例はテンキーパッドを改変して作った電卓用キー配列です。
    R・CM
    VK_F9
    M-
    VK_F10
    M+
    VK_F11
    +/-
    VK_F12
    CA
    VK_F17
    7
    VK_NUMPAD7
    8
    VK_NUMPAD8
    9
    VK_NUMPAD9

    VK_F14
    C・CE
    VK_F15
    4
    VK_NUMPAD4
    5
    VK_NUMPAD5
    6
    VK_NUMPAD6
    ×
    VK_MULTIPLY
    ÷
    VK_DIVIDE
    1
    VK_NUMPAD1
    2
    VK_NUMPAD2
    3
    VK_NUMPAD3

    VK_ADD

    VK_SUBTRACT
    0
    VK_NUMPAD0
    .
    VK_DECIMAL

    VK_F16

    このような配置になります。
  2. Keyboard Layout Compiler (kcc.exe) でキーボードレイアウトのバイナリを生成する
    "Keyboard Layout Compiler (kcc.exe)" はソースコードで提供されていますので、一旦コンパイルしてバイナリを生成します。出来上がった kcc.exe はコンソールアプリケーションです。 使い方はとてもシンプルで、
     
    kcc.exe キーボードレイアウトファイル

    とするだけです。正常にコンパイルされると、*.binary というファイルが生成されます。
     
    このコンパイラは Delphi のコンパイラや RTL のバージョンに依存すると思われるため、一度 kcc.exe を作ったらすべての Delphi で使えるとは思わない方がいいでしょう。面倒でもコンパイラ毎に kcc.exe を生成する必要があります。
  3. プロジェクトのリソースに RCDATA でキーボードレイアウトのバイナリを含める
    キーボードレイアウトファイルのバイナリ (*.binary) ができたら、リソースとしてアプリケーションに埋め込みます。Delphi ビルトインのリソースマネージャ ([プロジェクト | リソースと画像]) を使ってもいいのですが、何故かうまく動作しない事があるので手動でリソースを追加します。
    1. [ファイル | 新規作成 | その他...] から、テキストファイルを作成し、拡張子を *.rc にして保存します。
    2. このリソーススクリプトファイルに、以下の一行を記述します。
      CALCPADKEYBOARD         RCDATA "CALCPADKEYBOARD.binary"

    プロジェクトマネージャに *.rc があれば勝手にコンパイルされてリンクされます。リソース名は任意ですが、"KEYBOARD" という文字を含めて下さい。リソースの種類は RCDATA です。
  4. TTouchKeyboard.Layout を変更する
    アプリケーションにリソースを埋め込む事ができたら、コードで、
     
      TouchKeyboard1.Layout := 'CalcPad';

    キーボードレイアウトを変更します。

 画像はカスタムキーボードを電卓アプリケーションに用いている所です。

 レイアウト上、ダミーキーが必要なのだけれど、ダミーキーを表示させたくない場合には、以下を参考にして下さい。

  1. TCustomKeyboardButton の派生クラスを作る
     
      TKeyboardButtonEx = class(TCustomKeyboardButton)
      public
        procedure Paint(Canvas: TCustomCanvas = nil); override;
      end;
    
    
    { TKeyboardButtonEx }
    
    procedure TKeyboardButtonEx.Paint(Canvas: TCustomCanvas);
    begin
      if key.Vk = 0 then
        Exit;
      inherited;
    end;
  2. Form の OnCreate イベントハンドラ等で以下のように記述する
     
      TouchKeyboard1.DefaultButtonClass := TKeyboardButtonEx;
      TouchKeyboard1.Layout := 'CalcPad';

 このコードを書いておくと、VK = '0' に指定されているキーは描画されなくなり、VK = '0' に指定されているダミーキーをレイアウト計算用に使うことができます。

See Also:

スレート PC プログラミング向けの資料

 日本語で書かれたものは、ほぼ Microsoft のサイトに存在します。

 MSDN の資料は左ツリーの操作性が悪く、堂々巡りをしがちなので、MSDN エイリアスを用意しました。水平分割版もあります。


ここにある情報が役に立って、「調べる手間が省けたからオマイに飯でもおごってやるよ」 というハートウォーミングな方がいらっしゃいましたら、下のボタンからどうぞ。

メニュー: