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 にあたえるという感じです。
よろしくお願いします。
どうも無理っぽいです。
レジストリを読むか、別に記録するしか無さそうです。
その後。
質問した以上答えを書かねばならまいと思ったので、書いておきます。
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に栄光あれ。
解決しわすれました。申し訳ない。
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。
ちなみに 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からという話。
ただのうわさかもしれない。
補足も今回でおしまい。
何度も何度も悪い気がするので、これに関しては進展があってももう書かない事にします。
お疲れ様。
以上。
>ちなみに、その初期ディレクトリの値は、アプリケーション固有のファイル>の新規作成で、既存と重複のないファイル名を作るのに利用します。
>"新規ファイル(1).txt" のような番号つきファイル名作るわけですが、>TSaveDialog.Executeする前に、既存のファイル名を確認し、番号を進め>>>て、TSaveDialog.Filename にあたえるという感じです。
そもそも仕様としてへんだろ?
書き込むフォルダをきめとか無いのか?
毎回バラバラだといろいろなとこに保存されてしまうわけだが?
あるときは C:\Windows であるときは C:\Windows\system32 で
あるときは C:\MyDocument に書き込まれると・・・
あるフォルダで新規ファイル.txtで作れたとしても
フォルダ移動してそのフォルダに既にあった場合 新規ファイル(1).txt に
しなくちゃいけないわけだが?
結局実際にファイル作成する時に確認しなくてはいけなくなる
私もそう思う。
でも仕様が無いので、仕様が無い。
私も普段はこんな事はやりませんヨ。
これを導入しようとしていたプログラムはタブレットでも使われるので
キーボード操作やボタン操作を極限まで減らすのが目的です。
おっしゃるように、基本的に保存先は、ほぼ固定で使われるので
たぶんデータフォルダ的なものを決定しておけば、超ライトユーザー的な人は事足りるとは思います。
そのディレクトリ下にサブディレクトリを作って、そこに出力するような感じで使われる可能性
のほうが高く、出力先のディレクトリは、ユーザーのきまぐれで頻繁に代わる事が予想されるので。
ユーザーは、一度ディレクトリを決定すると、しばらくそのディレクトリで使い
別の目的が発生するとディレクトリを変更し、気まぐれで元のディレクトリに戻って使う
というような使われ方をし、場合によってはネットワークドライブ等に対してしばらく使い
それが終わるとまたローカルで使うという場合も人によっては頻繁にあると予想されるのです。
>フォルダ移動してそのフォルダに既にあった場合 新規ファイル(1).txt にしなくちゃいけないわけだが?
もちろん(1)に戻そうか、そのままにしておくかは考えました。
ファイルが1つもない場合でも、「新規ファイル(10).txt」とかだった場合は
そのままにしておくという方法を上のを書いた直後に実装して、しばらく試しました。
ありえないとは思ったけど、タブレットで使ってる分には快適だと思ったので
収穫はありました。ファイル名がすっきりしない気持ちの悪さは残りましたが
ファイル名を気にせずに、とりあえず保存できるというのは、すばらしい事のようです。
もちろん、ファイルを実際に作る直前にも既存ファイル名との重複チェックをやって
そこで初めて重複した場合に「上書きしてもいいですか?」というダイアログが出ますが
タブレットという性質上、とにかく操作を減らすという目的が最優先なので
人によってはタッチの操作回数さえ減ればそれでいいという人もいるかもしれず。
とりあえず試したかった。
で、上のプログラムは、結局採用せず、保存ダイアログ自体を作ってみましたがパッとしない。
保存ダイアログが開いてその中でディレクトリが変更されたときに
ファイル名の番号を重複しない最大値に戻すという処理をやってみたのだけど、既存のダイアログと似たようなデザインでは
ファイル名が変化したというのが分かりづらく、あまり気持ちの良いものではなかった。
とりあえず、デザインを変えるか、ファイル名に変化があったらハイライトでもしてみる予定だけど・・・
いっそ通算でファイル名に連番でも振ったほうがいいかもしれないとか。
いろいろ試さなければならない今日この頃なのです。
そんなところです。
初期フォルダを取るならこれで出来る
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;
ファイル名が固定ならば
通常の保存と上書き保存 とは別に追加保存?なメニューを作って
FolderDialogでフォルダだけ指定させて
あとはアプリの方で世代つけて保存させれば
いいだけじゃないのか?
ツイート | ![]() |