Delphi Tips 
-----------------------------

キーワード:コンポーネント開発

>> Index

05/17 TIdentToInt型とTIntToIdent型
09/11 エディットコントロールにポップアップウィンドウをつけたい
09/06 ButtonのCaptionで改行を使って文字を複数段で表示したい
09/06 エディットコントロールにコンボボックスのようなボタンをつけたい
08/14 カスタムコントロールの子コントロールをオブジェクトインスペクタに表示させない
07/14 基本的な階層プロパティ定義の例
02/08 自作コントロールで IME 入力時の変換候補をキャレット位置に表示したい
02/08 Delphi1/2で状況依存型のコンポーネントヘルプを作るときの注意
02/08 親の published プロパティを子クラスで隠蔽する
02/08 oDelphi1.0とDelphi2.0/3.0でコンポーネントのソースを共有したい
02/08 complib.dllが壊れた!また全部のコンポーネントをインストールするの?!

最終更新: 8004 日前

0323  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2002/05/17 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2002/05/17 osamu 編集
TIdentToInt型とTIntToIdent型

IdentToColor は TIdentToInt 型の変換関数で、TIntToIdent型 とともに、整数型のプロパティを見やすくするために登録される関数です。

TColorのような特定の整数型に対し、TIdentToInt 型の関数とTIntToIdent型の関数を RegisterIntegerConsts で登録しておくとコンポーネントの整数型プロパティの特定の値を見やすい文字表現でオブジェクトインスペクタに表示することができます。
また、dfm ファイルも読みやすくなります。

また、実行時に任意の整数型に対して

function FindIntToIdent(AIntegerType: Pointer): TIntToIdent;
function FindIdentToInt(AIntegerType: Pointer): TIdentToInt;

で変換関数が存在するか調べることもでき、整数を見やすい文字表現に変換したり、その逆も可能です。
参照: [Delphi-ML:66991]

0235  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/09/06 西坂良幸 rev 1.3
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/11 K.Takaoka 編集
エディットコントロールにポップアップウィンドウをつけたい

エディットコントロールにボタンをつけた、コンボボックスもどきは、カスタムコントロールでよく見かけます。ボタンを押すと小ウィンドウが開くヤツです。これはどのように作るのでしょうか。

このような小ウィンドウをインプレースコントロールと呼びます。
ポイントは、小ウィンドウの親をコントロールにすることと、独自のフォーカスを与えないことです。これさえ理解できれば後は、小ウィンドウのVisbleの切り替えで開いたり閉じたりします。

ここでは、プロパティを受け渡すことを無視し、ただ開閉だけをやってみます。(マウス右ボタンで開く)

//  定義部
  TxEdit = class;

  // インプレースコントロール ここではリストボックス
  TinPlaceList = class(TListbox)
  private
    FEdit: TxEdit;
  protected
    procedure CreateWnd; override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    constructor CreateList(AOwner: TComponent; Edit: TxEdit);
  end;

  // ここでは、TEditから継承する
  TxEdit = class(TEdit)
  private
    FInplaceList:TListbox;
  protected
    procedure Mousedown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  public
    constructor Create(AOwner: TComponent);override;
    destructor Destroy;override;
    procedure DropDown;
  end;

// 実装部

//コンストラクタでコントロールに接続させる
constructor TInplaceList.CreateList(AOwner: TComponent; Edit: TxEdit);
begin
  inherited Create(AOwner);
  // 小ウィンドウに親のポインタを持たせて接続する
  FEdit := TxEdit(Edit);
  Visible := false;
end;

// インプレースコントロールの生成とフォーカス制御
procedure TInplaceList.CreateWnd;
begin
  inherited CreateWnd;
   //親の変更を行う
  if not (csDesigning in ComponentState) then
    Windows.SetParent(Handle, 0);
  //独自のフォカスメッセージを避ける
  CallWindowProc(DefWndProc, Handle, WM_SETFOCUS, 0, 0);
end;

//とりあえずマウス(左右)で閉じることにする
procedure TInplaceList.MouseUp(Button: TMouseButton; Shift: TShiftState;
    X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  if Button = mbLeft then
  begin
    // プロパティの受け渡しをここらで行う
    Hide;
  end;
  if Button = mbRight then
    Hide;
end;

// コンストラクタで、インプレースコントロールを生成
constructor TxEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FInplaceList := TInplaceList.CreateList(Self, Self);
  with FInplaceList do
  begin
    Parent := Self;
    TabStop := false;
    Visible := false;
    Top := FInplaceList.Top + Self.Height;
  end;
end;

// デストラクタで念のため明示的に解放する
destructor TxEdit.Destroy;
begin
  FInplaceList.free;
  inherited Destroy;
end;

// ドロップダウンリストの開閉のメソッドを作成する
procedure TxEdit.DropDown;
var
  xyPos: TPoint;
begin
  if (FInplaceList <> nil) and not FInplaceList.Visible then
  with FInplaceList do
  begin
    xyPos := Self.ClientToScreen(Point(0 + Self.Width - Width,  Self.Height));
    SetWindowPos(Handle, 0, xyPos.X, xyPos.Y, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE);
    Windows.SetFocus(Handle);
    Visible := not Visible;
    // プロパティの受け渡しをここらで行う
  end;
  Invalidate;
end;

// マウス右ボタンで開く
procedure TxEdit.Mousedown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  if Button = mbRight then
    DropDown;
  inherited Mousedown(Button, Shift, X, Y);
end;

TListBoxのかわりに、何でも使えます。TPanelなら、電卓、カレンダなどになりますね。TGridでもいいですね。
実際にカスタムコントロールを作るときは
・やはりボタンをつける
・キーボード入力にも対応させる
・必要なプロパティの受け渡しを行う
・ドロップダウンなどイベントを記述する
・ロストフォーカスで開きっぱなしにしない
などが必要でしょうか。

参照: [Delphi-ML:40241] <コンポーネント > <Standard>

0123  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.3
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/06 西坂良幸 編集
ButtonのCaptionで改行を使って文字を複数段で表示したい

Windows95では #13#10 挿入することにより思った通り表示できるのですが
WindowsNTの場合は改行されません。

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetWindowLong(Button1.Handle, GWL_STYLE, GetWindowLong(Button1.Handle, GWL_STYLE) or BS_MULTILINE);
  Button1.Caption := 'ABC' + #13#10 + 'DEF';
end;


なお、コンポ−ネント化する場合はCreateParamsをオーバーライドして下さい。
この方法は、TButtonControl系(TRadioButton,TCheckBox)でほとんど使えますが、オーナードロー系(TBitBtn,TSpeedButtonなど)のボタンではできません。

また、以下のプロパティエディッタをインストールすれば、オブジェクトインスペクタで,改行コードを入力を'\n'ですることが出来るようになります。

// 定義部
type
  // 複数行の入力を\nで受け入れるプロパティエディッタ
  TMultCapProperty = Class(TCaptionProperty)
  Public
    Function GetValue: string; Override;
    Procedure SetValue(const Value: string); Override;
  End;

procedure Register;


// 実装部
// 置き換える関数
procedure ReplaceStr(var Source : string; Search, Replace : string);
  function XPos(Source, Search : string):integer;
  begin
    if StrPos(PChar(Source), PCHar(Search)) = nil then
      result := 0
    else
      result := StrPos(PChar(Source), PCHar(Search)) - PChar(Source) + 1;
  end;
var
  p, L1, L2 : Integer;
begin
  L2 := Length(Search);
  p := XPos(Source, Search);
  while p <> 0 do
  begin
    L1 := Length(Source);
    if p = 1 then
      Source := Replace + Copy(Source, L2 + 1, L1)
    else
      Source := Copy(Source, 1, p - 1) + Replace +
              Copy(Source, p + L2, L1);
    p := XPos(Source, Search);
  end;
end;

function TMultCapProperty.GetValue: string;
begin
  Result := GetStrValue;
  // 以下3つのパターンがある
  ReplaceStr(Result, #13 + #10, '\n');
  ReplaceStr(Result, #10, '\n');
  ReplaceStr(Result, #13, '\n');
end;

procedure TMultCapProperty.SetValue(const Value: string);
var
  Caption : string;
begin
  Caption := Value;
  ReplaceStr(Caption, '\n', #13);
  SetStrValue(Caption);
end;

// プロパティエディタとして登録する
procedure Register;
begin
  // TLabelのCaptionプロパティエディタの登録--'\n'で改行入力
  RegisterPropertyEditor(TypeInfo(TCaption), TLabel; , 'Caption', TMultCapProperty);
  // TButtonのCaptionプロパティエディタの登録--'\n'で改行入力
  // ただし上記BS_MULTILINEが設定されていないとダメ
  RegisterPropertyEditor(TypeInfo(TCaption), TButton; , 'Caption', TMultCapProperty);
end;

参照: [Delphi-ML:17735] [Delphi-ML:39679] [builder:6270] <コンポーネント > <Standard>

0236  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/09/06 西坂良幸 rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/06 西坂良幸 編集
エディットコントロールにコンボボックスのようなボタンをつけたい


エディットをParentとするフォーカスを持たないTSpeedButtonを、貼り付けてやれば簡単です。
注意するのは、編集領域がボタンに重ならないようにEM_SETRECTNPを送ることですが、このメッセージが有効になるには、TEditのスタイルフラッグにES_MULTILINEを加えなければなりません。

以下の例は、ボタンを押せば単にメッセージボックスがでるだけのものです。

// 定義部
  TxEdit = class(TEdit)
  private
    FButton: TSpeedButton;
    FOnButtonClick: TNotifyEvent;
    procedure SetEditRect;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  protected
    procedure ButtonClick (Sender: TObject);
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property OnButtonClick: TNotifyEvent read FOnButtonClick write FOnButtonClick;
  end;

// 実装部
constructor TxEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  // ボタンを作成する
  FButton := TSpeedButton.Create (Self);
  with FButton do
  begin
    Parent := Self;
    Width := 18;
    Height := Height - 4;
    // リソースから ▼ のビットマップ(10×8程度×2)を読むのは省略
    // NumGlyphs := 2;
    // result.Glyph.LoadFromResourceName(HInstance,'????');
    CurSor := crArrow;
    OnClick := ButtonClick;
  end;
end;

// スタイルフラッグにES_MULTILINEが必要です。
procedure TxEdit.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or ES_MULTILINE;
end;

// ハンドルが生成されてからSetEditRectを呼ぶ
procedure TxEdit.CreateWnd;
begin
  inherited CreateWnd;
  SetEditRect;
end;

// エディット(編集)領域を再設定する(ボタンの部分を排除)
procedure TxEdit.SetEditRect;
var
  Loc: TRect;
begin
  SendMessage(Handle, EM_GETRECT, 0, LongInt(@Loc));
  Loc.Right := ClientWidth - FButton.Width - 2;
  SendMessage(Handle, EM_SETRECTNP, 0, LongInt(@Loc));
end;

// 常に左端にアジャストさせる
procedure TxEdit.WMSize(var Message: TWMSize);
begin
  inherited;
  if FButton <> nil then
  begin
    if NewStyleControls and Ctl3D then
      FButton.SetBounds(Width - FButton.Width - 4, 0, FButton.Width, Height - 4)
    else FButton.SetBounds (Width - FButton.Width, 1, FButton.Width, Height - 2);
    SetEditRect;
  end;
end;

// ボタンのクリックに対応するイベントを設定する
procedure TxEdit.ButtonClick (Sender: TObject);
begin
  if Assigned(FOnButtonClick) then FOnButtonClick(self);
  ShowMessage('ボタンが押されました')
  // ここに必要な処理を書く
end;

この他、CM_EnabledChangeを捕まえて、EditとボタンのEnabledを同期させることが必要でしょうか。
参照: <コンポーネント > <Standard>

0012  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/08/14 osamu 編集
カスタムコントロールの子コントロールをオブジェクトインスペクタに表示させない

オブジェクトインスペクタには、OwnerがFormであるコントロールだけが表示されるので、カスタムコントロールの内部で作成するコンポーネントならば、OwnerにSelfを与えればよいです。
サンプルコンポーネントのTSpinEditなどを参照しましょう。
参照: [Delphi-ML:5112]

0200  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/07/14 久保田 宏光 rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/07/14 久保田 宏光 編集
基本的な階層プロパティ定義の例


// 子にするクラスの宣言

// ・TPersistentから継承する
// ・プロパティ内に、さらに他のクラスを定義する場合はAssignでコピーする。
//   直接代入を許さないように留意する(SetListメソッドを参照)
//   メソッドをprotectedにする場合はvirtualにする。
//   また、隠蔽したい場合はprivateに置く。
//   ※protected/privateが良く分からない場合はprotected/virtualが無難
// ・クラスにAssignを実装する必要がある。

type
  TChild = class(TPersistent)
  private
    FList:TStrings;
  public
    constructor Create;
    destructor  Destroy; override;
    procedure   Assign(Source:TPersistent); override ;
  protected
    procedure SetList(Value:TStrings); virtual;
  published
    property List :TStrings read FList write SetList;
  end;

//親側のクラス宣言

//・こちらでも、Childプロパティの書き込みにSetChildを実装する。

type
  TParent = class(TComponent)
  Private
    FChild:TChild;
  public
    constructor Create(AOwner:TComponent); override;
    destructor  Destroy; override;
  protected
    procedure SetChild(Value:TChild); override;
  published
    property Child :TChild read FChild write SetChild;
  end;

//実現部(子)

//・抽象クラスで宣言したプロパティは、こちらで実装クラスとして
//  create/freeする。
//・プロパティのwriteはメソッドになっている。間違っても
//   List := TStringList.Create
//   ~~~~   などとしないように。
//・コンポーネントがTStrings内でObjectsを使用している場合のみ、
//  free時に解放する。
//  
//  例 )
//  for i:=0 to FList.count - 1 to
//    FList.Objects[i].Free;
//  FList.Free;

constructor TChild.Create;
begin
  inherited Create;
  FList := TStringList.Create;
end;

destructor TChild.Destroy;
begin
  FList.Free;
  inherited Destroy;
end;

procedure TChildSetList(Value:TStrings);
begin
  if Value <> FList then
    FList.Assign(Value);
end;

Procedure TChild.Assign(Source:TPersistent);
begin
   if Source is TChild then
      FList.Assign(TChild(Source).List);
   inherited Assign(Source);
end;

//実現部(親)

//・FChildをcreate/freeするのを忘れないように。

constructor THyperText.Create(AOwner:TComponent);
begin
  inherited Create(AOWner);
  FChild := TChild.Create;
end;

destructor THyperText.Destroy;
begin
  FChild.Free;
  inherited Destroy;
end;

参照: <PASCAL>

0083  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
自作コントロールで IME 入力時の変換候補をキャレット位置に表示したい

IMEが編集を開始する直前にWM_IMESTARTCOMPOSITION というメッセージを送って来るので、そのメッセージを捕らえて設定してやります。

class TCustom : public TCustomControl
{
  ・・・・・・・・・・・・

   void __fastcall IMEStart(TMessage& Message);

   BEGIN_MESSAGE_MAP
      MESSAGE_HANDLER( WM_IME_STARTCOMPOSITION ,TMessage,IMEStart)
   END_MESSAGE_MAP(TCustomControl)
};

void __fastcall TCustom::IMEStart(TMessage& Message)
{
  //  IMEの位置をキャレットのポジションに設定
  COMPOSITIONFORM CompForm;
  POINT pt;
  LOGFONT lf;
  HIMC hImc=ImmGetContext(Handle);

  //キャンバスのフォントと同じに設定する
  GetObject(Canvas->Font->Handle,sizeof(LOGFONT),&lf);
  ImmSetCompositionFont(hImc,&lf);

  //キャレットのポジションに設定する
  ImmGetCompositionWindow(hImc,&CompForm);
  CompForm.dwStyle=CFS_POINT;
  GetCaretPos(&pt);
  CompForm.ptCurrentPos=pt;
  ImmSetCompositionWindow(hImc,&CompForm);

  ImmReleaseContext(Handle, hImc);

  // その他の処理
  ・・・・・・・・・・・・・
}
参照: [builder:5269] <その他Windows関連> <Windows> <コンポーネント > <Standard>

0087  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
Delphi1/2で状況依存型のコンポーネントヘルプを作るときの注意

[delphi-cw 127]より。
マニュアルによれば、ヘルプトピックに、決められた形式の B 脚注を入れれば良いことになっているのですが、この時、脚注の書式に気を付けないといけません。

#{\footnote hlp_TNkDIB}
${\footnote TNkDIB}
K{\footnote TNkDIB;NkDIB;DIB}
B{\footnote class_TNkDIB}

などとする変わりに、

#{\footnote # hlp_TNkDIB}
${\footnote $ TNkDIB}
K{\footnote K TNkDIB;NkDIB;DIB}
B{\footnote B class_TNkDIB}

のようにしないと、kwgen.exe がキーワードの読み取りに失敗してしまいます。
参照: <開発環境> <バグ> <ヘルプ>

0102  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
親の published プロパティを子クラスで隠蔽する

Object Pascal では、クラスの継承時に親クラスで指定されたアクセス指定子よりも厳しいアクセス指定子をメンバに指定しても無視されてしまいます。従って、親クラスで public に指定されたメンバを子クラスで protected にするようなことは出来ません。
これは、published も同じで、一度公開されたものを元に戻すことはできないのですが、単に「オブジェクト・インスペクタに表示させない」だけならば、「読み込み専用のプロパティ」として再宣言することで、オブジェクトインスペクタに表示しないようにすることができます。

例:AutoScrollプロパティを読み込み専用にする。

  THogeScrollBox = class(TScrollBox)
  private
    function GetAutoScroll: Boolean;
  published
    property AutoScroll: Boolean read GetAutoScroll;
  end;

  function THogeScrollBox.GetAutoScroll: Boolean;
  begin
    Result := inherited AutoScroll;
  end;

※当然、イベントも同じ方法で隠すことが出来ます。
参照: [Delphi-ML:21207]

0015  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
oDelphi1.0とDelphi2.0/3.0でコンポーネントのソースを共有したい

普通にやると、*.dcr のフォーマットが違うのと、*.dcu の形式が違うという2つの問題が生じます。

この前者を解決する方法が[Delphi-ML:6212]に説明されています。

味噌となるのは、コンポーネントを含むユニットと、コンポーネントを登録するユニット( RegisterComponentsを行うRegister手続きを含むユニット )を分離して記述することです。
実はこれは、VCL自身が採用しているやり方です。

後者については、手ですべての *.dcu ファイルを削除してやるしか方法を思い付きません。
参照: [Delphi-ML:6212] <開発環境>

0014  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
complib.dllが壊れた!また全部のコンポーネントをインストールするの?!

以下のようなファイルを一つ作っておくと、コンポーネントの再インストールの手間がかなり省けます。
この方法はDelphi1.0/2.0のどちらでも使えます。

----------------------------------------------------
unit MyReg;

interface

{インストールしたいコンポーネント}
{ユニットを列挙する              }

uses
  Stdreg,
  Sysreg,
  Sampreg,
  Ddereg;

{上記のコンポーネントユニットに  }
{対応するリソースファイルがあれば}
{インクルード?する              }

{$R D:\DELPHI\LIB\STDREG.DCR}
{$R D:\DELPHI\LIB\SYSREG.DCR}
{$R D:\DELPHI\LIB\SAMPREG.DCR}
{$R D:\DELPHI\LIB\DDEREG.DCR}

procedure Register;

implementation

{個々のユニットのRegister手続きを}
{呼び出す                        }

procedure Register;
begin
  Stdreg.Register;
  Sysreg.Register;
  Sampreg.Register;
  Ddereg.Register;
end;

end.
----------------------------------------------------

他のコンポーネントファイルをすべてアンインストールして、あるいは、まったく新しくライブラリを作って、このMyRegのみをインストールすると、中で呼び出した全てのコンポーネントユニットをすべてインストールしたのと同じ効果が得られます。

御自分で作った/ダウンロードしたユニットもここに加えれば、最インストールの手間はほとんどなくなります。

ただし、個々のコンポーネントがライブラリパス以外に含まれている場合には手動でライブラリパスを入力しなければならなくなるので、MyReg.Pas のようなユニットは、中で参照するユニットと同じディレクトリに置いておくのが良いでしょう。

御参考までに。
参照: [Delphi-ML:7402] <開発環境>

[新規作成] [最新の情報に更新]

How To
Lounge
KeyWords

Tips
Delphi
Home
Osamu Takeuchi osamu@big.or.jp

.