'99-Jan.-03rd Updated.


Delphi-Tips-1
(Delphiの情報)


実はDelphiの神託という名前を付けようと、96年の11月頃から思ってました。しかし、ソフトバンク発行のInsideWindows(お勧めの雑誌です。)で、 1997年1月号よりそのものずばり『Delphiの神託』という連載が始まり、一歩先を越されてしまった。(^_^;)
といっても誰でも思いつくネーミングですよね。それに、神託と名づけるほどの立派なもんじゃありませんし。

Delphiに関するFAQ(よくある質問とその回答)もいろいろな方々のページで紹介されるようになりました。ここでは、あまり取り上げられていないけれども、現在私の使っているルーチンと使いそうなルーチンをまとめてみました。
ちなみに、TIPSには情報という意味があります。1つ1つは小さいけれども、役に立つ情報を紹介できれば・・・、と考えてます。
項目はまだまだ少ないのですが、少しずつ増やしていきたいですね。形式は、FAQ形式でまとめました。

パート2パート3と合せてお使い下さい。

'97-Jan-30th 初出


質問一覧

項   目 初 出 訂正・加筆
●Delphi2で、PORT命令が無くなってしまいました。 '97-01-30 '97-07-29加筆
●テンポラリ(一時)ファイルを作成するのに、ユニークなファイル名を作る方法はありますか。 '97-01-30
●8.3形式のShort File名からLong File名を得る方法が分かりません。 '97-01-30 '97-07-29加筆
●MDIアプリケーションの親フォームの背景にBitmapを貼り付けられません。 '97-01-30 '99-01-01加筆
●RichEditのWordWrapプロパティをオフにして画面が横スクロールする時に、表示されなくなることがあります。 '97-02-02 '97-05-03加筆
●JISコードをSJISコードに変換するにはどうしたらよいでしょうか。 '97-02-02 '98-01-15加筆
●自作のエディタにオートインデント機能をつけるには、どうしたらよいでしょうか。 '97-02-02 '98-10-11追加
●壁紙の変更のしかたはどうしますか。 '97-02-02 '98-10-11加筆
●使っている環境の色の解像度を調べたいのですが。 '97-02-02 '97-02-23加筆
●NetScapeのDDE機能を使ったプログラミング例を教えてください。 '97-02-02 '99-01-03加筆
●スクリーンセーバーを起動する方法は? '97-02-23 '97-07-29修正
●RichEditで総行数を簡単に調べる方法は? '97-02-23
●RichEditで検索・置換処理を行なう方法は? '97-02-23 '97-03-09修正
●上の方法で、Memo用のものはありますか? '97-02-23 '97-03-09修正
●他の自作のアプリケーションに文字列を渡したいのです '97-02-23 '97-03-08修正
●MemoryStreamの使い方がよく分かりません '97-02-23 '97-03-08修正
●レコードの中身をバイト・ワード・ロングという様々な変数に読み出せますか? '97-05-03
●WIN95のファイルコピー時のフライングファイルアニメーションは使えますか? '97-05-03 '98-02-27追加
●WIN95のHTTPサーバー上にDelphiでCGIアプリケーションは作れますか? '97-05-03 '99-01-03加筆
●ログイン時のユーザー名を得るGetUserName APIの使い方を教えてください '97-05-03
●TRichEditを使ってるのに、32kより大きなファイルを読み込ませると、それ以上データを追加できません '97-05-03 '97-07-29加筆
●エクスプローラーからドラッグ&ドロップしたい '97-05-03
●アイコン化した時に、ある処理をしたいのですが '97-05-03
●マウスによる窓の移動をキャプションバー以外をクリックしても可能にしたいのですが。 '97-05-03 '97-05-11加筆
●Num Lockキーを制御したいのですが '97-05-03



from: <news:comp.lang.pascal.delphi.misc>
[Q] Delphi2で、PORT命令が無くなってしまいました。

[A] インラインアセンブラで記述できます。
しかし、これはWindows95だけです。 NTではドライバを書かなければなりません。

{ 任意のポートからバイトデータを読む }
function PortR( PortNo: word): byte;
begin
  asm
    mov dx, PortNo
    cli
    in  al, dx          // 読み込み
    sti
    mov Result, al
  end;
end;

{ 任意のポートへバイトデータを書き出す }
procedure PortW( PortNo: word; value: byte);
begin
  asm
    mov dx, PortNo
    mov al, value
    cli
    out dx, al          // 書き出し
    sti
  end;
end;

{ 任意のポートからバイトデータを読み、書き戻す }
function PortRW( PortNo: word): byte;
begin
  asm
    mov dx, PortNo
    cli
    in  al, dx          // 読み込み
    out dx, al          // 再書き出し(読むだけで破壊されるポートのため)
    sti
    mov Result, al
  end;
end;
補足:('97-07-29)
WINDOWS95環境で、パラレルポート(プリンタポート)をアクセスするアプリケーションを作成しましたが、きちんと動いてます。
ちなみに、パラレルポートのアドレスは、
コントロールレジスタ・・・378h
ステータスレジスタ・・・・379h
データレジスタ・・・・・・37Ah
です。
また、欧米で過去よく使われたMDA(Monochrome Adapter)は
コントロールレジスタ・・・3BCh
ステータスレジスタ・・・・3BDh
データレジスタ・・・・・・3BEh
です。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] テンポラリ(一時)ファイルを作成するのに、ユニークなファイル名を作る方法はありますか。

[A] GetTempFileNameというAPIがあります。これを使えば、ユニークな名前を生成できます。

var
  Junk: array[0..144] of char;
begin
  GetTempFileName('.', 'TMP', 0, @junk[0]);
  ShowMessage(StrPas(@junk[0]));
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] 8.3形式のShort File名からLong File名を得る方法が分かりません。

[A] 下の関数で行なえます。

{ 再帰的にSFN(Short File Name)をLFN(Long File Name)に変換する }
function TMainForm.ExpandShortName(const ShortName: string): string;
var
  FindData: TWin32FindData;
  Handle: THandle;
  Path, Tail: string;
begin
  Path := ExtractFilePath(ShortName);
  Tail := ExtractFileName(ShortName);
  if Tail = '' then  Result := ShortName else // ドライブ名だけの時 (例 D:\)
  begin
    if (Length(Path) > 0) and (Path[Length(Path)] = '\') then Delete(Path, Length(Path), 1);
    Path := ExpandShortName(Path);
    if (Length(Path) > 0) and (Path[Length(Path)] <> '\') then AppendStr(Path, '\');
    Handle := FindFirstFile(PChar(Path + Tail), FindData);
    if Handle = Invalid_Handle_Value then Result := Path + Tail else // ファイルが無い時
    try
      Result := Path + FindData.cFilename;
    finally
      Windows.FindClose(Handle);
    end;
  end;
end;

function TMainform.GetLongFileName(ShortName: string): string;
begin
  if Copy(ShortName, 1, 2) = '\\' then Exit else
    Result := ExpandShortName(ExpandFileName(ShortName));
end;
補足:('97-07-29)
ネットワーク接続で得られるディレクトリ名をきちんと変換できませんので、その場合スキップします。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] MDIアプリケーションの親フォームの背景にBitmapを貼り付けられません。

[A] SDIアプリの様にいかないのは、Windowsの仕様です。下の様な方法で可能になります。

(1) 親フォームにVCLからImageを貼り付けます。
(2) メインのPrivate宣言のところに、以下の変数と手続きを加えます。

private
  { Private 宣言 }
  FClientInstance, FPrevClientProc: TFarProc;
  procedure ClientWndProc(var Message: TMessage);
(3) 実現部分に手続きの処理を記述します。
procedure TMainForm.ClientWndProc(var Message: TMessage);
var
  MyDC: hDC;
  Ro,Co: Word;
begin
  with Message do
  begin
    if Msg = WM_ERASEBKGND then
    begin
      MyDC := TWMEraseBkGnd(Message).DC;
      for Ro := 0 to ClientHeight div Image1.Picture.Height do
      begin
        for Co := 0 to ClientWidth div Image1.Picture.Width do
          BitBlt( MyDC,
                  Co * Image1.Picture.Width,
                  Ro * Image1.Picture.Height,
                  Image1.Picture.Width,
                  Image1.Picture.Height,
                  Image1.Picture.Bitmap.Canvas.Handle,
                  0,
                  0,
                  SRCCOPY);
      end;
      Result := 1;
      Invalidate;  // Delphi4ではこの行が無いと更新されません
    end else
      Result := CallWindowProc(FPrevClientProc,
                               ClientHandle,
                               Msg,
                               wParam,
                               lParam);
  end;
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] RichEditのWordWrapプロパティをオフにして画面が横スクロールする時に、表示されなくなることがあります。

[A] RichEditにはHideScrollBarsというプロパティがありますが、これをオフにすれば直ります。

ヘルプには、

HideScrollBars プロパティはウィンドウをスクロールする必要がないときにスクロールバーを消去するかどうかを制御します。
デフォルトは True です。 このプロパティは ScrollBars プロパティがデフォルトの ssNone に設定されていないときにだけ役立ちます。

とありますが、ScrollBars プロパティと密接な関係があり、 ScrollBarsプロパティをSsBothなどにした時は、
HideScrollBarsプロパティをoffに設定しておくことが必要です。

補足:(97-05-03)
この方法を使用しても縦方向にリサイズすると不具合が出ます。
対処には2つの方法があります。

(1)タイマーを使う
Interval = 50msec、Enabled = False に設定したタイマーを置き、

procedure TForm1.FormResize(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  RichEdit1.Refresh;
  Timer1.Enabled := False;
end;
とします。

(2)EM_SETRECTメッセージを使う

procedure TForm1.SetEditRect;
var
  R: TRect;
begin
  with RichEdit1 do
  begin
    R := Rect(0, 0, ClientWidth, ClientHeight);
    Perform(EM_SETRECT, 0, Longint(@R));
  end;
end;

    という手続きを用意して、Form の Paint と Resize イベント時に呼び出します。
補足(97-05-11)
一見、(2)の方がスマートなようですが、これもおかしくなる時があるようです。
ダサいけれども(1)が確実。

質問一覧に戻る



from: <CASA original>
[Q] JISコードをSJISコードに変換するにはどうしたらよいでしょうか。

[A] 次の関数で可能です。また、逆のSJISからJISコードへの変換も載せます。
ついでに EUCとの変換も載せます。

{ JISコードをSJISコードに変換 }
function JisToSJis(c0,c1: char): string;
var
  v0: byte absolute c0;
  v1: byte absolute c1;
begin
  if (v0>=$21) and (v0<=$7E) then
  begin
    if (v0 and 1) = 1 then
    begin
      if v1 < $60 then Inc(v1,$1F) else Inc(v1,$20);
    end else
      Inc(v1,$7E);
    if v0 < $5F then v0 := (v0 + $E1) shr 1
                else v0 := (v0 + $161) shr 1;
     Result := c0 + c1;
  end else
  Result := '';
end;


{ SJISコードをJISコードに変換 }
function SJisToJis(c0,c1: char): string;
var
  v0: byte absolute c0;
  v1: byte absolute c1;
begin
  Result := '';
  if ((v0>=$81) and (v0<=$9F)) or ((v0>=$E0) and (v0<=$FC)) then
  begin
    if ((v1>=$40) and (v1<=$FC) and (v1<>$7F)) then
    begin
      if v0<=$9F then
      begin
        if v1<$9F then v0:=(v0 shl 1)-$E1 else v0:=(v0 shl 1)-$E0;
      end else
      begin
        if v1<$9F then v0:=(v0 shl 1)-$161 else v0:=(v0 shl 1)-$160;
      end;
      
      if (v1<$7F) then Dec(v1,$1F) else
      begin
        if (v1<$9F) then Dec(v1,$20) else Dec(v1,$7E);
      end;
      Result := C0 + c1;
    end;
  end;
end;


{ EUCをSJISコードに変換 }
function EucToSJis(c0,c1: char): string;
var
  v0: byte absolute c0;
  v1: byte absolute c1;
begin
  if (v0>=$A1) and (v0<=$FE) then
  begin
    if (v0 and 1) = 1 then
    begin
      if v1 < $E0 then Dec(v1,$61) else Dec(v1,$60);
    end else
      Dec(v1,2);
    if v0 < $DF then v0 := (v0 + $61) shr 1
                else v0 := (v0 + $E1) shr 1;
     Result := c0 + c1;
  end else
  Result := '';
end;


{ SJISコードをEUCに変換 }
function SJisToEuc(c0,c1: char): string;
var
  v0: byte absolute c0;
  v1: byte absolute c1;
begin
  Result := '';
  if ((v0>=$81) and (v0<=$9F)) or ((v0>=$E0) and (v0<=$FC)) then
  begin
    if ((v1>=$40) and (v1<=$FC) and (v1<>$7F)) then
    begin
      if v0<=$9F then
      begin
        if v1<$9F then v0:=(v0 shl 1)-$E1 else v0:=(v0 shl 1)-$E0;
      end else
      begin
        if v1<$9F then v0:=(v0 shl 1)-$161 else v0:=(v0 shl 1)-$160;
      end;
      
      if (v1<$7F) then Dec(v1,$1F) else
      begin
        if (v1<$9F) then Dec(v1,$20) else Dec(v1,$7E);
      end;
      v0 := v0 or $80;// EUC は JISのMSBを1にしたものです。
      v1 := v1 or $80;
      Result := C0 + c1;
    end;
  end;
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc + CASA original>
[Q] 自作のエディタにオートインデント機能をつけるには、どうしたらよいでしょうか。

[A] RichEditでの例を示します。OnKeyPressイベントの発生に対して、以下のようにします。 アプリケーション内での処理は、OnKeyPress→画面書き換え→OnKeyDownと進むので、 画面書き換え前に処理してやればいいのです。

(* キー押下 *)
procedure TMainForm.RichEdit1KeyPress(Sender: TObject; var Key: Char);
var
  CurrentLine  : string;
  LineNo,Column: LongInt;
  i : integer;
begin
  if Key = #13 then // CR
  begin
    with RichEdit1 do
    begin
      LineNo := SendMessage(Handle, EM_LINEFROMCHAR, SelStart, 0);  // 現在の行位置を求める
      Column := SelStart - SendMessage(Handle, EM_LINEINDEX, LineNo, 0);  // 現在の桁位置を求める
    end;
    CurrentLine := RichEdit1.Lines[LineNo];  // 現在のキャレットのある行を取り込む
    if (Length(CurrentLine) > 0) and (Column > 0) then
    begin
      { space、TAB の数を数える }
      i := 0;
      while (CurrentLine[i+1] = #$20) or (CurrentLine[i+1] = #$09) do
      begin
        Inc(i);
      end;
      // 例えば5個のSPACEだけの行があり3桁目にキャレットがある場合に対応
      if i > Column then i := Column;

      RichEdit1.SelText := #13 + #10 + Copy(CurrentLine, 1, i);  // 現在のキャレットの位置に挿入
    end else
      RichEdit1.SelText := #13 + #10;
    Key := #0;
  end;
end;

質問一覧に戻る



from: <Delphi-ML and CASA original>
[Q] 壁紙の変更のしかたはどうしますか。

[A] SystemParametersInfo というAPIを使います。敷き詰めるにはレジストリを書き換えます。
また、オープンになっている仕様ではありませんが、 Windows95ではX方向、Y方向にオフセットつけて表示も可能です。 Windows98では効果がありません。

(* BMPファイルを壁紙にする *)
procedure WChange(Sender: TObject; DrawFileName: string);
const
  WPaperKey = '\Control Panel\desktop';
var
  Ss : string;
  Reg: TRegistry;
begin
  if (UpperCase(ExtractFileExt(DrawFileName)) = '.BMP') then
  begin
    Reg := TRegistry.Create;
    try
      Reg.RootKey := HKey_Current_User;
      if not Reg.OpenKey(WPaperKey, False) then Exit else
        begin
          if TileCheckBox.Checked  // 別途チェックボックスを用意しておく
            then Reg.WriteString('TileWallpaper', '1')   // タイリング
            else Reg.WriteString('TileWallpaper', '0');  // 中央に表示
        end;
      // オフセットをつける。
      Reg.WriteString('WallPaperOriginX', IntToStr(SpinEdit1.Value)); // 別途SpinEditを用意しておく
      Reg.WriteString('WallPaperOriginY', IntToStr(SpinEdit2.Value));
    finally
      Reg.Free;
    end;
    Ss := DrawFileName + #0;
    SystemParametersInfo(SPI_SETDESKWALLPAPER,
                         0,
                         @Ss[1],
                         SPIF_UPDATEINIFILE);
  end;
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] 使っている環境の色の解像度を調べたいのですが。

[A] GetDeviceCaps というAPIを使います。 下の例では、BITSPIXELという予約された値を与えて色解像度を調べてますが、 HORZSIZEVERTSIZEという値を与えることにより、 画面の解像度も調べることができます。

(* ピクセルあたりの色のビット数を調べる *)
var
  ColorPerPixel : integer;
  DC            : HDC;
begin
  DC := GetDC(handle);
  ColorPerPixel := GetDeviceCaps(DC, BITSPIXEL);
  ReleaseDC(handle, DC);
end;

補足:
画面の縦、横のサイズを調べるだけならば、
  Screen.Height  Screen.Width
の方が簡単です。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] NetScapeのDDE機能を使ったプログラミング例を教えてください。

[A] すでにNetscapeが動いている時はそのWindowを目的のURLに変更し、 まだ動いていない時は新しくNetscapeを起動するような例を挙げます。
ただし、Delphi Ver3.1のみ動作しません。Ver.3.0、Ver.4.0はOKです。
Netscape DDEのAPIの詳細は、 http://home.netscape.com/newsref/std/ddeapi.htmlで得られます。

(* Netscape 3/4のDDE機能を使う *)
uses
  DDEMan;
  
procedure GotoURL(sURL: string);
var
  DDE : TDDEClientConv;
begin
  DDE := TDDECLientConv.Create(nil);
  with DDE do
  begin
    ServiceApplication := 'c:\ns32\program\netscape.exe';

    SetLink('Netscape', 'WWW_OpenURL');
    RequestData(sURL + ',,0xFFFFFFFF,0x3,,,');

    CloseLink;
  end;
  DDE.Free;
end;

また、Internet Explorer 4でも同様に以下のようにして可能となります。
(* Internet Explorer4のDDE機能を使う *)
uses
  DDEMan;
  
procedure GotoURL(sURL: string);
var
  DDE : TDDEClientConv;
begin
  DDE := TDDECLientConv.Create(nil);
  with DDE do
  begin
    ServiceApplication := 'c:\Program Files\Internet Explorer\Iexplore.exe';

    SetLink('Iexplore', 'WWW_OpenURL');
    RequestData(sURL + ',,0xFFFFFFFF,0x3,,,');

    CloseLink;
  end;
  DDE.Free;
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] スクリーンセーバーを起動する方法は?

[A] SenMessage API ファンクションを使います。

(* デフォルトの Screen Saver を起動する *)
  
begin
  SendMessage(GetDesktopWindow(), WM_SYSCOMMAND, SC_SCREENSAVE, 0);
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] RichEditで総行数を簡単に調べる方法は?

[A] SendMessage API ファンクションで、EM_GETLINECOUNT メッセージを使います。

(* 総行数を調べる *)
var  
  TotalLine: integer;
begin
  TotalLine := SendMessage(RichEdit1.Handle, EM_GETLINECOUNT, 0, 0);
end;

質問一覧に戻る



from: <news:fj.lang.pascal>
[Q] RichEditで検索・置換処理を行なう方法は?

[A] 以下の方法は、news:fj.lang.pascal に於いて、 比企さんが発表された方法です。
この方法には、Delphi 1も必要です。 なお、Delphi1を所有していない場合は、『Win32 APIのメッセージである EM_FINDTEXTEM_FINDTEXTEXを使えばできる。』という情報を得てますが、 私(CASA)は試していないので分かりません。
この情報の転載に快諾していただいた比企さんに感謝します。

1.Delphi1.0 の、Demos\Textdemo にある Search.pas を改造します。
  なぜならば、
    1.これは Memo 用である
    2.スクロールしてくれない
  からです。

2.Uses 節を以下のように変更します。
uses
  Windows, Messages, SysUtils, StdCtrls, Dialogs, ComCtrls;

3.function SearchMemo(Memo: TCustomEdit; const SearchString: String; Options: TFindOptions): Boolean;
  以下を次のように変更します。
 
function SearchMemo(Memo: TCustomEdit; const SearchString: String; Options: TFindOptions): Boolean;
var
  Buffer, P: PChar;
  Size     : Longint;  // Wordでしたが、64kを越えるデータに対応のため変更(CASA)
  FindLine : integer;
begin
  Result := False;
  if (Length(SearchString) = 0) then exit;
  Size := Memo.GetTextLen;
  if (Size = 0) then exit;
  Buffer := StrAlloc(Size + 1);
  try
    Memo.GetTextBuf(Buffer, Size + 1);
    P := SearchBuf(Buffer, Size, Memo.SelStart, Memo.SelLength,
                   SearchString, Options);
    if P <> nil then
    begin
      with Memo do
      begin
        SelStart := P - Buffer;
        SelLength := Length(SearchString);
        FindLine := Perform(EM_LINEFROMCHAR, Selstart, 0);
        if frDown in Options
          then Perform(WM_VSCROLL, SB_LINEDOWN, FindLine)
          else Perform(WM_VSCROLL, SB_LINEUP,   FindLine);
      end;
      Result := True;
    end;
  finally
    StrDispose(Buffer);
  end;
end;
 
4.TCustomEdit を TRichEdit に置換します。

5.次に、Search.pasを呼び出す部分を手直しします。
  Delphi1.0の \Demos\Textdemo の Main.pas の
  Find1click、Find、Replace1Click、
  FindNextClick、Replace イベントの中のコードを丸写しします。

6.Memo1 のところを、実際に使用している RichEdit の Name に置換します。

7.RichEdit の HideSelection プロパティをFalseにします。


さらに、私が見つけた項目です。
1.Options設定を[検索する方向]を[上へ向って(U)]にし、
  [単語単位で検索(W)]を選択すると、一番上の選択ワードまで検索したあと、
  もう検索ワードが無いにもかかわらず、ハングアップしてしまいます。

  簡単な対処方法は、
    function SearchBuf(Buf: PChar; BufLen: Integer;
                         SelStart, SelLength: Integer;
                         SearchString: String;
                         Options: TFindOptions): PChar;

  の関数の中で、
  while SearchCount >= 0 do
  の行を    
  while SearchCount > 0 do
  に変更します。

2.以下は漢字などの2バイトコードに対応した場合にのみ必要な処理です。
  2バイトコードを検索する場合、[大文字と小文字を区別する(C)]をチェックしていないと、
  検索できないことがあります。
  これは、このルーチンが Ansiキャラクタに特化したルーチンだからです。

  2バイトコードに対応するには、
function SearchBuf(Buf: PChar; BufLen: Integer;
                   SelStart, SelLength: Integer;
                   SearchString: String;
                   Options: TFindOptions): PChar;
var
  SearchCount, I: Integer;
  C             : Char;
  Direction     : Shortint;
  CharMap       : array [Char] of Char;
  AnsiFlg       : Boolean;  // この行を追加する

のようにフラグを設け、以下の追加・修正を行ないます。

  { Using a Char map array is faster than calling AnsiUpper on every character }
  for C := Low(CharMap) to High(CharMap) do
    CharMap[C] := C;

  { 漢字が検索ワードかどうかチェック }
  AnsiFlg := True;                                            // ^
  for i := 1 to Length(SearchString) do                     // | この部分を追加
  begin                                                     // |
    if SearchString[i] >= #$80 then AnsiFlg := false;      // V
  end;

  { 大文字小文字をチェックしないならば }
  if not (frMatchCase in Options) and AnsiFlg then  // 先のフラグも条件に加える
  begin
  //  AnsiUpperBuff(PChar(@CharMap), sizeof(CharMap));
  //  AnsiUpperBuff(@SearchString[1], Length(SearchString));
    CharUpperBuff(PChar(@CharMap), sizeof(CharMap));          // ついでに32bits APIに変更
    CharUpperBuff(@SearchString[1], Length(SearchString));    // ついでに32bits APIに変更
  end;

3.また、[単語単位で検索(W)]を選択すると、漢字などの2バイトコードでうまくいきませんから、
  漢字対応のアプリケーションの場合は、このOptionsの項目を使うのをあきらめます。
  Search.pas を呼び出す部分で、FindDialogを実行する直前に、

  FindDialog1.Options := [frHideWholeWord];  // この行を追加する
  FindDialog1.Execute;

4.次にこれがまた厄介なのですが、'b'(#$62)などの1文字だけを検索すると、
  シフトJISの'臣'(#$90+#$62)もHitしてしまいます。
  英語文化で育った人間が組んだプログラムですから仕方が無いけど、ちょっと愚痴りたくもなります。
  で、小修正でしかもエレガントな解法は見つけてないのですが、
    取りあえず、ラベルを使った汚い方法は見つけてあります。
  先の、SearchMemoに追加します。

function SearchMemo(Memo: TRichEdit;
                    const SearchString: String;
                    Options: TFindOptions): Boolean;
label  // ラベルを導入
  ReSearch;
var
  Buffer, P: PChar;
  Size     : Longint;
  FindLine : integer;

begin
  Result := False;
  if (Length(SearchString) = 0) then Exit;
  Size := Memo.GetTextLen;
  if (Size = 0) then Exit;
  Buffer := StrAlloc(Size + 1);
  try
    Memo.GetTextBuf(Buffer, Size + 1);
ReSearch:  // ここにラベルを設定
    P := SearchBuf(Buffer, Size, Memo.SelStart, Memo.SelLength,
                   SearchString, Options);
    if P <> nil then
    begin
      with Memo do
      begin
        SelStart := P - Buffer;
        SelLength := Length(SearchString);
        FindLine := Perform(EM_LINEFROMCHAR, Selstart, 0);

//        MainForm.Caption := Buffer[Memo.SelStart] + Buffer[Memo.SelStart + 1]; デバグ用です。
        { 以下を追加。1文字を検索し、バッファ先頭がAnsiコード以外なら・・・            }
        { ふつうならば、バッファの先頭には検索ワードの先頭と等しいコードとなるはずが、 }
        { なぜか、この時だけは、SJISの1バイト目が入ってます。                         }
        if (Length(SearchString) = 1) and (Buffer[Memo.SelStart] > #$80) then
        begin
          if frDown in Options then SelStart := SelStart + 2
                               else SelStart := SelStart - 1;
          goto Research;
        end
        else
        if frDown in Options
          then Perform(WM_VSCROLL, SB_LINEDOWN, FindLine)
          else Perform(WM_VSCROLL, SB_LINEUP,   FindLine);
      end;
      Result := True;
    end;
  finally
    StrDispose(Buffer);
  end;
end;

長くなりましたが、以上で高機能な検索ルーチンがTRichEditで使えるようになります。

質問一覧に戻る



from: <Mr.HIKI>
[Q] 上の方法で、Memo用のものはありますか?

[A] 以下の方法は、 比企さんから直接頂いたものです。
この方法では、先に示したような2バイトコード対策がなされていませんので、 2バイトコード対応が必要な時には、先の情報を参考に改造してください。 なお、Delphi1はまだ販売中ですから、全ソースを掲載しておりませんので、ご了承ください。
この情報の転載に快諾していただいた比企さんに感謝します。

1.Uses 節を以下のように変更します。
uses
  Windows, Messages, SysUtils, StdCtrls, Dialogs;

2.TCustomEdit を TMemo に置換します。

3.Interface部に、MemoLinesShowingという新しい関数を宣言します。
function MemoLinesShowing(Memo: TMemo): integer;

4.implementation部に実際の動作を記述します。
{ Memoの見えている行数を調べる }
function MemoLinesShowing(Memo: TMemo: integer;
var
  OldFont: HFont;        { the old font          }
  DC     : THandle;      { Device context handle }
  Tm     : TTextMetric;  { text metric structure }
  TheRect: TRect;
begin
  DC := GetDC(Memo.Handle);  { Get the memo's device context }
  try
    { Select the memo's font }
    OldFnt := SelectObject(DC, Memo.Font.Handle);
    try
      GetTextMetrics(DC, Tm); { Get the text metric info. }
      Memo.Perform(EM_GETRECT, 0, longint(@TheRect));
      Result := (TheRect.Bottom - TheRect.Top) div (Tm.tmHeight + Tm.tmExternalLeading);
    finally
      SelectObject(DC, OldFont); { Select the old font }
    end;
  finally
    ReleaseDC(Memo.Handle, DC);  { Release the device context }
  end;
end;


5.function SearchMemo(Memo: TMemo; const SearchString: String; Options: TFindOptions): Boolean;
  以下を次のように変更します。
 
function SearchMemo(Memo: TMemo; const SearchString: String; Options: TFindOptions): Boolean;
var
  Buffer, P        : PChar;
  Size             : Word;
  SeeLines         : integer;  { 見えている行数 }
  TopLine,BotomLine: integer;
  FindLine         : integer;
  Scrolling        : integer;  { スクロール行数 }
begin
  Result := False;
  if (Length(SearchString) = 0) then exit;
  Size := Memo.GetTextLen;
  if (Size = 0) then exit;
  Buffer := StrAlloc(Size + 1);
  try
    Memo.GetTextBuf(Buffer, Size + 1);
    P := SearchBuf(Buffer, Size, Memo.SelStart, Memo.SelLength,
                   SearchString, Options);
    if P <> nil then
    begin
      SeeLines := MemoLinesShowing(Memo);
      with Memo do
      begin
        TopLine := Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
        SelStart := P - Buffer;
        SelLength := Length(SearchString);
        FindLine := Perform(EM_LINEFROMCHAR, Selstart, 0);
        BottomLine := TopLine + SeeLines;
        if (FindLine >= BottomLine) and (frDown in Options) then
        begin
          Scrolling := FindLine - TopLine - (SeeLines div 2);
          Perform(EM_LINESCROLL, 0, Scrolling);
        end if (FindLine <= TopLine) and not (frDown in Options) then
        begin
          Scrolling := FindLine - TopLine - (SeeLines div 2);
          Perform(EM_LINESCROLL, 0, Scrolling);
        end;
      end;
      Result := True;
    end;
  finally
    StrDispose(Buffer);
  end;
end;

以上でスクロールを行なう高機能な検索ルーチンがTMemoで使えるようになります。

質問一覧に戻る



from: <CASA original>
[Q] 他の自作のアプリケーションに文字列を渡したいのです

[A] Windows95にはGlobalAtomという、複数のプログラムから共有できるメモリエリアがあります。 詳細は、APIのオンラインマニュアルを見てください。
実際の例を述べます。

1.送り出し側(書き込み側)の記述例
const
  MessageIDStr = 'Original Program ID by Quebnoi Casa';               // 特異なメッセージを定める
var
  s        : string;
  MessageID: longint;
  Atom     : TAtom;
begin
  s := 'あぅ、これは送るテスト文字列さっ。';
  MessageID := RegisterWindowMessage( MessageIDStr);
  Atom := GlobalAddAtom(@s[1]);                                       // GlobalAtomに書き込む
  if Atom <> 0 then PostMessage(hwnd_Broadcast, MessageID, 1, Atom)  // wParam=1にして、書いた事を通知
                else ShowMessage('ATOM生成に失敗!');
end;

2.受け取り側(読み出し側)の記述例
2−1.Private宣言部にメッセージの受け皿を追加する。
  private
    { Private 宣言 }
    :
    procedure AppMessage(var Msg: TMsg; var Handled: boolean);
    :

2−2.実装部のすぐ上に送り出し側と同じメッセージを宣言する。
  :
const
  MessageIDStr = 'Original Program ID by Quebnoi Casa';  // 送り出し側と同じメッセージ
var
  MessageID: longint;
implementation
  :

2−3.フォーム作成時の処理に、メッセージ受け取りのID取得と、メッセージを横取りする旨を、追加する。
procedure TMainForm.FormCreate(Sender: TObject);
begin
  :
  MessageID := RegisterWindowMessage( MessageIDStr);
  Application.OnMessage := AppMessage;
  :
end;

2−4.実際の処理を記述します。
procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: boolean);
var
  Exist : integer;
  Buffer: array[0..255] of Char;
begin
  with Msg do
  begin
    if Message = MessageID then
    begin
      if wParam = 1 then                                 // ATOM あり
      begin
        Exist := GlobalGetAtomName(lParam, Buffer, 255);  // lParamはAtomを指す
        if Exist > 0 then
        begin
          GlobalDeleteAtom(lParam);                       // Atomを削除
          ShowMessage(Buffer);
          :
        end;
      end;
      Handled := True;
    end;
  end;
end;

wParamをいろいろ変え、受信するアプリケーション側で動作を変えることもできます。

質問一覧に戻る



from: <CASA original>
[Q] MemoryStreamの使い方がよく分かりません

[A] TMemoryStreamは強力なクラスです。
DOSの時代は、64kbyteを越えてメモリの読み書きを行なうのに、 いちいち読み書きしたサイズや読み書きするポインタを管理しなければなりませんでした。
TMemoryStreamはこの作業をたいへん軽減してくれます。 ここでは、ファイルのテキストデータを大文字に加工してMemoryStreamに書き込み、 最後に、いっぺんにMemoに転送する例をあげます。

{ メモリオーバーフロー対策は行なっていません }
var
  fi          : TextFile;
  MemoryStream: TMemoryStream;
  c           : Char;
begin
  AssignFile(fi, 'c:\data\hogehoge.txt');
  try
    Reset(fi);
    MemoryStream :=TMemoryStream.Create;       // MemoryStreamの生成
    while not Eof(fi) do
    begin
      Read(fi,c);
      c := UpCase(c);
      MemoryStream.Write(c, 1);                // 1バイト書き込み
    end;
    MemoryStream.Seek(0, 0);                   // アクセス位置をストリームの先頭に
    Memo1.Lines.LoadFromStream(MemoryStream);  // Memoに読み込む
  finally
    MemoryStream.Free;                         // MemoryStreamの破棄
    CloseFile(fi);
  end;
end;

TmemoryStreamは、TStream→TCustomMemoryStreamから派生したクラスで、Seekメソッドの説明は
TCustomMemoryStreamまたはTStreamにあります。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] レコードの中身をバイト・ワード・ロングという様々な変数に読み出せますか?

[A] 型変換を行なうのが正直な方法ですが、以下の方法でも行なえます。

type
  TLongintPart = record
    case Byte of
      0: ( Long: Longint);
      1: ( Word1, Word2: Word);
      2: ( Byte1, Byte2, Byte3, Byte4: Byte);
    end;

var
  MyPart: TLongintPart;

begin
  MyPart.Long := $01234567;
  Label1.Caption := IntToStr(MyPart.Byte2);
  Label2.Caption := IntToStr(MyPart.Word2);
end;

注意しなければならないのは、メモリ上の変数の並びは上位と下位がひっくり返っており、
  Byte1 = $67
  Byte2 = $45
  Byte3 = $23
  Byte4 = $01
  Word1 = $4567
  Word2 = $0123
  Long  = $01234567
となっていることです。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] WIN95のファイルコピー時のフライングファイルアニメーションは使えますか?

[A] VCLのAnimateコンポネントが使えます。 APIを使うならばファイルの移動・複写・削除の時にSHFileOperationというAPIを使います。

(* SHFileOperationを使う例 *)
uses ShellAPI;

var
  SFO: TSHFileOpStruct;
  Res: integer;

begin
  with SFO do
  begin
    Wnd := Handle;
    wFunc := FO_COPY;  // コピー
    pFrom := PChar('d:\data'#0#0);
    pTo := PChar('c:\temp'#0#0);
    fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMMKDIR;
    fAnyOperationsAborted := False;
    hNameMapping := nil;
  end;

  try
    Res := SHFileOperation(SFO);
  finally
  end;
end;

複数のファイルやフォルダを選ぶ場合は、
    pFrom := PChar('d:\data'#0'd:\tool'#0#0);
のように、#0(null)キャラクタを挿みます。

また、デスクトップのごみ箱に移動する場合は、
    wFunc := FO_DELETE;
を使います。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] WIN95のHTTPサーバー上にDelphiでCGIアプリケーションは作れますか?

[A] できます。

C/S版、Delphi3.1 Professional版、Delphi4 +WebBroker for Delphi 4では、 CGI作成用のウィザードとコンポーネントが用意されていますので、 そちらを使う方がいいです。
これらを使わない場合でも可能です。 その場合標準入出力(STDIN/STDOUT)を使わなければなりませんので、 コンソールアプリケーションにします。

フォームは不要ですから、「メニュー→プロジェクト→プロジェクトから削除」を選び、 フォームを削除し、dprに以下の記述を行ないます。

program Project1;
{$AppType Console}

var
  aLine: string;

begin
  Readln(aLine);  // 標準入力から読み込む
  Writeln('content-type: text/html');
  Writeln('');    // 空白行が必要です
  Writeln('<HTML>');
  Writeln('<HEAD><TITLE>Delphi-TIPS</TITLE></HEAD>');
  Writeln('<BODY TEXT="#104020" BGCOLOR="#EEEEEE" LINK="#0060F0" VLINK="#A02060" onLoad="return true;">');
  :
  Writeln('</HTML>');
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] ログイン時のユーザー名を得るGetUserName APIの使い方を教えてください

[A] サンプルを見てください。

var
  UserName: array[0..127] of char;
  UserSize: integer;

begin
  UserSize := 128;
  GetUserName(@UserName, UserSize);
  ShowMessage('User "' + StrPas(UserName) + '"');
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] TRichEditを使ってるのに、32kより大きなファイルを読み込ませると、それ以上データを追加できません

[A] TRichEditのバグ?。
MaxLengthプロパティが0 ならばコントロールに入力できる文字数は制限されません。
とヘルプにありますが、このプロパティをきちんとバイト単位で設定するか、以下のコードを追加してください。

RichEdit1.Perform(EM_LIMITTEXT, 0, 20000000);  // 20MBまで可

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] エクスプローラーからドラッグ&ドロップしたい

[A] Delphiのアプリケーション内ではDragModeプロパティでやり取りできますが、
他のアプリケーションからDrag and Dropするには、メッセージを処理します。
以下のコードはエクスプローラーからMemoへドラッグ&ドロップを可能にします。

uses ShellAPI;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
  DragAcceptFiles( Form1.Handle, True);
end;

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
const
  BufferLength = 255;
var
  DroppedFileName: string;

begin
  if Msg.Message = WM_DROPFILES then
  begin
    SetLength( DroppedFileName, BufferLength);
    DragQueryFile(Msg.wParam, 0, PChar(DroppedFileName), BufferLength);
    Memo1.Lines.LoadFromFile( DroppedFileName);
    DragFinish(Msg.wParam);
    Handled := True;
  end;
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] アイコン化した時に、ある処理をしたいのですが

[A] OnMinimizeイベントを使います。
しかし、Formで、

procedure TForm1.OnMinimize(Sender: TObject);
としても処理できません。Applicationで検出します。

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    procedure AppMin(Sender: TObject);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMinimize := AppMin;
end;

procedure TForm1.AppMin(Sender: TObject);
begin
  ShowMessage('最小化検出成功');
end;

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc> <news:fj.os.ms-windows.programming>
[Q] マウスによる窓の移動をキャプションバー以外をクリックしても可能にしたいのですが。

[A] 次のマジックナンバーを使うと、フォームのどこをクリックしても窓のドラッグできます。

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  SC_DragMove = $F012;
begin
  ReleaseCapture;
  Perform(WM_SYSCOMMAND, SC_DragMove, 0);
end;
補足:(95-05-11)
fj.os.ms-windows.programmingで、
SATO Ryojiさんから非公開情報を使った場合、将来OSのバージョンが更新された時、互換性の問題があることを指摘してもらいました。
(公開されているとはいっても、いろいろ変更される場合も多々ありますが・・・。)
また、公開されている情報だけも可能な方法をMSC + SDC の例で教えていただきましたので、以下のように修正します。
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
  begin
    ReleaseCapture;
    DefWindowProc( handle, WM_NCLBUTTONDOWN, HTCAPTION, X+Y*$10000);
  end;
end;
lParam の X+Y*$10000 は、0 でも構わないようです。

質問一覧に戻る



from: <news:comp.lang.pascal.delphi.misc>
[Q] Num Lockキーを制御したいのですが

[A] KeyboardStateクラスがあります。

procedure TForm1.Button1Click(Sender: TObject);
var
  KeyState: TKeyboardState;
begin
  GetKeyboardState(KeyState);
  if (KeyState[VK_NUMLOCK] = 0)
    then KeyState[VK_NUMLOCK] := 1 else KeyState[VK_NUMLOCK] := 0;
  SetKeyboardState(KeyState);
end;
VK_NUMLOCKの代わりにVK_CAPITALを使うとCaps Lockキーを制御できます。

質問一覧に戻る



Delphi神殿に戻る

Delphi-TIPS(2)に進む
Delphi-TIPS(3)に進む

ご意見、間違いのご指摘などは、 mailto:sakai@nasu-net.or.jp までお寄せください。