Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2020年03月13日] [更新 2020年03月13日] Delphi サンプルプログラム集
   
058_TDBGrid のインプレイスボタンとその機能
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
BDE 5.2 + Paradox 7.0
058_TDBGrid_Button.zip [3,621 KB] 2020年03月13日版 (EXE 同梱)







TDBGrid のインプレイスボタンとその機能


TDBGrid には,インプレイスエディタが起動して,セルが編集状態になるとボタンを表示する機能が実装されています. カラム (TColumn) の ButtonStyle プロパティの値を= cbsEllipsis にすると,省略記号のボタンを表示します.ボタンクリックは OnEditButtonClick イベントで検出できます.

もう 1 つは,PickList というドロップダウンリストを表示するための三角形の矢印のボタンです.こちらはプロパティとして cbsAuto (デフォルト) を指定しておきます.該当するカラムの PickList プロパティに,文字列としてのリストが格納されていると自動的に表示します.

本ページは,それらの機能の確認とカスタマイズのサンプルです.
本ページのサンプルでは,継承クラスをコンポーネントとして登録しないで,添付の plChgCompo ユニットを使用して,実行時に TDBGrid コンポーネントを派生クラスに置き換えてテストしています.



[備考 1]
サンプルのプロジェクトには,添付のユニットを使用しているプロジェクトがあります.該当するユニットファイルをライブラリパスの通ったフォルダにコピーして使用して下さい.plChgCompo は,全てのサンプルで使用しています.


[備考 2]
本ページのサンプルでは,BDE と Paradox のサンプルデータを使用しています.
Delphi XE7 以降には BDE は実装されていません.必要であれば,BDE を別途入手するか,他のデータベースを利用してテストしてください.





01_TDBGrid のインプレイスボタン


TDBGrid のインプレイスボタンの機能を確認するテストです.
ボタンのスタイルが cbsEllipsis の時の省略記号のボタンは,TDBGrid.OnEditButtonClick イベントで検出できます.このイベント内でダイアログを表示する等の処理を実行します.

ボタンのスタイルが三角形の矢印のボタンは,クリック検出用のイベント類はありません.ボタンをクリックすると自動的にドロップダウンリストを表示しますが,ボタンがクリックされる前にリストを作成しておきす.このサンプルでは TDBGrid.OnCellClick イベント内で作成しています.
常に,間違いなく固定したリストしか使用しないのであれば,あらかじめリストを作成しておくことができます.データベースでは,他の表 (テーブル) のデータを参照することがあります.その参照元のデータの変更に対応するためにこの仕様にしています.

ドロップダウンリストで項目を選択すると,データセットが更新可能な状態であれば,自動でデータセットのデータを更新します.データセットの更新 (書き込み) の処理は必要ありません.

このドロップダウンリストでは,マウスホイール操作をするとリストが閉じ,スクロールメッセージは TDBGrid が受信します.



図1
省略記号ボタンを表示
  • セルが編集状態になると表示される
  • 選択ボタン,あいるは,参照ボタンと呼ぶこともある
図2
PickList 表示用ボタンを表示
  • セルが編集状態になると表示される
  • PickList はドロップダウンリスト,選択リストとも言う
  • [Alt] + [↓] でドロップダウンメニュー表示 (トグル)
  • 選択セルを編集モードにするのは [F2] または [Enter]
    ( [Enter] はトグル )

リスト1
TColumn.ButtonStyle := cbsEllipsis のボタンと TColumn.PickList 用のボタンの表示
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Bde.DBTables,
  Vcl.Grids, Vcl.DBGrids, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TplDBGrid = class(TDBGrid)
  end;

  TForm1 = class(TForm)
    DataSource1: TDataSource;
    Panel1: TPanel;
    Query1: TQuery;
    Button1: TButton;
    DBGrid1: TDBGrid;
    Database1: TDatabase;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure DBGrid1CellClick(Column: TColumn);
    procedure DBGrid1EditButtonClick(Sender: TObject);
  private
    { Private 宣言 }
    plDBGrid1 : TplDBGrid;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  plChgCompo;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LFieldNo : Integer;
begin
  // フォーム作成時にコンポーネントの入れ替え
  plDBGrid1 := TplDBGrid(ChangeComponent(DBGrid1, TplDBGrid));

  // データベースの設定
  Query1.Close;
  Database1.Connected := False;
  Database1.DatabaseName := 'DBDEMOS';
  Database1.TransIsolation := tiDirtyRead;

  DataSource1.DataSet  := Query1;
  plDBGrid1.DataSource := DataSource1;
  Query1.DatabaseName  := Database1.DatabaseName;

  // 更新可能にする
  Query1.RequestLive := True;

  // データベースからデータを取得して表示
  Query1.SQL.Clear;
  Query1.SQL.Add('select CustNo, Company, State, City from customer.db');
  Query1.Open;
  plDBGrid1.Columns[1].Width := 140;
  plDBGrid1.Columns[2].Width := 110;


  // トランザクション開始
  Database1.StartTransaction;

  // 省略記号ボタン (選択ボタン・参照ボタン) を表示する列の設定
  LFieldNo := plDBGrid1.DataSource.DataSet.FieldByName('Company').FieldNo;
  plDBGrid1.Columns[LFieldNo - 1].ButtonStyle := cbsEllipsis;

  ActiveControl := plDBGrid1;
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // テストなのでコミット (更新) しない
  Database1.Rollback;

  // データベースを閉じる
  Query1.Close;
  Database1.Close;
end;

//=============================================================================
//  ボタンがクリックされた時のイベント処理
//  ダブルクリックでもこのイベントが実行される
//=============================================================================
procedure TForm1.DBGrid1EditButtonClick(Sender: TObject);
var
  LText : string;
begin
  inherited;

  if plDBGrid1.SelectedField.FieldName = 'Company' then begin
    plDBGrid1.DataSource.DataSet.Edit;
    plDBGrid1.SelectedField.AsString := '新日本マリンショップ';
    plDBGrid1.DataSource.DataSet.Post;

    LText := plDBGrid1.SelectedField.Text;
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
    plDBGrid1.SetFocus;
  end;
end;

//=============================================================================
//  セルをクリックした時のイベント処理
//  ドロップダウンリスト (PickList) を作成してセット
//=============================================================================
procedure TForm1.DBGrid1CellClick(Column: TColumn);
var
  LItemList : TStringList;
begin
  // ドロップダウンリスト (PickList) を表示する列とリスト項目の設定
  if Column.FieldName = 'State' then begin
    LItemList := TStringList.Create;
    try
      LItemList.Add('富山県');
      LItemList.Add('岩手県');
      LItemList.Add('山形県');
      LItemList.Add('長野県');
      LItemList.Add('山梨県');

      plDBGrid1.Columns[Column.Field.FieldNo - 1].PickList := LItemList;
    finally
      FreeAndNil(LItemList);
    end;
  end;
end;

end.




02_TDBGrid のドロップダウンリストを表示する操作


PickList プロパティに文字列のリストが格納されている列では,TBGGrid のセルが編集状態になるとにドロップダウン用のボタンが表示されます.ボタンが表示されている時に [Alt] + [↓] のキー操作でドロップダウンリストが表示されます.このキー操作は表示のトグルとなっています.

選択されていなセルをクリックしたら直ちにドロップダウンリストを表示するには,例えば OnCellClick イベントで次のような処理をします.



//=============================================================================
//  セルをクリックした時のイベント処理
//  リスト (PickList) を作成してセットしてドロップダウンリストを表示
//
//  セルをクリックしたらドロップダウンリストを表示したい場合の参考コード
//=============================================================================
procedure TForm1.DBGrid1CellClick(Column: TColumn);
var
  LItemList : TStringList;
  LKeyFlag  : Cardinal;
begin
  // ドロップダウンリスト (PickList) を表示する列とリスト項目の設定
  if Column.FieldName = 'State' then begin
    LItemList := TStringList.Create;
    try
      LItemList.Add('富山県');
      LItemList.Add('岩手県');
      LItemList.Add('山形県');
      LItemList.Add('長野県');
      LItemList.Add('山梨県');

      plDBGrid1.Columns[Column.Field.FieldNo - 1].PickList := LItemList;
    finally
      FreeAndNil(LItemList);
    end;

    // 編集モードにする
    plDBGrid1.EditorMode := True;

    LKeyFlag := KEYEVENTF_EXTENDEDKEY;
    keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
    keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);

    LKeyFlag := KEYEVENTF_KEYUP or KEYEVENTF_EXTENDEDKEY;
    keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);
    keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0);
  end;
end;


OnColEnterイベントで処理する例です.
該当するデータの項目に相当する列に入力フォーカスを移動すると,自動的にドロップダウンリストを表示します.新規のレコードにデータを入力する場合,[TAB] キーや矢印キーで選択セルを移動して入力するには便利かも知れません.



//=============================================================================
//  OnColEnterイベント処理
//  セルを選択したらドロップダウンリストを直ちに表示したい場合の参考コード
//=============================================================================
procedure TForm1.DBGrid1ColEnter(Sender: TObject);
var
  LKeyFlag  : Cardinal;
begin
  if plDBGrid1.SelectedField.FieldName = 'State' then begin
    with plDBGrid1.Columns[plDBGrid1.Col - 1].PickList do begin
      Clear;
      Add('富山県');
      Add('岩手県');
      Add('山形県');
      Add('長野県');
      Add('山梨県');
    end;

    // 編集モードにする
    plDBGrid1.EditorMode := True;

    LKeyFlag := KEYEVENTF_EXTENDEDKEY;
    keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
    keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);

    LKeyFlag := KEYEVENTF_KEYUP or KEYEVENTF_EXTENDEDKEY;
    keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);
    keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0);
  end;
end;


データを入力する作業は編集作業とも言えます.次のコードは,その [Enter] キーでドロップダウンリストを表示する例です.OnColEnterイベントで処理する例です.

このように,ドロップダウンリストの表示は,いくつかのイベントで処理できます.継承クラスであれば各種のメソッドで処理できますが,これは,アプリケーションの操作性に関わる仕様です.その動作については,十分な検討が必要と思われます.



//=============================================================================
//  OnKeyDown イベント処理
//  編集中でない選択セルで [Enter] キー押したらドロップダウンリストを表示
//  TDBGrid では [Enter] キーは編集モードのトグル
//=============================================================================
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LKeyFlag  : Cardinal;
begin
  if Key = VK_RETURN then begin
    if plDBGrid1.SelectedField.FieldName = 'State' then begin
      with plDBGrid1.Columns[plDBGrid1.Col - 1].PickList do begin
        Clear;
        Add('富山県');
        Add('岩手県');
        Add('山形県');
        Add('長野県');
        Add('山梨県');
      end;

      LKeyFlag := KEYEVENTF_EXTENDEDKEY;
      keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
      keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);

      LKeyFlag := KEYEVENTF_KEYUP or KEYEVENTF_EXTENDEDKEY;
      keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), LKeyFlag, 0);
      keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0);
    end;
  end;
end;




03_TDBGrid のドロップダウンリストのカスタマイズ


TDBGrid のドロップダウンリストのカスタマイズの例です.
ドロップダウンのリストの文字列は,日本語の場合,行間が狭い感じになります.ウィンドウの影を表示しない設定ではリストを表示しているウィンドの境界がハッキリしません.また,リストの表示中にマウスホイールを操作するとリストのウィンドウが閉じてしまいます.

リストの選択中の項目の背景色の変更の機能も含めて,それらのドロップダウンの機能をカスタマイズするには,TDBGrid と TInplaceEditList の継承クラスを作成して使用します.
TDBGrid.GetEditStyle メソッドをオーバライドしてボタンのスタイルを設定します.
TDBGrid.CreateEditor をオーバライドしたメソッドで TInplaceEditList のインスタンスを生成します.これで,TInplaceEditList のメソッドやプロパティが利用可能になります.それらのメソッドやプロパティを使用してカスタマイズします.

省略記号のボタンクリックは DoEditButtonClick メソッドで検出できます.このメソッド内でボタンをクリックした時の処理を行います.

ドロップダウンのリストは DoGetPickListItems メソッド内で作成します.ドロップダウンのボタンクリックは DropDown メソッドで検出できます.
リストから項目を選択しても,インプレイスエディタの値は変更されますが,データセットの値は自動では更新されません.CloseUp メソッド内でデータセットのデータの更新処理を実行します.

PickList というドロップダウンリストの実体は TCustomListBox の継承クラスのコントロールで,クラス名は TPopupListbox です.本サンプルでは TListBox として扱っていますが,protected 部のメンバを使用したい場合は TCustomListBox に変更します.
選択項目の背景色の変更などは,この TListBox の OnDrawItem イベントで処理します.

このようなインプレイスボタン関係のカスタマイズは,TStringGrid, TValueListEditor でも,方法,手順は大体同じです.



[備考]
継承した TInplaceEditList で処理して操作できるのは DBGrid.GetEditStyle メソッドでボタンのスタイルを esEllipsis または esPickList に設定したセルだけです.それ以外のセルは元の機能のままです.



図3
カスタマイズした PickList (ドロップダウンリスト)
  • 選択中の項目のフォーカスの枠線描画なし
  • 選択中の項目の背景色の変更が可能
  • 行間も任意に設定可能
  • マウスホイール操作で閉じない
図4
省略記号ボタンクリック
  • アクティブセルの左下から表示
  • CreateMessageDialog を使用しているので位置やサイズの設定が可能

リスト2
TDBGrid のドロップダウンリストのカスタマイズ例
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Bde.DBTables,
  Vcl.Grids, Vcl.DBGrids, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TplDBGridInplaceEditList  = class(TInplaceEditList)
  private
    procedure PickListDrawItemEvent(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure PickListClickEvent(Sender: TObject);
  protected
    procedure CreateWnd; override;
    procedure DropDown; override;
    procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
    procedure DoGetPickListItems; override;
    procedure DoEditButtonClick; override;
    procedure CloseUp(Accept: Boolean); override;
  end;

  TplDBGrid = class(TDBGrid)
  public
    function CreateEditor: TInplaceEdit; override;
    function GetEditStyle(ACol: Integer; ARow: Integer): TEditStyle; override;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    DBGrid1: TDBGrid;
    Query1: TQuery;
    DataSource1: TDataSource;
    Database1: TDatabase;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private 宣言 }
     plDBGrid1 : TplDBGrid;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Types,
  plChgCompo;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用のデータを TDBGrid に表示と関係処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // フォーム作成時にコンポーネントの入れ替え
  plDBGrid1 := TplDBGrid(ChangeComponent(DBGrid1, TplDBGrid));

  // データベースの設定
  Query1.Close;
  Database1.Connected := False;
  Database1.DatabaseName := 'DBDEMOS';
  Database1.TransIsolation := tiDirtyRead;

  DataSource1.DataSet  := Query1;
  plDBGrid1.DataSource := DataSource1;
  Query1.DatabaseName  := Database1.DatabaseName;

  // 更新可能にする
  Query1.RequestLive := True;

  // データベースからデータを取得して表示
  Query1.SQL.Clear;
  Query1.SQL.Add('select CustNo, Company, State, City from customer.db');
  Query1.Open;
  plDBGrid1.Columns[1].Width := 140;
  plDBGrid1.Columns[2].Width := 110;

  // トランザクション開始
  Database1.StartTransaction;

  ActiveControl := plDBGrid1;
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // テストなのでコミット (更新) しない
  Database1.Rollback;

  // データベースを閉じる
  Query1.Close;
  Database1.Close;
end;

{ TplDBGrid }

//=============================================================================
//  TDBGirdのGetEditStyle メソッド処理
//  ボタンのスタイルを決定する際に,自動的に実行されるメソッド
//  どの項目 (列) にどのボタンを表示するかを設定する
//=============================================================================
function TplDBGrid.GetEditStyle(ACol, ARow: Integer): TEditStyle;
begin
  if SelectedField.FieldName = 'Company' then begin
    Result := esEllipsis;
  end else
  if SelectedField.FieldName = 'State' then begin
    Result := esPickList;
  end else begin
    Result := inherited GetEditStyle(ACol, ARow);
  end;
end;

//=============================================================================
//  TDBGridのCreateEditor メソッド処理
//  セルが編集状態になると自動的に実行されるメソッド
//  ここで,TInplaceEditList のインスタンスを生成する
//  TInplaceEditList の継承クラスを定義しておくとそれが自動的に使用される
//=============================================================================
function TplDBGrid.CreateEditor: TInplaceEdit;
begin
  inherited;
  Result := TplDBGridInplaceEditList.Create(Self);
end;

{ TplDBGridInplaceEditList }

//=============================================================================
//  TInplaceEditList 継承クラスの CreateWnd メソッド
//  PickList(TListBox) のスタイルを設定
//  インプレイスエディタが生成する TListBox のクラス名は TPopupListbox
//  TStringGrid, TDBGrid, TValueListEditor も同じ
//
//  ItemHeight は行の高さ (ピクセル単位)
//  DropDownRows より項目数が少ない場合はこの値より少ない値になる
//
//  TInplaceEditList には OnDrawItem, OnClick 相当のイベントやメソッドはない
//  これらはドロップダウンで表示する TPopupListBox (TListBox) のイベント
//  他のイベントも必要なら定義して使用可能
//=============================================================================
procedure TplDBGridInplaceEditList.CreateWnd;
begin
  inherited;

  // TPopupListboxの継承元はTCustomListBox
  with TListBox(PickList) do begin
    ItemHeight  := 20;
    BevelKind   := bkFlat;
    BevelWidth  := 1;
    BevelInner  := bvNone;
    BevelOuter  := bvRaised;
    Style       := lbOwnerDrawFixed;
    IntegralHeight := True;
    DropDownRows   := 7;

    // カスタム描画用とクリックイベント等の定義.不要なら設定不要
    OnDrawItem := PickListDrawItemEvent;
    OnClick    := PickListClickEvent;
  end;
end;

//=============================================================================
//  TInplaceEditList クラスの DropDown メソッド処理
//  TListBox が表示される時に発生するメソッド
//  CreateWnd の時点では設定できない内容を設定
//
//  一般的にドロップダウンのリストは [Alt] + [↓] でも表示できる
//=============================================================================
procedure TplDBGridInplaceEditList.DropDown;
var
  LDBGrid   : TplDBGrid;
  LListBox  : TListBox;
  LCellRect : TRect;
  LItemIdx  : Integer;
begin
  inherited;

  LDBGrid  := TplDBGrid(Grid);
  LListBox := TListBox(PickList);

  // 行と列番号からセルの矩形領域を取得
  LCellRect := LDBGrid.CellRect(LDBGrid.Col, LDBGrid.Row);

  // TListBox の背景色
  LListBox.Color := $00FEF8E7;

  // TListBox の表示幅
  LListBox.Width := LCellRect.Right - LCellRect.Left;

  // 表示開始時に選択状態にする項目番号 (0 ベース)
  // セルの表示と同じ文字列を選択状態にする
  LItemIdx := LListBox.Items.IndexOf(LDBGrid.SelectedField.Text);
  if LItemIdx < 0 then LItemIdx := 0;
  LListBox.ItemIndex := LItemIdx;
end;

//=============================================================================
//  DoGetPickListItems メソッド処理
//  リストの項目作成が必要になった時に実行されるメソッド
//  ここでリストの項目の文字列を作成
//
//  TDBGrid にはこのメソッドに相当するイベントはない
//=============================================================================
procedure TplDBGridInplaceEditList.DoGetPickListItems;
var
  LDBGrid : TplDBGrid;
begin
  inherited;

  LDBGrid := TplDBGrid(Grid);

  if LDBGrid.SelectedField.FieldName = 'State' then begin
    with PickList.Items do begin
      BeginUpdate;
      Clear;
      Add('東京都');
      Add('埼玉県');
      Add('神奈川県');
      Add('群馬県');
      Add('栃木県');
      Add('茨城県');
      Add('千葉県');
      Add('長野県');
      Add('山梨県');
      Add('岐阜県');
      Add('新潟県');
      Add('福島県');
      Add('山形県');
      EndUpdate;
    end;
  end;
end;

//=============================================================================
//  CloseUp メソッド処理
//  TListBox が閉じた時に発生する
//  ここでデータベースのデータを更新する
//  TInplaceEditList を自前て処理する場合はデータは自動で更新されない
//  インプレイスエディタの値は更新される
//
//  Accept = False の時は [ESC] や他のウィンドウをアクティブにして閉じた場合
//=============================================================================
procedure TplDBGridInplaceEditList.CloseUp(Accept: Boolean);
var
  LListBox : TListBox;
  LDBGrid  : TplDBGrid;
  LDataSet : TDataSet;
  LIndex   : Integer;
begin
  inherited;
  if not Accept then Exit;

  LListBox := TListBox(PickList);
  LDBGrid  := TplDBGrid(Grid);
  LDataSet := LDBGrid.DataSource.DataSet;

  LIndex := LListBox.ItemIndex;
  if LIndex < 0 then Exit;

  if LDBGrid.SelectedField.FieldName = 'State' then begin
    LDataSet.Edit;
    LDataSet.FieldByName('State').AsString := LListBox.Items[LIndex];
    LDataSet.Post;
  end;

  // 入力キャレットを文字列の最後尾に移動
  LDBGrid.InplaceEditor.SelStart := Length(LDBGrid.SelectedField.Text);

  // 以下のコードを実行した場合は上のコードは不要
  // ドロップダウンリストを閉じた後も編集が必要な場合は以下は不要
//  LDBGrid.EditorMode := False;
end;

//=============================================================================
//  省略記号のボタンをクリックすると実行されるメソッドの処理
//  セルの左下の座標値からダイアログを表示
//
//  TInplaceEditList の継承クラスを生成しているので
//  元の TDBGrid.OnEditButtonClick イベントは発生しないのでここで処理
//=============================================================================
procedure TplDBGridInplaceEditList.DoEditButtonClick;
var
  LDBGrid   : TplDBGrid;
  LPos      : TPoint;
  LCellRect : TRect;
  LDialog   : TForm;
  LText     : string;
begin
  inherited;

  LDBGrid := TplDBGrid(Grid);

  if Assigned(LDBGrid.OnEditButtonClick) then begin
    LDBGrid.OnEditButtonClick(Self);
    Exit;
  end;

  if LDBGrid.SelectedField.FieldName = 'Company' then begin
    LText := LDBGrid.SelectedField.FieldName + ' の' + sLineBreak
           + '省略記号ボタンクリック';

    LDialog := CreateMessageDialog(LText, mtInformation, [mbOK], mbOK);
    try
      LCellRect := LDBGrid.CellRect(LDBGrid.Col, LDBGrid.Row);
      LPos := LDBGrid.ClientToScreen(Point(LCellRect.Left, LCellRect.Bottom));

      LDialog.Left  := LPos.X;
      LDialog.Top   := LPos.Y + 5;
      LDialog.Width := 250;
      // ダイアログを表示
      LDialog.ShowModal;
    finally
      FreeAndNil(LDialog);
    end;
  end;

  // ダイアログを閉じた後も編集が必要な場合は以下は不要
//  LDBGrid.EditorMode := False;
end;

//-----------------------------------------------------------------------------
//  TListBox の OnClick イベント
//  ドロップダウンのリスト選択項目が変わった時に発生する
//  クリックで TListBox を閉じた時も発生する
//-----------------------------------------------------------------------------
procedure TplDBGridInplaceEditList.PickListClickEvent(Sender: TObject);
begin
//
end;

//=============================================================================
//  TListBox 上でのマウスホイール操作の処理
//  この処理がないとマウスホイール操作でリストが閉じてしまう
//=============================================================================
procedure TplDBGridInplaceEditList.WMMouseWheel(var Message: TWMMouseWheel);
var
  LListBox : TListBox;
begin
  LListBox := TListBox(PickList);

  if LListBox.MouseInClient then begin
    if Message.WheelDelta > 0 then begin
      LListBox.Perform(WM_VSCROLL, MakeWParam(SB_LINEUP, 0), 0);
    end else begin
      LListBox.Perform(WM_VSCROLL, MakeWParam(SB_LINEDOWN, 0), 0);
    end;
  end else begin
    // TListBox 領域外でのマウスホイール操作で TListBox を閉じたい場合
    // 以下のコードを実行する
//    inherited;
  end;
end;

//-----------------------------------------------------------------------------
//  TListBox の OnDrawItem イベント
//  カスタム描画が必要な場合に利用する
//  このイベントを使用すると全ての描画を行う必要がある
//  デフォルトの描画を使用する場合は削除する
//
//  選択項目のテキスト描画の色 (TFont.Color) のデフォルトは clWhite
//-----------------------------------------------------------------------------
procedure TplDBGridInplaceEditList.PickListDrawItemEvent(Control: TWinControl;
  Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  LListBox   : TCustomListBox;
  LRect      : TRect;
  LTempColor : TColor;
begin
  LListBox := TCustomListBox(Control);

  LTempColor := LListBox.Canvas.Font.Color;

  // カスタム描画の場合,まず背景を塗り潰す
  if odSelected in State then begin
    // 選択行の背景色と文字の色
    LListBox.Canvas.Brush.Color := clHighlight;
    LListBox.Canvas.Font.Color  := clWhite;
  end;
  LRect := Rect;
  LListBox.Canvas.FillRect(LRect);

  LRect := Rect;
  InflateRect(LRect, -3, 0);
  DrawText(LListBox.Canvas.Handle,
           PChar(LListBox.Items[Index]),
           -1,
           LRect,
           DT_SINGLELINE or DT_LEFT or DT_VCENTER);

  LListBox.Canvas.Font.Color := LTempColor;

  //  フォーカスの枠線を描画しない場合
  // 現在の TBrush.Color で枠線を重ね描画
  if odFocused in State then begin
    LRect := Rect;
    DrawFocusRect(LListBox.Canvas.Handle, LRect);
  end;
end;

end.




04_TDBGrid のドロップダウンリストに別の TListBox を使用


インプレイスエディタが提供しているドロップダウンではなく,別のリスト表示用のコントロールを使用する例です.このサンプルは,拙作の,ポップアップ表示する TListBox を使用する例です.

別のウィンドウを表示する場合は, TInplaceEditList.DropDown メソッドで表示します.ポップアップ表示する TListBox をこのメソッド内で表示するようにしています.TDBGrid のデフォルトのドロップダウンリストは表示しないので,inherited を削除しています.

このような自前のウィンドウを表示する場合も TInplaceEditList の機能を使用しますから,TDBGrid と TInplaceEditList の継承クラスを作成しないと処理できません.



図5
選択リスト表示用ボタンクリックで自前の選択リストを表示

リスト3
選択リスト表示用のボタンクリックで,自前の選択リストを表示する例
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Bde.DBTables,
  Vcl.Grids, Vcl.DBGrids, Vcl.StdCtrls, Vcl.ExtCtrls, plPopupListBox;

type
  TplDBGridInplaceEditList  = class(TInplaceEditList)
  protected
    procedure DropDown; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  end;

  TplDBGrid = class(TDBGrid)
  protected
    FPopUpListBox : TplPopupListBox;
    procedure SetParent(AParent: TWinControl); override;
    procedure OnplPopupListBox1CloseUp(Sender: TObject);
  public
    destructor Destroy; override;
    function CreateEditor: TInplaceEdit; override;
    function GetEditStyle(ACol: Integer; ARow: Integer): TEditStyle; override;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    DBGrid1: TDBGrid;
    Query1: TQuery;
    DataSource1: TDataSource;
    Database1: TDatabase;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private 宣言 }
     plDBGrid1 : TplDBGrid;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Types,
  plChgCompo;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用のデータを TDBGrid に表示と関係処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // フォーム作成時にコンポーネントの入れ替え
  plDBGrid1 := TplDBGrid(ChangeComponent(DBGrid1, TplDBGrid));

  // データベースの設定
  Query1.Close;
  Database1.Connected := False;
  Database1.DatabaseName := 'DBDEMOS';
  Database1.TransIsolation := tiDirtyRead;

  DataSource1.DataSet  := Query1;
  plDBGrid1.DataSource := DataSource1;
  Query1.DatabaseName  := Database1.DatabaseName;

  // 更新可能にする
  Query1.RequestLive := True;

  // データベースからデータを取得して表示
  Query1.SQL.Clear;
  Query1.SQL.Add('select CustNo, Company, State, City from customer.db');
  Query1.Open;
  plDBGrid1.Columns[1].Width := 140;
  plDBGrid1.Columns[2].Width := 110;

  // トランザクション開始
  Database1.StartTransaction;

  ActiveControl := plDBGrid1;
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // テストなのでコミット (更新) しない
  Database1.Rollback;

  // データベースを閉じる
  Query1.Close;
  Database1.Close;
end;


{ TplDBGrid }

//=============================================================================
//  TplDBGrid のデコンストラクタ
//=============================================================================
destructor TplDBGrid.Destroy;
begin
  FreeAndNil(FPopUpListBox);
  inherited;
end;

//=============================================================================
//  TplDBGrid の SetParent メソッド処理
//  ポップアップ表示する TListBox のインスタンスを生成
//  拙作の TplPopupListBox コンポーネントを使用
//
//  選択リストの選択行の既定の値はおそらく以下の値
//    FPopUpListBox.SelBackColor := clHighlight;
//    FPopUpListBox.SelFontColor := clWhite;
//=============================================================================
procedure TplDBGrid.SetParent(AParent: TWinControl);
begin
  inherited;
  if AParent = nil then Exit;

  if not Assigned(FPopUpListBox) then begin
    // ポップアップ表示する TListBox の生成と設定
    FPopUpListBox := TplPopupListBox.Create(Self);
    FPopUpListBox.Parent := Self;

    // ポップアップ TListBox が閉じる時に発生させるイベントの設定
    FPopUpListBox.OnCloseUp := OnplPopupListBox1CloseUp;

    FPopUpListBox.DefaultDraw  := False;
    FPopUpListBox.FocusRect    := True;
    FPopUpListBox.SelBackColor := clWebBurlywood;

   // Style プロパティの値を lbOwnerDrawFixed にして ItemHeight の値を変更
    FPopUpListBox.Style := lbOwnerDrawFixed;
    FPopUpListBox.ItemHeight := Trunc(FPopUpListBox.Canvas.TextHeight('Hg') * 1.5);
  end;
end;

//-----------------------------------------------------------------------------
//  TplPopupListBoxコンポのOnCloseUpイベント処理
//  項目をクリックすると TListBox が非表示になり,このイベントが発生する
//  ここで選択した TListBox の項目を取得してレコードのデータを更新する
//-----------------------------------------------------------------------------
procedure TplDBGrid.OnplPopupListBox1CloseUp(Sender: TObject);
var
  LIndex : Integer;
begin
  SetFocus;

  LIndex := FPopUpListBox.ItemIndex;
  if LIndex < 0 then Exit;
//
  // 選択中のレコードのデータを更新
  if SelectedField.FieldName = 'State' then begin
    DataSource.DataSet.Edit;
    SelectedField.AsString := FPopUpListBox.Items[LIndex];
    DataSource.DataSet.Post;

    // 入力キャレットの位置を変更
    // 必要なければ削除する
    if InplaceEditor <> nil then begin
      InplaceEditor.SelStart := Length(InplaceEditor.Text);
    end;

    // 編集モードを終了させる場合は以下のコードを実行
    // 入力後 [Enter] 操作なしで [TAB] 等で次のセルに移動する場合等
    EditorMode := False;
  end;
end;

//=============================================================================
//  TDBGirdのGetEditStyle メソッド処理
//  ボタンのスタイルを決定する際に,自動的に実行されるメソッド
//  ここでボタンのスタイルを設定する
//=============================================================================
function TplDBGrid.GetEditStyle(ACol, ARow: Integer): TEditStyle;
begin
  if SelectedField.FieldName = 'State' then begin
    Result := esPickList;
  end else begin
    Result := inherited GetEditStyle(ACol, ARow);
  end;
end;

//=============================================================================
//  TDBGridのCreateEditor メソッド処理
//  セルが編集状態になると自動的に実行されるメソッド
//  ここで,TInplaceEditList のインスタンスを生成する
//=============================================================================
function TplDBGrid.CreateEditor: TInplaceEdit;
begin
  inherited;
  Result := TplDBGridInplaceEditList.Create(Self);
end;

{ TplDBGridInplaceEditList }

//=============================================================================
//  TInplaceEditList クラスの DropDownメソッド処理
//  PickListボタン[↓]で別のウィンドウを表示
//  そのために,継承元のコードを実行しないようにする (inheritedを削除する)
//=============================================================================
procedure TplDBGridInplaceEditList.DropDown;
var
  LDBGrid   : TplDBGrid;
  LCellRect : TRect;
  LShowPos  : TPoint;
  LItemIdx  : Integer;
begin
  LDBGrid := TplDBGrid(Grid);

  // 行と列の値からセルの矩形領域 TRect の値を取得
  LCellRect := LDBGrid.CellRect(LDBGrid.Col, LDBGrid.Row);

  if LDBGrid.SelectedField.FieldName = 'State' then begin
    with LDBGrid.FPopUpListBox.Items do begin
      BeginUpdate;
      Clear;
      Add('富山県');
      Add('岩手県');
      Add('山形県');
      Add('長野県');
      Add('山梨県');
      Add('埼玉県');
      Add('群馬県');
      Add('新潟県');
      EndUpdate;
    end;

    LDBGrid.FPopUpListBox.Width := LCellRect.Right - LCellRect.Left;
    LDBGrid.FPopUpListBox.IntegralHeight := True;
    LDBGrid.FPopUpListBox.DropDownRows := 5;
//
//    // 位置を指定してポップアップ TListBox を表示
    LShowPos := LDBGrid.ClientToScreen(Point(LCellRect.Left, LCellRect.Bottom));
    LDBGrid.FPopUpListBox.ShowBeneath(LShowPos.X, LShowPos.y);

    // セルの表示と同じ文字列を選択状態にする
    LItemIdx := LDBGrid.FPopUpListBox.Items.IndexOf(LDBGrid.SelectedField.Text);
    if LItemIdx < 0 then LItemIdx := 0;
    LDBGrid.FPopUpListBox.ItemIndex := LItemIdx;
  end;
end;

//=============================================================================
//  [Enter] キーで選択リストを表示
//
//  [Enter] キーで表示すると CloseUp で自動的に編集モードが OFF になる
//  編集モードにするには CloseUp で明示的に EditorMode := True; を実行する
//=============================================================================
procedure TplDBGridInplaceEditList.KeyDown(var Key: Word; Shift: TShiftState);
var
  LDBGrid : TplDBGrid;
begin
  inherited;

  LDBGrid := TplDBGrid(Grid);

  if Key = VK_RETURN then begin
    if LDBGrid.SelectedField.FieldName = 'State' then begin
      if not LDBGrid.FPopUpListBox.Visible then begin
        DropDown;
      end;
    end;
  end;
end;

end.