ホーム >プログラム >Delphi 6 ローテクTips

Unicode対応のTSaveDialog

Unicode対応のTOpenDialogとSaveDialog。

ソースコード

gfnbOpenFileDialogのコンポーネント化です。

interface
uses
  Classes,
  Controls,
  Dialogs,
  Messages,
  Windows,
  myWStrings;  //Unicode対応のTStringsのようなクラス

{ファイル選択}
type
  { TMyOpenFileDialog }
  TMyOpenFileDialog = class(TOpenDialog)
  private
    F_hHandle:    HWND;
    F_bPosCenter: Boolean;
    F_sDefaultExt, F_sFileName, F_sInitialDir, F_sTitle: WideString;
    F_slFiles:    TMyWStrings;
    F_Owner:      TWinControl;

    function  F_GetCount: Integer;
  protected
    procedure DoShow; override;
    procedure DoClose; override;
    function  GetStaticRect: TRect; override;
    function  F_GetFiles(pBuff: PWideChar; iLen: DWORD; slFiles: TMyWStrings): WideString;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    function Execute: Boolean; override;
    function ShortFileName: WideString;
    function ShortFileNames(iIndex: Integer): WideString;

    property Count:  Integer     read F_GetCount;
    property Files:  TMyWStrings read F_slFiles;
    property Handle: HWND        read F_hHandle;
  published
    property DefaultExt: WideString  read F_sDefaultExt write F_sDefaultExt;
    property FileName:   WideString  read F_sFileName   write F_sFileName;
    property InitialDir: WideString  read F_sInitialDir write F_sInitialDir;
    property PosCenter:  Boolean     read F_bPosCenter  write F_bPosCenter default True;
    property Owner:      TWinControl read F_Owner       write F_Owner;
    property Title:      WideString  read F_sTitle      write F_sTitle;
  end;

TOpenDialogから派生させます。
FilterはWideString対応にしていません。
WideString対応にしようとすると(簡単に設定するためには)プロパティエディタも作る必要があるかと思うのでやめました。

GetStaticRectはカスタムダイアログを作るために必要です。
ノーマルのままで使用するなら実装する必要はありません。

implementation
uses
  CommDlg,
  Dlgs,
  Forms,
  MultiMon,
  SysUtils,
  myCommon;  //汎用ルーチン集


//------------------------------------------------------------------------------
//汎用だけれどもmyCommon.pasに入れるほどではないルーチン群

function gfnsShortFileNameGet(sFile: WideString): WideString;
//Unicode対応の短いファイル名を返す。
var
  lp_Buff: PWideChar;
begin
  lp_Buff := AllocMem((MAX_PATH +1) *2);
  try
    if (GetShortPathNameW(PWideChar(sFile), lp_Buff, (MAX_PATH + 1) * 2) > 0) then begin
      Result := WideString(lp_Buff);
    end else begin
      Result := sFile;
    end;
  finally
    FreeMem(lp_Buff);
  end;
end;

function gfnrcMonitorWorkAreaRectGet(ptPos: TPoint): TRect;
//ptPos上のモニターのワークエリアを返す。
var
  lr_Info:   TMonitorInfo;
  lh_Handle: HMONITOR;
  lrc_Rect:  TRect;
begin
  lrc_Rect  := Rect(ptPos.X, ptPos.Y, ptPos.X, ptPos.Y);
  lh_Handle := MultiMon.MonitorFromRect(@lrc_Rect, MONITOR_DEFAULTTONEAREST);

  lr_Info.cbSize := SizeOf(lr_Info);
  GetMonitorInfo(lh_Handle, @lr_Info);
  Result := lr_Info.rcWork;
end;
myCommon.pasは比較的使用頻度の高い汎用ルーチンを集めたunitです。
//------------------------------------------------------------------------------
//ファイル選択ダイアログ
{ TMyOpenFileDialog }
var
  My_Dialog: TMyOpenFileDialog = nil; //メッセージのフックのために必要

constructor TMyOpenFileDialog.Create(AOwner: TComponent);
begin
  inherited;

  F_slFiles     := TMyWStrings.Create;
  F_sFileName   := '';
  F_sInitialDir := '';
  F_sTitle      := '';
  F_bPosCenter  := True;
  if (AOwner is TWinControl) then begin
    F_Owner := (AOwner as TWinControl);
  end else begin
    F_Owner := nil;
  end;
end;

destructor TMyOpenFileDialog.Destroy;
begin
  F_slFiles.Free;
  inherited;
end;

function TMyOpenFileDialog.F_GetCount: Integer;
begin
  Result := F_slFiles.Count;
end;

function TMyOpenFileDialog.GetStaticRect: TRect;
//カスタムダイアログのために必要。
//カスタムダイアログを使用しなければ削除OK。

begin
  if (Handle <> 0) then begin
    //http://www.vbstation.net/spec/S3_1.htm
    GetWindowRect(GetDlgItem(Handle, stc32), Result);
    Result := Rect(0, 0, gfniRectWidth(Result), gfniRectHeight(Result));
  end else begin
    Result := Rect(0,0,0,0);
  end;
end;

procedure TMyOpenFileDialog.DoShow;
var
  lh_Handle: HWND;
  lrc_Rect:  TRect;
  lpt_Pos:   TPoint;
begin
  if Assigned(@OnShow) then begin
    OnShow(Self);
  end;

  if (F_bPosCenter) then begin
    //ダイアログをモニターの真ん中に
    lh_Handle := GetParent(Handle);
    GetWindowRect(lh_Handle, lrc_Rect);
    GetCursorPos(lpt_Pos);
    lrc_Rect := gfnrcRectCenter(gfnrcMonitorWorkAreaRectGet(lpt_Pos), lrc_Rect);
    SetWindowPos(lh_Handle, HWND_TOP, lrc_Rect.Left, lrc_Rect.Top, 0, 0, SWP_NOSIZE);
  end;
//  inherited DoShow; いらない。
end;

procedure TMyOpenFileDialog.DoClose;
begin
  My_Dialog := nil;
  inherited DoClose;
end;

function TMyOpenFileDialog.F_GetFiles(pBuff: PWideChar; iLen: DWORD; slFiles: TMyWStrings): WideString;
const
  lcs_SEP = #0;
var
  ls_Path, ls_Item: WideString;
  i, li_Start: DWORD;
begin
  slFiles.Clear;
  if not(ofAllowMultiSelect in Options) then begin
    //一つだけ選択
    slFiles.Add(WideString(pBuff));
  end else begin
    //複数ファイル選択
    //分解
    ls_Path  := '';
    li_Start := 0;
    for i := 0 to iLen -1 do begin
      if (pBuff[i] = lcs_SEP) then begin
        ls_Item := Copy(WideString(@pBuff[li_Start]), 1, i - li_Start);
        if (ls_Path = '') then begin
          ls_Path := ls_Item;
        end else begin
          slFiles.Add(ls_Path + '\' + ls_Item);
        end;
        if (i < iLen) and (pBuff[i +1] = lcs_SEP) then begin
          //#0が二つ続くので終了
          Break;
        end;
        li_Start := i +1; //区切りの次のインデックス
      end;
    end;
    if (slFiles.Count = 0) then begin
      slFiles.Add(ls_Path);
    end;
  end;

  if (slFiles.Count > 0) then begin
    Result := slFiles[0];
  end else begin
    Result := '';
  end;
end;

{
TOpenFilename(OpenFilename構造体)については↓に詳しい
http://mrxray.on.coocan.jp/Halbow/Chap18.html
http://yokohama.cool.ne.jp/chokuto/urawaza/struct/OPENFILENAME.html
コールバック関数について
http://www.vbstation.net/spec/S3_1.htm
}

//コールバック関数
function l_fniCallbackFileDialog(hHandle: HWND; iMsg: UINT; iWParam: WPARAM; iLParam: LPARAM): UINT stdcall;
var
  lp_Info:  POpenFilenameW;
  Msg: TMessage;
begin
  Result := 0;
  if (iMsg = WM_INITDIALOG) then begin
    lp_Info := POpenFilenameW(iLParam);  //WM_INITDIALOGの時にはiLParamにはTOpenFilenameW構造体のアドレスが入っている
    My_Dialog := TMyOpenFileDialog(lp_Info^.lCustData);
    My_Dialog.F_hHandle := hHandle;
  end else if (My_Dialog <> nil) then begin
    //unit内グローバル変数を使用。
    Msg.Msg    := iMsg;
    Msg.WParam := iWParam;
    Msg.LParam := iLParam;
    My_Dialog.WndProc(Msg); //WndProcへメッセージを横流し。
  end;
end;

function TMyOpenFileDialog.Execute: Boolean;
var
  lr_Info: TOpenFilenameW;
begin
  FillChar(lr_Info, SizeOf(lr_Info), 0);  //0で初期化
  with lr_Info do begin
    lStructSize := SizeOf(lr_Info);
    //オーナーウィンドウ
    if (F_Owner = nil) then begin
      hwndOwner := Application.Handle;
    end else begin
      hwndOwner := F_Owner.Handle;
    end;
    hInstance  := SysInit.HInstance; //カスタムダイアログで必要。
    //タイトル
    lpstrTitle := PWideChar(F_sTitle);
    //フィルタ
    if (Filter <> '') then begin
      lpstrFilter := PWideChar(WideString(StringReplace(Filter, '|', #0, [rfReplaceAll])));
    end;
    nFilterIndex := FilterIndex;
    //デフォルト拡張子
    if (F_sDefaultExt <> '') then begin
      lpstrDefExt  := PWideChar(F_sDefaultExt);
    end else begin
      lpstrDefExt := nil;
    end;
    //デフォルトフォルダ
    if (F_sInitialDir <> '') and (gfnbFolderExists(F_sInitialDir)) then begin
      lpstrInitialDir := PWideChar(F_sInitialDir);
    end else begin
      lpstrInitialDir := nil;
    end;
    //フラグ
    if (ofReadOnly            in Options) then Flags := Flags or OFN_READONLY;
    if (ofOverwritePrompt     in Options) then Flags := Flags or OFN_OVERWRITEPROMPT;
    if (ofHideReadOnly        in Options) then Flags := Flags or OFN_HIDEREADONLY;
    if (ofNoChangeDir         in Options) then Flags := Flags or OFN_NOCHANGEDIR;
    if (ofShowHelp            in Options) then Flags := Flags or OFN_SHOWHELP;
    if (ofNoValidate          in Options) then Flags := Flags or OFN_NOVALIDATE;
    if (ofAllowMultiSelect    in Options) then Flags := Flags or OFN_ALLOWMULTISELECT;
    if (ofExtensionDifferent  in Options) then Flags := Flags or OFN_EXTENSIONDIFFERENT;
    if (ofPathMustExist       in Options) then Flags := Flags or OFN_PATHMUSTEXIST;
    if (ofFileMustExist       in Options) then Flags := Flags or OFN_FILEMUSTEXIST;
    if (ofCreatePrompt        in Options) then Flags := Flags or OFN_CREATEPROMPT;
    if (ofShareAware          in Options) then Flags := Flags or OFN_SHAREAWARE;
    if (ofNoReadOnlyReturn    in Options) then Flags := Flags or OFN_NOREADONLYRETURN;
    if (ofNoTestFileCreate    in Options) then Flags := Flags or OFN_NOTESTFILECREATE;
//古いタイプは無効
//    if (ofNoNetworkButton     in Options) then Flags := Flags or OFN_NONETWORKBUTTON;
//    if (ofNoLongNames         in Options) then Flags := Flags or OFN_NOLONGNAMES;
//    if (ofOldStyleDialog      in Options) then Flags := Flags or OFN_EXPLORER;
    if (ofNoDereferenceLinks  in Options) then Flags := Flags or OFN_NODEREFERENCELINKS;
    if (ofEnableIncludeNotify in Options) then Flags := Flags or OFN_ENABLEINCLUDENOTIFY;
    if (ofEnableSizing        in Options) then Flags := Flags or OFN_ENABLESIZING;
    if (ofDontAddToRecent     in Options) then Flags := Flags or OFN_DONTADDTORECENT;
    if (ofForceShowHidden     in Options) then Flags := Flags or OFN_FORCESHOWHIDDEN;
    //エクスプローラ風は必須(古いタイプのとでは区切りが違うので)
    Flags := Flags or OFN_EXPLORER;

    //コールバック
    //メッセージフックのためコールバック関数を使う
    Flags     := Flags or OFN_ENABLEHOOK;
    lpfnHook  := l_fniCallbackFileDialog;
    lCustData := LPARAM(Self); //自身のアドレスを代入

    if (ofExNoPlacesBar in OptionsEx) then FlagsEx := FlagsEx or OFN_EX_NOPLACESBAR;

    //カスタムダイアログ。
    if (Template <> nil) then begin
      Flags := Flags or OFN_ENABLETEMPLATE;
      lpTemplateName := PWideChar(WideString(AnsiString(Template)));
    end;

    //ファイル名
//    nMaxFile := 1024 * 64 -1;  //一応この数値が限界のよう。これ以上だと一つだけ選択のときにエラーになることがあった
    nMaxFile  := 1024 * 32;
    lpstrFile := AllocMem((nMaxFile +2) * 2);
    try
      //存在しないファイルを指定するとエラーが返る
      if (F_sFileName <> '') and (gfnbFileExists(F_sFileName)) then begin
        lstrcpyW(lpstrFile, PWideChar(F_sFileName));
      end;

      Result := GetOpenFileNameW(lr_Info);
      if (Result) then begin
        if ((Flags and OFN_READONLY) <> 0) then begin
          //読み取り専用を選択
          if not(ofReadOnly in Options) then Options := Options + [ofReadOnly];
        end else begin
          //読み取り専用は非選択
          if (ofReadOnly in Options) then Options := Options - [ofReadOnly];
        end;
        F_sFileName := F_GetFiles(lpstrFile, nMaxFile, F_slFiles);
      end;
    finally
      FreeMem(lpstrFile);
    end;
  end;
end;

//短いファイル名
function TMyOpenFileDialog.ShortFileName: WideString;
begin
  Result := gfnsShortFileNameGet(Self.F_sFileName);
end;

function TMyOpenFileDialog.ShortFileNames(iIndex: Integer): WideString;
begin
  Result := gfnsShortFileNameGet(F_slFiles[iIndex]);
end;

myFileDialog.pas

gfnbOpenFileDialogと比べてコールバック関数でメッセージをWndProcへ横流ししている点が違います。
これはTOpenDialogのWndProcプロシージャ内でメッセージの内容に応じて各種イベントを呼んでいるので、そちらへメッセージを渡してあげないとOnCloseやOnShowなどのイベントが起きないためです。
それに伴いダイアログをモニターの中央に表示する位置調整処理をコールバック関数内で行わず、DoShowプロシージャで行っています。
ついでなので位置調整を行うかどうかのPosCenterプロパティを追加しています。

使い方はTOpenDialogと大体同じです。

関数版

{ OpenFileDialog }
function gfnbOpenFileDialog(slFiles: TMyWStrings; sDir: WideString; Opt: TOpenOptions): Boolean; overload;
var
  ldlg_OpenFile: TMyOpenFileDialog;
begin
  ldlg_OpenFile := TMyOpenFileDialog.Create(nil);
  with ldlg_OpenFile do begin
    try
      if (slFiles.Count > 0) then FileName := slFiles[0];
      InitialDir := sDir;
      Options    := Opt;
      Result     := Execute;
      if (Result) then begin
        slFiles.Assign(Files);
      end;
    finally
      Free;
    end;
  end;
end;

2009-07-31:カスタムダイアログ作成のために手直し。OnCloseやOnShowなどのイベントが発生するように修正。
2008-12-28:TMyOpenFileDialogとTMySaveFileDialogの両方lr_Info.lpstrFileまわりを簡素化。
2008-11-15:TMySaveFileDialogで
・ファイル名の後ろに余計なゴミがついてしまっていた不具合を修正
・FileNameプロパティにセットした文字列がダイアログのファイル名欄に表示されない不具合を修正。
2008-08-09: 前回選択したファイルをクリアしていなかった不具合を修正。
2008-07-24: