分享
 
 
 

Window SubClassing另类运用(之二)

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Window SubClassing另类运用(之二)

你大概已经熟悉通用对话框(打开/保存文件,选择字体/颜色,以及查找和替换)的使用,不过你是否了解如何调用“选择文件夹”对话框呢?如果答案是否的话,你可以先看看一个简单的例子,籍以做个热身。如果你自认为已经了解它的话,可以跳过下面这一段。

要调用“选择文件夹”对话框,和其他通用对话框所使用的方法非常类似:一个结构(

BROWSEINFO)加一个函数(SHBrowseForFolder)即可。请看代码:

procedure TForm1.Button2Click(Sender: TObject);

var

bi : BROWSEINFO;

szDisplay : array[0..MAX_PATH] of char;

pidl : PItemIDList;

str : string;

begin

with bi do begin

hwndOwner := Handle;

pidlRoot := nil;

pszDisplayName := szDisplay;

lpszTitle := 'Select a Directory';

ulFlags := BIF_RETURNONLYFSDIRS or BIF_STATUSTEXT;

lpfn := @BrowseCallback;

lParam := 0;

end;

pidl := SHBrowseForFolder(bi);

if pidl<>nil then begin

SetLength(str, MAX_PATH);

SHGetPathFromIDList(pidl, PChar(str));

str := PChar(str);

Caption := str;

CoTaskMemFree(pidl);

end;

end;

SHBrowseForFolder返回一个LPITEMIDLIST,你需要手动将它转换成一个实际的文件路径(除非你选择的是回收站和控制面板这样的虚拟路径)。最后还要用Shell API把获得的pidl释放。上述代码中,

BrowseCallback是一个自己编写的回调函数,如果不想处理回调的话,可以将它设置为nil。我还是处理了这个函数,因为我需要它的一些功能,如下:

function BrowseCallback(AWnd:HWND; uMsg:UINT; lp, lpData:LPARAM):Integer; stdcall;

var

strPath : string;

pidl : PItemIDList;

begin

case uMsg of

BFFM_SELCHANGED:

begin

pidl := PItemIDList(lp);

if pidl<>nil then begin

SetLength(strPath, MAX_PATH);

SHGetPathFromIDList(pidl, PChar(strPath));

strPath := PChar(strPath);

strPath := 'folder Selected: ' + strPath;

SendMessage(AWnd, BFFM_SETSTATUSTEXT, 1, LongInt(PChar(strPath)));

end;

end;

end;

Result := 0;

end;

BrowseCallback函数可以接受一些通知消息,例如上面列出的BFFM_SELCHANGED,当用户在文件夹列表中选择了另外一个项目的时候就会触发,程序员可以用另外一些消息(如BFFM_SETSTATUSTEXT)更新对话框其他相应的部分。

对SHBrowseForFolder的介绍说这么多也就足够了。不过,我对于这样单调的界面并不满意。一个最直接的想法就是:希望在对话框中添加一个列表,其中列出一些常用的文件夹供用户选择,而不需要每次都在“庭院深深”的层次树中一次再一次的Click。这又是一个使用SubClass的好地方。还记得在本文的系列之一中我提到的吗?要使用SubClass技术,充分必要条件就是获得一个窗口的句柄。非常幸运,这里我们有很简单的办法能够得到这个句柄,因为对话框初始化成功后会向上述的回调函数发送BFFM_INITIALIZED通知,我们的SubClass工作就在这里完成。

在上述的

BrowseCallback函数中添加如下的Message Dispatcher:

case uMsg of

BFFM_INITIALIZED:

begin

OldBrowseProc := TWindowProc(GetWindowLong(AWnd, GWL_WNDPROC));

SetWindowLong(AWnd, GWL_WNDPROC, LongInt(@NewBrowseProc));

AdjustDlg(AWnd);

end;

其中,OldBrowseProc是在implementation部分声明的变量:

var

OldBrowseProc : TWindowProc = nil;

而NewBrowseProc和AdjustDlg都是自己编写的函数,它们都比较长,我将分段讲述它们的内容。

先来看AdjustDlg的工作。它的任务是向对话框中添加一个组合框(Combo Box),并且向其中添加几个项目。听起来很简单,不过有许多琐碎的工作必须要做。因为我们是在对系统定义的窗口进行SubClass,所以VCL在这里基本上帮不上什么忙:我们必须大量使用API。

procedure AdjustDlg(AWnd:HWND);

var

wnd : HWND;

wndCombo : HWND;

rc : TRect;

Found : Boolean;

ClassName : array[0..80] of char;

SaveRect : TRect;

OldStyle : integer;

begin

// Find the TreeView first

wnd := GetWindow(AWnd, GW_CHILD);

Found := False;

while IsWindow(wnd) do begin

GetClassName(wnd, ClassName, 80);

if lstrcmpi(ClassName, 'SysTreeView32')=0 then begin

Found := True;

Break;

end;

wnd := GetWindow(wnd, GW_HWNDNEXT);

end;

if not Found then Exit;

为了能够让插入的ComboBox和其他窗口控件的布局协调一致,首先需要找到用来显示文件夹的TreeView窗口。我的计划是:让ComboBox占据TreeView原来的位置(当然它的高度要比TreeView小得多),然后,包括TreeView在内的其他窗口依次下移。下面是实现代码:

// Add combo Box and move other controls down

GetWindowRect(wnd, rc);

ScreenToClient(AWnd, rc.TopLeft);

ScreenToClient(AWnd, rc.BottomRight);

wndCombo := CreateWindow('COMBOBOX', '',

WS_CHILD or WS_VISIBLE or CBS_DROPDOWNLIST or CBS_OWNERDRAWFIXED or CBS_HASSTRINGS,

rc.Left, rc.Top,

rc.Right-rc.Left, rc.Bottom-rc.Top,

AWnd, HMENU(IDC_COMBO),

HInstance, nil);

SendMessage(wndCombo, WM_SETFONT,

SendMessage(AWnd, WM_GETFONT, 0, 0),

1);

OldStyle := GetWindowLong(wnd, GWL_STYLE);

SetWindowLong(wnd, GWL_STYLE, OldStyle or TVS_SHOWSELALWAYS);

SaveRect := rc;

wnd := GetWindow(AWnd, GW_CHILD);

while IsWindow(wnd) do begin

GetWindowRect(wnd, rc);

ScreenToClient(AWnd, rc.TopLeft);

ScreenToClient(AWnd, rc.BottomRight);

if (wnd<>wndCombo) and (rc.Top>=SaveRect.Top) then

SetWindowPos(wnd, HWND_NOTOPMOST, rc.Left, rc.Top+40, 0, 0, SWP_NOSIZE or SWP_NOZORDER);

wnd := GetWindow(wnd, GW_HWNDNEXT);

end;

GetWindowRect(AWnd, rc);

SetWindowPos(AWnd, HWND_NOTOPMOST, 0, 0, rc.Right-rc.Left, rc.Bottom-rc.Top+40, SWP_NOMOVE or SWP_NOZORDER);

如果你过去很少用API写程序,那么这些代码可能让你看得有点头晕。基本上上述程序完成如下的工作:

(1)计算TreeView在窗口中的位置;

(2)建立ComboBox窗口,并基于TreeView的位置将它放置到合理的地方;

(3)将ComboBox的字体设置为和整个窗体的字体相同(这一步是必要的,否则显示的效果会很难看);

(4)为TreeView的窗口风格添加TVS_SHOWSELALWAYS位,从而在焦点移动到ComboBox的时候,仍然可以明显的观察到TreeView中究竟选中了哪个项目;

(5)将窗口中的其他控件依次下移,从而为ComboBox腾出必要的空间;

(6)将窗口本身的高度也略微放大,从而适应添加ComboBox以后的大小。

下一步就是向ComboBox中增加一些表项,否则的话它就是一个鸡肋。我决定添加两种项目:(1)系统中的某些特殊路径,这些路径可以通过SHGetSpecialFolderLocation获得;(2)通常的文件路径。为了让代码简洁一些,我增加了一个辅助函数:

procedure InsertComboItem(hCombo:HWND; const Text:string; data:DWORD);

var

nIndex : integer;

begin

nIndex := SendMessage(hCombo, CB_ADDSTRING, 0, LongInt(PChar(Text)));

SendMessage(hCombo, CB_SETITEMDATA, nIndex, data);

end;

然后在AdjustDlg函数的末尾添加如下的代码:

InsertComboItem(wndCombo, '', CSIDL_DESKTOP);

InsertComboItem(wndCombo, '', CSIDL_FAVORITES);

InsertComboItem(wndCombo, '', CSIDL_STARTMENU);

InsertComboItem(wndCombo, '', CSIDL_DRIVES);

InsertComboItem(wndCombo, 'c:\', 555);

InsertComboItem(wndCombo, 'd:\winnt', 555);

InsertComboItem(wndCombo, 'c:\windows\system', 555);

这里用555并没有什么特别的意义。我本来想用0来标志普通文件夹,但后来发现CSIDL_DESKTOP正是定义为0,所以必须用其他数字来区分。555是我信手写的,你当然可以用别的数字,只要注意不要和预定义的CSIDL常量冲突即可。

AdjustDlg函数的内容就这么多。接下来是NewBrowseProc函数的内容,它的基本结构如下:

function NewBrowseProc(AWnd:HWND; uMsg:UINT; wp:WPARAM; lp:LPARAM):LongInt; stdcall;

begin

Result := 0;

case uMsg of

end;

if Assigned(OldBrowseProc) then

Result := OldBrowseProc(AWnd, uMsg, wp, lp);

end;

在NewBrowseProc中必须处理几条消息。第一个就是用户在ComboBox中选择一项的时候,在TreeView中必须同步跳转到同样的文件夹:

case uMsg of

WM_COMMAND:

if HiWord(wp)=CBN_SELCHANGE then begin

hCombo := GetDlgItem(AWnd, IDC_COMBO);

index := SendMessage(hCombo, CB_GETCURSEL, 0, 0);

if index=CB_ERR then Exit;

csidl := SendMessage(hCombo, CB_GETITEMDATA, index, 0);

if csidl<>555 then begin // csidl

SHGetSpecialFolderLocation(AWnd, csidl, pidl);

SendMessage(AWnd, BFFM_SETSELECTION, 0, LongInt(pidl));

CoTaskMemFree(pidl);

end

else begin // normal Folder

SetLength(str, MAX_PATH);

SendMessage(hCombo, CB_GETLBTEXT, index, LongInt(PChar(str)));

str := PChar(str);

SendMessage(AWnd, BFFM_SETSELECTION, 1, LongInt(PChar(str)));

end;

end;

由于我们添加的ComboBox是一个自绘风格(Owner-Draw)的列表,所以我们还必须处理WM_MEASUREITEM和WM_DRAWITEM消息。WM_MEASUREITEM的处理相对简单,因为对于ComboBox来说项目的宽度无所谓(它自动由ComboBox本身的宽度来决定),我们只需要设置它的高度即可。为了简化起见,我用了硬编码的方法,当然基于系统设置进行仔细的计算也是可行的(而且完全应该):

WM_MEASUREITEM:

begin

pmis := PMEASUREITEMSTRUCT(lp);

if pmis^.CtlType=ODT_COMBOBOX then

pmis^.itemHeight := 20;

end;

其中pmis声明为一个PMEASUREITEMSTRUCT结构指针。

WM_DRAWITEM的处理要复杂的多。因为对于系统级的文件夹,必须从System ImageList中获得它的图标,而且还要从LPITEMIDLIST取得文件夹的名称(不一定是文件路径:比如,c:\windows\desktop在Shell中的名称是“桌面”)。为此我添加了几个辅助函数,用来简化WM_DRAWITEM的处理:

function GetNameFromPIDL(pidl:PItemIDList) : string;

var

sfi : SHFILEINFO;

begin

SHGetFileInfo(PChar(pidl), 0, sfi, sizeof(sfi), SHGFI_DISPLAYNAME or SHGFI_PIDL);

Result := StrPas(sfi.szDisplayName);

end;

function GetPathFromPIDL(pidl:PItemIDList) : string;

var

str : string;

begin

SetLength(str, MAX_PATH);

SHGetPathFromIDList(pidl, PChar(str));

str := PChar(str);

Result := str;

end;

procedure GetSmallIconFromPIDL(pidl:PItemIDList; var iml:HIMAGELIST; var index:integer);

var

sfi : SHFILEINFO;

begin

iml := SHGetFileInfo(PChar(pidl), 0, sfi, sizeof(sfi), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_PIDL);

index := sfi.iIcon;

end;

procedure GetSmallIconFromPath(const Path:string; var iml:HIMAGELIST; var index:integer);

var

sfi : SHFILEINFO;

begin

iml := SHGetFileInfo(PChar(Path), 0, sfi, sizeof(sfi), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);

index := sfi.iIcon;

end;

处理项目绘制的代码其实从原理上来讲非常简单,但是比较琐碎,必须大量调用SendMessage和Shell API接口函数,还包括GDI对象的管理。我不打算仔细解释下面这些代码;这些代码的效果就是在ComboBox中为每一个项目前面添加一个代表其文件夹的图标。

WM_DRAWITEM:

begin

pdis := PDRAWITEMSTRUCT(lp);

if pdis^.CtlType=ODT_COMBOBOX then begin

hCombo := pdis^.hwndItem;

if pdis^.itemID=$ffffffff then Exit;

csidl := DWORD(SendMessage(hCombo, CB_GETITEMDATA, pdis^.itemID, 0));

if (pdis^.itemState and ODS_SELECTED)=ODS_SELECTED then begin

FillRect(pdis^.hDC, pdis^.rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));

SetTextColor(pdis^.hDC, GetsysColor(COLOR_HIGHLIGHTTEXT));

end

else begin

FillRect(pdis^.hDC, pdis^.rcItem, GetSysColorBrush(COLOR_WINDOW));

SetTextColor(pdis^.hDC, GetSysColor(COLOR_WINDOWTEXT));

end;

SetBkMode(pdis^.hDC, TRANSPARENT);

if csidl<>555 then begin // csidl

SHGetSpecialFolderLocation(AWnd, csidl, pidl);

str := GetNameFromPIDL(pidl);

GetSmallIconFromPIDL(pidl, himl, iImage);

ImageList_Draw(himl, iImage, pdis^.hDC, pdis^.rcItem.Left+2, pdis^.rcItem.Top+2, ILD_TRANSPARENT);

Inc(pdis^.rcItem.Left, 20);

DrawText(pdis^.hdc, PChar(str), -1, pdis^.rcItem, DT_SINGLELINE or DT_LEFT or DT_VCENTER);

CoTaskMemFree(pidl);

end

else begin // normal path

SetLength(str, MAX_PATH);

SendMessage(hCombo, CB_GETLBTEXT, pdis^.itemID, LongInt(PChar(str)));

str := PChar(str);

GetSmallIconFromPath(str, himl, iImage);

ImageList_Draw(himl, iImage, pdis^.hDC, pdis^.rcItem.Left+2, pdis^.rcItem.Top+2, ILD_TRANSPARENT);

Inc(pdis^.rcItem.Left, 20);

DrawText(pdis^.hDC, PChar(str), -1, pdis^.rcItem, DT_SINGLELINE or DT_LEFT or DT_VCENTER);

end;

end;

end;

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有