2014년 10월 29일 수요일

CMFCShellTreeCtrl의 EnumObjects 구현

CMFCShellTreeCtrl를 사용할 때, 기본적으로 모든 폴더 및 가상폴더(제어판, 라이브러리, 휴지통 등등)들이 보인다.



헌데 폴더만 보고 싶을 경우에는 EnumObjects를 구현해주어야 한다. 우선 CMFCShellTreeCtrl를 상속받아 Class를 하나 만들고, 헤더파일에 아래 내용을 추가해준다.



virtual HRESULT EnumObjects(HTREEITEM hParentItem, LPSHELLFOLDER pParentFolder, LPITEMIDLIST pidlParent);



그리고 소스파일에 내용을 구현해준다.



HRESULT CGYMFCShellTreeCtrl::EnumObjects(HTREEITEM hParentItem, LPSHELLFOLDER pParentFolder, LPITEMIDLIST pidlParent)

{

LPENUMIDLIST pEnum = NULL;

CMainFrame *pMainFrame = ( CMainFrame* )AfxGetMainWnd();    //CMainFrame에 만든 함수를 사용하기 위해.



HRESULT hr = pParentFolder->EnumObjects(NULL, m_dwFlags, &pEnum);

if (FAILED(hr) || pEnum == NULL)

{

return hr;

}



LPITEMIDLIST pidlTemp;

DWORD dwFetched = 1;



//////////////////////////////////////////////////////////////////////////

//

// 보여주지 말아야 할 항목들을 검색해온다.

//

//////////////////////////////////////////////////////////////////////////

ULONG nEaten=0;

ULONG nCPAttrib = 0;



LPITEMIDLIST pidl_Recycle = NULL, pidl_Control1 = NULL, pidl_Control2 = NULL, pidl_Network = NULL, pidl_Library = NULL;

SHGetFolderLocation( NULL, CSIDL_BITBUCKET, NULL, 0, &pidl_Recycle ); //휴지통

//CSIDL_CONTROLS -> 이 항목은 제어판이 맞기는 한데, 모든 제어판 항목이라고 등록되어있다. 제어판과는 다른것으로 보인다.

hr = pParentFolder->ParseDisplayName(NULL, NULL, _T("::{26EE0668-A00A-44D7-9371-BEB064C98683}"), &nEaten, &pidl_Control1, &nCPAttrib); //control panel. vista

SHGetFolderLocation( NULL, CSIDL_NETWORK, NULL, 0, &pidl_Network ); //네트워크



nEaten = 0; nCPAttrib = 0;

hr = pParentFolder->ParseDisplayName(NULL, NULL, _T("::{031E4825-7B94-4dc3-B131-E946B44C8DD5}"), &nEaten, &pidl_Library, &nCPAttrib); //라이브러리



nEaten = 0; nCPAttrib = 0;

hr = pParentFolder->ParseDisplayName(NULL, NULL, _T("::{21EC2020-3AEA-1069-A2DD-08002B30309D}"), &nEaten, &pidl_Control2, &nCPAttrib); //제어판 xp.



DWORD dwlParam = SHCIDS_CANONICALONLY;

// Enumerate the item's PIDLs:

while (SUCCEEDED(pEnum->Next(1, &pidlTemp, &dwFetched)) && dwFetched)

{

if( ILIsEqual( pidlTemp, pidl_Recycle ) || ILIsEqual( pidlTemp, pidl_Network ) )

continue;



if( pMainFrame->isWinVerLargerThan6() )

{

if( ILIsEqual( pidlTemp, pidl_Control1 ) || ILIsEqual( pidlTemp, pidl_Library ) )

continue;

}

else

{

if( ILIsEqual( pidlTemp, pidl_Control2 ) )

continue;

}

//if( ILIsEqual( pidlTemp, pidl_Recycle ) || ILIsEqual( pidlTemp, pidl_Network ) || ILIsEqual( pidlTemp, pidl_Control1 ) || ILIsEqual( pidlTemp, pidl_Library ) || ILIsEqual( pidlTemp, pidl_Control2 ) )

// continue;



TVITEM tvItem;

ZeroMemory(&tvItem, sizeof(tvItem));



// Fill in the TV_ITEM structure for this item:

tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;



// AddRef the parent folder so it's pointer stays valid:

pParentFolder->AddRef();



// Put the private information in the lParam:

LPAFX_SHELLITEMINFO pItem = (LPAFX_SHELLITEMINFO)GlobalAlloc(GPTR, sizeof(AFX_SHELLITEMINFO));

ENSURE(pItem != NULL);



CDSSFTPApp* pApp = (CDSSFTPApp*)AfxGetApp();

pItem->pidlRel = pidlTemp;

pItem->pidlFQ = pApp->GetShellManager()->ConcatenateItem(pidlParent, pidlTemp);



pItem->pParentFolder = pParentFolder;

tvItem.lParam = (LPARAM)pItem;



CString strItem = OnGetItemText(pItem);

tvItem.pszText = strItem.GetBuffer(strItem.GetLength());

tvItem.iImage = OnGetItemIcon(pItem, FALSE);

tvItem.iSelectedImage = OnGetItemIcon(pItem, TRUE);



// Determine if the item has children:

//DWORD dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_DISPLAYATTRMASK | SFGAO_CANRENAME | SFGAO_FILESYSANCESTOR;

DWORD dwAttribs = SFGAO_STREAM;



pParentFolder->GetAttributesOf(1, (LPCITEMIDLIST*) &pidlTemp, &dwAttribs);

if( dwAttribs & SFGAO_STREAM )    //ZIP또는 cab등등의 압축파일.

continue;



dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_DISPLAYATTRMASK | SFGAO_CANRENAME | SFGAO_FILESYSANCESTOR;    //나머지 종류의 파일 전부 읽어온다.(내코드에서는 기본적인 속성이 폴더만 읽어오게 되어 있음). Flag설정을 통해 파일도 가져올 수 있음.



pParentFolder->GetAttributesOf(1, (LPCITEMIDLIST*) &pidlTemp, &dwAttribs);

tvItem.cChildren = (dwAttribs & (SFGAO_HASSUBFOLDER | SFGAO_FILESYSANCESTOR));



// Determine if the item is shared:

if (dwAttribs & SFGAO_SHARE)

{

tvItem.mask |= TVIF_STATE;

tvItem.stateMask |= TVIS_OVERLAYMASK;

tvItem.state |= INDEXTOOVERLAYMASK(1); //1 is the index for the shared overlay image

}



// Fill in the TV_INSERTSTRUCT structure for this item:

TVINSERTSTRUCT tvInsert;



tvInsert.item = tvItem;

tvInsert.hInsertAfter = TVI_LAST;

tvInsert.hParent = hParentItem;



InsertItem(&tvInsert);

dwFetched = 0;

}



//////////////////////////////////////////////////////////////////////////

//

// ParseDisplayName를 이용하여 ITEMIDLIST를 가져왔을 경우, CoTaskMemFree를 호출하여 해제를 해야한다.

//

//////////////////////////////////////////////////////////////////////////

if( pidl_Control1 != NULL )

CoTaskMemFree( pidl_Control1 );



if( pidl_Library != NULL )

CoTaskMemFree( pidl_Library );



if( pidl_Control2 != NULL )

CoTaskMemFree( pidl_Control2 );



pEnum->Release();

return S_OK;

}



ParseDisplayName을 통해서 ITEMIDLIST를 가져오는게 있고, SHGetFolderLocation를 통해서 가져오는게 있는데, SHGetFolderLocation를 통해서 가져올 경우, 매칭이 안되는 경우가 좀 있는 것으로 보인다. ParseDisplayName의 경우 CLSID를 갖고 ITEMIDLIST를 가져오는데, CLSID를 정확히 알아야 할 필요가 있다.



나는 레지스트리의 CLSID 위치를 뒤져서 확인했다. 검색으로 비슷한거 하나씩 입력하면서 GetDisplayNameOf로 찍어보면서 이름이 어떻게 나오나 확인을 했다.



윈도우 비스타부터 존재하는 '라이브러리'의 경우엔 XP에는 없다. 제어판의 경우 CLSID가 XP와 비스타(윈도우7)이 달라서 따로 찾아봤다.



EnumObject 소스 전체를 이해하지는 못하겠고, 중간에 어떻게 변경해야 원하는 내용만 출력하는지 확인을 했는데....테스트가 좀 더 필요할 것으로 보인다.

댓글 없음: