●Gen's FAQ集以外の日本語でのFAQ&Tips集


Gen's 不定期(^^;ローテク講座


D1からの内容がそのままなので、そろそろ整理、追加していきます。


[Q]共有イベントでどのボタン(コントロール)から呼ばれたかを調べるには?

いのですか?

[A]
   いくつか方法がありますが、簡単なやり方を...
   ポイントはTagプロパティの活用!!

   1:呼び出し元のコンポーネントのTagプロパティの番号を参照する
     procedure TForm1.AnyButtonClick(Sender:TObject);
     begin
       If TButton(Sender).Tag = 1 Then  begin
        {ボタン1の処理};
       end;
     end;

   数が増えてくればcase文で分岐させればいいです。

   2:呼び出し元コンポーネントのNameプロパティを参照する
     If TButton(Sender).Name = 'Button1' Then begin
       {ボタン1の処理};
     end;

[補足]
    上の例では、TButton型にキャストしていますが、これを汎用にするには
    TComponent(Sender).Tag とやればいいです。VBのコントロール配列も
    1の、Tagプロパティを使えば簡単に実現できます。「Tagプロパティは何
    に使うんだろう?」と思っていた方には、目から鱗、知ってしまえばしょ
    っちゅう使います。

[目次]  [次に進む]


[Q]システムメニューに項目を追加するには?

[A]
   項目の追加にはAPIを使います。また、メニューの項目を識別するために
   独自のメニューIDを定義します。システムメニューがクリックされた時は、
   WM_SYSCOMMANDというメッセージが飛んできますので、これをキャッチし
   て追加した項目に対するイベントハンドラを呼んでやります。

  FormのPrivateセクションに、

  Procedure WMSYSCOMMAND(var Msg : TWMSYSCOMMAND);Message WM_SYSCOMMAND;

  と宣言しておいて、{*1}

  implementation

  Const 
    Menu_ID = 10;   {追加メニューのメニューID}

  {システムメニューに独自の項目を追加する}
  procedure TForm1.FormCreate(Sender: TObject);
  var
       Mn : HMenu;
  begin
   {*2}Mn := GetSystemMenu(Handle,False); 
       If Not AppendMenu(Mn,MF_STRING,Menu_ID,'項目1') Then
          ShowMessage('Error');
       If Not InsertMenu(Mn,2, MF_BYPOSITION Or MF_STRING,Menu_ID+1,'項目2')
          Then
          ShowMessage('Error');
  end;

  {追加した項目に対するイベントハンドラの処理}
  Procedure TForm1.WMSYSCOMMAND(var Msg : TWMSYSCOMMAND);
  Begin
       If Msg.CmdType = Menu_ID Then begin
          {項目1をクリックした時の処理}
       end;
       If Msg.CmdType = Menu_ID + 1 Then begin
          {項目2をクリックした時の処理}
       end;
   {*3}DefaultHandler(Msg);
  End;

[補足]
{*1}この宣言のしかたは、他のMessageを捕捉する時も同じようなやり方
    で宣言出来ますので覚えておいて損はないです。
{*2}Handleの所をApplication.Handleに変えるとアプリケーションのシ
    ステムコマンドになります。
{*3}通常のシステムメニュー項目がクリックされた場合、その処理をす
    るためののためのオマジナイ。

[Help]
  GetSystemMenu,AppendMenu,InsertMenu,WM_SYSCOMMAND

[前に戻る] [目次] [次に進む]


[Q]他のアプリのダイアログを終了させるには?

[A]
   そのアプリのウィンドウハンドル、及びダイアログのキャプションの名前が
   分かっていれば比較的簡単にできます。他のアプリを制御する時は、
   Application.ProcessMessagesでループさせる事が多いので以下そのような
   コードを書きます。ダイアログのキャプションが '警告' だとすると
   
   SomeAppWHnd: HWnd;{制御するアプリのウィンドウハンドル}
   DlgWHnd : HWnd;   {ダイアログのウィンドウハンドル}
   isDone            {メッセージを送ったかどうかのフラグ}
   
   isDone := false;
   repeat
     Application.ProcessMessages;
     DlgWHnd := FindWindow(NIL,'警告');
     if (DlgWHnd  0) and (not isDone) then begin
       SendMessage(DlgWHnd,WM_COMMAND,IDCANCEL,0);
       isDone := true;
     end;
   until SomeAppWHnd = 0;
   
  わざわざフラグを立てているのは、Messageを送っても、直ちにダイアログが
  終了するわけではない(そのダイアログの親が処理をする時間がかかる)の
  で、何度もMessageを送らなくていいようにするためです。試しにフラグなし
  でやってみると、ひたすらMessageを送っているのが分かります

[補足]
  SendMessageを使わずに、EndDialogというAPIを使っても出来ます。但し、
  ダイアログがモードレスの場合後ろに隠れてしまうだけで、終了している
  わけではないようなので注意が必要です。通常は、こちらのやり方でいい
  でしょう。
  後、そのダイアログの親を知りたければAPIのGetParentを使います。

[help]
  SendMessage、EndDialog、GetParent

[前に戻る] [目次] [次に進む]


[Q]TStringListに文字列と一緒に整数を保持させるには?

[A]お薦めできる方法かどうかは分かりませんが...以下の方法を試してみてください。
   ちょっとトリッキーなやり方ですが、TStringの持っているTObject配列を
   使えば可能です。このデータは4バイトのポインタ型なので、整数を保持
   させるためにキャストします。例えば、ListBoxに '項目1' という値とと
   もに、12345という値を格納するには、
   
   ListBox1.Items.AddObject('項目1',TObject(12345));
   
   取り出す時には、上で追加した項目が先頭の場合、
   
   LongInt( ListBox1.Items.Objects[0]);
                                  ^^^何番目の項目か
[補足]
  保持する時はTObjectでキャスト、取り出す(参照する)時はTObjectが4バイト
  のポインタ型だという点に気を付けてキャストするようにして下さい。

  2つ以上の数字を格納したければ、例えば下のように自分でオブジェクトの
  宣言をしてから使えばいいと思うのですが、これについてはもう少し実験し
  てから書きます。
  type
    hogehoge = class(TObject)
      ItemOne: Integer;
      ItemTwo: Integer;
    end;
  
[help]
  TObject、TStringList、TString、型キャスト

[前に戻る] [目次]


[Q]あるコンポーネントを名前で参照するには?

[A]
   FindComponentメソッドを使います。例えば
   
   for i := 1 to 5 do
     Form1.FindComponent('EditBox'+IntToStr(i)) as TEditBox do
       Text := 'Default';
   
   この例では、フォームにEditBoxが5つあるとして、そのすべてに'Default'
   という文字列を入れます。Delphiでは、コンポーネントをパレットから貼
   り付けていくと、デフォルトでEditBox1、EditBox2、・・・、と名前がついて
   いきますので、これを利用します。
   
[補足]
  また、FindComponentはTComponent型を返すので、2行目の所で目的の型
  (この場合TEditBox)にキャストしてやってます。実際に使う時は例外など
  にも対処するようにしておいてくださいね(^^)
  
  関連したことで、ヘルプでComponentsプロパティ、ComponentCountsプロパティ
  の使用例を見ると、色々使い方が浮かんでくると思います。

[help]
  FindComponent

[前に戻る] [目次] [次に進む]


[Q]仮想キーコードの英数字が未定義なのですが...

次のようなコードをコンパイルすると VK_N の部分が、「未定義の識別子
です」といって怒られます。なぜですか?VK_F10等はすんなり通ります。

OnKeyDown イベントのなかで
  if Key = VK_N then begin
    {Nキーが押された時の処理}
  end;

[A]
   'A'〜'Z'、'0'〜'9'のキーの仮想キーコードが実は定義されていません。
   何故かは知りません。判別式の部分を以下のようにしてみて下さい。

   Chr(Key) = 'N'

   送られてくるKeyをcharにキャストするわけです。

[前に戻る] [目次] [次に進む]


[Q]ある実行ファイルから、そこに格納されているアイコンを取り出すにはどうすればいいのですか?

[A]
   APIのExtractIconを使います。次のコードを参考にして下さい。

   var
     Icon: TIcon;
   begin
     Icon := TIcon.Create;
     Icon.Handle := ExtractIcon(hInstance, 'EXEファイル名', 0);
     Image1.Picture.Icon := Icon;                            {^^^*1}
     Icon.Free;
   end;

   {^^^*1}の部分について...
   これを-1とするとそのファイルの中にあるアイコンの総数を得られるの
   ですが、これをするとメモリを食い潰すというバグ付きのAPIなので気
   を付けてください。決してDelphiが悪いのではありません。例のように
   0を指定すると最初に登録されているアイコンのハンドルが得られます。
   Win32でも同じAPIがありますが、バグがそのままかどうかは未確認です。


[前に戻る] [目次] [次に進む]


[Q]イメージエディタで作ったカーソルや、ビットマップをリソースファイルとして保存したのですが、実行時になると読み込めません。

[A]
   リソースファイル(〜.RES)の名前をプロジェクトの名前(〜.DPR)と同じ
   にしているのが原因です。Delphiではプロジェクトと同じ名前のリソー
   スファイルをIDEが管理します。ですから、何か別の名前で保存して
   下さい。

   また、格納するリソースの名前は、すべて大文字で記述して下さい。

[前に戻る] [目次] [次に進む]


[Q]TABキー以外でコントロールのフォーカスを移動させる方法

[A]
フォーカスの移動にはSendMessage(API)を使うと比較的簡単に行えます。
以下の例を参考にしてみて下さい。

{Enterキーで次のコントロールに移動}
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then begin
    SendMessage(MyForm.Handle, WM_NEXTDLGCTL, 0, 0);
    Key := #0;
  end;
  if Key  #0 then inherited KeyPress(Key);
end;

{上下キーで前後のコントロールに移動}
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
var
  Direction: Word;
begin
  if (Key = VK_UP) or (Key = VK_DOWN) then begin
    if Key = VK_UP then Direction := 1
    else Direction :=0;
    SendMessage(Handle, WM_NEXTDLGCTL, Direction, 0);
  end
  else inherited KeyDown(Key, Shift);
end;

{制限字数になったら次のコントロールに移動}
procedure TForm1.Edit1Change(Sender: TObject);
begin
  if Length(Edit1.Text) >= Edit1.MaxLength then
     SendMessage(Handle, WM_NEXTDLGCTL, 0, 0);
end;

※長さの判定の所を以下のように変えて、複数のEditControlの
  共有イベントにすれば制限字数の異なるEditControlがあっても
  イベントハンドラの記述は一つですみます。
  Length(TEdit(Sender).Text) >= TEdit(Sender).MaxLength

----------------------------------------------------------------

※OnKeyPressとOnKeyDownの違いについては、ヘルプを参照して
  勉強して下さい。

※SendMessage(Handle, WM_NEXTDLGCTL, 0, 0);
  の第3引数が0であれば次のコントロール、
              1であれば前のコントロール
  に移動できるようです。

[前に戻る] [目次] [次に進む]


[Q]delay手続きはないのですか?

[A]
フォームのPrivateメソッドとして次のような手続きを定義してそれを使ってみて下さい。

{delay相当の手続き(単位はミリ秒)}
procedure TForm1.Delay(ms:integer);
var
   StartCount:longint;
begin
  StartCount:=GetTickCount;
  repeat
    Application.ProcessMessages;
  until ((GetTickCount-StartCount) >= Longint(ms));
end;

[前に戻る] [目次] [次に進む]


[Q]OpenDialogでルートのファイル名が変ですまた、一度Dialogを閉じた後、二度とOpenDialogが開けません。

[A]
   これは、Delphiのバグです。VCLソースを持っている方は、ataruさんの私家版FAQ
   を参考にしてソースを書き換えて下さい。お持ちでない方は、
   
   ・ファイル名については、自分で、'\\'→'\'へ変換するようにプログラムを組む。
   ・Dialogが開けなくなる点については、以下のような一行を加えてみて下さい。
   
   if OpenDialog1.Execute then begin
     {処理・・・}
     OpenDialog1.FileName := ''; {この行を加えます。}
   end;
   

[前に戻る] [目次] [次に進む]


[Q]MemoコンポーネントのFAQ&Tips

1.memoコンポーネントで、現在のキャレットの位置に、文字列を挿入す
  るには
2.上記1.でファイルから読み込んだ時(ReadLnを使った時)など、改行
  コードは読み込まれませんが、改行させたい時はどうすれば
3.指定の行番号にキャレットを移動させるには
4.現在キャレットのある行の行番号を求めるには
5.特定の行の色を変えるには

[A]
1.Memo1.SelText := Edit1.Text;
                   ^^^^^^^^^^要するに挿入したい文字列(255文字以内)
  Memo1.SetSelTextBuf(PChar型変数) (それ以上の文字数の場合)
  とすれば出来ます。

2.Memo1.SelText := Edit1.Text+#13+#10;
                             ^^^^^^^^この部分を追加します。
                             Chr(13)+chr(10)でも良いです。

3.Memo1.SelStart:=SendMessage(Memo1.Handle,EM_LineIndex,行番号,0);
  としてやればOK

4.Memo1.SelStart:=SendMessage(Memo1.Handle,EM_LineFormChar,SelStart,0);
  としてやればOK

5.MemoはWindowsの標準コントロールを使っているので、TMemoを使う限
  り出来ません。delphi2.0ではTRichEditコントロールが有るので、こ
  れを使えば出来ます。


[補足]


[help]

[前に戻る] [目次] [次に進む]


[Q]コマンドライン引数の取得はどうすれば?

[A]
コマンドラインを調べるために以下の関数が使えます。

ParamCount  ・・・ コマンドライン引数の個数(Word型)
ParamStr(n) ・・・ n番目のコマンドライン文字列(Strings型)

PramStr(0)は、Application.ExeNameと同じ事で、実行プログラムのフルパス
を返します。

あと、CmdLine(PChar型)と言う変数を参照すると、コマンドラインがそのまま
(スペース区切りなども含めて)調べられます。この変数はSystemユニットで
定義されているので、改めて宣言する必要はありません。

[例]
a:\bin\MyApp.Exe /A /B /C
としてプログラムを実行すると、これらのコマンドラインを調べるのには、
以下のような方法があります。

--- ParamCount,ParamStrを使った方法

{ParamCountの数だけ引数を調べる}
if ParamCount > 0 then
  for i := 1 to ParamCount do begin
    if ParamStr(i) = '/A' then {/Aがある時の処理}
    if ParamStr(i) = '/B' then {/Bがある時の処理}
    if ParamStr(i) = '/C' then {/Cがある時の処理}
  end;

※実際には、このように引数がなかった場合の事も考慮して下さい。

--- CmdLineを使った方法

var
  S : String;

{コマンドライン中に指定の文字列が含まれているかどうかを一気に調べる}
S := StrPas(CmdLine);
if Pos('/A',S) > 0 then {/Aがある時の処理}
if Pos('/B',S) > 0 then {/Bがある時の処理}
if Pos('/C',S) > 0 then {/Cがある時の処理}


[前に戻る] [目次] [次に進む]


[Q]メニュー部分の高さはどうやって取得すればいいのですか?

[A]
パラメータにSM_CYMENUを指定してWinAPIのGetSystemMetricsを使います。
ヘルプの総合検索で、GetSystemMetricsを引いてみるとその他にもいろ
いろ載っています。

(注)単一行の高さしか取得できないので、ウィンドウの幅を最少に変えた
場合など、メニューが複数行に渡っている場合などは注意してください。

[キーワード]
GetSystemMetrics

[前に戻る] [目次] [次に進む]


[Q]TStringListのFindメソッドがうまく動作しないのですが?

[A]
Findメソッドを使うためには、SortedプロパティをTrueにする必要が
あります。Sortedプロパティがfalseの場合は、indexOfメソッドを使
ってください。

[キーワード]
TStringList、indexOf、Find

[前に戻る] [目次] [次に進む]


[Q]日付処理に関するFAQ

TDateTime型の変数に例えば96年8月10日を代入するためにプログラム中で
    aDate := 96/8/10;
  とやると日付が変になります。

[A]
  TDateTime型はDouble型で、整数部分が日付、小数部分が時間となって
  います。質問にあるような代入をすると96÷8÷10で1.2が代入されてし
  まうので、おかしくなります。下記のようにStrToDate関数を使って代
  入してください。
  
  aDate := StrToDate('96/8/10');


[前に戻る] [目次] [次に進む]


[Q]StringGridのFAQ&TIPS

  1.セルの色付け、フォント、描画などを個別にするには...

  2.表形式で表示したいだけで入力は必要ないとき、フォーカスを示す描画は必要ないのですが...

  3.セルの表示内容を、ECXELに表示したい...

[A]
  1.2.個別に描画したいセルが少ないときはDefaultDrawing
    をTrueにしたままOnDrawCellイベントハンドラの中に記述すればOK。デフォ
    ルトの描画に上書き描画することになります。
  3.行ごとにセルの内容をTABあるいはカンマで区切ってテキスト形式でクリップ
    ボードに  コピー、そしてECXELでペーストしてやればOK(^^)。

[例]
  procedure TForm1.StringGrid1OnDrawCell(Sender: TObject; Col,
    Row: Longint; Rect: TRect; State: TGridDrawState);
  begin
    with StringGrid1.Canvas do begin
      
      //フォントの色を設定
      Font := StringGrid1.Font;
      if (Col = 0) and (Row = 0) then
        Font.Color := clBlack
      else if Col = 1 then
        Font.Color := clBlue
      else begin
        Font.Color := clBlack;
      end;
      
      // セルの色を設定
      if (Col = 0) or (Row = 0) then
        Brush.Color := StringGrid1.FixedColor
      else if (gdFocused in State) or (State = []) then
        Brush.Color := StringGrid1.Color;
      
      // セルに書き込む
      TextRect(Rect, Rect.Left + 2, Rect.Top + 2,
               StringGrid1.Cells[Col, Row]);
    end;
  end;
  
  //クリップボードにコピー
  //String型が拡張されたのをいい事にこんな大胆な事が出来ちゃいます。
  procedure CopyStringGrid(SG:TStringGrid);
  var
    i,j:integer;
    tmpS:string;
  begin
    with SG do begin
      tmpS := '行項目\列項目';
      for j := 0 to RowCount - 1 do begin
        for i := 0 to Rows[j].count -1 do
          if i = 0 then
            tmpS := tmpS + Rows[j].strings[i]
          else if i in Rows[j].count -2 then
            tmpS := tmpS + #9 + Rows[j].strings[i]
          else
            tmpS := tmpS + #9 + Rows[j].strings[i] +#13+#10;
      end;
      Clipboard.SetTextBuf(PChar(tmpS));
    end;
  end;

[前に戻る] [目次] [次に進む]


[Q]Editコントロールに ALT+[key]でフォーカスを移したい

[A]
一般にEditコントロールを使うときにはLabelを一緒に使って何の入力
に使うかをユーザに示します。この時LabelのCaptionに (&S) を追
加、FocusControlにフォーカスを移したいEditコントロールを指定すれ
ばこの場合 ALT+S キーでエディットコントロールにフォーカス移動できます。

[help]
TLabel, FocusControl

[前に戻る] [目次] 


[Q]Cの日付時刻をDelphi用に直すには

Cで良く使われている、1970年1月1日 00:00:00からの経過秒数で表わされた形式を Delphiで使うにはどうすればいいですか?

[A]
例えば上記の場合だと、1970年1月1日をDelphiのTDateTime型に直し、経
過秒数からTDateTime型の整数部(日数)と小数部(時刻)を計算すれば出来
ます。関数にすると以下のようになります。

function CTime2Date(DT:Longint):TDateTime;
begin
  Result := EncodeDate(1970,1,1)      //Cの基準日付を変換
            + (DT + 32400) div 86400  //日付の算出
            + (DT mod 86400) / 86400; //時刻の算出
end;

[補足]
32400はGMTとの時差(9時間)、86400は一日の秒数です。GMTとの時差が異
なる場合32400の部分を変えて下さい。また、基準日付の部分はDelphiの
バージョンによって(Delphiの中での基準日付が)異なるためEncodeDate
でやっておいたほうがいいと思います。

[help]

[前に戻る] [目次] [次に進む]


プログラムを組んでいて、実際に遭遇した事などを基準にして載せています。また、このFAQの中で内容が不正確な点、疑問点などがありましたらげん ysakai@mars.dtinet.or.jpまで。一応私のマシンで実際に走っているコードを載せていますが、このFAQにある内容を実行する時は各自の責任の下で行ってください。