Delphi Programming / Object Pascal
ロゴマーク
[掲載 2012年07月17日] [更新 2012年07月17日] Delphi サンプルプログラム集
082_TPanel 等の複数行表示と protected 部へのアクセス
動作確認等 Windows XP(SP3) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE Pro
082_TPanelMultipleLines.zip [2,176 KB] 2012年07月17日版 (EXE 同梱)







TPanel 等の複数行表示と protected 部へのアクセス


TPanel や TSpeedButton 等のコントロールの Caption を複数行にして表示するための参考用サンプルです。protected 部にアクセスするための参考コードも掲載しています。
ここでは、対象コントロール (コンポーネント) を、TSpeedButton にしています。





01_sLineBreak ( 改行文字 ) による複数行表示


次のコードのように、文字列の途中に改行文字を挿入すれば、複数行表示となります。
このコードでは、ボタンをクリックすると複数行表示するようにしています。


図1
設計時画面
図2
実行結果

リスト1
sLineBreak ( 改行文字 ) による複数行表示
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  TSpeeButtonに複数行表示(TPanelでも同じ方法で可能)
//  文字列を改行文字で結合する方法
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  SpeedButton1.Caption := '123' + sLineBreak + 'ABC';
end;

end.




02_Canvas への描画と protected 部へのアクセス


上のサンプルでは、行間はシステムによって決まってしまいます。また、文字列の描画位置も限られています。
行間を任意の値にしたり、表示位置を自由に変更するには、コントロールの Canvas に描画します。ただし、TPanel 等の Canvas プロパティは、protected 部で定義されているため、そのままではアクセスできません。そこで、TPanel を継承したクラスを定義し、派生クラスでキャストして protected 部にアクセスするようにしたのが次のサンプルです。

継承クラスを定義すると、元のクラスの protected 部のメンバ、つまりプロパティやメソッドが使えるようになります。新規にプロジェクトを作成すると、TForm の protected 部のメンバーも使用可能となっています。それと同じ原理です。
より新しいバージョンの Delphi では、以前は protected 部にあったメンバーが publuc, published 部に移動していることもあります。

ただし、この方法では、SpeedButton の再描画が行われると、描画した文字列が消えてしまいます。


図3
設計時画面
図4
実行して Button1 をクリック
  • Caption が 2 行表示となる
図5
マウスを移動
  • マウスがくると文字列が消えてしまう
  • 他のウィンドウが上に重なっても消えてしまう

リスト2
Canvas への描画と protected 部へのアクセス
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//  protected部にアクセスするためのクラス定義
//  インチキでもなんでもない
type
  TMySpeedButton = class(TSpeedButton);

//=============================================================================
//  CanvasのTextOutやWin32APIのDrawTextを使用してCanvasに描画する方法
//  この方法なら任意の位置にテキストや画像を表示可能
//  ただし、永続的には表示できない
//
//  コンポーネントとして実装するにはPaintメソッド等を使用する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  //SpeedButton1をTMySpeedButtonにキャストする
  TMySpeedButton(SpeedButton1).Canvas.TextOut(20, 10, '123');
  TMySpeedButton(SpeedButton1).Canvas.TextOut(20, 30, 'ABC');
end;

end.




03_複数行表示機能付きのコンポーネント


上のサンプルコードでは、TSpeedButton に他のウインドウやマウスが移動してくると、折角描画した文字列が消えてしまいます。TPaintBox への描画と同じ現象です。
protected 部へのアクセスは便利ですが、描画関係はこのような問題があります。これを避けるには、元のコンポーネントの Paint メソッドをオーバーライドした継承コンポーネントを作成し、Paint メソッド内で Canvas に描画します。
Paint メソッドは、再描画が必要な時に自動的に実行されるので、描画した内容が消えることはありません。次のサンプルでは、コンポーネントをインストールするのは面倒なので、実行時に生成してテストしています。

このコンポーネントでは、Caption をコンポーネント内部で設定してしまっています。実用的な意味では、外部から操作可能な public や published なプロパティにしておくべきでしょう。
文字列配列をプロパティとしてもいいし、改行を含む文字列をプロパティとして、コンポーネントの内部で改行文字を判定して複数行にする方法も考えられます。

プロパティやメソッド類の実装方法は、他のコンポーネント類のコードを参考にしてください。



[備考]
DrawText 関数、DrawTextEx 関数を使用すれば改行を含む文字列を処理できますが、これらの関数には行間を調整する機能はありません。
TextOut 関数を使用する場合、改行文字で分割して複数行にする方法も考えられます。



図6
設計時画面
図7
実行結果

リスト3
複数行機能付きのコンポーネントを生成して使用する
unit Unit1;

interface

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

type
  TMySpeedButton = class(TSpeedButton)
  protected
    procedure Paint; override;
  end;

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォームの生成時に、複数行表示のTPanelやTSpeedButtonを生成する
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  MySpeedButton1 := TMySpeedButton.Create(Self);
  MySpeedButton1.Parent := Self;

  MySpeedButton1.Left   := 24;
  MySpeedButton1.Top    := 55;
  MySpeedButton1.Width  := 145;
  MySpeedButton1.Height := 49;
end;

{ TMySpeedButton }

//=============================================================================
//  複数行表示機能のコンポーネントのPaintメソッド(のoverride)
//  Paintメソッドは自動的に実行される
//=============================================================================
procedure TMySpeedButton.Paint;
begin
  inherited;

  Canvas.TextOut(20, 10, '123');
  Canvas.TextOut(20, 30, 'ABC');
end;

end.




04_複数行表示機能付きのコンポーネントを実行時に置き換える


コンポーネントをインストールしないでテストする別の方法です。
派生コンポーネントの型の変数を宣言 (定義) して、実行時に、フォームに配置したコンポーネントを継承したコンポーネントと置き換えて使用します。そのために、以下のリンクの記事で説明しているユニットを利用しています。このユニットのソースコードは、サンプルのプロジェクに添付しています。


図8
設計時画面
  • フォームに配置してある SpeedButtonを、実行時に派生コンポーネントに入れ換える
図9
実行結果

リスト4
実行時にコンポーネントを置き換える
unit Unit1;

interface

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

type
  TMySpeedButton = class(TSpeedButton)
  protected
    procedure Paint; override;
  end;

  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    // 派生コンポのオブジェクトを定義
    MySpeedButton1 : TMySpeedButton;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  plChgCompo;

{$R *.DFM}

//=============================================================================
//  フォームの生成時にコンポーネントを置き換える
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //フォーム作成時にコンポーネントの入れ替え
  // フォーム作成時にコンポーネントの入れ替え
  MySpeedButton1 := TMySpeedButton(ChangeComponent(SpeedButton1, TMySpeedButton));
end;

{ TMySpeedButton }

//=============================================================================
//  複数行表示機能のコンポーネントのPaintメソッド(のoverride)
//  Paintメソッドは自動的に実行される
//=============================================================================
procedure TMySpeedButton.Paint;
begin
  inherited;

  Canvas.TextOut(20, 10, '123');
  Canvas.TextOut(20, 30, 'ABC');
end;

end.


派生コンポーネントのプロパティを使用する例です。
このサンプルでは、派生コンポーネントに Text という String 型のプロパティを実装しています。


図10
実行結果

リスト5
実行時にコンポーネントを置き換える時のプロパティの設定方法
unit Unit1;

interface

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

type
  TMySpeedButton = class(TSpeedButton)
  private
    FText : String;
  protected
    procedure Paint; override;
  public
    property Text : String read FText write FText;
  end;

  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    // 派生コンポのオブジェクトを定義
    MySpeedButton1 : TMySpeedButton;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  plChgCompo;

{$R *.DFM}

//=============================================================================
//  フォームの生成時にコンポーネントを置き換える
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // フォーム作成時にコンポーネントの入れ替え
  MySpeedButton1 := TMySpeedButton(ChangeComponent(SpeedButton1, TMySpeedButton));
  MySpeedButton1.Text := '東京都';
end;

{ TMySpeedButton }

//=============================================================================
//  複数行表示機能のコンポーネントのPaintメソッド(のoverride)
//  Paintメソッドは自動的に実行される
//=============================================================================
procedure TMySpeedButton.Paint;
begin
  inherited;

  Canvas.TextOut(20, 10, FText);
end;

end.