[MFC] 디렉토리 선택 가능한 대화상자(SHBrowseForFolder)


디렉토리만 선택해야하는 경우 아래와 같은 박스가 필요함. 아래 코드에서 약간은 수정하여 사용할 수 있습니다.
플래그 설정으로 디렉토리, 파일 선택을 할 수 있습니다. (파일도 가능함)


[Source Code]
        :
static int CALLBACK BrowseCallbakProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM pData)

{

switch(uMsg)

{

case BFFM_VALIDATEFAILED:

CString strText;

strText.Format("%s", reinterpret_cast<LPTSTR>(lParam));

AfxMessageBox(strText);

break;

}

return 0;

}

 

void CNet_ProgDlg::OnButton6() // 여기는 클레스, 함수 이름이 바꾸어야 합니다.

{

    // TODO: Add your control notification handler code here

   
    ITEMIDLIST *pildBrowse;

char pszPathname[MAX_PATH];

      

LPITEMIDLIST pidl = NULL;

BROWSEINFO bInfo;

ZeroMemory( &bInfo, sizeof(BROWSEINFO) );

      

SHPathToPidl( CSIDL_DESKTOP, &pidl ); // 루트 디렉토리 설정 부분.

bInfo.hwndOwner = GetSafeHwnd();

bInfo.pidlRoot = pidl;

bInfo.pszDisplayName = pszPathname;

bInfo.lpszTitle = "Please Select Directory....";

bInfo.lpfn = BrowseCallbakProc;              
    // 콜백함수가 필요없다면 NULL이나 지움. 위의 static int 함수 필요 없어짐

bInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE ;
    // 디렉토리만 설정가능하도록 하였습니다... 플래그 설정은 맨 아래 참조하세요, 또는 MSDN

 

pildBrowse = ::SHBrowseForFolder(&bInfo);

      

if( pildBrowse != NULL )

{

SHGetPathFromIDList(pildBrowse, pszPathname);

        m_strDownFolder = (LPCTSTR)pszPathname; // 선택한 폴더 또는 파일… 이 부분을 목적에 맞게 수정.

        UpdateData(FALSE);                              

}

 

}

 

HRESULT CNet_ProgDlg::SHPathToPidl( LPCTSTR szPath, LPITEMIDLIST* ppidl )  // 여기는 클레스 이름 바꾸어야 함.

{

LPSHELLFOLDER pShellFolder = NULL;

OLECHAR wszPath[MAX_PATH] = {0};

ULONG nCharsParsed = 0;

      

HRESULT hr = SHGetDesktopFolder( &pShellFolder );

      

if( FAILED(hr) ) return FALSE;

      

MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, szPath, -1, wszPath, MAX_PATH );

hr = pShellFolder->ParseDisplayName( NULL, NULL, wszPath, &nCharsParsed, ppidl, NULL );

pShellFolder->Release();

      

return hr;

}

        : 

 

 

/*

// Browsing for directory.

#define BIF_RETURNONLYFSDIRS 0x0001 // For finding a folder to start document searching

#define BIF_DONTGOBELOWDOMAIN 0x0002 // For starting the Find Computer

#define BIF_STATUSTEXT 0x0004

#define BIF_RETURNFSANCESTORS 0x0008

#define BIF_EDITBOX 0x0010

#define BIF_VALIDATE 0x0020 // insist on valid result (or CANCEL)

 

#define BIF_BROWSEFORCOMPUTER 0x1000 // Browsing for Computers.

#define BIF_BROWSEFORPRINTER 0x2000 // Browsing for Printers

#define BIF_BROWSEINCLUDEFILES 0x4000 // Browsing for Everything

*/

-bInfo.ulFlags 플래그

 

BIF_BROWSEFORCOMPUTER : 네트워크의 컴퓨터만 선택가능

BIF_BROWSEFORPRINTER : 프린터만 선택가능

BIF_BROWSEINCLUDEFILES : 파일도 표시

BIF_DONTGOBELOWDOMAIN : 네트워크의 컴퓨터를 표시하지 않는다

BIF_EDITBOX : 에디트 박스를 표시한다

BIF_RETURNFSANCESTORS : 네트워크의 컴퓨터만 선택가능

BIF_RETURNONLYFSDIRS : 폴더만 선택가능

BIF_STATUSTEXT : 스테이터스 텍스트를 표시한다

BIF_VALIDATE : 부정 입력시에, BFFM_VALIDATEFAILED 이벤트

 

 

//Must be static funtion.
static int CALLBACK BrowseForFolder_CallbackProc(HWND m_hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
 if(uMsg == BFFM_INITIALIZED)
  SendMessage(m_hWnd, BFFM_SETSELECTION, (WPARAM)TRUE, (LPARAM)lpData);

 return 0;
}

위 함수는 꼭 static 함수여야 한다. 그리고 아래에서 함수포인터를 넣어준다.
아래 코드는 폴더 선택 다이얼로그를 띄워야 하는 곳에 넣어주면 되겠지.

 BROWSEINFO bi;
 ZeroMemory(&bi, sizeof(BROWSEINFO));
 bi.hwndOwner =  this->GetSafeHwnd();
 bi.pidlRoot = NULL;
 bi.pszDisplayName = NULL;
 bi.lpszTitle = NULL;
 bi.ulFlags = BIF_RETURNONLYFSDIRS;
 bi.lpfn = BrowseForFolder_CallbackProc;
 bi.lParam = (LPARAM)(LPCTSTR)m_strContentsDownloadFolder; //초기값 설정.

 LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
 TCHAR szPath[MAX_PATH] = {0,};  

 if( pidl )
 {
  SHGetPathFromIDList( pidl, szPath );
  m_strContentsDownloadFolder = szPath;
  GetDlgItem( IDC_DOWNLOAD_ADDRESS )->SetWindowText( m_strContentsDownloadFolder );
 }

위와 같이 하면 폴더를 선택할 수 있는 다이얼로그가 뜬다.

 

 

보통 많은 프로그램들을 보면 버튼 눌렀을때 폴더 선택할수 있게 다이얼로그를 띄워준다.

이것을 어케 하는지 알아본다.

간단하다.

 

   ITEMIDLIST *pidlBrowse;
   char pszPathname[1000];
   BROWSEINFO BrInfo;
   BrInfo.hwndOwner = GetSafeHwnd();
   BrInfo.pidlRoot = NULL;

  
   memset(&BrInfo, 0, sizeof(BrInfo));
   BrInfo.pszDisplayName = pszPathname;
   BrInfo.lpszTitle = "경로 선택";
   BrInfo.ulFlags = BIF_RETURNONLYFSDIRS;

   pidlBrowse = ::SHBrowseForFolder(&BrInfo);

   if( pidlBrowse != NULL)
   {
       ::SHGetPathFromIDList(pidlBrowse, pszPathname);
       m_strFileDownDir = pszPathname;  // 폴더 경로
   }

 

반환되는 폴더 경로는 pszPathname 에 저장된다.

 

출처 : http://blog.naver.com/gml81/40037809685

MapWindowPoints : CWnd의 좌표계로부터 다른 윈도우의 좌표계로 지정된 점들을 매핑시 킨다.
ClientToSreen : 클라이언트 좌표계를 화면 좌표계로 변환한다.
ScreenToClient : 화면 좌표계를 클라이언트 좌표계로 변환한다. 
-----------------------------------------------------------------------------

OnPrepareDC()에서

    dc.SetMapMode(MM_ISOTROPIC);

    dc.SetWindowExt(1000,1000);

    dc.SetViewportExt(2000,2000);

      CClientDC dc (this);
     OnPrepareDC (&dc);
     dc.DPtoLP (&point);
point가 마우스 좌표라면 논리 좌표로 변환해 주는 코드입니다.

 ----------------------------------------------------------------------------------

jpg 이미지가 255*255 픽셀 크기인데요...
출력을하면은 255*255 크기가 아니고 적게 출력이 되는데.
무슨 이유입니까??

좌표계가 MM_TEXT로 설정되어 있어서 그럴겁니다

 MM_TEXT 좌표계는 하나의 픽셀이 기준이 됩니다
질문하셨다 시피 jpg 이미지가 255*255 크기라면   
좀 픽셀이 큰 모니터에서는 이미지가 더 크게 나오겠죠?

출력을 할때도 마찬가지로
프린터 장치의 dpi라고 하죠 1인치 사각형안에 얼마나 많은 점들을 찍을수 있느냐 하는 건데
(보통 300dpi 뭐 이런 얘기 많이 들어보셨을 겁니다)
이 프린터의 dpi에 따라 이미지 사진이 더 크게나올수도 있고 작게나올수도 있는겁니다

왜냐하면 기준이 픽셀이기 때문이죠 

그럼 화면과 출력물의 크기가 같아지기 위해서는 어떻게 해야하느냐 하면
MM_LOMETRIC같은 좌표계를 쓰시는 겁니다

MM_LOMETRIC은 자표계 기준이 0.1미리 입니다
따라서 MM_LOMETRIC좌표계 기준으로 1000*1000의 그림은
화면에서나 출력에서나 10cm * 10cm로 동일하게 출력되는 것이죠

쉽게 말씀드리면

 SetMapMode() 함수를 이용해서 좌표계를 바꿔주라는 이야기 입니다 ^^
SetMapMode(hPrinterDC, MM_LOMETRIC); // 이런식으로요

물론 출력하실때 좌표계가 바뀌었으니 출력될 좌표는 신경쓰셔야 겠죠 ^^
참고로 MM_LOMETRIC은 MM_TEXT와는 달리

y축이 아래로 가면 - 값 입니다

덧붙이자면

BOOL DPtoLP() // 디바이스 좌표를 로직좌표계로 바꿔주는 함수와 그 반대 함수들이 존재하니 참고 하시구요

 -----------------------------------------------------------------------

 DPtoLP는 함수 이름 대로 디바이스 좌표를 논리적 좌표로 변경해주는 함수입니다...
 디바이스 좌표는 화면의 한 Pixel, 프린터의 한 Dot를 뜻합니다.
논리적 좌표는 논리적으로 단위를 정한 좌표입니다.(예로 10cm단위로 1, 2, 3...)
 일반적으로 GetDC()를 하게 되면 리턴되는 DC는 MM_TEXT 모드가 됩니다.
MM_TEXT는 디바이스 좌표와 논리 좌표가 1:1 대응(화면의 한 점이 1이 됩니다.)됩니다.
결국 DPtoLP를 해도 값의 변화가 없겠죠...

하지만 map mode를 MM_HIMETRIC으로 변경했다고 가정하면 논리적 좌표 1은
0.01mm를 나타내기 때문에 화면의 한 pixel은 화면 DPI에 따라 1이 아닌 다른 논리적 값이 됩니다...

[출처] [MFC] 좌표계|작성자 영희

출처 : http://tong.nate.com/gayanim/27925620

GetWindowRect
윈도우의 현재 위치와 크기를 구해준다. (left, top)은 윈도우의 현재 좌상단 위치를 나타내는데 이 좌표는 전체 화면을 기준으로 한 좌표이다. (right, bottom)은 윈도우의 우하단 위치를 나타내며 역시 전체 화면을 기준으로 한 좌표이다. 윈도우의 현재 크기(폭과 높이)를 구하고 싶으면 right-left, bottom-top을 계산하면 된다.

GetClientRect
윈도우의 작업영역 크기를 계산해 준다. 크기만 계산해 주기 때문에 좌상단(left, top)값은 항상 0,0이며 우하단 좌표(right, bottom)가 곧 윈도우의 크기를 나타낸다. 작업영역이란 윈도우의 타이틀바, 스크롤 바, 경계선, 메뉴 등을 제외한 영역이며 윈도우가 그리기를 하는 대상 영역이다

 

ScreenToClient
화면의 원점을 기준으로 하는 좌표 lpPoint를 hWnd의 작업 영역을 기준으로 하는 좌표로 변환한다. hWnd윈도우의 작업 영역 원점의 화면 좌표가 cx, cy일 때 lpPoint는 lpPoint.x - cx, lpPoint - cy로 변환된다. GetCursorPos, MoveWindow, GetWindowRect 등과 같이 화면 좌표를 리턴하는 함수로부터 작업 영역의 좌표로 변환하고자 할 때 이 함수를 사용한다.

리스트뷰 컨트롤의 추가, 삭제, 수정, 검색 등

아주 기초적인 사용법에 대해 적어보고자 합니다.




이런식으로 전체적인 레이아웃을 잡고 시작한다고 봐야겠죠?

그리고 중요한 컨트롤의 컨트롤의 ID 의 값을 각각 아래와 같이 정하고 시작하겠습니다.




각각의 컨트롤에 대해서는 WM_COMMAND 메시지를 다루면 되겠죠.



추가



추가의 경우에는 지난번에 했던 방식 그대로

ListView_InsertItem 과 ListView_SetItemText 매크로를 이용하면 됩니다.


   li.mask=LVIF_TEXT;
   li.state=0;
   li.stateMask=0;
   
   idx=ListView_GetItemCount(hList);

   GetDlgItemText(hWnd,IDC_NAME,szName,255);
   GetDlgItemText(hWnd,IDC_SEX,szSex,255);
   GetDlgItemText(hWnd,IDC_ADDR,szAddr,255);


   li.iItem=idx;
   li.iSubItem=0;
   li.pszText=szName;


   ListView_InsertItem(hList,&li);
   ListView_SetItemText(hList,idx,1,szSex);
   ListView_SetItemText(hList,idx,2,szAddr);



그나마 지난번과 달라진건 컨트롤의 값을 읽어오는 GetDlgItemText 와

리스트 뷰의 아이템들의 수를 읽는 ListView_GetItemCount 가 추가된 것 밖에 없겠군요 ^^


int ListView_GetItemCount(HWND hWnd)


이게 원형입니다. sizeof 와 비슷하다고 보면 되겠군요.



삭제


삭제를 하기 위해서 사용되는 매크로는 아래와 같습니다.


int ListView_GetNextItem(HWND hWnd, int iStart, UINT flags);

BOOL ListView_DeleteItem(HWND hWnd, int iItem)


ListView_GetNextItem 의 첫번째 인수는 당연히 리스트의 핸들이고,

두번째는 어디서부터 찾을 것인가를 정하는 것입니다. -1 의 경우는

전체에서 찾는 것이고 주어지는 값에 따라 그 인덱스부터 차례로 찾아가는 것이죠.


리턴값이 int 형인데 이건 찾은 인덱스 값을 리턴합니다 ^^ 몇 번째 존재하는지를 리턴하죠.

세번째 인수는 Flags 값을 지정하는 것인데 LVNI_ALL | LVNI_SELECTED 를 보통 지정합니다.

왜 그런가 싶으면 LVNI_ALL 이나 LVNI_SELECTED 를 하나씩 삭제하여 컴파일해보면 알겠죠?


idx=ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED);


이 경우는 만약 찾았을 경우에는 idx 의 값에는 해당 인덱스가 들어가겠지만,

찾기 실패하였을 경우에는 idx 에 -1 값이 들어갑니다.



   idx=ListView_GetNextItem(hList,-1,LVNI_ALL|LVNI_SELECTED);
   if(idx==-1)
        MessageBox(hWnd,"삭제할 아이템을 선택하세요","Error",MB_OK);
   else
   {
        while(idx!=-1)
        {
             ListView_DeleteItem(hList,idx);
             idx=ListView_GetNextItem(hList,idx-1,LVNI_ALL|LVNI_SELECTED);
        }
   }



여기서 중요한 것은 while 문의 ListView_GetNextItem 에 idx-1 이 들어간다는 것이죠.

선택된 아이템들을 하나씩 찾아서 지워주는 것이기 때문에 idx 보다 -1 된 것을 넣어야

차례로 지워나갑니다. idx  를 그대로 넣어준다면 무슨 일이 일어나는지는 직접 해보시면 압니다.


선택된 아이템들이 전부 삭제가 되지 않겠죠;



수정


수정은 추가와 비슷하여 상당히 쉽습니다. 다만 ListView_InsertItem 매크로 대신

ListView_SetItem 을 통하여 재설정을 해줘야 합니다. 아주 조금 추가와 다를 뿐,

기본 방식은 같습니다.



   idx=ListView_GetNextItem(hList,-1,LVNI_ALL|LVNI_SELECTED);

   if(idx==-1)
        MessageBox(hWnd,"수정할 아이템을 선택하세요","Error",MB_OK);
   else
   {
        GetDlgItemText(hWnd,IDC_NAME,szName,255);
        GetDlgItemText(hWnd,IDC_SEX,szSex,255);
        GetDlgItemText(hWnd,IDC_ADDR,szAddr,255);


        li.iItem=idx;
        li.iSubItem=0;
   
        ListView_SetItem(hList,&li);
        ListView_SetItemText(hList,idx,0,szName);
        ListView_SetItemText(hList,idx,1,szSex);
        ListView_SetItemText(hList,idx,2,szAddr);
   }



보시면 아시겠지만 일단 선택된 것을 찾아서 idx 에 그 인덱스 값을 넣어줍니다.

만약 선택되지 않았다면 Error 메시지박스가 뜨겠죠. 이제 LVITEM 구조체인

li 의 iItem 에 idx 값을 지정하고 iSubItem 멤버에도 첫번째 컬럼을 의미하는 0을 지정한 뒤


ListView_SetItem 으로 li 를 hList 에 설정합니다.

일단 내가 몇 번째 리스트 뷰 아이템을 설정한다고 알려주는 것이죠.


그런 뒤 ListView_SetItemText 매크로를 통하여 수정해주면 됩니다 ^^



검색



다행히 검색을 할 때도 편리하게(?) 매크로가 존재합니다.


int ListView_FindItem(HWND hWnd, int iStart, const LPLVFINDINFO plvfi);


여기서 중요한건 LVFINFO 구조체가 들어가는데요, 이건 검색의 조건으로


typedef struct tagLVFINDINFO {
    UINT flags;
    LPCTSTR psz;
    LPARAM lParam;
    POINT pt;
    UINT vkDirection;
} LVFINDINFO, *LPFINDINFO;


flags 멤버로 중요한건


LVFI_STRING    psz 멤버가 지정하는 문자열과 일치하는 텍스트를 가진 항목을 검색

LVFIN_WRAP    검색 대상이 발견되지 않을 경우 처음부터 다시 검색


이 두 개 정도로만 봐도 가장 기초적인 곳에서는 사용할 수 있습니다.

그리고 vkDirection 이라는 특이한 멤버가 존재하는데요,

이건 VK_LEFT , VK_DOWN, VK_HOME 등 가상 키를 넣어주는겁니다.


대게 VK_DOWN 을 설정하는 편이라는군요.



   LVFINDINFO fi;

   GetDlgItemText(hWnd,IDC_NAME,szName,255);


   fi.flags=LVFI_STRING;
   fi.psz=szName;
   fi.vkDirection=VK_DOWN;

   idx=ListView_FindItem(hList,-1,&fi);


   if(idx==-1)
        MessageBox(hWnd,"검색된 아이템이 없습니다","Error",MB_OK);
   else
   {
        ListView_SetItemState(hList,-1,0,LVIS_FOCUSED|LVIS_SELECTED);
        ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);
        ListView_EnsureVisible(hList,idx,FALSE);
   }



이렇게 IDC_NAME 의 값을 읽어와 psz 에 설정한 후 ListView_FindItem 매크로에서 찾아주면 되네요.

ListView_FindItem 에서 -1 은 가장 처음에서 찾는, 전체에서 찾는다는 의미죠. 같은 문자열로 찾기를

계속 이어주려면 idx-1 값으로 루프를 돌려주면서 찾으면 되겠군요 ^^


찾았을 경우 ListView_SetItemState 로 상태를 설정해줘야하는데요.


ListView_SetItemState(hList,-1, 0,LVIS_FOCUSED|LVIS_SELECTED);

ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);


이 두개가 왜 존재할까요? -1은 전체를 의미하고, 0은 상태를 없앤다고 보면 되죠.

일단 리스트 뷰에 어떤 항목들이 선택되어있거나 한다면 리플래시를 시켜 없앱니다.


그게 바로 ListView_SetItemState(hList,-1, 0,LVIS_FOCUSED|LVIS_SELECTED) 입니다.

그리고 나서 찾은 idx 번째의 리스트 뷰의 아이템을 선택해야하는데요, 그게 바로 두 번째인


ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);


입니다. 마지막 인수가 16진수로 되어있지만 이것도 역시 아래와 같이 바꿀 수 있습니다.


ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);


여기까지가 가장 기초적인(?) 방법이었네요.

사실상 개인적으로 시간이 많이 먹었던 부분이예요.


좀더 자세한건 소스 첨부 했으니 참고하시면 되구요 ^^;

다음엔 WM_NOTIFY 에 해당되는 리스트 뷰의 메시지 관리를 적어보겠습니다.

출처 : http://cafe.naver.com/seen/131

출처 : http://blog.naver.com/epicros/10018002323

MFC 클래스간 통신

SDI 형태
1.     MainFrame 얻기
-          CMainFrame *pFrame = (CmainFrame *) AfxGetMainWnd();

2.     App 포인터 얻기
-          CTestApp *pApp = (CtestApp *) AfxGetApp();

3.     Document 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
    CTestDoc *pDoc = (CTestDoc *)pFrame->GetActiveDocument();
-          CTestDoc *pDoc = ((CMainFrame *)AfxGetMainWnd())->GetActiveDocument();

4.     View 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
     CTestView *pView = (CTestView *)pFrame->GetActiveView();
-          CTestView *pView = ((CMainFrame *)AfxGetMainWnd())->GetActiveView();


MDI 형태
1.     ChildFrame 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
    CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
-          CChildFrame *pChild = ((CMainFrame *)AfxGetMainWnd())->GetActiveFrame();

2.     Document 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame)AfxGetMainWnd();
    CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
    CMdiTestDoc *pDoc = (CMdiTestDoc *)pChild->GetActiveDocument();
-          CMdiTestDoc *pDoc = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveDocument();

3.     View 포인터 얻기
-          CCainFrame *pFrame = (CMainFrame)AfxGetMainWnd();
    CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
    CMdiTestView *pView = (CMdiTestDoc *)pChild->GetActiveView();
-          CMdiTestView *pView = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveView();


출처 - 뿌리깊은 나무

+ Recent posts