beta 的第五篇心得:指定 TShellTreeView 的初始目录(20分)

B

beta

Unregistered / Unconfirmed
GUEST, unregistred user!
声明:本文乃 熊恒(beta) 原创,如要转载请保持文章完整。
指定 TShellTreeView 的初始目录 --- by 熊恒(beta)
这时很久以前写的,本来觉得没有什么技术含量,不准备贴出来的,不过刚才看到有
富翁在问这个问题,就贴出来吧。
初一看,我觉得跟指定 SelectFolder 的初始目录差不多,于是决定用发送
BFFM_SETSELECTION 消息的方法试一下,而且由于取得 ShellTreeView 的句柄远比
取得 SelectFolder 的句柄要方便的多(直接通过属性获取),如下:
SendMessage(ShellTreeView1.Handle, BFFM_SETSELECTION, Ord(TRUE),
Longint(PChar(ExtractFilePath(Application.ExeName))));
但是发现不行 :-( 又只好另觅它法了。进入 ShellCtrls 单元,一窥其源码,发现
属性 Path 对应的 SetPath 看起来很像是干这个事情的,于是立即进行试验:
ShellTreeView1.Path := ExtractFilePath(Application.ExeName);
还是不行 :-( 怎么回事?进去看一下:
procedure TCustomShellTreeView.SetPath(const Value: string);
var
P: PWideChar;
NewPIDL: PItemIDList;
Flags,
NumChars: LongWord;
begin
NumChars := Length(Value);
Flags := 0;
P := StringToOleStr(Value);
try
OLECheck(DesktopShellFolder.ParseDisplayName(
0,
nil,
P,
NumChars,
NewPIDL,
Flags)
);
FUpdating := True;
SetPathFromID(NewPIDL);
except on EOleSysErrordo
raise EInvalidPath.CreateFmt(SErrorSettingPath, [Value]);
end;
FUpdating := False;
end;

原来它是靠调用 SetPathFromID 来实现的,它自己只是负责把 string 类型的 Path
转化为 PItemIDList 而已,再去看看这个过程的原型吧:
procedure TCustomShellTreeView.SetPathFromID(ID: PItemIDList);
var
I: Integer;
Pidls: TList;
Temp, Node: TTreeNode;
begin
if FUpdating or (csLoading in ComponentState)
or ((SelectedFolder <> nil)
and SamePIDL(SelectedFolder.AbsoluteID, ID)) then
Exit;
FUpdating := True;
Items.begin
Update;
try
//...省略部分代码
finally
Items.EndUpdate;
FUpdating := False;
end;
end;

看出点问题了没有?注意在 SetPath 里面调用该过程的附近的代码:
FUpdating := True;
SetPathFromID(NewPIDL);
看出来了吗?它在调用 SetPathFromID 之前就把 FUpdating 设为 True 了,然后一
进入 SetPathFromID 又立刻判断 if FUpdating or ... then
Exit;
那当然就 Exit
了,也就是说后面的真正有用的语句根本就没有被执行!OK, 问题已经找到了,把
SetPath 里面对 FUpdating 属性的两次设置的语句删除,再试一下:
ShellTreeView1.Path := ExtractFilePath(Application.ExeName);
成功了吧^_^ 别高兴的太早,还有点尾巴没有处理完。经我验证,这个 SetPathFromID
还是要点时间的,你把人家的更新标志去掉了,那在选择到预定目录之前用户完全有
可能打开其他目录,从而可能导致引起不必要的麻烦,所以更新还是要的。我当然不
会再叫你把那两句加回去 :) 怎么办呢?答案就是 Items.begin
Update 了。
然后,你会发现,如果你所指定的初始目录的兄弟目录比较多,而改初始目录又位于
中间甚至后面,你就会看不到你所指定的初始目录,虽然他已经被选中。这倒不是什
么大问题,只要设置一下 TopItem 就可以搞定了。最后我给 TCustomShellTreeView
添加了一个方法以解决以上问题:
procedure TCustomShellTreeView.SetVisiblePath(const Value: string;
Indent: Integer = 0);
var
P: PWideChar;
NewPIDL: PItemIDList;
Flags,
NumChars: LongWord;
i: Integer;
begin
NumChars := Length(Value);
Flags := 0;
P := StringToOleStr(Value);
try
OLECheck(DesktopShellFolder.ParseDisplayName(
0,
nil,
P,
NumChars,
NewPIDL,
Flags)
);
Items.begin
Update;
SetPathFromID(NewPIDL);

if SelectionCount > 0 then
// 若存在选定项
begin
TopItem := Selected;
// 将选定项即指定目录置为顶端
for i := 1 to Indentdo
// 依次将该目录的前一个节点置顶
if Assigned(TopItem.GetPrev) then
TopItem := TopItem.GetPrev
else
Break;
end;

except on EOleSysErrordo
raise EInvalidPath.CreateFmt(SErrorSettingPath, [Value]);
end;
Items.EndUpdate;
end;

基本上是从 SetPath 里面 Copy 出来的,不过做了相应的修改,并添加了一些代码。
增加了一个 Indent 参数,使你可以指定你的初始目录出现在 ShellTreeView 的位置
若缺省则是在顶端。
小结:从前面的对 FUpdating 属性的设置方面的分析,可以看出这应该是 Delphi
的一个小 Bug, 估计这也是 Borland 公司公开 VCL 源代码的一个原因之一(哪怕只
占很少的成分:p
 
好啊,把所有的心得做成一个chm更好。
谢谢。
 
2 楼的也太懒了吧 [:D]
借贵宝地一用 [:)]
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2086581
 
呵呵,你就不懒吗,你的问题直接查 Windows 帮助不是写的很详细吗[:D]
 
在来一个人就分赃:)
 
这可是你说的 :)
 
呵呵,骗你不成:)
 
顶部