Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2014年11月26日] [更新 2019年01月04日] Delphi サンプルプログラム集
165_TCalendar 等を配置したフォームをポップアップ表示
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
165_CalendarForm.zip [4,312 KB] 2019年01月04日版 (EXE 同梱)



  • 2014年12月09日
  • 必要なファイルをプロジェクトに追加するのはトラブル発生の要因のようなので、全てライブラリパスに置いて使用しする方法に変更
  • TplDropStringGrid コンポーネントを最新版に差し替え
  • 2016年08月22日
  • 「指定した日が祝祭日かを調査する関数」の修正に伴う更新
  • 2019年01月04日
  • 「指定した日が祝祭日かを調査する関数」の修正に伴う更新




TCalendar とボタン類を配置したフォーム ( カレンダフォーム )


TCalendar コンポーネントをポップアップ表示するためのサンプルです。
実際には前の年月や次の年月等の操作を考えて、ボタン類も配置したフォームを、ボタンクリック等でポップアップ表示するようにしています。これを「カレンダフォーム」と呼ぶことにします。

[164] のサンプルでは、日付入力用のエディットコントロールとボタンを一体化したコンポーネントを作成しました。[164] に掲載しているコンポーネントは、内部でフォームを生成していますが、本ページのサンプルは、そのフォーム部分だけ、日付ピッカーの部分だけということになります。
コンポーネントにはしていません。単なるフォームです。したがって、自由にデザインを変更することができます。



[備考 1]
ロケールの日付の書式設定によっては、書式を伴う日付の処理が正しく行われない場合があります。


[備考 2]
FireMonkey であれば、フォームの代わりに TPopup コンポーネントを使用して、同様な機能を実装することができます。



図1
カレンダフォーム
  • これを、ボタンクリック等でポップアップ表示する

本ページのサンプルでは、以下のユニット類を使用しています。
添付のプロジェクトを使用するには、これらのファイルを、ライブラリパスの通ったフォルダに置いて使用します。


ファイル名 機能
plCalendarFormUnit.dfm
plCalendarFormUnit.pas
カレンダフォームの本体
添付のサンプルでプロジェクトに追加しているのは pas ファイル
plDateEditIsHoliday.pas 国民の祝日を処理するためのユニット
DEKO 氏が提供しているコードをユニットにしたもの
サンプルの [164] に添付の同名のユニットと中身は全く同じ
plDropStringGrid.pas TStringGrid にドロップダウンと参照ボタンの機能を追加したもの
TStringGrid を使用するサンプルだけで使用
plChgCompo.pas 実行時にコンポーネントを置き換えるためのユニット
サンプルプログラム集の他のサンプルでも使用している同名のユニットと中身は全く同じ




01_カレンダフォームの基本的な使用方法


カレンダフォームの基本的な使用方法のテストです。
ボタンをクリックすると、現在 TEdit に表示されている値を日付に変換して、カレンダの日付をそれと同じにします。カレンダの日付を、年月移動ボタン、あるいは矢印キーで移動すると、リアルタイムでその日付を TEdit に表示します。

実際に操作してみると分かりますが、カレンダフォームは、ボタン操作では閉じることができません。このようなポップアップのウインドウをボタンで表示する場合、ボタンの再クリックで閉じるのが普通です。つまり、ボタンはトグル操作となっているのが一般的です。



図2
設計時画面
  • TEdit と TSpeedButton を配置
図3
ボタンクリックでカレンダフォームを表示
図4
マウス左ボタンを押す
  • カレンダフォームが閉じる
図5
マウス左ボタンを放す
  • カレンダフォームが表示されてしまう
  • つまり、ポンタクリックではカレンダフォームを閉じることはできない

リスト1
カレンダフォームの基本的な使い方のコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, StdCtrls, Buttons, plCalendarFormUnit, ExtCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    CalForm : TplCalendarForm;
    procedure OnCalendarChangeEvent(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.SelStart   := 1;
  Edit1.AutoSelect := False;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', Now);
end;

//=============================================================================
//  ボタンをクリックしたらカレンダフォームを表示
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  APos     : TPoint;
  CalDate  : TDate;
begin
  //カレンダフォームが生成されていないなら生成して、イベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.Calendar.OnChange := OnCalendarChangeEvent;
  end;

  if CalForm.Visible then begin
    CalForm.Close;
  end else begin
    //表示位置をスクリーン座標値に変換
    APos := Self.ClientToScreen(Point(Edit1.Left, Edit1.Top + Edit1.Height));
    CalForm.ShowPopUp(APos.X, APos.Y, Edit1.Height);
    //Edit1の値を日付に変換してカレンダフォームのカレンダにセット
    try
      CalDate := VarToDateTime(Edit1.Text);
    except
      CalDate := Trunc(Now);
    end;
    CalForm.Calendar.CalendarDate := CalDate;
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのTCalendarのOnChangeイベント処理
//  カレンダで選択中の日付をリアルタイムで取得する場合
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarChangeEvent(Sender: TObject);
var
  CalDate : TDate;
begin
  CalDate := CalForm.Calendar.CalendarDate;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', CalDate);
end;

end.




02_カレンダフォームをボタンクリックで閉じる ( トグル操作 )


前項のサンプルは、ボタンクリックでカレンダフォームが閉じることができませんでした。
ポップアップウィンドウは、自分以外の場所がアクティブになると閉じるのが普通です。カレンダフォームもそうなっています。したがって、ボタンを押下するとカレンダフォームは閉じます。しかし、ボタンを放すと「クリック」になりますから、当然カレンダフォームを表示します。
ボタンを押下した状態でマウスカーソルを別の場所に移動すれば、カレンダフォームを閉じることができます。

この現象を回避するために、カレンダフォームに、次のメソッドを用意しました。
このメソッドは、指定した領域でマウスが押下された場合、非アクティブの検出から除外します。ただし、この方法は、ボタン操作をコードで実行した場合は機能しません。

次のコードは、このメソッドを使用したサンプルです。これ以降のサンプルでは、全てのこのメソッドを使用しています。



リスト2
カレンダフォームで非アクティブを検出する領域から除外するメソッド
    function SetExceptRect(AControl: TWinControl; ARect: TRect): Boolean; overload;
    function SetExceptRect(AControl: TWinControl;
      ALeft, ATop, ARight, ABottom: Integer): Boolean; overload;


図6
ボタンをクリック
  • カレンダフォームが表示される
図7
またボタンをクリックする
  • カレンダフォームが閉じる

リスト3
カレンダフォームを表示するボタンをトグル操作にする
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, StdCtrls, Buttons, plCalendarFormUnit, ExtCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    CalForm : TplCalendarForm;
    procedure OnCalendarChangeEvent(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.SelStart   := 1;
  Edit1.AutoSelect := False;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', Now);
end;

//=============================================================================
//  ボタンをクリックしたらカレンダフォームを表示
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  APos    : TPoint;
  CalDate : TDate;
begin
  //カレンダフォームが生成されていなければ生成して、イベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.Calendar.OnChange := OnCalendarChangeEvent;
  end;

  if CalForm.Visible then begin
    CalForm.Close;
  end else begin
    //表示位置をスクリーン座標値に変換
    APos := Self.ClientToScreen(Point(Edit1.Left, Edit1.Top + Edit1.Height));
    CalForm.ShowPopUp(APos.X, APos.Y, Edit1.Height);
    //Edit1の値を日付に変換してカレンダフォームのカレンダにセット
    try
      CalDate := VarToDateTime(Edit1.Text);
    except
      CalDate := Trunc(Now);
    end;
    CalForm.Calendar.CalendarDate := CalDate;

    //非アクティブ検出を除外する矩形領域の設定
    //前回と位置が異なる場合があるかも知れないのでShowの後に実行
    //TSpeedButtonはTWinControlではないので、その親を第1引数に指定する
    CalForm.SetExceptRect(SpeedButton1.Parent, SpeedButton1.BoundsRect);
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのTCalendarのOnChangeイベント処理
//  カレンダで選択中の日付をリアルタイムで取得する場合
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarChangeEvent(Sender: TObject);
var
  CalDate : TDate;
begin
  CalDate := CalForm.Calendar.CalendarDate;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', CalDate);
end;

end.




03_TMaskEdit での使用例


上のサンプルでは、日付の表示と入力に TEdit を使用しましたが、指定位置だけを入力可能にするコントロールとして TMaskEdit があります。その TMaskEdit を使用したサンプルです。
TMaskEdit を使用している以外は、基本的に、上のサンプルと動作に変わりはありません。

TMaskEditは、指定桁数の値を入力すると、自動で次のフィールドに移動してくれます。指定桁数未満の入力で次のフィールドに移動するには、矢印キーを使用しなければなりません。この意味では、年月日の入力桁数は必須とした方が便利と言えます。



図8
設計時画面
  • TMaskEdit と TSpeedButton を配置
図9
実行開始画面
  • 一応、本日の日付を表示
図10
ボタンクリックでカレンダフォーム表示
  • カレンダの日付が、TMaskEdit のと同じになる

リスト4
TMaskEdit コンポーネントと組み合わせてカレンダフォームを使用する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, StdCtrls, Buttons, plCalendarFormUnit, ExtCtrls, Mask;

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    MaskEdit1: TMaskEdit;
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    CalForm : TplCalendarForm;
    procedure OnCalendarChangeEvent(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  MaskEdit1.Text       := '';
  MaskEdit1.EditMask   := '!0000年00月00\日;1; ';
  MaskEdit1.SelStart   := 0;
  MaskEdit1.AutoSelect := False;
end;

//=============================================================================
//  ボタンをクリックしたらカレンダフォームを表示
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  APos     : TPoint;
  CalDate  : TDate;
begin
  //カレンダフォームが生成されていなければ生成して、イベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.Calendar.OnChange := OnCalendarChangeEvent;
  end;

  if CalForm.Visible then begin
    CalForm.Close;
  end else begin
    //表示位置をスクリーン座標値に変換
    APos := Self.ClientToScreen(Point(MaskEdit1.Left,
                                      MaskEdit1.Top + MaskEdit1.Height));
    CalForm.ShowPopUp(APos.X, APos.Y, MaskEdit1.Height);
    //MaskEdit1の値を日付に変換してカレンダフォームのカレンダにセット
    try
      CalDate := VarToDateTime(MaskEdit1.Text);
    except
      CalDate := Trunc(Now);
    end;
    CalForm.Calendar.CalendarDate := CalDate;

    //SpeedButton1の領域を、非アクティブ検出領域から除外
    //前回と位置が異なる場合があるかも知れないのでShowの後に実行
    //TSpeedButtonはTWinControlではないので、その親を第1引数に指定する
    CalForm.SetExceptRect(SpeedButton1.Parent, SpeedButton1.BoundsRect);
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのTCalendarのOnChangeイベント処理
//  カレンダで選択中の日付をリアルタイムで取得する場合
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarChangeEvent(Sender: TObject);
var
  CalDate : TDate;
begin
  CalDate := CalForm.Calendar.CalendarDate;
  MaskEdit1.Text := FormatDateTime('yyyy年 MM月 dd日', CalDate);
end;

end.




04_TStringGrid での使用例


TStringGRid での使用例です。TStringGrid で日付を入力する際、カレンダフォームを表示して、日付をビックアップします。
下記のリンクでドロップダウン用のボタンと、参照ボタンが表示可能な TStringGrid コンポーネントを提供しています。それを使用したサンプルです。

TStringGrid のセルの編集時には、インプレイスエディタが起動しています。TStringGrid のセルの値を取得したり、カレンダの日付を TStringGrid のセルに文字列として代入する時は、このインプレイスのエディットコントロールに対して行います。

入力マスクは TStringGrid の OnGetEditMaskイベントで設定しています。
本サンプルでは、一度でもカレンダフォームを表示したセルには、年月日の文字列がセットされます。一度もカレンダフォームを表示しないセルは空白のままです。
この TStringGrid のセルの描画は、OnDrawCell イベントで制御できます。必要であれば追加することになります。



[備考]
マスクの設定は、実際にフォームに TMaskEdit コンポーネントを配置して確認すると便利です。
TMaskEdit コンポーネントのポップアップメニューの [入力マスクの設定]、あるいはオブジェクトインスペクタの EditMask から設定ダイアログを表示することができます。



図11
設計時画面
図12
実行開始画面
図13
ドロップダウン用のボタンをクリック
  • TStringGrid のセルの値を日付に変換して、カレンダの日付とする
  • カレンダの日付が、リアルタイムで TStringGrid のセルに表示される
図14
参照ボタンをクリック
  • 動作はドロップダウン用ボタンをクリックした場合と同じ
図15
カレンダフォームを表示していない状態
  • カレンダフォームを表示したことがないセルは空白状態
  • 一度でもカレン多フォームを表示したセルは年月日の文字列がセットされる
  • この表示状態は TStringGrid の OnDrawCell イベントで制御できる

リスト5
TStringGrid でカレンダフォームを利用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Grids, StdCtrls, plDropStringGrid, plCalendarFormUnit;

type
  TplMyDropStringGrid = class(TplDropStringGrid)
  protected
    function CanEditShow: Boolean; override;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    StringGrid1: TStringGrid;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    plDropGrid1 : TplMyDropStringGrid;
    CalForm : TplCalendarForm;
    procedure OnGetEditStyleEvent(TSender: TObject; ACol,
      ARow: Integer; var EditStyle: TEditStyle);
    procedure OnEditButtonClickEvent(Sender: TObject; ACol,
      ARow: Integer; ARect: TRect);
    procedure OnDropDownListEvent(Sender: TObject; ACol,
      ARow: Integer; ARect: TRect; var DefaultAction: Boolean);
    procedure OnEditMaskEvent(Sender: TObject; ACol, ARow: Integer;
      var Value: string);
    procedure OnCalendarChangeEvent(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses plChgCompo;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  カレンダフォームを生成
//  テスト用に列数と列幅等を設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //カレンダフォームを生成してイベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.Calendar.OnChange := OnCalendarChangeEvent;
  end;

  //フォームに配置したStringGrid1をplDropGrid(TplMyDropStringGrid)に置き換える
  plDropGrid1 := TplMyDropStringGrid(ChangeComponent(StringGrid1, TplMyDropStringGrid));


  //編集可能にしないとインプレイスエディタは起動しない
  plDropGrid1.Options := plDropGrid1.Options + [goEditing];

  plDropGrid1.DefaultDrawing := True;
  //編集可能にしないとインプレイスエディタは起動しない
  plDropGrid1.Options := plDropGrid1.Options + [goEditing];

  plDropGrid1.DefaultColWidth  := 130;
  plDropGrid1.DefaultRowHeight := 24;
  //左端の列幅設定
  plDropGrid1.ColWidths[0]  := 10;

  plDropGrid1.FixedCols := 1;
  plDropGrid1.FixedRows := 1;
  plDropGrid1.ColCount  := 3;
  plDropGrid1.RowCount  := 4;

  //テスト用の値をセット(固定行)
  plDropGrid1.Cells[1,0] := '開始年月日';
  plDropGrid1.Cells[2,0] := '終了年月日';

  //各種プロパティの設定(TplDropStringGridで追加したもの)
  plDropGrid1.InplaceButtonWidth := 18;
  plDropGrid1.DropListRowCount   := 10;
  plDropGrid1.DropListItemHeight := 17;
  plDropGrid1.DropListColor      := clWindow;

  //イベントの設定(標準のTStringGridにあるもの)
  plDropGrid1.OnGetEditMask := OnEditMaskEvent;

  //イベントの設定(TplDropStringGridで追加したもの)
  plDropGrid1.OnGetEditStyle     := OnGetEditStyleEvent;
  plDropGrid1.OnEditButtonClick  := OnEditButtonClickEvent;
  plDropGrid1.OnDropDownList     := OnDropDownListEvent;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnGetEditStyleイベント処理
//  列単位でボタンの種類を設定
//-----------------------------------------------------------------------------
procedure TForm1.OnGetEditStyleEvent(TSender: TObject; ACol,
  ARow: Integer; var EditStyle: TEditStyle);
begin
  if ACol = 1 then EditStyle := esPickList;
  if ACol = 2 then EditStyle := esEllipsis;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnDropDownListイベント処理
//  ドロップダウンのボタンをクリックした時のイベント
//-----------------------------------------------------------------------------
procedure TForm1.OnDropDownListEvent(Sender: TObject; ACol, ARow: Integer;
  ARect: TRect; var DefaultAction: Boolean);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (plDropGrid1.Col = 1) then begin
    //Falseにするとドロップダウンのリストを表示しない
    DefaultAction := False;

    if CalForm.Visible then begin
      CalForm.Close;
    end else begin
      CalForm.ShowPopUp(TplDropStringGrid(Sender),
                        ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //ドロップダウンボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right - plDropGrid1.InplaceButtonWidth;
      CalForm.SetExceptRect(TplDropStringGrid(Sender), ARect);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnEditButtonClickイベント処理
//  参照ボタン(省略記号ボタン)をクリックした時の実行内容
//-----------------------------------------------------------------------------
procedure TForm1.OnEditButtonClickEvent(Sender: TObject; ACol,
  ARow: Integer; ARect: TRect);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (plDropGrid1.Col = 2) then begin
    if CalForm.Visible then begin
      CalForm.Close;
    end else begin
      CalForm.ShowPopUp(TplDropStringGrid(Sender),
                        ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //参照ボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right - plDropGrid1.InplaceButtonWidth;
      CalForm.SetExceptRect(TplDropStringGrid(Sender), ARect);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのTCalendarのOnChangeイベント処理
//  カレンダで選択中の日付をリアルタイムで取得する場合
//  インプレイスエディタに値をセットする
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarChangeEvent(Sender: TObject);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (plDropGrid1.Col = 1) or (plDropGrid1.Col = 2) then begin
    InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
    if InpEditor <> nil then begin
      CalDate := CalForm.Calendar.CalendarDate;
      InpEditor.Text := FormatDateTime('yyyy年MM月dd日', CalDate);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TStringGrid(TplDropStringGrid)のOnGetEditMaskイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.OnEditMaskEvent(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  if (ACol = 1) or (ACol = 2) then begin
    Value := '!0000年00月00\日;1; ';
  end;
end;

{ TMyplDropStringGrid }

//-----------------------------------------------------------------------------
//  TplDropStringGridのCanEditShowメソッド処理
//  セルが編集モードになった時に自動的に実行される関数
//-----------------------------------------------------------------------------
function TplMyDropStringGrid.CanEditShow: Boolean;
begin
  Result := inherited CanEditShow;

  if InplaceEditor <> nil then begin
    //キャレットの位置をセルのテキストの最後尾にする
    //セルのテキストの長さではなく、インプレイスエディットのテキストの長さ
    InplaceEditor.SelStart := Length(InplaceEditor.Text);

    //セルの背景色
    if Col = 1 then begin
      InplaceEditor.Brush.Color := $00D0FDDA;
    end else begin
      InplaceEditor.Brush.Color := clWindow;
    end;
  end;
end;

end.




05_TStringGrid での使用例  -  カレンダフォームの OnClose イベント


前項のサンプルは、TComboBox のように、エディットコントロールに、リアルタイムで日付を表示する形式でした。
実際のドロップダウンのリストでの選択方式は、現在のセルの値は変更せずに、選択用のウィンドウが閉じた時に新しい値をセルに代入するのが一般的です。つまり、現在の値を確認しながら入力します。参照ボタンの方は元々ダイアログの表示用ですから、そうなります。
次のサンプルは、そのような仕様にした例です。

カレンダフォームの OnClose イベントで処理しています。カレンダフォームを [Enter] キーまたはマウスクリックで閉じた場合のみ、TStringGrid のセルの値を更新します。[ESC] キー等、その他の操作で閉じた時は、セルの値を更新しないようにしています。



図16
カレンダフォームを表示
  • セルの値は変化しない
図17
セルに値が入力済みの場合
  • 矢印キーや年月変更用のボタンをクリックしてもセルの値は変化しない
  • [Enter] キーまたはクリックでカレンダフォームを閉じると、セルの値が変わる

リスト6
TStringGrid でカレンダフォームを利用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Grids, StdCtrls, plDropStringGrid, plCalendarFormUnit;

type
  TplMyDropStringGrid = class(TplDropStringGrid)
  protected
    function CanEditShow: Boolean; override;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    StringGrid1: TStringGrid;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    plDropGrid1 : TplMyDropStringGrid;
    CalForm : TplCalendarForm;
    procedure OnGetEditStyleEvent(TSender: TObject; ACol,
      ARow: Integer; var EditStyle: TEditStyle);
    procedure OnEditButtonClickEvent(Sender: TObject; ACol,
      ARow: Integer; ARect: TRect);
    procedure OnDropDownListEvent(Sender: TObject; ACol,
      ARow: Integer; ARect: TRect; var DefaultAction: Boolean);
    procedure OnEditMaskEvent(Sender: TObject; ACol, ARow: Integer;
      var Value: string);
    procedure OnCalendarFormCloseEvent(Sender: TObject; var Action: TCloseAction);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses plChgCompo;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  カレンダフォームを生成
//  テスト用に列数と列幅等を設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //カレンダフォームを生成してイベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.OnClose := OnCalendarFormCloseEvent;
  end;

  //フォームに配置したStringGrid1をplDropGrid(TplMyDropStringGrid)に置き換える
  plDropGrid1 := TplMyDropStringGrid(ChangeComponent(StringGrid1, TplMyDropStringGrid));


  //編集可能にしないとインプレイスエディタは起動しない
  plDropGrid1.Options := plDropGrid1.Options + [goEditing];

  plDropGrid1.DefaultDrawing := True;
  //編集可能にしないとインプレイスエディタは起動しない
  plDropGrid1.Options := plDropGrid1.Options + [goEditing];

  plDropGrid1.DefaultColWidth  := 130;
  plDropGrid1.DefaultRowHeight := 24;
  //左端の列幅設定
  plDropGrid1.ColWidths[0]  := 10;

  plDropGrid1.FixedCols := 1;
  plDropGrid1.FixedRows := 1;
  plDropGrid1.ColCount  := 3;
  plDropGrid1.RowCount  := 4;

  //テスト用の値をセット(固定行)
  plDropGrid1.Cells[1,0] := '開始年月日';
  plDropGrid1.Cells[2,0] := '終了年月日';

  //各種プロパティの設定(TplDropStringGridで追加したもの)
  plDropGrid1.InplaceButtonWidth := 18;
  plDropGrid1.DropListRowCount   := 10;
  plDropGrid1.DropListItemHeight := 17;
  plDropGrid1.DropListColor      := clWindow;

  //イベントの設定(標準のTStringGridにあるもの)
  plDropGrid1.OnGetEditMask := OnEditMaskEvent;

  //イベントの設定(TplDropStringGridで追加したもの)
  plDropGrid1.OnGetEditStyle     := OnGetEditStyleEvent;
  plDropGrid1.OnEditButtonClick  := OnEditButtonClickEvent;
  plDropGrid1.OnDropDownList     := OnDropDownListEvent;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnGetEditStyleイベント処理
//  列単位でボタンの種類を設定
//-----------------------------------------------------------------------------
procedure TForm1.OnGetEditStyleEvent(TSender: TObject; ACol,
  ARow: Integer; var EditStyle: TEditStyle);
begin
  if ACol = 1 then EditStyle := esPickList;
  if ACol = 2 then EditStyle := esEllipsis;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnDropDownListイベント処理
//  ドロップダウンのボタンをクリックした時のイベント
//-----------------------------------------------------------------------------
procedure TForm1.OnDropDownListEvent(Sender: TObject; ACol, ARow: Integer;
  ARect: TRect; var DefaultAction: Boolean);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (ACol = 1) then begin
    //Falseにするとドロップダウンのリストを表示しない
    DefaultAction := False;

    if CalForm.Visible then begin
      CalForm.Close;
    end else begin
      CalForm.ShowPopUp(TplDropStringGrid(Sender),
                        ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //ドロップダウンボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right - plDropGrid1.InplaceButtonWidth;
      CalForm.SetExceptRect(TplDropStringGrid(Sender), ARect);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TplDropStringGridコンポのOnEditButtonClickイベント処理
//  参照ボタン(省略記号ボタン)をクリックした時の実行内容
//-----------------------------------------------------------------------------
procedure TForm1.OnEditButtonClickEvent(Sender: TObject; ACol,
  ARow: Integer; ARect: TRect);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (ACol = 2) then begin
    if CalForm.Visible then begin
      CalForm.Close;
    end else begin
      CalForm.ShowPopUp(TplDropStringGrid(Sender),
                        ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //参照ボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right - plDropGrid1.InplaceButtonWidth;
      CalForm.SetExceptRect(TplDropStringGrid(Sender), ARect);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのOnCloseイベント処理
//  日付の文字列は、セルではなくインプレイスエディタにセットする
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarFormCloseEvent(Sender: TObject;
  var Action: TCloseAction);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (plDropGrid1.Col = 1) or (plDropGrid1.Col = 2) then begin
    InpEditor := TplMyDropStringGrid(plDropGrid1).InplaceEditor;
    if InpEditor <> nil then begin
      //クリックまたは[Enter]で終了した場合のみ処理
      if CalForm.KindOfClose = VK_RETURN then begin
        CalDate := CalForm.Calendar.CalendarDate;
        InpEditor.Text := FormatDateTime('yyyy年MM月dd日', CalDate);
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TStringGrid(TplDropStringGrid)のOnGetEditMaskイベント処理
//-----------------------------------------------------------------------------
procedure TForm1.OnEditMaskEvent(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  if (ACol = 1) or (ACol = 2) then begin
    Value := '!0000年00月00\日;1; ';
  end;
end;

{ TMyplDropStringGrid }

//-----------------------------------------------------------------------------
//  TplDropStringGridのCanEditShowメソッド処理
//  セルが編集モードになった時に自動的に実行される関数
//-----------------------------------------------------------------------------
function TplMyDropStringGrid.CanEditShow: Boolean;
begin
  Result := inherited CanEditShow;

  if InplaceEditor <> nil then begin
    //キャレットの位置をセルのテキストの最後尾にする
    //セルのテキストの長さではなく、インプレイスエディットのテキストの長さ
    InplaceEditor.SelStart := Length(InplaceEditor.Text);

    //セルの背景色
    if Col = 1 then begin
      InplaceEditor.Brush.Color := $00D0FDDA;
    end else begin
      InplaceEditor.Brush.Color := clWindow;
    end;
  end;
end;

end.




06_TValueListEditor での使用例


TValueListEditor コンポーネントのドロップダウン用のボタンと、参照用のボタンクリックでカレン肌フォームを表示するサンプルです。
TValueListEditor では、内蔵のリストを表示しない場合、ドロップダウン用のボタンをクリックすると、入力フォーカスが TValueListEditor に移動してしまいます。そこで、強制的にカレンダフォームにフォーカスを移動するようにしています。これには TApplicationEvents の OnMessage イベントを使用しています。

本サンプルでは、一度でもカレンダフォームを表示したセルには、年月日の文字列がセットされます。
一度もカレンダフォームを表示しないセルは空白のままです。
この TValueListEditor のセルの描画は、OnDrawCell イベントで制御できます。必要であれば追加することになります。



[備考]
マスクの設定は、実際にフォームに TMaskEdit コンポーネントを配置して確認できます。TMaskEdit コンポーネントのポップアップメニューの [入力マスクの設定]、あるいはオブジェクトインスペクタの EditMask から設定ダイアログを表示することができます。



図18
設計時画面
  • TValueListEditor コンポーネントを配置
  • TApplicationEvents コンポーネントも配置
図19
起動開始画面
図20
参照用ボタンをクリック
  • カレンダフォームが表示される
  • [Enter] キーまたはクリックでカレンダフォームを閉じると、セルに日付が代入される
図21
ドロップダウン用のボタンをクリック
  • 動作仕様は、参照用のボタンをクリックした場合と同じ

リスト7
TValueListEditor での使用例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, Grids, ValEdit, ExtCtrls, plCalendarFormUnit,
  AppEvnts;

type
  TplValueListEditor = class(TValueListEditor)
  protected
    function CanEditShow: Boolean; override;
  end;

  TForm1 = class(TForm)
    ValueListEditor1: TValueListEditor;
    Panel1: TPanel;
    ApplicationEvents1: TApplicationEvents;
    procedure FormCreate(Sender: TObject);
    procedure ValueListEditor1EditButtonClick(Sender: TObject);
    procedure ValueListEditor1GetPickList(Sender: TObject;
      const KeyName: string; Values: TStrings);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
  private
    { Private 宣言 }
    plValueListEditor1: TValueListEditor;
    CalForm : TplCalendarForm;
    procedure OnCalendarFormCloseEvent(Sender: TObject; var Action: TCloseAction);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses plChgCompo;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  カレンダフォームを生成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  i : Integer;
begin
  //カレンダフォームを生成してイベントも設定
  if not Assigned(CalForm) then begin
    CalForm := TplCalendarForm.Create(Self);
    CalForm.OnClose := OnCalendarFormCloseEvent;
  end;

  //フォームに配置したValueListEditor1をplValueListEditor1に置き換える
  plValueListEditor1 := TplValueListEditor(ChangeComponent
                                           (ValueListEditor1, TplValueListEditor));

  //値を全てクリア
  plValueListEditor1.Strings.Clear;

  //キー例の幅
  plValueListEditor1.ColWidths[0] := 90;

  //各列のタイトル文字を設定
  plValueListEditor1.TitleCaptions.Text := '';

  //行を追加してキー項目を設定
  plValueListEditor1.InsertRow('開始年月日', '', True);
  plValueListEditor1.InsertRow('終了年月日', '', True);
  plValueListEditor1.InsertRow('備      考', '', True);


  //最初の行の入力はドロップダウン用のボタンを表示する
  plValueListEditor1.ItemProps[0].EditStyle := esPickList;
  plValueListEditor1.ItemProps[0].EditMask  := '!0000年00月00\日;1; ';;
  //2番名の行の編集時は参照ボタンを表示する
  plValueListEditor1.ItemProps[1].EditStyle := esEllipsis;
  plValueListEditor1.ItemProps[1].EditMask  := '!0000年00月00\日;1; ';;

  //行の高さは全て同じにする(0はタイトル行)
  //DefaultRowHeightプロパティでも設定できる
  for i := 0 to plValueListEditor1.RowCount - 1 do begin
    plValueListEditor1.RowHeights[i] := 20;
  end;

  //最初は2行目にフォーカスを移動
  plValueListEditor1.Row := 2;
end;

//=============================================================================
//  TApplicationEventsコンポのOnMessageメッセージ処理
//
//  デフォルトのドロップダウンのリストを表示しない場合、TValueListEditorは、
//  ドロップダウンボタンをクリックすると、フォーカスがTValueListEditorに移動し
//  てしまう
//  そこで、TApplicationEvents.OnMessageでカレンダフォームを検出して、カレンダ
//  フォームにフォーカスがなかったらフォーカスを移動する
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  if Assigned(CalForm)  then begin
    if (Msg.hwnd = CalForm.Handle) and (CalForm.Visible) then begin
      if GetForegroundWindow <> CalForm.Calendar.Handle then begin
        CalForm.Calendar.SetFocus;
      end;
    end;
  end;
end;

//=============================================================================
//  TValueListEditorのOnGetPickListイベント処理
//  矢印のボタンをクリックすると発生する
//=============================================================================
procedure TForm1.ValueListEditor1GetPickList(Sender: TObject;
  const KeyName: string; Values: TStrings);
var
  VLEditor  : TValueListEditor;
  InpEditor : TInplaceEdit;
  ACol      : Integer;
  ARow      : Integer;
  ARect     : TRect;
  CalDate   : TDate;
begin
  if KeyName = '開始年月日' then begin
    VLEditor := TValueListEditor(Sender);
    ACol     := VLEditor.Col;
    ARow     := VLEditor.Row;

    if CalForm.Visible then begin
      //ドロップダウンボタンをクリックするとフォーカスがTValueListEditorに移動する
      //そのため、カレンダフォームにフォーカス移動してキー操作で閉じる
      CalForm.Calendar.SetFocus;
      PostMessage(CalForm.Calendar.Handle, WM_KEYDOWN, VK_ESCAPE, $00010001);
      PostMessage(CalForm.Calendar.Handle, WM_KEYUP,   VK_ESCAPE, $C0010001);
    end else begin
      ARect := VLEditor.CellRect(ACol, ARow);
      CalForm.ShowPopUp(VLEditor, ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplValueListEditor(VLEditor).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //ドロップダウンボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right -17;
      CalForm.SetExceptRect(VLEditor, ARect);
    end;
  end;
end;

//=============================================================================
//  TValueListEditorのOnEditButtonClickイベント処理
//  参照[...]ボタンをクリックすると発生する
//=============================================================================
procedure TForm1.ValueListEditor1EditButtonClick(Sender: TObject);
var
  VLEditor : TValueListEditor;
  InpEditor : TInplaceEdit;
  ACol      : Integer;

  ARow      : Integer;
  ARect     : TRect;
  CalDate   : TDate;
begin
  VLEditor := TplValueListEditor(Sender);
  ACol     := VLEditor.Col;
  ARow     := VLEditor.Row;

  if ARow = 2 then begin
    if CalForm.Visible then begin
      CalForm.Close;
    end else begin
      ARect := VLEditor.CellRect(ACol, ARow);
      CalForm.ShowPopUp(VLEditor, ARect.Left, ARect.Bottom, ARect.Bottom - ARect.Top);

      //インプレイスエディタの値を取得してカレンダにセット
      InpEditor := TplValueListEditor(VLEditor).InplaceEditor;
      if InpEditor <> nil then begin
        try
          CalDate := VarToDateTime(InpEditor.Text);
        except
          CalDate := Trunc(Now);
        end;
        CalForm.Calendar.CalendarDate := CalDate;
      end;
      //参照ボタンの領域を、非アクティブ検出領域から除外
      ARect.Left := ARect.Right -17;
      CalForm.SetExceptRect(VLEditor, ARect);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのOnCloseイベント処理
//  日付の文字列は、セルではなくインプレイスエディタにセットする
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarFormCloseEvent(Sender: TObject;
  var Action: TCloseAction);
var
  InpEditor : TInplaceEdit;
  CalDate   : TDate;
begin
  if (plValueListEditor1.Col = 1) or (plValueListEditor1.Col = 2) then begin
    InpEditor := TplValueListEditor(plValueListEditor1).InplaceEditor;
    if InpEditor <> nil then begin
      //クリックまたは[Enter]で終了した場合のみ処理
      if CalForm.KindOfClose = VK_RETURN then begin
        CalDate := CalForm.Calendar.CalendarDate;
        InpEditor.Text := FormatDateTime('yyyy年MM月dd日', CalDate);
      end;
    end;
  end;
end;

{ TplValueListEditor }

//-----------------------------------------------------------------------------
//  TMyValueListEditorのCanEditShowメソッド処理
//  セルが編集モードになった時に自動的に実行される関数
//-----------------------------------------------------------------------------
function TplValueListEditor.CanEditShow: Boolean;
begin
  Result := inherited CanEditShow;

  if InplaceEditor <> nil then begin
    //キャレットの位置をセルのテキストの最後尾にする
    //セルのテキストの長さではなく、インプレイスエディットのテキストの長さ
    InplaceEditor.SelStart := Length(InplaceEditor.Text);

    //セルの背景色
    InplaceEditor.Brush.Color := $00FFF4D2;
  end;
end;

end.




07_カレンダフォームのソースコード


このフォームのユニットとフォームのファイル (拡張子は .dfm) を、ライブラリパスの通った場所に置いて使用します。plDateEditIsHoliday.pas, plChgCompo.pas もライブラリのパスが通った場所に置いておく必要があります。
このフォームの継承フォームを作成して、それを使用すれば、継承したフォームは、使用するアプリケーション専用として利用できます。



[備考]
2019年01月04日版で、指定した日が祝祭日かを調査する関数のユニット名を plDateEditIsHoliday に変更しました。



リスト8
カレンダフォームのコード
unit plCalendarFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Grids, Calendar, ExtCtrls, Buttons;

type
  TplCalendarForm = class;

  TplCalendarFormCalendar = class(TCalendar)
  private
    FCalForm      : TplCalendarForm;
    FCanClose     : Boolean;
  protected
    procedure DrawCell(ACol: Integer; ARow: Integer; ARect: TRect;
      AState: TGridDrawState); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure Click; override;
    procedure Change; override;
  end;

  TplCalendarForm = class(TForm)
    Panel: TPanel;
    btnPrevYear: TSpeedButton;
    btnPrevMonth: TSpeedButton;
    btnToday: TSpeedButton;
    btnNextMonth: TSpeedButton;
    btnNextYear: TSpeedButton;
    Calendar: TCalendar;
    procedure btnMoveYearMonthClick(Sender: TObject);
  private
    { Private 宣言 }
    FplCalendar      : TplCalendarFormCalendar;
    FhActivePrevForm : HWND;
    FExceptRect      : TRect;
    FKindOfClose     : Integer;
    FPanelFormatDate : String;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CMShowingchanged(var Message: TMessage);
      message CM_SHOWINGCHANGED;
    procedure WMActivateApp(var Message: TWMActivateApp);
      message WM_ACTIVATEAPP;
    procedure DoShow; override;
    procedure DoCreate; override;
    procedure Deactivate; override;
    procedure CMActivate(var Message: TCMActivate); message CM_ACTIVATE;
  public
    { Public 宣言 }
    procedure ShowPopUp(ALeft, ATop: Integer; AHeight: Integer=0); overload;
    procedure ShowPopUp(AControl: TWinControl; ALeft, ATop: Integer;
      AHeight: Integer=0); overload;
    function SetExceptRect(AControl: TWinControl; ARect: TRect): Boolean; overload;
    function SetExceptRect(AControl: TWinControl;
      ALeft, ATop, ARight, ABottom: Integer): Boolean; overload;
    property KindOfClose  : Integer read FKindOfClose;
    property PanelFormatDate : String read FPanelFormatDate write FPanelFormatDate;
  end;

var
  plCalendarForm: TplCalendarForm;

implementation

uses plChgCompo, plDateEditIsHoliday;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  TCalendarのオブジェクトを生成して、ボタン類などの位置を調整
//=============================================================================
procedure TplCalendarForm.DoCreate;
var
  btnTop    : Integer;
  btnMargin : Integer;
  btnSpace  : Integer;
begin
  inherited;

  //フォーム作成時にコンポーネントの入れ替え
  //FplCalendarはTplCalendarFormCalendarクラスのオブジェクトであるが、
  //外部からはTplCalendarForm.Calendarでアクセス可能となる
  FplCalendar := TplCalendarFormCalendar(ChangeComponent(Calendar,
                                         TplCalendarFormCalendar));

  Self.Color := FplCalendar.Color;

  btnMargin := 3;
  btnSpace          := 2;
  btnTop            := Panel.Height + FplCalendar.Height + 6;

  with FplCalendar do begin
    Align       := alNone;
    FCalForm    := Self;
    FCanClose   := True;
    SetBounds(-1, Panel.Height, Self.ClientWidth + 2, FplCalendar.Height);
  end;

  //ボタンの位置を調整
  btnPrevYear.SetBounds  (btnMargin, btnTop,
                          btnPrevYear.Width, btnPrevYear.Height);
  btnPrevMonth.SetBounds (btnPrevYear.Left + btnPrevYear.Width + btnSpace, btnTop,
                          btnPrevMonth.Width, btnPrevMonth.Height);
  btnNextYear.SetBounds  (ClientWidth - btnMargin - btnNextYear.Width, btnTop,
                          btnNextYear.Width, btnNextYear.Height);
  btnNextMonth.SetBounds (btnNextYear.Left - btnSpace - btnNextMonth.Width, btnTop,
                          btnNextMonth.Width, btnNextMonth.Height);
  btnToday.SetBounds     ((ClientWidth - btnToday.Width) div 2 + 1, btnTop,
                          btnToday.Width, btnToday.Height);

  SetRectEmpty(FExceptRect);

  FPanelFormatDate := 'yyyy年 M月';
  Panel.Caption := FormatDateTime(PanelFormatDate, FplCalendar.CalendarDate);
end;

//=============================================================================
//  このフォームのウインドウスタイルの設定
//  好みに応じて変更する
//=============================================================================
procedure TplCalendarForm.CreateParams(var Params: TCreateParams);
begin
  inherited;
  FhActivePrevForm := Application.ActiveFormHandle;

  //このフォームはキャプションバーなしとしてWS_THICKFRAMEも除外
  Params.Style := WS_POPUP or WS_BORDER;
  //このフォームは最前面表示とする
  //これがなくても動作に支障はない
  //なぜなら、このフォーム以外がアクティブになったら閉じてしまうので
  Params.ExStyle := Params.ExStyle or WS_EX_TOPMOST;
  //影付きとする
  Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
end;

//=============================================================================
//  フォームを表示する時の処理
//=============================================================================
procedure TplCalendarForm.DoShow;
begin
  inherited;
  //直前にアクティブだったフォームのハンドルを取得
  FhActivePrevForm := Application.ActiveFormHandle;
end;

//=============================================================================
//  このフォームを表示したフォームを疑似的にアクティブにする
//=============================================================================
procedure TplCalendarForm.CMActivate(var Message: TCMActivate);
begin
  inherited;
  FplCalendar.SetFocus;
  if FhActivePrevForm <> 0 then begin
    PostMessage(FhActivePrevForm, WM_NCACTIVATE, 1, 0);
  end;
end;

//=============================================================================
//  引数を指定した表示メソッド
//  ALeft,ATop(スクリーン座標)の位置から表示する
//  AHeightを指定すると、画面下端で表示する際に、その分だけ上から表示する
//=============================================================================
procedure TplCalendarForm.ShowPopUp(ALeft, ATop: Integer; AHeight: Integer=0);
var
  ABottom : Integer;
  ARight  : Integer;
  CtrlPos : TPoint;
  Monitor : TMonitor;
begin
  CtrlPos := Point(ALeft, ATop);
  //表示位置の調整
  ARight  := CtrlPos.X + Self.Width;
  ABottom := CtrlPos.Y + Self.Height;

  Monitor := Screen.MonitorFromPoint(CtrlPos);
  if Monitor.WorkareaRect.Bottom < ABottom then begin
    CtrlPos.Y := CtrlPos.Y - AHeight - Self.Height;
  end;
  if Monitor.WorkareaRect.Left > CtrlPos.X then begin
    CtrlPos.X := Monitor.WorkareaRect.Left;
  end else
  if Monitor.WorkareaRect.Right < ARight then begin
    CtrlPos.X := Monitor.WorkareaRect.Right - Self.Width;
  end;

  SetBounds(CtrlPos.x, CtrlPos.y, Self.Width, Self.Height);
  //Showメソッドを実行
  Show;
end;

//=============================================================================
//  引数を指定した表示メソッド
//  ALeft,ATop(クライアント座標)の位置から表示する
//  スクリーン座標への変換はAControlを基に計算する
//  AHeightを指定すると、画面下端で表示する際に、その分だけ上から表示する
//=============================================================================
procedure TplCalendarForm.ShowPopUp(AControl: TWinControl; ALeft, ATop: Integer;
  AHeight: Integer=0);
var
  APos : TPoint;
begin
  if TWinControl(AControl) <> nil then begin
    APos := TWinControl(AControl).ClientToScreen(Point(ALeft,  ATop));
    ShowPopUp(APos.X, APos.Y, AHeight);
  end;
end;

//=============================================================================
//  CM_SHOWINGCHANGEDメッセージ処理
//  フォームが表示されたらTCalendarにフォーカス移動
//=============================================================================
procedure TplCalendarForm.CMShowingchanged(var Message: TMessage);
begin
  inherited;
  if Visible then begin
    FplCalendar.SetFocus;
  end;
end;

//=============================================================================
//  非アクティブになったら閉じる
//  ただし、現在のマウスカーソルがFExceptRectの範囲内だったら閉じない
//  外部のボタンクリックイベントで処理する
//
//  これはボタン操作で、トグル動作を実現するためのもの
//  単にトグル仕様のボタンを使用しても、このカレンダフォームでは無理がある
//  マウス操作以外でボタン類を操作されてしまった場合は機能しない
//=============================================================================
procedure TplCalendarForm.Deactivate;
var
  CurPos : TPoint;
begin
  inherited;

  FKindOfClose := VK_ESCAPE;
  GetCursorPos(CurPos);
  if not IsRectEmpty(FExceptRect) then begin
    if not PtInRect(FExceptRect, CurPos) then begin
      Close;
    end;
  end else begin
    Close;
  end;
end;

//=============================================================================
//  他のアプリがアクティブになったら閉じる
//  このフォームを表示したフォームも非アクティブにする
//  添付のサンプルに、メインフォームにカレンダフォームを配置するサンプルがある
//  その時は機能しない
//=============================================================================
procedure TplCalendarForm.WMActivateApp(var Message: TWMActivateApp);
begin
  inherited;

  FKindOfClose := VK_ESCAPE;
  if Visible then begin
    if FhActivePrevForm <> 0 then begin
      PostMessage(FhActivePrevForm, WM_NCACTIVATE, Ord(Message.Active), 0);
    end;
    Close;
  end;
end;

//=============================================================================
//  引数のTWinControlを基準にしたスクリーン座標における矩形領域を計算
//  非アクティブになった時の除外領域の設定用
//  TSpeedButton等はTWinControlではないので、その親をAControlに指定する
//=============================================================================
function TplCalendarForm.SetExceptRect(AControl: TWinControl; ALeft, ATop,
  ARight, ABottom: Integer): Boolean;
var
  PointTL : TPoint;
  PointBR : TPoint;
begin
  Result := True;
  if TWinControl(AControl) = nil then begin
    Result := False;
  end else begin
    PointTL := TWinControl(AControl).ClientToScreen(Point(ALeft,  ATop));
    PointBR := TWinControl(AControl).ClientToScreen(Point(ARight, ABottom));
    SetRect(FExceptRect, PointTL.X, PointTL.Y, PointBR.X, PointBR.Y);
  end;
end;

//=============================================================================
//  引数のTWinControlを基準にしたスクリーン座標における矩形領域を計算
//  非アクティブになった時の除外領域の設定用
//  TSpeedButton等はTWinControlではないので、その親をAControlに指定する
//=============================================================================
function TplCalendarForm.SetExceptRect(AControl: TWinControl;
  ARect: TRect): Boolean;
var
  PointTL : TPoint;
  PointBR : TPoint;
begin
  Result := True;
  if TWinControl(AControl) = nil then begin
    Result := False;
  end else begin
    PointTL := TWinControl(AControl).ClientToScreen(ARect.TopLeft);
    PointBR := TWinControl(AControl).ClientToScreen(ARect.BottomRight);
    SetRect(FExceptRect, PointTL.X, PointTL.Y, PointBR.X, PointBR.Y);
  end;
end;

//=============================================================================
//  [前年][前月][今日][翌月][翌年]ボタンのクリック
//=============================================================================
procedure TplCalendarForm.btnMoveYearMonthClick(Sender: TObject);
begin
  if Sender = btnPrevYear  then FplCalendar.PrevYear;
  if Sender = btnPrevMonth then FplCalendar.PrevMonth;
  if Sender = btnToday     then FplCalendar.CalendarDate := Now;
  if Sender = btnNextMonth then FplCalendar.NextMonth;
  if Sender = btnNextYear  then FplCalendar.NextYear;
end;

{ TplCalendarFormCalendar }

//-----------------------------------------------------------------------------
//  TCalendarのChange処理
//-----------------------------------------------------------------------------
procedure TplCalendarFormCalendar.Change;
begin
  FCanClose := True;
  inherited Change;
  //本カレンダフォームの上部のTPanelに表示する文字列
  FCalForm.Panel.Caption := FormatDateTime(FCalForm.PanelFormatDate, CalendarDate);
end;

//-----------------------------------------------------------------------------
//  TCalendarのClick処理
//-----------------------------------------------------------------------------
procedure TplCalendarFormCalendar.Click;
begin
  FCalForm.FKindOfClose := VK_RETURN;
  if FCanClose then begin
    Day := StrToInt(CellText[Col, Row]);
    FCalForm.Close;
  end;
  inherited Click;
end;

//-----------------------------------------------------------------------------
//  TCalendarのKeyDown処理
//  カーソル移動キーでもClickが発生するので、閉じないようにする
//  この閉じないようにする判定にFCanCloseを使用している
//
//  キー操作を追加、変更する場合は、このコードを編集する
//-----------------------------------------------------------------------------
procedure TplCalendarFormCalendar.KeyDown(var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    FCalForm.FKindOfClose := VK_RETURN;
    FCalForm.Close;
  end else
  if Key = VK_ESCAPE then begin
    FCalForm.FKindOfClose := VK_ESCAPE;
    FCalForm.Close;
  end else begin
    if Key in [VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN] then begin
      FCanClose := False;
    end else begin
      FCanClose := True;
    end;
  end;

  inherited KeyDown(Key, Shift);
end;

//-----------------------------------------------------------------------------
//  フォームに表示しているTCalendarのDrawCellメソッド
//  ここで日付の文字をTCalendarに描画している
//  日曜日と国民の祝日を赤文字に、土曜日を青色にする
//
//  カスタム描画が必要な場合は、このコードを編集する
//-----------------------------------------------------------------------------
procedure TplCalendarFormCalendar.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);
var
  TheText   : string;
  ADate     : TDate;
  iDay      : Word;
  StrText   : string;
  SundayCol : Byte;
begin
  if StartOfWeek = 0 then begin
    SundayCol := 0;
  end else begin
    SundayCol := 7 - StartOfWeek;
  end;
  if (ACol = SundayCol) then Canvas.Font.Color := clRed;
  if (ACol = (6 - StartOfWeek)) then Canvas.Font.Color := $00C86615;

  TheText := CellText[ACol, ARow];
  if (not (gdFixed in AState)) then begin
    iDay := StrToIntDef(TheText, 0);
    if iDay <> 0 then begin
      ADate := EncodeDate(Year, Month, iDay);
      if IsSpecialHoliday(ADate, StrText) then begin
        Canvas.Font.Color := clRed;
      end;
    end;
  end;

  //TCalendarの元の描画コードを実行i
  inherited DrawCell(ACol, ARow, ARect, AState);
end;

end.




08_フォームにカレンダフォームを配置


フォームに別のフォームを配置するサンプルです。
ボップアップ表示するフォームを作成したのに、フォームに配置することはないのですが、コミュニティの場でそのような質問を見かけたので、テストしてみました。どのような目的で使用するとか、どのような場面で必要となるのかは分かりません。

同じような機能を実装する方法として、TFrame を使用する方法があります。TFrame は設計時にフォーム上に配置できますが、フォームのようにキャプションバー や枠は表示されません。
各種のコンポーネント類を配置するためのコンテナとして機能します。

設計時には、別のフォームをフォームの上に載せることはできません。実行時に配置することになります。本サンプルでは、カレンダフォームを実行時に生成してフォームの上に表示します。



図22
設計時画面
  • このフォームにカレンダフォームを載せる
図23
実行開始画面
  • ポップアップウィンドウではないので、表示されない部分がある状態
図24
フォームの高さを大きくする
  • カレンダフォームが全て見えるようになる

本サンプルのコードの中に、次のようなコードがあります。
この処理がないと、カレンダフォームにフォーカスがある状態で、別のアプリケーションがアクティブになった場合、フォームのアクティブ状態が変化しません。キャプションバー (タイトルバー) がアクティブ状態になったままになります。実際には非アクティブです。



procedure TForm1.WMActivateApp(var Message: TWMActivateApp);
begin
  inherited;
  if Visible then begin
    if Message.Active = False then begin
      PostMessage(Handle, WM_NCACTIVATE, Ord(Message.Active), 0);
    end;
  end;
end;



リスト9
フォームに別のフォームを載せる (配置する) ための参考コード
WM_ACTIVATEAPP メッセージを処理する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, StdCtrls, Buttons, ExtCtrls, plCalendarFormUnit;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    CalForm : TplCalendarForm;
    procedure OnCalendarChangeEvent(Sender: TObject);
  protected
    procedure CMShowingchanged(var Message: TMessage);
      message CM_SHOWINGCHANGED;
    procedure WMActivateApp(var Message: TWMActivateApp);
      message WM_ACTIVATEAPP;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Edit1.SelStart   := 1;
  Edit1.AutoSelect := False;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', Now);
end;

//=============================================================================
//  フォーム表示終了時のメッセージ処理
//=============================================================================
procedure TForm1.CMShowingchanged(var Message: TMessage);
var
  CalDate : TDate;
begin
  inherited;

  if Visible then begin
    //カレンダフォームが生成されいなければ生成して、イベントも設定
    if not Assigned(CalForm) then begin
      CalForm := TplCalendarForm.Create(Self);
      CalForm.Calendar.OnChange := OnCalendarChangeEvent;
    end;

    //位置を設定して表示
    CalForm.Top  := Edit1.Top + Edit1.Height + 5;
    CalForm.Left := Edit1.Left;
    CalForm.Show;
    //親ウィンドウを変更
    //Showの後で設定しないと不定
    Windows.SetParent(CalForm.Handle, Handle);

    try
      CalDate := VarToDateTime(Edit1.Text);
    except
      CalDate := Trunc(Now);
    end;
    CalForm.Calendar.CalendarDate := CalDate;
  end;
end;

//-----------------------------------------------------------------------------
//  カレンダフォームのTCalendarのOnChangeイベント処理
//  カレンダで選択中の日付をリアルタイムで取得する
//-----------------------------------------------------------------------------
procedure TForm1.OnCalendarChangeEvent(Sender: TObject);
var
  CalDate : TDate;
begin
  CalDate := CalForm.Calendar.CalendarDate;
  Edit1.Text := FormatDateTime('yyyy年 MM月 dd日', CalDate);
end;

//-----------------------------------------------------------------------------
//  WM_ACTIVATEAPPメッセージ処理
//  キャプションバー(タイトルバー)のアクティブ・非アクティブ状態を制御
//-----------------------------------------------------------------------------
procedure TForm1.WMActivateApp(var Message: TWMActivateApp);
begin
  inherited;
  if Visible then begin
    if Message.Active = False then begin
      PostMessage(Handle, WM_NCACTIVATE, Ord(Message.Active), 0);
    end;
  end;
end;

end.


カレンダフォームは、通常のフォームとは違います。キャプションバーがありません。
下の図は、新規にプロジェクトを作成した後、フォームを追加して動作を確認したものです。



図25
Form2 に Form3 を配置 (埋め込む)
  • Form3 は移動リサイズが可能
図26
Form3 をアクティブにした状態

procedure TForm2.Button1Click(Sender: TObject);
begin
  Form3.SetBounds(30, 40, 220, 150);
  Form3.Show;
  //親を設定
  Windows.SetParent(Form3.Handle, Form2.Handle);
  //以下のように親を設定すると、Form3をアクティブにできなくなる
  //Form3.Parent := Self;
end;