(2013/02/01~)
2013/02/01

GPS USB Dongle GT-730F

 超小型の USB GPS モジュール。大きさはちょっと昔の USB スティックメモリ程度しかない (100円 電子ライターと同程度)。アイオーデータの "NAVI CLIP" (ディスコン) と同程度だが、地図ソフトが付属しないのでとにかく安い (~3,000円)。

 トレッキングやバイクが趣味の方には、若干サイズが大きくなるがログ機能を持った GPS Route Logger Dongle GT-730FL-S の方がいいと思う。取り扱い店舗が多く、値段も安い事が多い。

 GPS USB Data Logger Dongle GT-730F(L) というのもある。サイズは GT-730F と同じでログ機能も付いているのだが、電池を内蔵していないので GT-730FL-S のようにスタンドアロンなロガーとして使うことはできない (秋月電子では独自に電池ボックスを付けて販売している)。加えて取り扱い店舗が少ない。

 ログ機能が要らないのなら GT-730F、スタンドアロンなロガーとしても使いたいなら GT-730FL-S がいいだろう。GT-730F(L) はデータロガーとして使おうとすると、別途モバイルバッテリーを携行しなくてはならないので中途半端な気がする。まぁ、内蔵電池がヘタってしまえば GT-730FL-S でも同じ事なのだろうが。

 なお、このトピックの内容は GT-730F / GT-730F(L) / GT-730FL-S いずれにも当てはまるので、該当機種をお持ちの場合はその機種で読み替えてほしい。

 ドライバは Windows 7 以降のロケーション API 対応のものではなく、単なる USB<->シリアル変換で、COM ポートから NMEA-0183 形式のデータが垂れ流しで送られてくる。ドライバのインストールは簡単で、付属の CD-ROM から ▶USB Driver▶USB_Driver(Win_7_vista_XP_2000) と辿り、PL2303_Prolific_DriverInstaller_v130.exe をインストールするだけ。

 USB ドライバのインストールが完了したら PC に GT-730F を挿す訳だが、その前にデバイスマネージャを開いておく。そして GT-730F を挿すと COMxx で認識されるのが確認できる。

 "Prolific USB-to-Serial Comm Port (COMxx)" というのがあったら、それが GT-730F の COM ポートとなる。当方では COM13 で認識された。GT-730FNMEA-0183 の標準 bps である 4800 だけではなく、9600 / 19200 / 38400 bps にも対応しているので、ドライバのプロパティで "ビット/秒" を 38400 に設定しておくといいだろう。

 Windows Vista 以降かつネット接続されている場合、多少時間は掛かるが USB に挿すだけでドライバを拾ってくるので光学ドライブがなくてもなんとかなる。正しくインストールされたらポップアップで COM ポートも表示されるので、デバイスマネージャを事前に開かなくても構わない (初期 bps は 9600 だったと思う)。経験則から言わせて貰うと USB 機器はドライバを事前にインストールしてから挿すようにした方がトラブルは少ないのだが...。

 それと、ドライバに更新があった場合には Windows Update の "オプションの更新プログラム" にリストアップされるが、GT-730F を挿した状態でアップデートを行わないと 0x800F020B のエラーが出てドライバが更新されないので注意が必要だ。

 付属のアプリは後で試すことにして、とりあえず NMEA-0183 データが取れるか調べてみる...データ垂れ流しの場合は昔懐かしの "ハイパーターミナル" を使えば一発なのだが、残念なことに Vista 以降の Windows には "ハイパーターミナル" は付属しない。

 ...という事で、4river さん作の "NMEA Monitor for windows" でNMEA データを拾ってみる事にする。なお、このアプリケーションは Delphi 2007 製である。

 屋外であればサクっと衛星を補足する。屋内では窓際でない限り補足できないだろうが、正常動作している事は確認できたのでよしとする。

 付属 CD-ROM のドライバで問題があるようなら CanMore のダウンロードページProlific のサポートページ (ID / PASS に "GUEST" と入力するとゲストログインできる) から最新ドライバを DL してみるといいかもしれない。現時点での最新ドライバには以下のようなものがある。

 Linux (Android 含む) 用のドライバは Linux Kernel 2.4.31 以降でビルトインで存在するとの事。Windows で "The Device Cannot Start (Code 10)" のエラーを拝んだ場合には、最新のドライバを使うかこの辺りを参考にするといいと思う。

 断るまでもないかもしれないが、"Prolific PL-2303" というのは USB<->シリアル 変換チップの事で、GPS モジュールの事ではない

 GPS モジュールは SkyTraq 社の Venus 6 (Venus 621) が搭載されている。旧タイプの GT-730F には Venus 5 が搭載されていたようだ。

 つまり "USB ドライバはあるが GPS ドライバは存在しない" という事なのだが、GT-730F / GT-730F(L) / GT-730FL-S を購入したヒトの中には、"ドライバさえ入れればアプリ側で勝手に GPS が使えるようになる" と勘違いしている方も少なくないようだ (設定手順がマニュアルに書いてあるのに)。

 余談になるが、Windows 用の v1.7.0 ドライバには PL-2303 チップチェッカ (PL2303CheckChipVersion.exe) が同梱されている事を覚えておくといいかもしれない。GT-730F だけでなく、PL-2303 を採用した USB シリアル (RS-232C) ケーブル等もチェックできる。

 何故こんな事を書くのかと言うと、PL-2303 には Windows 8 に対応していないバージョンがあるからだ。

 PL-2303H / HX (Rev A) / HXA / X / XA は既にディスコンなため、Windows 8 のドライバが提供される事はないらしい。Windows 8 な PC に PL-2303H / HX (Rev A) / HXA / X / XA チップ搭載機器を接続すると "エラーコード10" となってしまうようだ。

 手持ちの GT-730F は PL-2303 HXD が使われており Windows 8 でも動作するのだが、同型番で仕様変更になった経緯があるので、ひょっとすると Windows 8 で動作しない GT-730F があるのかもしれない。

 See Also:

TComm と TCommX

 Delphi でのシリアル通信コンポーネントと言うと、TComm とか TCommX が有名だと思う (TurboPower Async Pro とかもあるけど)。これらのコンポーネントはニフティサーブで入手できた。経緯があいまいだけれど、大野さんが作った Delphi (16bit) 用が TComm で (Delphi Q&A 120選にも付属)、それを 32bit に改変したのが Duke さんの TComm (Delphi 2 達人テクニック にも付属)、スレッド周りをさらに改変されたものがエックスさんの TCommX だったように思う。これ以降の経緯はよく知らない。

 Delphi で NMEA-0183 形式のデータをどうにかしたいなら、シリアル通信 (RS-232C) のプログラミングを行えばいい事になる。Unicode 版 Delphi で動作する TComm (コンポーネントエディタ分離済み) も TCommX も作成済みだが、配布条件がよくわからないので公開はしていない。

NMEA-0183 解析したいんじゃない、位置情報が知りたいだけなんだ!」

 GT-730F にはロケーション API 対応ドライバはないので、それは無理...ではない。世の中には "COM ポート経由で NMEA-0183 データを吐く GPS をロケーション API 対応に化けさせるドライバ" というものがある。

 図解すると以下のようになる。

 早速 GPSDirect をインストールしてみることにする。インストーラを実行すると COM ポートと bps を尋ねられるので、GT-730F の設定を入力する。

 [Install] ボタンを押すと、ドライバがインストールされ、

 センサーを有効にするかどうかを尋ねてくる。

 インストールが正しく行われるとデバイスマネージャに GPSDirect のセンサードライバが現れる。

 注意点だが、理屈上このセンサードライバが有効な状態だと COM ポートをオープンする事ができない (オープンできたとしても双方が正常に動作する保障はない)。つまり、ロケーション API を使うアプリと NMEA-0183 データを COM ポートから取得するアプリは排他利用という事になる。センサードライバのアンインストール / 再インストールは簡単なので、ドライバインストール後もインストーラファイルは PC のどこかに残しておくべきだろう。devcon 叩くとかすれば、ドライバの制御ができるような気がしないでもないけれど。

 もちろん一般的な用途だけであれば、ドライバのアンインストール / 再インストールを行うより [コントロールパネル | 位置センサーとその他のセンサー] で有効 / 無効を切り替えた方が早い。

 デメリットが大きいような書き方になってしまったけれど、既に RS-232C 接続あるいは Bluetooth (SPP) 接続な GPS を所持している場合でも、COM ポート経由で NMEA-0183 データが取れさえすればロケーション API が使えるというのは結構大きなメリットだと思う。

 Delphi XE3 のセンサーコンポーネントの使い方は過去にも散々やったので省略 (概略は Tips を参照)。やはり GPS は物理 GPS に限る。

Geosense for Windows

 デブキャンでも紹介した三角測量ドライバ。Wi-fi 等を使って三角測量し、大体の位置情報を取得する。

 サイトをご覧いただければ一目瞭然だが、ディスコンで DL できなくなっている。理由もそこに書いてある通りで、Windows 8 では同様の事を行う機能が標準で付いているから。Internet Archive にはまだ残っているので、

 必要な方はお早めに。

iPod touchでGPS。(Stormbringer)

 iPod Touch に物理 GPS を付けるという記事。この記事は出来合いの GPS モジュールを取り付けるというものではなく、秋月電子からパーツを購入して GPS を自作するというもの。

 Windows マシンでやりたければ GPS モジュール (GT-720F / GT-723F) と GPS 用 USB 変換ケーブル だけ用意すれば普通に使えると思う。GPS モジュールの型番で判るように、これも CanMore 社の製品で Venus 6 を搭載しており、USB 変換ケーブル もまた Prolific 社 の PL-2303 を使っている。

 一旦 Windows で初期設定さえ行ってしまえば、RS-232C が使える大抵の機器で GPS データの受信が可能 というのがミソ。やろうと思えば SHARP PC-E500 (ポケコン) でも GPS が使えるとか胸熱。


2013/02/02

FastReport FMX - Embarcadero Edition

 FMX で使える FastReport。XE3 ユーザが対象となります (FastReport 自体は XE2 にも対応しています)。

 マルチプラットフォームで運用すると "フォントによる描画の差異の問題" が出るかもしれませんが、そんな事言い出したら Windows でだってプリンタが異なれば印刷結果が異なるし、下手すりゃ Windows のバーション / ドライバの差異によっても印刷結果が異なるのであまり深く考えないほうがいいのかもしれません。

 ともあれ、FMX でビジネスアプリを作る上での障害がまた一つ取り除かれた事は素直に歓迎すべきでしょうね。そういえば、懸案事項の一つである "FMX で TabOrder が動作しない / 存在しない件" ですが、現在 Resolved で Checked In な QC には TabOrder 絡みのものが複数含まれていますね。これはひょっとして...。


2013/02/03

Fujitsu FMV-BIBLO NF40U の CPU を換装

 Vista でメモリ 512KB (最大 2GB)、CPU も非力な Intel Celeron M 410 (1.46GHz) というオイオイなノート PC。2007 年 1 月末発売モデルだから、6 年前の機種という事になる。

 クソ遅い以外は問題ないので CPU を交換する事にした。社内に、全く動作しない Toshiba dynabook Satellite T30 166E/5W があったので、Core 2 Duo を頂くことにする。こちらも無線 LAN は内蔵していないわ、DVD-ROM だわで、ちょっとオイオイではある。

 上記サイトの分解手順を参考にしたのだが、キーボードが外れない。

 どこかの保険調査のオプな人 (考古学者さん?) 風味なやり方だが、要はキーボードが両面テープで固定されているのだ (Panasonic の Let's Note なんかもそうだったハズ)。キーボードさえ外れれば後は簡単、サクっと CPU 交換完了。起動してみたらちゃんと動いた。

 ついでにメモリを 1.5GB にしてめでたしめでたし。


2013/02/04

コンポーネントの自動 (?) 解放 (Delphi 6 またはそれ以降)

 "[delphi-users:2980] ガーベッジコレクションについてどうお考えですか" で思いついたネタです。GC が要るとか要らないとかそういう話ではないのでご了承下さい。知っているヒトには当たり前の事なので、「だからどうした」 とかいう罵声を浴びせるのもおやめください (w

 例えばボタンがクリックされたら TTimer を 100 個生成するコードは以下のようになると思います。

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Timer: TTimer;
begin
  for i:=1 to 100 do
    begin
      Timer := TTimer.Create(nil);
    end;
end;

 プロジェクトファイルで

ReportMemoryLeaksOnShutdown := True;

 ReportMemoryLeaksOnShutdown が有効になっている場合、このままアプリケーションを終了するとメモリリークが報告されます...TTimer の解放処理がないので当たり前ですね。

 では、TTimer のコンストラクタに所有者 (Owner) を指定したらどうなるでしょう?

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Timer: TTimer;
begin
  for i:=1 to 100 do
    begin
      Timer := TTimer.Create(Self);
    end;
end;

 今度はメモリリークが報告されなかったと思います。TComponent は自身が所有しているコンポーネントの破棄義務があるのでこういう結果になります (TFormTComponent の下位クラスなのでフォームが所有している TComponent もフォームが破棄される時点で破棄される)。

 でも、これだとフォームが破棄されない限りコンポーネントは破棄されません。非ビジュアルコンポーネントを任意のタイミングで自動破棄したい事もあるでしょう。そういう場合には、TComponentList を使います。

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    CompList: TComponentList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  CompList := TComponentList.Create(True);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CompList.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Timer: TTimer;
begin
  for i:=1 to 100 do
    begin
      Timer := TTimer.Create(nil);
      CompList.Add(Timer); // リストに追加
    end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  CompList.Clear;          // リストをクリア
end;

end.

 このコードでは、Button2 を押したタイミングで TComponentList に登録された動的作成コンポーネントを削除します。削除し忘れてもフォームが破棄された時点で動的作成したコンポーネントも削除されます。

 See Also:

クラスの自動 (?) 解放 (Delphi 6 またはそれ以降)

 今度はクラスです。クラスは TObject からの派生なので、TComponent の上位から派生している場合 (親クラスが TComponent でない場合) だと TComponentList は使えません。

 クラスの自動解放には TObjectList を使います。

unit Unit1;

interface

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

type
  // テスト用クラス
  TMyClass = class (TObject)
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    ClassList: TObjectList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  ClassList := TComponentList.Create(True);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClassList.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  MyClass: TMyClass;
begin
  for i:=1 to 100 do
    begin
      MyClass := TMyClass.Create;
      ClassList.Add(MyClass); // リストに追加
    end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ClassList.Clear;            // リストをクリア
end;

end.

 TComponentTObject の下位クラスなので、使い分けが必要でないのなら TComponentList の代わりに TObjectList を使ってもいいと思います。

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Timer: TTimer;
begin
  for i:=1 to 100 do
    begin
      Timer := TTimer.Create(nil);
      ClassList.Add(Timer); // リストに追加
    end;
end;

 これでもいいという事です。

 See Also:

クラスの自動 (?) 解放 (Delphi 2009 またはそれ以降)

 以前 au2010 さんが記事にされてましたのでご存知の方も多いかと思いますが、Unicode 版 Delphi では TStringList のコンストラクタに引数として True を与えると AddObject() されたクラスが自動解放されるようになります (OwnsObjects プロパティを True にするのと同義です)。

unit Unit1;

interface

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

type
  // テスト用クラス
  TMyClass = class (TObject)
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    ClassList: TStringList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  ClassList := TStringList.Create(True);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClassList.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  MyClass: TMyClass;
begin
  for i:=1 to 100 do
    begin
      MyClass := TMyClass.Create;
      ClassList.AddObject('', MyClass); // リストに追加
    end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ClassList.Clear;                      // リストをクリア
end;

end.

 これまた便利ですね。


2013/02/08

色の差、メイドの差 明度の差、輝度の差

 とりあえず Checkpoint 2.2 - Ensure that foreground and background color combinations provide sufficient contrast when viewed by someone having color deficits or when viewed on a black and white screen (Techniques For Accessibility Evaluation And Repair Tools - W3C) を参照の事。

unit uColorDiff;

interface

uses
  Graphics, Math;

type
  TARGB = packed record
    case BYTE of
      0: (ARGB: UInt32);
      1: (R, G, B, A: Byte);
    end;

  TColorDiffType = (cdtColor, cdtBrightness, cdtLuminance);

  TColorDiff = class(TObject)
  strict private
    FSrcColor: TColor;
    FDstColor: TColor;
    class function Difference(Src, Dst: TColor; DiffType: TColorDiffType): Integer; overload;
  public
    // Constructor / Destructor
    constructor Create;
    destructor Destroy; override;
    // Methods
    function ColorDifference: Integer; overload;
    function ColorBrightnessDifference: Integer; overload;
    function ColorLuminanceDifference: Integer; overload;
    // Class Functions
    class function ColorDifference(Src, Dst: TColor): Integer; overload;
    class function ColorBrightnessDifference(Src, Dst: TColor): Integer; overload;
    class function ColorLuminanceDifference(Src, Dst: TColor): Integer; overload;
    // Properties
    property SrcColor: TColor read FSrcColor write FSrcColor default clBlack;
    property DstColor: TColor read FDstColor write FDstColor default clBlack;
  end;

implementation

{ TColorDiff }

constructor TColorDiff.Create;
begin
  inherited;
  FSrcColor := clBlack;
  FDstColor := clBlack;
end;

destructor TColorDiff.Destroy;
begin
  inherited;
end;

function TColorDiff.ColorDifference: Integer;
var
  SrcARGB, DstARGB: TARGB;
begin
  SrcARGB.ARGB := ColorToRGB(FSrcColor);
  DstARGB.ARGB := ColorToRGB(FDstColor);
  result := (Max(SrcARGB.R, DstARGB.R) - Min(SrcARGB.R, DstARGB.R)) +
            (Max(SrcARGB.G, DstARGB.G) - Min(SrcARGB.G, DstARGB.G)) +
            (Max(SrcARGB.B, DstARGB.B) - Min(SrcARGB.B, DstARGB.B));
end;

function TColorDiff.ColorBrightnessDifference: Integer;
  function GetBrightness(Color: TColor): Integer;
  var
    ARGB: TARGB;
  begin
    ARGB.ARGB := ColorToRGB(Color);
    result := Trunc(((ARGB.R * 299) + (ARGB.G * 587) + (ARGB.B * 114)) / 1000);
  end;
begin
  result := Abs(GetBrightness(FSrcColor) - GetBrightness(FDstColor));
end;

function TColorDiff.ColorLuminanceDifference: Integer;
  function GetLuminance(Color: TColor): Integer;
  var
    ARGB: TARGB;
  begin
    ARGB.ARGB := ColorToRGB(Color);
    result := Trunc((0.298912 * ARGB.R) + (0.586611 * ARGB.G) + (0.114478 * ARGB.B));
  end;
begin
  result := Abs(GetLuminance(FSrcColor) - GetLuminance(FDstColor));
end;

class function TColorDiff.Difference(Src, Dst: TColor; DiffType: TColorDiffType): Integer;
var
  CD: TColorDiff;
begin
  CD := TColorDiff.Create;
  try
    CD.SrcColor := Src;
    CD.DstColor := Dst;
    case DiffType of
      cdtBrightness:
        result := CD.ColorBrightnessDifference;
      cdtLuminance:
        result := CD.ColorLuminanceDifference;
    else
      result := CD.ColorDifference;
    end;
  finally
    CD.Free;
  end;
end;

class function TColorDiff.ColorDifference(Src, Dst: TColor): Integer;
begin
  result := Difference(Src, Dst, cdtColor);
end;

class function TColorDiff.ColorBrightnessDifference(Src, Dst: TColor): Integer;
begin
  result := Difference(Src, Dst, cdtBrightness);
end;

class function TColorDiff.ColorLuminanceDifference(Src, Dst: TColor): Integer;
begin
  result := Difference(Src, Dst, cdtLuminance);
end;

end.

 何かに使えるようなら使って下さい。


2013/02/10

Delphi でリストに入った構造体を任意に抽出する

 世の中には LINQ というものがあります。

 で、EasyLinq for Delphi というものがあり、Delphi 2010 以降で利用可能です。名前に "Linq" と付いているからといって .NET の LINQ を想像してはいけません。(Prism ではない) Delphi でアレをやろうとすると、言語仕様から変更しないと無理です (メソッドチェーンでモドキなら作れるかもしれませんが)。じゃ、どういうものかといいますと...

unit Unit1;

interface

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

type
  TMyRecord = record
    Code: Integer;
    Name: string;
  end;

  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    RecordLinq: TEasyLinQ<TMyRecord>;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  R: TMyRecord;
begin
  RecordLinq := TEasyLinQ<TMyRecord>.Create;
  // ダミーデータ生成
  for i:=0 to 9 do
    begin
      R.Code := i+1;
      R.Name := StringOfChar(Chr(Ord('A') + i), 10);
      RecordLinq.Add(R);
    end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  RecordLinq.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  R: TMyRecord;
  RL: TEasyLinQ<TMyRecord>;
begin
  Memo1.Lines.Clear;
  // 抽出条件で絞込み実行
  RL := RecordLinq.Execute(Edit1.Text);
  try
    // 実行結果を取り出す
    for R in RL do
      Memo1.Lines.Add(Format('%d %s', [R.Code, R.Name]));
  finally
    RL.Free;
  end;
end;

end.

 そのまま実行して、Edit1 を空にして Button を押すとこうなります。

 Edit1 に条件を入れてみましょう (Select の入力は任意。Where からでも同じ)。

 さらに Edit1 に条件を入れてみましょう。

 Code が 5 以上のレコードを降順で表示できました。EasyLinq という名前から想像したものとは違っていたかもしれませんが、これはこれで便利だと思いませんか?

Delphi でリストに入ったクラスを任意に抽出する

 EasyLinq はクラスも扱えます。

unit Unit1;

interface

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

type
  TMyClass = class(TObject)
  private
    FName: string;
    FCode: Integer;
  public
    property Code: Integer read FCode write FCode;
    property Name: string read FName write FName;
  end;

  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ClassLinq: TEasyLinQ<TMyClass>;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  C: TMyClass;
begin
  ClassLinq := TEasyLinQ<TMyClass>.Create;
  // ダミーデータ生成
  for i:=0 to 9 do
    begin
      C := TMyClass.Create;
      C.Code := i+1;
      C.Name := StringOfChar(Chr(Ord('A') + i), 10);
      ClassLinq.Add(C);
    end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClassLinq.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  C: TMyClass;
  CL: TEasyLinQ<TMyClass>;
begin
  Memo1.Lines.Clear;
  // 抽出条件で絞込み実行
  CL := ClassLinq.Execute(Edit1.Text);
  try
    // 実行結果を取り出す
    for C in CL do
      Memo1.Lines.Add(Format('%d %s', [C.Code, C.Name]));
  finally
    CL.Free;
  end;
end;

end.

 先程の条件を入れてみると...

 全く同じ結果ですね。え、つまらない?ではこんなのはどうでしょう?

 EasyLinq はクラスに対してだと UPDATE 文も使えます。

 とある条件を満たしたコンポーネントのプロパティを一斉に変更する、なんて事もできるので応用次第で面白い事がやれそうですね。

SINTRONES Technology VDB-800SG

 http://www.sintrones.com/products/VDB-800SG.php

 Mini-PCIe な GPS & 3 軸加速度センサー。ONKYO TW317 シリーズもこれを載せれば幾分マシになる気がする。GPS モジュールは u-blox 6 だからセンサードライバはちゃんとあるのよね...結局 GPS アンテナの入手方法 & 設置場所に困るのだろうけれど。


2013/02/14

EditBone (テキストエディタ)

 http://www.bonecode.com/

 "Delphi製オープンソースのタブ切り替え型テキストエディタ「EditBone」(山本隆の開発日誌)" 経由で知ったテキストエディタ。Delphi-IDE のコードエディタに似ており、キーバインドもほぼそのまま。

 エディタ部には (Uni)SynEdit が使われているので Unicode 対応。構文強調表示はもちろん、ファイル比較 / HTML 文法チェッカ / XML 整形機能も備えている。VCL スタイルにも対応している。主要な スクリーンショットはこちら

 日本語 Windows 環境でアーカイブを普通に解凍してしまうと、起動時に "''Fran㌢isLanguageSelectAction'' is not a valid component name." のエラーが出てしまうと思う (由緒正しき CP437 のアーカイブなので)。これを回避するには、ZipDecompress を使って CP437 で解凍するか、

 Languages フォルダの中の文字化けした Fran㌢is.lng を Français.lng にリネームすればいい。

 さて、EditBone 4.9.0 には日本語の言語ファイルが含まれていないが、言語ファイルを作ったのでこれを利用すれば簡単に日本語化できる。

 言語ファイルを Languages フォルダの中にコピーして、[View | Language | Japanese] で日本語化される。先のフランス語言語ファイルの件を解決しないとこの言語ファイルを適用できない点には注意が必要だ。

 この言語ファイルは作者にメールしておいたので、次のバージョン (5.0.0) に取り込まれるかもしれない。


2013/02/15

IBConsole 日本語版+α Windows Unicode Edition rel.27 / ANSI Edition rel.66 (但し人柱専用)

 できました。

 今回の修正は文章で説明するよりスクショの方が手っ取り早いでしょう。

 要はコードテンプレート (ライブテンプレートではない) です。

 コードテンプレートはカレントフォルダの sql.dci を読みに行きます。サンプルが付属しているのでご自由に書き換えて下さい。文法は以下のようになります。

[テンプレート名 | 説明]
テンプレート本文

※ テンプレート本文内の | はテンプレート入力後のキャレット位置。

 忘れがちな SQL 文を片っ端から突っ込んでおくといいでしょう。

 要は入力支援機能です。

 通常はテーブルやビュー等のオブジェクトがリストアップされますが、SQL 文にテーブル名が含まれると、そのフィールド名も候補として列挙されます。ただ、SQL をパースしている訳ではないのでアバウトです。入力支援機能は .(ピリオド) を入力しても発動しますが、前後の文脈は無視されます。

 理屈的にですが、入力支援候補が膨大な量になるとエラーが発生する可能性があります...なので、このバージョンは人柱扱いとします (ジャンクボックスの方は前バージョンのままです)。

 追記(13:50): ちょっとコードに問題があったので同名で修正版をアップ。ついでにテーブルのフィルタ式でも入力支援を可能にしました。
 追記(16:50): 古いバージョンで作成されたデータベースで問題が発生するようなので同名で修正版をアップ。


2013/02/16

InkEdit 再び

 去年の雑談で ActiveX の InkEdit を使う方法をご紹介しました。

 あの時にも書いたのですが "InkEdit は RichEdit のスーパーセット" な訳で、TRichEdit をチョイといじると InkEdit になるのです。TEdit をいじって IP アドレスコントロールにするようなものですね。では InkEdit を実装してみましょう。

 ...という訳で (?)、出来上がった InkEdit 用のユニットがこちらになります。

[uInkEdit.pas]
unit uInkEdit;

interface

uses
  Winapi.Windows, Winapi.Messages, Winapi.RichEdit, Vcl.Controls, Vcl.ComCtrls;

type
  TInkMode =
    (IM_Disabled      = 0,
     IM_Ink           = 1,
     IM_InkAndGesture = 2);

  TInkInsertMode =
    (IEM_InsertAsText = 0,
     IEM_InsertAsInk  = 1);

  TInkDisplayMode =
    (IDM_Ink          = 0,
     IDM_Text         = 1);

  TInkEditStatus =
    (IES_Idle         = 0,
     IES_Collecting   = 1,
     IES_Recognizing  = 2);

  TInkEdit = class(Vcl.ComCtrls.TRichEdit)
  private
    function GetUseMouseForInput: Boolean;
    procedure SetUseMouseForInput(const Value: Boolean);
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  published
    property UseMouseForInput: Boolean read GetUseMouseForInput write SetUseMouseForInput;
  end;

const
  InkEditClassName = 'INKEDIT';
  IEC__BASE               = WM_USER + $0200;
  EM_GETINKMODE           = IEC__BASE +  1;
  EM_SETINKMODE           = IEC__BASE +  2;
  EM_GETINKINSERTMODE     = IEC__BASE +  3;
  EM_SETINKINSERTMODE     = IEC__BASE +  4;
  EM_GETDRAWATTR          = IEC__BASE +  5;
  EM_SETDRAWATTR          = IEC__BASE +  6;
  EM_GETRECOTIMEOUT       = IEC__BASE +  7;
  EM_SETRECOTIMEOUT       = IEC__BASE +  8;
  EM_GETGESTURESTATUS     = IEC__BASE +  9;
  EM_SETGESTURESTATUS     = IEC__BASE + 10;
  EM_GETRECOGNIZER        = IEC__BASE + 11;
  EM_SETRECOGNIZER        = IEC__BASE + 12;
  EM_GETFACTOID           = IEC__BASE + 13;
  EM_SETFACTOID           = IEC__BASE + 14;
  EM_GETSELINK            = IEC__BASE + 15;
  EM_SETSELINK            = IEC__BASE + 16;
  EM_GETMOUSEICON         = IEC__BASE + 17;
  EM_SETMOUSEICON         = IEC__BASE + 18;
  EM_GETMOUSEPOINTER      = IEC__BASE + 19;
  EM_SETMOUSEPOINTER      = IEC__BASE + 20;
  EM_GETSTATUS            = IEC__BASE + 21;
  EM_RECOGNIZE            = IEC__BASE + 22;
  EM_GETUSEMOUSEFORINPUT  = IEC__BASE + 23;
  EM_SETUSEMOUSEFORINPUT  = IEC__BASE + 24;
  EM_SETSELINKDISPLAYMODE = IEC__BASE + 25;
  EM_GETSELINKDISPLAYMODE = IEC__BASE + 26;

  IECN__BASE              = $0800;
  IECN_STROKE             = IECN__BASE + 1;
  IECN_GESTURE            = IECN__BASE + 2;
  IECN_RECOGNITIONRESULT  = IECN__BASE + 3;

var
  FInkEditModule: THandle;

implementation

{ TInkEdit }

procedure TInkEdit.CreateParams(var Params: TCreateParams);
const
  dHideScrollBars: array[Boolean] of DWORD = (ES_DISABLENOSCROLL, 0);
  dHideSelections: array[Boolean] of DWORD = (ES_NOHIDESEL, 0);
begin
  if FInkEditModule = 0 then
    begin
      FInkEditModule := LoadLibrary('InkEd.dll');
      if FInkEditModule <= HINSTANCE_ERROR then
        FInkEditModule := 0;
    end;
  inherited CreateParams(Params);

  CreateSubClass(Params, InkEditClassName);
  with Params do
    begin
      Style := Style or dHideScrollBars[HideScrollBars] or
        dHideSelections[HideSelection];
      WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
    end;
end;

function TInkEdit.GetUseMouseForInput: Boolean;
begin
  result := SendMessage(Self.Handle, EM_GETUSEMOUSEFORINPUT, 00) <> 0;
end;

procedure TInkEdit.SetUseMouseForInput(const Value: Boolean);
begin
  SendMessage(Self.Handle, EM_SETUSEMOUSEFORINPUT, WPARAM(Value), 0);
end;

initialization
  FInkEditModule := 0;

finalization
  if FInkEditModule <> 0 then
    FreeLibrary(FInkEditModule);
end.

 新規に追加したプロパティは UseMouseForInput だけです。他に必要なプロパティがあればご自由に実装しちゃってください。インクジェスチャ (バックスペースとか) はデフォルトで有効になってますので、一般的に必要そうなものといえば RecTimeOut (インク入力タイムアウト時間) くらいですかね?

 TInkEdit を動的作成して使ってもいいのですが、設計時に形だけでもあると便利ですよね。そんな時はフォームに TRichEdit を貼って小細工をします。

unit Unit1;

interface

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

type
  TRichEdit = class(TInkEdit); // これを追加

  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  RichEdit1.UseMouseForInput := True;
end;

end.

 これだけで TRichEdit が InkEdit に化けます。

 結果はご覧のとおりです。フォント指定も ActiveX 版より判り易くていいですね (Font プロパティで設定可能)。

 CreateParams() をオーバーライドするという、よくある手法で TInkEdit を実装しています。プロパティの追加は EM_~ メッセージを SendMessage() で処理するだけなので、Microsoft の "InkEdit Messages (Win32 Only) (Windows)" を参考にすれば実装は難しくないと思います。

 余談ですが、インクジェスチャとバッティングするので 一画目が横棒の漢字はインク入力が困難です ("三十" とか)。これを回避するには、やや右肩上がりでインク入力してください...右肩下がりでも大丈夫な事が多いですが、それは縁起がよろしくないでしょう?


2013/02/18

EditBone 5.0.0 (テキストエディタ)

 http://www.bonecode.com/

 5.0.0 がリリースされたようです。日本語言語ファイルが同梱されるようになりました...が、5.0.0 で追加された新機能である "XML ツリー" の部分が文字化けします...てな訳で、最新の言語ファイルです。

 一部日本語化されない部分がありますが、これは EditBone 側が言語ファイルを読んでいないからで、翻訳されていないからではありません。

 ついでに送っておいた Delphi-IDE / VisualStudio 互換ブロックコピーのロジックも反映されているようですね。これは "IBConsole 日本語版+α" のエディタ部で実装してあるブロックコピーのロジックと同じものです。


2013/02/19

InkEdit が使えない場合には?

 "[Delphi Q&A] oleコントロールのウィンドウハンドルの取得に失敗しました" より。

 サービスで "Tablet PC Input Service" が無効になっていないかを確認し、[コントロールパネル | プログラムと機能 | Windows の機能の有効化または無効化] (OptionalFeatures.exe) で "Tablet PC コンポーネント" (Vista では "Tablet PC オプション コンポーネント") にチェックが入っているかどうかを調べる。

 Windows の高速化設定を載せたサイトでは、デメリットの説明を省略して "Tablet PC 関連" を無効にする事を薦めているトコロが少なくない。そりゃ、エアコンもパワーウィンドウも後部座席もフロアマットもすべて取り外せば車は速くなるだろうよ...快適さと引き換えにね。理解してやってる分には問題はないだろうけれど、何ができなくなるのかも書いておかないとね。

 Windows Server 2008 R2 等のサーバ OS では、サーバでは必須ではない "Vista / 7 等のデスクトップ用 OS で使える一部の機能" がインストールされていない。単にインストールされていないだけなので、インストールすれば使えるようになる。

 Windows Server 2012 だとちょっと UI が異なるけれど、やる事は同じ。

 InkEd.dll が存在しても、COM コンポーネント (ActiveX) として登録されていなければ、ActiveX 版 InkEdit は動作しない (Win32 版でも一部機能が使えない可能性がある)。普通はこの状態になる事はないだろうが、サーバ OS では有り得るかもしれない。デスクトップ エクスペリエンス機能をインストールするか、以下の方法で ActiveX を登録すれば解決するハズ。

 面倒でなければ、Microsoft の "Regsvr32 ツールを使用する方法および Regsvr32 のエラー メッセージをトラブルシューティングする方法" を読んでおくといいと思う。


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