(12/09/01~)

12/09/01

EULA Change: No Client/Server in XE3 Pro. Not even 3rd Party. の顛末 (その1)
 このサイトは XE3 のリーク情報を載せているブログで、かいつまんで話すと、

 ...てな、話が書いてあるエントリがありました。これが騒ぎになって、  公式フォーラムで喧々諤々となりましたとさ。

 元々のブログを読んでみると、"EULA 変更"と言っている根拠 (元ソース) となっているのは、とある BBS の "Changes in the Delphi EULA" というトピックのようでした (フォーラムの方は二次以降のソースなので、あまり読む気にもなりませんでした)。

 "Changes in the Delphi EULA" を読んで思った事は、

 という事でした。そこで、  「大体、こんな感じの話じゃないの?」と僕は推測しました。僕の twitter のツイートはこの推測が元になっています。どんなツイートをしたかと言うと...

 では、「何故このような推測をしたのか?」ですが、3 年前の出来事が頭にあったからです。

 知らないヒトのためにちょっと解説すると、RAD Studio 2010 の時に Firebird の DBX ドライバが追加されました。RTM の数週間前から 「2010 では DBX による Firebird サポートが行われる」 と公式に発表がなされていましたが、"Professional 版はリモート接続禁止はおろかドライバすら提供されない" という事実は製品発売時点で初めて判明しました。FTer だった僕も A7M さんもこの事を知りませんでした。

 ぶっちゃけ、FTer であろうと "RTM 前に (ちゃんとした) EULA なんか読めない" って事です。SKU 毎の違いなんかも RTM 前には知り得ません。つまり、"EULA 自体は変わってないけど解釈に齟齬が生じた" としか思えなかったのです。

 もちろん、8/28 が RTM 当日だった (その日に EULA を読む事が出来た) 可能性もありますけど、もしそうだったとしても Partner DVD の構成で大混乱が起きるハズですから、"土壇場で EULA が変更された事を通知する" なんて事はやらないハズです。普通に考えれば、相当前に EULA 変更通知を 3rd Party に行うでしょうしね。

EULA Change: No Client/Server in XE3 Pro. Not even 3rd Party. の顛末 (その2)
 公式フォーラム はその後しばらく盛り上がりを見せます..."荒れた" とも言いますが。

 そしてフォーラムに新しいスレッドが立ちます。そして、ここに XE3 の EULA の一部が投下されました。

ADDITIONAL LICENSE TERMS APPLICABLE TO STARTER EDITION

In the event Licensee has obtained a Starter Edition license the following terms hereby apply. Licensee may use the Product to develop software (i) for which Licensee does not charge directly or indirectly a fee or receive other consideration including but not limited a license fee, a service fee, a development fee, a consulting fee, a subscription fee, a support fee, a hosting fee or the like and (ii) for which Licensee only charges fees (including but not limited a license fee, a service fee, a development fee, a consulting fee, a subscription fee, a support fee, a hosting fee or the like) that cumulatively, on an annual basis, do not exceed USD$1000. In the event any (for profit or non-profit) company elects to license the software then (i) the total company revenues may not exceed USD$1000 and (ii) the total number of Starter Edition licenses deployed may not exceed 5.

ADDITIONAL LICENSE TERMS APPLICABLE TO SOFTWARE LICENSED FOR EDUCATIONAL USE

In the event Licensee has obtained an educational license the following terms apply. Licensee may exercise Licensee's rights under this Agreement to use the Product and to create Works solely for Licensee's own personal use in providing or receiving instruction within the limited scope of guided computer programming and/or software training courses in which Licensee are a direct and personal participant, either as student or instructor ("Courses"). Licensee may only reproduce, distribute and use Works, in source or object code form, to other participants of the Courses and then only for educational or training purposes. Licensee may not use the Products or Works created with the products for any commercial, business, governmental or institutional purpose of any kind, except to the extent Licensee are an instructor teaching a Course. All rights not specifically granted to Licensee herein are retained by Licensor.

ADDITIONAL LICENSE TERMS APPLICABLE TO RAD STUDIO, DELPHI AND C++BUILDER, PROFESSIONAL AND PROFESSIONAL ACADEMIC EDITIONS

In the event Licensee has obtained a RAD Studio, Delphi or C++Builder Professional, or Professional Academic product license then the following terms apply.

Subject to the terms and conditions of this Agreement, Licensor grants to Licensee as the licensed user of the Product the limited right to use that portion of the Product identified as "dbExpress", in executable form only, to access a local database installed on the same machine as the Work. Licensee may not use that portion of the Product identified as "dbExpress" in association with a database located on a different machine other than the machine on which the Works are installed.

CLIENT/SERVER PACK

If licensee has purchased a Client/Server Pack, the Licensee of RAD Studio, Delphi, or C++Builder XE3 Professional Edition ("Product") may deploy that portion of the Product identified as "dbExpress" and dbExpress enterprise database drivers, in executable form only, to enable client server database access. Embarcadero may deliver the Product identified as "Enterprise," however Licensee is licensed to use only the "Professional" edition features plus "dbExpress" and the Enterprise dbExpress database drivers in a client/server configuration. Licensee may evaluate the n-Tier DataSnap functionality included in the Enterprise Product delivered, but may not deploy or redistribute DataSnap.

 読むと解りますが、これは XE2 の EULA"CLIENT/SERVER PACK" 用の条文が追加されただけです。で、またもやツイートしました。

 結論: XE3 の EULA は XE2 と変わらない。
 教訓: 明確な元ソースがない場合は話半分で聞け。


12/09/02

Delphi VCL Tips
 忙しさにかまけてアナウンスしていませんでしたが、ひとつ追加されています。

 Delphi-ML の質問 "[delphi-users:2599] dbfファイルを読み込む" に対する回答です。

Delphi FireMonkey Tips
 忙しさにかまけてアナウンスしていませんでしたが、幾つか追加されています。

 サンプルソースコードを見る限りでは、XE3 の FM2 だとちょっと違うトコもあるようですが。

Delphi XE3 および C++Builder XE3 の新機能 (DocWiki)
 公開されたようですので、ちょっとじっくり読んでみたいと思います。

 関連して上記二つも。EDN に XE3 の EULA (End User License Agreement) もアップされています


12/09/03

XE3 の DocWiki を読んで判明した事、再確認できた事。
 とりあえずつらつらと (だいたい) 原典付きで。

 個人的に気になる点は大体こんなトコ。C++Builder の変更点は少ないので、第22回デブキャン【T2】セッション で語られていた事は実現できていないようだ...64bit コンパイラも含めて。

FireMonkey 2 (XE3) についての感想
 XE2 の FireMonkey で足りないと感じていた機能がほぼ網羅された。来年初に発売予定の Microsoft Surface (Pro) を含むスレート PC プログラミングには最適な開発環境になったかもしれない。てか、それをウリにすればいいのに。てか、スレート PC バンドル製品出せばいいのに。ONKYO TW3A-A31 シリーズ とかオススメ。

 タッチおよびジェスチャと仮想キーボードが実装された事により、スレート PC プログラミングが実用的なものになった。オーディオが実装された事により、Flash みたいな事ができるようになった。ビデオとセンサー&ロケーションが実装された事により、AR (仮想現実) が比較的簡単に実現できるようになった。スバラシイ!

iOS 開発がオミットされた件 (XE3) についての感想
 このサイトでは以前から 「現時点では iOS に注力すべきではない」 とか、「C++Builder には FPC に相当するコンパイラが存在しない」 とか書いてたしなぁ...。"ARM コンパイラを自社開発している" というアナウンスがあったので、このツールチェインによる手法は将来的には無くなる方向にある事は目に見えていたし。まさか XE3 で早々にツールチェインできなくなるとは思わなかったけれど。

 個人的にはスマホやタブレット開発は別製品とまではいかなくともある程度分離すべきだと思う。デスクトップアプリをスマホアプリにしたりその逆をやって失敗したのが Windows Mobile ですよね?操作性が異なるものを無理にまとめるべきではないと思う。マルチプラットフォームのフレームワークに大きな足枷が付くだけだし...かと言って、Smart Mobile Studio のようなものでお茶を濁すのも勘弁してください。コレでお茶を濁すようならツールチェインの方がまだマシです。

 これまた個人的には 「FireMonkey もあっていいけど "VCL for iOS" のようなネイティブ開発環境がいいなぁ」 と思っております。

Metropolis UI アプリケーションのライブタイル (XE3) についての感想
 これが有用かどうかは置いといて、面白い技術だと思う。ただ、疑問点がない訳ではない。

 "Metroスタイル・アプリの開発者が知るべき3つのこと(@IT)" にあるように、"Modern UI (Metro) アプリ" は Windows Store で配布しなくてはならないが、問題は Embarcadero.Metropolis.LiveTileProxy.exe がどうやって配布されるのか? という事だ。

 Embarcadero.Metropolis.LiveTileProxy.exe は Embarcadero が Windows Store にアップすれば済む話なのか?それとも個別にアップしなくてはならないのか?Windows Store を経由しない場合、サイドローディングを行わなくてはならないが、その場合には Windows 8 Enterprise が必要となってしまう。この辺りがどうなっているのかが記されたドキュメントは未だ存在しないようだ。

 追記: サイドローディングの方法に関しては "Metropolis UI 用ライブ タイルの開発環境のセットアップ" に記述があった...んーちょっと面倒かな (^^;A

SkRegExp 1.3.2 リリース
 小宮さん作の正規表現ライブラリ SkRegExp の version 1.3.2 がリリースされました。

RAD STUDIO XE3 トライアル版リリース
 XE3 のトライアル版がリリースされました。

   
   

 当方ではトライアル版をインストールした際、「上限に達した」旨のメッセージが出て "機能制限付き 14 日トライアル" になってしまいましたので、カスタマーサポートから上限を更新してもらいました。

XE3 製品情報
 XE3 の製品情報が公開されました。

 SQLite は Professional 版で (ローカル接続) 利用可能なようです。BDE 代替の手段が増えました! (^o^)/

Delphi VCL Tips
 XE3 に合わせて更新しました。

 EULA の件の日本語訳が載っています。

Delphi FireMonkey Tips
 XE3 に合わせて更新しました。

 コンポーネントは地道に増えてますね。


12/09/04

第 23 回エンバカデロデベロッパーキャンプ開催!
 UStream によるライブ配信も行われます...午前の部のライブ配信がないのが少々残念ですが。感想は後程。

XE3 ショップリンク
 XE3 を購入できるショップへのリンクです。恒例ですね。

特別バージョンアップ (2009~XE2 ユーザー向け) 2012/09/27 まで

ジャンプアップキャンペーン (1~2007 ユーザー及び Starter Edition ユーザー向け) 2012/12/26 まで

 ※価格にポイント等は反映されていません。また、価格は変動する事がありますのでリンク先を必ず確かめるようにして下さい。

 以前に掲載していた OSJ オンラインさんは残念ながら 9/18 で閉店だそうです。

Delphi の製品情報
 Delphi XE3 情報を更新しました。


12/09/05

RAD Studio XE3 での旧製品ライセンス
 XE 以降恒例ですが、XE3 を購入すると旧製品を利用する事ができます (Starter / アカデミック除く)。

 しばらく Delphi 単体製品の購入が続いた方には RAD Studio がお買い得だと思います。

エンバカデロ (標準) 製品価格表
 RAD Studio XE3 を含む エンバカデロの製品価格表が更新されました。実は以前から Delphi の製品情報 の左ペインに "Price List" があったのですけど...。製品価格表を読んでいて思ったのですが、Archtect 版の "インストールメディア (DVD) 付き製品" はなくなったのですね。Ultimate 同様 ESD のみのようです。

 製品価格表には載っていませんが、XE3 の EULAClient/Server Pack が明記されているという事はそのうち XE3 用の Client/Server Pack が発売されるのでしょうか?てか、最初から商品ラインナップにあればいいのになぁ。


12/09/06

RAD Studio XE3 サポート KB より

 トレンドマイクロェ...。

 また、XE3 のインストーラが何時まで経っても終わらないと思ったらタスクバーを確認してみて下さい。ヘルプ (Document Explorer) セットアップが裏に回っている事があります。

XE3 のトライアル版が「登録の上限」と言われて製品登録できない場合には?
 事前に EDN にログインしておくといいと思います。EDN アカウント作成、編集、ログイン方法については "ディスカッションフォーラムの使い方" を参考にしてみて下さい。

 まず、製品登録(使用許諾)ファイルの回復 を試してみて下さい。これでうまく行かなかった場合 (例えば XE3 トライアルの登録コードの番号が 0 になっているとか) には以下を試してみて下さい。

  1. ご質問フォーム (https://support.embarcadero.com/forms/) に行きます。
  2. 入力欄を埋めます。
  3. [ご質問の送信] ボタンを押します。
 メールで上限更新の通知が送られてきたら、再度製品登録を行ってみてください。

Embarcadero のデモの神様についてトゥギャってみた。
 やりとりが面白かったのでトゥギャってみました。

 ...なんか林さんが罠にはめられた感がありますね。登場人物でマトモなのは DH 山本さんだけってのが、また。・゜・(/Д`)・゜・。

 余談ですが、アテナの杖は "ニケの杖" で、アテナの随神であるニケの変化だとか。こちらで右手の上に乗ってるのがニケ神。ナイキとも言いますね...有名ですが、あの Nike のロゴはニケの翼が由来です。


12/09/07

DocWiki 内カスタム検索
 Embarcadero 公式フォーラムでイロイロありまして、"Embarcadero RAD Studio DocWiki 内カスタム検索" を作ってみました。

 例えば、RAD Studio 2010 の検索を利用すると、XE 以降で追加された新しい関数やメソッドはヒットしなくなります。"その時点での機能""バージョン間の差異" を調べる事ができるという訳です。ヘルプが DocWiki ベースになったのは 2010 からですから、2009 以前の検索はできません。2007 は 付属のオンラインヘルプ (MS-Help2 / Help Update 4 相当) の元になった HTML が公開されているので、そこを検索対象としています (DocWiki ベースではありません)。

 「DocWiki メインページの左側から各バージョンへ行けるじゃん?」 と言われそうですね...確かに左側の "以前のバージョン" から各バージョンのトピックには行けますし、リンクを辿る分には問題ありません (VCL を辿ると最新版かも...)。ですが、左上の検索を使った時点で最新版 RAD Studio のリファレンスになってしまうのです...大抵は最新版でも問題はないのですが。

 過去に作ったカスタム検索はこちらとなります。

 イロイロな語句での検索を試してみてください。ブラウザのブックマークに入れておくと便利かもしれません。


12/09/08

AnsiStrings.AnsiPos が不正な値を返す (XE のみ)
 何故なのかよく解りませんが...

uses
  ..., AnsiStrings;


var
  Sub, Src: AnsiString;
begin
  // Case 1:
  Sub := #$82#$A4;                                     // Japanese Hiragana 'u'
  Src := #$82#$A0#$82#$A2#$82#$A4#$82#$A6#$82#$A8;     // Japanese Hiragana 'a' 'i' 'u' 'e' 'o'
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));

  // Case 2:
  Sub := AnsiString('う');                             // Japanese Hiragana 'u'
  Src := AnsiString('あいうえお');                     // Japanese Hiragana 'a' 'i' 'u' 'e' 'o'
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));

  // Case 3:
  Sub := #$95#$5C;                                     // Japanese 'Hyou'
  Src := #$8C#$76#$8F#$57#$95#$5C;                     // Japanese 'SyuuKeiHyou'
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));

  // Case 4:
  Sub := AnsiString('表');                             // Japanese 'Hyou'
  Src := AnsiString('集計表');                         // Japanese 'SyuuKeiHyou'
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));
end;

 このコードは Case 1 と Case 2、Case 3 と Case 4 は同じ結果になるハズです。コントロール文字列で記述しているか、文字定数で記述しているかの違いしかありません。AnsiString な AnsiPos() なので、結果はバイト位置で返ります。すべて 5 が返ってくるハズです。

 ...が、実際には

 このような結果になります。XE のみで発生し、2010 以前や XE2 以降では発生しません。

 XE で AnsiString の処理をされる場合にはご注意を。修正は望み薄だなぁ...MECSUtils に実装しようかなぁ。


12/09/09

続・AnsiStrings.AnsiPos が不正な値を返す (XE のみ)
 RTL をざっと眺めてみたけれど不審な点は見当たらない。イロイロやってて気付いたのは、以下のコードならうまくいくという事だった。

uses
  ..., AnsiStrings;


var
  Sub, Src: AnsiString;
  uSub, uSrc: UnicodeString;
begin
  // Case 2:
  uSub := 'う';                    // Japanese Hiragana 'u'
  uSrc := 'あいうえお';            // Japanese Hiragana 'a' 'i' 'u' 'e' 'o'
  Sub := AnsiString(uSub);
  Src := AnsiString(uSrc);
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));

  // Case 4:
  uSub := '表';                    // Japanese 'Hyou'
  uSrc := '集計表';                // Japanese 'SyuuKeiHyou'
  Sub := AnsiString(uSub);
  Src := AnsiString(uSrc);
  ShowMessage(IntToStr(AnsiStrings.AnsiPos(Sub, Src)));
end;

 いよいよ訳がわかんねぇ...。XE の何かがおかしくなってる (自分の環境だけで起きる現象) としか思えないので、再インストールを試みるとしよう。なお、Windows のロケールがおかしくなっているというオチでない事は確認済デス。

続々・AnsiStrings.AnsiPos が不正な値を返す (XE のみ)
 あ゛。完全アンインストール後に再インストールしてテストしたら再現しないや。変な拡張とかライブラリ入れた覚えはないんだけど?とりあえず、再インストール直後に問題は出ないので QC は取り下げとこう。

 少なくとも Delphi XE のバグではない事は証明されたけれど、コンポーネントやらライブラリやら突っ込んで元の環境に戻してどうなるかだな...って、再現しないし!元と違いが無いと思える位には環境を構築してみたけど、ちゃんとすべて 5 を返す。しまった!変な値を返す奴のバイナリを保存しとけばよかったーーー!アレさえあれば何がおかしかったのかの判断材料になったかもしれないのに!!

 ...えーっと。お騒がせして申し訳ありませんでした m(_ _)m


12/09/10

Pos() と AnsiPos() と PosEx()
 ある文字列から部分文字列を検索するには Pos() を使います。

var
  Index: Integer;
  Src: String;
begin
  Src := 'あいうえお';
  Index := Pos('う', Src);
  ShowMessage(IntToStr(Index));
end;

 このコードを ANSI 版 Delphi で実行すると 5 が表示され、Unicode 版 Delphi で実行すると 3 が表示されます。Unicode 版 Delphi で Ansi 処理を行うために、

var
  Index: Integer;
  Src: AnsiString;
begin
  Src := 'あいうえお';
  Index := Pos('う', Src);
  ShowMessage(IntToStr(Index));
end;

 このように書き換えてみました。ですが、結果はやっぱり 3 が表示されてしまいます。これは 'う' という文字列定数が UnicodeString と解釈され、Unicode 版のオーバーロード関数が呼ばれてしまうからです。

 これを回避するには、

var
  Index: Integer;
  Src: AnsiString;
begin
  Src := 'あいうえお';
  Index := Pos(AnsiString('う'), Src);
  ShowMessage(IntToStr(Index));
end;

var
  Index: Integer;
  Src, Sub: AnsiString;
begin
  Src := 'あいうえお';
  Sub := 'う';
  Index := Pos(Sub, Src);
  ShowMessage(IntToStr(Index));
end;

 文字列定数をキャストするか、AnsiString の変数を渡すようにします。

 Pos() の ANSI 版はマルチバイトを考慮しないため、いわゆる "0x5C 問題" が発生します。

var
  Index: Integer;
  Src, Sub: AnsiString;
begin
  Sub := '\';
  Src := '表1\Sheet1.xls';
  Index := Pos(Sub, Src);
  ShowMessage(IntToStr(Index));
end;

 このコードでは 4 が表示されるのを期待しますが、実行すると ANSI 版 / Unicode 版いずれの Delphi でも 2 が表示されます。'表' の文字は 0x955C なので、2 バイト目の 0x5C にヒットしてしまうのです。

 これを回避するには、マルチバイトを考慮してくれる AnsiPos() を使います。

var
  Index: Integer;
  Src, Sub: AnsiString;
begin
  Sub := '\';
  Src := '表1\Sheet1.xls';
  Index := AnsiPos(Sub, Src);
  ShowMessage(IntToStr(Index));
end;

 ANSI 版 Delphi では期待通りに 4 が表示されます...が、Unicode 版 Delphi では 3 が表示されてしまいます。何故でしょうか?

 実は Unicode 版 Delphi の AnsiString 用 AnsiPos() は SysUtils ではなく AnsiStrings に存在するのです。つまり、SysUtils にある UnicodeString 版の AnsiPos() が呼ばれてしまっているのです。正しくは以下のようなコードを書かなくてはなりません。

uses
  ..., AnsiStrings;

var
  Index: Integer;
  Src, Sub: AnsiString;
begin
  Sub := '\';
  Src := '表1\Sheet1.xls';
  Index := AnsiStrings.AnsiPos(Sub, Src);
  ShowMessage(IntToStr(Index));
end;

 こうする事で確実に AnsiStrings にある AnsiString 版の AnsiPos() が呼ばれるようになります。

 Pos() / AnsiPos() は一致した部分文字列の位置を返してくれますが、開始位置を指定して検索する事ができません。開始位置を指定するには StrUtils.PosEx() を使います (Delphi 7 以降で利用可能)。Unicode 版 Delphi の AnsiString 版は AnsiStrings.PosEx() です。

uses
  ..., AnsiStrings;

var
  Index: Integer;
  Src, Sub: AnsiString;
begin
  Sub := '\';
  Src := 'C:\ABC\DEF\GHI.TXT';
  Index := AnsiStrings.PosEx(Sub, Src, 4);
  ShowMessage(IntToStr(Index));
end;

 4 文字 (バイト) 目から検索しますので、7 が表示されます。

 ...しかしながら AnsiPosEx() という関数はどこにも存在しないのですよ。

"ANSI 版 Delphi" と "Unicode 版 Delphi" と名前空間
 Unicode では "0x5C問題" を始め、多くのマルチバイト関連の問題は起きないので、Unicode 版 Delphi で UnicodeString だけ扱っていれば Pos() を使おうが AnsiPos() を使おうが結果は同じになります。日本語があっても PosEx() も使えますしね。

 ですが、ANSI 版 Delphi や、Unicode 版 Delphi で AnsiString を扱おうとすると、"Ansi~()" と名の付く関数を使う必要があったり、PosEx() が使えなかったりします。別の名前空間に同名関数があったりするのも厄介ですね。

 ANSI 版 Delphi から Unicode 版 Delphi へマイグレーションし、AnsiString で扱っていたものを UnicodeString で扱うようにした場合にはとても楽に移行できる事が解りますね。Unicode 版 Delphi で AnsiString の処理をする場合は...uses し忘れによるバグとかで軽く死ねそうです。ANSI アプリケーションでいいのなら、素直に Delphi 2007 を使う事をオススメします。

MECSUtils ver1.55 リリース
 だが、しかし!たまーに、AnsiString 用の "AnsiPosEx()" が欲しい事があるんじゃい! ...という動機で MECSUtils に AnsiPosEx() を実装しました。「AnsiPosEx() 欲しい」 という QC を入れて 4 年が経つけど、流石にもう見込み薄なので。

 MecsAnsiPosEx() 以外はその実装過程で作られた副産物です。未保証ですが、Delphi 7 でコンパイルできる事を確認しているので、PosEx() の使えない Delphi 6 とかでも MecsAnsiPosEx() が使えると思います。

TurboPower LockBox v2 for Delphi 2009-XE3, Win32 & OSX
 Turbo Power シリーズの暗号化コンポーネント群。先月調査したのは SourceForge のものだったけれど、それとは違う ver 2 の XE3 対応版 (OSX にも対応!) が CodeCentral にアップされていた。

 v3 よりはサポートされる種類が少ないようだ。LockBox v2 のマニュアルは http://sourceforge.net/projects/tplockbox/files/TurboPower LockBox/LockBox version 2/2.07/ から DL できる。


12/09/11

Delphi XE3 とセンサー&ロケーション
 既報の通り Delphi XE3 には FireMonkey のツールパレットに [Sensors] カテゴリがあり、センサー&ロケーションが使えるようになっている。

 FireMonkey から TLocationSensor と TMotionSensor コンポーネントが使えるのだが、恐らくこれは Windows の場合には Windows 7 以降のセンサーロケーション API を使っている。 先のデブキャンで David.I が GeoSense を使えば物理的な GPS がなくともロケーションを取得できる」 と言っていたから間違いないだろう。

 裏を返せば、PC に物理的なセンサーが搭載されていても、Windows 7 対応のドライバが存在しない...例えば特定のアプリケーションからのみ利用される専用ドライバを使っているとか、シリアルポートで通信するようなセンサーは利用できないという事になる。

Geosense
 Windows 7 以降用 GPS 仮想ドライバ。

 GPS を物理的に搭載していなくとも、Wi-Fi 等を使っておおまかな位置情報を取得できる。32bit / 64bit 版があるので、どちらかをインストールし、コントロールパネルの [位置センサーとその他のセンサー] でセンサーを有効にする事で利用可能となる。

 例えば 64bit OS に64bit ドライバをインストールすれば、32bit / 64bit アプリケーションから Geosense を利用できる。

センサーのサンプルプロジェクト (Delphi XE3)
 サンプルプロジェクトはサンプルフォルダの "FireMonkey\LocationDemo" にある (最新のサンプルソースコードの取得方法はこちら)。Geosense を有効にした状態でこのサンプルを動作させてみた。

 センサーマネージャのチェックボックスを ON にし、センサーをアクティブにするチェックボックスを ON にすると、このように位置 (緯度/経度) 情報が正しく取得できた。そこで実際のソースコードを眺めてみることに。センサーマネージャからセンサーをリストして選択して有効にしてるのか...ちょっと面倒臭いなぁ...ん?

 僕はここで "ある事" に気付き、フォームを確認してみた。

キタ━━━━(゚∀゚)━━━━!!!!

 先程つぶやいていたデータを取得する方法は、まさにセンサー&ロケーション API でデータを得る手順そのものであり、このフォームには TLocationSensor が貼り付けられていない。つまりこのサンプルは FMX ではなく RTL を使ったセンサーのサンプルという事になる。じゃ、ソースコード中で使われている TSensorManager のユニットスコープは何なんだ...

System.Sensors キタ━━━━(゚∀゚)━━━━!!!!

 ユニットスコープが "System" で始まるという事は、VCL からも FMX からも利用可能な事を意味する...もうお解かりだろうが XE3 では VCL からもセンサーが使える という事に他ならない。

 FMX のソースを VCL 用に書き直してみる。System.Sensors を uses し、チェックボックスの IsChecked を Checked にする程度でいいハズだ。

キタ━━━━(゚∀゚)━━━━!!!!

...という事で、結論として Delphi XE3 ではセンサー&ロケーションは FireMonkey だけではなく VCL からも使えます。DocWiki が追い付いてないようなのでドキュメントがありませんが、使うのはそんなに難しくありません...って、アレ?TLocationSensor と TMotionSensor はどうやって使うんだ? (^^;A

センサーのサンプルプロジェクト with Google Map (Delphi XE3)
 緯度経度が取れるという事は、Google Map でその位置へジャンプできるという事だ。

var
  URL: string;
begin
  URL := Format('https://maps.google.co.jp/maps?q=%s,%s&z=14&hl=ja&output=embed',
                 [etLatitude.Text, etLongitude.Text]);
  WebBrowser1.Navigate2(URL);
end;

 先程の VCL のサンプルに TWebBrowser とボタンを貼って、Google Map を表示してみたのがコレ。

 あまり意味はないのかもしれないが、なかなか面白い。


12/09/12

既報 (その1): Delphi XE3 のフォームデザイナは埋め込みデザイナのみ
 Delphi XE3 は[ツール | オプション] から ▶環境オプション▶VCL デザイナ を見ても "埋め込みデザイナ" が見当たらない。

 但し、機能が死んでいる訳ではなく UI が存在しないだけのようだ。IDE 起動前に以下のレジストリを追加すれば埋め込みデザイナはオフ...つまり、フローティングデザイナになる。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Embarcadero\BDS\10.0\Form Design]
"Embedded Designer"="False"

 元 (埋め込みデザイナ) に戻すにはこちら。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Embarcadero\BDS\10.0\Form Design]
"Embedded Designer"="True"

 もちろん、これをやったとて FireMonkey でフローティングデザイナが使える訳ではない。フローティングデザイナが使えるのは VCL のみ。

既報 (その2): Professional 版で SQLite が使えない
 ...いや、本来ライセンス的には使えるのです。インストーラのミスだか何のミスだか解りませんが、"動かない" という意味で使えないようです。

 BDE 難民救済手段となり得るのですから、早めに使えるようにして欲しいトコロですね。ちなみに CodeCentral には D6 / D7 用の SQLite DBX ドライバがあります。他のバージョンでは動かない可能性が大ですけどね。

既報 (その3): IDE の起動が遅い
 RAN さんのトコによると違法コピー対策との事。激遅なのは PC 起動後の初回起動時だけな感じはしますが...。

 素人考えかもしれませんが、Windows のスタートアップに "XE3 のすべての DLL をロードしてアンロードするだけのプログラム" を仕込んで事前に実行させるようにすれば、いざ XE3 を使うという時になった際の起動速度が幾分稼げる気がします。

XE3 DLL Preloader
 書いてみた。

program xe3preload;

{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}
{$WEAKLINKRTTI ON}

uses
  System.Masks,
  System.SysUtils,
  System.IOUtils,
  Winapi.Windows;

{$R *.res}

var
  FileName: String;
  DLLWnd: THandle;
begin
  System.NoErrMsg := True;
  for FileName in TDirectory.GetFiles('C:\Program Files\Embarcadero\RAD Studio\10.0',
                                      '*.dll',
                                      TSearchOption.soAllDirectories) do
    begin
      if MatchesMask(ExtractFileName(FileName), 'dbkw64*.dll'then
        Continue;
      if MatchesMask(ExtractFileName(FileName), 'dcc64*.dll'then
        Continue;
      try
        DLLWnd := LoadLibrary(PWideChar(FileName));
        if DLLWnd > 0 then
          FreeLibrary(DLLWnd);
      except
      end;
    end;
end.

 短いのでソース公開。気休め程度には XE3 の起動時間を短縮できるかもしれない。但し、理屈上 "PC 起動後、初回の XE3 起動" しか早くならない。また、プリローダ起動後に数多くのアプリケーションを起動して XE3 を起動すると効果が望めない事がある (OS のキャッシュを期待したプリローダだから)。2 回目以降の起動が遅いのは...どうしようもないかも。

 XE3 のパスは 64bit OS だと "Program Files (x86)" な事に注意!で、細川さんが修正を加えたのがこちら

第23回 エンバカデロ・デベロッパーキャンプ - セッション資料ダウンロード
 公開された模様です。じっくり確認したいと思います。togetter もありますよ。

 最近、デブキャン公式まとめ (デベロッパーキャンプ・アーカイブ)が出来たので、あまり有難味がないですが、"88.とりあえず、デベロッパーキャンプの資料を読んでみようか。(Delphi VCL Tips)" も更新されています。

XE3 DLL Preloader を実測してみた
 とりあえず効果がワカンナイので実測してみる事にした。PC 起動後 3 分間 PC が落ち着くまで待ち、初回の XE3 起動にかかる時間を計測する事にした。

結果: 有意な差はみられない。

 細川さんのプリローダを使ったのだが、dwFlags が LOAD_LIBRARY_AS_DATAFILE であれ 0 であれ差はなかった。プリローダで有意差がないとすると、"本当に DLL チェックに時間が掛かっている" のかもしれない。他にやれそうな事はと言えば [コントロールパネル] から "インデックスのオプション" で XE3 のフォルダをインデックス対象にする位か...これまた気休めだろうけど。

 XE3 に限らず、XE2 や XE も二回目の起動の方が高速な事を考えると、"BDS.exe /preload" みたいなオプション (サイレントで起動してすぐに終了する) があれば、スタートアップに登録することもできて便利なのかもしれない。


12/09/13

XE3 Preloader - 「RAD Studio XE3 の IDE 起動時間をどうにかしたい!」 をトゥギャってみた。
 その後の経緯です。

 「OS の (何らかの) キャッシュ機構に作用する事を狙う」 という考え方はどれも同じですが、やはり bds.exe そのものを実際に走らせると効果絶大なようです。

 その代わり、当然ながら bds.exe をプリロードする場合にはプリロード時に時間が掛かります。@onimaro2010 (aka au2010) さんの事前照合方式だと、プリロード時間は短いですが、初回 IDE 起動速度は 2 回目以降の起動速度よりもやや遅くなります。この辺りはトレードオフなので、ご自分の環境に合わせてお好きな方を使うといいと思います。

ちょっといじった XE3 Preloader
 大した修正ではないのですが、細川さんの XE3 Preloader をちょっといじってサイレント動作可能なようにしてみました。そのままコンパイルすればサイレントなウィンドウアプリケーションになり、Xe3Preloader.dpr の {$DEFINE VERBOSE} のコメントを取ってコンパイルすれば細川さんのと同じ動作 (コンソールアプリケーション) になります。

program Xe3Preloader;

{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}
{$WEAKLINKRTTI ON}

// {$DEFINE VERBOSE}   // <- ここのコメントを外せばコンソールアプリになります

{$IFDEF VERBOSE}
{$APPTYPE CONSOLE}
{$ELSE}
{$R *.res}
{$ENDIF}

uses
  Winapi.Windows,
  Winapi.Messages,
  ...

 ウィンドウアプリケーションの場合、メッセージは OutputDebugString() で吐かれますので、イザという時は外部デバッガからも追えます。アーカイブにはバイナリが 2 種類 (コンソール/ウィンドウアプリ) 収録されています。もちろんソースコード付きです。

ちょっといじった XE3 DLL Preloader (with WinVerify Trust)
 大した修正ではないのですが、@onimaro2010 さんの XE3 DLL Preloader をちょっといじってサイレント動作可能なようにしてみました。そのままコンパイルすればサイレントなウィンドウアプリケーションになり、Xe3Preloader.dpr の {$DEFINE VERBOSE} のコメントを取ってコンパイルすれば @onimaro2010 さんのと同じ動作 (コンソールアプリケーション) になります。

program Xe3dllPreloader;

{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}
{$WEAKLINKRTTI ON}

// {$DEFINE VERBOSE}   // <- ここのコメントを外せばコンソールアプリになります

{$IFDEF VERBOSE}
{$APPTYPE CONSOLE}
{$ELSE}
{$R *.res}
{$ENDIF}

uses
  System.SysUtils,
  System.IOUtils,
  ...

 ウィンドウアプリケーションの場合、メッセージは OutputDebugString() で吐かれますので、イザという時は外部デバッガからも追えます。アーカイブにはバイナリが 2 種類 (コンソール/ウィンドウアプリ) 収録されています。もちろんソースコード付きです。XE3 Preloader と exe 名がカブるので、こちらは "xe3dllpreloader.exe" に改名してあります。

さらに改良された XE3 Preloader (by @pik)
 細川さんが "できるだけオリジナルの環境に影響を与えないように bds.exe をプリロードする XE3 Preloader" を公開されました。

 仕様...というか、理屈的なものは togetter にて twitter でのやりとりを確認して下さい。プリロード専用のレジストリを用いて起動するため、プリロード中に XE3 を起動する事があってもオリジナル環境に悪影響を及ぼす事がありません。

 bds.exe に /rpreload スイッチが追加されていますので、初回のプリローダ処理時間は長いです。これは "preload" というレジストリエントリが初回実行時に作られるためです。次回以降のプリローダ実行時間は従来と変わらなくなります。初回起動は手動でコマンドライン版 (バーボーズ版) を走らせて動作チェックするといいのかもしれませんね。


12/09/14

FireMonkey で仮想キーボードを有効にするには? (XE3)
 FMX.Platform を uses し、VKAutoShowMode 変数の値を設定します。

uses
  ..., FMX.Platform;


procedure TForm1.FormCreate(Sender: TObject);
begin
  VKAutoShowMode := vkasAlways;
end;  

 表示する仮想キーボードの種類は入力コントロールの KeyboardType プロパティで指定します。  ...って、

思いっきり iPhone 準拠じゃねーか!

  ∧_∧
⊂(#・д・)
 /   ノ∪
 し―-J |l| |
         人ペシッ!!
       __
       \  \
          ̄ ̄

 Windows だとシステム標準の "スクリーン キーボード" が出てくるので、仮想キーボードの種類の指定は無意味です。位置もサイズ指定できない (そりゃ、やろうと思えばできるでしょうが) のでイマイチ使い勝手がよくないと思います。

 XE3 では iOS サポートがなくなっているので、この機能は微妙ですね。FireMonkey なのだから、システム非依存の...それこそ VCL の TTouchKeyboard みたいなのが欲しいトコロです。理由については実際にスレート PC で "スクリーン キーボード" を使ってみると解ると思います。それでも "出ないよりはマシ" ですけど。

Platform 変数 (XE3)
 あり?XE3 では Platform 変数 (FMX.Platform.Platform) なくなったのか?

 てか、TPlatform 自体が存在しないような。"XE3 向けに行われた FireMonkey の変更点 (DocWiki)" に廃止されたものも書いておいて欲しいなぁ。FMX.Forms.TScreen (そして Screen 変数) にはディスプレイサイズを特定できるようなプロパティはないので、Platform 変数がなくなるとマズい気がする。XE2 には Platform.GetScreenSize() があったのに...?

 前のトピックに関連して。XE2 には Platform.ShowVirtualKeyboard() と Platform.HideVirtualKeyboard() というメソッドがあったのですが...いや、もちろん XE2 では動作しなかったのですが、一度も陽の目を見る事なく消えてしまいました。

 ...本当に TPlatform はないのか?XE2 の TPlatform (DocWiki) を見てもらえば解るけど、GetTick() も GetClipboard() / SetClipboard() も使えない事になっちゃうけど?

SkRegExp version 1.4 へ
 SkRegExp 新版の投入が予定されているようです。

TPlatformServices と TPlatformService (XE3)
 ぬう...そういう事か。細川さんのツイート"And the prize for ugliest API goes to… (Delphi Haven)" を読んで理解した。

 例えば Clipboard を使うには、

// 現在のクリップボードの内容を Edit1 に表示
var
  ClipboardService: IFMXClipboardService;
begin
  ClipboardService := IFMXClipboardService(TPlatformServices.Current.GetPlatformService(IFMXClipboardService));
  Edit1.Text := ClipboardService.GetClipboard.AsString;
end;

// Edit1 の内容をクリップボードに入れる
var
  ClipboardService: IFMXClipboardService;
begin
  ClipboardService := IFMXClipboardService(TPlatformServices.Current.GetPlatformService(IFMXClipboardService));
  ClipboardService.SetClipboard(Edit1.Text);
end;

 こうなる、と。確かにコード記述の冗長っぽさがちょっとヤだな。

 Delphi Haven の記事には解決方法も書いてあって、高度なレコード型でクラスメソッド作っとけば (ユニットはこちら)、

unit uPlatformService;

interface

uses
  FMX.Platform;

type
  TPlatformService = record
      class function Available<IntfType: IInterface>(out Service: IntfType): Boolean; static;
      class function Get<IntfType: IInterface>: IntfType; static;
    end;

implementation

uses
  System.TypInfo;

class function TPlatformService.Available<IntfType>(out Service: IntfType): Boolean;
var
  Guid: TGUID;
begin
  Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid;
  Result := TPlatformServices.Current.SupportsPlatformService(Guid, IInterface(Service));
end;

class function TPlatformService.Get<IntfType>: IntfType;
var
  Guid: TGUID;
begin
  Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid;
  Result := IntfType(TPlatformServices.Current.GetPlatformService(Guid));
end;

end.

 こんな感じに書けたり、

uses
  ..., uPlatformService;


// 現在のクリップボードの内容を Edit1 に表示
var
  ClipboardService: IFMXClipboardService;
begin
  ClipboardService := TPlatformService.Get<IFMXClipboardService>;
  Edit1.Text := ClipboardService.GetClipboard.AsString;
end;

// Edit1 の内容をクリップボードに入れる
var
  ClipboardService: IFMXClipboardService;
begin
  ClipboardService := TPlatformService.Get<IFMXClipboardService>;
  ClipboardService.SetClipboard(Edit1.Text);
end;

 変数なしで記述してもそんなに長くならない

  // 現在のクリップボードの内容を Edit1 に表示
  Edit1.Text := TPlatformService.Get<IFMXClipboardService>.GetClipboard.AsString;

  // Edit1 の内容をクリップボードに入れる
  TPlatformService.Get<IFMXClipboardService>.SetClipboard(Edit1.Text);

 ...という事らしいです。FMX.Platform にあるのは TPlatformServices で、高度なレコード型の方は TPlatformService です。最初、記事読んでて混乱しちゃいました (^^;A

グローバル変数 (XE3)
 従来、非推奨だったグローバル変数が XE3 では "削除されていて" 使えなくなっています。TFormatSettings の使い方に関しては、ナイスタイミング (?) で山本隆さんが記事を書いてらっしゃいます。

 GJ! (^-^)b


12/09/15

OutputDebugString() をトレースする
 何でもいいので Windows 用のデバッガを用意すれば、大抵のデバッガは OutputDebugString() で吐かれた文字列を表示する事ができます。

 OutputDebugString() をトレースするだけなら、インストール不要で使える DebugView がお手軽です。

 画像は 細川さんの XE3 Preloader を実行して確認してみたトコロです (ウィンドウアプリケーション版を実行しています)。

 DebugView は "管理者として実行" で実行し、

 キャプチャ対象のチェックを確認してから監視対象のアプリケーションを起動します。  お客さんトコにノート PC 持ち込めないとか、リモートデバッガすら利用できない時には役に立つかも知れません。転ばぬ先の OutputDebugString()

 Delphi IDE 上での OutputDebugString() を使ったデバッグ手法に関しては 第21回 エンバカデロ・デベロッパーキャンプ【T2】「実践!Delphiデバッグテクニック」 を参照して下さい。

 ※ フルスクリーンで再生する事をオススメします。


12/09/16

TMS Smooth Control Pack (無償版) について
 2009 / 2010 / XE では、TMS Software の TMS Smooth Control Pack が貰えました (XE2 用の "無償版 TMS Smooth Control Pack" は提供されませんでした)。

 2009 の場合は期間限定ではありましたが、いずれもキャンペーン期間内にエンバカデロ製品を購入していなくとも、ダウンロードは可能でした。

 これら TMS Smooth Control Pack は現在 CodeCentral から DL できません。

 つまり、過去のプロジェクトに TMS Smooth Controls Pack を利用していた場合、プロジェクトを XE2 や XE3 へアップデートするには製品版を購入するしかなく、開発環境をフッ飛ばして再インストールするハメになった時でもアーカイブを所持していなければこれまた製品版を購入するしかありません。

 但し、過去に CodeCentral から 無償版 TMS Smooth Control Pack をDL したのであれば、条件付ですが TMS Software のサイトからアップデータ (インストーラ) を DL する事ができます。

 TMS の製品ライフサイクルは以下のようになっています (いずれか)。  私の場合、TMS Software のサイトからダウンロード可能だったのは XE 用だけでした。

TMS Smooth Control Pack (無償版) の DL について
 TMS Software からのアップデータ入手方法は以下の通りです。

  1. TMS Software へ行く。
  2. 左側の LOGIN からログインする

    過去に CodeCentral から TMS Smooth Control Pack を DL していれば、登録コードが書かれたメールが送られてきている。もし、登録コードが判らないのなら、左下の Forgot code? を押し、

    メールアドレスを入力して [Verify] ボタンを押せば、登録コードがメールで送られてくる。
  3. DL 可能であれば、以下の画像のようになる。

    Registered version のリンクからアップデータ (インストーラ) を DL する。
  4. DL 不能であれば、製品名が表示されないか、以下の画像のようになる。

    この例では登録日より 2 年を超過しているので expired になっている (アップデータ入手期間満了)。
 必要な方はお早めに入手する事をオススメします。


12/09/17

Delphi FireMonkey Tips
 地道に更新しております。

 FM2 用の記述が追加されていたりします。

FireMonkey のフォーム (HD / 3D) でクリック / ダブルクリックイベントを検出してみる
 "Fire Monkey 3D で作成した Shape を回転させるには? (Delphi Q&A)" のネタです。FireMonkey のフォームには OnClick / OnDblClick イベントがありません (XE2 の初版には OnKeyDown イベントすらありませんでした) が、これをどうにかしてみようという趣旨です。

 FireMonkey HD / 3D 用の Click / DblClick を判定するユニットを用意してみました (FMX.BaseForm.pas)。

unit FMX.BaseForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  {$IFDEF MSWINDOWS}
  Winapi.Windows,
  {$ENDIF}
  {$IFDEF MACOS}
  MacAPI.Foundation, MacAPI.AppKit,
  {$ENDIF}
  FMX.Types, FMX.Forms;

type
  { TBaseFormHD }
  TBaseFormHD = class(TForm)
    procedure Timer_Timer(Sender: TObject);
  private
    { private 宣言 }
    FTimer: TTimer;
    FStartPoint: TPointF;
    FEndPoint: TPointF;
    FIntervalOver: Boolean;
    FFinished: Boolean;
    FMouseUpCount: SmallInt;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

  { TBaseForm3D }
  TBaseForm3D = class(TForm3D)
    procedure Timer_Timer(Sender: TObject);
  private
    { private 宣言 }
    FTimer: TTimer;
    FStartPoint: TPointF;
    FEndPoint: TPointF;
    FIntervalOver: Boolean;
    FFinished: Boolean;
    FMouseUpCount: SmallInt;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

const
  CLICK_JUDGE_DIST  =   4// クリック判定用
  DOUBLE_CLICK_TIME = 500// ダブルクリック判定時間 (規定)

implementation

function CalcDist(a, b: TPointF): Single;
begin
  result := Sqrt(Sqr(a.X - b.X) + Sqr(a.Y - b.Y));
end;

{ TBaseFormHD }

constructor TBaseFormHD.Create(AOwner: TComponent);
begin
  inherited;
  FMouseUpCount   := 0;
  FIntervalOver   := False;
  FFinished       := False;

  FTimer := TTimer.Create(Self);
  FTimer.Enabled  := False;

  FTimer.Interval := DOUBLE_CLICK_TIME;
  {$IFDEF MSWINDOWS}
  FTimer.Interval := GetDoubleClickTime;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}

  FTimer.OnTimer  := Timer_Timer;
end;

procedure TBaseFormHD.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) and ((FMouseUpCount = 0or FFinished) then
      begin
        FStartPoint   := PointF(X, Y);
        FIntervalOver := False;
        FFinished     := False;
        if Assigned(FMouseDblClickEvent) then
          FTimer.Enabled := True;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        FEndPoint := PointF(X, Y);
        Inc(FMouseUpCount);
        if not Assigned(FMouseDblClickEvent) then
          FIntervalOver := True;
        if FIntervalOver then
          Timer_Timer(FTimer);
      end;
  finally
    inherited;
  end;
end;

function TBaseFormHD.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    begin
      FTimer.Enabled := False;
      FMouseUpCount := 0;
    end;
end;

procedure TBaseFormHD.Timer_Timer(Sender: TObject);
begin
  FIntervalOver := True;
  if FMouseUpCount = 0 then
    Exit;
  FTimer.Enabled := False;
  if CalcDist(FStartPoint, FEndPoint) < CLICK_JUDGE_DIST then
    begin
      if (FMouseUpCount > 1then
        begin
          if Assigned(FMouseDblClickEvent) then
            OnDblClick(Self);
        end
      else
        begin
          if Assigned(FMouseClickEvent) then
            OnClick(Self);
        end;
    end;
  FFinished := True;
  FMouseUpCount := 0;
end;

{ TBaseForm3D }

constructor TBaseForm3D.Create(AOwner: TComponent);
begin
  inherited;
  FMouseUpCount   := 0;
  FIntervalOver   := False;
  FFinished       := False;

  FTimer := TTimer.Create(Self);
  FTimer.Enabled  := False;

  FTimer.Interval := DOUBLE_CLICK_TIME;
  {$IFDEF MSWINDOWS}
  FTimer.Interval := GetDoubleClickTime;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}

  FTimer.OnTimer  := Timer_Timer;
end;

procedure TBaseForm3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) and ((FMouseUpCount = 0or FFinished) then
      begin
        FStartPoint   := PointF(X, Y);
        FIntervalOver := False;
        FFinished     := False;
        if Assigned(FMouseDblClickEvent) then
          FTimer.Enabled := True;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        FEndPoint := PointF(X, Y);
        Inc(FMouseUpCount);
        if not Assigned(FMouseDblClickEvent) then
          FIntervalOver := True;
        if FIntervalOver then
          Timer_Timer(FTimer);
      end;
  finally
    inherited;
  end;
end;

function TBaseForm3D.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    begin
      FTimer.Enabled := False;
      FMouseUpCount := 0;
    end;
end;

procedure TBaseForm3D.Timer_Timer(Sender: TObject);
begin
  FIntervalOver := True;
  if FMouseUpCount = 0 then
    Exit;
  FTimer.Enabled := False;
  if CalcDist(FStartPoint, FEndPoint) < CLICK_JUDGE_DIST then
    begin
      if (FMouseUpCount > 1then
        begin
          if Assigned(FMouseDblClickEvent) then
            OnDblClick(Self);
        end
      else
        begin
          if Assigned(FMouseClickEvent) then
            OnClick(Self);
        end;
    end;
  FFinished := True;
  FMouseUpCount := 0;
end;
end.

 FireMonkey HD / 3D 用の Click / DblClick を判定するユニットを用意してみました。

 仕様は下記の通りです。

 使い方はこんな感じです。

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Platform, FMX.BaseForm;

type
  TForm1 = class(TBaseFormHD)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Form1Click(Sender: TObject);
    procedure Form1DblClick(Sender: TObject);
  private
    { private 宣言 }
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  OnClick    := Form1Click;    // クリックイベントをアサイン
  OnDblClick := Form1DblClick; // ダブルクリックイベントをアサイン
end;

procedure TForm1.Form1Click(Sender: TObject);
begin
  ShowMessage('Clicked!');
end;

procedure TForm1.Form1DblClick(Sender: TObject);
begin
  ShowMessage('Double Clicked!');
end;

end.

 コードは FireMonkey HD 用なので TBaseFormHD を継承していますが、FireMonkey 3D を使うときは TBaseForm3D を継承して下さい。OS ネイティブのイベントを用いない "擬似クリック / ダブルクリック" イベントですが、それっぽい動きはすると思います。

  1. BaseFormHD / BaseForm3D とかの名前でフォームを作る
  2. FMX.BaseForm の内容をコピペ
  3. フォームをプロジェクトに追加
  4. 継承して使う
 ...って使い方が楽かもしれません。バグがあったらゴメンナサイ。


12/09/18

ssDouble じゃいかんのか? (from 2ch)
 ...できれば twitter とかでツッコミ入れておくれよー。

 さて。つまりは 「このようなコードでいいんちゃうんか?」 という事だと思います (或いは Delphi-ML Tips のコレ)。

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if ssDouble in Shift then
    ShowMessage('Double Clicked!')
  else
    ShowMessage('Clicked!');
end;

 しかしながらこれはうまく動作しません。MouseUp イベントでは Shift に ssDouble が入ってくることはないからです。ssDouble が検出できるのは MouseDown イベントの時です。

 今度は MouseDown イベントに書いてみましょう。

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if ssDouble in Shift then
    ShowMessage('Double Clicked!')
  else
    ShowMessage('Clicked!');
end;

 これまたうまく動作しません。初回 MouseDown 時にもこのイベントが発生してしまいますので、ダブルクリックを判定できません。加えて言えば、クリック判定はマウスボタンを離した時に行われますから、その意味でもマズい事になります。例えば VCL アプリケーションで TButton をマウス左ボタンで押下したまま TButton からマウスカーソルを外してマウスボタンを離す事でクリックをキャンセルできますが、MouseDown イベントでのみ検出しようとするとこのキャンセル動作を実現できません。

 では、ダブルクリックの判定を MouseDown でやって、クリックを MouseUp で処理してみます。

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if ssDouble in Shift then
    ShowMessage('Double Clicked!');
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  ShowMessage('Clicked!');
end;

 これもダメです。最初の MouseUp でクリック判定されてしまうのでダブルクリックを検出できません。

 ...つまり、クリックとダブルクリックの両方を判定する場合には "ダブルクリック時間を経過するまで待つ必要がある" という事になるのです。片方だけの場合なら検出は簡単ですし、「ダブルクリックの判定には ssDouble 使え」 というのは正しいと思います。

 しかしながら、今回のような場合には ssDouble を判定に使おうが使うまいがコードの複雑さに変わりはないように思えるのですが...いかがでしょうか?私の思慮が足りないだけで、"実は ssDouble を使ってもっと簡単に記述できる方法がある" のでしたら教えて下さい m(_ _)m

ちなみに...
 元ネタにはこう書いてあって、

# VCL の挙動とは異なりますが、
# この後にダブルクリック判定を組み込むのが楽になります。

 "FireMonkey のフォーム (HD / 3D) でクリック / ダブルクリックイベントを検出してみる" はその伏線 (?) を回収するための記事でした。

改定
 ダブルクリックイベントハンドラが割り当てられていない場合にはダブルクリック時間を待たないように "FireMonkey のフォーム (HD / 3D) でクリック / ダブルクリックイベントを検出してみる" のコードを修正してみました。

改定 (その2)
 おっと。フォームの MouseUp / MouseDown イベントはフォーム上に貼られたコントロールをクリックしても発生するのか。と言う訳で、マウスボタン押下位置にコントロールが存在する場合にはクリック/ダブルクリックイベントを発生させないようにコードを修正してみました。

 なお、FireMonkey のフォームに OnClick イベントが存在しない件は QC に入っています。

 Open になっているので将来のバージョンで実装されるのかもしれません。


12/09/19

FireMonkey のフォーム (HD / 3D) でクリック / ダブルクリックイベントを検出してみる (with GetTick)
 タイマーを使わない "GetTick 版" のコードも貼っておきます。

unit FMX.BaseForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  {$IFDEF MSWINDOWS}
  Winapi.Windows,
  {$ENDIF}
  {$IFDEF MACOS}
  MacAPI.Foundation, MacAPI.AppKit,
  {$ENDIF}
  FMX.Types, FMX.Forms, FMX.Platform;

type
  TClickType = (ctNone, ctClick, ctDblClick);

  { TBaseFormHD }
  TBaseFormHD = class(TForm)
  private
    { private 宣言 }
    FDoubleClickTime: Extended;
    FStartPoint: TPointF;
    FStartTime: Extended;
    FMouseUpCount: SmallInt;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

  { TBaseForm3D }
  TBaseForm3D = class(TForm3D)
  private
    { private 宣言 }
    FDoubleClickTime: Extended;
    FStartPoint: TPointF;
    FStartTime: Extended;
    FMouseUpCount: SmallInt;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

const
  CLICK_JUDGE_DIST  =   4// クリック判定用
  DOUBLE_CLICK_TIME = 500// ダブルクリック判定時間 (規定)

implementation

function Get_Tick: Extended;
{$IF FireMonkeyVersion >= 17.0}
begin
  result := IFMXTimerService(TPlatformServices.Current.GetPlatformService(IFMXTimerService)).GetTick;
end;
{$ELSE}
begin
  result := Platform.GetTick;
end;
{$IFEND}

function CalcDist(a, b: TPointF): Single;
begin
  result := Sqrt(Sqr(a.X - b.X) + Sqr(a.Y - b.Y));
end;

{ TBaseFormHD }

constructor TBaseFormHD.Create(AOwner: TComponent);
begin
  inherited;
  FMouseUpCount    := 0;

  FDoubleClickTime := DOUBLE_CLICK_TIME  / 1000;
  {$IFDEF MSWINDOWS}
  FDoubleClickTime := GetDoubleClickTime / 1000;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}
end;

procedure TBaseFormHD.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if (FMouseUpCount = 0then
          begin
            FStartPoint := PointF(X, Y);
            FStartTime  := Get_Tick;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
var
  ClickType: TClickType;
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        Inc(FMouseUpCount);

        ClickType := ctNone;
        if Assigned(FMouseDblClickEvent) then
          begin
            if (FMouseUpCount > 1then
              ClickType := ctDblClick
            else
              begin
                while ((Get_Tick - FStartTime) < FDoubleClickTime) do
                  begin
                    if (FMouseUpCount > 1then
                      Exit;
                    Application.ProcessMessages;
                  end;
                if (FMouseUpCount = 1then
                  ClickType := ctClick;
              end;
          end
        else
          ClickType := ctClick;

        if (ClickType <> ctNone) then
          begin
            if (CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST) then
              case ClickType of
                ctClick:
                  if Assigned(FMouseClickEvent) then
                    OnClick(Self);
                ctDblClick:
                  if Assigned(FMouseDblClickEvent) then
                    OnDblClick(Self);
              end;
            FMouseUpCount := 0;
          end;
      end;
  finally
    inherited;
  end;
end;

function TBaseFormHD.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    FMouseUpCount := 0;
end;

{ TBaseForm3D }

constructor TBaseForm3D.Create(AOwner: TComponent);
begin
  inherited;
  FMouseUpCount    := 0;

  FDoubleClickTime := DOUBLE_CLICK_TIME  / 1000;
  {$IFDEF MSWINDOWS}
  FDoubleClickTime := GetDoubleClickTime / 1000;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}
end;

procedure TBaseForm3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if (FMouseUpCount = 0then
          begin
            FStartPoint := PointF(X, Y);
            FStartTime  := Get_Tick;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
var
  ClickType: TClickType;
begin
  try
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        Inc(FMouseUpCount);

        ClickType := ctNone;
        if Assigned(FMouseDblClickEvent) then
          begin
            if (FMouseUpCount > 1then
              ClickType := ctDblClick
            else
              begin
                while ((Get_Tick - FStartTime) < FDoubleClickTime) do
                  begin
                    if (FMouseUpCount > 1then
                      Exit;
                    Application.ProcessMessages;
                  end;
                if (FMouseUpCount = 1then
                  ClickType := ctClick;
              end;
          end
        else
          ClickType := ctClick;

        if (ClickType <> ctNone) then
          begin
            if (CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST) then
              case ClickType of
                ctClick:
                  if Assigned(FMouseClickEvent) then
                    OnClick(Self);
                ctDblClick:
                  if Assigned(FMouseDblClickEvent) then
                    OnDblClick(Self);
              end;
            FMouseUpCount := 0;
          end;
      end;
  finally
    inherited;
  end;
end;

function TBaseForm3D.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    FMouseUpCount := 0;
end;
end.

 FireMonkey Tips"経過時間を得るには?" にもあるように、GetTick() の使い方が XE2 / XE3 で異なる事に注意が必要です。コード量的には TTimer 版と大差ないですね。

 TTimer 版も GetTick 版も仕様的には同じです。


12/09/20

さらに FireMonkey のフォーム (HD / 3D) でクリック / ダブルクリックイベントを検出してみる
 先日までのコードではダブルクリックキャンセルが行えた。

 Windows の標準動作的にはダブルクリックのキャンセルは存在しないし、ダブルクリックキャンセルを使う事はまずないだろう。それよりは少しでもダブルクリックのレスポンスを向上させるべきだと思い、ダブルクリック判定を MouseDown で行い、最適なタイミングで OnDblClick() イベントを起こすようにしてみた。折角なので ssDouble も使ってみた。

 仕様変更。

 まずは TTimer 版

// -----------------------------------------------------------------------------
// OnClick / OnDblClick を備えたベースフォーム
// TTimer 版
// -----------------------------------------------------------------------------
unit FMX.BaseForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  {$IFDEF MSWINDOWS}
  Winapi.Windows,
  {$ENDIF}
  {$IFDEF MACOS}
  MacAPI.Foundation, MacAPI.AppKit,
  {$ENDIF}
  FMX.Types, FMX.Forms;

type
  { TBaseFormHD }
  TBaseFormHD = class(TForm)
    procedure Timer_Timer(Sender: TObject);
  private
    { private 宣言 }
    FTimer: TTimer;
    FStartPoint: TPointF;
    FIntervalOver: Boolean;
    FIsMouseUp: Boolean;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
    procedure GenerateClickEvent;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

  { TBaseForm3D }
  TBaseForm3D = class(TForm3D)
    procedure Timer_Timer(Sender: TObject);
  private
    { private 宣言 }
    FTimer: TTimer;
    FStartPoint: TPointF;
    FIntervalOver: Boolean;
    FIsMouseUp: Boolean;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    function IsMouseOnControl(X, Y: Single): Boolean;
    procedure GenerateClickEvent;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

const
  CLICK_JUDGE_DIST  =   4// クリック判定用
  DOUBLE_CLICK_TIME = 500// ダブルクリック判定時間 (規定)

implementation

function CalcDist(a, b: TPointF): Single;
begin
  result := Sqrt(Sqr(a.X - b.X) + Sqr(a.Y - b.Y));
end;

{ TBaseFormHD }

constructor TBaseFormHD.Create(AOwner: TComponent);
begin
  inherited;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled  := False;

  FTimer.Interval := DOUBLE_CLICK_TIME;
  {$IFDEF MSWINDOWS}
  FTimer.Interval := GetDoubleClickTime;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}

  FTimer.OnTimer  := Timer_Timer;
end;

procedure TBaseFormHD.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft)  then
      begin
        if (ssDouble in Shift) then
          begin
            FTimer.Enabled  := False;
            if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
              if Assigned(FMouseDblClickEvent) then
                OnDblClick(Self);
          end
        else
          begin
            FStartPoint := PointF(X, Y);
            if Assigned(FMouseDblClickEvent) then
              begin
                FIntervalOver  := False;
                FIsMouseUp     := False;
                FTimer.Enabled := True;
              end;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
          begin
            if (not Assigned(FMouseDblClickEvent)) or FIntervalOver then
              GenerateClickEvent
            else
              FIsMouseUp := True;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.Timer_Timer(Sender: TObject);
begin
  FIntervalOver := True;
  FTimer.Enabled  := False;
  if FIsMouseUp then
    GenerateClickEvent;
end;

function TBaseFormHD.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    FTimer.Enabled  := False;
end;

procedure TBaseFormHD.GenerateClickEvent;
begin
  FIntervalOver := False;
  FTimer.Enabled  := False;
  if Assigned(FMouseClickEvent) then
    OnClick(Self);
end;

{ TBaseForm3D }

constructor TBaseForm3D.Create(AOwner: TComponent);
begin
  inherited;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled  := False;

  FTimer.Interval := DOUBLE_CLICK_TIME;
  {$IFDEF MSWINDOWS}
  FTimer.Interval := GetDoubleClickTime;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}

  FTimer.OnTimer  := Timer_Timer;
end;

procedure TBaseForm3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft)  then
      begin
        if (ssDouble in Shift) then
          begin
            FTimer.Enabled  := False;
            if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
              if Assigned(FMouseDblClickEvent) then
                OnDblClick(Self);
          end
        else
          begin
            FStartPoint := PointF(X, Y);
            if Assigned(FMouseDblClickEvent) then
              begin
                FIntervalOver  := False;
                FIsMouseUp     := False;
                FTimer.Enabled := True;
              end;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
          begin
            if (not Assigned(FMouseDblClickEvent)) or FIntervalOver then
              GenerateClickEvent
            else
              FIsMouseUp := True;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.Timer_Timer(Sender: TObject);
begin
  FIntervalOver := True;
  FTimer.Enabled  := False;
  if FIsMouseUp then
    GenerateClickEvent;
end;

function TBaseForm3D.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
  if result then
    FTimer.Enabled  := False;
end;

procedure TBaseForm3D.GenerateClickEvent;
begin
  FIntervalOver := False;
  FTimer.Enabled  := False;
  if Assigned(FMouseClickEvent) then
    OnClick(Self);
end;
end.

 次に GetTick 版。

// -----------------------------------------------------------------------------
// OnClick / OnDblClick を備えたベースフォーム
// GetTick 版
// -----------------------------------------------------------------------------
unit FMX.BaseForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  {$IFDEF MSWINDOWS}
  Winapi.Windows,
  {$ENDIF}
  {$IFDEF MACOS}
  MacAPI.Foundation, MacAPI.AppKit,
  {$ENDIF}
  FMX.Types, FMX.Forms, FMX.Platform;

type
  { TBaseFormHD }
  TBaseFormHD = class(TForm)
  private
    { private 宣言 }
    FDoubleClickTime: Extended;
    FStartPoint: TPointF;
    FStartTime: Extended;
    FDoubleClicked: Boolean;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    FIsMouseUp: Boolean;
    FIntervalOver: Boolean;
    function IsMouseOnControl(X, Y: Single): Boolean;
    procedure GenerateClickEvent;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

  { TBaseForm3D }
  TBaseForm3D = class(TForm3D)
  private
    { private 宣言 }
    FDoubleClickTime: Extended;
    FStartPoint: TPointF;
    FStartTime: Extended;
    FDoubleClicked: Boolean;
    FMouseClickEvent: TNotifyEvent;
    FMouseDblClickEvent: TNotifyEvent;
    FIsMouseUp: Boolean;
    FIntervalOver: Boolean;
    function IsMouseOnControl(X, Y: Single): Boolean;
    procedure GenerateClickEvent;
  public
    { public 宣言 }
    constructor Create(AOwner: TComponent); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
  published
    { published 宣言 }
    property OnClick: TNotifyEvent read FMouseClickEvent write FMouseClickEvent;
    property OnDblClick: TNotifyEvent read FMouseDblClickEvent write FMouseDblClickEvent;
  end;

const
  CLICK_JUDGE_DIST  =   4// クリック判定用
  DOUBLE_CLICK_TIME = 500// ダブルクリック判定時間 (規定)

implementation

function Get_Tick: Extended;
{$IF FireMonkeyVersion >= 17.0}
begin
  result := IFMXTimerService(TPlatformServices.Current.GetPlatformService(IFMXTimerService)).GetTick;
end;
{$ELSE}
begin
  result := Platform.GetTick;
end;
{$IFEND}

function CalcDist(a, b: TPointF): Single;
begin
  result := Sqrt(Sqr(a.X - b.X) + Sqr(a.Y - b.Y));
end;

{ TBaseFormHD }

constructor TBaseFormHD.Create(AOwner: TComponent);
begin
  inherited;
  FDoubleClickTime := DOUBLE_CLICK_TIME  / 1000;
  {$IFDEF MSWINDOWS}
  FDoubleClickTime := GetDoubleClickTime / 1000;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}
end;

procedure TBaseFormHD.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if (ssDouble in Shift) then
          begin
            FDoubleClicked := True;
            if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
              if Assigned(FMouseDblClickEvent) then
                OnDblClick(Self);
          end
        else
          begin
            FStartPoint    := PointF(X, Y);
            FStartTime     := Get_Tick;
            FDoubleClicked := False;
            FIsMouseUp     := False;
            FIntervalOver  := False;
            while ((Get_Tick - FStartTime) < FDoubleClickTime) do
              begin
                Application.ProcessMessages;
                if FDoubleClicked then
                  Exit;
              end;
            FIntervalOver := not FDoubleClicked;
            if FIsMouseUp then
              GenerateClickEvent;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
          begin
            if (not Assigned(FMouseDblClickEvent)) or FIntervalOver then
              GenerateClickEvent
            else
              FIsMouseUp := True;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseFormHD.GenerateClickEvent;
begin
  FDoubleClicked := False;
  if Assigned(FMouseClickEvent) then
    OnClick(Self);
end;

function TBaseFormHD.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
end;

{ TBaseForm3D }

constructor TBaseForm3D.Create(AOwner: TComponent);
begin
  inherited;
  FDoubleClickTime := DOUBLE_CLICK_TIME  / 1000;
  {$IFDEF MSWINDOWS}
  FDoubleClickTime := GetDoubleClickTime / 1000;
  {$ENDIF}
  {$IFDEF MACOS}
  // OSX 用の処理
  {$ENDIF}
end;

procedure TBaseForm3D.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if (ssDouble in Shift) then
          begin
            FDoubleClicked := True;
            if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
              if Assigned(FMouseDblClickEvent) then
                OnDblClick(Self);
          end
        else
          begin
            FStartPoint    := PointF(X, Y);
            FStartTime     := Get_Tick;
            FDoubleClicked := False;
            FIsMouseUp     := False;
            FIntervalOver  := False;
            while ((Get_Tick - FStartTime) < FDoubleClickTime) do
              begin
                Application.ProcessMessages;
                if FDoubleClicked then
                  Exit;
              end;
            FIntervalOver := not FDoubleClicked;
            if FIsMouseUp then
              GenerateClickEvent;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  try
    if not (Assigned(FMouseClickEvent) or Assigned(FMouseDblClickEvent)) then
      Exit;
    if IsMouseOnControl(X, Y) then
      Exit;
    if (Button = TMouseButton.mbLeft) then
      begin
        if CalcDist(FStartPoint, PointF(X, Y)) < CLICK_JUDGE_DIST then
          begin
            if (not Assigned(FMouseDblClickEvent)) or FIntervalOver then
              GenerateClickEvent
            else
              FIsMouseUp := True;
          end;
      end;
  finally
    inherited;
  end;
end;

procedure TBaseForm3D.GenerateClickEvent;
begin
  FDoubleClicked := False;
  if Assigned(FMouseClickEvent) then
    OnClick(Self);
end;

function TBaseForm3D.IsMouseOnControl(X, Y: Single): Boolean;
begin
  result := (IControl(ObjectAtPoint(ClientToScreen(PointF(X, Y)))) <> nil);
end;
end.

 以前のコードでは二回目の MouseUp までをダブルクリック時間として処理していたので、

 という問題があった。現状では考えられるすべてのパターンで最短 (最速という意味ではない) でクリック / ダブルクリック判定が行われる...このテのアルゴリズムを考えるのは面白いな。ちなみに Microsoft のクリック / ダブルクリック判別サンプルは "方法 : クリックとダブルクリックを識別する" にある。

ダブルクリックのキャンセルは存在しない
 シングルクリックは MouseDown▽ -> MouseUp▲ と、MouseUp のタイミングで行われる。MouseDown のままマウスカーソルを移動させて MouseUp を行うとシングルクリックキャンセルを行う事ができる。

 ダブルクリックは "二回 (ダブル) のクリック" なので MouseDown▽ -> MouseUp△ -> MouseDown▽ -> MouseUp▲ と、二回目の MouseUp のタイミングでダブルクリックになる...訳じゃなく、ダブルクリック判定は MouseDown▽ -> MouseUp△ -> MouseDown▼ -> MouseUp△ この二回目の MouseDown のタイミングで行われる。よって、二回目の MouseDown のままマウスカーソルを移動させて MouseUp してもダブルクリックキャンセルは行えない。

 例えば、デスクトップにある任意のアイコンを 1.5 回クリック (Down, Up, Down) すると、二回目の MouseDown でダブルクリック判定され、押しっぱなしのマウスボタンをどこで離そうがダブルクリック動作に影響しない事が解る。


12/09/21

Delphi / C++Builder XE3 Starter Edition について
 XE3 Starter Edition には幾つかの注意点があります。

 これらの制限を購入前によく検討して下さい。  Starter Edition に関する詳しい情報については上記資料で確認できます。

XE3 Starter Edition ショップリンク
 恒例のショップリンクです。

 ※価格にポイント等は反映されていません。また、価格は変動する事がありますのでリンク先を必ず確かめるようにして下さい。


12/09/22

TSingleHelper / TDoubleHelper / TExtendedHelper そして TStringHelper (XE3)
 TSingleHelper / TDoubleHelper / TExtendedHelperXE2 で実装された "浮動小数点用レコード" の代替となるヘルパーです。わざわざ高度なレコード型を使わなくとも、同等の事ができます。"同等" というのがミソなのですが、例えば、TSingleRec のコードを単純にヘルパーに置き換えられるかというとそうではありません。

var
  F: Single;
  I: Integer;
begin
  F := -0.5;

  // display the hexadecimal representation of the single precision floating-point value
  // leftmost bytes are the most significant
  for I := High(F.Bytes) downto Low(F.Bytes) do
    Write(IntToHex(F.Bytes[I], 2) + ' ');
  Writeln;

  // display the value
  Writeln(FloatToStr(F));

   // ...
end;

 これは XE2 の TSingleRec のドキュメントにあったサンプルソースコードをヘルパーで置き換えただけのものですが、コンパイルすると Bytes の High / Low の取得でエラーになってしまいます...大した事ではありませんが。

 TStringHelperTStringBuilder を使わなくとも、

var
  s: string;
begin
  s := 'ABCDEFG'.Substring(23);
  s := s.ToLower;
  Writeln(s);
end;

 このような記述が可能になるという事です。

 しかしながら、このヘルパーが使えるのは String (UnicodeString) だけであり、AnsiString や WideString 用のヘルパーは用意されていません。つまり、AnsiString や WideString は旧来の文字列操作関数群を使う必要があります。

 加えて言えば、Copy 関数と同等のものは TStringHelper では SubString() ですが、TStringHelper.Copy() という同名ながら違う機能を持つメソッドもあります。関数とヘルパーのメソッドを混在させると訳がわからなくなるため、使い所が難しいかもしれません。逆に、"文字列の種類を混在させなければ使い勝手のいい機能" だとも思います...コード補完も利用できる訳ですから。

 すべてのプリミティブ型にヘルパーが用意されると使い勝手も変わってくるのだとは思いますが...。

   See Also:

SkRegExp 1.4.1 リリース
 小宮さん作の正規表現ライブラリ SkRegExp の version 1.4.1 がリリースされました。


12/09/23

Delphi FireMonkey Tips
 XE3 に合わせて記事をあちこち変更してあります。どうでもいいけど、XE3 で 3D Shapes をフラットシェーディングにしたりワイヤーフレームにしたりするにはどうすればいいのだろう?

Apple Keyboard の分解清掃
 メンドイ。ガワが透明アクリルなだけに、ゴミが入ると汚れが目立って仕方がない。

 画像で上にあるのが Power Mac G4 の "Apple Pro Keyboard (M7803J/A)"、下は iMac 5.1 (17″Late 2006) の "Apple Pro Keyboard (M7803J/A)"。分解には、

 が必要となる。六角レンチは 0.05 インチ (1.27mm) でもイケない事はないが、ネジが硬い場合にはネジ穴をなめてしまうかもしれない。ダイソーで探せばあるハズだ。

 キーボード裏にある 5 本のネジ を外せばガワは外れる。見当たらないであろう 1 本は隠しネジとなっており、

 こんなトコロにあるので表面のシールをカッター等で切り取る必要がある。

 画像で上にあるのが "Apple Keyboard (M9034J/A)"、下にあるのが "Apple Wireless Keyboard (M9270J/A)"。M9270J/A の分解には、

 も必要となる...どんだけメンドイんだよ。まぁ、Y字ドライバーは一本持っておけば任天堂製品の分解にも使えるしね。

 分解清掃にあった方がいいものは、

 キートップは手で外れるのでキートップ外し用工具は要らないと思う。

 キーボードを洗剤で洗うと必要な油分まで取れてしまいキーがスムーズに押下できなくなったりするので、シリコンスプレーがあるといい。綿棒に付けてキートップが収まる穴をクルっと撫でてやると動作がスムーズになる。

 ガワがなまじアクリルなので、水洗いすると水アカが目立ってしまう。布巾で拭きとっても拭き残しがあったり、布巾の糸状のホコリが付着するので最後はメガネ拭きでキレイにするといい。外側は後でも拭けるので内側を重点的に。指紋にも気を付けて。アクリルは間違ってもティッシュで擦ってはいけない。ティッシュで擦ると擦り傷が付いてしまう。

 分解清掃前の画像?あまりの汚さにドン引きしかねないので、最初から撮っておりませぬ (w


12/09/24

FireMonkey のメディア再生 (XE3)
 XE2 では動画再生用コントロールである TVideo / TVideo3D がオミットされてしまいましたが、XE3 では TMediaPlayer として実装されています。

 使い方は...説明するのがバカらしくなる程簡単なのですが、簡単にチュートリアルを。

  1. FireMonkey HD アプリケーションを新規作成
  2. TMediaPlayerTMediaPlayerControlTButon を一つずつ貼る。
  3. MediaPlayerControl1 (TMediaPlayerControl) の MediaPlayer プロパティに MediaPlayer1 (TMediaPlayer) を指定。
  4. Button1 (TButon) の OnClick イベントハンドラに以下のように記述
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      MediaPlayer1.FileName := 'C:\Users\Public\Videos\Sample Videos\Wildlife.wmv';
      MediaPlayer1.Play;
    end;
  5. コンパイルして実行。
 Windows 7 なら、ボタンを押せばサンプル動画が再生されるはずです。VCL 版の TMediaPlayer のように再生 GUI はありませんが、ボタンでチャチャっと作れば終わりです。TCornerButton とかで作れば多少見栄えがするかもしれません。

 TAnimation とコレがあれば Flash みたいな事ができますね。

.delphi maniacs
 デブキャン講師としておなじみ、シリアルゲームズ細川さんの Delphi 系ブログがオープンしました (^o^)/


12/09/25

Delphi の開発環境 (デバッグ環境) として使える Mac は?
 XE2 以降で FireMonkey (またはコンソールアプリケーション) による OSX 開発が可能になりました。開発環境 (デバッグ環境) に使える Mac は簡単に言えば以下の条件を満たしている必要があります。

 Intel Mac であっても Tiger (10.4) や Leopard (10.5) では PAServer が動作しません。FireMonkey の正式サポートは Snow Leopard (10.6) 以降です。なお、Snow Leopard (10.6) は Intel Mac であれば、どの機種でも動作します。

 さて。ここに iMac 5.1 (17″Late 2006 / Tiger 搭載) と Leopard (10.5) がある訳だが...。

Delphi でコンパイルされたバイナリを PAServer を使わずに Leopard (10.5) 以前で無理矢理実行したらどうなるのか?

 のエラーを拝むだけだと思います...何故そんな事が言えるのかって?

 そりゃ、やってみたからさ (ToT)

Delphi / C++Builder でコンパイルされたバイナリを PAServer を使わずに Mac OSX 上で実行するには?
 ビルドされたバイナリを OSX に普通にコピーしただけでは実行できません。

 それでも、何らかの問題で PAServer が使えない場合には手動で実行できるようにしなくてはならないため、PAServer を使わずに Mac OSX でバイナリを実行する方法を書いておきます。

  1. Windows で、ビルドされたバイナリがある場所 (.\OSX\Release) へ行く。
  2. アプリケーション名が MyApp だとすると、以下のようにファイルを配置する。
    [MyApp.app]
      |  
      +-[Contents]
        |  
        +-[MacOS]
        |  |
        |  +-MyApp
        |  |
        |  +-libcgcrtl.dylib       <- $(BDS)\Redist\osx32 から持ってくる: C++Builder のみ
        |  |
        |  +-libcgstl.dylib        <- $(BDS)\Redist\osx32 から持ってくる: C++Builder のみ
        |  |
        |  +-libcgunwind.1.0.dylib <- $(BDS)\Redist\osx32 から持ってくる
        |   
        +-[Resources]
        |  |
        |  +-MyApp.icns
        |
        +-info.plist               <- MyApp.info.plist をリネーム
  3. MyApp.app フォルダごと OSX に持っていく (Ex.~/Delphi/ の下とかに)。
  4. Finder から実行。
 トップフォルダを "プロジェクト名.app" にして、指定のフォルダにファイルを配置するだけなので、解ってしまえば簡単です。PAServer を使えればもっと簡単です (w


12/09/27

FireMonkey でセンサーを使ってみる (XE3)
 XE3 の FireMonkey には、

 という 2 つのコンポーネントがツールパレット (コンポーネントツールバー) にあります。とりあえず簡単にこれらを使ってみる事にします。

TLocationSensor (XE3)
 使い方は簡単です。

  1. フォームに TLocationSensor を貼ります (LocationSensor1)。
  2. フォームに TButton を貼ります (Button1)。
  3. フォームに TLabel を 2 つ貼ります (Label1 / Label2)。
  4. Button1 の OnClick() イベントハンドラを以下のように記述します。
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      LocationSensor1.Active := True;
    end;
  5. LocationSensor1 の OnLocationChangedイベントハンドラを以下のように記述します。
    procedure TForm1.LocationSensor1LocationChanged(Sender: TObject;
      const OldLocation, NewLocation: TLocationCoord2D);
    begin
      Label1.Text := Format('緯度: %f', [NewLocation.Latitude]);
      Label2.Text := Format('経度: %f', [NewLocation.Longitude]);
    end;
  6. 完成です。コンパイルして実行してみましょう。
 ボタンを押すとセンサーが有効になり、現在位置の緯度と経度をラベルに表示します。GPS を持っていなくても、無線 LAN さえあれば 09/12 の雑談で紹介した Geosense を使ってロケーションのテストができます。

TMotionSensor (XE3)
 こちらも TLocationSensor と使い方は同じです。データが更新されると OnDataChanged イベントが飛んでくるので、そこで Sensor プロパティの各メンバを参照します (AccelerationX / AccelerationY / AccelerationZ など)。

 ただ、モーションセンサーと一口に言っても、加速度センサー、ジャイセンサーなど色々種類があるため、取れるデータや参照すべきプロパティは様々です。それ故に、具体的なコードを書きようがないので、詳細は割愛させて頂きます。モーションセンサー (3 軸加速度センサーなど) が搭載されている PC をお持ちの方は是非チャレンジしてみて下さい。

More Sensors! (XE3)
 実はツールパレット (コンポーネントツールバー) にコンポーネントとして存在しないだけで、FMX.Sensors には下記クラスが存在します。

  試しに ALS (照度センサー) をテストしてみましょう。仮想 ALS については以前も記事にしているのでそちらを参照してください。最新の SDK には VirtualLightSensor.exe は含まれないようですのでご注意を。  照度センサーを調べるには TLightSensor を使います。順を追ったコードはメンドイので抜粋で。

uses
  ..., FMX.Sensors;

  ...
    procedure FormCreate(Sender: TObject);
    procedure btnActiveClick(Sender: TObject);
    procedure DataChanged(Sender: TObject);
  private
    { private 宣言 }
    LightSensor: TLightSensor;
  public
    { public 宣言 }
  end;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
  // 照度センサーの生成とイベントハンドラ設定
  LightSensor := TLightSensor.Create(Self);
  LightSensor.OnDataChanged := DataChanged;
end;

procedure TForm1.btnActiveClick(Sender: TObject);
begin
  // センサーをアクティブに
  LightSensor.Active := True;
end;

procedure TForm1.DataChanged(Sender: TObject);
begin
  // データが更新された
  lblLUX.Text := Format('%f LUX', [LightSensor.Sensor.Lux]);
end;

 フォームにはラベル (lblLUX) とボタン (btnActive) が貼ってあるだけです。

 ボタンを押してセンサーをアクティブにしておき、VirtualLightSensor.exe で照度を変更すると、照度が LUX で表示されます。メチャメチャ簡単ですね。悩みまくった挙句、「助けて!ブラザー!!」 してたのがウソのようです。

 ...それにしても、何故ツールパレット (コンポーネントツールバー) にコンポーネントとして登録してないのだろう?忘れてた?それともコンポーネントアイコン作るのがメンドウだったとか?


12/09/28

RAD Studio XE3 Hotfix 1-2-3
 この "1-2-3" という表記で Lotus 1-2-3 (表計算ソフト) を思い出しても、魔道物語 1-2-3 (コンパイルの RPG。ぷよぷよのキャラはここからきている) を思い出しても、結構いいお歳の方なのではないだろうか。

 それはともかく、RAD Studio XE3 の Hotix がリリースされている。特に "SQLite が Pro 版で使えない" という問題が修正されたのは朗報だと言える。

FireMonkey で "オミットされた!" コンポーネントと "次に実装される?" コンポーネント
 TVideo / TVideo3D というコンポーネントが XE2 にはあったらしいのだが、RTM の時点で消滅してしまった。その名残は DocWiki に残っており、検索すると今でも一部ヒットする。ただ、XE3 で TMediaPlayer / TMediaPlayerControl が実装されたので、これらのコンポーネントはもう実装されないだろう。

 で、アップデータ或いは次のバージョンでは VCL にある TWebBrowser と同等のブラウザコンポーネントが追加されると思う。実際問題、09/11 の雑談のような事を FireMonkey でやろうとしても、組み込みブラウザがないために実現できなかったりするからだ。これではセンサー機能 (ロケーション) が充分に活かせない。

 ブラウザコンポーネントが追加されるとしたら、おそらくは Chromium を使った物になるだろう。クラスプラットフォームなので、IE コンポを実装する訳にはいかないし、Mozilla の "組み込み Gecko" はサポートを終了しているし、選択肢らしい選択肢が Chromium しかないのが現状だからだ。 Delphi 用の Chromium コンポーネントとしては、Delphi Chromium Embedded というプロジェクトが既にあり、FireMonkey 対応も進められている (SVN からソースを持ってくると XE2 用だが FireMonkey のサンプルプロジェクトもある)。そういった背景から、Chromium ベースのブラウザコンポが追加されると思う。


 ...さーて、もっともらしい事も長々と語った事だし、ネタばらしをするか。

 実は FireMonkey のコンポーネントアイコンリソースに TWebBrowser と、TWebBrowserControl というのが残ってるのですよ。こんなのが >

More, More, Sensors! (XE3)
 やっぱりメンドイので、ツールパレット (コンポーネントツールバー) に存在しないセンサークラスをコンポーネントとして登録するパッケージを作ってみた。

 多分動くと思う。左から順に、  となる。コンポーネントアイコンは PowerPoint のクリップアートを適当に加工したものだから文句は言わないこと (w

 追記: やっぱりアイコンに統一性がない&視認性が悪いので新しいのを作ってみた。

 Windows 7 にあるセンサー用のリソースアイコンからのパクリ。アーカイブ内の FMX.SensorsEx.dcr.old は古いコンポーネントリソースファイルなので、要らなければ削除して構わないです。


12/09/29

SkRegExp 1.4.3 リリース
 小宮さん作の正規表現ライブラリ SkRegExp の version 1.4.3 がリリースされました。

Listing the available sensors using RAD Studio XE3 and FireMonkey 2 (Sip from the Firehose)
 twitter で RAN 氏に教えて貰った David.I のセンサーの記事。09/11 の記事で書いた LocationDemo の件同様、この記事中のソースコードもまた VCL アプリケーションで動作する。

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Sensors;

type
  TForm3 = class(TForm)
    Button2: TButton;
    Label2: TLabel;
    Memo2: TMemo;
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    NumberOfSensors : integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm3;

implementation

{$R *.dfm}

uses System.TypInfo;

procedure TForm3.Button2Click(Sender: TObject);
var
  i : integer;
begin
  // get list of found sensors - if any
  TSensorManager.Current.Active := true;
  NumberofSensors := TSensorManager.Current.Count;
  Label2.Caption := 'Sensors: '+IntToStr(NumberOfSensors);
  Memo2.Lines.Clear;
  for i := 0 to NumberOfSensors-1 do begin
    Memo2.Lines.Add(
      IntToStr(i)
      + ': '
      + TSensorManager.Current.Sensors[i].Name
      + '", Category: '
      + GetEnumName(System.TypeInfo(TSensorCategory),
            Ord(TSensorManager.Current.Sensors[i].Category))
    );
  end;
  TSensorManager.Current.Active := false;
end;

 Label2.Text が Label2.Text になったのと uses にリストされているネームスペースが違うだけだ。

 さて、センサーをリストアップするコードは以前 C++Builder で書いた事がある...いわゆる 「助けて!ブラザー!!」 だ。この時のバイナリはまだ DL できる。ONKYO TW317A5 で、これら "センサーを列挙するアプリケーション" を実行してみる事にする。

 上が C++Builder で SensorAPI を使った場合、下が Delphi XE3 の TSensorManager を使った場合だ。XE3 版では "G Sensor (Pegatron 三軸加速度センサー)" が列挙されていない事が判ると思う。

 どうやら、ONKYO TW317A5 の加速度センサードライバは既知のセンサーカテゴリを返さないようなのだ...つまりはドライバが悪いという事なのだが、逆に言えば TSensorManager は既知のカテゴリー以外のセンサーを列挙できないという事も判った。マルチプラットフォームの絡みなのだろうとは思う (実装しても OSX で使えない列挙方法だという事) けれど、カテゴリーは無視してすべてのセンサーを列挙して欲しいところ。センサーカテゴリとして "Unknown" を返せば問題ないハズなのだし。

 まぁ、こんな行儀の悪いセンサーは SensorAPI で直接どうにかしてやればいいという話ではあるのだけれど...。


12/09/30

TSelectionPoint3D (XE2)
 XE2 にはあって XE3 で廃止されたコンポーネント。3D shape を移動または回転させるのに使う。

 簡単に使い方を説明すると、

  1. Firemonkey HD アプリケーションを新規作成
  2. TViewport3D (Viewport3D1) を貼って、プロパティを以下のように設定する。
  3. Viewport3D1 に TCamera (Camera1) を貼って、邪魔にならない左上などに移動。
  4. Viewport3D1 に TRountCube (RoundCube1) を貼って、プロパティを以下のように設定する。
  5. Viewport3D1 に TSelectionPoint3D (SelectionPoint3D_X) を貼って、プロパティを以下のように設定する。
  6. Viewport3D1 に TSelectionPoint3D (SelectionPoint3D_Y) を貼って、プロパティを以下のように設定する。
  7. Viewport3D1 に TSelectionPoint3D (SelectionPoint3D_Z) を貼って、プロパティを以下のように設定する。
 ここまでの作業で以下の画像のようになるハズだ。軸の関係が解りにくいので、以前作った "右手座標系サンプル" の画像も並べておく。

 RoundCube1 を回転させるコードは OnTrack イベントハンドラに記述する。

procedure TForm1.SelectionPoint3D_XTrack(Sender: TObject);
begin
  RoundCube1.RotationAngle.X := -SelectionPoint3D_X.Angle;
end;

procedure TForm1.SelectionPoint3D_YTrack(Sender: TObject);
begin
  RoundCube1.RotationAngle.Y := -SelectionPoint3D_Y.Angle;
end;

procedure TForm1.SelectionPoint3D_ZTrack(Sender: TObject);
begin
  RoundCube1.RotationAngle.Z := -SelectionPoint3D_Z.Angle;
end;

 これを実行してみると、X 軸の選択ポイント (SelectionPoint3D_X) が動かせない事が判る...軌道と一致しているため動かせないのだ。2D の座標で考えた場合、該当する 3D 座標は手前と奥の 2 地点存在するのだから。SelectionPoint3D_X の X 位置をほんの少し右か左へズラしてやれば動かせるようになる。もちろん、選択ポイントにオフセットを設定したとしても、"選択ポイントを移動させて 3D Shape を回転させた結果、軌道と一致してしまった場合" にはやはり選択ポイントは動かせなくなってしまう。

 それと、このままだと各選択ポイントを動かしても他の選択ポイントは追従しない。例えば Y 軸の選択ポイント (SelectionPoint3D_Y) を動かしても X 軸の選択ポイント (SelectionPoint3D_X) は回らない。加えて、このサンプルは原点で動作させるからうまく動くのであって、RoundCube1 が原点を外れているとうまく動作しない。これを解決するには、TDummy を貼って、RoundCube1 と SelectionPoint3D_X / Y / Z を子として登録する...ローカル座標を一致させればいいのだ。しかしながら、TDummy の子にしてしまうと、選択ポイントは歪な動きをする。FMX のフォームデザイナのような動きはしない。

 また、TSelectionPoint3D は一軸毎の回転であり、選択ポイントの位置や移動角度の値は各 TSelectionPoint3D のインスタンスが保持している。この仕様では、他の TSelectionPoint3D の値を参照していないため、3 軸が独立して回転するような UI だと ジンバルロックが起こってしまう。XE3 で TSelectionPoint3D が廃止された理由もこの辺りにあるのではないかと思う...それでも、"無いよりはあったほうがマシ" なのだがなぁ。

 今回の元ネタは Disucussion Forum の "実行時に3Dオブジェクトをマウス操作するには? @ FireMonkey" です。


 BACK   古いのを読む   新しいのを読む