탐색기의 왼쪽 폴더 리스트처럼 폴더 트리를 만드는 방법.
MFC의 CTreeView로 작성 함. (자료찾기 힘들다. VC++ 책들은 줄줄이 절판중..)
 
 
 
결과 화면
 
 
 
"CTreeView" .h 헤더에 추가한 멤버 함수
bool composeTreeList(void);
int GetItemIcon(LPITEMIDLIST lpi, UINT flags);
static int CALLBACK CompareItemForSorting(LPARAM, LPARAM, LPARAM);​
 

"CTreeView" .cpp 소스
void CFolderListView::OnInitialUpdate()
{
	CTreeView::OnInitialUpdate();

	// TODO: You may populate your TreeView with items by directly accessing
	//  its tree control through a call to GetTreeCtrl().

	//폴더 정보 출력
	composeTreeList();

	//루트 트리 펼침
	HTREEITEM hRootItem = treeCtrl.GetRootItem();
	treeCtrl.Expand(hRootItem, TVE_EXPAND); //펼침

	//특정 함수로 정렬
	TV_SORTCB tvscb;
	tvscb.hParent = hRootItem;
	tvscb.lParam = 0;
	tvscb.lpfnCompare = CompareItemForSorting; //아이템 정렬을 위한 함수

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	treeCtrl.SortChildrenCB(&tvscb);
}

//TreeList 를 구성함
bool CFolderListView::buildFolderList(void)
{
	bool bRet = true;;
	HRESULT hr;

	HTREEITEM hPrevItem = NULL;
	LPSHELLFOLDER lpsf = NULL;

	//트리 초기화 (아이템 모두 삭제)
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	treeCtrl.DeleteAllItems();


	//root 아이템으로 바탕화면 추가
	CString strDesktop = _T("바탕 화면");
	LPITEMIDLIST pDesktopIDList = NULL;
	SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pDesktopIDList);

	TV_INSERTSTRUCT tviRoot;
	tviRoot.hParent = TVI_ROOT; //root 레벨로 추가함
	tviRoot.hInsertAfter = TVI_FIRST; //맨 위에 추가

	tviRoot.item.pszText = strDesktop.GetBuffer(); //이름
	tviRoot.item.cchTextMax = strDesktop.GetLength(); //최대길이
	tviRoot.item.iImage = GetItemIcon(pDesktopIDList, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); //아이콘
	tviRoot.item.iSelectedImage = GetItemIcon(pDesktopIDList, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON); //선택시 아이콘
	tviRoot.item.lParam = (LPARAM)tviRoot.item.pszText;
	tviRoot.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; //사용할 멤버변수

	HTREEITEM hRootItem = treeCtrl.InsertItem(&tviRoot);

	try
	{
		//shell 메모리 할당
		LPMALLOC lpMalloc = NULL;
		if (FAILED(CoGetMalloc(1, &lpMalloc)))
			throw CString(_T("CoGetMalloc error"));

		//데스크탑 폴더에 대한 인터페이스 획득
		hr = SHGetDesktopFolder(&lpsf);
		if (FAILED(hr))
			throw CString(_T("SHGetDesktopFolder error"));


		//아이템 enum 획득
		IEnumIDList* pEnumIDList = NULL;
		hr = lpsf->EnumObjects(::GetParent(m_hWnd), SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList);
		if (FAILED(hr))
			throw CString(_T("lpsf->EnumObjects error"));


		LPITEMIDLIST pItemIDList; //아이템을 담을 변수
		ULONG ulFetched; //pItemIDList의 길이 정보를 받을 변수

		//아이템 enum순회하며 TreeView에 등록 : pItemIDList에 아이템을 담음
		while (pEnumIDList->Next(1, &pItemIDList, &ulFetched) == S_OK)
		{

			//아이템 속성 획득
			ULONG attrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER; //획득하고픈 속성 : 하위 폴더 있는지, 폴더 인지
			hr = lpsf->GetAttributesOf(1, (const struct _ITEMIDLIST**)&pItemIDList, &attrs); //attrs엔 아이템이 갖고 있는 모든 속성이 세팅됨
			if (hr != S_OK)
				throw CString(_T("lpsf->GetAttributesOf"));


			//속성별 설정
			if (attrs & (SFGAO_HASSUBFOLDER | SFGAO_FOLDER)) //하위 폴더가 있거나 폴더인 경우
			{
				if (attrs & SFGAO_FOLDER) //폴더인 경우
				{
					//아이템 이름 구하기
					STRRET str; //아이템 이름이 담길 구조체
					hr = lpsf->GetDisplayNameOf(pItemIDList, SHGDN_NORMAL, &str); //아이템 이름 조회
					if (hr != S_OK)
						throw CString(_T("lpsf->GetDisplayNameOf error"));

					TCHAR displayName[MAX_PATH]; //아이템 이름을 담을 변수
					switch (str.uType) //획득한 문자열(폴더이름)에 대한 타입
					{
					case STRRET_CSTR: //문자열이 cStr 멤버에 담겨있는 경우
						MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str.cStr, strlen(str.cStr), displayName, MAX_PATH);
						break;

					case STRRET_OFFSET: //문자열의 시작 주소가 별도로 지정된 경우(uOffset멤버변수)
					//lstrcpy(displayName, (LPTSTR)pItemIDList + str.uOffset);
						MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)(pItemIDList + str.uOffset), strlen((const char*)pItemIDList + str.uOffset), displayName, MAX_PATH);
						break;

					case STRRET_WSTR: //문자열이 pOleStr 멤버에 담겨있는 경우 :유니코드환경이면 이것만 사용됨
						lstrcpy(displayName, str.pOleStr);
						break;

					default:
						throw CString(_T("lpsf->GetDisplayNameOf error"));
					}

					//lParam
					TCHAR* lParam = (TCHAR*)lpMalloc->Alloc(sizeof(TCHAR) * (lstrlen(displayName) + 1)); //lParam을 위한 메모리 할당
					lstrcpy(lParam, displayName); //값 할당


					//TreeView에 추가할 아이템의 속성 설정
					TV_ITEM tvi;
					tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN; //사용할 멤버변수 지정
					tvi.pszText = displayName; //폴더 이름
					tvi.cchTextMax = MAX_PATH; //폴더 이름 최대 길이
					tvi.cChildren = (attrs & SFGAO_HASSUBFOLDER) ? 1 : 0; //하위 폴더 존재 여부 설정
					tvi.iImage = GetItemIcon(pItemIDList, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); //아이콘
					tvi.iSelectedImage = GetItemIcon(pItemIDList, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON); //펼칠때 아이콘
					tvi.lParam = (LPARAM)lParam; //정렬할때 사용하기 위한 lParam


					//TreeView에 추가를 위한 변수
					TV_INSERTSTRUCT tvins;
					tvins.item = tvi; //속성 변수
					tvins.hParent = hRootItem; //root 아이템 아래 추가
					tvins.hInsertAfter = hPrevItem; //hPrevItem 다음에 추가

					//TreeView에 추가
					hPrevItem = treeCtrl.InsertItem(&tvins); //다음 item추가를 위해 현재 아이템 저장
				}
			}
			lpMalloc->Free(pItemIDList);
			pItemIDList = NULL;
		}
	}
	catch (CString& errorMsg)
	{
		AfxMessageBox(errorMsg);
		bRet = false;
	}

	if (lpsf != NULL) lpsf->Release();

	return bRet;
}

//아이템의 아이콘을 구하기 위한 함수
int CFolderListView::GetItemIcon(LPITEMIDLIST lpi, UINT flags)
{
	SHFILEINFO sfi;
	SHGetFileInfo((LPCTSTR)lpi, 0, &sfi, sizeof(SHFILEINFO), flags);
	return sfi.iIcon;
}

//아이템 정렬을 위한 compare 함수
int CALLBACK CFolderListView::CompareItemForSorting(LPARAM lparam1, LPARAM lparam2, LPARAM lparamSort)
{
	/*
	   CString str1 = (TCHAR*)lparam1;
	   CString str2 = (TCHAR*)lparam2;

	return str1.Compare(str2);
	*/
	return 0;
}