Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2013年04月16日] [更新 2013年04月16日] Delphi サンプルプログラム集
   
030_タイトルバー ( キャプションバー ) にボタン類を追加配置
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
Windows Vista 以降,Delphi 2009 以降専用
030_CaptionBar_Button.zip [2,345 KB] 2013年04月16日版 (EXE 同梱)







タイトルバーにコントロールを追加配置


キャプションバー (タイトルバー) にボタン等を追加,配置するサンプルは,ネット上を検索するといくつかあります.
しかし,それらの多くは,Windows Vista 以降のガラスフレーム (Glass Frame) と呼ばれる Aero テーマには対応していません.Aero に対応するには,DWM 関係の API を使用して描画しなければなりません.簡単に言うと「簡単」ではありません.

「Delphi XE2 Foundations」の著者である Chris Rolliston 氏のサイトに記事がありました.そこの記事のサンプルは,ID: 27688 でダウンロードできます.そのコードを以下のように改変してテストしてみました.
  • GlassFrame の上下左右のいずれの設定も有効となるようにした
  • GlassFrame の Enabled プロパティが False の時は通常描画するようにした
  • TGraphicControl のイベント処理もできるようにした
  • フレーム (TFrame) のユニットには,TFrame 用のコードだけにした
  • 最大化を繰り返した時にアイコンの位置が不正になる現象を修正
  • 後で改変しやすいようにコメントを付けた
配布条件は,元のコードと同じで,GPL 1.1 ということになります.
なお,元のサンプルコードと同じく,VCL スタイルには対応していません.


[備考]
本ページのサンプルとは趣旨が違いますが,キャプションバーのデザイン,スタイルを変更するには,VCL スタイルを自分で編集する方法があります.また,Windows XP 時代にはスキンと呼ばれていたスタイルの変更用のコンポーネントもあります.

キャプションバーのないフォームを作成し,フォームの上部に必要な機能を持つボタン類を配置してしまう方法も考えられます.
そのままではキャプションバーに相当する部分をマウスで操作してもフォームの移動ができません.その場合は,キャプションパーのないフォームの移動リサイズの機能を利用します.





01_TFrame ( フレーム ) を TForm ( フォーム ) に配置する方法


Chris Rolliston さんのサンプルでは,TFrame (フレーム) を使用しています.
そこで,この TFrame について復習しておきます.

TFrame は,TForm をコンポーネントにしたような機能を提供しています.フォーム (TForm) 自身はコンポーネントにできませんが,TFrame にいろいろなコントロールを配置したものを保存しておくと,それをテンプレートして利用できます.
例えば,TFrame は次のように作成して利用します.
まず,TFrame を使用するプロジェクトを作成します.Unit1.pas の Form1 ができます.


図1
新規に VCL フォームアプリを作成

次にフレームを作成します.
[ファイル] [新規作成] [その他] から [Delphi ファイル] を選択します.この中の [フレーム] を選択して,[OK] ボタンでダイアログを閉じます.
すでに作成して,どこかに保存してあるフレームを使用するには,[プロジェクト] [プロジェクトに追加] で追加します.
ここでは,メインフォームと区別するために,TButton を 1 つだけ配置しています.


図2
フレームの新規作成
図3
作成したフレーム
  • TButton を 1 つ配置しておく

コンポーネントパレットの [Frame] をクリックして,フレームを貼り付けるフォームをマウスでクリックします.すると,どのフレームを貼りつけるか,選択ダイアログが現れます.今は 1 つだけですからこれを選択します.
これで,フォームにフレームが配置されました.後は,通常のビジュアルコンポーネントと同じように操作します.添付のサンプルには,フレームに配置したボタンにクリックイベントを定義しています.


図4
コンポーネントパレットから [Frames] を選択
  • [Standard] タブの左端にある
図5
フレームの貼り付け
  • フレームを貼り付けるフォームをマウスでクリック
  • ダイアログでフレームを選択
図6
選択状態のコントロールがフレーム
  • Button1 は元のフレームのもの
  • したがって,削除はできない.削除するには,元のフレームを表示して削除する
  • 移動やサイズの変更はできる
  • フレームそのものを削除するには,そのフレームを選択して [DELETE] キーで削除する

[備考]
作成したフレームを他のプロジェクトでも使用することができます.
コンポーネントパレットに登録して使用する方法もありますが,フレームを使用するプロジェクトの [プロジェクト] [プロジェクトに追加] で該当フレームのユニットを追加すると,上の図で説明したように,[Standard] タブからそのフレームを選択してフォームに配置することができます.もちろん,フレームのフォームファイルはフレームのユニットファイルと同じ場所にあって,関係するファイル類は,ライブラリのパスが通っている場所に存在している必要があります.





02_TFrame を使用したサンプル


このサンプルは,実際にはタイトルバーにボタン類のコントロールを描画するわけではありません.
クライアント領域をフォームの上まで拡張し,そこに「偽」のタイトルバーを描画します.
「偽」のタイトルバーを描画するわけですから,マウスイベント等のメッセージの処理も独自で実装しなければなりません.また,リサイズの時の処理も必要です.「偽」のタイトルバーは,DWM という API 群を使用して描画します.
タイトルバーに追加,配置するボタン類の位置は,設計時にそれらのボタン類がどこにあるかによって決まることになります.

Windows 8 ではウィンドウの描画は全て DWM で行われるようになったようですが,Windows Vista, 7 ではガラスフレームが有効の時に DWM で描画が行われます.したがって,GlassFrame の Enabled プロパティが False の時はタイトルバーにコントロールを配置することはできません.


図7
GlassFrame の Enabled の値を True にする

また,下図のように,[プロジェクト] [プロジェクトのオプション] で,PE ヘッダの値を Vista のメジャーバージョンを意味する 6.0 にしています.そうしないと,DWM による描画が正常に行われない場合があります.


図8
PE ヘッダの値は 6.0

動作原理から,フォームに配置したコントロールは全て,タイトルバーの高さ分だけ上に移動することになります.
このサンプルでは,TFrame の Align プロパティを alTop または alClient にしています.これにより,TFrame が新しいクライアント領域内全体に配置されます.
この設定を,次のように,OnCreate イベントの最初の方で設定しています.Align プロパティがこれ以外の場合は,この後のサンプルを参考にしてください.


  Frame11.Align := alClient;


このサンプルは「偽」のタイトルバーを描画します.逆に,追加するコントロールをタイトルバーに描画する方法もあります.その方法のサンプルはネット上でも探すことができます.
Delphi の TButton 等を生成してタイトルバーに描画することはできません.Windows API を使用して生成することになります.この方法で,Windows XP のテーマや Aero に対応するには,それらコントロール別のコードが必要となります.一方,タイトルバーは 1 つだけですから,方法としては,効率がいいことになります.

このサンプルの処理コードを複雑と感じるかも知れませんが.ボタンクリック等の実装は簡単です.通常の方法でコーディングができます.処理内容をブラックボックス的に利用すれば使いやすいと言えるでしょう.

なお,Chris Rolliston 氏が提供している App.DwmUtils.pas の内容は変更していません.そのまま使用しています.


図9
設計時画面
  • Form1 の GlassFrame プロパティの Enabled メンバーの値を True にしておく
図10
実行開始画面
図11
[追加ボタン] をクリック
図12
[Button1] をクリック

下図は,Form1 の GlassFrame の SheetOfGlass を True にして,Left, Top 等の値を 0 以外の値にしてコンパイル,実行した結果です.
ウィンドウのフレームの幅を上下左右個別に変更でき,TFrame がクライアント領域に描画されていることが分かります.ウィンドウの上部のフレームの高さが大きくなっていますが,タイトルバーの高は元のままです.


図13
ウィンドウのフレーム太さ変更
  • タイトルバーが高くなっているわけではない

リスト1
タイトルバーにボタンを追加配置するサンプル
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    Frame11: TFrame1;
    procedure FormActivate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure Frame11Button1Click(Sender: TObject);
  strict private
    FDwmBorderIconsRect: TRect;
    FOldIconChangeHandler: TNotifyEvent;
    FSysIconImageList: TImageList;
    FUseCustomFrame: Boolean;
    FWndFrameSize: Integer;
    FOriginGlassFrameTop : Integer;
    function GetSysIconRect: TRect;
    procedure IconChanged(Sender: TObject);
    procedure InvalidateCustomTitleBar;
    procedure RecalcGlassFrameBounds(UpdateFrame: Boolean = True);
  protected
    procedure AdjustClientRect(var Rect: TRect); override; //
    procedure Paint; override;
    procedure PaintWindow(DC: HDC); override;
    procedure Resize; override;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
    procedure WMActivate(var Message: TWMActivate); message WM_ACTIVATE;
    procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCRButtonUp(var Message: TWMNCRButtonUp); message WM_NCRBUTTONUP;
    procedure WMSetIcon(var Message: TWMSetIcon); message WM_SETICON;
    procedure WMWindowPosChanging(var Message: TWMWindowPosChanging);
      message WM_WINDOWPOSCHANGING;
    procedure WMWindowPosChanged(var Message: TWMWindowPosChanged);
      message WM_WINDOWPOSCHANGED;
    procedure WndProc(var Message: TMessage); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  Form1: TForm1;

implementation

uses CommCtrl, DwmApi, UxTheme, App.DwmUtils;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  AdjustClientRectメソッド
//  コントロールの子コントロールクライアント領域に配置される時に実行される
//  ここでは,TFormのGlassFrameの値で設定した内部をクライアント領域に再設定
//  TFrameのAlignをalClientにすると,この領域に収まるように描画される
//-----------------------------------------------------------------------------
procedure TForm1.AdjustClientRect(var Rect: TRect);
begin
  inherited;
  if FUseCustomFrame then begin
    Inc(Rect.Top,     GlassFrame.Top);
    Inc(Rect.Left,    GlassFrame.Left);
    Inc(Rect.Right,  -GlassFrame.Right);
    Inc(Rect.Bottom, -GlassFrame.Bottom);
  end;
end;

//-----------------------------------------------------------------------------
//  Cm_TextChangeメッセージ処理
//  コンポーネントのテキストあるいはキャプションが変更された時に,自分自身に送
//  ってくるメッセージの処理
//-----------------------------------------------------------------------------
procedure TForm1.CMTextChanged(var Message: TMessage);
begin
  inherited;
  InvalidateCustomTitleBar;
end;

//-----------------------------------------------------------------------------
//  フォームのアイコンの描画領域を取得
//-----------------------------------------------------------------------------
function TForm1.GetSysIconRect: TRect;
begin
  if not (biSystemMenu in BorderIcons) or
     not (BorderStyle in [bsSingle, bsSizeable]) then begin
    SetRectEmpty(Result);
  end else begin
    Result.Left   := 0;
    Result.Right  := GetSystemMetrics(SM_CXSMICON);
    Result.Bottom := GetSystemMetrics(SM_CYSMICON);

    if WindowState = wsMaximized then begin
      if CheckWin32Version(6, 0) then begin
        Result.Top := GlassFrame.Top - Result.Bottom - 2;
      end else begin
        Result.Top := GlassFrame.Top - Result.Bottom - 4;
      end;
    end else begin
      if CheckWin32Version(6, 0) then begin
        Result.Top := 6;
      end else begin
        Result.Top := 8;
     end;
   end;

    Inc(Result.Bottom, Result.Top);
  end;
end;

//-----------------------------------------------------------------------------
//  IconのOnChangedイベントハンドラ
//  現在のTImageListを破棄して元のIconのOnChangedイベントハンドラを実行
//-----------------------------------------------------------------------------
procedure TForm1.IconChanged(Sender: TObject);
begin
  FreeAndNil(FSysIconImageList);
  FOldIconChangeHandler(Sender);
end;

//-----------------------------------------------------------------------------
//  Paintメソッド処理
//  描画が必要になると実行されるメソッド
//
//  一度TImageListに格納して,TImageListに格納したアイコンを描画する
//  32ビットの画像としてTImageListに格納する,元の画像が32ビットでない場合でも
//  正確に描画できる
//-----------------------------------------------------------------------------
procedure TForm1.Paint;
var
  R : TRect;
begin
  if not FUseCustomFrame then begin
    inherited;
    exit;
  end;

  //アイコンの描画領域を取得してアイコンを描画
  R := GetSysIconRect;
  if not IsRectEmpty(R) then begin

    //TImageListを生成して,そこにアイコンの画像を格納
    if FSysIconImageList = nil then begin
      FSysIconImageList := Create32BitImageList(Self,
                                                GetSystemMetrics(SM_CXSMICON),
                                                GetSystemMetrics(SM_CYSMICON),
                                                1);
      AddFormIconToImageList(Self, FSysIconImageList);
    end;

    //格納した画像を描画
    R := GetSysIconRect;
    if WindowState = wsMaximized then begin
      R.Top := FWndFrameSize
    end;
    FSysIconImageList.Draw(Canvas, R.Left, R.Top, 0);
    R.Left := R.Right + FWndFrameSize - 3;
  end else begin
    R.Left := 0;
  end;

  //キャプションのテキストを描画
  R.Right  := FDwmBorderIconsRect.Left - FWndFrameSize - 1;
  R.Bottom := GlassFrame.Top;
  DrawGlassCaption(Self, R, True, taLeftJustify, tlTop);
  SelectClipRgn(Canvas.Handle, 0);

  inherited;
end;

//-----------------------------------------------------------------------------
//  PaintWindowメソッド処理
//  カスタムコントロールが WM_PAINT メッセージを受け取ると自動的に実行される
//  クライアント領域をクリップする
//-----------------------------------------------------------------------------
procedure TForm1.PaintWindow(DC: HDC);
var
  R : TRect;
begin
  if FUseCustomFrame then begin
    R := GetClientRect;
    with GlassFrame do begin
      ExcludeClipRect(DC, Left, Top, R.Right - Right, R.Bottom - Bottom);
    end;
  end;

  inherited;
end;

//-----------------------------------------------------------------------------
//  フォーム生成時とウィンドウの位置が変更になった時に実行
//  -R.Topはデフォルトでは30(タイトルバーの高さ)
//-----------------------------------------------------------------------------
procedure TForm1.RecalcGlassFrameBounds(UpdateFrame: Boolean = True);
var
  R : TRect;
begin
  SetRectEmpty(R);
  AdjustWindowRectEx(R,
                     GetWindowLong(Handle, GWL_STYLE),
                     False,
                     GetWindowLong(Handle, GWL_EXSTYLE));
  FWndFrameSize := R.Right;

  if FUseCustomFrame then begin
    GlassFrame.Top := FOriginGlassFrameTop - R.Top;
  end else begin
    GlassFrame.Top := 0;
  end;

  if UpdateFrame then
    SetWindowPos(Handle, 0, Left, Top, Width, Height, SWP_FRAMECHANGED);
end;

//-----------------------------------------------------------------------------
//  Resizeメソッド
//  リサイズの際に実行される
//  システムのアイコン(閉じるや最小化等)ボタンの左端の座標値を取得
//  キャプションの描画位置の設定に使用する
//-----------------------------------------------------------------------------
procedure TForm1.Resize;
begin
  if FUseCustomFrame then FDwmBorderIconsRect := GetDwmBorderIconsRect(Self);
  inherited;
end;

//-----------------------------------------------------------------------------
//  WM_ACTIVATEメッセージ処理
//  アクティブになったらタイトルバーを再描画
//-----------------------------------------------------------------------------
procedure TForm1.WMActivate(var Message: TWMActivate);
begin
  inherited;
  InvalidateCustomTitleBar;
end;

//-----------------------------------------------------------------------------
//  WM_NCCALCSIZEメッセージ処理
//  非クライアント領域のサイズ変更があった時に,クライアント領域を再計算
//  GlassFrameの上下左右の値の変更時に発生する
//  FWndFrameSizeのデフォルト値は8
//-----------------------------------------------------------------------------
procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
  if not FUseCustomFrame then begin
    inherited;
  end else begin
    with Message.CalcSize_Params.rgrc[0] do begin
      Inc(Left,   FWndFrameSize);
      Dec(Right,  FWndFrameSize);
      Dec(Bottom, FWndFrameSize);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  WM_NCHITTESTメッセージ処理
//  マウスの移動によって発生するメッセージ
//  タイトルバーは描画だけなので,そのままではマウスイベント等を受け付けない
//  このメッセージ内でマウス位置を検出して,その位置によって必要な結果を返す
//-----------------------------------------------------------------------------
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
var
  ClientPos : TPoint;
  IconRect  : TRect;
begin
  inherited;

  if not FUseCustomFrame then Exit;
  if not GlassFrame.Enabled then Exit;
  case Message.Result of
    HTCLIENT: ;
    HTMINBUTTON, HTMAXBUTTON, HTCLOSE:
    begin
      Message.Result := HTCAPTION;
      Exit;
    end;
  else exit;
  end;

  ClientPos := ScreenToClient(Point(Message.XPos, Message.YPos));
  if ClientPos.Y > GlassFrame.Top then Exit;

  //ID: 27688 のコードは,このControlAtPosがない
  //これがないと,TGraphicControlのクリックイベントが検出できない
  if ControlAtPos(ClientPos, True) <> nil then Exit;

  IconRect := GetSysIconRect;
  if (ClientPos.X < IconRect.Right) and ((WindowState = wsMaximized) or
     ((ClientPos.Y >= IconRect.Top) and (ClientPos.Y < IconRect.Bottom)))
     then begin
    Message.Result := HTSYSMENU
  end else
  if ClientPos.Y < FWndFrameSize then begin
    Message.Result := HTTOP;
  end else begin
    Message.Result := HTCAPTION;
  end;
end;

//-----------------------------------------------------------------------------
//  WM_NCRBUTTONUPメッセージ処理
//  メニューやアイコンでマウス右ボタンをUP下時の処理
//-----------------------------------------------------------------------------
procedure TForm1.WMNCRButtonUp(var Message: TWMNCRButtonUp);
begin
  if not FUseCustomFrame or not (biSystemMenu in BorderIcons) then
    inherited
  else
    case Message.HitTest of
      HTCAPTION, HTSYSMENU: ShowSystemMenu(Self, Message);
    else inherited;
    end;
end;

//-----------------------------------------------------------------------------
//  WM_SETICONメッセージ処理
//-----------------------------------------------------------------------------
procedure TForm1.WMSetIcon(var Message: TWMSetIcon);
begin
  inherited;
  InvalidateCustomTitleBar;
end;

//-----------------------------------------------------------------------------
//  WM_WINDOWPOSCHANGINGメッセージ処理
//  ウィンドウの位置が変更されようとしている時の処理
//  ガラスフレームのサイズを再計算
//-----------------------------------------------------------------------------
procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosChanging);
const
  SWP_STATECHANGED = $8000;
begin
  if FUseCustomFrame then begin
    if (Message.WindowPos.flags and SWP_STATECHANGED) = SWP_STATECHANGED then begin
      Invalidate;
    end;
  end else begin
    InvalidateCustomTitleBar;
  end;

  inherited;

  if (Message.WindowPos.flags and SWP_FRAMECHANGED <> 0) and FUseCustomFrame and
     (Message.WindowPos.flags <> SWP_FRAMECHANGED) then
    RecalcGlassFrameBounds(False);
end;

//-----------------------------------------------------------------------------
//  WM_WINDOWPOSCHANGEDメッセージ処理
//  ウィンドウの位置が変更されたら行う処理
//  変更されようとしている時に再計算した値で再設定
//-----------------------------------------------------------------------------
procedure TForm1.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
  inherited;
  if (Message.WindowPos.flags and SWP_FRAMECHANGED <> 0) and FUseCustomFrame then
    Realign;
end;

//-----------------------------------------------------------------------------
//  WndProc処理
//  まだDWMによる描画ができない状態の時は元のWndProcを実行
//-----------------------------------------------------------------------------
procedure TForm1.WndProc(var Message: TMessage);
begin
  if not FUseCustomFrame or not HandleAllocated or
     not DwmDefWindowProc(Handle, Message.Msg, Message.WParam, Message.LParam,
                          Message.Result) then inherited;
end;

//-----------------------------------------------------------------------------
//  タイトルバー領域の再描画
//  キャプション(タイトル)の文字列,アイコン,フォームのりサイズの時に実行
//-----------------------------------------------------------------------------
procedure TForm1.InvalidateCustomTitleBar;
var
  R : TRect;
begin
  if not HandleAllocated or not FUseCustomFrame then Exit;

  R.Left   := 0;
  R.Top    := 0;
  R.Right  := Width;
  R.Bottom := GlassFrame.Top;
  InvalidateRect(Handle, @R, False);
end;

//=============================================================================
//  このフォームのOnCreateイベント
//=============================================================================
constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;

  //フレームのアライメントの設定
  Frame11.Align := alClient;

  FOriginGlassFrameTop := GlassFrame.Top;
  //アプリ終了時にメモリリークの報告をする
  ReportMemoryLeaksOnShutdown := True;
  //コントロールのダブルバッファの設定
  EnableAppropriateDoubleBuffering(Self);

  //DWM(Desktop Window Manager)が有効どうかを調査
  //有効ならばウィンドウのGlassFrameのサイズ設定をする
  FUseCustomFrame := DwmCompositionEnabled and Self.GlassFrame.Enabled;
  if FUseCustomFrame then begin
    RecalcGlassFrameBounds;
  end;

  //表示するTFrame側に何か初期化の時に実行することがあれば実行
  //Frame11.Initialize(FUseCustomFrame);

  //アイコンが変更になった場合を考えて,IconのOnChangedイベントを定義
  FOldIconChangeHandler := Icon.OnChange;
  Icon.OnChange := IconChanged;
end;


//-----------------------------------------------------------------------------
//  この上は汎用的で,ブラックボックス的に使用できる
//  (OnCreateイベントは,コントロールの初期化等に使用する場合あり)
//  この下のコードは,このフォーム専用のコード
//-----------------------------------------------------------------------------


{ TForm1 }

//=============================================================================
//  このフォームのOnActivateイベント
//=============================================================================
procedure TForm1.FormActivate(Sender: TObject);
begin
  OnActivate := nil;
  if not DwmCompositionEnabled then
    MessageDlg('システムの Aero ( エアロ ) が無効になっています.',
               mtWarning, [mbOK], 0)
  else if FormsHaveCompatibilityCoords then
    MessageDlg('PE ヘッダの値を 6.0 ( Vista ) 以上にしてください',
               mtWarning, [mbOK], 0)
end;

//=============================================================================
//  フレームのButton1のOnClickイベント
//=============================================================================
procedure TForm1.Frame11Button1Click(Sender: TObject);
begin
  Application.MessageBox('フレームのボタンクリック', '情報');
end;

//=============================================================================
//  追加ボタンのOnClickイベント
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  Application.MessageBox('追加ボタンクリック', '情報');
end;

end.


本サンプルで,メインフォームに配置して使用したフレーム (TFrame) です.ボタンを 1 つだけ配置しているだけです.


図14
フレームの設計時画面

リスト2
メインフォームに配置するフレーム (TFrame) のコード
unit Unit2;

interface

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

type
  TFrame1 = class(TFrame)
    Button1: TButton;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure Initialize(SupportsCustomFrame: Boolean);
  end;

implementation

{$R *.dfm}

//=============================================================================
//  表示する時に何か初期化実行することがあれば実行
//  このメソッドは,このフレームを配置したフォーム側から実行する
//=============================================================================
procedure TFrame1.Initialize(SupportsCustomFrame: Boolean);
begin
//
end;

end.




03_TFrame を使用しない場合


フレーム (TFrame) を使用しない場合の参考です.
添付のサンプルは,上のサンプルプロジェクトを,以下のように変更して作成しています.
  • (1) フォームからフレーム Frame1 を削除
  • (2) [プロジェクト] [プロジェクトから削除] で Unit2 (フレームのユニット) を削除
  • (3) uses 部から Unit2 を削除
  • (4) Frame1 に関する行を削除 ([Ctrl] + [F9] でエラーとなる行)
  • (5) TPanel をフォームに配置し,その上に TButton を配置
そして,OnCreate イベントコードの最後に次のコードを追加しています.


  Panel1.Top := Panel1.Top + GlassFrame.Top;


Align プロパティの値が alTop または alClient の時は,特にコードを追加しなくても,位置がずれることはありません.その他の場合は上のように設定が必要です.コンテナコントロール上のコントロール類は設定する必要ありません.コンテナコントロールだけ設定します.


図15
設計時画面
  • [追加ボタン] の Anchors の値は
    [akTop, akRight]
図16
実行画面
  • フォームをリサイズしても [追加ボタン] の位置は [最小化] ボタンの左端

実行時に,タイトルバーに配置したコントロールの位置を変更したい場合は,Paint メソッドのコードの最後に追加します.
例えば,WindowState が wsMaximized の時,サイズ変更枠が表示されないため,追加したボタンとフォームの最小化,最大化,閉じるボタンとの位置バランスが崩れます.次のコードは,その体裁を整えます.


    if WindowState = wsMaximized then begin
      SpeedButton1.Top := FWndFrameSize - 3;
    end else begin
      SpeedButton1.Top := -1;
    end;




04_GlassFrame プロパティと DoubleBuffered プロパティ


DWM による描画では,GlassFrame の Enabled プロパティの値を True にします.
GlassFrame は Aero のガラス効果を制御するプロパティですが,使用には少し注意が必要です.以下はこのプロパティについてテストした結果です.コードは何もありません.コントロールを配置しただけの状態です.


図17
設計時画面
図18
実行画面
  • GlassFrame の Enabled の値は Flase

次に,GlassFrame プロパティの Enabled と SheetOfGrass の値を,オブジェクトインスペクタで,共に True します.


図19
Enabled と SheetOfGlass の値を True に

下図が実行結果です.筆者は,Aero テーマの透明度を無効にしているのでこのようになります.色はシステムの設定によります.コントロールも文字列も半透明のガラス効果となっています.
コントロールの DoubleBuffered の値を True にすると,そのコントロールと,コントロール上の子コントロールの透明度が無効となることが分かります.DoubleBuffered はオブジェクトインスペクタで設定できます.


図20
実行結果
  • 半透明の曇りガラス効果とは言うが,あまり納得できる色とは言えない気もする
図21
Form1 の DoubleBuffered を True にした結果
図22
Panel1 の DoubleBuffered も True にした場合

以下は,Aero テーマの透明度を有効にした場合です.


図23
実行結果
図24
Form1 の DoubleBuffered を True にした結果
図25
Panel1 の DoubleBuffered も True にした場合

この GlassFrame の設定は,Aero テーマが無効の状態の時は,クライアント領域の背景描画が行われないようです.下図はそれを確認した結果です.
Aero テーマが無効の状態の場合,ボタンをタイトルバーに追加するサンプルも正常に動作しません.


図26
Aero テーマ無効
図27
Aero テーマ有効
図28
Aero テーマ無効で GlassFrame 設定

リスト3
Aero テーマの無効と有効の切り換え.uses に DwmApi が必要
procedure TForm1.Button1Click(Sender: TObject);
var
  dwAttribute : Cardinal;
  pvAttribute : Cardinal;
begin
  dwAttribute := DWMWA_NCRENDERING_POLICY;
  pvAttribute := DWMNCRP_DISABLED;
  DwmSetWindowAttribute(Handle, dwAttribute, @pvAttribute, SizeOf(pvAttribute));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  dwAttribute : Cardinal;
  pvAttribute : Cardinal;
begin
  dwAttribute := DWMWA_NCRENDERING_POLICY;
  pvAttribute := DWMNCRP_ENABLED;
  DwmSetWindowAttribute(Handle, dwAttribute, @pvAttribute, SizeOf(pvAttribute));
end;