« 2007年7月 | トップページ | 2007年9月 »

■TiburónでUnicodeとGenerics対応

CodeGear Developer Camp in Osaka で Malcolm Groves氏からTiburón(2008年前半完成予定)でVCLのUnicodeとGenericへの対応を実現する予定だと話がありました。そしてGenericの概要については、少し時間を取って説明をされていました。でも、Genericって、.NET2.0で既に実装済みでしたよね。まあ、Win32ベースでも使えるというのではあれば、それはそれでいいことですけどね。
Genericとは、特定の型に依存しないTListのようなもので、インスタンスを作成する時にそのリストで扱う型を指定します。その仕組みにより、追加するときには、指定の型以外を受け付けませんし、値を取り出すときには型キャストが不要になります。 メリットしては、実行速度の高速化とコードの簡略化かな?(正しく理解していないかも?)
Genericのより詳しい内容は、下記のサイトをご覧下さい。 (C#&VBですけどね)


CodeGear
Delphi and C++Builder Roadmap


@IT
C#&VBジェネリック超入門(前編)
ジェネリック・クラスで変わるC#とVBのコレクション


C#&VBジェネリック超入門(後編)
ジェネリックなメソッドやデリゲートがもたらす新スタイル

|

■コードエディタの機能紹介

Delphi 2007のコードエディタには、便利な機能がたくさん備わっています。
その中のほんの一部を紹介します。(CodeGear Developer Camp in Osaka で Malcolm Groves氏 が説明されていたものをまとめてみました)

支援機能その1

文字列入力
文字列を入れて、括弧で括らずにリターンキーを押します。
自動的に括弧を閉じて、+を付加した上で、次の行でも文字列が入力できるようになっています。

Codeeditor1




支援機能その2 

テンプレート1 try ~ finally ~ end;
try を入力した後、リターンキーを押すと、次のように補完されます。

Codeeditor2

又、try を入力した後、スペースキーを押すと、同期編集モードになります。

Codeeditor3

ここで、MyClassをMyList、ComponentをTList、(Self)でDELを押してTABを押すと、MyList: TListと自動で宣言してくれます。

Codeeditor4




テンプレート2 case ~ end;
case を入力した後、スペースキーを押すと、次のようになります。

Codeeditor5

この状態でTrueをFromStyleに書き換えてタブキーを押すと、自動的にメンバー?を並べてくれます。

Codeeditor6




テンプレート3 class
Typeの次行で、CTRL+SPACEを押します。
(Malcolm Groves氏の説明では、Typeの次行でclassと入力してからスペースを押していたと思うのですが、私のPCでは実現できませんでした)

Codeeditor7

classを選択してリターンを押します。

Codeeditor8

publicでCTRL+SPACEを押します。

Codeeditor9

この位置では、Publicで使えそうなメンバーを選択できます。



その他にもたくさん機能がありますので、ぜひヘルプで確認してみて下さい。
目次→RAD Studio→RAD Studio(共通)→ファーストステップ→コードエディタにあります。

|

■第6回 CodeGearデベロッパーキャンプ(大阪) 参加してきました。

大阪、梅田スカイビルにて、10時から17時まで、下記のプログラムで行われました。
個人的には、Malcolm Grovesさんの「Delphi 2007の新機能とマイグレーションのポイント」の中での話で、 コードエディタの機能紹介が印象に残っています。 まとめられたら報告しますね。

第6回 CodeGearデベロッパーキャンプ(大阪) 

2007/08/28 梅田スカイビル タワーイースト 36F 梅田スカイルーム

10:00~11:00
【G1】ジェネラルセッション
「CodeGear製品ロードマップアップデート、新製品の概要」
CodeGear Malcolm Groves氏

11:15~12:15
【T2】Delphiテクニカルセッション
「知って得する!現役ヘルプデスクが答えるDelphiテクニカルエッセンス」
株式会社ミガロ RAD事業部 技術支援課 顧客サポート 吉原泰介氏

13:30~14:30
【T3】C++Builderテクニカルセッション
「C++Builder 2007の新機能と活用のポイント」
CodeGear 高橋智宏氏

14:45~15:45
【T4】Delphiテクニカルセッション
「Delphi 2007の新機能とマイグレーションのポイント」
CodeGear Malcolm Groves氏

16:00~17:00
【T5】Delphi for PHPテクニカルセッション
「Delphi for PHPによるビジュアルWebアプリケーション開発」
CodeGear 高橋智宏氏


ちなみに、会場に使われたこの建物は、1993年3月に竣工で、株式会社アトリエ・ファイ建築研究所、木村俊彦構造設計事務所、株式会社竹中工務店の設計です。空中庭園の施工方法がとてもユニークで当時、夕方のニュースでも取り上げられていましたね。(えっ、知らないって?)
私も竣工前に見学する機会があり、ワイヤリフトアップ工法について、現場で説明を受けました。当時の資料を見てみますと、西棟と東棟をそれぞれ外側に40ミリ倒して施工し、空中庭園を吊る事により、真っ直ぐになるとメモしています。又、空中庭園についても、130ミリのむくり(反り)をつけて施工しておき、仕上が施されると水平になるとメモしています。すごい技術ですね。 会場の天井も原広司先生の雲?のデザインが取り入れられていました。

|

☆TMS Advanced ToolBars & Menus v3.0.1.0での質問

次の点がよくわからなかったので、tmssoftware.comのサポートにメールしてみました。

1.なぜかTAdvOfficeStatusBarとTAdvOfficeStatusBarOfficeStylerのアイコンだけが、Delphiのデフォルトになっています。
→TMSMenusD2007.dprojと開いて、AdvOfficeStatusBarReg.pasを削除し、もう一度 AdvOfficeStatusBarReg.pas 追加します。そして再コンパイルして、インストールすると解決します。

2.AdvGrowButtonのAntiAliasプロパティは、日本語(SHIFT-JIS)の場合、デフォルトのaaClearTypeやaaAntiAliasだと文字化けします。
→aaNoneを使って下さい。
(やはりAntiAliasは使えないようです。)

私の拙い英文でも、すばやく回答を頂きました。(添付ファイルも届いていなかったのに)
ありがとうございました。>tmssoftware.comのご担当者さま

って、ここで日本語で書いてもね(笑)

|

☆TComboBoxExを使ってみる。

TComboBoxExのカテゴリが、なぜAdditionalじゃなくて、Win32なのか・・・疑問に思って調べてみたら、 TreeViewやListViewと同様、Windowsのラッパーなんですね。知らなかったです。

今まで、ComboBoxでインデントさせたり、イメージを表示するには、OnDrawItemで描画する必要がありましたが、 ComboBoxExを使うと簡単に表示できます。
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ComboBoxEx1: TComboBoxEx;
    ImageList1: TImageList;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//テスト用アイコン作成
procedure MakeTestIcon(ImageList: TImageList);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Height := 16;
    Bitmap.Width := 16;
    Bitmap.Canvas.Brush.Color := clRed;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
    Bitmap.Canvas.Brush.Color := clBlue;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
  finally
    Bitmap.Free;
  end;
end;

function GetCategory(Index: Integer): String;
begin
  case Index of
    0: Result := 'Guitar';
    1: Result := 'Synthesizer';
  else
       Result := 'Piano'
  end;
end;

procedure GetGuitarItem(ComboBoxEx: TComboBoxEx; Ident: Integer);
var
  ComboExItem: TComboExItem;
  I: Integer;
  S: String;
begin
  for I := 0 to 3 do
  begin
    case I of
      0: S := 'Ibanez';
      1: S := 'Gibson';
      2: S := 'Yamaha';
    else
      S := 'Fender';
    end;
    ComboExItem := ComboBoxEx.ItemsEx.Add;
    ComboExItem.Caption := S;
    ComboExItem.Indent := Ident;
    ComboExItem.ImageIndex := 1;
  end;
end;

procedure GetSynthesizerItem(ComboBoxEx: TComboBoxEx; Ident: Integer);
var
  ComboExItem: TComboExItem;
  I: Integer;
  S: String;
begin
  for I := 0 to 2 do
  begin
    case I of
      0: S := 'Roland';
      1: S := 'Yamaha';
    else
         S := 'KORG';
    end;
    ComboExItem := ComboBoxEx.ItemsEx.Add;
    ComboExItem.Caption := S;
    ComboExItem.Indent := Ident;
    ComboExItem.ImageIndex := 1;
  end;
end;

procedure GetPianoItem(ComboBoxEx: TComboBoxEx; Ident: Integer);
var
  ComboExItem: TComboExItem;
  I: Integer;
  S: String;
begin
  for I := 0 to 2 do
  begin
    case I of
      0: S := 'Steinway & Sons';
      1: S := 'Yamaha';
    else
         S := 'Kawai';
    end;
    ComboExItem := ComboBoxEx.ItemsEx.Add;
    ComboExItem.Caption := S;
    ComboExItem.Indent := Ident;
    ComboExItem.ImageIndex := 1;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  MakeTestIcon(ImageList1);
  ComboBoxEx1.Style := csExDropDownList;

  ComboBoxEx1.ItemsEx.Clear;
  ComboBoxEx1.ItemsEx.SortType := stNone;

  for I := 0 to 2 do
  begin
    ComboBoxEx1.ItemsEx.AddItem(GetCategory(I), 0, 0, -1, 0, nil);
    case I of
      0: GetGuitarItem(ComboBoxEx1,1);
      1: GetSynthesizerItem(ComboBoxEx1,1);
    else
      GetPianoItem(ComboBoxEx1,1);
    end;
  end;
  ComboBoxEx1.ItemIndex := 0;
end;

end.

実行するとこんな感じです。

Comboboxex

又、ComboBoxExでは、ソートもテキストだけでなく、SortTypeでstDataを指定し、ItemsExが保持しているポインタで ソートすることも可能です。


参考
MSDN
ComboBoxEx Control Reference

|

■カウンターをつけてみました。

ココログサポートによると2006年5月18日からのデータとの事です。アクセス解析の数値よりかなり低いのですが、アクセス解析システムがそれぞれ違うので、そういうものらしいです。
どちらにしてもたくさんアクセスして頂いてうれしいです\(^O^)/

|

■TMS Advanced ToolBars & Menusアップデート

TMS Advanced ToolBars & Menus が v3.0.0.0 から v3.0.1.0 にアップデートされました。

メーカーホームページからの引用です。

What's new:
in v3.0.1.0
- New : Autosizing statusbar panels
- New : Configurable stretchpanel in statusbar
- New : design time resizing of statusbar panels
- New : Notes text with NotesFont in TAdvGlowButton
- New : Notes text with NotesFont in TAdvMainMenu, TAdvPopupMenu
- New : Windows Vista color style for TAdvMainMenu, TAdvPopupMenu
- New : Antialiased menu text drawing
- Improved : multimonitor support for TAdvToolBarForm
- Improved : maximize state handling of TAdvToolBarForm
- Improved : painting of TAdvGlowButton, TAdvContainer
- Improved : statusbar painting
- Improved : Windows system menu handling on TAdvToolBarPager Caption
- Various smaller fixes & improvements

tmssoftware.com

|

☆TDockTabSetを使ってみる。

Delphiを使っておられる方なら、新コンポーネントの仕様通りに動作しない点やメモリ違反に 悩まされた方も多いと思います。私、昔は率先して新コンポーネント採用してきた方なのですが、 ひどい経験を積み重ねる内に、新コンポーネントの採用は敬遠するようになっていました(笑)

このTDockTabSetも同様で、全く使っていなかったのですが、 Delphi2005で登場してから2世代経って、問題なく動作しているようですね。 (当初から問題なかったのかどうかは知りませんが)

CodeGearのサイトに左側にTDockTabSetを表示するサンプルがあるのですが、 今回は右側に表示させてみました。(ほとんどサンプルのままですけど)

Memo1、Splitter1、Panel1、DockTabSet1を図のように配置します。

Tdocktabset1

次にコードです。
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Splitter1: TSplitter;
    Panel1: TPanel;
    DockTabSet1: TDockTabSet;
    procedure FormShow(Sender: TObject);
    procedure DockTabSet1TabRemoved(Sender: TObject);
    procedure DockTabSet1DockDrop(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer);
    procedure Panel1DockDrop(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer);
    procedure Panel1DockOver(Sender: TObject; Source: TDragDockObject; X,
      Y: Integer; State: TDragState; var Accept: Boolean);
    procedure Panel1UnDock(Sender: TObject; Client: TControl;
      NewTarget: TWinControl; var Allow: Boolean);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses Unit2, Unit3,
   ComCtrls; // ←tpLeftが宣言されています。

{$R *.dfm}

const
  FormWidth = 180;

// 初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Panel1の設定
  Panel1.DockSite := True;
  Panel1.Width := 0;

  // DockTabSet1の設定
  DockTabSet1.Width := 25;
  DockTabSet1.DockSite := False;
  DockTabSet1.ShrinkToFit := True;
  DockTabSet1.Style := tsModernTabs;
  DockTabSet1.DestinationDockSite := Panel1;
  DockTabSet1.TabPosition := tpLeft;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  Panel1.Caption := '';

  // フォームの幅
  Form2.Width := FormWidth;
  Form3.Width := FormWidth;

  // フォームが一瞬表示されてしまうので、画面の外に表示させます。
  Form2.Left := -2000;
  Form3.Left := -2000;

  // フォームの表示
  Form2.Show;
  Form3.Show;

  Form2.ManualDock(Panel1);


  // 収納して表示
  Form3.ManualDock(DockTabSet1);
  //Form3.ManualDock(Panel1, nil, alBottom);
end;

procedure TForm1.DockTabSet1DockDrop(Sender: TObject; Source: TDragDockObject;
  X, Y: Integer);
begin
  DockTabSet1.Visible := True;
  // 次の処理をしないとPanel1の左側に表示される
  DockTabSet1.Left := Form1.Width;
end;

procedure TForm1.DockTabSet1TabRemoved(Sender: TObject);
begin
  DockTabSet1.Visible := DockTabSet1.Tabs.Count > 0;
end;

procedure TForm1.Panel1DockDrop(Sender: TObject; Source: TDragDockObject; X,
  Y: Integer);
begin
  if Panel1.Width = 0 then
    Panel1.Width := FormWidth;
  Splitter1.Visible := True;
  Splitter1.Left :=  Self.Width -(Panel1.Width + DockTabSet1.Width)-1;
  DockTabSet1.Left := Form1.Width;
end;

procedure TForm1.Panel1DockOver(Sender: TObject; Source: TDragDockObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
var
  Rect: TRect;
begin
  Accept := (Source.Control = Form2) or (Source.Control = Form3);
  if Accept then
  begin
    Rect.TopLeft := Panel1.ClientToScreen(Point(0, 0));
    Rect.BottomRight := Panel1.ClientToScreen(Point(FormWidth, Panel1.Height));
    Source.DockRect := Rect;
  end;
end;

procedure TForm1.Panel1UnDock(Sender: TObject; Client: TControl;
  NewTarget: TWinControl; var Allow: Boolean);
begin
  if Panel1.DockClientCount = 1 then
  begin
    Panel1.Width := 0;
    Splitter1.Visible := False;
  end;
end;

end.

Unit2には、前回作成したTCategoryButtonsを使ったフォーム、 Unit3には、ListBoxを一つ配置したものを使いました。
// Unit2, Unit3の設定
  BorderStyle := bsSizeToolWin;
  DragKind := dkDock;
  DragMode := dmAutomatic;


実行するとこんな感じです。

Tdocktabset3

Tdocktabset2


DockTabSet1のStyleをtsOwnerDrawにすれば、タブにアイコンを描画することができました。
しかしながら、フォームのキャプション部分にはできませんでした。DelphiのIDEでは、 表示されているので何か方法があると思うのですけどね。


参考にしたサイト

CodeGear
Using the TDockTabSet component by Jeremy North

|

☆TCategoryButtonsを使ってみる。

フォームにTCategoryButtonsとアイコンを一つ設定したImageListを配置します。
ButtonCategoryやButtonItemの設定は、オブジェクトインスペクタから簡単にできますが、 ここではプログラムで処理してみます。

まずは、デフォルトのTCategoryButtonsに項目を設定してみます。
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    CategoryButtons1: TCategoryButtons;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function GetCategory(Index: Integer): String;
begin
  case Index of
    0: Result := 'Guitar';
    1: Result := 'Synthesizer';
  else
       Result := 'Piano'
  end;
end;

procedure GetSynthesizerItem(ButtonCategory: TButtonCategory);
var
  ButtonItem: TButtonItem;
  I: Integer;
begin
  for I := 0 to 2 do
  begin
  ButtonItem := ButtonCategory.Items.Add;
  ButtonItem.ImageIndex := 0;
    case I of
      0: ButtonItem.Caption := 'Roland';
      1: ButtonItem.Caption := 'Yamaha';
    else
         ButtonItem.Caption := 'KORG';
    end;
  end;
end;

procedure GetGuitarItem(ButtonCategory: TButtonCategory);
var
  ButtonItem: TButtonItem;
  I: Integer;
begin
  for I := 0 to 3 do
  begin
  ButtonItem := ButtonCategory.Items.Add;
  ButtonItem.ImageIndex := 0;
    case I of
      0: ButtonItem.Caption := 'Ibanez';
      1: ButtonItem.Caption := 'Gibson';
      2: ButtonItem.Caption := 'Yamaha';
    else
      ButtonItem.Caption := 'Fender';
    end;
  end;
end;

procedure GetPianoItem(ButtonCategory: TButtonCategory);
var
  ButtonItem: TButtonItem;
  I: Integer;
begin
  for I := 0 to 2 do
  begin
  ButtonItem := ButtonCategory.Items.Add;
  ButtonItem.ImageIndex := 0;
    case I of
      0: ButtonItem.Caption := 'Steinway & Sons';
      1: ButtonItem.Caption := 'Yamaha';
    else
         ButtonItem.Caption := 'Kawai';
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  ButtonCategory: TButtonCategory;
begin
  // CategoryButtons1の設定
  CategoryButtons1.Align := alClient;
  CategoryButtons1.Images := ImageList1;

  // ButtonCategory、ButtonItemの設定
  for I := 0 to 2 do
  begin
    ButtonCategory := CategoryButtons1.Categories.Add;
    ButtonCategory.Caption := GetCategory(I);
    case I of
      0: GetGuitarItem(ButtonCategory);
      1: GetSynthesizerItem(ButtonCategory);
      2: GetPianoItem(ButtonCategory);
    end;
  end;
end;

end.


これを実行するとこのようになります。

Tcategorybuttons1

なんだか、これでは今ひとつ使い道がなさそうな気がします。
そこで、Delphiのツールパレットのような表示にしてみます。

FormのOnCreateで、CategoryButtons1のプロパティの設定を行います。
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  ButtonCategory: TButtonCategory;
begin
  // CategoryButtons1の設定
  CategoryButtons1.Align := alClient;
  CategoryButtons1.Images := ImageList1;
  CategoryButtons1.BorderStyle := bsNone;
  CategoryButtons1.AlignWithMargins := True;
  CategoryButtons1.ButtonFlow := cbfVertical;
  CategoryButtons1.ButtonOptions :=
    CategoryButtons1.ButtonOptions +
      [boFullSize, boBoldCaptions, boUsePlusMinus, boCaptionOnlyBorder] -
      [boVerticalCategoryCaptions];  
  
  // ButtonCategory、ButtonItemの設定
  for I := 0 to 2 do
  begin
    ButtonCategory := CategoryButtons1.Categories.Add;
    ButtonCategory.Caption := GetCategory(I);
    case I of
      0: GetGuitarItem(ButtonCategory);
      1: GetSynthesizerItem(ButtonCategory);
      2: GetPianoItem(ButtonCategory);
    end;
  end;
end;

Tcategorybuttons2


これでいい感じになりました。

|

■Delphi 2007 初めての感想

本体及びコンポーネントのインストールがようやく終わりました。
今更ながら、Delphi 2007 を使ってみた感想です。

今日一日作業していましたが、、Delphi 2006 の時のようにメモリが異常に増えて動作が遅くなることもなく、快適に使えています。

コンポーネントは、Delphi 2006 とほとんど変わっていないようですね。
ということは、Delphi 2005 で登場したDockTabSetやCategoryButtons辺りもそろそろ安定して使える頃かも知れません。 これらのコンポーネントについては、又、レポートしたいと思います。
(Delphi2005、Delphi2006で、とても新しいコンポーネントを試そうなんて気にならなかったです)

ボタン等も標準でXPスタイルになっています。
もっとも、Vista対応なので、いまさらXPスタイル云々なんて書かれても・・・って感じでしょうけど(^^;
(Vistaの環境がないので、お許しを・・・。)

あと、噂のヘルプですが、本当に使い物にならないですね。
DockTabSet1のShrinkToFitでF1キーを押すと、
 説明
 TTabSet クラスのメンバー: ShrinkToFit
だって・・・悲しすぎて笑えます。早くなんとかしてほしいです。

全体としては、Delphi 2006 とほとんど変わりませんが、動作も軽く、安定感は全く違うようです。
まあ、反対に Delphi 2006 とほとんど変わらないというのなら、このレベルにしてから販売すべきだと言いたいですけどね。

今まで、仕事上で文字列の加工が必要になったときとか、ちょっとしたプログラムはDelphi7で作っていましたが、今後は Delphi 2007 が活躍することになりそうです。

|

■Delphi 2007 届きました。

会社から帰ると Delphi 2007 for Win32 が届いていました。

早速インストール&アップデートして、もうこんな時間になってしまいました。(現在、午前3時30分)
明日、会社に遅刻するといけないので、コンポーネントのインストールは、また今度にします。
ということで後れ馳せながら、ようやくDelphi 2007使いの仲間入りです。

これからの記事は、Delphi 2007 for Win32 Professional、Windows XP Professional SP2、Internet Explorer 7という環境で試していきますね。(OSがVistaでないところが悲しいけど)

|

☆TreeViewにチェックボックスを表示する。

あまり使うことがないのですが、TreeViewにチェックボックスを表示させてみました。
チェックボックスを利用するには、ウィンドウスタイルの設定だけでいいと思うのですが、同じチェックボックスを何度かクリックしたときの応答がいまいちのような気がします。この点については、よくわかりませんでした。
今回、チェックボックスがクリックされたタイミングで、設定状態を取得する処理も書いてみました。

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    StatusBar1: TStatusBar;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Commctrl;

const
  TVIS_CHECKED  = $2000;

// ウィンドウスタイルの設定---チェックボックスを設定します。
procedure TForm1.FormCreate(Sender: TObject);
var
  WindowStyles: Integer;
  I: Integer;
begin
  WindowStyles := GetWindowLong(TreeView1.Handle, GWL_STYLE);
  SetWindowLong(TreeView1.Handle, GWL_STYLE, WindowStyles or TVS_CHECKBOXES);
  StatusBar1.SimplePanel := True;

  // サンプルデータ
  for I := 1 to 9 do
    TreeView1.Items.Add(nil, 'Item' + IntToStr(I));
end;

// チェックボックスがクリックされたことを取得します。
procedure TForm1.TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  lpht: TTVHitTestInfo;
  TvItem: TTVItem;
  Node: TTreeNode;
  Check: Boolean;
begin
  // クリックした位置のノードを取得します。
  Node := TreeView1.GetNodeAt(X, Y);
  if Node = nil then Exit;

  // クリックした位置がチェックボックス上からどうかを調べます。
  lpht.pt.x := X;
  lpht.pt.y := Y;
  TreeView_HitTest(TreeView1.Handle, lpht);

  if lpht.flags = TVHT_ONITEMSTATEICON then
  begin
    // チェックボックス上をクリックされた場合の処理

    // チェックされているかどうかを調べます。
    TvItem.Mask := TVIF_STATE;
    TvItem.hItem := Node.ItemId;
    TreeView_GetItem(TreeView1.Handle, TvItem);
    Check := (TvItem.State and TVIS_CHECKED) > 0;

    if Check then
      StatusBar1.SimpleText := 'Checked'
    else
      StatusBar1.SimpleText := 'Unchecked';

    // チェックボックスをクリックした時に、ノードを移動させます。
    TreeView1.Selected := Node;
  end;
end;

end.

参考にしたサイト

MSDN
Tree-View Controls

Microsoft サポートオンライン
[HOWTO] TreeView コントロールでユーザーがチェック ボックスをクリックしたときに通知を受け取る方法

|

■ヘルプについて

CodeGear DEE ELLINGさんのブログでヘルプの問題が取り上げられています。
(彼女はCodeGear documentation groupのマネージャーです。)

私は、新しいヘルプは表示が遅い上、必要な情報がほとんど表示されないので、Delphi7のものを使っています。 彼のブログの中でも多くのユーザーが古いヘルプを使っていると書いてるので、国を問わず、みなさん同じなんですね。

なぜこんなヘルプになってしまったのかということで、「XMLから変換する時にサンプルコードが脱落した」等、問題点を挙げておられます。しかし、ここで言いたいのは、そんなことぐらい確認してから製品として出荷すべきじゃないの?ってことです。次期製品ではきちんとしたものを、というのではなく、Delphi2007のヘルプをぜひ刷新して頂きたいものです。

※Delphi2007・・・まだ手元に届いていないんですが(笑)

ヘルプの件では、DEKOさんが独自に分析された問題点を、CodeGearの日本のニュースグループで挙げておられます。 私にはそんな技量はないので、いつもすごいなと感心し、又、Delphiユーザーとして感謝しています。書籍もなかなか手に入らない現状なので、まともなヘルプに成長することを願っています。

Dee Ellingさん
Dee Elling
CodeGear Information Development

Old Help and New

DEKOさん
DEKOのアヤシいお部屋。

CodeGear
日本語ニュースグループディレクトリ

|

☆FirebirdのBackup&Restore

TIBBackupServiceとTIBRestoreServiceを使ってFirebirdのデータベースファイルの バックアップとリストアを試してみました。
(本当はInterBaseなんだけど、Firebirdでも使えるというのが正しい表現かも知れませんね。)
フォームにコンポーネントを配置する方法だと、なぜか時々エラーが表示されるので、インスタンスを作成しています。

まずは定義及び設定部分です。
uses
  IBServices;

var
  DatabaseName, BackupFile: String;
  UserName, PW: String;

procedure TForm1.FormCreate(Sender: TObject);
var
  Path: String;
begin
  // c:\test\test.gdbをC:\test\bakにバックアップする場合
  Path := 'C:\Test\';
  DatabaseName := Path + 'test.gdb';
  BackupFile := Path + 'bak\test.bak';
  UserName := 'USERNAME';
  PW := '********'
end;

バックアップ処理です。
// バックアップ Backup
procedure TForm1.Button1Click(Sender: TObject);
var
  IBBackupService1: TIBBackupService;
begin
  IBBackupService1:= TIBBackupService.Create(nil);
  try
    IBBackupService1.LoginPrompt := False;
    IBBackupService1.Protocol := TCP;
    IBBackupService1.ServerName := 'localhost';
    IBBackupService1.Params.Add('user_name=' + UserName);
    IBBackupService1.Params.Add('password=' + PW);
    IBBackupService1.DatabaseName := DatabaseName;
    IBBackupService1.BackupFile.Add(BackupFile);

    IBBackupService1.Active := True;
    try
      IBBackupService1.ServiceStart;
    finally
      IBBackupService1.Active := False;
    end;
  finally
    IBBackupService1.Free;
  end;
end;

リストア処理です。
// リストア Restore
procedure TForm1.Button2Click(Sender: TObject);
var
IBRestoreService1: TIBRestoreService;
begin
  IBRestoreService1:= TIBRestoreService.Create(nil);
  try
    IBRestoreService1.LoginPrompt := False;
    IBRestoreService1.Protocol := TCP;
    IBRestoreService1.ServerName := 'Localhost';
    IBRestoreService1.Params.Add('user_name=' + UserName);
    IBRestoreService1.Params.Add('password=' + PW);
    IBRestoreService1.DatabaseName.Add(DatabaseName);
    IBRestoreService1.BackupFile.Add(BackupFile);
    IBRestoreService1.Options := IBRestoreService1.Options + [Replace];
    IBRestoreService1.Active := True;
    try
      IBRestoreService1.ServiceStart;
    finally
      IBRestoreService1.Active := False;
    end;
  finally
    IBRestoreService1.Free;
  end;
end;

DelphiはBDS2006PRO、Firebirdは、Firebird-1.5.4.4910-0で試しました。
エラー処理は行っていません。

重要
本稼動しているデータベースに試すことは避けて下さい。
あくまで、サンプルなので、何が起こるかわかりません。

|

■Visual Studio 2008 Beta2 日本語版

「Visual Studio 2008 Beta2 日本語版、提供開始!」というMicrosoftからメールが届きました。.NET Framework 3.5 への対応だけでなく・・・

いえいえ、私が反応したのはそこではなく、その案内の署名です。

マイクロソフト株式会社
デベロッパー&プラットフォーム統括本部
デベロッパー エバンジェリスト
大野 元久

元ボーランドの方であり、Delphi Q&A 150選の著者である大野さんですよね。
さすがにできる人はどこでも活躍の場があるんですね(^^;

|

☆TListのSortの使用方法

TListのSortを使うと簡単に並び替えができます。
本と著者を管理する簡単なクラスを作って試してみます。

unit TListSortSampleClass;

interface

uses
  Windows, SysUtils, Classes;

type
  TMyBook = class(TObject)
  private
    FBookName: String;
    FAuthor: String;
  public
    constructor Create(BookName, Author: String);
  published
    property BookName: String read FBookName write FBookName;
    property Author: String read FAuthor write FAuthor;
  end;

  TMyBookList = class(TObject)
  private
    FList: TList;                                       { リスト }
    function GetData(Index: Integer): TMyBook;          { 読み込み }
    procedure SetData(Index: Integer; MyBook: TMyBook); { 書き込み }
    function GetCount: Integer;                         { リストのカウント数 }
  protected
    procedure Error;                                    { エラーの表示 }
  public
    constructor Create;                                 { 生成 }
    destructor Destroy; override;                       { 破棄 }
    procedure Clear;                                    { 消去 }
    function Add(MyBook: TMyBook): Integer;             { 追加 }
    procedure Sort;                                     { 並べ替え }
    property Items[Index: Integer]: TMyBook read GetData write SetData; default;
  published
    property Count: Integer read GetCount;              { リストのカウント数 }
  end;

implementation

{ TMyBook }

constructor TMyBook.Create(BookName, Author: String);
begin
  inherited Create;
  FBookName:= BookName;
  FAuthor := Author;
end;

{ TMyBookList }

// 生成
constructor TMyBookList.Create;
begin
  FList := TList.Create;
end;

// 破棄
destructor TMyBookList.Destroy;
begin
  Clear;
  FList.Free;
  inherited Destroy;
end;

// 消去
procedure TMyBookList.Clear;
var
  I: Integer;
begin
  for I := 0 to FList.Count -1 do
    TMyBook(FList[I]).Free;
  FList.Clear;
end;

// 追加
function TMyBookList.Add(MyBook: TMyBook): Integer;
begin
  Result := FList.Add(MyBook);
end;

// エラー処理
procedure TMyBookList.Error;
begin
  raise Exception.Create('インデックスがリストの範囲を超えています');
end;

// リストからの取得
function TMyBookList.GetData(Index: Integer): TMyBook;
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  Result := TMyBook(FList[Index]);
end;

// リストへの設定
procedure TMyBookList.SetData(Index: Integer; MyBook: TMyBook);
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  FList[Index] := MyBook;
end;

// リストのカウント数
function TMyBookList.GetCount: Integer;
begin
  Result := FList.Count;
end;

ソート部分のプログラムです。
function ListSortCompare(Item1,Item2: Pointer): Integer; 
var
  S1, S2: String;
begin
  S1 := TMyBook(Item1).BookName;
  S2 := TMyBook(Item2).BookName;
  Result := AnsiCompareText(S1, S2);
end;

procedure TMyBookList.Sort;
begin
  FList.Sort(@ListSortCompare);
end;

end.

上記のリストを利用するサンプルです。
適当にデータを入力して、試してみて下さい。
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Edit1: TEdit;  // 本の名前
    Edit2: TEdit;  // 著者
    Memo1: TMemo;
    Memo2: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    MyBookList: TMyBookList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyBookList:= TMyBookList.Create;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyBookList.Add(TMyBook.Create(Edit1.Text, Edit2.Text));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  I: Integer;
begin
  // 並べ替え前のデータ
  for I := 0 to MyBookList.Count - 1 do
    Memo1.Lines.Add(MyBookList[I].BookName);

  // 並べ替え後のデータ
  MyBookList.Sort;
  for I := 0 to MyBookList.Count - 1 do
    Memo2.Lines.Add(MyBookList[I].BookName);
end;

end.

Memo1には入力順に、Memo2には、並べ替えされたデータが設定されていると思います。
わざわざクイックソートのプログラムを書かなくても簡単に並べ替えができるので便利ですね。

|

■遅いと思ったら・・・。

8/9にFAXで注文したはずのDelphi2007ですが、未だ届かないのでCodeGearに電話で確認してみました。発送済みという返事を期待していたのですが、オーダーを受けてないって言われました。

そう言えば私の手元にあるバージョンアップの案内は、CodeGearが新宿にあった頃の電話番号だったので、昔のFAX番号に送っていたようです。では、私のオーダーはどこに行ったのでしょう?

カードの番号も書いてあるためちょっと心配だったので、白紙のまま昔の番号に送って、CodeGearに電話で確認してみました。きちんと空白の紙が届いているということで問題なさそうでした。ということは、あの時のオーダーはどこに行ったのでしょう?カードの番号を書いているため何度もFAX番号を確認しましたし、送信エラーが出ていないことも確認したのに・・・不思議です。

|

☆TabControlのDrag&Drop

TabControlのDrag&Dropのサンプルです。(自コントロール内の処理としています)
TabControlのTabsは、TStringsなので移動はとても簡単です。

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    TabControl1: TTabControl;
    procedure TabControl1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure TabControl1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure TabControl1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  // タブ名の設定
  for I := 1 to 10 do
    TabControl1.Tabs.Add(IntToStr(I));
end;

procedure TForm1.TabControl1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  // ちょっとややこしいですけど、自分自身の間でしか許可しない設定です。 
  Accept := ((Sender is TTabControl) and (Source is TTabControl)) and
    ((Sender as TTabControl) = TabControl1) and 
    (TabControl1.TabIndex <> TabControl1.IndexOfTabAt(X,Y));
end;

procedure TForm1.TabControl1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) then
    TabControl1.BeginDrag(False, 5);
end;

procedure TForm1.TabControl1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  DstIndex, SrcIndex: Integer;
begin
  SrcIndex := TabControl1.IndexOfTabAt(X, Y);
  DstIndex := TabControl1.TabIndex;
  TabControl1.Tabs.Move(DstIndex,SrcIndex);
end;

end.

|

☆ListViewの項目並び替え

ListViewのカラムクリック時に項目の並び替えをするサンプルです。

// ソート用コールバック関数
function Sort(Item1,Item2: TListItem; Index: Integer): Integer; stdcall;
var
  S1, S2: String;
begin
  if Index = 0 then
    begin
      S1 := Item1.Caption;
      S2 := Item2.Caption;
    end
  else
    begin
      S1 := Item1.SubItems[Index-1];
      S2 := Item2.SubItems[Index-1];
    end;
  // 昇順  ascending order
  Result := AnsiCompareText(S1, S2);
  // 降順  descending order
  //Result := -Result;
end;     

procedure TForm1.ListView1ColumnClick(Sender: TObject;
  Column: TListColumn);
begin
  ListView1.CustomSort(@Sort, Column.Index);
end;

|

☆Microsoft SQL Server 2005 Express Editionへの接続方法

検索が多い項目なので、ちょっとだけ丁寧に説明したいと思います。
データベースやテーブルの作成については、Delphiで接続する以前の問題なので、ここでは省略します。 SQLが苦手な人もSQL Server Management Studio Expressを使えば簡単に作成できますので、まずはデータベースを作っておいて下さい。又、接続にはADOを使っています。(私のはPro版なので、DBExpress用ドライバーがないからです)

(1) フォームにADOConnectionを配置し、それをダブルクリックする。
   (右クリックしてポップアップメニューから ConnectionStringの編集を選択してもいいです。)

(2) 接続文字列を使うにチェックを入れて[ビルド]を押す。

Sql2005_1


(3) SQL Native Clientを選択して[次へ]を押す。

Sql2005_2_3


(4) 接続タブを以下のように設定します。

Sql2005_3

1. データソースにlocalhost\sqlexpressと入力する。 2. 「Windows NT の統合セキュリティを使用する」にチェックを入れる。 (Microsoft SQL Server 2005 Express Editonを推奨設定でインストールした場合) 3. 使用する初期カタログを入力します コンボボックスから、データベースを選択する。

(5) [接続のテスト]ボタンを押して、接続できるかどうかをチェックします。

Sql2005_4


(6) オブジェクトインスペクタのConnectedをTrueにしてみましょう。

Sql2005_5

パスワードを設定していない場合には、そのまま[OK]ボタンを押すだけです。 問題なくTRUEになれば、成功です。

フォームにADOTableDatasourceDBGridを配置して、確認してみます。
  1. ADOTable1ConnectionADOConnection1 を設定する。
  2. ADOTable1 TableName Table を設定する。
  3. DataSource1 DataSet ADOTable1 を設定する。
  4. DBGrid1 DataSource DataSource1 を設定する。
  5. ADOTable1 Active True にする。

ちゃんと表示されたかな?

参考までに
Microsoft SQL Server 2005 Express Editionは、デフォルトのインストールでは、ローカル接続のみとなっているようです。 リモート接続したい場合には、SQL Server セキュリティ構成を実行して、SQLEXPRESS->Database Engine->リモート接続で、「ローカル接続のみ」から「ローカル接続およびリモート接続」に変更する必要があります。 データソースで、サーバー名を設定しても接続できない場合は、ファイヤーウォールよりも先に、この点をチェックしてみて下さい。
と書きつつも、私はまだローカル接続でしか使っていませんけど(笑)
あっ、XQueryを試す場合には、DataTypeCompatibility=80とする必要があるみたいです。Microsoftのホームページのどこかに書いていた記憶があります。

|

■TMS Advanced ToolBars & Menusアップデート

TMS Advanced ToolBars & Menus が v2.9.1.0 から v3.0.0.0 にアップデートされました。

メーカーホームページからの引用です。

What's new:
in v3.0.0.0
- New : Notes text with NotesFont in TAdvGlowButton
- New : Notes text with NotesFont in TAdvMainMenu, TAdvPopupMenu
- New : Windows Vista color style for TAdvMainMenu, TAdvPopupMenu
- New : Antialiased menu text drawing
- Improved : multimonitor support for TAdvToolBarForm
- Improved : maximize state handling of TAdvToolBarForm
- Improved : painting of TAdvGlowButton, TAdvContainer
- Improved : statusbar painting
- Improved : Windows system menu handling on TAdvToolBarPager Caption
- Various smaller fixes & improvements

tmssoftware.com

|

☆タブのOwnerDraw

StyleがtsTabsなPageControlの場合、 タブをグラデーションさせると柔らかい印象になり、ちょっといい感じです。

uses
  GraphUtil;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PageControl1.OwnerDraw := True;
end;

procedure TForm1.PageControl1DrawTab(Control: TCustomTabControl;
  TabIndex: Integer; const Rect: TRect; Active: Boolean);
begin
  // 色は適当に・・・。
  GradientFillCanvas(Control.Canvas,clBtnFace,$00CAC4C6,
    Rect,gdVertical);
  SetBkMode(Control.Canvas.Handle, TRANSPARENT);
  if Active then
    Control.Canvas.Textout(Rect.Left+7, Rect.Top+5,
      PageControl1.Pages[TabIndex].Caption)
  else
    Control.Canvas.Textout(Rect.Left+4, Rect.Top,
      PageControl1.Pages[TabIndex].Caption);
end;

|

☆ListViewのヘッダーの色を変える。

ヘッダー(ViewStyleがvsReportのカラム部分)の色を変えたいと思い、 いろいろ探してみましたが、サンプルを見つけることができませんでした。 仕方がないので、いろいろと試行錯誤した結果、ほぼ期待通りの動作になったので、 書いておきますね。

※ListViewとして実際に使っていないので、いろいろと問題があるかも知れません。又、カラムをクリックした時、文字の再描画ができていないので、文字が沈まないという雑な作りです(^^;

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
  private
    Header_Handle: HWND;
    procedure DrawColumns;
  public
  end;

  function NewHeaderWndProc(hWnd: HWND; Msg: UINT;
    WParam: wPARAM; lParam: LPARAM): LRESULT;  stdcall;

var
  Form1: TForm1;
  FOldHeaderWndProc: TFNWndProc;

implementation

{$R *.dfm}

uses
  ComObj, CommCtrl, GraphUtil;

function NewHeaderWndProc(hWnd: HWND; Msg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
  Result := CallWindowProc(FOldHeaderWndProc, hWnd, Msg, wParam, lParam);
  case Msg of
    WM_PAINT: Form1.DrawColumns;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Header_Handle := ListView_GetHeader(ListView1.Handle);

  FOldHeaderWndProc := TFNWndProc(SetWindowLong(Header_Handle,
    GWL_WNDPROC, Longint(@NewHeaderWndProc)));
end;

procedure TForm1.DrawColumns;
var
  Header_Rect, Column_Rect, OutofColumns_Rect: TRect;
  I, X, W1, W2, CW: Integer;
  Canvas: TCanvas;
  Column: TListColumn;
  dwFormat: DWORD;
begin
  if Header_Handle = 0 then Exit;

  // Header領域を取得します。
  Windows.GetClientRect(Header_Handle, Header_Rect);

  // 境界の幅を取得します。
  W1 := GetSystemMetrics(SM_CXBORDER);
  W2 := GetSystemMetrics(SM_CXFIXEDFRAME) - W1;

  Canvas := TCanvas.Create;
  try
    Canvas.Handle := GetDC(Header_Handle);

    // カラムの描画
    X := 0;
    for I := 0 to ListView1.Columns.Count - 1 do
    begin
      Column := ListView1.Columns[I];
      CW := ListView1.Columns[I].Width;

      // 背景の描画
      Column_Rect := Rect(X+W1, W1, X+CW-W2, Header_Rect.Bottom-W2);
      GradientFillCanvas(Canvas, clWhite, $00CAC4C6, Column_Rect, gdVertical);
      SetBkMode(Canvas.Handle, TRANSPARENT);

      // 文字の描画
      Canvas.Font.Color := clBlack;

      dwFormat := DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS;
      case Column.Alignment of
        taRightJustify: dwFormat := dwFormat or DT_RIGHT;
        taCenter      : dwFormat := dwFormat or DT_CENTER;
      else
        dwFormat := dwFormat or DT_LEFT;
      end;
      InflateRect(Column_Rect, -4, 0);
      DrawText(Canvas.Handle, PChar(Column.Caption), -1, Column_Rect, dwFormat);
      X := X + CW;
    end;

    // Columnより右の部分の描画
    OutofColumns_Rect := Rect(X+W1, W1, Header_Rect.Right, Header_Rect.Bottom-W2);
    GradientFillCanvas(Canvas, clWhite, $00CAC4C6, OutofColumns_Rect, gdVertical);
  finally
    Canvas.Free;
  end;
end;

end.

|

■Delphi2007 Update2ができない。

会社の先輩がC++Builder2007を使っていて、「Update2は最悪!」と言っていました。
なんかダイアログが出てきて、インストールできないという話でした。
Delphi2007でも同じらしく、対応策が公表されていますね。

CodeGear
RAD Studio Update2を実行すると「オリジナルのセットアップファイルが保存されている場所」のダイアログが表示される

先日、注文したDelphi2007はまだ届きません。
お盆休みに遊ぼうと思ってたのに・・・残念。
注文時期が悪すぎたかな?

|

☆ListViewの背景に画像を描画する。

DelphiFAQ.comを見ていたら、(ちょっと前の記事ですが)ListViewの背景に画像を描画する方法が載っていたので、早速試してみました。 DelphiFAQ.comでは、派生コンポーネントを使う方法で紹介されていましたが、こちらではサブクラスを使って実現したいと思います。 サブクラスには、前にも使わせて頂いたMr.XRAYさんところにあるHalbowさんのサブクラス化コンポーネントを使わせて頂きました。

注意すべき点は次の通りです。
1.ComObjを忘れない。
  忘れてもコンパイルできますが、実行時にはエラーになります。
  こういうエラーは原因がなかなかわからないですしね。
  って、実は私自身がはまっていました(笑)
2.ListView1.DoubleBuffered を設定する。
  ちらつきを抑えることができます。


unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
  private
    SubClass1: TSubClass;
    procedure ImageDraw;
  public
    procedure SubClass1MessageAfter(Sender: TObject;
      var Message: TMessage);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// ComObjがなくてもコンパイルはできますが、
// 実行時にエラーになります。
uses
  ComObj, CommCtrl;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // これを設定しないとちらつきます。
  ListView1.DoubleBuffered := True;

  // サブクラスの設定
  SubClass1:= TSubClass.Create(Self);
  SubClass1.TargetControl := ListView1;
  SubClass1.OnMessageAfter := SubClass1MessageAfter;

  // 背景画像の描画
  ImageDraw;
end;

procedure TForm1.SubClass1MessageAfter(Sender: TObject;
  var Message: TMessage);
begin
  if Message.Msg = WM_ERASEBKGND then
    ListView1.DefaultHandler(Message);
end;

procedure TForm1.ImageDraw;
var
  LVBKImage: TLVBKImage;
  S: String;
begin
  S := 'c:\windows\サポテック織り.bmp';

  LVBKImage.ulFlags := LVBKIF_SOURCE_URL or LVBKIF_STYLE_TILE;
  LVBKImage.hbm := 0;
  LVBKImage.pszImage := PChar(S);
  LVBKImage.cchImageMax := 0;
  LVBKImage.xOffsetPercent := 0;
  LVBKImage.yOffsetPercent := 0;
  // マクロを使ってみます。
  ListView_SetTextBkColor(ListView1.Handle, CLR_NONE);
  ListView_SetBkImage(ListView1.Handle, @LVBKImage);
end;

end.


DelphiFAQ.com
Putting a background image on a ListView (Delphi 7)

Delphi Library [Mr.XRAY]
サブクラス化コンポーネント

MSDN
ListView_SetBkImage Macro
LVBKIMAGE Structure
ListView_SetTextBkColor Macro

|

☆ListViewのDrag&Drop

ViewStyleがvsReportに設定されたListViewのDrag&Dropの処理です。
ListViewは、Ctrl+左クリックで飛び飛び選択?Shift+左クリックで範囲選択?を行えますね。
ということで次の2点を考慮した処理にしたいと思います。

1.複数選択されたアイテムを扱える。
2.他のListViewへのDrag&Dropができる。(同じアイテムを表示している場合)
  ※自アプリ内に限ります。他アプリではOLE Drag&Drop処理が必要です。

通常DropTargetがnilの場合には、処理させないことが多いんじゃないかと思います。しかし今回は、他のListViewへのDrag&Dropも考慮するので、nilの場合には最後に追加する形で処理させたいと思います。

ListView1DragOver、ListView1DragDropは、ListView1、ListView2共に設定しておいて下さい。

procedure MakeSampleData(ListView: TListView);
var
  Column: TListColumn;
  ListItem: TListItem;
  I: Integer;
  S,S1,S2,S3: String;
begin
  ListView.ViewStyle := vsReport;

  // Columnの設定
  for I := 0 to 2 do
  begin
    Column := ListView.Columns.Add;
    case I of
      0: S := 'name';
      1: S := 'address';
      2: S := 'telephone';
    end;
    Column.Caption := S;
    Column.Width := 100;
  end;

  // ListItemの設定
  for I := 0 to 6 do
  begin
    ListItem := ListView.Items.Add;
    case I of
      0: begin
          S1 := '(1) hiderin';
          S2 := 'kyoto';
          S3 := '075-xxx-xxxx';
         end;
      1: begin
          S1 := '(2) yuki';
          S2 := 'tokyo';
          S3 := '03-xxxx-xxxx';
         end;
      2: begin
          S1 := '(3) picasso';
          S2 := 'barcelona';
          S3 := '932-xxx-xxx';
         end;
      3: begin
          S1 := '(4) momo';
          S2 := 'kobe';
          S3 := '078-xxx-xxxx';
         end;
      4: begin
          S1 := '(5) john';
          S2 := 'newyork';
          S3 := '212-xxx-xxxx';
         end;
      5: begin
          S1 := '(6) taro';
          S2 := 'fukuoka';
          S3 := '092-xxx-xxxx';
         end;
      6: begin
          S1 := '(7) jun';
          S2 := 'sapporo';
          S3 := '011-xxx-xxxx';
         end;
    end;

    ListItem.Caption := S1;
    ListItem.SubItems.Add(S2);
    ListItem.SubItems.Add(S3);
  end;
  ListView.DragMode := dmAutomatic;
  ListView.MultiSelect := True;
  
  // ドラッグ中のイメージをさせない---なんか中途半端で嫌なので(^^;
  ListView.ControlStyle := ListView.ControlStyle - [csDisplayDragImage];
end;



procedure TForm1.FormCreate(Sender: TObject);
begin
  // アイテムを作成します。
  MakeSampleData(ListView1);
  MakeSampleData(ListView2);
end;

procedure TForm1.ListView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := True;
end;

procedure TForm1.ListView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  I: Integer;
begin
  for I := 0 to (Source as TListView).SelCount -1 do
  begin
    if ((Sender as TListView).DropTarget = nil) then
      // 追加
      (Sender as TListView).Items.Add.Assign((Source as TListView).Selected)
    else
      // 挿入
      (Sender as TListView).Items.Insert(
        (Sender as TListView).DropTarget.Index).Assign(
        (Source as TListView).Selected);

    (Source as TListView).Selected.Delete;
  end;
end;


Drag&Drop時に隠れているアイテムをスクロール表示させるには、TreeViewで行った方法がそのまま使えます。 ぜひ実装してより実用的なものにして下さい。

参考
☆TreeViewのDrag&Drop

|

☆TListを使ったリスト No3


それではMySampleClassesが、きちんと動作しているかどうか確かめてみます。
unit Unit1;

interface

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

type

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    MyBookList1, MyBookList2: TMyBookList;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyBookList1 := TMyBookList.Create;
  MyBookList2 := TMyBookList.Create;
end;

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

// test1
procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  S1, S2: String;  
begin
  MyBookList1.Clear;
  MyBookList2.Clear;

  // MyBookList1に追加
  MyBookList1.Add(TMyBook.Create('Delphi オブジェクト指向プログラミング','塚越一雄'));
  MyBookList1.Add(TMyBook.Create('delphi-fan','hiderin'));
  MyBookList1.Add(TMyBook.Create('コンピュータ・アルゴリズム事典','奥村晴彦'));

  // MyBookList1から削除
  MyBookList1.Delete(1); // delphi-fan

  // MyBookList1に挿入
  MyBookList1.Insert(0,TMyBook.Create('Delphi6プログラミングバイブル','Marco Cantu'));

  // MyBookList2にMyBookList1のデータをコピーする。
  MyBookList2.Assign(MyBookList1);

  // MyBookList2にきちんとコピーされたか確認する。
  Memo1.Lines.Clear;
  for I := 0 to MyBooKList2.Count - 1 do
  begin
    S1 := MyBookList2[I].BookName;
    S2 := MyBookList2[I].Author;
    Memo1.Lines.Add(Concat(S1, #9, S2));
  end;
end;

// test2
procedure TForm1.Button2Click(Sender: TObject);
var
  I: Integer;
  S1, S2: String;
begin
  // SaveToFile---Stream関係をチェックする。
   MyBookList2.SaveToFile('c:\aaa.dat');

  // LoadFromFile---Stream関係をチェックする。
  MyBookList1.LoadFromFile('c:\aaa.dat');

  // ファイルから読み込みできたかチェックします。
  Memo1.Lines.Clear;
  for I := 0 to MyBooKList1.Count - 1 do
  begin
    S1 := MyBookList1[I].BookName;
    S2 := MyBookList1[I].Author;
    Memo1.Lines.Add(Concat(S1, #9, S2));
  end;
end;

end.
一応、動作しているようですね。でも、『TListを使ったリスト No1~No3』まで夜中に一気に適当に書いたので、どこかに間違いがあるかも知れないので気をつけて下さい(笑)

又、専用の追加メソッドを作れば、いちいちTMyBookを生成させなくてもBookNameとAuthorを追加できますね。 いろいろな方法、いろいろな形で作ることができるので、アプリに最適なものを考えていけばよいと思います。

TListを使ったリスト No1
TListを使ったリスト No2
TListを使ったリスト No3

|

☆TListを使ったリスト No2

前回、作成したTHRBase、THRBaseListを使って本と著者名を管理するリストを作ってみます。 又、リストをファイルに読み書きできるように拡張します。
わざわざ、HRBaseClassesというユニットを作ったのは、このように必要な部分のみを拡張して使うことを意図していたからです。
unit MySampleClasses;

interface

uses
  SysUtils, Classes, HRBaseClasses;

type
  TMyBook = class(THRBase)
  private
    FBookName: String;
    FAuthor: String;
  public
    constructor Create(BookName, Author: String);
    procedure SaveToStream(var Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
    procedure Assign(Source: TPersistent); override;
  published
    property BookName: String read FBookName write FBookName;
    property Author: String read FAuthor write FAuthor;
  end;

  TMyBookList = class(THRBaseList)
  private
    function GetData(Index: Integer): TMyBook;
    procedure SetData(Index: Integer; MyBook: TMyBook);
  public
    procedure SaveToStream(var Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
    procedure SaveToFile(const FileName: String);
    procedure LoadFromFile(const FileName: String);
    procedure Assign(Source: TPersistent); override;
    property Items[Index: Integer]: TMyBook
      read GetData write SetData; default;
  end;

implementation

// Streamに文字列を書き込みます。
procedure WriteStringToStream(var Stream: TStream; Value: String);
var
  I: Integer;
begin
  with Stream do
  begin
    I := Length(Value);
    WriteBuffer(I, SizeOf(Integer));
    WriteBuffer(PChar(Value)^, I);
  end;
end;

// Streamから文字列に読み込みます。
function ReadStringFromStream(const Stream: TStream): String;
var
  I: Integer;
begin
  with Stream do
  begin
    ReadBuffer(I, SizeOf(Integer));
    SetLength(Result, I);
    ReadBuffer(PChar(Result)^, I);
  end;
end;

{ TMyBook }

constructor TMyBook.Create(BookName, Author: String);
begin
  inherited Create;
  FBookName:= BookName;
  FAuthor := Author;
end;

procedure TMyBook.Assign(Source: TPersistent);
begin
  if (Source is TMyBook) then
  begin
    BookName:= (Source as TMyBook).BookName;
    Author := (Source as TMyBook).Author;
    Exit;
  end;
  inherited Assign(Source);
end;

procedure TMyBook.LoadFromStream(Stream: TStream);
begin
  FBookName := ReadStringFromStream(Stream);
  FAuthor := ReadStringFromStream(Stream);
end;

procedure TMyBook.SaveToStream(var Stream: TStream);
begin
  WriteStringToStream(Stream, FBookName);
  WriteStringToStream(Stream, FAuthor);
end;

{ TMyBookList }

procedure TMyBookList.SetData(Index: Integer; MyBook: TMyBook);
begin
  List[Index] := MyBook;
end;

function TMyBookList.GetData(Index: Integer): TMyBook;
begin
  Result := TMyBook(List[Index]);
end;

procedure TMyBookList.Assign(Source: TPersistent);
var
  I: Integer;
  MyBook: TMyBook;
begin
  if (Source is TMyBookList) then
  begin
    Clear;
    for I := 0 to (Source as TMyBookList).Count -1 do
    begin
      MyBook := TMyBook.Create('','');
      MyBook.Assign((Source as TMyBookList)[I]);
      Add(MyBook);
    end;
    Exit;
  end;
  inherited Assign(Source);
end;

procedure TMyBookList.LoadFromStream(Stream: TStream);
var
  I, K: Integer;
  MyBook: TMyBook;
begin
  Clear;
  Stream.ReadBuffer(K, SizeOf(Integer));

  for I := 0 to K -1 do
  begin
    MyBook := TMyBook.Create('','');
    MyBook.LoadFromStream(Stream);
    Add(MyBook);
  end;
end;

procedure TMyBookList.SaveToStream(var Stream: TStream);
var
  I: Integer;
begin
  I := Count;
  Stream.WriteBuffer(I, SizeOf(Integer));

  for I := 0 to Count -1 do
    TMyBook(List[I]).SavetoStream(Stream);
end;

procedure TMyBookList.SaveToFile(const FileName: String);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
  try
    Stream.Seek(0, soFromBeginning);
    SaveToStream(TStream(Stream));
  finally
    Stream.Free;
  end;
end;

procedure TMyBookList.LoadFromFile(const FileName: String);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Stream.Seek(0, soFromBeginning);
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;


end.

TListを使ったリスト No1
TListを使ったリスト No2
TListを使ったリスト No3

|

☆TListを使ったリスト No1

その昔、ポインタを繋いだり、繋ぎ変えたり、ガベージコレクションさせたりと誰もが自作リストでクラス(当時はレコード型だったかな)を管理していました。しかしDelphiには、TListという汎用性のあるリストが用意されていますので、わざわざ可読性の悪いリストを自作する必要はないです。

さて、そのTListですがポインタを扱うので、次のように型キャストが必要になります。
procedure TForm1.Button1Click(Sender: TObject);
var
  List: TList;
  I: Integer;
begin
  List := TList.Create;
  try
    List.Add(TStringList.Create);
    TStringList(List[0]).Text := 'delphi-fan';
    Memo1.Lines.Assign(TStringList(List[0]));
  finally
    for I := 0 to List.Count - 1 do
      TStringList(List[I]).Free;
    List.Free;
  end;
end;

これだとやはり可読性がよくない!ということで、特定のクラスを管理するリストを作ってみたいと思います。 人によっては、MyList = class(TList)という形で作られておられますが、 この場合、TListのメンバーに直接アクセスできる、すなわちポインタとしても扱えるためどんな型も 管理することができてしまいます。となるとバグが潜む可能性が出てきますよね。
(この方法を否定しているわけではないです)
ということで、私はTListをprivateなフィールドにしたクラスが好きです(笑)

せっかくなので拡張性を持たせたサンプルを作っていきたいと思います。
リストの読み書きを汎用ポインタではなく、THRBaseクラスで行えるようにしています。
unit HRBaseClasses;

interface

uses
  SysUtils, Classes;

type

  THRBase = class(TPersistent);

  THRBaseList = class(TPersistent)
  private
    FList: TList;                                       { リスト }
    function GetData(Index: Integer): THRBase;          { 読み込み }
    procedure SetData(Index: Integer; HRBase: THRBase); { 書き込み }
    function GetCount: Integer;                         { リストのカウント数 }
  protected
    procedure Error;                                    { エラーの表示 }
    property List[Index: Integer]: THRBase              { リストへのアクセス }
      read GetData write SetData; default;
  public
    constructor Create;                                 { 生成 }
    destructor Destroy; override;                       { 破棄 }
    procedure Clear;                                    { 消去 }
    function Add(HRBase: THRBase): Integer;             { 追加 }
    procedure Insert(Index: Integer; HRBase: THRBase);  { 挿入 }
    procedure Delete(Index: Integer);                   { 削除 }
    procedure Assign(Source: TPersistent); override;    { Assign }
  published
    property Count: Integer read GetCount;              { リストのカウント数 }
  end;

implementation

{ THRBaseList }

// 生成
constructor THRBaseList.Create;
begin
  FList := TList.Create;
end;

// 破棄
destructor THRBaseList.Destroy;
begin
  Clear;
  FList.Free;
  inherited Destroy;
end;

// 消去
procedure THRBaseList.Clear;
var
  I: Integer;
begin
  for I := 0 to FList.Count -1 do
    THRBase(FList[I]).Free;
  FList.Clear;
end;

// 追加
function THRBaseList.Add(HRBase: THRBase): Integer;
begin
  Result := FList.Add(HRBase);
end;

// 挿入
procedure THRBaseList.Insert(Index: Integer; HRBase: THRBase);
begin
  FList.Insert(Index, HRBase);
end;

// 削除
procedure THRBaseList.Delete(Index: Integer);
begin
  THRBase(FList[Index]).Free;
  FList.Delete(Index);
end;

// エラー処理
procedure THRBaseList.Error;
begin
  raise Exception.Create('インデックスがリストの範囲を超えています');
end;

// リストからの取得
function THRBaseList.GetData(Index: Integer): THRBase;
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  Result := THRBase(FList[Index]);
end;

// リストへの設定
procedure THRBaseList.SetData(Index: Integer; HRBase: THRBase);
begin
  if (Index < 0) or (Index >= FList.Count) then Error;
  FList[Index] := HRBase;
end;

// リストのカウント数
function THRBaseList.GetCount: Integer;
begin
  Result := FList.Count;
end;

// Assign
procedure THRBaseList.Assign(Source: TPersistent);
var
  I: Integer;
  HRBase: THRBase;
begin
  if (Source is THRBaseList) then
  begin
    Clear;
    for I := 0 to (Source as THRBaseList).Count -1 do
    begin
      HRBase := THRBase.Create;
      HRBase.Assign((Source as THRBaseList)[I]);
      Add(HRBase);
    end;
    Exit;
  end;
  inherited Assign(Source);
end;

end.

なぜTObjectではなくて、TPersistentから継承しているのか、疑問に思われる方も多いかも知れませんね。 その理由は、私が作るリストの中では、TPersistentのAssignという仮想メソッドを再定義することが多いからです(笑)
※procedure Assign(Source: TPersistent); override; という部分ですね。

今回は、単純なクラスを拡張していくという目的で作っているので、少し回りくどい処理になっているかも知れません。 HRBaseClassesを取り込んだ形でいきなり「TListを使ったリスト No2」のMySampleClassesにあるクラスを作ることも多いです。

TListを使ったリスト No1
TListを使ったリスト No2
TListを使ったリスト No3

|

■Webセミナーに関するアンケート

CodeGearのサイトで、これから開催予定のWebセミナーに関するアンケートを実施されてます。 アンケートの中で、メルアドを入力することに抵抗を感じる方が多いかも知れませんが、 一度ご覧下さい。あっ、もちろん私はCodeGearの関係者でも、回し者でもないです(笑)

Webセミナーに関するアンケート

|

☆DBGridでのBeginDrag

DBGridのOnMouseDownは、セルのある場所では動きません。OnMouserDownでの処理を行いたい場合には、通常コンポーネントにしてMouseDownをoverrideし、そこで処理をさせています。 ただ、Drag開始のためだけにコンポーネントを作るのも面倒なので、何か方法がないかいろいろと試し、次のようにOnMouseMoveを使えばうまくいきました。
// BDEでTableにDEMOSのanimals.dbfを設定して試しました。
// Memo1とTable1-DataSource1-DBGrid1を配置しています。

var
  FieldIndex: Integer;

procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  Cell: TGridCoord;
begin
  // BeginDrag
  if (csLButtonDown in DBGrid1.ControlState) then
  begin
    Cell := DBGrid1.MouseCoord(X, Y);
    FieldIndex := Cell.X - 1;
    DBGrid1.BeginDrag(False);
  end;
end;
Memo1のDrag&Dropの設定を行います。
procedure TForm1.Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := True;
end;


procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  Memo1.Lines.Add(Table1.Fields[FieldIndex].AsString);
end;
一番簡単なのは、DragModeをdmAutomaticにすることですが、常にDrag&Drop処理だけで使うわけではないので除外ですね。

※Delphi Users' Forumへコメントしたものです(^^;

|

■Delphi 2007 を申し込みました。

相変わらずVistaを使える環境がないのですが、Update2がリリースされたタイミングでは購入しようと考えていたので、先程申し込みをしました。早く届かないかな(笑)

|

■Delphi 2007 Update2 が公開されました。

さきほど届いたCodeGearからのメールに Delphi 2007 for Win32 / C++Builder 2007 Update2 が公開されたとありました。Update2を適用する前に、Update1を適用しておく必要があるようです。

Delphi 2007 for Win32 および C++Builder 2007 Update 2 リリースノート

|

■GUNGLAって何?

初代DelphiかDelphi2の頃、隠しキーでバージョン情報にAnders Hejlsberg氏を始め、開発者の名前が出てきたことを、ふと思い出し、Delphi2006でバージョン情報を表示させて
  ALTキーを押しながら team
と入力して見ました。

BORLAND RULES! という大きな文字とその下に「了解。」と書かれたボタンが表示されました。 又、左下には、Teamというボタン そして右下にはGUNGLAと書いてます。

Teamボタンをクリックすると、開発関係者と思われる名前が上からスライドしてきます。中にはHELLO WORLDなんていうのも混じってますけど。

そして、GUNGLAという謎の文字をクリックすると、中央にサーフィンをする猿の写真が表示されました。なんのこっちゃ(笑)

|

■ナッキーの「Turbo Delphiはじめて奮戦記」

IDEのホームページに『ナッキーの「Turbo Delphiはじめて奮戦記」 - 第22回 占いプログラムでWebサービス-クライアント編』というのが表示されていました。早速見てみますと、いろいろなテーマ毎に、たくさんのスクリーンショットを使いながら丁寧に説明されていて、かなりわかりやすい内容になっています。

ただひとつ残念に思うことは、トップページは専用のサイトになっていて『やさしさ』を全面的に打ち出しているにもかかわらず、次のページからは、CodeGearのフレーム内に表示されていて、なんかごちゃごちゃしてわかりにくい印象を受けました。せっかくだからフレームから独立して、もう少し文字を大きくして読みやすいページにすれば最高だと思うんですけどね。(Delphi Tutorialsという大きな表記もいらない?)まあ、ここで書いてても仕方ないですけど。

ナッキーの「Turbo Delphiはじめて奮戦記」

|

☆DBGridのセル位置取得

さきほど Delphi Users' Forum でコメントに書いたものですが、せっかくなのでこちらにも記録しておきますね。
トリッキーな方法かも知れませんが、型キャストすることにより上位クラスが持っている関数等を利用しています。 サンプルでは、選択されたセルの位置やサイズを知ることができます。今まで使ったことがないので、何に使えるのかよくわかりませんけど(笑)
type
  TDummyDBGrid = class(TCustomDBGrid);

procedure TForm1.DBGrid1CellClick(Column: TColumn);
var
  Col, Row: Integer;
  R: TRect; 
begin
  Col := TDummyDBGrid(DBGrid1).Col;
  Row := TDummyDBGrid(DBGrid1).Row;
  // DBGrid上ではこの位置
  R := TDummyDBGrid(DBGrid1).CellRect(Col, Row);
  // フォーム上ではこの位置
  R.Top := R.Top + DBGrid1.Top;
  R.Left := R.Left + DBGrid1.Left;
end;

|

☆TWebBrowserで自動ログイン

フリーメール以外でもWEBからアクセスできるメールサービスが多くあり、ブラウザさえ見れる環境があれば外部からメールのチェックが可能ですよね。今回は、そういったメールに自動ログインして、メーラーとして使えるような処理を考えたいと思います。サンプルでは、コード中にユーザーIDやパスワードを入力していますが、危険なので暗号化し、別ファイルに保存して読み書きするのが便利だと思います。そしてUSBメモリーに入れて持ち歩くと、人のパソコンでも簡単に自分のメーラーにすることができます(^^;キー入力によるパスワード等の盗難?の可能性も減りますし・・・。

私は、簡易な暗号しか作れないので、IDは簡易な暗号化をしてファイルに保存し、パスワードのみ入力するようにしています。現在 @nifty、eo、Hotmail、Gmail、Yahoo Groupをチェックできるようにしています。

まず、フォームにTWebBrowserとTButtonを一つずつ配置します。
そして、ログインするための処理を作成します。

var
  IsLogin: Boolean;

procedure TForm1.Login;
var
  URL: String;
begin
  // Gmailの場合
  URL := 'https://www.google.com/accounts/ServiceLogin?'+
         'service=mail&passive=true&rm=false&continue=http%3A%2F%2'+
         'Fmail.google.com%2Fmail%3Fhl%3Dja%26ui%3Dhtml%26zy%3Dl'+
         '&l'+'tmpl=cm_tlsosm_t&l'+'tmplcache=2&hl=ja';
  UserName := 'xxxxxxx@gmail.com'; // ここにユーザーIDを入力します。
  Password := '*******'; // ここにパスワードを入力します。
  IsLogin := False;
  WebBrowser1.Navigate(URL);
end;


次に自動ログインする処理です。 この処理方法については、どのサイトだったのかわからないのですが、いろいろなサイトを参考にさせてもらっています。名前やアドレスが書けず、ごめんなさい。
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var
  iDoc: IHtmlDocument2;
  I: integer;
  ov: OleVariant;
  iDisp: IDispatch;
  iColl: IHTMLElementCollection;
  iInputElement: IHTMLInputElement;
begin
  if IsLogin then Exit;

  WebBrowser1.ControlInterface.Document.QueryInterface(IID_IHTMLDocument, iDoc);
  if not assigned(iDoc) then
    Exit;

  ov := 'INPUT';
  IDisp := iDoc.all.tags(ov);
  if assigned(IDisp) then
  begin
    IDisp.QueryInterface(IID_IHTMLElementCollection, iColl);
    if assigned(iColl) then
    begin
      for I := 1 to iColl.Get_length do
      begin
        iDisp := iColl.item(pred(I), 0);
        iDisp.QueryInterface(IID_IHTMLInputElement, iInputElement);
        if assigned(iInputElement) then
        begin
          // username ※iInputElement.nameについては、事前にHTMLを見て調べます。
          if (iInputElement.type_='text') and (iInputElement.name='Email') then
            iInputElement.value:= UserName;

          // password ※iInputElement.nameについては、事前にHTMLを見て調べます。
          if (iInputElement.type_= 'password') and (iInputElement.name='Passwd') then
            iInputElement.value:= Password;

          // submit
          if (iInputElement.type_= 'submit') then
          begin
            IsLogin := True;
            iInputElement.form.submit;
          end;
        end;
      end;
    end;
  end;
end;


最後にログインボタンの処理です。
procedure TForm1.Button1Click(Sender: TObject);
begin
  Login;
end;


プログラムにパスワードを書き込んで試した場合、バックアップファイルに残っていたりしますので、ご注意下さい。

|

■Delphi2007 PDF版ヘルプの公開

ニュースグループ borland.public.delphi.japanese の情報ですが、 Delphi Documentation SubmissionsからDelphi2007 / C++Builder2007のPDF版ヘルプがダウンロードできるとのことでした。

ということで、早速ダウンロードしてみました。
VCLのリファレンスなんかA-L 10612ページ、M-Z 8114ページ 計18726ページにもなります。 デスクの上にある彰国社の建築大辞典という分厚い本(8cmぐらい)が、2000ページぐらいなので、その量のすごさは圧巻です。 ちょっと中身を見てみました。SysUtils等、ユニットと呼んでいたんですが、リファレンスによると ネームスペース(名前空間)という呼び名になってます。 まあ、これは私が知らなかっただけでしょうけど。だって、Delphi 2006を愛用していますが、HELPはDelphi7のものをずっと使っていますから。
なぜDelphi7なのか?・・・ユーザーのみなさんなら理由はわかってもらえるでしょうけど(笑)
Delphi 2007では、これから改善されていくのかな?

Dee Elling -CodeGear Information Development-
More RAD Studio 2007 PDFs posted

ニュースグループでは直リンクを載せて頂いているので、そちらを参照された方が簡単です。

|

☆TreeViewのお手軽なUndo処理

前回のTreeViewのDrag&Dropのコードを使って、お手軽なUndo処理(手抜き?)を紹介します。
一番お手軽なのは、SaveToFile、LoadFromFileを使って一時ファイルでやり取りする方法です。しかしこれでは、展開状態を別に保存する必要があり面倒です。そこでもう一つTreeViewを用意してクローンを作成することにします。

まず、前回のコードの中で、TreeView1DragDropを次のように修正します。(2行追加するだけ)
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  srcNode, dstNode: TTreeNode;
  HT: THitTests;
begin
  // Explorer等は、ファイル名でソートされていますが、
  // ソートではなく意図したノードの順番に移動させたい場合があります。
  // そのような時に私のアプリでは、
  // アイコンで挿入移動、文字列で子追加移動という仕様にしています。
  // これって結構便利だと思うんですけど、あまり採用されてないみたい。

  srcNode :=TreeView1.Selected;           // 選択ノード
  dstNode := TreeView1.DropTarget;        // ドロップターゲットノード

  // Undo用TreeViewに登録させます。       //←追加
  UndoRegist;                             //←追加

  HT := TreeView1.GetHitTestInfoAt(X, Y);
  if (htOnIcon in HT) then
    srcNode.MoveTo(dstNode, naInsert)     // アイコンを選択している場合
  else
    srcNode.MoveTo(dstNode, naAddChild);  // 項目を選択している場合
end;

次にUndoの処理です。
フォームにTreeViewを1つ、SpeedButtonを2つ追加します。
そして、次のコードを最後に追加します。
var
  idx1: Integer;
  CanUndo: Boolean = False;
  // TopIndex: Integer; // (A)

// Undo用TreeViewに登録させます。
procedure TForm1.UndoRegist;
var
  I: Integer;
  F: Boolean;
begin
  TreeView1.Items.BeginUpdate;
  try
    // TopIndex := TreeView1.TopItem.AbsoluteIndex; // (A)

    if TreeView1.Selected = nil then
      idx1 := -1
    else
      idx1 := TreeView1.Selected.AbsoluteIndex;

    TreeView2.Items.Assign(TreeView1.Items);

    // 展開状態の保存
    for I := TreeView1.Items.Count -1 downto 0 do
    begin
      F := TreeView1.Items[I].Expanded;
      if TreeView2.Items[I].Expanded <> F then
        TreeView2.Items[I].Expanded := True;
    end;
    CanUndo := True;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

// Undo
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  I: Integer;
  F: Boolean;
begin
  // Undoできない場合の処理
  if not CanUndo then Exit;

  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Assign(TreeView2.Items);

    // 展開状態の再現
    for I := TreeView2.Items.Count -1 downto 0 do
    begin
      F := TreeView2.Items[I].Expanded;
      if TreeView1.Items[I].Expanded <> F then
        TreeView1.Items[I].Expanded := True;
    end;

    if idx1 >= 0 then
      TreeView1.Items[idx1].Selected := True;
    TreeView1.Selected.MakeVisible; // (B)

    CanUndo := False;
  finally
    TreeView1.Items.EndUpdate;
    // TreeView1.TopItem := TreeView1.Items[TopIndex]; // (A)
  end;
end;

// Delete
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  if TreeView1.Selected <> nil then
  begin
    // Undo用TreeViewに登録させます。
    UndoRegist;
    TreeView1.Selected.Delete;
  end;
end;


あとは実行するだけです。移動や削除をしてからUndoボタンを押すと、Undoできます。処理前にUndoRegistを呼び出せば、追加、挿入時もこのUndo機能が使えます。 Undo後は、MakeVisibleで、Undo前の選択ノード表示していますが、TopItemにこだわるのであれば、(B)をコメントアウトして、(A)を有効にします。 TopItemへの設定は、TreeView1.Items.BeginUpdate~EndUpdate内では無効みたいなので、ちょっとちらつきます。

TreeViewのアイテム数にもよりますが、最近のマシンでは、これぐらいのメモリの無駄は問題ないと思います。しかし、これはあくまで一つのサンプルなので、きちんとしたアプリでは、クラスとしてUndoList、RedoListを作成する 、ファイルを利用する等で、リソースが「MOTTAINAI」と言われないような処理を心掛けたいですね(笑)

[2007/08/04 am10:54 全面的に訂正しました]
展開状態を設定し忘れていたからです(^^;

|

☆TreeViewのDrag&Drop

TreeView Drag drop delphi・・・このキーワードでここに来られる方がおられます。以前に .NET(Windowsフォームアプリケーション)での処理を書いていますので、それが該当するんでしょうね。
でもおそらくWIN32での処理をお探しだと思いますので、その処理を書きたいと思います。

簡単に説明すると「TreeView1.OnDragDropで Node の Selected、DropTarget、MoveTo を使って処理するだけです。」と書いてしまえばいいんでしょうが、せっかくなので縮小ノードの展開及び非表示部分のスクロール処理、並びにターゲットの選択部分によるノードの移動処理を含めたサンプルにしたいと思います。

でもTreeViewは奥深いので、Node.DataやOnChange等が原因で様々な問題が起こります。又、DragModeをdmAutomaticで使うとクリックしたかっただけなのに、ノード移動させてしまったという問題も比較的よく起こります。くれぐれもご注意を(笑)

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, ImgList, ExtCtrls;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    ImageList1: TImageList;
    Timer1: TTimer;
    Timer2: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


//テスト用アイコン作成
procedure MakeTestIcon(ImageList: TImageList);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Height := 16;
    Bitmap.Width := 16;
    Bitmap.Canvas.Brush.Color := clRed;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
    Bitmap.Canvas.Brush.Color := clBlue;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
  finally
    Bitmap.Free;
  end;
end;

// 初期設定いろいろ
procedure TForm1.FormCreate(Sender: TObject);
const
  FileName = 'data.txt';
var
  Node: TTreeNode;
  Path: String;
begin
  // 展開用タイマー設定
  Timer1.Enabled := False;
  Timer1.Interval := 1500;

  // スクロール用タイマー設定
  Timer2.Enabled := False;
  Timer2.Interval := 50;

  // アイコンの作成
  MakeTestIcon(ImageList1);

  // TreeView1の設定
  TreeView1.Images := ImageList1;
  TreeView1.ReadOnly := True;
  TreeView1.DragMode := dmAutomatic;
  TreeView1.Height := 150;

  // データの読み込み
  Path := ExtractFilePath(Application.ExeName);
  TreeView1.LoadFromFile(Path + FileName);

  Node := TreeView1.Items.GetFirstNode;
  repeat
    Node.ImageIndex := 1;
    Node.StateIndex := 2;
    Node := Node.GetNext;
  until Node = nil;

  // スクロールの際、ごみが出るから(^^;
  TreeView1.ControlStyle := TreeView1.ControlStyle - [csDisplayDragImage];
end;

var
  ScrollBarCommand: Integer;

procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);

  function IsSelfOrMyChild(dtNode, slNode: TTreeNode): Boolean;
  begin
    Result := False;
    while (dtNode <> nil) do
    begin
      if (slNode = dtNode) then
      begin
        Result := True;
        Exit;
      end;
      dtNode := dtNode.Parent;
    end;
  end;

const
  Offset = 20;
var
  slNode, dtNode: TTreeNode;
begin
  slNode := TreeView1.Selected;
  dtNode := TreeView1.DropTarget;

  Accept := ((Sender as TTreeView) = (Source as TTreeView)) and
            (not IsSelfOrMyChild(dtNode, slNode));

  if not Accept then Exit;
  
  // ノードを展開させます。
  if (dtNode <> nil) then
  begin
    if Timer1.Enabled and (Timer1.Tag <> dtNode.AbsoluteIndex) then
      Timer1.Enabled := False;
    if (not dtNode.Expanded) and dtNode.HasChildren then
    begin
      Timer1.Tag := dtNode.AbsoluteIndex;
      Timer1.Enabled := True;
    end;
  end;

  // 表示されていない部分をスクロールさせます。
  if (Y < Offset) or (Y >= TreeView1.ClientHeight - Offset) then
    begin
      if (Y < Offset) then
        ScrollBarCommand := SB_LINEUP
      else
        ScrollBarCommand := SB_LINEDOWN;
      Timer2.Enabled := True;
    end
  else
    begin
      ScrollBarCommand := -1;
      Timer2.Enabled := False;
    end;
end;

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  srcNode, dstNode: TTreeNode;
  HT: THitTests;
begin
  // Explorer等は、ファイル名でソートされていますが、
  // ソートではなく意図したノードの順番に移動させたい場合があります。
  // そのような時に私のアプリでは、
  // アイコンで挿入移動、文字列で子追加移動という仕様にしています。
  // これって結構便利だと思うんですけど、あまり採用されてないみたい。

  srcNode :=TreeView1.Selected;           // 選択ノード
  dstNode := TreeView1.DropTarget;        // ドロップターゲットノード

  HT := TreeView1.GetHitTestInfoAt(X, Y);
  if (htOnIcon in HT) then
    srcNode.MoveTo(dstNode, naInsert)     // アイコンを選択している場合
  else
    srcNode.MoveTo(dstNode, naAddChild);  // 項目を選択している場合
end;

procedure TForm1.TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  ScrollBarCommand := -1;
  Timer1.Enabled := False;
  Timer2.Enabled := False;
end;

// 閉じているノードを展開させます。
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  TreeView1.Items[Timer1.Tag].Expanded := True;
  Timer1.Enabled := False;
end;

// スクロールさせます。
procedure TForm1.Timer2Timer(Sender: TObject);
begin
  if (ScrollBarCommand > -1) then
    SendMessage(TreeView1.Handle, WM_VSCROLL, ScrollBarCommand, 0);
end;

end.

プログラムと同じフォルダに、下記の内容を書き込んだテキストファイルを作成して下さい。
ファイル名は、data.txtです。
node-01
	node-02
	node-03
		node-04
		node-05
node-06
	node-07
		node-08
node-09
	node-10
node-11
node-12
node-13
node-14
	node-15
node-16
node-17
node-18
node-19
node-20
node-21
	node-22
node-23

|

■Delphi起動直後に表示される「ホームページ」

ニュースグループ borland.public.delphi.japanese にあったのですが、BDS2006(2005)の起動直後に表示される「ホームページ」をCodeGear用に修正したものが公開されているとのことです。

修正方法は、下記ホームページからダウンロードし、解凍したファイルを<BDS>\Welcomepageフォルダに上書きコピーするだけです。

CodeCentral
ID: 24705, Change welcome page in BDS2006(Japanese Edition)
ID: 24704, Change welcome page in Delphi2005(Japanese Edition)

|

■ブログの配色を変えました。

ブログの配色を少し変えました。CSSも手探りで変更するのは面倒ですね。
色を選ぶときに RGBをWeb用の表記に変換するプログラムを書いて作業をしましたので、 そのコードを書いておきますね。
// R=255,G=255,B=255 を #FFFFFF のように変換します。
procedure TForm1.Button1Click(Sender: TObject);
var
  r,g,b: Integer;
begin
  r := StrToInt(Edit1.Text);
  g := StrToInt(Edit2.Text);
  b := StrToInt(Edit3.Text);
  Edit4.Text := Format('#%.2x%.2x%.2x', [r, g, b]);
end;

|

■第6回 CodeGearデベロッパーキャンプ

2007年8月28日、梅田スカイルーム(大阪・梅田)にて、大阪では初の開催となる第6回 CodeGearデベロッパーキャンプが実施されるそうです。又、デベロッパーキャンプの開催にあわせて、Delphi User’s Forum主催のDelphiユーザーミーティングも開催されるそうです。私は、仕事と直接関係ないのですが、delphi-fanとして興味があるので参加するつもりです。

詳細は、CodeGearのホームページにて
第6回 CodeGearデベロッパーキャンプ開催のお知らせ

|

« 2007年7月 | トップページ | 2007年9月 »