Shell对话框(二)

 
外壳对话框(2)
2008-01-30 15:40
如果函数调用成功的话,返回值是NO_ERROR,如果用户取消的对话框,则返回   -1($FFFFFFFF),如果调用失败则返回其他的错误代码,具体错误信息可以用GetLastError API调用获得。
关闭系统对话框
     ExitWindowsDialog和RestartDialog函数可以用来显示关闭和重启系统对话框(如图2.31),它们同公开的ExitWindowsEx API函数没有什么太大的不同,但在其过程中都会产生一个对话框。ExitWindowsDialog函数可以按索引值60从Shell32.dll中公开出来,RestartDialog函数的在Shell32.dll中的索引值则是59,两个函数的定义如下:
     procedure ExitWindowsDialog(Owner: HWND); stdcall;
     function RestartDialog(Owner: HWND; Reason: Pointer; ExitType: UINT): DWORD; stdcall;
     对ExitWindowsDialog函数来说,对话框好像并不使用Owner参数作为父窗口,在Windows 95上,当操作成功的话owner窗口会收到一个WM_QUIT消息。在Windows NT上,owner 窗口根本不被使用。同时这个函数没有返回值,所以没有办法知道用户选择了什么操作以及操作是否被取消了。
     RestartDialog函数更有用一些,当我们修改了系统的设置,并希望重新启动系统使修改生效的时候可以使用这个函数。Reason参数指向一个要显示在对话框中的字符串,用来解释关闭系统的原因。ExitType参数指定关闭类型,可以使用ExitWindowsEX函数使用值的一个子集及额外的几个新值,下面是它们的完全列表:
     EWX_LOGOFF         = $00;
     EWX_SHUTDOWN       = $01;
     EWX_REBOOT         = $02;
     EW_RESTARTWINDOWS = $42;
     EW_REBOOTSYSTEM    = $43;
     EW_EXITANDEXECAPP = $44;
     如果用户选择执行关闭操作,函数返回IDYES,否则返回IDNO。
     要注意的是显示在对话框中的原因字符串后总会跟着一个系统缺省提供的字符串用来显示确认信息,所以应该在我们的Reason字符串后附上空格或回车换行字符。另外返回值不能用于确定操作的成功性,它只表明用户的选择,如果重启操作由于某些原因失败了,返回值仍然是IDYES。同时要注意的是要想调用成功,用户还必须有SE_SHUTDOWN_NAME权限(在NT上)。
缺少内存对话框
     SHOutOfMemoryMessageBox是一个未公开的函数,当系统内存不足时可以用来显示标准的外壳信息对话框,它在Shell32.dll中的索引值是126,函数定义如下:
     function SHOutOfMemoryMessageBox(Owner: HWND;
       Caption: Pointer; Style: UINT): Integer; stdcall;
     它会调用MessageBox API,同时传递3个标准的参数和ERROR_OUTOFMEMORY错误消息。Caption参数指向对话框标题字符串。如果Caption为nil,父窗口的标题就会被使用。Style参数可以被设置为任意MessageBox函数使用的MB_XXX常数的组合,通常设置它为MB_OK或MB_ICONHAND。函数调用返回值参见SDK中MessageBox函数说明。
     当MessageBox函数被调用时,MB_SETFOREGROUND标识会被添加到Style参数中,但如果第一次调用失败了的话,MessageBox函数会被再次调用,这次MB_SYSTEMMODAL 标识会被添加到Style参数中。MB_SYSTEMMODAL同MB_ICONHAND标识结合后会忽略内存状况来显示消息对话框。当内存确实不足时,函数不会显示任何东西,然而它仍然会返回MessageBox函数调用结果。所以我们可以根据返回值判断函数是否调用成功了。
空间不足对话框

                                                                                                            图2.32
      另一个资源相关的函数是SHHandleDiskFull,它会显示磁盘不足的信息对话框(如图2.32)。我们可以在由于没有足够磁盘空间时导致程序无法运行的条件下调用这个函数,调用后,如果回收站中有什么东西没有删除的话,对话框允许用户清空回收站来释放磁盘空间。它在Shell32.dll中的索引值为185,函数的定义如下:
     procedure SHHandleDiskFull(Owner: HWND; Drive: UINT); stdcall;
     Drive参数用于指定以0为底的驱动器盘符。0代表A:/,1代表B:/,依此类推。这个函数的应用比较困难,因为当回收站中没有任何东西时对话框不会显示,同时也没有任何返回值表示对话框是否显示,还无法知道用户的操作,比如它是否真的清空了。看起来比较可行的应用只能是程序自行监视磁盘剩余空间,只是使用这个对话框作为一个快速修复的工具。
一般外壳消息对话框
     ShellMessageBox函数仅仅是一个对MessageBox函数的简单封装函数,它允许使用字符串资源标识符或标准的以null结尾的字符串,同时还允许加入支持格式化ForamtMessage函数的控制符。ShellMessageBox函数在Shell32.dll中的索引值为183:
     function ShellMessageBoxA(Module: THandle; Owner: HWND;
       Text: PChar; Caption: PChar; Style: UINT;
       Parameters: array of Pointer): Integer; cdecl;
     更确切地说这个函数应该叫ShellMessageBoxA因为它只支持ANSI字符串,还有一个UNICODE的版本的函数ShellMessageBoxW,它的索引值为182,但它只在Windows NT上才有,函数定义如下:
     function ShellMessageBoxW(Module: THandle; Owner: HWND;
      Text: PWideChar; Caption: PWideChar; Style: UINT;
       Parameters: array of Pointer): Integer; cdecl;
     Module参数是提供字符串资源的模块句柄,句柄可以用GetModuleHandle函数获得。顾名思义Text 参数指向一个要显示在对话框中的文本,它也可以是资源字符串ID,文本中可以包括格式控制序列,它将会被在Parameters中提供的额外字符串替代。控制符格式为“%#”,其中“#”是额外字符串在参数中的位置,比如“%1”将被第一个Parameters数组中的字符串元素替代,“%3”将会被第三个元素替代,依此类推。Caption参数指向对话框标题文本,同样它也可以是资源ID,如果参数为nil,Owner指定的窗口标题将被用于对话框标题。Style参数是由位掩码标识组成的,可以设置成MessageBox函数支持的MB_XXX常数的组合。返回值同MessageBox完全一样。
     对于这个函数很重要的一点是微软公司使用cdecl来输出这个函数而不是通常的 stdcall。此外,Parameters参数使用了C语言中的可变参数列表,这意味着这个函数不是语言无关的,这使得调用起来非常麻烦,因为Delphi并不直接支持cdecl和可变参数列表。为了解决这个问题,Parameters参数被映射为一个动态指针列表。同时我们还需要使用嵌入式汇编来建立cdecl样式的堆栈。由于动态指针列表的性质,我们必须至少指定一个指针值。如果不想指定要替代的字符串,简单设置为nil就可以了。
实现组件化
封装外壳对话框为VCL组件是非常简单的工作。只是对调用SHBrowseForFolder函数的浏览目录对话框和运行文件对话框的封装稍微麻烦一些,因为这个函数内部集成有消息通知机制。其他就很简单直接了,通常这类对话框组件首先需要实现一个构造函数Constructor来初始化数据,并提供一些属性直接对应于API函数调用所需要的参数,以及Execute方法在运行时显示对话框。下面给出一个对查找文件对话框的封装,由于它实在是太简单了,所以这里只给出实现代码而不加以说明。
unit FindDialogs;
interface
uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ShlObj;
type
   TFindFilesDialog = class(TComponent)
   private
     { Private declarations }
   protected
     { Protected declarations }
   public
     { Public declarations }
     function Execute: Boolean;
     function ExecuteEx(pidlRoot, pidlSavedSearch: PItemIDList): Boolean;
   published
     { Published declarations }
end;
type
   TFindComputerDialog = class(TComponent)
private
     { Private declarations }
   protected
     { Protected declarations }
   public
     { Public declarations }
     function Execute: Boolean;
     function ExecuteEx(pidlRoot, pidlSavedSearch: PItemIDList): Boolean;
   published
     { Published declarations }
   end;
procedure Register;
implementation
function SHFindFiles(pidlRoot: PItemIDList; pidlSavedSearch: PItemIDList): Boolean;
stdcall; external 'Shell32.dll' index 90;
function SHFindComputer(pidlRoot: PItemIDList; pidlSavedSearch: PItemIDList): Boolean;
stdcall; external 'Shell32.dll' index 91;
function TFindFilesDialog.Execute: Boolean;
begin
Result := SHFindFiles(nil,nil);
end;
function TFindFilesDialog.ExecuteEx(pidlRoot, pidlSavedSearch: PItemIDList): Boolean;
begin
Result := SHFindFiles(pidlRoot,pidlSavedSearch);
end;
function TFindComputerDialog.Execute: Boolean;
begin
Result := SHFindComputer(nil,nil);
end;
function TFindComputerDialog.ExecuteEx(pidlRoot, pidlSavedSearch: PItemIDList): Boolean;
begin
Result := SHFindComputer(pidlRoot,pidlSavedSearch);
end;
procedure Register;
begin
   RegisterComponents('Dialogs', [TFindFilesDialog,TFindComputerDialog]);
end;
end.
     注意在封装API为组件的过程中,要把所有的标识类型常数转化为枚举和集合类型,这有两个目的,第一是它可以使编译器执行强制类型检验,同时避免我们去记那些实际的数字,为了避免在代码中反复转换枚举类型为对应的Windows常数,这里实现了转化函数来进行简化处理。另外,组件中最重要的部分毫无疑问是Execute方法,因为在这里我们将调用底层的API函数。
     正如上面提到的,外壳对话框中最复杂的要属浏览文件夹对话框了,下面将详细解释对它的封装。SHBrowseForFolder API函数既包括了一个复杂的初始化过程,同时还包括了一个回调机制需要去实现。另外,这个函数中最困难的部分是它需要处理可怕的PIDL,也就是Delphi中定义的PItemIDList记录类型(在shlobj单元中定义)。从简单的意义上来说,PIDL实际上就是外壳版本的DOS路径。每个文件系统对象都可以用PIDL或路径来表示。然而,许多文件系统对象,像控制面板只能用PIDL来表示。因为是基于PIDL的,所以浏览文件夹对话框允许我们选择非文件的外壳对象。注意PIDL可以被认为是由外壳提供的一个指针,它指向的绝对数据不应该以任何方式修改。下面实现的组件将进行转化文件系统路径和PIDL之间的相互转化,另外还需要知道,外壳为PIDL分配的内存空间不能使用Delphi提供的FreeMem函数,而要使用下面提供的FreePIDL函数。
     所有浏览文件夹对话框显示前都需要指定根文件夹,但由于浏览文件夹对话框可以显示非文件对象,而这些对象没有相应的路径名称,那么我们如果想指定它的根目录为桌面对象的话该如何做呢?这时我们只能使用PIDL类型的参数来指定根文件夹,现在又产生了一个新问题,PIDL不是普通的字符串形式,那么如何在设计时也能指定PIDL。
     解决办法是使用下面这个枚举类型的属性TkbsdSpecialLocation,枚举数据对应一系列的指定代表特殊外壳对象的Windows常数,比如对应于控制面板、网络邻居和桌面等。SHGetSpecialFolderLocation API可以通过这些常数来获得对应外壳对象的PIDL。通过这些枚举类型,开发者就可以在设计时很简单地指定这些特殊的非文件对象。另外设定TkbsdSpecialLocation为kbsdPath,将允许用户输入更常见的文件路径作为起始路径,下面是它的实现部分代码:
     {如果RootFolder属性指定使用Path属性作为根目录
     获得路径的PIDL然后设定BrowseInfo记录的pidlRoot参数}
     if (Self.RootFolder = kbsdPath) then
     begin
       BrowseInfo.pidlRoot := GetPIDLFromPath(Self.RootPath);
     end   { if }
     { 如果起始路径是桌面,只需要设为nil}
     else if (Self.RootFolder = kbsdDesktop) then
      begin
         BrowseInfo.pidlRoot := nil;
       end   { else if }
     { 如果根目录为特殊的外壳对象,就获取相应的PIDL}
     else
     begin
       BrowseInfo.pidlRoot :=
       GetSpecialLocationPIDL(Self.RootFolder);
     end; { else }
     一旦根目录的问题解决了,剩下的初始化部分相当直接,我们只要简单填写TBrowseInfo记录中剩下的部分,然后调用函数就可以了。接下来的问题是如何实现回调函数,实现代码如下:
     function BrowseForFolderCallback(DialogHandle: HWND;
       MessageID: UINT; PIDL: LPARAM; ComponentPointer: LPARAM):
       Integer; stdcall;
     var
       DialogComponent: TkbBrowseForFolderDialog;
     begin
       if (ComponentPointer <> 0) then
       begin
        DialogComponent := TkbBrowseForFolderDialog(ComponentPointer);
        { 根据激活回调的消息让控件来处理相应的事件}
        case (MessageID) of
          BFFM_INITIALIZED:
          DialogComponent.Initialize(DialogHandle);
          BFFM_SELCHANGED:
          TkbBrowseForFolderDialog(DialogComponent).Change(
          DialogHandle, PItemIDList(PIDL));
        end;
      end;
      { 总返回0 }
      Result := 0;
     end;
     回调函数的唯一目的是激活组件的事件处理方法,传递需要的数据。通常使用回调中的用户定义参数传递指向我们组件的指针,这很重要,因为回调不是一个对象成员函数,因此无法使用Self变量来引用,另外这个对象外部函数需要调用组件的事件处理函数,但组件的事件处理过程定义protected,这通常是无法从外部调用的,但Delphi有一个特性,就是如果函数和对象实现在一个单元中的话,它就可以直接调用对象的保护方法。
     值得一提的是回调函数还使得我们可以修改系统缺省提供的浏览对话框标题,本来 SHBrowseForFolder函数没有办法直接指定对话框的标题,但我们可以在BFFM_INITIALIZED消息回调后保存窗口句柄,这时就可以使用SetWindowText API 函数来改变对话框的标题了,利用窗口句柄还可以做很多其他的工作,比如改变大小、改变文本、发送消息、接收通知消息等等。
结论
     这些标准对话框可以把程序同外壳更好地结合,我们可以使用他们来节约时间,同时使程序显得更加专业。对他们组件化可以简化应用,提供一个更方便的应用界面。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值