呼叫TOpenPictureDialog選擇大量圖片時候發現,回傳的Files里面只有前面的一千多個檔案,實際只有我選擇檔案的一半左右。開始以為是CB的BUG,于是把OpenDialog原始碼中的MultiSelectBufferSize改大,測驗沒效果。網上有人說,GetOpenFileName的Ansi版本有32K記憶體限制,Unicode版本沒有限制。實際我的程式已經是Unicode的了。Ansi的API只是進行的字符轉換,之后還是呼叫Unicode版本的API,所以這個方法行不通。
在一個博客里面介紹,可以通過Shell介面自己獲取選擇的檔案,有參考代碼,這就好辦了,有了解決問題的方向。
那怎么加入CB中呢,直接呼叫GetOpenFileName? 重寫TOpenDialog?都太麻煩了,打開原始碼,發現有TOpenDialog有個函式void __fastcall GetFileNames(tagOFNW &OpenFileName)是用來取得所選檔案的,正好就改它了。這里給出改好的代碼,加入工程原始碼中就可以。
#include <shlobj.h>
#ifndef WM_GETISHELLBROWSER
#define WM_GETISHELLBROWSER (WM_USER+7)
#endif
#define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
BOOL ILIsFile(LPCITEMIDLIST pidl)
{
BOOL bRet = FALSE;
LPCITEMIDLIST pidlChild = NULL;
IShellFolder* psf = NULL;
HRESULT hr = SHBindToParent(pidl, IID_IShellFolder, (LPVOID*) & psf, &pidlChild);
if (SUCCEEDED(hr) && psf)
{
SFGAOF rgfInOut = SFGAO_FOLDER | SFGAO_FILESYSTEM;
hr = psf->GetAttributesOf(1, &pidlChild, &rgfInOut);
if (SUCCEEDED(hr))
{
if ((~rgfInOut & SFGAO_FOLDER) && (rgfInOut & SFGAO_FILESYSTEM))
{
bRet = TRUE;
}
}
psf->Release();
}
return bRet;
}
void __fastcall TOpenDialog::GetFileNames(tagOFNW &OpenFileName)
{
static TStringList *lst = new TStringList;
FORMATETC fmte;
STGMEDIUM stgmedium;
LPITEMIDLIST pidlFull = NULL;
IShellView * pIShellView = NULL;
LPMALLOC pMalloc = NULL;
IDataObject* pIDataObject = NULL;
IShellBrowser* pSB = (IShellBrowser*)SendMessage(GetParent(Handle), WM_GETISHELLBROWSER, 0, 0);
TCHAR szPath[_MAX_PATH];
if (pSB == NULL) //GetFileNames被呼叫兩次,第二次pSB為空, 回傳第一次結果
{
if (lst)
{
FFileName = lst->Count > 0 ? lst->Strings[0] : String("");
FFiles->Assign(lst);
delete lst;
lst = NULL;
}
return;
}
if (lst == NULL)
lst = new TStringList;
ZeroMemory((LPVOID) & fmte, sizeof(STGMEDIUM));
ZeroMemory((LPVOID) & fmte, sizeof(FORMATETC));
fmte.tymed = TYMED_HGLOBAL;
fmte.lindex = -1;
fmte.dwAspect = DVASPECT_CONTENT;
fmte.cfFormat = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
do
{
HRESULT hr = pSB->QueryActiveShellView(&pIShellView);
if (FAILED(hr))
break;
hr = ::SHGetMalloc(&pMalloc);
if (FAILED(hr))
break;
hr = pIShellView->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (LPVOID*) & pIDataObject);
if (FAILED(hr))
break;
if (pIDataObject == NULL)
break;
hr = pIDataObject->GetData(&fmte, &stgmedium);
if (FAILED(hr))
break;
LPIDA pida = (LPIDA)GlobalLock(stgmedium.hGlobal);
if (pida)
{
LPCITEMIDLIST pidlFolder = GetPIDLFolder(pida);
for (UINT i = 0; i < pida->cidl; i++)
{
LPCITEMIDLIST pidl = GetPIDLItem(pida, i);
pidlFull = ILCombine(pidlFolder, pidl);
if (ILIsFile(pidlFull))
{
ZeroMemory(szPath, sizeof(TCHAR) * _MAX_PATH);
hr = SHGetPathFromIDList(pidlFull, szPath);
if (SUCCEEDED(hr))
{
lst->Add((szPath));
}
}
pMalloc->Free(pidlFull);
pidlFull = NULL;
}
}
GlobalUnlock(stgmedium.hGlobal);
ReleaseStgMedium(&stgmedium);
}
while (FALSE);
if (pIDataObject) pIDataObject->Release();
if (pIShellView) pIShellView->Release();
if (pMalloc)
{
if (pidlFull) pMalloc->Free(pidlFull);
pMalloc->Release();
}
}
加入代碼后,不管TOpenDialog還是TOpenPictureDialog,打開檔案數量都不再受限制(當然受記憶體限制,也不是無限的)。
在此BS一下微軟的程式員,同時也接受BS,歡迎拍磚。
uj5u.com熱心網友回復:
好!推薦一下。uj5u.com熱心網友回復:
汗!剛提交就被發現,老妖真迅速uj5u.com熱心網友回復:
都好牛啊uj5u.com熱心網友回復:
樓主代碼在 bcb6 中能用嗎?uj5u.com熱心網友回復:
是錯啊,值得收藏了。uj5u.com熱心網友回復:
這個得收藏一下,以后可能用得到.uj5u.com熱心網友回復:
樓主代碼在c++builder2010 中編譯通過;使用:
ListBox1->Items->Clear();
OpenDialog1->Options << ofAllowMultiSelect;
OpenDialog1->InitialDir =L"C:\\Users\\mabao\\AppData\\Local\\Temp\\";
if(OpenDialog1->Execute(this->Handle) )
ListBox1->Items->Assign(OpenDialog1->Files);//將選擇的多個檔案顯示于ListBox組件
StatusBar1->SimplePanel = true;
StatusBar1->SimpleText = ListBox1->Items->Count; //12610 個檔案一個不少
不過我發現,不使用樓主代碼,12610 個檔案好像也一個不少(系統 win7 64位 家庭版)
uj5u.com熱心網友回復:
是錯啊,值得收藏了。uj5u.com熱心網友回復:
不錯,值得收藏uj5u.com熱心網友回復:
寫的還不錯!uj5u.com熱心網友回復:
to mabaoyes在CB6中理論上也可以用的, 只是我沒有安裝CB6測驗.
主要是GetOpenFileName 這個API的問題, Win7下可能修復了.
我的環境是CB2010+XP sp3
uj5u.com熱心網友回復:
哇。。。收藏了、、、uj5u.com熱心網友回復:
好強哦uj5u.com熱心網友回復:
給力啊uj5u.com熱心網友回復:
汗! 只好 BS 自己了uj5u.com熱心網友回復:
非常好!uj5u.com熱心網友回復:
小論壇地址:http://rayyu.5d6d.com/thread-9444-1-1.htmluj5u.com熱心網友回復:
都好牛啊!!!uj5u.com熱心網友回復:
牛啊!uj5u.com熱心網友回復:
謝謝!!!!uj5u.com熱心網友回復:
不錯,好好學習uj5u.com熱心網友回復:
CBXE+XP sp2/sp3測驗都正常,用TOpenDialog添加2300多個檔案到TMemo。uj5u.com熱心網友回復:
好,不錯。uj5u.com熱心網友回復:
寫的還不錯uj5u.com熱心網友回復:
寫的還不錯uj5u.com熱心網友回復:
寫的還不錯!uj5u.com熱心網友回復:
不錯,不錯uj5u.com熱心網友回復:
寫的還不錯!uj5u.com熱心網友回復:
前面說的不太明確,補充下,打開檔案數量限制不是固定的,跟路徑長度有關,看看API宣告就知道了.BOOL GetOpenFileName(LPOPENFILENAME lpofn);
typedef struct tagOFN { // ofn
DWORD lStructSize;
HWND hwndOwner;
HINSTANCE hInstance;
LPCTSTR lpstrFilter;
LPTSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex;
LPTSTR lpstrFile;
DWORD nMaxFile;
LPTSTR lpstrFileTitle;
DWORD nMaxFileTitle;
LPCTSTR lpstrInitialDir;
LPCTSTR lpstrTitle;
DWORD Flags;
WORD nFileOffset;
WORD nFileExtension;
LPCTSTR lpstrDefExt;
DWORD lCustData;
LPOFNHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
} OPENFILENAME;
其中 nMaxFile是存放所有選擇檔案的路徑的記憶體大小.
TOpenFile呼叫了這個函式, 在檔案多選時候, 給出的nMaxFile 是65519位元組.所以在XP以以前的系統中,一定有數量限制的. 在Vista及以后系統, 雖然也呼叫了這個函式, 但已經不使用lpstrFile了,也是使用直接從Shell查找檔案的方法.
uj5u.com熱心網友回復:
版主都推薦了啊,學習一下uj5u.com熱心網友回復:
43樓的解釋很好。uj5u.com熱心網友回復:
多謝樓主!你解決了我的一大難題啊!轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/107123.html
標籤:VCL組件使用和開發
