非常感谢大家的关注!!!不过还是期盼着高手的到来!!! (200分)

  • 主题发起人 主题发起人 HD_Copy
  • 开始时间 开始时间
如果你用的是D6,在窗口上放置一个TShellListView或TShellTreeView就一切正常了。
所以我在尝试 ShellCtrls.pas (可以去Borland下载源码) 中的细节操作。但尚未成功..

var
ShellMalloc: IMalloc;
DesktopFolder: IShellFolder;

procedure TForm1.Button5Click(Sender: TObject);
var
PIDL: PItemIDList;
FileInfo: TSHFileInfoW;
Eaten, Attributes: Cardinal;
path: string;
P: PWideChar;
begin
path := 'C:/Documents and Settings/Administrator/My Documents/Delphi_Tmp/A.CPI';
Eaten := Length(path);
P := StringToOleStr(path);
Attributes := 0;
OLECheck(DesktopFolder.ParseDisplayName(Application.Handle, nil, P, Eaten, PIDL, Attributes));

SHGetFileInfoW(PChar(PIDL), 0, FileInfo, SizeOf(FileInfo),
SHGFI_PIDL or SHGFI_DISPLAYNAME or SHGFI_TYPENAME);
Caption := FileInfo.szDisplayName + ' - ' + FileInfo.szTypeName;
end;

initialization
OleInitialize(nil);
OLECheck(ShGetMalloc(ShellMalloc));
OLECheck(SHGetDesktopFolder(DesktopFolder));

finalization
OleUninitialize;
DesktopFolder := nil;
ShellMalloc := nil;

end.
 
我这没问题,不论我先按哪个 BUTTON 。我的环境 Win2000 + D5 update 2
但按李颖的说法,我查了一下SHBrowseForFolder,可见DELPHI用的是SHBrowseForFolderA,
但这应该和字符集没什么关系的,所以,应该是SHBrowseForFolder初始化了一些东西。
等查查MSDN再说吧。
function SHBrowseForFolderA; external shell32 name 'SHBrowseForFolderA';
function SHBrowseForFolderW; external shell32 name 'SHBrowseForFolderW';
function SHBrowseForFolder; external shell32 name 'SHBrowseForFolderA';
 
我的也没问题。
 
在Win2k Professional sp2+Delphi 5 测试,没问题!
 
瞎起劲。
人家认定了不可能是windows得毛病(不知道为什么这么确定)。 你们老往windows的bug上引干吗?
 
呵呵~,我曾经碰到过这个问题,可是后来也忘了怎么搞好的。。。。
得找找原来的代码才行。。
 
HD_Copy大侠: 忠实于你的原著, 代码纹丝不动。
编程环境: D6+Win98
运行测试环境: Win98、Win2000P、Win2000S
三者皆没有问题, 试过了很多目录。一些临时文件例如pas~~、---、$$$等等,
甚至连文件夹、回收站这些东东也兼收并蓄(文件夹显示“文件夹”,回收站显示
“回收站”)等等。

仍然不放心, 拿D5又搞了一回, 仍然没有问题啊, 操作步骤都是忠实于原著的。
你的代码不错, 很有收藏意义, 小弟谢了。 对你这种不到黄河不折头的精神, 实在
是无比的佩服。同时小弟在这里关注事态的进展。

我向来都对“晕倒死”系统心存疑忌的,看了楼上各位的,看来只好认为是“晕倒死”惹
的祸了........ :)

另外插一句: 在这贴上居然见到了hubdog大侠, 真是如雷贯耳, 三生有幸, 希望以后
再出些Delphi葵花宝典一类的武林秘籍, 让小弟们的武功能突飞猛进。
 
to 代鱼:
首先,感谢你对我这个问题的关注。但不要叫我“大侠”,我现有的水平离“大侠”这两个字还差得
很远!但我这个问题自从提出以来,的确受到不少真正的大侠的关注,如:aimingoo、李颖、OopsWare,
他们都是顶尖的高手!不过,一方面是由于我这个问题也不算太简单;另一方面,每个人总是业有所
专攻,没有人敢说自己是电脑方面的全才,或许这个问题不对他们的路吧,因此,至今没有得到满意
的答复。但不管怎么说,我还是要向各位高手以及所有关注我这个帖子的朋友们表示衷心的感谢!!!

>>对你这种不到黄河不折头的精神, 实在是无比的佩服。
这不是什么“不到黄河不折头”,我说得这种现象确实存在!!!这已经得到了多方的验证!!!
这个结论已经是不争的事实!!!现在的问题是怎么解决?关键是没有人能说清楚在SHGetFileInfo
被调用之前,初始化了一些什么?怎样初始化的?
至于某些朋友的Win2000下没问题,只能说明在这之前已经进行了初始化,但你自己不知道。

这种现象应该是系统的Shell32.dll在各个Windows版本之间的不兼容引起的,而不能说是系统的Bug,
因为事实证明,只要进行了正确的初始化,就可以解决这个问题。

to All:
那些对我问题中描述的这种现象是否存在还心存疑虑的朋友,请不要在我这个帖子上浪费你的宝贵时
间了,多谢合作。

!!!!!!!!!!!!!期盼着高手的到来!!!!!!!!!!!!!!!
!!!!!!!!!!!!!期盼着高手的到来!!!!!!!!!!!!!!!
 
問題確實存在:
ShBrowseForFolder(BrowseInfo);的執行與否對
SHGetFileInfo(PChar(CurrentPath+sr.Name), 0, FileInfo, SizeOf(FileInfo), SHGFI_TYPENAME);
的結果起決定作用.兩者都是api function.
再用vb,vc試試,就可發現問題出在那里了.
 
to all: 终于解决了!有兴趣的话看看下面的代码,
to Hd_copy:
你可以不再使用FindFirst取文件了,试试下面的完全的Shell编程方式。
我也说不太清原因,直接获得文件的PItemIDList并不能得到特殊文件的类型说明
而绕个弯路,先取其所在路径,用路径的PItemIDList枚举出文件的PItemIDList
就没有问题!?

implementation

uses
FileCtrl, ShellAPI, ActiveX, ShlObj, ComObj;

{$R *.DFM}

var
ShellMalloc: IMalloc;
DesktopFolder: IShellFolder;
CS : TRTLCriticalSection;
FileInfo: TSHFileInfo;

function NextPIDL(PIDL: PItemIDList): PItemIDList;
begin
Result := PIDL;
Inc(PChar(Result), PIDL^.mkid.cb);
end;

function GetPIDLSize(PIDL: PItemIDList): Integer;
begin
Result := 0;
if Assigned(PIDL) then begin
Result := SizeOf(PIDL^.mkid.cb);
while PIDL^.mkid.cb <> 0 do begin
Result := Result + PIDL^.mkid.cb;
PIDL := NextPIDL(PIDL);
end;
end;
end;

function CreatePIDL(Size: Integer): PItemIDList;
begin
try
Result := nil;
Result := ShellMalloc.Alloc(Size);
if Assigned(Result) then FillChar(Result^, Size, 0);
finally
end;
end;

procedure FreePIDL(PIDL: PItemIDList);
begin
if PIDL <> nil then ShellMalloc.Free(PIDL);
end;

function ConcatPIDLs(PIDL1, PIDL2: PItemIDList): PItemIDList;
var cb1, cb2: Integer;
begin
if Assigned(PIDL1) then cb1 := GetPIDLSize(PIDL1) - SizeOf(PIDL1^.mkid.cb) else cb1 := 0;
cb2 := GetPIDLSize(PIDL2);
Result := CreatePIDL(cb1 + cb2);
if Assigned(Result) then begin
if Assigned(PIDL1) then CopyMemory(PChar(Result), PIDL1, cb1);
CopyMemory(PChar(Result) + cb1, PIDL2, cb2);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
PIDL, RelativePIDL, NewPIDL: PItemIDList;
Eaten, Attributes: Cardinal;
path: string;
P: PWideChar;
ShellFolder: IShellFolder;
EnumIDList: IEnumIDList;
NumIDs: Cardinal;
begin
// 设置要显示的路径,取其 PItemIDList
path := 'C:/Documents and Settings/Administrator/My Documents/Delphi_Tmp';
Eaten := Length(path);
P := StringToOleStr(path);
Attributes := 0;
OLECheck(DesktopFolder.ParseDisplayName(Application.Handle, nil, P, Eaten, PIDL, Attributes));
// 根据 PItemIDList 得到 IShellFolder
// 可以直接取文件的PIDL,但是存在与以前一样的现象,现在取其目录
DesktopFolder.BindToObject(PIDL, nil, IID_IShellFolder, Pointer(ShellFolder));
// 枚举目录下的文件到 EnumIDList
ShellFolder.EnumObjects(Application.Handle, SHCONTF_NONFOLDERS, EnumIDList);
NewPIDL := nil;
while EnumIDList.Next(1, RelativePIDL, NumIDs) = S_OK do
try
// 将列出的文件PIDL与目录合并得到文件的 PItemIDList
NewPIDL := ConcatPIDLs(PIDL, RelativePIDL);
// 根据合并出的完整文件PItemIDList取文件名和属性!
FillChar(FileInfo, SizeOf(FileInfo), 0);
SHGetFileInfo(PChar(NewPIDL), 0, FileInfo, SizeOf(FileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME or SHGFI_TYPENAME);
ListBox1.Items.Add( FileInfo.szDisplayName + ' - ' + FileInfo.szTypeName );
finally
FreePIDL(NewPIDL);
FreePIDL(RelativePIDL);
end;
end;

initialization
OLECheck(SHGetDesktopFolder(DesktopFolder));
InitializeCriticalSection(CS);
OleInitialize(nil);
OLECheck(ShGetMalloc(ShellMalloc));

finalization
DesktopFolder := nil;
ShellMalloc := nil;
OleUninitialize;
DeleteCriticalSection(CS);
end.
 
to OopsWare:
老大,你们的项目完工了吗?多谢你能在百忙当中还抽时间多次关注我的问题!
不过,你的解答还不能令我满意!你的代码和Delphi自带的VirtualListView例子基本上差不多,只不
过它是显示某个目录时需要遍历一下,而你这里显示某个具体文件也要遍历一下。
其实,你应该对我这个问题的细节和要求很清楚啊,我们在MSN上讨论过的呀,这里,应该对其他朋友
说声对不起了,因为我在这个帖子中没有把一些更深的细节说出来。实际上,我是要在ListView控件中显
示硬盘中的文件,就象资源管理器右边的文件列表一模一样。有时,要显示某个目录下的所有文件;有时,
要显示从数据库中搜索出来的一些文件(这些文件可能分布在硬盘不同的分区、不同的目录中,有点象Win2000
下资源管理器中的“搜索”,但又有所不同,资源管理器搜索特定的文件,肯定需要遍历目录;而我这里
并不需要,因为我从数据库中取出的文件的全路径和文件名都是已知的了)。
所以说,OopsWare上面的代码也仅仅是个变通的方法,我们姑且称为[变通方法一]。而我目前使用的
方法是下面的代码:
/* --- 取文件类型 --- */
TempStr = FileInfo.szTypeName;
if( TempStr.Length() == 0 ) //如果是系统不可识别的文件类型
{
TempStr = ExtractFileExt( FileWholeName );
if( TempStr.Length() == 0 ) //如果文件没有扩展名,则......
GetItem(i)->strType = "文件";
else
GetItem(i)->strType = UpperCase(TempStr.Delete(1,1)) + " 文件";
}
else
GetItem(i)->strType = TempStr;
我们也姑且称之为[变通方法二],这样,我就要考虑一下到底[变通方法一]和[变通方法二]哪个效率高呀?
我觉得还是[变通方法二]要高一些。其实,还有另外一种变通的方法,姑且称之为[变通方法三],就是在
调用SHGetFileInfo之前,不管是执行SelectDirectory()也好、还是SHBrowseForFolder也罢、或者是别的
什么,只要不在屏幕上显示出来,而且不会造成系统混乱、或其他什么不良的后果,只是让它初始化一下,
然后就不管它了,我觉得这种变通的方法倒是可以接受,其实,好像OopsWare也曾经说过的。当然,最好
还是有人知道怎样从根本上进行初始化。

我现在希望:
1. 有人能给出[变通方法三]。
2. 哪位英语好的朋友将我这个问题翻译过来,在诸如www.experts-exchange.com这样的地方问一下,那种
地方的高手深不可测,应该会有人能解答出来。
3. 有人能给出根本的解决方法。

在没解决之前,我目前用的方法就是我自己的[变通方法二]

再次感谢OopsWare!!
 
to HD-copy:
用你的方法多次遍历一个目录的时间是一定的,而我的方法遍历一个目录第一次的
效率与你的差不多,但多次使用的速度就很快,因为IMalloc已经将这些信息记入了
Shell管理的区域。用FindFirst,FindNext可以取道文件系统的所有物理文件,但
我的方法还可以取道所有的名字空间(如网上邻居...)。
我的这段代码应该是适合所有版本的Win32的,Windows捆绑IE4以后Shell就完全是
COM了,应该更多的使用COM的思想去处理Shell操作。所谓的初始化的那段操作也就是
我代码中的 ShellFolder.EnumObjects(Application.Handle, SHCONTF_NONFOLDERS, EnumIDList);
测试一下这段代码:
procedure TForm1.Button3Click(Sender: TObject);
var
DesktopFolder: IShellFolder;
EnumIDList: IEnumIDList;
begin
OLECheck(SHGetDesktopFolder(DesktopFolder));
DesktopFolder.EnumObjects(Application.Handle, SHCONTF_NONFOLDERS, EnumIDList);
Caption := MrsGetFileType('C:/Documents and Settings/Administrator/My Documents/Delphi_Tmp/A.CPI')
end;
也就证实了SelectDir所初始化的内容。
Shell使用的内存是由 IMalloc 统一管理的。也许是为了节省内存的存储空间,多数
重复的内容在他管理下,仅保留一个副本,直接取得文件的PIDL应该可以获得文件的
全部属性。但是这时win2k可能忘记了将文件的说明信息存入IMalloc的共享内存
区域。换一个思路,去取文件所在目录的PIDL,再枚举目录下的所有文件,就得到了所
需要的信息。也就是说,ShBrowseForFolder(BrowseInfo);也同样使用了枚举文件的
用法,从而将文件的说明信息存入了IMalloc的管理区域。也就就像前面李颖的的贴子
中提到的“初始化了一些信息”。这应该是windows的shell编程的正常用法(我的代码多数
借见了Delphi的ShellCtrls.pas,这在D6好像只有.dcu,请到Borland的CodeCenter下载源
程序,使用D5的用户只能想其他的方法了,例如ShellPack的控件)。
你说的[变通方法三]代码如下:

var
DesktopFolder: IShellFolder;
EnumIDList: IEnumIDList;
initialization
OLECheck(SHGetDesktopFolder(DesktopFolder));
DesktopFolder.EnumObjects(Application.Handle, SHCONTF_NONFOLDERS, EnumIDList);
OleInitialize(nil);
finalization
DesktopFolder := nil;
OleUninitialize;
end.

其实我前一贴的大宗代码是希望你完全将 Shell 编程的思想,用在你的程序中。
 
to OopsWare:
哈哈哈,:D :D :D I love you , baby , mum,mum,mum,mum,mum......
说句急功近利的话,当我看到你这个帖子中在OLECheck(SHGetDesktopFolder(DesktopFolder));的后面
马上马上马上马上马上就用了EnumObjects()!!我的眼睛都发亮光了!!我的天啊,问题出在这里呀!这
就是初始化啊!!5~~~~~~~~~~~~~~ 看来,我对Shell编程还知道的不够全面,不够深刻,先前也仅仅是
模仿啊!5~~~~~~~~~~~

不过,
>>用你的方法多次遍历一个目录的时间是一定的,而我的方法遍历一个目录第一次的
>>效率与你的差不多,但多次使用的速度就很快,因为IMalloc已经将这些信息记入了
>>Shell管理的区域。
我并不这么认为。就是说,在实现我上面所说的“要显示从数据库中搜索出来的一些文件”时,你的
这种仍然需要遍历的方法,总觉得...... 因为你不能仅仅这样说“IMalloc已经将这些信息记入了Shell管
理的区域。”,其实,我们都知道Windows的其它操作也都是利用缓存技术的,用我的方法在执行第二遍时
也比第一遍快的。由于在已知文件的全路径名时,通过Shell编程是不能直接取得这个文件的PIDL的,所以,
要用Shell编程,也只能遍历。
但我现在又想问一个很基本的问题了,就是Windows系统是怎样定位一个已知全路径名的文件呢?如果也是
在后台用了遍历,那我就彻底向你“投降”,改用Shell编程了 :P

至此,这个问题基本解决了,非常感谢OopsWare!!!

请OopsWare到http://www.delphibbs.com/delphibbs/dispq.asp?lid=1178271来拿300分!!
 
多人接受答案了。
 
哈哈。看来问题解决了。恭喜HD-COPY了。哈哈。

去北京了半个多月,才回来。HD-Copy的问题被解决可以说是回郑州后见到的第一件开心事。:)

开始报着学习的心态开始仔细地读Oops的文字……
^-^
 
to aimingoo:
aimingoo大侠,又见到你了,真高兴!你这个人好像永远没有烦恼,永远这么快乐,以至于在你的贴
子中总有“^哈哈”的笑声。最近好吗?
虽然这个题目不是你最终帮我解决的,但还是非常感谢你的分析,你永远是我心目中的大富翁论坛
第一高手!!这不仅是因为你水平高,而且你这个人从来不摆架子,为人谦和。说实在的,以我的水平
能交到你这样的高手做朋友,确实是我的荣幸啊!!
我心目中的大富翁10大高手分别是(当然,这种事情总是带有个人倾向的):
1. aimingoo
2. 李颖
3. Another_eyes
4. OopsWare
5. 温柔一刀
前5名不用多想,6到10名人选众多,需要仔细斟酌,我就不说了
 
曾经在某个贴子里面看到过aimingoo的自我介绍,
当时就感觉到aimingoo确实是我等学习的榜样。
印象当中好像aiming已经完成了研究生的学业,
那么aiming当初有没有非常痛苦的学习数学呢?
我最近是准备学数学考研的,数学基础不是很好,
因为数学课从来就没有好好听过,
请问各位大虾以及aiming有没有什么可以传授与
小弟的Tips?
email jd79@163.com
 
to bubble,
---------------
周五去北京出差了,没看见你的贴子。所以,对不起~~~~~~现在才能回。

我其实也没有不同的,我是非计算机专业的,所以关于编程的一切差不多都是自学。从94
年开始到现在。

我还在读研究生,在职的。每年去北京两次。已经读了一年了。正在准备全国综合考试。
数学方面,我准备考《组合数学》,主要是因为它不需要太多的基础数学知识,但据说它
的难度非常之大。哈哈~~~~~

综合科目我准备考的是下面三门,你可以参考:
《组合数学》
《人工智能》(或《软件工程)
《图形学原理》

此外,学习方面,我真的没有什么捷径,由于全靠自学,所以,我的窍门只有一个:
“书读百遍,其异自见”。
 
后退
顶部