TSaveDialogの初期ディレクトリを取得するには?

解決


にゃんこ  2011-01-29 23:52:12  No: 39929

Delphi 5, XP Pro です。
TSaveDialogの初期ディレクトリを取得する方法をご教示いただければと思いまして、質問させていただきました。

調べてみると、TSaveDialogはレジストリHKCUのExplorer\OpenSaveMRUのディレクトリを初期値にしているようです。
(おそらく履歴をとらないポリシーにするとまた異なる動作となるのでしょうけど未確認です)

なので、この値をスマートに取得する方法?とも言えるかもしれませんが。

TSaveDialog.InitalDirは空のまま利用しています。
Filterには*.txt などのフィルタをあたえています。
そのせいか、TSaveDialogは、*.txt に関わるアプリケーションで最後に確定したディレクトリを最初に開いてくれるようです。

GetCurrentDirはアプリケーションのディレクトリを示していて、TSaveDialogがアプリケーション起動後に初めて開くディレクトリとは異なります。

TSaveDialog.Executeで一度でも確定すると、その後GetCurrentDirは、そのディレクトリを示すようになります。
なので、一度でも確定すれば GetCurrentDirで事足ります。

TSavDialogをインスタンス化する前に、その初期ディレクトリを知りたいのですが、レジストリにアクセスする以外にはないのでしょうか。

あるいは、アプリケーション側でそれらの値を記憶し、初期ディレクトリは制御するべきでしょうか?
今までは制御していたのですが、ふと疑問になりまして。

みなさん、どうされていますか?

ちなみに、その初期ディレクトリの値は、アプリケーション固有のファイルの新規作成で、既存と重複のないファイル名を作るのに利用します。
"新規ファイル(1).txt" のような番号つきファイル名作るわけですが、TSaveDialog.Executeする前に、既存のファイル名を確認し、番号を進めて、TSaveDialog.Filename にあたえるという感じです。

よろしくお願いします。


にゃんこ  2011-01-30 01:24:56  No: 39930

どうも無理っぽいです。
レジストリを読むか、別に記録するしか無さそうです。


にゃんこ  2011-01-30 03:20:26  No: 39931

その後。

質問した以上答えを書かねばならまいと思ったので、書いておきます。

OpenSaveMRUではなく

HKCUのSoftware\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU

を読むらしい。

MRUList に履歴の順序が入ってる模様。

'edbdafg' のように。

順序はともかく、それをキーにして REG_BINARYを読むとアプリケーションのファイル名と最後に確定したディレクトリ名が#0区切りで入っている。

以下ソース。
unicode なのでPWideCharがそのバイト数で飛ぶのでけっこう面倒だった。
その辺がまだ怪しいかもしれない。

function GetLastVisitedMRU: string;
var
    reg: TRegistry;
    list: string;
    i,len: integer;
    buffer: pByteArray;
    buflen: integer;
    dir,fn:WideString;
    p,s,e: PWideChar;
    appname:string;
    appdir :string;
    tmplen: integer;
begin
    Result:='';
    reg:= TRegistry.Create;
    try
        reg.RootKey:= HKEY_CURRENT_USER;
        if reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU',False) then
        begin
            if reg.ValueExists('MRUList') then
            begin
                list:=reg.ReadString('MRUList');
                len:= Length(list);
                if len>0 then
                begin
                    for i:= 1 to len do
                    begin
                        if reg.ValueExists(list[i]) then
                        begin
                            {
                            case reg.GetDataType(list[i]) of
                                rdUnknown     : DEBUGSTROBJ(Self,'RegType:Unknown');
                                rdString      : DEBUGSTROBJ(Self,'RegType:String');
                                rdExpandString: DEBUGSTROBJ(Self,'RegType:ExpandString');
                                rdInteger     : DEBUGSTROBJ(Self,'RegType:Integer');
                                rdBinary      : DEBUGSTROBJ(Self,'RegType:Binary');
                            end;
                            }
                            buflen:= reg.GetDataSize(list[i]);
                            if buflen>0 then
                            begin
                                GetMem(buffer,buflen);
                                try
                                    reg.ReadBinaryData(list[i],pByte(buffer)^,buflen);

                                    s:= PWideChar(buffer);
                                    p:= s;
                                    e:= s + buflen;

                                    tmplen:=0;
                                    while p<=e do
                                    begin
                                        if p^ = #0 then
                                        begin
                                            tmplen:= (p-s);
                                            Break;
                                        end;
                                        inc(p);
                                    end;

                                    if tmplen>0 then
                                    begin
                                        SetLength(fn,tmplen);
                                        copymemory(@fn[1],s,tmplen*SizeOf(WideChar));
                                    end;

                                    s:= p;
                                    inc(s);
                                    p:= s;

                                    tmplen:=0;
                                    while p<=e do
                                    begin
                                        if p^ = #0 then
                                        begin
                                            tmplen:= (p-s);
                                            Break;
                                        end;
                                        inc(p);
                                    end;

                                    if tmplen>0 then
                                    begin
                                        SetLength(dir,tmplen);
                                        copymemory(@dir[1],s,tmplen*SizeOf(WideChar));
                                    end;

                                    appname:=WideCharToString(@fn[1]);
                                    if appname=ExtractFilename(Application.Exename) then
                                    begin
                                        appdir :=WideCharToString(@dir[1]);
                                        Result:= appdir;
                                        OutPutDebugString(Pchar(list[i]+':'+appname+','+appdir));
                                        Exit;
                                    end;

                                finally
                                    FreeMem(buffer,buflen);
                                end;
                            end;
                        end;
                    end;
                end;
            end;
            reg.CloseKey;   //finally不要
        end;
    finally
        reg.Free;
    end;

end;

できたてほやほやなのでご注意。
delphiに栄光あれ。


にゃんこ  2011-01-30 03:21:57  No: 39932

解決しわすれました。申し訳ない。


にゃんこ  2011-01-30 06:46:47  No: 39933

if tmplen>0 then
 ではじまるブロックは、威光の影響を受ける範囲をすべてブロック内におさめるべき。

    reg:= TRegistry.Create;
    try
        reg.RootKey:= HKEY_CURRENT_USER;
        if reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU',False) then
        begin
            if reg.ValueExists('MRUList') then
            begin
                list:=reg.ReadString('MRUList');
                len:= Length(list);
                if len>0 then
                begin
                    for i:= 1 to len do
                    begin
                        if reg.ValueExists(list[i]) then
                        begin
                            buflen:= reg.GetDataSize(list[i]);
                            if buflen>0 then
                            begin
                                GetMem(buffer,buflen);
                                try
                                    reg.ReadBinaryData(list[i],pByte(buffer)^,buflen);

                                    s:= PWideChar(buffer);
                                    p:= s;
                                    e:= s + buflen;

                                    tmplen:=0;
                                    while p<=e do
                                    begin
                                        if p^ = #0 then
                                        begin
                                            tmplen:= (p-s);
                                            Break;
                                        end;
                                        inc(p);
                                    end;

                                    if tmplen>0 then
                                    begin
                                        SetLength(fn,tmplen);
                                        copymemory(@fn[1],s,tmplen*SizeOf(WideChar));

                                        s:= p;
                                        inc(s);
                                        p:= s;

                                        tmplen:=0;
                                        while p<=e do
                                        begin
                                            if p^ = #0 then
                                            begin
                                                tmplen:= (p-s);
                                                Break;
                                            end;
                                            inc(p);
                                        end;

                                        if tmplen>0 then
                                        begin
                                            SetLength(dir,tmplen);
                                            copymemory(@dir[1],s,tmplen*SizeOf(WideChar));

                                            appname:=WideCharToString(@fn[1]);
                                            if appname=ExtractFilename(Application.Exename) then
                                            begin
                                                appdir :=WideCharToString(@dir[1]);
                                                Result:= appdir;
                                                OutPutDebugString(Pchar(list[i]+':'+appname+','+appdir));
                                                Exit;
                                            end;
                                        end;
                                    end;

                                finally
                                    FreeMem(buffer,buflen);
                                end;
                            end;
                        end;
                    end;
                end;
            end;
            reg.CloseKey;   //finally不要
        end;
    finally
        reg.Free;
    end;

こういうふうに。
たぶん他はOK。


にゃんこ  2011-01-30 07:25:30  No: 39934

ちなみに Vista, 7 だと。

LastVisitedMRUがLastVisitedPidlMRUになっている。

さらにMRUListがMRUListExになる。

また値の名前が edbdafg などといったcharではなく、
値の名前は数字(文字)になり、MRUListEx中には、その数値(整数)が入るようです。
しかも終端に ffffffとついていて、謎めいています。

また、ComDlg32というキーが存在していない場合があるらしい。

UACのしばりをうけている環境では、Explorerというキーまではあってもそれ以下のMRUに関わるキー自体無いような気がする。

ということで、けっこう環境依存な感じですのでご注意ください。
対策としては、OSの判定をしなくとも、if reg.OpenKey() or reg.OpenKey() then begin でいけると思います。同様にMRUList,MRUListExも判定し、MRUListExの場合は、数値を文字に変換して値名でアクセスしないといけないのだと思います。

Windows 98 とかだとこの機構がなくて GetCurrentDir で事足りるようなのだけど、未確認。
こういう面倒なことになったのは、どうも Windows 2000からという話。
ただのうわさかもしれない。

補足も今回でおしまい。
何度も何度も悪い気がするので、これに関しては進展があってももう書かない事にします。
お疲れ様。

以上。


KHE00221  2011-01-31 04:08:45  No: 39935

>ちなみに、その初期ディレクトリの値は、アプリケーション固有のファイル>の新規作成で、既存と重複のないファイル名を作るのに利用します。
>"新規ファイル(1).txt" のような番号つきファイル名作るわけですが、>TSaveDialog.Executeする前に、既存のファイル名を確認し、番号を進め>>>て、TSaveDialog.Filename にあたえるという感じです。

そもそも仕様としてへんだろ?

書き込むフォルダをきめとか無いのか?
毎回バラバラだといろいろなとこに保存されてしまうわけだが?

あるときは C:\Windows であるときは C:\Windows\system32 で
あるときは C:\MyDocument に書き込まれると・・・

あるフォルダで新規ファイル.txtで作れたとしても
フォルダ移動してそのフォルダに既にあった場合 新規ファイル(1).txt に
しなくちゃいけないわけだが?

結局実際にファイル作成する時に確認しなくてはいけなくなる


にゃんこ  2011-02-01 04:22:42  No: 39936

私もそう思う。
でも仕様が無いので、仕様が無い。

私も普段はこんな事はやりませんヨ。

これを導入しようとしていたプログラムはタブレットでも使われるので
キーボード操作やボタン操作を極限まで減らすのが目的です。

おっしゃるように、基本的に保存先は、ほぼ固定で使われるので
たぶんデータフォルダ的なものを決定しておけば、超ライトユーザー的な人は事足りるとは思います。

そのディレクトリ下にサブディレクトリを作って、そこに出力するような感じで使われる可能性
のほうが高く、出力先のディレクトリは、ユーザーのきまぐれで頻繁に代わる事が予想されるので。

ユーザーは、一度ディレクトリを決定すると、しばらくそのディレクトリで使い
別の目的が発生するとディレクトリを変更し、気まぐれで元のディレクトリに戻って使う
というような使われ方をし、場合によってはネットワークドライブ等に対してしばらく使い
それが終わるとまたローカルで使うという場合も人によっては頻繁にあると予想されるのです。

>フォルダ移動してそのフォルダに既にあった場合 新規ファイル(1).txt にしなくちゃいけないわけだが?

もちろん(1)に戻そうか、そのままにしておくかは考えました。
ファイルが1つもない場合でも、「新規ファイル(10).txt」とかだった場合は
そのままにしておくという方法を上のを書いた直後に実装して、しばらく試しました。
ありえないとは思ったけど、タブレットで使ってる分には快適だと思ったので
収穫はありました。ファイル名がすっきりしない気持ちの悪さは残りましたが
ファイル名を気にせずに、とりあえず保存できるというのは、すばらしい事のようです。

もちろん、ファイルを実際に作る直前にも既存ファイル名との重複チェックをやって
そこで初めて重複した場合に「上書きしてもいいですか?」というダイアログが出ますが
タブレットという性質上、とにかく操作を減らすという目的が最優先なので
人によってはタッチの操作回数さえ減ればそれでいいという人もいるかもしれず。
とりあえず試したかった。

で、上のプログラムは、結局採用せず、保存ダイアログ自体を作ってみましたがパッとしない。
保存ダイアログが開いてその中でディレクトリが変更されたときに
ファイル名の番号を重複しない最大値に戻すという処理をやってみたのだけど、既存のダイアログと似たようなデザインでは
ファイル名が変化したというのが分かりづらく、あまり気持ちの良いものではなかった。
とりあえず、デザインを変えるか、ファイル名に変化があったらハイライトでもしてみる予定だけど・・・
いっそ通算でファイル名に連番でも振ったほうがいいかもしれないとか。
いろいろ試さなければならない今日この頃なのです。

そんなところです。


KHE00221  2011-02-01 05:59:42  No: 39937

初期フォルダを取るならこれで出来る

  TSaveDialog = class(Dialogs.TSaveDialog)
  protected
    procedure DoSelectionChange; override;
  end;

//フォルダやファイルを選択する度に実行される
procedure TSaveDialog.DoSelectionChange;
begin
    inherited;

    //初期はフォルダが入っている
    //ファイルを選択すると選択したフォルダ+ファイル名になる   
    Form1.Caption := Self.FileName; 
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    SaveDialog1.FileName := ''; //全開のファイル名が残るので消す
    if SaveDialog1.Execute = True then
    begin
      Exit;
    end;
end;


KHE00221  2011-02-01 18:48:09  No: 39938

ファイル名が固定ならば

通常の保存と上書き保存  とは別に追加保存?なメニューを作って

FolderDialogでフォルダだけ指定させて
あとはアプリの方で世代つけて保存させれば
いいだけじゃないのか?


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加